Imco van Gent
Imco van Gent

Reputation: 65

How to set the initial guess for a convergence variable with a separate model component?

This is not an issue with OpenMDAO, but more a topic I would like to hear your thoughts about with a (simplistic) suggestion for a solution. The issue is about setting the initial value for a variable that needs to be converged with a solver. In my case I can have a model where one tool (InitialGuess) is able to give an initial guess for a variable (let's say for y1), which would be converged in a group where y1 is output of another tool (D1). At the moment it is not allowed to have two tools that have y1 as output (and understandably so). Hence it is not possible to have InitialGuess as a preconditioner and let D1 calculate the updated value(s). However, in some models it could be helpful to have such a preconditioner as one of the components. In an aircraft design example, a lower fidelity tool could provide initial guesses, in order for the solver to start at a nicer point with higher fidelity tools.

I have created a small example to illustrate my point (based on the Sellar problem). The code is shown below the picture. This picture shows the 'initial guess connections' that should be created in blue:

enter image description here

from openmdao.core.explicitcomponent import ExplicitComponent
from openmdao.devtools.problem_viewer.problem_viewer import view_model
from openmdao.solvers.nonlinear.nonlinear_block_gs import NonlinearBlockGS


class InitialGuess(ExplicitComponent):

    def setup(self):
        self.add_input('x1', val=0.)
        self.add_input('z1', val=0.)
        self.add_input('z2', val=0.)

        self.add_output('y1', val=0.)
        self.add_output('y2', val=0.)

    def compute(self, inputs, outputs):
        x1 = inputs['x1']
        z1 = inputs['z1']
        z2 = inputs['z2']

        outputs['y1'] = (-0.1 + ((z1 - 0.1) ** 2 + 0.8 * z2 + x1) ** (0.5)) ** 2
        outputs['y2'] = z1 + z2 - 0.1 + ((z1 - 0.1) ** 2 + 0.8 * z2 + x1) ** (0.5)


class D1(ExplicitComponent):

    def setup(self):
        self.add_input('x1', val=0.)
        self.add_input('z1', val=0.)
        self.add_input('z2', val=0.)
        self.add_input('y2', val=0.)

        self.add_output('y1', val=0.)

    def compute(self, inputs, outputs):
        x1 = inputs['x1']
        z1 = inputs['z1']
        z2 = inputs['z2']
        y2 = inputs['y2']

        outputs['y1'] = z1 ** 2. + x1 + z2 - .2 * y2


class D2(ExplicitComponent):

    def setup(self):
        self.add_input('z1', val=0.)
        self.add_input('z2', val=0.)
        self.add_input('y1', val=0.)

        self.add_output('y2', val=0.)

    def compute(self, inputs, outputs):
        z1 = inputs['z1']
        z2 = inputs['z2']
        y1 = inputs['y1']

        outputs['y2'] = abs(y1)**.5 + z1 + z2


if __name__ == "__main__":
    from openmdao.core.problem import Problem
    from openmdao.core.group import Group
    from openmdao.core.indepvarcomp import IndepVarComp

    model = Group()
    ivc = IndepVarComp()
    ivc.add_output('x1', 3.0)
    ivc.add_output('z1', 2.0)
    ivc.add_output('z2', 2.0)
    model.add_subsystem('des_vars', ivc)
    model.add_subsystem('initial_guess', InitialGuess())

    conv_group = Group()
    conv_group.add_subsystem('d1_comp', D1())
    conv_group.add_subsystem('d2_comp', D2())
    model.add_subsystem('conv_group', conv_group)

    model.connect('des_vars.x1', 'initial_guess.x1')
    model.connect('des_vars.x1', 'conv_group.d1_comp.x1')
    model.connect('des_vars.z1', 'initial_guess.z1')
    model.connect('des_vars.z1', 'conv_group.d1_comp.z1')
    model.connect('des_vars.z1', 'conv_group.d2_comp.z1')
    model.connect('des_vars.z2', 'initial_guess.z2')
    model.connect('des_vars.z2', 'conv_group.d1_comp.z2')
    model.connect('des_vars.z2', 'conv_group.d2_comp.z2')
    model.connect('conv_group.d1_comp.y1', 'conv_group.d2_comp.y1')
    model.connect('conv_group.d2_comp.y2', 'conv_group.d1_comp.y2')

    ###
    # PSEUDO_CODE
    # model.connect_as('initial_guess', 'initial_guess.y1', 'conv_group.d2_comp.y1')
    # model.connect_as('initial_guess', 'initial_guess.y2', 'conv_group.d1_comp.y1')
    ###

    conv_group.nonlinear_solver = NonlinearBlockGS()

    prob = Problem(model)
    prob.setup()
    view_model(prob, outfile='n2_initial_guess_example.html', show_browser=False)
    prob.run_model()

    print('y1 guess = ' + str(prob['initial_guess.y1'][0]))
    print('y1 conv = ' + str(prob['conv_group.d1_comp.y1'][0]))
    print('y2 guess = ' + str(prob['initial_guess.y2'][0]))
    print('y2 conv = ' + str(prob['conv_group.d2_comp.y2'][0]))

In the example above the initial_guess and the conv_group are completely separated, however, it would be very helpful if, with a single run_model call, the initial_guess component updates the y1 and y2 values before the conv_group is solved. In this case, this would even mean that conv_group would be solved at the first iteration, but in a realistic case it would merely lead to less iterations. The pseudo-code in the example provides a suggestion as to how this could be defined in the script.

I was wondering if there already is a way to do this in OpenMDAO? If not, I thought it might be a nice idea to include a capability like this.

Upvotes: 0

Views: 140

Answers (1)

Rob Falck
Rob Falck

Reputation: 2704

OpenMDAO master branch (as of 05/01/2019) OpenMDAO supports providing an initial guess using the guess_nonlinear method on systems: http://openmdao.org/twodocs/versions/latest/features/core_features/grouping_components/guess_method.html

This feature will be released in V2.7, but can be accessed by pulling from master in the meantime.

This method gets run before solving the group and sets the implicit outputs to whatever initial value you want.

A convenient thing about guess_nonlinear is that you can use it to do whatever calculations you want, assuming that you only need access to inputs, outputs, or residuals.

Upvotes: 1

Related Questions