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