Search code examples
optimizationsolveropenmdao

It there a way to cache OpenMDAO component outputs to avoid duplicate executions?


I am writing a model consists of five subsystems. The first subsystem is generates data for other subsystems using the inputs it does not solve anything iteratively, therefore its outputs not changed during computation. I want it calls once the compute method just like the initialization. How can I write a model that calls once in run_model and calls every time just once in run_driver?


Solution

  • It's a little hard to be sure without more details, but you mention "iterative" so Im guessing you have a solver at the top level of your model and there is a component not involved in that solver loop, but that is getting called each time the solver iterates.

    The solution to this is to make a sub-group in your model that has just the components that need to iterate. Put your only-run-once component at the top of the model, along with that group. Put the iterative solver on the sub-group.

    An alternative solution is to add a bit of caching to your component, so it checks its inputs to see if they have changed. If they have, re-run. If they have not, just keep the old answer.

    Here is an example that includes both features (note: the solver in this example does not converge because its a toy problem that doesn't have a valid physical solution. I just threw it together to illustrate the model structure and caching)

    OpenMDAO Model with sub-solver and caching component

    import openmdao.api as om
    # from openmdao.utils.assert_utils import assert_check_totals
    
    
    class StingyComp(om.ExplicitComponent): 
    
        def setup(self): 
    
            self.add_input('x1', val=2.)
            self.add_input('x2', val=3.)
    
            self.add_output('x')
    
            self._input_hash = None
    
    
        def compute(self, inputs, outputs):
    
            x1 = inputs['x1'][0] # pull the scalar out so you can hash it
            x2 = inputs['x2'][0]
    
            print("running StingyComp")
            current_input_hash = hash((x1, x2))
            if self._input_hash != current_input_hash : 
                print('    ran compute')
                outputs['x'] = 2*x1 + x2**2
                self._input_hash = current_input_hash
            else: 
                print('    skipped compute')
    
    
    class NormalComp(om.ExplicitComponent): 
    
        def setup(self): 
    
            self.add_input('x1', val=2.)
            self.add_input('x2', val=3.)
    
            self.add_output('y')
    
        def compute(self, inputs, outputs):
    
            x1 = inputs['x1']
            x2 = inputs['x2']
    
            print("running normal Comp")
            outputs['y'] = x1 + x2
    
    
    p = om.Problem()
    
    
    
    p.model.add_subsystem('run_once1', NormalComp(), promotes=['*'])
    p.model.add_subsystem('run_once2', StingyComp(), promotes=['*'])
    
    sub_group = p.model.add_subsystem('sub_group', om.Group(), promotes=['*']) # transparent group that could hold sub-solver
    sub_group.add_subsystem('C1', om.ExecComp('f1 = f2**2 + 1.5 * x - y**2.5'), promotes=['*'])
    sub_group.add_subsystem('C2', om.ExecComp('f2 = f1**2 + x**1.5 - 2.5*y'), promotes=['*'])
    sub_group.nonlinear_solver = om.NewtonSolver(solve_subsystems=False)
    sub_group.linear_solver = om.DirectSolver()
    
    
    p.setup()
    
    
    print('first run')
    p.run_model()
    
    print('second run, same inputs')
    p.run_model()
    
    p['x1'] = 10
    p['x2'] = 27.5
    
    print('third run, new inputs')
    p.run_model()