Skip to content

Commit

Permalink
Merge pull request #343 from Kenneth-T-Moore/ken3
Browse files Browse the repository at this point in the history
Guess nonlinear needs to behave like a NonlinearRunOnce iteration and do data-passing between components after each call to guess.
  • Loading branch information
naylor-b committed Aug 23, 2017
2 parents e92b487 + bf16e74 commit 568ecc8
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 12 deletions.
8 changes: 8 additions & 0 deletions openmdao/core/component.py
Expand Up @@ -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
22 changes: 13 additions & 9 deletions openmdao/core/group.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions openmdao/core/implicitcomponent.py
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions openmdao/core/system.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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.
Expand Down
132 changes: 130 additions & 2 deletions openmdao/core/tests/test_impl_comp.py
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand Down
7 changes: 6 additions & 1 deletion openmdao/solvers/nonlinear/newton.py
Expand Up @@ -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 += '+ '

Expand All @@ -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:
Expand Down

0 comments on commit 568ecc8

Please sign in to comment.