如何将离散时间估计集成到 OpenMDAO 组件中?

How to integrate discrete time estimation into OpenMDAO Components?

我正在尝试熟悉 OpenMDAO。我发现很难理解的一件事是 integrated/state 变量如何在 OpenMDAO 的单独组件中工作。我认为这很重要,我想了解基础知识。

假设我有一个推力恒定且质量不断变化的火箭,我想用离散时间和 0.1 秒的时间步长对前 10 秒的飞行进行建模。假设不断变化的质量也是时间的函数,但也假装它是一个依赖于许多事物的困难变量,所以我们希望在不同的组件中计算它并简单地作为该组件的输入。

-如何确保质量在速度的离散时间步长计算中更新,同时处于不同的组件中?

下面的代码是我试图解决这个示例问题的平庸尝试,但我现在相当确定 Mass 是一个简单的静态输入,而它应该随时间以某种任意关系发生变化。 (假设推力不变)

from openmdao.api import ExplicitComponent

class VelocityComponent(ExplicitComponent):

    def setup(self):
        self.add_input('T', desc='Propulsion Thrust')
        self.add_input('M', desc='Instanteneous Mass')\

        self.add_output('v', desc='Satellite Velocity')

        self.declare_partials('*','*')

    def compute(self, inputs, outputs)

        v = 10                          #some initial velocity value
        t = 0                           #initial time value
        tstep = 0.1
        tend = 10

        for i in range(0,tend/tstep):
            a = inputs['T']/inputs['M'] #calculate acceleration  
            v += a                      #update velocity
            t += tstep                  #next time step 

        outputs['v'] = v 

速度 v 应与随时间变化的加速度 a 相结合,而不是恒定加速度。

PS:由于我对所有这一切都比较陌生,但愿意学习,因此非常感谢任何可以帮助像我这样的初学者使用 OpenMDAO 的资源提示。

PSPS: 我已阅读 OpenMDAO 文档的初学者和高级用户指南,但找不到包含集成变量的示例。旧文档有一个 engine and transmission system 的示例,它的引擎组件确实包含状态变量和一些离散的集成步骤,但它使用旧的 OpenMDAO 版本,我不知道它在新版本中如何工作(如果我什至正确理解旧的)

这是关于 ODE 积分的讨论,您选择了一个相当复杂的主题,因为 1) 有很多不同的方法可以完成积分(例如显式 Euler、RK4、BDF ...) 2)通过时间积分携带解析导数是非常棘手的。

对于#2,困难的根源正是您所确定的问题。如果您还想将一组不同的组件组织成一个组来构造 ODE,则不能在单个组件内使用简单的 for 循环结构。

好消息是已经编写了一个库来为您处理所有时间积分:Dymos。截至 2019 年 4 月,该库由 OpenMDAO 团队自己积极开发,并且仍在进行某些模式 API 修订和功能添加。尽管 APIs 有点流畅,我还是建议您看一下那里的示例。对于复杂的问题,这是最好的选择。

但是,您无需额外的库即可实现简单的时间步长欧拉积分。诀窍是您为每个时间步消除 ODE 的一个实例,并将新状态从一个实例传递到下一个实例。 这是一个在恒定重力下坠落物体的简单示例:

from openmdao.api import IndepVarComp, Problem, ExplicitComponent


class Cannonball(ExplicitComponent): 

    def initialize(self): 

        self.options.declare('delta_t', default=0.1)

    def setup(self): 

        self.add_input('Yi', units='m',   desc='position at the start of the time-step')
        self.add_input('Vi', units='m/s', desc='velocity at the start of the time-step')

        self.add_output('Ye', units='m',   desc='position at the end of the time-step')
        self.add_output('Ve', units='m/s', desc='velocity at the end of the time-step')

        self.declare_partials(of='*', wrt='*', method='cs')


    def compute(self, inputs, outputs): 

        dt = self.options['delta_t']

        outputs['Ve'] = 9.81 * dt + inputs['Vi']
        outputs['Ye'] = 0.5 * 9.81 * dt**2 + inputs['Vi'] * dt + inputs['Yi']


if __name__ == "__main__": 
    import numpy as np
    import matplotlib.pylab as plt

    N_TIMES = 10

    p = Problem()

    ivc = p.model.add_subsystem('init_conditions', IndepVarComp(), promotes=['*'])
    ivc.add_output('Y0', 100., units='m')
    ivc.add_output('V0', 0, units='m/s')

    p.model.connect('Y0', 't_0.Yi')
    p.model.connect('V0', 't_0.Vi')

    for i in range(N_TIMES): 
        p.model.add_subsystem(f't_{i}', Cannonball())


    for i in range(N_TIMES-1): 
        p.model.connect(f't_{i}.Ye', f't_{i+1}.Yi')
        p.model.connect(f't_{i}.Ve', f't_{i+1}.Vi')

    p.setup()

    p.run_model()


    # collect the data into an array for plotting
    Y = [p['Y0'],]
    V = [p['V0'],]
    for i in range(N_TIMES): 
        Y.append(p[f't_{i}.Ye'])
        V.append(p[f't_{i}.Ve'])

    times = np.arange(N_TIMES+1) * .01 # delta_t

    fig, ax = plt.subplots()
    ax.plot(times, Y)
    ax.set_ylabel('velocity (m/s')
    ax.set_xlabel('time (s)')

    plt.show()

这将为您的模型提供时间结构的前馈(使用 OpenMDAO's built-in N2 viewer).

你可以看到你得到了预期的时间二次方位置。

您可以通过将不同的 ODE 方案编码到此组件(例如 RK4)中来进行更复杂的集成。您还可以编写一个由多个组件组成的更复杂的组作为您的时间步长,然后剔除该组的多个副本。

我想强调的是,虽然上面的示例有助于加深理解,但这并不是在 OpenMDAO 中实际进行时间积分的非常有效的方法。 Dymos 在内部做事非常不同,使用及时向量化的组件以提高效率。 None 更少,如果你真的对 OpenMDAO 中最简单的时间积分方案感兴趣......就是这样。