openmdao compute gradient of constraints but not objective - openmdao

How can I compute the gradient of my constraints without computing the gradient of my objective? I've tried this,
self._problem().compute_totals(of=self.consList, wrt=self._dvlist,
return_format='array')
but this still calls the objective function gradients. (self.consList is a list with one element, which is the name of my constraint component).
Here is an example of this issue:
import openmdao.api as om
prob = om.Problem()
model = prob.model
class comp1(om.ExplicitComponent):
def setup(self):
self.add_input('x', 2, units="degF")
self.add_output('y1', 2, units="degF")
def setup_partials(self):
self.declare_partials('y1', ['x'])
def compute(self, inputs, outputs):
outputs['y1'] = 2.0 * inputs['x']
def compute_partials(self, inputs, J):
print('I need these partials')
J['y1', 'x'] = 2.0
class comp2(om.ExplicitComponent):
def setup(self):
self.add_input('x', 2, units="degF")
self.add_output('y2', 2, units="degF")
def setup_partials(self):
self.declare_partials('y2', ['x'])
def compute(self, inputs, outputs):
outputs['y2'] = 3.0 * inputs['x']
def compute_partials(self, inputs, J):
print('I dont need these partials')
J['y2', 'x'] = 3.0
model.add_subsystem('comp1', comp1(), promotes=['*'])
model.add_subsystem('comp2', comp2(), promotes=['*'])
model.set_input_defaults('x', 35.0, units='degF')
model.add_design_var('x', units='degC', lower=0.0, upper=100.0)
model.add_constraint('y1', units='degC', lower=0.0, upper=100.0)
model.add_objective('y2', units='degC')
prob.setup()
prob.run_model()
print('computing constraint partials')
res = prob.driver._compute_totals(of='y1', wrt='x')

You could add a custom attribute to stop your model from computing the gradient of the second component:
import openmdao.api as om
prob = om.Problem()
model = prob.model
class comp1(om.ExplicitComponent):
def setup(self):
self.add_input('x', 2, units="degF")
self.add_output('y1', 2, units="degF")
def setup_partials(self):
self.declare_partials('y1', ['x'])
def compute(self, inputs, outputs):
outputs['y1'] = 2.0 * inputs['x']
def compute_partials(self, inputs, J):
print('I need these partials')
J['y1', 'x'] = 2.0
class comp2(om.ExplicitComponent):
def setup(self):
self.add_input('x', 2, units="degF")
self.add_output('y2', 2, units="degF")
self.skip_partial = False
def setup_partials(self):
self.declare_partials('y2', ['x'])
def compute(self, inputs, outputs):
outputs['y2'] = 3.0 * inputs['x']
def compute_partials(self, inputs, J):
if self.skip_partial: return
print('I dont need these partials')
J['y2', 'x'] = 3.0
model.add_subsystem('comp1', comp1(), promotes=['*'])
model.add_subsystem('comp2', comp2(), promotes=['*'])
model.set_input_defaults('x', 35.0, units='degF')
model.add_design_var('x', units='degC', lower=0.0, upper=100.0)
model.add_constraint('y1', units='degC', lower=0.0, upper=100.0)
model.add_objective('y2', units='degC')
prob.setup()
prob.run_model()
prob.model._subsystems_myproc[2].skip_partial = True
print('computing constraint partials')
res = prob.driver._compute_totals(of='y1', wrt='x')

Related

OpenMDAO2 restricting modular software architecture

