OpenMDAO 并行采样 / 为什么 test_brute_force.py 不会失败

OpenMDAO parallelized sampling / why does test_brute_force.py not fail

我修改了 test_brute_force.py 以尝试并行化抛物面模型的采样(请参见下面的代码)。我收到错误 'sellars': promoted name 'sellar99.p1.f_xy' matches multiple unknowns: ('sellars.sellar99.p1.f_xy', 'sellars.sellar99.p1.f_xy')。这个错误是什么意思?为什么 test_brute_force.py 没有遇到这个错误?

from __future__ import print_function
from florisse.floris import AEPGroup
import unittest

from six.moves import range
from six import iteritems
import numpy as np
from openmdao.api import Problem, Group, ParallelGroup, \
                         Component, IndepVarComp, ExecComp, \
                         Driver, ScipyOptimizer, SqliteRecorder
from openmdao.core.mpi_wrap import MPI

if MPI:
    from openmdao.core.petsc_impl import PetscImpl as impl
else:
    from openmdao.api import BasicImpl as impl

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_p', val=6.0)
        self.add_param('y', val=-7.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_p']
        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_p']
        y = params['y']
        J = {}

        J['f_xy', 'x_p'] = 2.0*x - 6.0 + y
        J['f_xy', 'y'] = 2.0*y + 8.0 + x
        return J

pboidGroup = Group()
pboidGroup.add('p1', Paraboloid(), promotes=['x_p', 'y'])
pboidGroup.add('p2', Paraboloid(), promotes=['x_p', 'y'])
pboidGroup.connect('p1.x_p', 'p2.x_p')
pboidGroup.connect('p1.x_p', 'p2.y')

class Randomize(Component):
    """ add random uncertainty to params and distribute

    Args
    ----
    n : number of points to generate for each param

    params : collection of (name, value, std_dev) specifying the params
             that are to be randommized.
    """
    def __init__(self, n=0, params=[]):
        super(Randomize, self).__init__()

        self.dists = {}

        for name, value, std_dev in params:
            # add param
            self.add_param(name, val=value)

            # add an output array var to distribute the modified param values
            if isinstance(value, np.ndarray):
                shape = (n, value.size)
            else:
                shape = (n, 1)

            # generate a standard normal distribution (size n) for this param
            self.dists[name] = np.random.normal(0.0, std_dev, n*shape[1]).reshape(shape)
            #self.dists[name] = std_dev*np.random.normal(0.0, 1.0, n*shape[1]).reshape(shape)

            self.add_output('dist_'+name, val=np.zeros(shape))

    def solve_nonlinear(self, params, unknowns, resids):
        """ add random uncertainty to params
        """
        for name, dist in iteritems(self.dists):
            unknowns['dist_'+name] = params[name] + dist

    def linearize(self, params, unknowns, resids):
        """ derivatives
        """
        J = {}
        for u in unknowns:
            name = u.split('_', 1)[1]
            for p in params:
                shape = (unknowns[u].size, params[p].size)
                if p == name:
                    J[u, p] = np.eye(shape[0], shape[1])
                else:
                    J[u, p] = np.zeros(shape)
        return J


class Collector(Component):
    """ collect the inputs and compute the mean of each

    Args
    ----
    n : number of points to collect for each input

    names : collection of `Str` specifying the names of the inputs to
            collect and the resulting outputs.
    """
    def __init__(self, n=10, names=[]):
        super(Collector, self).__init__()

        self.names = names

        # create n params for each input
        for i in range(n):
            for name in names:
                self.add_param('%s_%i' % (name, i),  val=0.)

        # create an output for the mean of each input
        for name in names:
            self.add_output(name,  val=0.)

    def solve_nonlinear(self, params, unknowns, resids):
        """ compute the mean of each input
        """
        inputs = {}

        for p in params:
            name = p.split('_', 1)[0]
            if name not in inputs:
                inputs[name] = data = [0.0, 0.0]
            else:
                data = inputs[name]
            data[0] += 1
            data[1] += params[p]

        for name in self.names:
            unknowns[name]  = inputs[name][1]/inputs[name][0]

    def linearize(self, params, unknowns, resids):
        """ derivatives
        """
        J = {}
        for p in params:
            name, idx = p.split('_', 1)
            for u in unknowns:
                if u == name:
                    J[u, p] = 1
                else:
                    J[u, p] = 0
        return J


class BruteForceSellarProblem(Problem):
    """ Performs optimization on the Sellar problem.

        Applies a normal distribution to the design vars and runs all of the
        samples, then collects the values of all of the outputs, calculates
        the mean of those and stuffs that back into the unknowns vector.

        This is the brute force version that just stamps out N separate
        sellar models in a parallel group and sets the input of each
        one to be one of these random design vars.

    Args
    ----
    n : number of randomized points to generate for each input value

    derivs : if True, use user-defined derivatives, else use Finite Difference
    """
    def __init__(self, n=10, derivs=False):
        super(BruteForceSellarProblem, self).__init__(impl=impl)

        root = self.root = Group()
        if not derivs:
            root.deriv_options['type'] = 'fd'

        sellars = root.add('sellars', ParallelGroup())
        for i in range(n):
            name = 'sellar%i' % i
            sellars.add(name, pboidGroup)
            #sellars.add(name, SellarDerivatives())

            root.connect('x_p', 'sellars.'+name+'.x')#, src_indices=[i])
            #root.connect('yaw0', 'sellars.'+name+'.yaw0')#, src_indices=[i])
            #root.connect('dist_z', 'sellars.'+name+'.z', src_indices=[i*2, i*2+1])

            root.connect('sellars.'+name+'.f_xy',  'collect.obj_%i'  % i)
            #root.connect('sellars.'+name+'.con1', 'collect.con1_%i' % i)
            #root.connect('sellars.'+name+'.con2', 'collect.con2_%i' % i)

        root.add('indep', IndepVarComp([
                    ('x', 1.0),
                    ('z', np.array([5.0, 2.0]))
                ]),
                promotes=['x', 'z'])

        root.add('random', Randomize(n=n, params=[
                    # name, value, std dev
                    ('x', 1.0, 1e-2),
                    ('z', np.array([5.0, 2.0]), 1e-2)
                ]),
                promotes=['x', 'z', 'dist_x', 'dist_z'])

        root.add('collect', Collector(n=n, names=['obj', 'con1', 'con2']),
                promotes=['obj', 'con1', 'con2'])

        # top level driver setup
        self.driver = ScipyOptimizer()
        self.driver.options['optimizer'] = 'SLSQP'
        self.driver.options['tol'] = 1.0e-8
        self.driver.options['maxiter'] = 50
        self.driver.options['disp'] = False

        self.driver.add_desvar('z', lower=np.array([-10.0,  0.0]),
                                    upper=np.array([ 10.0, 10.0]))
        self.driver.add_desvar('x', lower=0.0, upper=10.0)

        self.driver.add_objective('obj')
        self.driver.add_constraint('con1', upper=0.0)
        self.driver.add_constraint('con2', upper=0.0)

prob = BruteForceSellarProblem(100, derivs=False)
prob.setup(check=False)
prob.run()
print(prob["obj"])

我不完全确定您要建模的是什么,或者什么应该与什么相关联,但我可以帮助您克服该错误。问题是您在循环中多次添加抛物面组 pboidGroup 的相同实例,而 OpenMDAO 不支持在多个地方使用相同的组件实例。每次都需要创建一个新的实例。

为了解决这个问题,我只是将代码向下移动到靠近使用它的地方的循环中,所以在循环中我们得到:

        pboidGroup = Group()
        pboidGroup.add('p1', Paraboloid())
        pboidGroup.add('p2', Paraboloid())
        pboidGroup.connect('p1.x_p', 'p2.x_p')
        pboidGroup.connect('p1.x_p', 'p2.y')

        name = 'sellar%i' % i
        sellars.add(name, pboidGroup)
        #sellars.add(name, SellarDerivatives())

既然我已经这样做了,我在连接中遇到错误,而且我不确定 x_p 在根目录中意味着什么(也许它需要一个 IndepVarComp),但是也许这会让你超越停止点。