openmdao

Proper setup of an ExternalCodeComp optimization


I am new to OpenMDAO and am trying to get a simple optimization set up with one external code component (as a starting point in my overall effort). I am running into a few issues:

  1. I suspect something isn't set up correctly because my optimization is terminating "successfully" but is resulting in a pair of warnings that the objective cannot be impacted by the design variables and the design variables have no impact on the constraint/objective.
  2. I'm not sure if I am correctly setting up a constraint using a additional output of the external code. I am not sure that what I am doing is wrong, it's just I can't really know whether it's behaving until I get the first problem sorted out.
  3. I'm not sure if I am setting up a "maximize" optimization in the best way or if there is some better way to have set it up. Right now I just added an additional output which is -1.0 times the thing I want to maximize.

I first set up a class to call the external code deriving from ExternalCodeComp:

import openmdao.api as om
import os
import APPItool

class ExternalCodeAPPI(om.ExternalCodeComp):
    def setup(self):
        self.add_input('FPR', val=1.9)
        self.add_input('Wdes', val=39.0)

        self.add_output('negToS', val=0.0, ref=1e2)          # for optimization minimization
        self.add_output('timeOnStation', val=0.0, ref=1e2)   # actual objective to maximize
        self.add_output('loiterInitialWf', val=0.0, ref=1e3) # constrain >=0

        self.APPIrootFolder = os.getcwd() + '/..'

        self.options['command'] = ('python APPItool.py {}').format(self.APPIrootFolder)

    def setup_partials(self):
        # this external code does not provide derivatives, use finite difference
        self.declare_partials('*', '*', method='fd')
    
    def compute(self,inputs,outputs):
        # read component inputs
        FPR = inputs['FPR']
        Wdes = inputs['Wdes']

        # generate the input parameters file for running the APPI tool
        APPItool.writeInputFile(FPR,Wdes,self.APPIrootFolder)

        # run the APPI tool
        APPItool.run(self.APPIrootFolder)

        # parse the output parameters file
        runResult = APPItool.readOutputFile(self.APPIrootFolder)
        FPR = runResult[0]
        Wdes = runResult[1]
        loiterInitialWf = runResult[2]
        timeOnStation = runResult[3]

        # assign component outputs
        outputs['negToS'] = -1.0*timeOnStation
        outputs['timeOnStation'] = timeOnStation
        outputs['loiterInitialWf'] = loiterInitialWf

Using this class I am able to successfully have OpenMDAO run different sets of inputs to get outputs:

import openmdao.api as om
from ExternalCodeAPPI import ExternalCodeAPPI

prob = om.Problem()
model = prob.model

model.add_subsystem('APPI',ExternalCodeAPPI(), promotes_inputs=['FPR','Wdes'])
prob.setup()
prob.set_val('FPR', 1.75)
prob.set_val('Wdes', 39.0)
prob.run_model()
print('Run with FPR=%g resulted in timeOnStation=%g' % (prob.get_val('FPR'),prob.get_val('APPI.timeOnStation')))

prob.set_val('FPR', 1.9)
prob.set_val('Wdes', 40.0)
prob.run_model()
print('Run with FPR=%g resulted in timeOnStation=%g' % (prob.get_val('FPR'),prob.get_val('APPI.timeOnStation')))

results in:

Run with FPR=1.75 resulted in timeOnStation=200.3
Run with FPR=1.9 resulted in timeOnStation=132.5

Based on that success, I tried to set it up as an optimization problem, but this is where things went awry. I tried the following:

import openmdao.api as om
from ExternalCodeAPPI import ExternalCodeAPPI

prob = om.Problem()
model = prob.model

model.add_subsystem('APPI',ExternalCodeAPPI(), promotes_inputs=['FPR','Wdes'])

# Set default input values
prob.model.set_input_defaults('FPR', 1.888)
prob.model.set_input_defaults('Wdes', 39.0)

# find optimal solution with SciPy optimize
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'

prob.model.add_design_var('FPR', lower=1.5, upper=2.4)
prob.model.add_design_var('Wdes', lower=35.1, upper=42.9)
prob.model.add_objective('APPI.negToS')

