0

For compatibility reasons I am using OpenMDAO v0.10.3.2

I am trying to set up an optimization problem in OpenMDAO that requires the use of a case iterator driver within the workflow of an optimizer. To illustrate, I have created the simple example shown below. The code seems to run, but the optimizer does not appear to alter the parameters and exits after two runs of its workflow.

I am looking for suggestions in setting up this type of problem and insights into what may be going wrong with my current formulation.

from openmdao.main.api import Assembly, Component
from openmdao.lib.datatypes.api import Float, Array, List
from openmdao.lib.drivers.api import DOEdriver, SLSQPdriver, CaseIteratorDriver

import numpy as np


class component1(Component):

    x = Float(iotype='in')
    term1 = Float(iotype='out')
    a = Float(iotype='in', default_value=1)
    def execute(self):
        x = self.x
        a = self.a

        term1 = a*x**2
        self.term1 = term1


class component2(Component):

    x = Float(iotype='in')
    y = Float(iotype='in')
    term1 = Float(iotype='in')
    f = Float(iotype='out')

    def execute(self):

        y = self.y
        x = self.x
        term1 = self.term1

        f = term1 + x + y**2

        self.f = f


class summer(Component):

    fs = Array(iotype='in', desc='f values from all cases')
    total = Float(iotype='out', desc='sum of all f values')

    def execute(self):
        self.total = sum(self.fs)
        print 'In summer, fs = %s and total = %s' % (self.fs, self.total)


class assembly(Assembly):

    cases_a = Array(iotype='in', desc='list of cases')
    x = Float(iotype='in')
    y = Float(iotype='in')
    f = Float(iotype='out')
    total = Float(iotype='out', default_value=100)

    def configure(self):

        # create instances of components
        self.add('component1', component1())
        self.add('component2', component2())
        self.add('summer', summer())

        # set up main driver (optimizer)
        self.add('driver', SLSQPdriver())
        self.driver.iprint = 1
        self.driver.maxiter = 100
        self.driver.accuracy = 1.0e-6
        self.driver.add('summer', summer())
        self.driver.add_parameter('x', low=-5., high=5.)
        self.driver.add_parameter('y', low=-5., high=5.)
        self.driver.add_objective('summer.total')

        # set up case iterator driver
        self.add('case_driver', CaseIteratorDriver())
        self.case_driver.workflow.add(['component1', 'component2'])
        self.case_driver.add_parameter('component1.a')
        self.case_driver.add_response('component2.f')

        # Set up connections
        self.connect('x', 'component1.x')
        self.connect('y', 'component2.y')
        self.connect('component1.x', 'component2.x')
        self.connect('component1.term1', 'component2.term1')
        self.connect('component2.f', 'f')
        self.connect('cases_a', 'case_driver.case_inputs.component1.a')
        self.connect('case_driver.case_outputs.component2.f', 'summer.fs')
        self.connect('summer.total', 'total')

        # establish main workflow
        self.driver.workflow.add(['case_driver', 'summer'])

if __name__ == "__main__":
    """ the result should be -1 at (x, y) = (-0.5, 0) """

    import time

    test = assembly()
    values = [1, 1, 1, 1]
    test.cases_a = np.array(values)
    test.x = 4
    test.y = 4

    tt = time.time()
    test.run()

    print "Elapsed time: ", time.time()-tt, "seconds"

    print 'result = ', test.total
    print '(x, y) = (%s, %s)' % (test.x, test.y)
jthomas
  • 2,437
  • 1
  • 13
  • 15
  • I have solved the problem using ALPSO, NSGA2, MIDACO, COBYLA, and ALHSO, while SLSQP, CONMIN, KSOPT, and SOLVOPT fail. This means that the problem can be solved using gradient-free optimization methods, but fails for gradient-based methods. However, the underlying problem is one that should be easily solvable using gradient-based algorithms. This makes me think that the problem is related to the use of the case iterator. – jthomas Jul 17 '15 at 22:29

1 Answers1

1

There are a lot of challenges associated with propagating derivatives across a CID driver, and we never quite got it working the way we wanted to. So instead, I suggest an alternate approach where you create a separate instance for each case that you want to run. This will work much better, especially if you're planning to use analytic derivatives at some point

