OpenMDAO Logo

An open-source framework for efficient multidisciplinary optimization.

November 5, 2020
by Justin Gray
Comments Off on RevHack2020 was Awesome!

RevHack2020 was Awesome!

While the Dev team is putting the finishing touches on OpenMDAO RevHack2020, I wanted give a wrap-up summary.

First, let say thank you to everyone who submitted problems for us to work on. Thanks to Remi Lafage, Adam Chase, John Jasa, Anil Yildirim. Extra special thanks for Shamsheer Chauhan for submitting 4 different problems! Our goal for RevHack 2020 was to develop a better understanding of your perspective and we simply couldn’t have done that without your help.

Check out the cool stuff we did:

A new perspective on the “right way” to use OpenMDAO

We used RevHack2020 as a chance to step outside the normal development process and examine things from a new perspective. That started with trying to really understand what was the core of a particular user struggle, but we took that further, asking the following:

Is there a better way to do this?
What is the value of OpenMDAO for this problem?
Are we really making this easier than it would be without OpenMDAO?

Here are some of the semi-philosophical answers that I came up with.

If you don’t want to use analytic derivatives, is OpenMDAO worth it?

As the leader of the OpenMDAO project, it is a little scary to ask myself this question. I know that it is extremely powerful and effective for our work at NASA. We couldn’t write tools like pyCycle or Dymos without it but we leverage the derivatives capabilities heavily.

OpenMDAO enforces some specific coding structures on you, and those are derived from the goals of modularity and support for analytic derivatives. Sometimes those are getting users way and if you are not getting the benefits of the derivatives, I can see how frustrating that would be.

So I have come to two conclusions:

1) If you are not planning to ever use analytic derivatives, then the case for OpenMDAO is much weaker. If you do want to have a path to using them, OpenMDAO is worth the effort.

2) We need to better job of making OpenMDAO easier to use. That means making bigger components, supporting algorithmic differentiation, and giving clear examples of how to leverage sub-problems when you want to do things like for-loops, use derivatives as outputs in a model, and sub-optimizations.

Maybe we don’t need drivers at all

I was personally shocked when thinking about CMA-ES problem brought me to the conclusion that the whole Driver interface in OpenMDAO is non critical. I still think it has a lot of value, but its non-critical because you could get all of the value from OpenMDAO without it just using the apis on the problem itself.

There is a lot of code in our Driver interfaces, and I can see that users find it intimidating to figure out how to navigate that. That code is there because it allows the developers to switch between the multiple optimization libraries that we use. If you want to use a single specific library though, you don’t need any of that.

I am not going to rip the Driver interface out of OpenMDAO, because its useful to the development team and I don’t want to break models of anyone who uses it. However, I am explicitly saying that you should feel free to disregard it and roll your own interface to the optimizer. Here is a more in depth discussion of this topic

If you build a massively complex optimization, expect it to be hard to solve!

One thing that OpenMDAO has done is make it easier to build bigger, more complex optimization problems. I can see that a lot of users are still struggling, and I acknowledge that we have more work to do. However, I have also seen users build models that are intensely complex, and set up massive optimization problems that are fundamentally hard to solve.

So even if it’s hard to do, I argue that there are definitely classes of problems that you would struggle to solve without OpenMDAO. Ironically, since OpenMDAO is making it easier to build these hard problems, more users seem to be running into the fact that hard problems are … hard to solve (shocking, I know!)

In the end, this is a good problem to have. If we really are to the point where you can build a problem that is so complex you can’t figure out how to optimize it then OpenMDAO has contributed something to the collective community. Now one of our next challenges is to give you some tools that make these hard problems a little less hard.

October 27, 2020
by Justin Gray
Comments Off on What to do when OpenMDAO Deprecates A Component?

What to do when OpenMDAO Deprecates A Component?

Working on the RevHack2020 effort, I wanted to run the eVTOL trajectory optimization provided by Shamsheer Chauhan. This code was written for OpenMDAO V2, and I had V3 installed on my machine. While there were no API backwards incompatibilities in V3 that I ran into, the code did use the BSplineComp from the V2.10 standard library which doesn’t exist in V3.

So I couldn’t run the model in my V3 install. I could have installed V2.10 as a separate conda environment, but that seems like overkill. Instead, here are two better options. Both take less than 5 minutes total time:

Fix 1: Update to the latest APIS

We provide an upgrade guide from 2.10 to 3.0, in which there is an example of how to update to SplineComp (V2.10 is in red, V3 is in green).

You can see that it wouldn’t take major changes to get it updated, and the results should be identical but if you would rather not hack on the code at all… maybe you just want to run it exactly as is.

Fix 2: Pull the old component into your model

We deprecated the older BsplineComp, in favor of the newer SplineComp because the newer one has a more consistent API and offers more features. Still, you might be totally happy with the BsplineComp, or perhaps in some other situation there is some missing functionality in the new component thats a problem for you. You just want to use the old component!

One of the best things about using an open source project is that … you have access to the source! You can go back to the last V2 release (V2.10.1) and grab the component code there, then pull it into your own repo.

First go to the tags at the top of the OM repo

Next pick the specific version you want to grab code from, by clicking on the commit hash for that version.

Next follow the “Browse Files” link to get to the code

Then you can navigate the repo, go get the code you want and pull it into your own project. The upside is that you now don’t have to change your code (except to change where you imported the component from). The downside is that you now how to maintain this component yourself if you need any changes in the future.

October 25, 2020
by Justin Gray
Comments Off on RevHack 2020 — Halfway there

RevHack 2020 — Halfway there

We are halfway through the OpenMDAO 2020 Reverse Hackathon, so it is time to give all you users an update on what we’ve learned so far and what to expect for the last week.

What we have learned so far

From all of the problems submitted, we identified three solution approaches that were broadly relevant:

I will be adding more detailed write ups of these topics into the repository for RevHack 2020, but I want to cover one over-arching idea that is common across all of them: OpenMDAO does not have to be in charge, nor does it have to be the interface you present to your users

Though the dev team tends to let OpenMDAO be in charge, that doesn’t mean that you have to. In fact, it’s possible that for some applications adding a different layer on top that provides a simpler and more standardized interface for your users is the better solution. You’ll see a lot of our answers to the problems posed for RevHack2020 are going to be posed this way — not all, but many.

We have some good features to support this kind of use case, even some specifically for analytic derivative situations. Fairly recently we added a feature that allows you to propagate derivatives across an entire problem in an efficient manner using a for-loop around a problem.

Moving forward, I think we’ll try to emphasize this use-case more. The native OpenMDAO way isn’t the solution to every single problem. More importantly I am trying to see the world through your (the users) perspective, and would like to find ways to map OpenMDAO use the common — some might argue more natural — types of coding patterns.

What to expect for the second week of RevHack2020

We have started work on solutions for every problem. Most of our effort in the first week went into figuring out what needed to be done and how best to do it. Now that we have had a chance to make some progress, we’ll be posting actual solutions to the repo soon, along with some detailed explanation of why we did things that way. (there are some solutions up already for the CMA-ES problem).

I am putting a lot of effort into seeing the world from your perspective, and the solutions we post will reflect that. As we see them, please feel free to post your own PR’s to modify/question our solutions. The more feedback we get, the better we can tailor things to be useful to you.

October 14, 2020
by Justin Gray
Comments Off on 3 Tools for MDAO Success

3 Tools for MDAO Success

Multidisciplinary Design Analysis and Optimization … MDAO

Practitioners of MDAO must develop skills in three separate, but closely related, areas: nonlinear optimization, model construction, and model differentiation. An undergraduate STEM education will likely have given given you a brief introduction to all three, and this is enough to get most people started with MDAO.

As your models grow more complex you start to hit walls because your model won’t converge, the optimizer can’t find a “good” answer, the compute cost grows to high, etc. We’ve built OpenMDAO to help alleviate many of these common problems, but it has become a bit of an arms race between the development team and the users. As we add features to solve existing common challenges, our users tend to add model complexity till they run into new ones. I don’t (yet) have a solution to end the arms race but I want to draw a clearer picture of the battle field.

So let’s take a closer look at the three areas I have deemed the keys to success in MDAO. I obviously can’t cover everything, but even just understanding the classification can help you narrow down your problem area and and enable a more focused search for solutions.

Nonlinear Optimization

Do you know what “optimization” is? Some have joked that it’s an automatic process to find all the bugs, holes, and weaknesses in your model… if you found yourself wincing at that jab, it’s because you know there is more than a little truth to that 🙁