prob.driver.options['disp'] = True

prob.setup()
prob.run_driver()

print('Unconstrained optimization result:')
print('FPR=%g, Wdes=%g' % (prob.get_val('FPR'),prob.get_val('Wdes')))
print('timeOnStation=%g (and loiterInitialWf=%g)' % (prob.get_val('APPI.timeOnStation'),prob.get_val('APPI.loiterInitialWf')))

prob.model.add_constraint('APPI.loiterInitialWf', lower=0)

prob.setup()
prob.run_driver()

print('Constrained optimization result:')
print('FPR=%g, Wdes=%g' % (prob.get_val('FPR'),prob.get_val('Wdes')))
print('timeOnStation=%g (and loiterInitialWf=%g)' % (prob.get_val('APPI.timeOnStation'),prob.get_val('APPI.loiterInitialWf')))

which results in the following:

c:\ProgramData\anaconda3\Lib\site-packages\openmdao\core\total_jac.py:1782: DerivativesWarning:Constraints or objectives [('APPI.timeOnStation', inds=[0])] cannot be impacted by the design variables of the problem.
c:\ProgramData\anaconda3\Lib\site-packages\openmdao\core\total_jac.py:1811: DerivativesWarning:Design variables [('FPR', inds=[0]), ('Wdes', inds=[0])] have no impact on the constraints or objective.
Optimization terminated successfully    (Exit mode 0)
            Current function value: 153.3
            Iterations: 1
            Function evaluations: 1
            Gradient evaluations: 1
Optimization Complete
-----------------------------------
Unconstrained optimization result:
FPR=1.888, Wdes=39
timeOnStation=153.3 (and loiterInitialWf=665)
Optimization terminated successfully    (Exit mode 0)
            Current function value: 153.3
            Iterations: 1
            Function evaluations: 1
            Gradient evaluations: 1
Optimization Complete
-----------------------------------
Constrained optimization result:
FPR=1.888, Wdes=39
timeOnStation=153.3 (and loiterInitialWf=665)
c:\ProgramData\anaconda3\Lib\site-packages\openmdao\core\total_jac.py:1782: DerivativesWarning:Constraints or objectives [('APPI.timeOnStation', inds=[0]), ('APPI.loiterInitialWf', inds=[0])] cannot be impacted by the design variables of the problem.
c:\ProgramData\anaconda3\Lib\site-packages\openmdao\core\total_jac.py:1811: DerivativesWarning:Design variables [('FPR', inds=[0]), ('Wdes', inds=[0])] have no impact on the constraints or objective.

While it ran without raising an exception, it did not actually run an optimization (and it's reporting the result for the inputs at the default values). It would appear that I don't have things connected properly since it's complaining that the design variables don't affect the objective and the objective is not affected by the design variables. However, it's not obvious to me what I've done wrong. The N2 diagram it generates for this seems pretty similar to the ones for the paraboloid examples. N2 diagram


Solution

  • Thank you for including the files from your example. I ran them (replacing the reads with some equations since I don't have your external application) and got SLSQP to optimize as expected, without a warning about non-impacting design vars. This leads me to believe the problem APPItool, and I have some ideas of what it is.

    When OpenMDAO computes derivatives with finite difference, it takes a step in the design variables using the default stepsize, which is 1e-6. This stepsize is pretty small, but you need a small step to get an accurate derivative. OpenMDAO has determined that this derivative is zero, which can happen if the outputs do not change after adding 1e-6 * mag to the input and running it. This can happen for a number of reasons, including:

    1. The output file that you are parsing truncates the result (e.g., 153.3 instead of 153.313934293092903).
    2. An internal solver in APPItool has a loose tolerance (e.g., 1e-3), so it doesn't re-converge after the step and you get the same answer.

    These are both common occurrences with legacy codes, so I recommend looking into each possibility. One way to diagnose this is to do a sweep. For this model, run it with FPR=1.5 and print all the outputs. Then, run it with FPR = 1.5 + 1e-6 and print them again. If the outputs are the same, then you know that you need to address one of the above-listed problems in your APPItool.