Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Part 1 of setup refactor. #312

Merged
merged 13 commits into from
Aug 3, 2017
14 changes: 0 additions & 14 deletions openmdao/core/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,6 @@ def _setup_vars(self, recurse=True):
num_var = self._num_var
num_var_byset = self._num_var_byset

self._var_rel_names = {'input': [], 'output': []}
self._var_rel2data_io = {}
self._design_vars = {}
self._responses = {}

self._static_mode = False
self._var_rel2data_io.update(self._static_var_rel2data_io)
for type_ in ['input', 'output']:
self._var_rel_names[type_].extend(self._static_var_rel_names[type_])
self._design_vars.update(self._static_design_vars)
self._responses.update(self._static_responses)
self.setup()
self._static_mode = True

# Compute num_var
for type_ in ['input', 'output']:
num_var[type_] = len(self._var_rel_names[type_])
Expand Down
40 changes: 39 additions & 1 deletion openmdao/core/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ def __init__(self, **kwargs):

def setup(self):
"""
Add subsystems to this group.
Build this group.

This method should be overidden by your Group's method.

You may call 'add_subsystem' to add systems to this group. You may also issue connections,
and set the linear and nonlinear solvers for this group level. You cannot safely change
anything on children systems; use the 'configure' method instead.

Available attributes:
name
Expand All @@ -66,6 +72,38 @@ def setup(self):
"""
pass

def configure(self):
"""
Configure this group to assign children settings.

This method may optionally be overidden by your Group's method.

You may only use this method to change settings on your children subsystems. This includes
setting solvers in cases where you want to override the defaults.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should include some detail about timing here.

"You can assume that the full hierarchy below your level has been instantiated and has already called its own configure methods."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed


You can assume that the full hierarchy below your level has been instantiated and has
already called its own configure methods.

Available attributes:
name
pathname
comm
metadata
system hieararchy with attribute access
"""
pass

def _configure(self):
"""
Configure our model recursively to assign any children settings.

Highest system's settings take precedence.
"""
for subsys in self._subsystems_myproc:
subsys._configure()

self.configure()

def _setup_procs(self, pathname, comm):
"""
Distribute processors and assign pathnames.
Expand Down
29 changes: 29 additions & 0 deletions openmdao/core/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,12 @@ def initialize(self):
"""
pass

def _configure(self):
"""
Configure this system to assign children settings.
"""
pass