A more mathematical perspective on “optimization” is as a means of converting a underdefined problem with an infinite number of solutions (more degrees of freedom than equations to constrain them) into a well defined one with a single solution (equal number of degrees of freedom and equations). This conversion is done by using the objective function (usually represented by “f”) to say which solution you prefer. Sometimes there are also constraints (represented by “g” and “h”) as well, which help to further constrain the solution space.

There are lots of different kinds of optimizers, some that use derivatives and some that don’t. Optimizers see your model as a black box that maps design variables to objective and constraint values (and sometimes the associated derivatives). The optimizer doesn’t care how you accomplish that mapping, as long as you give it the data it needs.

A lot of optimization challenges occur because you have picked a bad objective function, a bad constraint, or had too many constraints. I find it useful to think of the black-box picture when trying to diagnose these kind of problem-formulation issues. You can ignore all of the internal workings of your model; assume/pretend/wish/fantasize) that it will work perfectly for any given inputs. Then think about how you expect the outputs to react to the inputs, and look for holes there.

OpenMDAO, despite the “O” in the name, doesn’t actually address the optimization area that much. We have a driver interface that provides access to a few of optimizers. Our interface is a useful abstraction that makes it easier to swap between optimizers, but it does not provide the optimization algorithms itself. If you already have a code that provides function evaluations (and derivatives) of everything you care about, then you might actually be better off interfacing directly with some of the optimizer libraries themselves.

Model Construction

The black box perspective says f=F(x). Model construction is everything that has to go into making those six characters actually compute f, for any reasonable value of x. There are two fundamental things you have to deal with when doing model construction: data passing and convergence.

Data passing is usually fairly strait forward. If you’re writing code, it can be as simple as passing the return values of one function as the arguments to the next. Some models use file i/o to pass complex data formats around. Generally speaking data passing is something you probably don’t think about a lot, but depending on what you’re doing it can be pretty important. Thats especially true in any kind of parallel memory situation, or if you have to work on geographically distributed computing and across firewalls. OpenMDAO uses a pretty fancy scheme based on the MAUD architecture that works well both in serial and distributed situations.

With regard to convergence, there is a whole spectrum from trivial to insanely hard challenges that you may encounter. Some models are composed of explicit functions arranged in a purely feed forward sequence. If your model looks like this, I’m jealous! Most of the time though, you end up with some implicit functions in the model. Maybe one of the boxes is implicit (i.e. there is a nonlinear solver inside it), or you have a cycle in the data flow between boxes that requires a top level solver. Often you have both implicit boxes and cyclic data flows… thats when things really start to get interesting. Making models sufficiently stable, so that for any value of “x” you can converge on a value of “f” is critically important. I promise you that if there is some area where your solvers don’t converge, an optimizer is going to find it!

Often times you have very complex graphs of data-flow with lots of different cycles (and cycles within cycles). These problems are where OpenMDAO’s unique hierarchical model structure starts to provide real advantages.

I used the term “nonlinear solver” which you may or may not be familiar with. I am quite certain you’ve used one before. Perhaps you’ve written a while loop to iterate till some error term gets below a tolerance? Thats called a fixed-point solver, or alternatively a nonlinear Gauss-Seidel solver. There is the classic Newton’s method, and a bunch of variations that are very similar — scipy has a nice collection to check out. Of course, OpenMDAO has a good selection too.

Model Differentiation

Not every MDAO application demands derivatives, but they are usually needed whenever you have expensive function evaluations, large numbers of design variables, or require very tight convergence. Many MDAO practitioners avoid this topic area. It’s understandable, since it’s both challenging and potentially time consuming. But derivatives offer many orders of magnitude computational speedup and greatly improved numerical stability, so they are well worth considering!

This picture looks a lot like he one for model construction. That is intentional. One way to think of this whole process is that you could replace every single box in the model with its local linearized representation then finite-difference over the whole model to compute the total derivatives. Thats not a particularly efficient or simple implementation, but its a nice conceptual context to understand whats needed.

There are lots of ways to compute derivatives. There are numerical approaches such as finite-difference and complex-step methods. You likely learned about manual differentiation techniques based on the chain-rule. You may have also used symbolic differentiation (e.g. sympy) when you found it tedious to manually work through all the equations. Some modern scientific computing languages have an advanced form of symbolic differentiation called algorithmic differentiation which works on computer code directly (rather than having to type it in to the symbolic engine and convert back to code). These computational tools have their roots in the 1960’s when particle physicists were struggling with massive volumes of equations. The 1999 Nobel Prize in physics went to two scientists who pioneered the computer algebra techniques for their work!

All of these techniques work fairly well when you you have feed-forward models of explicit functions. Whenever you have implicit functions, or any kind of cycles in your data passing then the chain rule (and the blunt application of symbolic or algorithmic differentiation) start to break down. More complex techniques like adjoint derivatives, direct derivatives, or the unified derivative equations become valuable. These topics are some of the least widely known amongst MDAO practitioners, and they are also where OpenMDAO offers the most advanced and unique features. OpenMDAO lets you take advantage of the unified derivative equations without actually having to understand them at all!

October 2, 2020
by Justin Gray
Comments Off on OpenMDAO V3.4.0

OpenMDAO V3.4.0

OpenMDAO V3.4.0 has been released. Check out the full list of changes in the release notes. One change nice new feature is “noisy warnings” if AnalysisError is raised inside an optimization loop. Many optimizers can catch these errors and recover by backtracking on the line search, but previously this all happened silently. Now you’ll see some feedback on what went wrong in standard output. You can use that information to find the problem and potentially alleviate it for future runs.

I also want to highlight the new experimental feature that lets you size inputs based on their connection. The docs are a little bare at the moment, but we’re working on updating them. The gist of this though is that you can now have variables in components that are sized by the thing they are connected to!

As you can imagine, this is a pretty powerful new feature but it also has a lot of potential to cause confusion. So we’re marking it as experimental and we’re going to see how the user community likes it! We welcome all feedback here.

The credit for this feature goes to Josh Anibal from the University of Michigan MDO Lab. He submitted POEM 22 to propose the idea. Then he (unexpectedly) provided us with a prototype implementation in OpenMDAO. Josh’s prototype was absolutely critical in making this feature happen. The dev team didn’t see the same vision that he did, and we couldn’t get past some implementation challenges that he saw through clearly. We were about to reject the POEM, till we saw his solution.

One of the fundamental goals of the OpenMDAO project is to help spread innovations from MDO researchers to the user community more quickly. For me personally, Josh’s contributions are a major milestone towards achieving that goal. He’s definitely not the first person to make code contributions to OpenMDAO, but he’s the first to provide a fundamental change to the setup stack which allows users to build models differently.

Anyone who uses this feature has Josh to thank, and I look forward future contributions by him and others. One day, I hope we’ll have a huge list of people to thank!

September 4, 2020
by Justin Gray
Comments Off on OpenMDAO V3.3.0 released

OpenMDAO V3.3.0 released

OpenMDAO V3.3.0 is live. You can read the release notes for full details, but the major new feature in this release is the ability to query for input/output metadata from within the configure method. This introspection feature allows you to build much more reactive groups that can update themselves based on data from their children. For example, you can look at the size of an output in one component, then create tell some other component to create an input of the same size.

With this update, you now have a lot of flexibility in the way you build models. Groups now have two key methods that are used in model building setup and configure. setup is a top-down recursion where you can construct your model hierarchy. configure is a bottom-up recursion where you can inspect your children and then make changes to the I/O of other children accordingly.

We’ll be making good use of this new feature in a new version of Dymos in the very near future! We hope you find the feature useful too.

September 1, 2020
by Justin Gray
Comments Off on New Lecture: Sparse Derivatives for Vectorized Outputs

New Lecture: Sparse Derivatives for Vectorized Outputs

I’ve posted a new lecture that provides an introduction to sparse partial derivatives. This lecture uses a simple beam moment calculation, with vector inputs and outputs with fairly sparse partial derivatives.

The derivatives are a little tricky to get right, and I walk through how the code structure should look and ways to debug as you go.

September 1, 2020
by Justin Gray
Comments Off on 2020 OpenMDAO Reverse Hackathon

2020 OpenMDAO Reverse Hackathon

The 2020 OpenMDAO workshop will be a reverse hackathon, that will be held October 19 – 30. This will be a virtual, asynchronous event, with no cost to anyone who wants to participate.

Check out the github repo for the event, to submit your ideas

