(Openmdao 2.4.0) 'compute_partials' function of a Component seems to be run even when forcing 'declare_partials' to FD for this component

佐手、 提交于 2019-12-24 10:56:21

问题


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:

  1. Don't call compute_partials if no derivatives were explicitly declared as analytic.

  2. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!