问题
I want to solve MDA for Sellar using Newton non linear solver for the Group . I have defined Disciplines with Derivatives (using 'compute_partials') but I want to check the number of calls to Discipline 'compute' and 'compute_partials' when forcing or not the disciplines not to use their analytical derivatives (using 'declare_partials' in the Problem definition ). The problem is that is seems that the 'compute_partials' function is still called even though I force not to use it . Here is an example (Sellar)
So for Discipline 2, I add a counter and I have
from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2
class SellarDis2withDerivatives(SellarDis2):
"""
Component containing Discipline 2 -- derivatives version.
"""
def _do_declares(self):
# Analytic Derivs
self.declare_partials(of='*', wrt='*')
self.exec_count_d = 0
def compute_partials(self, inputs, J):
"""
Jacobian for Sellar discipline 2.
"""
y1 = inputs['y1']
if y1.real < 0.0:
y1 *= -1
J['y2', 'y1'] = .5*y1**-.5
J['y2', 'z'] = np.array([[1.0, 1.0]])
self.exec_count_d += 1
I create a similar MDA as on OpendMDAO docs but calling SellarDis2withDerivatives I have created and SellarDis1withDerivatives and changing the nonlinear_solver for Newton_solver() like this
cycle.add_subsystem('d1', SellarDis1withDerivatives(), promotes_inputs=['x', 'z', 'y2'], promotes_outputs=['y1'])
cycle.add_subsystem('d2', SellarDis2withDerivatives(), promotes_inputs=['z', 'y1'], promotes_outputs=['y2'])
# Nonlinear Block Gauss Seidel is a gradient free solver
cycle.nonlinear_solver = NewtonSolver()
cycle.linear_solver = DirectSolver()
Then I run the following problem
prob2 = Problem()
prob2.model = SellarMDA()
prob2.setup()
prob2.model.cycle.d1.declare_partials('*', '*', method='fd')
prob2.model.cycle.d2.declare_partials('*', '*', method='fd')
prob2['x'] = 2.
prob2['z'] = [-1., -1.]
prob2.run_model()
count = prob2.model.cycle.d2.exec_count_d
print("Number of derivatives calls (%i)"% (count))
And , as a results, I obtain
=====
cycle
NL: Newton Converged in 3 iterations Number of derivatives calls (3)
Therefore, it seems that the function 'compute_partials' is still called somehow (even if the derivatives are computed with FD ). Does someone as an explanation ?
回答1:
I believe this to be a bug (or perhaps an unintended consequence of how derivatives are specified.)
This behavior is a by-product of mixed declaration of derivative, where we allow the user to specify some derivatives on a component to be 'fd' and other derivatives to be analytic. So, we are always capable of doing both fd and compute_partials
on a component.
There are two changes we could make in openmdao to remedy this:
Don't call
compute_partials
if no derivatives were explicitly declared as analytic.Filter out any variables declared as 'fd' so that if a user tries to set them in
compute_partials
, a keyerror is raised (or maybe just a warning, and the derivative value is not overwritten)
In the meantime, the only workarounds would be to comment out the compute_partials
method, or alternatively enclose the component in a group and finite difference the group.
回答2:
Another workaround is to have an attribute (here called _call_compute_partials
) in your class, which tracks, if there where any analytical derivatives declared. And the conditional in compute_partials()
could be implemented outside the method, where the method is called.
from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.core.indepvarcomp import IndepVarComp
from openmdao.core.problem import Problem
from openmdao.drivers.scipy_optimizer import ScipyOptimizeDriver
class ExplicitComponent2(ExplicitComponent):
def __init__(self, **kwargs):
super(ExplicitComponent2, self).__init__(**kwargs)
self._call_compute_partials = False
def declare_partials(self, of, wrt, dependent=True, rows=None, cols=None, val=None,
method='exact', step=None, form=None, step_calc=None):
if method == 'exact':
self._call_compute_partials = True
super(ExplicitComponent2, self).declare_partials(of, wrt, dependent, rows, cols, val,
method, step, form, step_calc)
class Cylinder(ExplicitComponent2):
"""Main class"""
def setup(self):
self.add_input('radius', val=1.0)
self.add_input('height', val=1.0)
self.add_output('Area', val=1.0)
self.add_output('Volume', val=1.0)
# self.declare_partials('*', '*', method='fd')
# self.declare_partials('*', '*')
self.declare_partials('Volume', 'height', method='fd')
self.declare_partials('Volume', 'radius', method='fd')
self.declare_partials('Area', 'height', method='fd')
self.declare_partials('Area', 'radius')
# self.declare_partials('Area', 'radius', method='fd')
def compute(self, inputs, outputs):
radius = inputs['radius']
height = inputs['height']
area = height * radius * 2 * 3.14 + 3.14 * radius ** 2 * 2
volume = 3.14 * radius ** 2 * height
outputs['Area'] = area
outputs['Volume'] = volume
def compute_partials(self, inputs, partials):
if self._call_compute_partials:
print('Calculate partials...')
if __name__ == "__main__":
prob = Problem()
indeps = prob.model.add_subsystem('indeps', IndepVarComp(), promotes=['*'])
indeps.add_output('radius', 2.) # height
indeps.add_output('height', 3.) # radius
main = prob.model.add_subsystem('cylinder', Cylinder(), promotes=['*'])
# setup the optimization
prob.driver = ScipyOptimizeDriver()
prob.model.add_design_var('radius', lower=0.5, upper=5.)
prob.model.add_design_var('height', lower=0.5, upper=5.)
prob.model.add_objective('Area')
prob.model.add_constraint('Volume', lower=10.)
prob.setup()
prob.run_driver()
print(prob['Volume']) # should be around 10
来源:https://stackoverflow.com/questions/54406473/openmdao-2-4-0-compute-partials-function-of-a-component-seems-to-be-run-even