我正在尝试使自己熟悉OpenMDAO。我发现很难理解的一件事是集成/状态变量如何在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,我不知道在新版本中该如何工作(如果我什至正确理解了旧版本的话)
答案 0 :(得分:4)
这是有关ODE集成的讨论,您选择了一个非常复杂的主题,因为 1)有很多完成集成的方法(例如,显式Euler,RK4,BDF ...) 2)通过时间积分携带解析导数是非常复杂的。
对于#2,困难的根源正是您所确定的问题。如果您还想从组织成组的一组不同组件中构建ODE,则不能在单个组件中使用简单的for循环结构。
好消息是,已经有一个库可以为您处理所有时间积分:Dymos。截至2019年4月,OpenMDAO团队本身正在积极开发此库,并且仍在对API进行一些模式修订和功能添加。尽管API有点流畅,但我还是建议您看一下其中的示例。对于复杂的问题,这是您最好的选择。
但是,您无需额外的库就可以实现简单的时间分步欧拉集成。诀窍在于,您需要为每个时间步标记一个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在内部做事的方式大不相同,它们与及时矢量化的组件一起工作以提高效率。但是,如果您真的对OpenMDAO中最无效的简单时间积分方案感兴趣,就可以了。