【问题标题】:OpenMDAO parallelized sampling / why does test_brute_force.py not failOpenMDAO 并行采样/为什么 test_brute_force.py 不会失败
【发布时间】:2017-01-11 19:32:50
【问题描述】:

我修改了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"])

【问题讨论】:

    标签: openmdao


    【解决方案1】:

    我不完全确定您要建模什么,或者什么应该与什么相关联,但我可以帮助您克服这个错误。问题是您在循环中多次添加抛物面组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),但也许这会让你通过停止点。

    【讨论】:

    • 至于我要建模的内容,我想使用并行采样找到f_xy 的预期值,给定一个正态分布的x_p。我添加了连接以使其更接近我的实际问题。
    • 现在我收到错误Source 'p1.x_p' cannot be connected to target 'sellars.sellar0.x': 'p1.x_p' does not exist. 为什么在原始示例中dist_z 没有出现类似的错误?我们将 root 初始化为空的 Group() 我很惊讶我们可以毫无错误地引用 dist_z
    • 所以,问题出在root.connect('x_p', 'sellars.'+name+'.x')#, src_indices=[i])这一行。在这里,您将每个卖方系统中的 x_p 连接到源 x_p,该源需要是某个地方的输出,但没有任何东西提供。在原始示例中,我们连接到由Randomize 提供的“dist_x”。要匹配,您需要在 Randomize 组件中将 dist_x 重命名为 x_p 或连接到 dist_x。 (为此,您必须取消注释源索引)
    • 谢谢!现在这是有道理的。在我真正的问题中,我使用的是 FLORISSE 的AEPGroup。我只看到在Problem 中使用了它,在调用setup 之后设置了几个值,例如github.com/WISDEM/FLORISSE/blob/develop/test/exampleCall.py 中的第 48-62 行。在 sellars.add(name, AEPGroup(**kwargs)) 添加 AEPGroup 后,有没有办法可以将这些值设置为卖家中的每个 AEPGroup
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-25
    • 1970-01-01
    • 2020-07-20
    • 2016-04-11
    • 2021-10-10
    • 2013-01-19
    • 1970-01-01
    相关资源
    最近更新 更多