OpenMDAO2 restrictions on group connections and input/output variables is throwing a wrench into my desire to write clean, modular software
from openmdao.api import Group, ExplicitComponent, IndepVarComp, Problem
class A(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Az', val=0.0)
def compute(self, inputs, outputs):
outputs['Az'] = inputs['x'] + inputs['y']
class B(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Bz', val=0.0)
def compute(self, inputs, outputs):
outputs['Bz'] = 2*inputs['x'] - inputs['y']
class AB(Group):
def setup(self):
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
class C(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Cz', val=0.0)
def compute(self, inputs, outputs):
outputs['Cz'] = 3*inputs['x'] - 2*inputs['y']
class D(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Dz', val=0.0)
def compute(self, inputs, outputs):
outputs['Dz'] = 4*inputs['x'] - 2.5*inputs['y']
class CD(Group):
def setup(self):
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
Sometimes I would like to work with Group AB only (run scenarios, optimize, etc.) and sometimes I would like to work with Group CD only. I can do that with,
prob = Problem()
prob.model = AB()
prob.setup()
prob['x'] = 10.0
prob['y'] = 20.0
prob.run_model()
print(prob['Az'],prob['Bz'])
However, sometimes I would like to work with Group ABCD:
class ABCD(Group):
def setup(self):
self.add_subsystem('AB', AB())
self.add_subsystem('CD', CD())
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.connect('xx', ['AB.x', 'CD.x'])
self.connect('yy', ['AB.y', 'CD.y'])
In that case, there is no combination of variable promotion, connections, or usage of IndepVarComps that doesn't give me an error of "inputs with multiple connections".
In OpenMDAO 1.x I was able to get around that by removing the IndepVarComps from the lower level groups (AB, CD) and only using them at the highest level group (see answer). However, OpenMDAO 2.x throws an error that two inputs are connected without an output. For example:
class AB(Group):
def setup(self):
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
Now I am stuck with maintaining multiple copies of the same code, one copy for AB and another for ABCD, or just manually connecting my modules in pure python and moving away from OpenMDAO. Am I missing something? Any help or guidance would be welcome.
In your simple example, I can see two ways of solving the problem. Both involve the use of options on the groups. Since im not sure which way will work better, I included both in my sample below.
One way is that you make it optional if AB/CD own their own indeps. Then you can toggle the needed behavior as you see fit. This works, but I personally think its messy.
The second option is to make just a single group, but use the options to control what is created by the mode argument. I think this way is cleaner, since you just have the 4 components and the one group.
from openmdao.api import Group, ExplicitComponent, IndepVarComp
class A(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Az', val=0.0)
def compute(self, inputs, outputs):
outputs['Az'] = inputs['x'] + inputs['y']
class B(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Bz', val=0.0)
def compute(self, inputs, outputs):
outputs['Bz'] = 2*inputs['x'] - inputs['y']
class AB(Group):
def initialize(self):
self.options.declare('owns_indeps', types=bool, default=True)
def setup(self):
if self.options['owns_indeps']:
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
class C(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Cz', val=0.0)
def compute(self, inputs, outputs):
outputs['Cz'] = 3*inputs['x'] - 2*inputs['y']
class D(ExplicitComponent):
def setup(self):
self.add_input('x', val=0.0)
self.add_input('y', val=0.0)
self.add_output('Dz', val=0.0)
def compute(self, inputs, outputs):
outputs['Dz'] = 4*inputs['x'] - 2.5*inputs['y']
class CD(Group):
def initialize(self):
self.options.declare('owns_indeps', types=bool, default=True)
def setup(self):
if self.options['owns_indeps']:
indeps = IndepVarComp()
indeps.add_output('x', 0.0)
indeps.add_output('y', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
class ABCD(Group):
def setup(self):
self.add_subsystem('AB', AB(owns_indeps=False))
self.add_subsystem('CD', CD(owns_indeps=False))
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
self.connect('xx', ['AB.x', 'CD.x'])
self.connect('yy', ['AB.y', 'CD.y'])
class ABCD_ALT(Group):
"""Alternate approach that would not require more than one group class at all"""
def initialize(self):
self.options.declare('mode', values=['AB', 'CD', 'ABCD'], default='AB')
def setup(self):
mode = self.options['mode']
indeps = IndepVarComp()
indeps.add_output('xx', 0.0)
indeps.add_output('yy', 0.0)
self.add_subsystem('indeps', indeps, promotes=['*'])
if 'AB' in mode:
self.add_subsystem('A', A(), promotes=['*'])
self.add_subsystem('B', B(), promotes=['*'])
if 'CD' in mode:
self.add_subsystem('C', C(), promotes=['*'])
self.add_subsystem('D', D(), promotes=['*'])
self.connect('xx', 'x')
self.connect('yy', 'y')
if __name__ == "__main__":
from openmdao.api import Problem
p = Problem()
# p.model = AB()
# p.model = CD()
p.model = ABCD()
# p.model = ABCD_ALT(mode='AB')
p.setup()

Openmdao - compute finite difference in parallel when optimizing

I have made a simple example (see below) which does the fd in serial. What is the best way to do this in parallel? I am using Python 3.6 and OpenMDAO 2.4.0.
import numpy as np
from openmdao.api import Problem, ScipyOptimizeDriver, ExecComp, IndepVarComp, ExplicitComponent, Group
class WorkFlow(ExplicitComponent):
def setup(self):
self.add_input('x', np.ones(5))
self.add_output('y', 2.0)
self.declare_partials('y', 'x', method='fd')
def compute(self, inputs, outputs):
print('comm:', self.comm.rank, inputs['x'])
print()
outputs['y'] = abs(np.sum(inputs['x']**2) - 9)
prob = Problem()
indeps = prob.model.add_subsystem('indeps', IndepVarComp(), promotes=['*'])
indeps.add_output('x', np.ones(5))
prob.model.add_subsystem('wf', WorkFlow(), promotes_inputs=['x'])
prob.driver = ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'
prob.driver.options['tol'] = 1e-9
prob.model.add_design_var('x', lower=-10.0, upper=10.0)
prob.model.add_objective('wf.y')
prob.setup()
prob.run_driver()
print(prob['x'])
print(prob['wf.y'])
As of OpenMDAO v2.4 you could not do parallel finite-difference. However, the feature has been recently added to the master branch of OpenMDAO and will be officially released in OpenMDAO V2.5 soon.
To use the feature right now, install the repository version of openMDAO (You can not do pip install openmdao. instead clone the repository from github and then pip install -e <location of the cloned repo>).
Then you can follow the instructions on the documentation page for parallel FD.
Here is your actual code though. The only change to the component is that when you instantiate it you provide the num_par_fd=5 argument to the component. Then when you call the file, you should run it under mpi like this:
mpiexec -n 5 python test.py
Here is what test.py should look like:
import numpy as np
from openmdao.api import Problem, ScipyOptimizeDriver, ExecComp, IndepVarComp, ExplicitComponent, Group
class WorkFlow(ExplicitComponent):
def setup(self):
self.add_input('x', np.ones(5))
self.add_output('y', 2.0)
self.declare_partials('y', 'x', method='fd')
def compute(self, inputs, outputs):
print('comm:', self.comm.rank, inputs['x'])
print()
outputs['y'] = abs(np.sum(inputs['x']**2) - 9)
prob = Problem()
indeps = prob.model.add_subsystem('indeps', IndepVarComp(), promotes=['*'])
indeps.add_output('x', np.ones(5))
prob.model.add_subsystem('wf', WorkFlow(num_par_fd=5), promotes_inputs=['x'])
prob.driver = ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'
prob.driver.options['tol'] = 1e-9
prob.model.add_design_var('x', lower=-10.0, upper=10.0)
prob.model.add_objective('wf.y')
prob.setup()
prob.run_driver()
print(prob['x'])
print(prob['wf.y'])

External code incompatibility with metadata?

If I use the same setup with metadata for an external code component and explicit component it seems to fail with the externalcode comp. The code below has both of the component. As is there is no error, if i comment out the explicit component part I get an error :
comp = GFWrapper(DVLO=np.array(DVLIST))
TypeError: init() got an unexpected keyword argument 'DVLO'
import numpy as np
import json as js
import re,sys,subprocess,os
from openmdao.api import Problem, Group, IndepVarComp
from openmdao.api import ScipyOptimizeDriver
from openmdao.api import ExternalCode
class GFWrapper(ExternalCode):
def initialize(self):
self.metadata.declare('DVLO', types=np.ndarray)
def setup(self):
DVLO = self.metadata['DVLO']
for dv in DVLO:
self.add_input(dv)
self.add_output('OBJECTIVE')
self.input_file = 'GFWrapper_input.dat'
self.output_file = 'GFWrapper_output.dat'
self.options['external_input_files'] = [self.input_file,]
self.options['external_output_files'] = [self.output_file,]
self.options['command'] = [
'python', 'run.py', self.input_file, self.output_file
]
#self.declare_partials(of='*', wrt='*', method='fd')
for dv in DVLO:
self.declare_partials(of='OBJECTIVE', wrt=dv, method='fd')
def compute(self, inputs, outputs):
DVLO = self.metadata['DVLO']
# generate the input file for the external code
outF = open(self.input_file, "w")
for dv in DVLO:
outF.write(inputs[dv])
print(dv,inputs[dv])
outF.write("\n")
outF.close()
# the parent compute function actually runs the external code
super(GFWrapper, self).compute(inputs, outputs)
# parse the output file from the external code
file_contents=np.loadtxt(self.output_file)
outputs['OBJECTIVE'] = file_contents[0]
from openmdao.api import ExplicitComponent
#
class GFWrapper(ExplicitComponent):
def initialize(self):
self.metadata.declare('DVLO', types=np.ndarray)
def setup(self):
DVLO = self.metadata['DVLO']
for dv in DVLO:
self.add_input(dv)
self.add_output('OBJECTIVE')
#self.declare_partials(of='*', wrt='*', method='fd')
for dv in DVLO:
self.declare_partials(of='OBJECTIVE', wrt=dv, method='fd')
def compute(self, inputs, outputs):
DVLO = self.metadata['DVLO']
powetemp = 0
for dv in DVLO:
powetemp += inputs[dv]
outputs['OBJECTIVE'] = powetemp
#
DVLIST=['DV1','DV2']
DVMIN =[2,11]
DVMAX =[3,12]
InitDVVal=[3,5]
nr_of_desvar=len(DVLIST)
top = Problem()
top.model = model = Group()
" Introduce independent variables later will be design variables (optimization parameters) "
inputs_comp = IndepVarComp()
for i in range(nr_of_desvar):
inputs_comp.add_output(DVLIST[i],InitDVVal[i])
" Add components/subsystems to the Group-model- "
model.add_subsystem('inputs_comp', inputs_comp)
comp = GFWrapper(DVLO=np.array(DVLIST))
model.add_subsystem('GFWrapper', comp)
This is a bug in OpenMDAO version 2.2. The error occurs because we didn't include **kwargs in the init statement for ExternalCode. We intend to fix this for V2.3, but in the meantime, you can work around for your GFWrapper component by adding an init statement like this:
class GFWrapper(ExternalCode):
def __init__(self, **kwargs):
super(GFWrapper, self).__init__()
self.metadata.update(kwargs)

In OpenMDAO how to specify values to IndepVarComp or other components?

I am just playing around with the Paraboloid tutorial in OpenMDAO. I tried something simple, basically changing the input values to the Paraboloid component. See the following code. When I run it though, it will print the same result as if nothing happened. So what is going on? if I have a group, how would I modify the inputs?
from __future__ import print_function
from openmdao.api import IndepVarComp, Component, Problem, Group
class Paraboloid(Component):
""" Evaluates the equation f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3 """
def __init__(self):
super(Paraboloid, self).__init__()
self.add_param('x', val=0.0)
self.add_param('y', val=0.0)
self.add_output('f_xy', val=0.0)
def solve_nonlinear(self, params, unknowns, resids):
"""f(x,y) = (x-3)^2 + xy + (y+4)^2 - 3
"""
x = params['x']
y = params['y']
unknowns['f_xy'] = (x-3.0)**2 + x*y + (y+4.0)**2 - 3.0
def linearize(self, params, unknowns, resids):
""" Jacobian for our paraboloid."""
x = params['x']
y = params['y']
J = {}
J['f_xy', 'x'] = 2.0*x - 6.0 + y
J['f_xy', 'y'] = 2.0*y + 8.0 + x
return J
if __name__ == "__main__":
top = Problem()
root = top.root = Group()
root.add('p1', IndepVarComp('x', 3.0))
root.add('p2', IndepVarComp('y', -4.0))
root.add('p', Paraboloid())
root.connect('p1.x', 'p.x')
root.connect('p2.y', 'p.y')
root.p1.x=3.0;
root.p2.y=-4.0;
top.setup()
top.run()
print(root.p.unknowns['f_xy'])
root.p1.x=5.0;
root.p2.y=5.0;
top.setup()
top.run()
print(root.p.unknowns['f_xy'])
You have to call setup() before you can set any values.
After that you set them via a dictionary like access from the problem instance. For your sample code it should look like:
top.setup()
top['p1.x'] = 3.
top['p2.y'] = 4.
top.run()
print(top['p.f_xy'])
top['p1.x'] = 10.
top['p2.y'] = 10.
top.run()
print(top['p.f_xy'])
When I run this model with the adjusted script I get:
##############################################
Setup: Checking for potential issues...
No recorders have been specified, so no data will be saved.
Setup: Check complete.
##############################################
73.0
342.0

Connecting an element of array to another Component's input

I am trying to connect the last element of an output of one component to the input of another component. An example is shown below:
import numpy as np
from openmdao.api import Component, Problem, Group
class C1(Component):
def __init__(self):
super(C1, self).__init__()
self.add_param('fin', val=1.0)
self.add_output('arr', val=np.zeros(5))
def solve_nonlinear(self, params, unknowns, resids):
fin = params['fin']
unknowns['arr'] = np.array([2*fin])
class C2(Component):
def __init__(self):
super(C2, self).__init__()
self.add_param('flt', val=0.0)
self.add_output('fout', val=0.0)
def solve_nonlinear(self, params, unknowns, resids):
flt = params['flt']
unknowns['fout'] = 2*flt
class A(Group):
def __init__(self):
super(A, self).__init__()
self.add('c1', C1())
self.add('c2', C2())
self.connect('c1.arr[-1]', 'c2.flt')
if __name__ == '__main__':
a = Problem()
a.root = A()
a.setup()
a.run()
print a.root.c2.unknowns['fout']
I am given the error:
openmdao.core.checks.ConnectError: Source 'c1.arr[-1]' cannot be connected to target 'c2.flt': 'c1.arr[-1]' does not exist.
Is there a way to do this? I know it worked in the old version of OpenMDAO.
OpenMDAO supports connection to specific indices of a source by using the 'src_indices' arg. For example:
self.connect('c1.arr', 'c2.flt', src_indices=[4])
Negative indices are not currently supported.
There are a number of small issues here. First the solve_nonlinear method of C1 has the wrong size for its array computation. It ends up working out, but you should really set the array to the right size (length 5).
To part of an array (see docs and more advanced docs) , you specify src_indices argument to connect.
import numpy as np
from openmdao.api import Component, Problem, Group
class C1(Component):
def __init__(self):
super(C1, self).__init__()
self.add_param('fin', val=1.0)
self.add_output('arr', val=np.zeros(5))
def solve_nonlinear(self, params, unknowns, resids):
fin = params['fin']
unknowns['arr'] = fin*np.arange(5)
class C2(Component):
def __init__(self):
super(C2, self).__init__()
self.add_param('flt', val=0.0)
self.add_output('fout', val=0.0)
def solve_nonlinear(self, params, unknowns, resids):
flt = params['flt']
unknowns['fout'] = 2*flt
class A(Group):
def __init__(self):
super(A, self).__init__()
self.add('c1', C1())
self.add('c2', C2())
self.connect('c1.arr', 'c2.flt', src_indices=[4,])
if __name__ == '__main__':
a = Problem()
a.root = A()
a.setup()
a.run()
print a.root.c2.unknowns['fout']

Resources