Implicit Relationships with SubsystemsΒΆ
Residual variables may depend on the result of a subsystem. For example, the solution to a quadratic equation depends on the coefficients, but one of those coefficients may not be constant, and may depend on a subsystem.
In this example, we solve \(ax^2+bx+c=0\), but \(a\) is a fixed point of \((3 + a - 2a^2)^\frac{1}{4}\).
In order to compute \(a\), omtools
creates a new
openmdao.Problem
instance within an ImplicitComponent
.
The Problem
instance contains a model, which is an instance of
ot.Group
.
The ImplicitComponent
class provides acces to this ot.Group
object with self.group
.
Residuals that depend on subsystems may be defined with
calls to self.group.add_subsystem
.
Subsystems added by calling ``self.group.add_subsystem`` are part of the residual(s), not the ``ImplicitComponent``.
All inputs to the ``ImplicitComponent`` must be declared using calls to ``self.group.declare_input`` at the beginning of ``self.setup``, before any calls to ``self.group.add_subsystem``.
In this example, the only input is c
.
Both a
and b
are outputs of subsystems used to define the
residual.
from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np
class ExampleWithSubsystems(ImplicitComponent):
def setup(self):
g = self.group
# define a subsystem (this is a very simple example)
group = Group()
p = group.create_indep_var('p', val=7)
q = group.create_indep_var('q', val=8)
r = p + q
group.register_output('r', r)
# add child system
g.add_subsystem('R', group, promotes=['*'])
# declare output of child system as input to parent system
r = g.declare_input('r')
c = g.declare_input('c', val=18)
# a == (3 + a - 2 * a**2)**(1 / 4)
group = Group()
a = group.create_output('a')
a.define((3 + a - 2 * a**2)**(1 / 4))
group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)
g.add_subsystem('coeff_a', group, promotes=['*'])
a = g.declare_input('a')
group = Group()
group.create_indep_var('b', val=-4)
g.add_subsystem('coeff_b', group, promotes=['*'])
b = g.declare_input('b')
y = g.create_implicit_output('y')
z = a * y**2 + b * y + c - r
y.define_residual(z)
self.linear_solver = ScipyKrylov()
self.nonlinear_solver = NewtonSolver(
solve_subsystems=False,
maxiter=100,
)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystems())
prob.setup(force_alloc_complex=True)
prob.run_model()
============
comp_example
============
NL: Newton Converged in 3 iterations
Note that calls to ImplicitComponent.add_subsystem
result in adding
a subsystem to the internal Problem
instance (not shown), and not
the ImplicitComponent
itself.
Here is the n2 diagram generated by passing n2=True
to the
constructor for ImplicitOutput
to verify that the model
structure for the residual is correct.
Just as with residuals that do not require subsystems to converge, bracketing solutions is an option as well.
from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np
class ExampleWithSubsystemsBracketedScalar(ImplicitComponent):
def setup(self):
g = self.group
# define a subsystem (this is a very simple example)
group = Group()
p = group.create_indep_var('p', val=7)
q = group.create_indep_var('q', val=8)
r = p + q
group.register_output('r', r)
# add child system
g.add_subsystem('R', group, promotes=['*'])
# declare output of child system as input to parent system
r = g.declare_input('r')
c = g.declare_input('c', val=18)
# a == (3 + a - 2 * a**2)**(1 / 4)
with g.create_group('coeff_a') as group:
a = group.create_output('a')
a.define((3 + a - 2 * a**2)**(1 / 4))
group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)
a = g.declare_input('a')
with g.create_group('coeff_b') as group:
group.create_indep_var('b', val=-4)
b = g.declare_input('b')
y = g.create_implicit_output('y')
z = a * y**2 + b * y + c - r
y.define_residual_bracketed(z, x1=0, x2=2)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystemsBracketedScalar())
prob.setup(force_alloc_complex=True)
prob.run_model()
print('y', prob['y'].shape)
print(prob['y'])
y (1,)
[1.07440944]
Brackets may also be specified for multidimensional array values.
from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np
class ExampleWithSubsystemsBracketedArray(ImplicitComponent):
def setup(self):
g = self.group
# define a subsystem (this is a very simple example)
group = Group()
p = group.create_indep_var('p', val=[7, -7])
q = group.create_indep_var('q', val=[8, -8])
r = p + q
group.register_output('r', r)
# add child system
g.add_subsystem('R', group, promotes=['*'])
# declare output of child system as input to parent system
r = g.declare_input('r', shape=(2, ))
c = g.declare_input('c', val=[18, -18])
# a == (3 + a - 2 * a**2)**(1 / 4)
with g.create_group('coeff_a') as group:
a = group.create_output('a')
a.define((3 + a - 2 * a**2)**(1 / 4))
group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)
# store positive and negative values of `a` in an array
ap = g.declare_input('a')
an = -ap
a = g.create_output('vec_a', shape=(2, ))
a[0] = ap
a[1] = an
with g.create_group('coeff_b') as group:
group.create_indep_var('b', val=[-4, 4])
b = g.declare_input('b', shape=(2, ))
y = g.create_implicit_output('y', shape=(2, ))
z = a * y**2 + b * y + c - r
y.define_residual_bracketed(
z,
x1=[0, 2.],
x2=[2, np.pi],
)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystemsBracketedArray())
prob.setup(force_alloc_complex=True)
prob.run_model()
print('y', prob['y'].shape)
print(prob['y'])
y (2,)
[1.07440944 2.48391993]