def _get_initial_procs(self, comm, initial):
"""
Get initial values for pathname and comm.
Expand Down Expand Up @@ -643,8 +649,13 @@ def _setup(self, comm, vector_class, setup_mode, force_alloc_complex=False):

# If we're only updating and not recursing, processors don't need to be redistributed
if recurse:

# Besides setting up the processors, this method also builds the model hierarchy.
self._setup_procs(*self._get_initial_procs(comm, initial))

# Recurse model from the bottom to the top for configuring.
self._configure()

# For updating variable and connection data, setup needs to be performed only
# in the current system, by gathering data from immediate subsystems,
# and no recursion is necessary.
Expand Down Expand Up @@ -697,6 +708,24 @@ def _setup_procs(self, pathname, comm):
self.comm = comm
self._subsystems_proc_range = []

# TODO: This version only runs for Components, because it is overriden in Group, so
# maybe we should move this to Component?

# Clear out old variable information so that we can call setup on the component.
self._var_rel_names = {'input': [], 'output': []}
self._var_rel2data_io = {}
self._design_vars = {}
self._responses = {}

self._static_mode = False
self._var_rel2data_io.update(self._static_var_rel2data_io)
for type_ in ['input', 'output']:
self._var_rel_names[type_].extend(self._static_var_rel_names[type_])
self._design_vars.update(self._static_design_vars)
self._responses.update(self._static_responses)
self.setup()
self._static_mode = True

minp, maxp = self.get_req_procs()
if MPI and comm is not None and comm != MPI.COMM_NULL and comm.size < minp:
raise RuntimeError("%s needs %d MPI processes, but was given only %d." %
Expand Down
104 changes: 101 additions & 3 deletions openmdao/core/tests/test_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import numpy as np

from openmdao.core.problem import Problem, get_relevant_vars
from openmdao.api import Group, IndepVarComp, PETScVector, NonlinearBlockGS, \
ScipyOptimizer, ExecComp
from openmdao.api import Group, IndepVarComp, PETScVector, NonlinearBlockGS, ScipyOptimizer, \
ExecComp, Group, NewtonSolver, ImplicitComponent, ScipyIterativeSolver
from openmdao.devtools.testutil import assert_rel_error
from openmdao.test_suite.components.paraboloid import Paraboloid

from openmdao.test_suite.components.paraboloid import Paraboloid
from openmdao.test_suite.components.sellar import SellarDerivatives, SellarDerivativesConnected


Expand Down Expand Up @@ -556,5 +556,103 @@ def test_relevance(self):
self.assertEqual(outputs, indep1_outs | indep2_outs)
self.assertEqual(systems, indep1_sys | indep2_sys)

def test_system_setup_and_configure(self):
# Test that we can change solver settings on a subsystem in a system's setup method.
# Also assures that highest system's settings take precedence.

class ImplSimple(ImplicitComponent):

def setup(self):
self.add_input('a', val=1.)
self.add_output('x', val=0.)

def apply_nonlinear(self, inputs, outputs, residuals):
residuals['x'] = np.exp(outputs['x']) - \
inputs['a']**2 * outputs['x']**2

def linearize(self, inputs, outputs, jacobian):
jacobian['x', 'x'] = np.exp(outputs['x']) - \
2 * inputs['a']**2 * outputs['x']
jacobian['x', 'a'] = -2 * inputs['a'] * outputs['x']**2


class Sub(Group):

def setup(self):
self.add_subsystem('comp', ImplSimple())

# This will not solve it
self.nonlinear_solver = NonlinearBlockGS()

def configure(self):
# This will not solve it either.
self.nonlinear_solver = NonlinearBlockGS()


class Super(Group):

def setup(self):
self.add_subsystem('sub', Sub())

def configure(self):
# This will solve it.
self.sub.nonlinear_solver = NewtonSolver()
self.sub.linear_solver = ScipyIterativeSolver()


top = Problem()
top.model = Super()

top.setup(check=False)

self.assertTrue(isinstance(top.model.sub.nonlinear_solver, NewtonSolver))
self.assertTrue(isinstance(top.model.sub.linear_solver, ScipyIterativeSolver))

def test_feature_system_configure(self):

class ImplSimple(ImplicitComponent):

def setup(self):
self.add_input('a', val=1.)
self.add_output('x', val=0.)

def apply_nonlinear(self, inputs, outputs, residuals):
residuals['x'] = np.exp(outputs['x']) - \
inputs['a']**2 * outputs['x']**2

def linearize(self, inputs, outputs, jacobian):
jacobian['x', 'x'] = np.exp(outputs['x']) - \
2 * inputs['a']**2 * outputs['x']
jacobian['x', 'a'] = -2 * inputs['a'] * outputs['x']**2


class Sub(Group):
def setup(self):
self.add_subsystem('comp', ImplSimple())

def configure(self):
# This solver won't solve the sytem. We want
# to override it in the parent.
self.nonlinear_solver = NonlinearBlockGS()


class Super(Group):
def setup(self):
self.add_subsystem('sub', Sub())

def configure(self):
# This will solve it.
self.sub.nonlinear_solver = NewtonSolver()
self.sub.linear_solver = ScipyIterativeSolver()


top = Problem()
top.model = Super()

top.setup(check=False)

print(isinstance(top.model.sub.nonlinear_solver, NewtonSolver))
print(isinstance(top.model.sub.linear_solver, ScipyIterativeSolver))

if __name__ == "__main__":
unittest.main()
52 changes: 52 additions & 0 deletions openmdao/docs/features/grouping_components/standalone_groups.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@

Building Models with Standalone Groups
--------------------------------------

So far we have showed how to build groups, add subsystems to them, and connect variables in your run script. You can also
create custom groups that can be instantiated as part of larger models. To do this, create a new class that inherits from
Group and give it one method named "setup". Inside this method, you can add subsystems, make connections, and modify the
group level linear and nonlinear solvers.

Here is an example where we take two Sellar models and connect them in a cycle. We also set the linear and nonlinear solvers.

.. embed-code::
openmdao.test_suite.components.sellar_feature.DoubleSellar

Configuring Child Subsystems
----------------------------

Most of the time, the "setup" method is the only one you need to define on a group. The main exception is the case where you
want to modify a solver that was set in one of your children groups. When you call add_subsystem, the system you add is
instantiated, but its "setup" method is not called until after the parent group's "setup" method is finished with its
execution. That means that anything you do with that subsystem (such as changing the nonlinear solver) will potentially be
overwritten by the child system's setup if it is assigned there as well.

To get around this timing problem, there is a second setup method called "configure" that runs after the "setup" on all
subsystems has completed. While "setup" recurses from top down, "configure" recurses from bottom up, so that the highest
system in the hiearchy takes precedence over all lower ones for any modifications.

Here is a simple example where a lower system sets a solver, but we want to change it to a different one in the top-most
system.

.. embed-test::
openmdao.core.tests.test_problem.TestProblem.test_feature_system_configure

Here is a reference on what you can do in each method:

**setup**

- Add subsystems
- Issue Connections
- Assign linear and nonlinear solvers at group level
- Change solver settings in group
- Assign Jacobians at group level
- Set system execution order

**configure**

- Assign linear and nonlinear solvers to subsystems
- Change solver settings in subsystems
- Assign Jacobians to subsystems
- Set execution order in subsystems

.. tags:: Group, System
1 change: 1 addition & 0 deletions openmdao/docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Grouping components for more complex models
features/grouping_components/set_order
features/grouping_components/src_indices
features/grouping_components/parallel_group
features/grouping_components/standalone_groups

Defining partial derivatives
================================
Expand Down
3 changes: 3 additions & 0 deletions openmdao/test_suite/components/double_sellar.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
from openmdao.test_suite.components.sellar import SellarDis1withDerivatives, SellarDis2withDerivatives
from openmdao.test_suite.components.sellar import SellarImplicitDis1

# TODO -- Need to convert these over to use `setup` after we have two setup
# phases split up and have the ability to change driver settings


class SubSellar(Group):

Expand Down
19 changes: 12 additions & 7 deletions openmdao/test_suite/components/sellar.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,12 @@ def setup(self):
if self.metadata['nl_maxiter']:
self.nonlinear_solver.options['maxiter'] = self.metadata['nl_maxiter']

cycle.linear_solver = self.metadata['linear_solver']
def configure(self):
self.cycle.linear_solver = self.metadata['linear_solver']
if self.metadata['ln_atol']:
cycle.linear_solver.options['atol'] = self.metadata['ln_atol']
self.cycle.linear_solver.options['atol'] = self.metadata['ln_atol']
if self.metadata['ln_maxiter']:
cycle.linear_solver.options['maxiter'] = self.metadata['ln_maxiter']
self.cycle.linear_solver.options['maxiter'] = self.metadata['ln_maxiter']


class SellarDerivatives(Group):
Expand Down Expand Up @@ -320,7 +321,6 @@ def setup(self):
self.add_subsystem('pz', IndepVarComp('z', np.array([5.0, 2.0])), promotes=['z'])

mda = self.add_subsystem('mda', Group(), promotes=['x', 'z', 'y1', 'y2'])
mda.linear_solver = ScipyIterativeSolver()
mda.add_subsystem('d1', SellarDis1withDerivatives(), promotes=['x', 'z', 'y1', 'y2'])
mda.add_subsystem('d2', SellarDis2withDerivatives(), promotes=['z', 'y1', 'y2'])

Expand All @@ -331,7 +331,6 @@ def setup(self):
self.add_subsystem('con_cmp1', ExecComp('con1 = 3.16 - y1'), promotes=['con1', 'y1'])
self.add_subsystem('con_cmp2', ExecComp('con2 = y2 - 24.0'), promotes=['con2', 'y2'])

mda.nonlinear_solver = NonlinearBlockGS()
self.linear_solver = ScipyIterativeSolver()

self.nonlinear_solver = self.metadata['nonlinear_solver']
Expand All @@ -346,6 +345,10 @@ def setup(self):
if self.metadata['ln_maxiter']:
self.linear_solver.options['maxiter'] = self.metadata['ln_maxiter']

def configure(self):
self.mda.linear_solver = ScipyIterativeSolver()
self.mda.nonlinear_solver = NonlinearBlockGS()


class StateConnection(ImplicitComponent):
"""
Expand Down Expand Up @@ -409,10 +412,8 @@ def setup(self):
self.add_subsystem('pz', IndepVarComp('z', np.array([5.0, 2.0])), promotes=['z'])

sub = self.add_subsystem('sub', Group(), promotes=['x', 'z', 'y1', 'state_eq.y2_actual', 'state_eq.y2_command', 'd1.y2', 'd2.y2'])
sub.linear_solver = ScipyIterativeSolver()

subgrp = sub.add_subsystem('state_eq_group', Group(), promotes=['state_eq.y2_actual', 'state_eq.y2_command'])
subgrp.linear_solver = ScipyIterativeSolver()
subgrp.add_subsystem('state_eq', StateConnection())

sub.add_subsystem('d1', SellarDis1withDerivatives(), promotes=['x', 'z', 'y1'])
Expand Down Expand Up @@ -442,6 +443,10 @@ def setup(self):
if self.metadata['ln_maxiter']:
self.linear_solver.options['maxiter'] = self.metadata['ln_maxiter']

def configure(self):
self.sub.linear_solver = ScipyIterativeSolver()
self.sub.state_eq_group.linear_solver = ScipyIterativeSolver()


class SellarImplicitDis1(ImplicitComponent):
"""
Expand Down