from openmdao.main.api import Assembly, Component
from openmdao.lib.datatypes.api import Float, Array, List
from openmdao.lib.drivers.api import DOEdriver, SLSQPdriver, COBYLAdriver, CaseIteratorDriver

import numpy as np


class component1(Component):

    x = Float(iotype='in')
    term1 = Float(iotype='out')
    a = Float(iotype='in', default_value=1)
    def execute(self):
        x = self.x
        a = self.a

        term1 = a*x**2
        self.term1 = term1

        print "In comp1", self.name, self.a, self.x, self.term1


class component2(Component):

    x = Float(iotype='in')
    y = Float(iotype='in')
    term1 = Float(iotype='in')
    f = Float(iotype='out')

    def execute(self):

        y = self.y
        x = self.x
        term1 = self.term1

        f = term1 + x + y**2

        self.f = f
        print "In comp2", self.name, self.x, self.y, self.term1, self.f



class summer(Component):


    total = Float(iotype='out', desc='sum of all f values')

    def __init__(self, size):
        super(summer, self).__init__()
        self.size = size

        self.add('fs', Array(np.zeros(size), iotype='in', desc='f values from all cases'))

    def execute(self):
        self.total = sum(self.fs)
        print 'In summer, fs = %s and total = %s' % (self.fs, self.total)


class assembly(Assembly):

    x = Float(iotype='in')
    y = Float(iotype='in')
    total = Float(iotype='out', default_value=100)

    def __init__(self, a_vals=[1, 1, 1, 1]):
        self.a_vals = a_vals

        super(assembly, self).__init__()



    def configure(self):

        #add the driver first, so I don't overwrite the workflow later on
        self.add('driver', SLSQPdriver())


        #create this first, so we can connect to it
        self.add('summer', summer(size=len(self.a_vals)))
        self.connect('summer.total', 'total')

        # create instances of components
        for i, a in enumerate(self.a_vals):
            c1 = self.add('comp1_%d'%i, component1())
            c1.a = a
            c2 = self.add('comp2_%d'%i, component2())

            self.connect('x', ['comp1_%d.x'%i,'comp2_%d.x'%i])
            self.connect('y', 'comp2_%d.y'%i)
            self.connect( 'comp1_%d.term1'%i, 'comp2_%d.term1'%i)

            self.connect('comp2_%d.f'%i, 'summer.fs[%d]'%i)

            self.driver.workflow.add(['comp1_%d'%i, 'comp2_%d'%i])

        # establish main workflow


        # set up main driver (optimizer)
        self.driver.iprint = 1
        self.driver.maxiter = 100
        self.driver.accuracy = 1.0e-6
        self.driver.add_parameter('x', low=-5., high=5.)
        self.driver.add_parameter('y', low=-5., high=5.)
        self.driver.add_objective('summer.total')


if __name__ == "__main__":
    """ the result should be -1 at (x, y) = (-0.5, 0) """

    import time

    test = assembly([1, 1, 1, 1])

    test.x = 2
    test.y = 4

    tt = time.time()
    test.run()

    print "Elapsed time: ", time.time()-tt, "seconds"

    print 'result = ', test.total
    print '(x, y) = (%s, %s)' % (test.x, test.y)
Justin Gray
  • 5,605
  • 1
  • 11
  • 16
  • Thank you for your response. This looks like a good solution, however, when I run your code I get an error "AttributeError: object has no attribute 'a_vals'" pertaining to line 77. It looks like the configure is not recognizing the attribute added by the init method. Do you have any ideas on why that would not be working? I have not used super() much and have not been able to figure out what is wrong. – jthomas Jul 20 '15 at 16:33
  • Based on print results, it seems that the configure method is running before the init method. – jthomas Jul 20 '15 at 17:12
  • In older versions of OpenMDAO (0.12.0 and older) the base __init__ implementation from Assembly called configure on all its children. This changed in 0.13.0, but to make my script work in older versions I edited it to create the a_vals attribute before calling Super (the parent class's __init__). I edited my answer to include this small fix. – Justin Gray Jul 20 '15 at 17:18
  • Even after making that fix, I could not get this to work in OpenMDAO 0.10.3.2, but it does work in 0.9.7. I will go ahead and use that version for now. Thank you. – jthomas Jul 20 '15 at 20:28
  • I suspect there is a bug in 0.10.3.2. It should work in later versions (0.12 and 0.13 as well) – Justin Gray Jul 21 '15 at 22:33