diff --git a/openmdao/core/component.py b/openmdao/core/component.py index 66f9adafd3..2d4d1b58a0 100644 --- a/openmdao/core/component.py +++ b/openmdao/core/component.py @@ -792,3 +792,11 @@ def _set_partials_meta(self): for approx in itervalues(self._approx_schemes): approx._init_approximations() + + def _guess_nonlinear(self): + """ + Provide initial guess for states. + + Does nothing on any non-implicit component. + """ + pass diff --git a/openmdao/core/group.py b/openmdao/core/group.py index bee5f74422..d4282f71d5 100644 --- a/openmdao/core/group.py +++ b/openmdao/core/group.py @@ -102,6 +102,9 @@ def _configure(self): for subsys in self._subsystems_myproc: subsys._configure() + if subsys._has_guess: + self._has_guess = True + self.configure() def _setup_procs(self, pathname, comm): @@ -1267,20 +1270,21 @@ def _solve_nonlinear(self): name = self.pathname if self.pathname else 'root' - # Execute guess_nonlinear if specified. - # We need to call this early enough so that any solver that needs initial guesses has - # them. - # TODO: It is pointless to run this ahead of non-iterative solvers. - for sub in self.system_iter(recurse=True): - if hasattr(sub, 'guess_nonlinear'): - with sub._unscaled_context(outputs=[sub._outputs], residuals=[sub._residuals]): - sub.guess_nonlinear(sub._inputs, sub._outputs, sub._residuals) - with Recording(name + '._solve_nonlinear', self.iter_count, self): result = self._nonlinear_solver.solve() return result + def _guess_nonlinear(self): + """ + Provide initial guess for states. + """ + if self._has_guess: + for isub, sub in enumerate(self._subsystems_myproc): + if sub._has_guess: + self._transfer('nonlinear', 'fwd', isub) + sub._guess_nonlinear() + def _apply_linear(self, vec_names, mode, scope_out=None, scope_in=None): """ Compute jac-vec product. The model is assumed to be in a scaled state. diff --git a/openmdao/core/implicitcomponent.py b/openmdao/core/implicitcomponent.py index 8ddf69c4e0..a316c1bf9f 100644 --- a/openmdao/core/implicitcomponent.py +++ b/openmdao/core/implicitcomponent.py @@ -26,6 +26,15 @@ def __init__(self, **kwargs): """ super(ImplicitComponent, self).__init__(**kwargs) + if overrides_method('guess_nonlinear', self, ImplicitComponent): + self._has_guess = True + + def _configure(self): + """ + Configure this system to assign children settings. + + Also tag component if it provides a guess_nonlinear. + """ if overrides_method('apply_linear', self, ImplicitComponent): self.matrix_free = True @@ -71,6 +80,13 @@ def _solve_nonlinear(self): else: return result + def _guess_nonlinear(self): + """ + Provide initial guess for states. + """ + with self._unscaled_context(outputs=[self._outputs], residuals=[self._residuals]): + self.guess_nonlinear(self._inputs, self._outputs, self._residuals) + def _apply_linear(self, vec_names, mode, scope_out=None, scope_in=None): """ Compute jac-vec product. The model is assumed to be in a scaled state. diff --git a/openmdao/core/system.py b/openmdao/core/system.py index d181e238f3..a2a48e6546 100644 --- a/openmdao/core/system.py +++ b/openmdao/core/system.py @@ -237,6 +237,9 @@ class System(object): # _reconfigured : bool If True, this system has reconfigured, and the immediate parent should update. + # + _has_guess : bool + True if this system has or contains a system with a `guess_nonlinear` method defined. """ def __init__(self, **kwargs): @@ -344,6 +347,8 @@ def __init__(self, **kwargs): self.initialize() self.metadata.update(kwargs) + self._has_guess = False + def _check_reconf(self): """ Check if this systems wants to reconfigure and if so, perform the reconfiguration. diff --git a/openmdao/core/tests/test_impl_comp.py b/openmdao/core/tests/test_impl_comp.py index 8be090ddad..154298f0a1 100644 --- a/openmdao/core/tests/test_impl_comp.py +++ b/openmdao/core/tests/test_impl_comp.py @@ -16,8 +16,8 @@ class QuadraticComp(ImplicitComponent): """ A Simple Implicit Component representing a Quadratic Equation. - - R(a, b, c, x) = ax^2 + bx + c + + R(a, b, c, x) = ax^2 + bx + c Solution via Quadratic Formula: x = (-b + sqrt(b^2 - 4ac)) / 2a @@ -286,6 +286,134 @@ def guess_nonlinear(self, inputs, outputs, resids): prob.run_model() assert_rel_error(self, prob['comp2.x'], 3.) + def test_guess_nonlinear_transfer(self): + # Test that data is transfered to a component before calling guess_nonlinear. + + class ImpWithInitial(ImplicitComponent): + + def setup(self): + self.add_input('x', 3.0) + self.add_output('y', 4.0) + + def solve_nonlinear(self, inputs, outputs): + """ Do nothing. """ + pass + + def apply_nonlinear(self, inputs, outputs, resids): + """ Do nothing. """ + pass + + def guess_nonlinear(self, inputs, outputs, resids): + # Passthrough + outputs['y'] = inputs['x'] + + + group = Group() + + group.add_subsystem('px', IndepVarComp('x', 77.0)) + group.add_subsystem('comp1', ImpWithInitial()) + group.add_subsystem('comp2', ImpWithInitial()) + group.connect('px.x', 'comp1.x') + group.connect('comp1.y', 'comp2.x') + + group.nonlinear_solver = NewtonSolver() + group.nonlinear_solver.options['maxiter'] = 1 + + prob = Problem(model=group) + prob.set_solver_print(level=0) + prob.setup(check=False) + + prob.run_model() + assert_rel_error(self, prob['comp2.y'], 77., 1e-5) + + def test_guess_nonlinear_transfer_subbed(self): + # Test that data is transfered to a component before calling guess_nonlinear. + + class ImpWithInitial(ImplicitComponent): + + def setup(self): + self.add_input('x', 3.0) + self.add_output('y', 4.0) + + def solve_nonlinear(self, inputs, outputs): + """ Do nothing. """ + pass + + def apply_nonlinear(self, inputs, outputs, resids): + """ Do nothing. """ + resids['y'] = 1.0e-6 + pass + + def guess_nonlinear(self, inputs, outputs, resids): + # Passthrough + outputs['y'] = inputs['x'] + + + group = Group() + sub = Group() + + group.add_subsystem('px', IndepVarComp('x', 77.0)) + sub.add_subsystem('comp1', ImpWithInitial()) + sub.add_subsystem('comp2', ImpWithInitial()) + group.connect('px.x', 'sub.comp1.x') + group.connect('sub.comp1.y', 'sub.comp2.x') + + group.add_subsystem('sub', sub) + + group.nonlinear_solver = NewtonSolver() + group.nonlinear_solver.options['maxiter'] = 1 + + prob = Problem(model=group) + prob.set_solver_print(level=0) + prob.setup(check=False) + + prob.run_model() + assert_rel_error(self, prob['sub.comp2.y'], 77., 1e-5) + + def test_guess_nonlinear_transfer_subbed2(self): + # Test that data is transfered to a component before calling guess_nonlinear. + + class ImpWithInitial(ImplicitComponent): + + def setup(self): + self.add_input('x', 3.0) + self.add_output('y', 4.0) + + def solve_nonlinear(self, inputs, outputs): + """ Do nothing. """ + pass + + def apply_nonlinear(self, inputs, outputs, resids): + """ Do nothing. """ + resids['y'] = 1.0e-6 + pass + + def guess_nonlinear(self, inputs, outputs, resids): + # Passthrough + outputs['y'] = inputs['x'] + + + group = Group() + sub = Group() + + group.add_subsystem('px', IndepVarComp('x', 77.0)) + sub.add_subsystem('comp1', ImpWithInitial()) + sub.add_subsystem('comp2', ImpWithInitial()) + group.connect('px.x', 'sub.comp1.x') + group.connect('sub.comp1.y', 'sub.comp2.x') + + group.add_subsystem('sub', sub) + + sub.nonlinear_solver = NewtonSolver() + sub.nonlinear_solver.options['maxiter'] = 1 + + prob = Problem(model=group) + prob.set_solver_print(level=0) + prob.setup(check=False) + + prob.run_model() + assert_rel_error(self, prob['sub.comp2.y'], 77., 1e-5) + def test_guess_nonlinear_feature(self): class ImpWithInitial(ImplicitComponent): diff --git a/openmdao/solvers/nonlinear/newton.py b/openmdao/solvers/nonlinear/newton.py index ef84a8e735..12acb9c94c 100644 --- a/openmdao/solvers/nonlinear/newton.py +++ b/openmdao/solvers/nonlinear/newton.py @@ -182,10 +182,11 @@ def _iter_initialize(self): float error at the first iteration. """ + system = self._system + with Recording('Newton_subsolve', 0, self): if self.options['solve_subsystems'] and \ (self._iter_count <= self.options['max_sub_solves']): - system = self._system self._solver_info.prefix += '+ ' @@ -201,6 +202,10 @@ def _iter_initialize(self): self._solver_info.prefix = self._solver_info.prefix[:-3] if self.options['maxiter'] > 0: + + # Execute guess_nonlinear if specified. + system._guess_nonlinear() + self._run_apply() norm = self._iter_get_norm() else: