如何在OpenMDAO 2.5.0中的可重配置模型中更新连接大小?

时间:2018-11-09 18:03:00

标签: openmdao

使用reconfigurable model execution可以调整组件的输入和输出的大小。连接重新配置的输出和输入时,如何更新连接?

在下面的示例中,每次运行模型时,将调整输出c2.yc3.y的大小。如N2图表所示,此输入和输出应该已连接。但是,重新配置后,连接大小似乎并未自动更新,因此会引发以下错误:

ValueError: The source and target shapes do not match or are ambiguous for the connection 'c2.y' to 'c3.y'. Expected (1,) but got (2,).

我提供了以下3种测试,其中包括升级后的连接,绝对连接以及最后一个带有重新配置但没有连接的测试(有效)。

最后一次机会是在comps的父组中声明连接,而我还没有尝试过。

N2 diagram of the example problem. <code>c2</code> and <code>c3</code> are reconfigurable components

测试:

  1. 升级后的连接
  2. 绝对连接
  3. 没有连接

可重新配置的组件类和测试:

from __future__ import division

import logging

import numpy as np
import unittest

from openmdao.api import Problem, Group, IndepVarComp, ExplicitComponent
from openmdao.utils.assert_utils import assert_rel_error


class ReconfComp(ExplicitComponent):

    def initialize(self):
        self.size = 1
        self.counter = 0

    def reconfigure(self):
        logging.info('reconf started {}'.format(self.pathname))
        self.counter += 1
        logging.info('reconf ended {}'.format(self.pathname))

        if self.counter % 2 == 0:
            self.size += 1
            return True
        else:
            return False

    def setup(self):
        logging.info('setup started {}'.format(self.pathname))
        self.add_input('x', val=1.0)
        self.add_output('y', val=np.zeros(self.size))
        # All derivatives are defined.
        self.declare_partials(of='*', wrt='*')
        logging.info('setup ended {}'.format(self.pathname))

    def compute(self, inputs, outputs):
        logging.info('compute started {}'.format(self.pathname))
        outputs['y'] = 2 * inputs['x']
        logging.info('compute ended {}'.format(self.pathname))

    def compute_partials(self, inputs, jacobian):
        jacobian['y', 'x'] = 2 * np.ones((self.size, 1))


class ReconfComp2(ReconfComp):
    """The size of the y input changes the same as way as in ReconfComp"""

    def setup(self):
        logging.info('setup started {}'.format(self.pathname))
        self.add_input('y', val=np.zeros(self.size))
        self.add_output('f', val=np.zeros(self.size))
        # All derivatives are defined.
        self.declare_partials(of='*', wrt='*')
        logging.info('setup ended {}'.format(self.pathname))

    def compute(self, inputs, outputs):
        logging.info('compute started {}'.format(self.pathname))
        outputs['f'] = 2 * inputs['y']
        logging.info('compute ended {}'.format(self.pathname))

    def compute_partials(self, inputs, jacobian):
        jacobian['f', 'y'] = 2 * np.ones((self.size, 1))


class TestReconfConnections(unittest.TestCase):

    def test_reconf_comp_promoted_connections(self):
        p = Problem()

        p.model = Group()
        p.model.add_subsystem('c1', IndepVarComp('x', 1.0), promotes_outputs=['x'])
        p.model.add_subsystem('c2', ReconfComp(), promotes_inputs=['x'], promotes_outputs=['y'])
        p.model.add_subsystem('c3', ReconfComp2(), promotes_inputs=['y'],
                              promotes_outputs=['f'])

        p.setup()
        p['x'] = 3.

        # First run the model once; counter = 1, size of y = 1
        p.run_model()
        totals = p.compute_totals(wrt=['x'], of=['y'])
        assert_rel_error(self, p['x'], 3.0)
        assert_rel_error(self, p['y'], 6.0)
        assert_rel_error(self, totals['y', 'x'], [[2.0]])
        print(p['x'], p['y'], totals['y', 'x'].flatten())

        # Run the model again, which will trigger reconfiguration; counter = 2, size of y = 2
        p.run_model()  # FIXME Fails with ValueError

    def test_reconf_comp_connections(self):
        p = Problem()

        p.model = Group()
        p.model.add_subsystem('c1', IndepVarComp('x', 1.0), promotes_outputs=['x'])
        p.model.add_subsystem('c2', ReconfComp(), promotes_inputs=['x'])
        p.model.add_subsystem('c3', ReconfComp2(), promotes_outputs=['f'])
        p.model.connect('c2.y', 'c3.y')
        p.setup()
        p['x'] = 3.

        # First run the model once; counter = 1, size of y = 1
        p.run_model()

        # Run the model again, which will trigger reconfiguration; counter = 2, size of y = 2
        p.run_model()  # FIXME Fails with ValueError

    def test_reconf_comp_not_connected(self):
        p = Problem()

        p.model = Group()
        p.model.add_subsystem('c1', IndepVarComp('x', 1.0), promotes_outputs=['x'])
        p.model.add_subsystem('c2', ReconfComp(), promotes_inputs=['x'])
        p.model.add_subsystem('c3', ReconfComp2(), promotes_outputs=['f'])
        # c2.y not connected to c3.y
        p.setup()
        p['x'] = 3.

        # First run the model once; counter = 1, size of y = 1
        p.run_model()

        # Run the model again, which will trigger reconfiguration; counter = 2, size of y = 2
        fail, _, _ = p.run_model()
        self.assertFalse(fail)


if __name__ == '__main__':
    unittest.main()

更新:

似乎,在Group._var_abs2meta中,仅源大小被更新,而不是目标大小。在建立父组或其他组件的连接之前,将开始建立连接。

更新2:

默认的NonlinearRunOnce求解器会发生这种情况,NewtonSolverNonlinearBlockGS时不会出错,但变量大小也不会改变。

1 个答案:

答案 0 :(得分:1)

从OpenMDAO V2.5开始,reconfigurable model variables在框架中不是官方支持的功能。自从完成这项研究以来,该功能的基本内容就一直存在于代码中,但这并不是我们最终确定的优先事项。 V2.4中最近的一项重大重构重新设计了某些基础数据结构的工作方式,并且必须破坏了此功能。

再次使它工作在我们的开发优先级列表中,但在该列表中并不算高。我们主要将开发重点放在具有直接内部应用程序的功能上,而我们还没有这些功能之一。

如果您可以为此提供一套不错的测试,我们可以看看如何使该功能正常工作。