Our goal for this event is to spur the adoption of the most advanced features in OpenMDAO by the broader user community, so we are explicitly offering to help you upgrade your models!

What is a reverse hackathon?

In a normal hackathon, the organizers pose the problems and the participants solve them. We’re flipping that around.

You pose challenge problems to the OpenMDAO dev team. We’ll solve as many of them as we can for you! On top of that, we’ll give a write-up on our solution with important details about what we did and why.

While we’re in the middle of solving your problems, we’ll likely have lots of questions. So we expect there to be lots of discussion back and forth.

Critical Dates:

  • Initial Project Submissions Due: October 1st, 2020
  • Final Project Submissions Due: October 19th, 2020
  • RevHack 2020 Presentations: October 30th, 2020

Besides the due dates for project submissions, the other date to note is the 30th. That’s when we’ll be posting some talks on important topics for the community, including a summary of the most important new features developed in 2020 and the 2021 development roadmap.

How will the devs pick which ones to work on?

It is critical that your problem is scoped so it can be tackled within the two week window. It can certainly be shorter than 2 weeks (we’re totally fine solving smaller problems!) but it can’t be longer than that. That’s not a lot of time, so we’re stipulating that we won’t add any new features to OpenMDAO. You must submit a problem that can be solved with current features!

We’re also asking for a well defined request such as:

  • Convert the provided code to an OpenMDAO component/model
  • Differentiate the given component
  • Profile and speed up a given model
  • Stabilize an optimization problem so it finds a solution more robustly
  • Generate a data-post processing script (e.g. plots Y vs X over the entire iteration history) for a model that is being executed in a strange way

If you have an interesting problem that you would like to work on, but you’re not totally sure what the best request to make would be, that’s ok! You can submit a less well defined idea and we’ll try to work with you to narrow it down a bit.

Here are some suggestions to get you started here are a few broad ideas:

  • Pushing the boundaries: a problem where the solution will extend the capabilities of OpenMDAO by creating a new technique for using the framework
  • Software design: finding the best way to integrate a modular analysis into OpenMDAO or convert a non OpenMDAO analysis into an OpenMDAO component
  • Derivatives: Differentiating a tricky component, or speeding up the performance of your existing derivatives implementation with advanced coloring features.
  • Performance: Identifying bottlenecks and speeding up code that is already implemented in OpenMDAO
  • Numerical Stability: Improving the robustness of a nonlinear solve or optimization so that it gets a converged answer more frequently

Problem Requirements:

Open Source: The number one most important requirement is that all the code associated with the problem needs to be open source. That includes any and all dependencies that your code has. It’s ok if you need a few external libraries as long as they are open source!

Concise: Please keep your code as short as possible. We suggest a soft limit of 1500 lines of code (excluding external dependencies). We won’t automatically reject the problem if there is too many lines, but we’ll be hesitant. We’re asking you to put some effort into crafting a concise chunk of code for us to work with and others can relatively easily digest.

Tests: Your submission should include some tests that show us how to run your code and what the expected answer should be (even if its not currently getting the right answer).

How do you submit a problem?

You submit a pull request to the RevHack2020 repository. Add a new folder, and in it include whatever code is needed along with a readme file that describes the code and the specific, well defined request you have.

It’s ok if your initial pull request isn’t fully baked. The code doesn’t need to be there at first. You could have only a readme that spells out what you’re thinking. The devs will review your PR and respond, asking questions or making suggestions.

We’ll we’re likely to pick your project to work on, or if we think your proposal is too narrowly focused and won’t offer enough value to the broad community. Preparing a good problem that is concise, but has the details necessary will takes time. We’re happy to give you a strong indication of whether your time will be well spent or not.

Want to do some of the hacking yourself?

Think you have what it takes to tackle one of the submitted problems yourself? You’re more than welcome to! You can comment on the PRs with the proposed problems just like the devs will. The more the merrier!

August 25, 2020
by Justin Gray
Comments Off on New OpenMDAO Mailing List

New OpenMDAO Mailing List

SUBSCRIBE HERE

We’re starting a mailing list for announcements by the dev team. It will not be too noisy, but you can expect to hear from us whenever we make a new release, post a new POEM, or want to invite you to an event. If you want to stay up to date with the latest OpenMDAO news, go here to subscribe.

