Implicit RelationshipsΒΆ
It is possible to compute outputs implicitly by defining a residual variable in terms of the output and inputs.
In the first example, we solve a quadratic equation.
This quadratic has two solutions: 1
and 3
.
Depending on the starting value of the output variable, OpenMDAO will
find one root or the other.
from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np
class ExampleApplyNonlinear(ImplicitComponent):
def setup(self):
g = self.group
with g.create_group('sys') as group:
group.create_indep_var('a', val=1)
group.create_indep_var('b', val=-4)
group.create_indep_var('c', val=3)
a = g.declare_input('a')
b = g.declare_input('b')
c = g.declare_input('c')
x = g.create_implicit_output('x')
y = a * x**2 + b * x + c
x.define_residual(y)
self.linear_solver = ScipyKrylov()
self.nonlinear_solver = NewtonSolver(solve_subsystems=False)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleApplyNonlinear())
prob.setup(force_alloc_complex=True)
prob.run_model()
print('using default x=1')
import omtools.examples.valid.ex_implicit_apply_nonlinear as ex
print('setting x=1.9')
ex.prob.set_val('x', 1.9)
ex.prob.run_model()
print(ex.prob['x'])
print('setting x=2.1')
ex.prob.set_val('x', 2.1)
ex.prob.run_model()
print(ex.prob['x'])
using default x=1
============
comp_example
============
NL: Newton Converged in 0 iterations
setting x=1.9
============
comp_example
============
NL: Newton Converged in 7 iterations
[1.]
setting x=2.1
============
comp_example
============
NL: Newton Converged in 7 iterations
[3.]
The expressions for the residuals will tell OpenMDAO to construct the
relevant Component
objects, but they will be part of a Problem
within the generated ImplicitComponent
object.
As a result, the expressions defined above do not translate to
Component
objects in the outer Problem
whose model is displayed
in the n2 diagram below.
For especially complicated problems, where the residual may converge for
multiple solutions, or where the residual is difficult to converge over
some interval, omtools
provides an API for bracketing solutions.
from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np
class ExampleBracketedScalar(ImplicitComponent):
def setup(self):
g = self.group
with g.create_group('sys') as group:
group.create_indep_var('a', val=1)
group.create_indep_var('b', val=-4)
group.create_indep_var('c', val=3)
a = g.declare_input('a')
b = g.declare_input('b')
c = g.declare_input('c')
x = g.create_implicit_output('x')
y = a * x**2 + b * x + c
x.define_residual_bracketed(
y,
x1=0,
x2=2,
)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleBracketedScalar())
prob.setup(force_alloc_complex=True)
prob.run_model()
print('x', prob['x'].shape)
print(prob['x'])
x (1,)
[1.]
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 ExampleBracketedArray(ImplicitComponent):
def setup(self):
g = self.group
with g.create_group('sys') as group:
group.create_indep_var('a', val=[1, -1])
group.create_indep_var('b', val=[-4, 4])
group.create_indep_var('c', val=[3, -3])
a = g.declare_input('a', shape=(2, ))
b = g.declare_input('b', shape=(2, ))
c = g.declare_input('c', shape=(2, ))
x = g.create_implicit_output('x', shape=(2, ))
y = a * x**2 + b * x + c
x.define_residual_bracketed(
y,
x1=[0, 2.],
x2=[2, np.pi],
)
prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleBracketedArray())
prob.setup(force_alloc_complex=True)
prob.run_model()
print('x', prob['x'].shape)
print(prob['x'])
x (2,)
[1. 3.]