We’ll be making an announcement about a virtual OpenMDAO community event in the near future, so subscribe to the mailing list to make sure you don’t miss out!

SUBSCRIBE HERE

July 22, 2020
by Justin Gray
Comments Off on OpenMDAO V3.2 is live and it’s a big deal!

OpenMDAO V3.2 is live and it’s a big deal!

OpenMDAO 3.2 is live. You can read the details in the release notes, but I want to specifically highlight something that all current users will notice. You no longer need to manually create any IndepVarComps.

Following POEM 015, we’ve implemented an automatic IndepVarComp feature that means in the vast majority of use cases you do not need to manually create them.

The IndepVarComp was always a bit awkward from a user perspective, but due to some of the details in the math that powers OpenMDAO it is necessary that every single variable have an ultimate source associated with it. The IndepVarComps served as that source for the independent variables in your models.

Since they were necessary, we grew to accept this idiosyncrasy of OpenMDAO syntax. However, recently a series of other updates to setup stack (POEM 003 and POEM 009) made it possible to create I/O in components much later in the setup process. These improvements were not made for the purposes of eliminating manually created IndepVarComps, but they were crucial to being able to add this new functionality.

We’ve made the new feature highly backwards compatible, so your old models should still work with the manually created IndepVarComps in place. For that matter, if you so choose you are still free to create them wherever you like. There are a few corner cases where backwards compatibility was not possible, and if you run into anything that the devs haven’t foreseen please don’t hesitate to reach out!

Upgrade Guide

To help you adopt the new coding style, we’ve put together an upgrade guide for you. This covers all the common cases that we could think of. We’ve also updated all of the docs to use the new auto-ivc capability in all the examples.

There is one use case for the new functionality that I am most excited about. You can leverage the new apis to create pass-through style variables in your groups. The variable name can either be used directly as a design variable or be connected into from some other source.

Here is a contrived example that demonstrates the basic concept. In the first use case, “g0.x” is used directly as a design variable. In the second use case we connect “upstream.x” to “g0.x”. Note that there is no IndepVarComp anywhere in these examples.

import openmdao.api as om

class FancyPassthroughGroup(om.Group): 

    def setup(self): 

        self.add_subsystem('comp0', om.ExecComp('y0 = 3*x', 
                                                 x={'units':'ft'}, 
                                                 y0={'units':'ft'}), 
                           promotes=['*'])        
        self.add_subsystem('comp1', om.ExecComp('y1 = x**2', 
                                                x={'units':'m'}, 
                                                y1={'units':'m'}), 
                           promotes=['*'])

        self.set_input_defaults('x', units='cm', val=3.0)


#############################################################
# First usage of the fancy group is to set up a direct 
# optimization of the group: minimize y1 w.r.t x
#############################################################
prob0 = om.Problem()
prob0.model.add_subsystem('g0', FancyPassthroughGroup())
# note: this design variable will be in 
#       units of `cm` following the input default
prob0.model.add_design_var('g0.x', lower=-10, upper=10) 
prob0.model.add_objective('g0.y1')

prob0.driver = om.ScipyOptimizeDriver()

prob0.setup()

# this will find x=0 to minimize y1=x**2
prob0.run_driver()
print('x: ', prob0.get_val('g0.x')) 
print('y1: ', prob0.get_val('g0.y1')) 


#############################################################
# Second usage of the fancy group is connect add a modifier 
# upstream and connect that into x, then optimize the w.r.t 
# the new variable
#############################################################
prob1 = om.Problem()
prob1.model.add_subsystem('upstream', om.ExecComp('x = x_prime + 3',
                                                  x_prime={'units':'inch'}, 
                                                  x={'units':'inch'}))
prob1.model.add_subsystem('g0', FancyPassthroughGroup())
prob1.model.connect('upstream.x', 'g0.x')
# note: this design variable will be in 
#       units of `inch` following units on the upstream input
prob1.model.add_design_var('upstream.x_prime', lower=-10, upper=10) 
prob1.model.add_objective('g0.y1')

prob1.driver = om.ScipyOptimizeDriver()
prob1.driver.options['tol'] = 1e-6

prob1.setup()

# this will find x=0 to minimize y1=x**3
prob1.run_driver()
print('x_prime: ', prob1.get_val('upstream.x_prime'))
print('y1: ', prob1.get_val('g0.y1'))

Fork me on GitHub