【问题标题】:Computing the Same Results MATLAB ODE45 and Python计算相同的结果 MATLAB ODE45 和 Python
【发布时间】:2021-01-17 19:28:16
【问题描述】:

我试图了解如何在 Python 中获得与在 MATLAB 中相同的结果。附件是我尝试过的源代码,两种不同方法的结果都不正确。代码底部是 MATLAB 的预期解决方案。对此问题的任何帮助将不胜感激。

from scipy.integrate import ode
from scipy import integrate
import numpy as np


def function2(x, mu):
    x, y, z = x
    r1 = np.sqrt((x + mu) ** 2 + (y ** 2) + (z ** 2))
    r2 = np.sqrt((x - (1 - mu)) ** 2 + (y ** 2) + (z ** 2))
    u1_x = 1 - (1 - mu) * (1 / (r1 ** 3) - 3 * ((x + mu) ** 2) / (r1 ** 5)) - \
           mu * (1 / (r2 ** 3) - 3 * ((x - (1 - mu)) ** 2) / (r2 ** 5))
    u2_y = 1 - (1 - mu) * (1 / (r1 ** 3)) - 3 * y ** 2 / (r1 ** 5) - \
           mu * (1 / r2 ** 3 - 3 * y ** 2 / r2 ** 5)
    u3_z = (-1) * (1 - mu) * (1 / r1 ** 3) - 3 * z ** 2 / r1 ** 5 - mu * \
           (1 / r2 ** 3 - 3 * z ** 2 / r2 ** 5)
    u1_y = 3 * (1 - mu) * y * (x + mu) / r1 ** 5 + \
           3 * mu * y * (z - (1 - mu)) / r2 ** 5
    u1_z = 3 * (1 - mu) * z * (x + mu) / r1 ** 5 + \
           3 * mu * z * (x - (1 - mu)) / r2 ** 5
    u2_z = 3 * (1 - mu) * y * z / r1 ** 5 + 3 * mu * y * z / r2 ** 5
    u3_y = u2_z
    u2_x = u1_y
    u3_x = u1_z
    gmatrix = np.array([[u1_x, u1_y, u1_z],
                        [u2_x, u2_y, u2_z],
                        [u3_x, u3_y, u3_z]])
    return gmatrix


def function(t, y, mu):
    x = y[36:39]
    GMatrix = function2(x, mu)
    OxO = np.zeros([3, 3])
    Ind = np.identity(3)
    K = np.array([[0, 2, 0], [-2, 0, 0], [0, 0, 0]])
    Df = np.bmat([[OxO[0], Ind[0]],
                  [OxO[1], Ind[1]],
                  [OxO[2], Ind[2]],
                  [GMatrix[0], K[0]],
                  [GMatrix[1], K[1]],
                  [GMatrix[2], K[2]]])
    Df = np.reshape(Df, (6, 6))
    A_temp = np.squeeze(np.array(y))
    A_temp = A_temp.flatten()
    B_temp = [0]*42
    for i in range(len(A_temp)):
       B_temp[i] = A_temp[i]
    B_temp = B_temp[:-6]
    B_temp = np.array(B_temp)
    A = B_temp.reshape(6, 6)
    DfA = np.matmul(Df, A)
    a = [0] * 36
    b = np.squeeze(np.array(DfA))
    b = b.flatten()
    for i in range(len(b)):
        a[i] = b[i]
    r1 = np.sqrt((mu+y[36])**2 + (y[37]**2) + (y[38]**2))
    r2 = np.sqrt((1-mu-y[36])**2 + (y[37]**2) + (y[38]**2))
    m1 = 1 - mu
    m2 = mu
    c = [y[39],
         y[40],
         y[41],
         y[36] + 2 * y[40] + m1 * (-mu - y[36]) / (r1**3) + m2 * (1-mu-y[36]) / (r2**3),
         y[37] - 2 * y[39] - m1 * (y[37]) / (r1**3) - m2 * y[37] / (r2**3),
         -m1 * y[38] / (r1**3) - m2 * y[38] / (r2**3)]
    ydot = a + c
    return ydot

将集成 ODE 的 driver


if __name__ == '__main__':
    t0 = 0
    tf = 1.450000000000000
    mu = 3.054248395728148e-06
    x_n = [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0,
           0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
           0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0, 0, 0, 0, 0, 0, 1.0,
           0.9919755553772727, 0.0, -0.0018716577540106951,
           0.0, -0.0117506137115032, 0.0]
    #meth = 'adams'
    meth = 'bdf'
    r = ode(function).set_integrator('vode',method=meth,rtol=1e-13,
                                                        atol=1e-22,                                                      
                                      with_jacobian=False)
    r.set_initial_value(x_n,t0).set_f_params(mu)
    r.integrate(tf)
    temp = r.y
    index2 = [41, 40, 39, 38, 37, 36]
    temp = np.delete(temp,index2)
    temp = temp.reshape(6,6)
    time = [t0, tf]
    states = integrate.solve_ivp(fun=lambda t, y:
                                 function(t, x_n, mu),
                                 t_span=time, y0=x_n, method='LSODA', dense_output=True,
                                 rtol=1e-13,atol=1e-22)
    new_time = states.t
    new_temp = states.y[:,-1]
    index2 = [41, 40, 39, 38, 37, 36]
    new_temp = np.delete(new_temp,index2)
    new_temp = new_temp.reshape(6,6)
    print(new_temp)
    print(temp)

期望的解决方案 // MATLAB ode45 & ode113 结果相同

enter image description here

这是我正在编写的更多脚本系列的一部分,我不希望我的代码在 MATLAB 中。我知道 MATLAB 的答案是正确的,因为最终解决方案提供了我想要创建的所需轨道。我还应该注意,看起来 MATLAB 使用的是自适应步长,而不是像 Python np.linspace(start,end,step)

那样创建的预定义时间序列

建议的方法是 ivp_solver rk45,dense_out = true enter image description here

但是这种方法也不能提供正确的结果。 以下是该方法的结果: enter image description here

更新:当我使用 MATLAB 使用的第一个时间步在纸上手动计算 RK45 时,我得到了相同的答案。此外,当我强制时间序列使用第一个时间间隔时,我会得到与 solve_ivp->RK45 相同的答案,但结果是密集的。然而,即使使用 MATLAB 中的相同完整时间序列,我得到的结果也与 MATLAB 不同。

@Lutz Lehmann 在对各种不同的方法进行了一些研究和测试之后,您认为 r.integrate 只集成一次是正确的。为了在每个点上进行积分,需要一个循环。此外,我能够让 ode 和 solve_ivp 得到相同的答案(尽管它是错误的答案)。使用 solve_ivp 时,我必须执行以下操作,这在使用 ode 时给出了相同的答案。

    r = integrate.solve_ivp(fun=lambda t, y: function(t, y, mu),
                          t_span=time, y0=y, method='RK45', dense_output=True,
                            rtol=1e-13, atol=1e-22)
    i = 0
    while r.t[i] < tf:
        r = integrate.solve_ivp(fun=lambda t, y:  function(t, y, mu),
                                t_span=time, y0=y, method='RK45', dense_output=True,
                                rtol=1e-13, atol=1e-22)
        print(r.t[i])
        i += 1
    new_time = r.t
    new_temp = r.y[:, -1]
    index2 = [41, 40, 39, 38, 37, 36]
    new_temp = np.delete(new_temp, index2)
    print(new_temp)

    r = ode(function)
    r.set_integrator('vode', method='bdf', rtol=1e-13, atol=1e-22, with_jacobian=False)
    r.set_initial_value(y, t0)
    r.set_f_params(mu)
    r.integrate(tf)

    t = []
    Y = [y]

    while r.t < tf:
        r.integrate(tf, step=True)
        Y = np.vstack((Y, [r.y]))
        t.append([r.t])

    new_temp = Y[-1, :]
    index2 = [41, 40, 39, 38, 37, 36]
    new_temp = np.delete(new_temp, index2)
    test = new_temp.reshape(6,6)
    print(test)

我应该注意到,与使用 ode 相比,使用 solve_ivp 的方法要慢得多。产生相同结果的速度差异可能意味着 ode 是首选方法(不确定)。

这就是我得到的解决方案。 enter image description here

不幸的是,这意味着根据您上次发布的最新更新,我回到了我开始的地方。 ODE 和 solve_ivp 提供了相同的答案,但这仍然不是解决方案。

【问题讨论】:

  • 您可以轻松调试 ODE 函数——传递相同的输入并查看它们是否从 Matlab 到 Python 产生相同的输出。如果可行,请检查 Ode 求解器——检查您是否使用相同的方法,您的语法是否正确。不过很难回答您的问题,因为您的帖子格式不太好(格式不一致,空格很多,难以阅读)。
  • Python 也使用自适应时间步进。就像您可以在 Matlab 中的指定时间强制评估一样。快速使用情况的主要区别在于solve_ivp-&gt;RK45 没有Refine 选项,如ode45(默认值为4,附加点的插值)。因此,必须使用密集输出来填补空白以绘制合理的图表。
  • @David 在调试函数时,初始时间步长确实会产生相同的结果,但是错误的是 ode 求解器。我不知道如何更正解决方案以产生相同的结果。对不起,当我复制并通过代码表单蜘蛛时,它似乎出现了格式,引入了很多空格。如果您删除空格并更正格式,它将在任何给定的 python ide 中运行。
  • @LutzLehmann 我确实尝试了具有密集输出的solve_ivp -> rk45,不幸的是,与所提供的方法和matlab ode45相比,它产生的结果误差更大。我担心自适应步长大小不一样,并且由于 ode 的刚度,它会产生不正确的结果。如果您愿意,我可以发布使用 rk45 和 dense_output = True 的 solve_ivp 的结果。
  • @LutzLehmann 我无法在 cmets 中发布您建议的方法,因此我将其添加到原始帖子中。现在可以在问题的底部看到方法和输出。

标签: python matlab scipy ode orbital-mechanics


【解决方案1】:
  • solve_ivp调用中你定义的函数错误,正确的方法是
fun=lambda t, y:  function(t, y, mu)
  • 函数r.integrate 中的ode 求解器似乎最多执行一个内部步骤。要到达最后一次tf,您必须循环此调用:
while r.t < tf: r.integrate(tf)

那么两者的结果足够重合,在前 10 位左右。有关 Matlab 结果差异的来源,请参见最后一节。


PS:你可以大幅减少第二个功能,所有的扁平化和复制都不是必须的。我把它改写为

def function(t, u, mu):
    A = np.reshape(u[:36],[6,6])
    x = u[36:39]
    v = u[39:]

    GMatrix = function2(x, mu)
    OxO = np.zeros([3, 3])
    Ind = np.identity(3)
    K = np.array([[0, 2, 0], [-2, 0, 0], [0, 0, 0]])
 
    Df = np.block([[OxO, Ind],
                   [GMatrix, K]])
    DfA = np.matmul(Df, A)

    x,y,z = x
    vx,vy,vz = v
    r1 = np.sqrt((mu+x)**2 + (y**2) + (z**2))
    r2 = np.sqrt((1-mu-x)**2 + (y**2) + (z**2))
    m1 = 1 - mu
    m2 = mu

    a = [x + 2 * vy - m1 * (mu + x) / (r1**3) + m2 * (1-mu-x) / (r2**3),
         y - 2 * vx - m1 * y / (r1**3) - m2 * y / (r2**3),
         -m1 * z / (r1**3) - m2 * z / (r2**3)]

    ydot = np.concatenate([DfA.flatten(), v, a])
    return ydot

在计算function2 中的潜力的黑森州时,索引和括号放置有许多小错误。重新组织并使用更多结构变量,函数看起来像

def function2(x, mu):
    ce1, ce2 = -mu, 1-mu
    m1, m2   = 1-mu, mu
    r1 = ((x[0]-ce1)**2+x[1]**2+x[2]**2)**0.5
    r2 = ((x[0]-ce2)**2+x[1]**2+x[2]**2)**0.5
 
    u1_x = 1 - m1 * (1 / r1**3 - 3 * (x[0] - ce1)**2 / r1**5) \
             - m2 * (1 / r2**3 - 3 * (x[0] - ce2)**2 / r2**5)

    u1_y = 3 * m1 * x[1] * (x[0] - ce1) / r1**5 \
         + 3 * m2 * x[1] * (x[0] - ce2) / r2**5

    u1_z = 3 * m1 * x[2] * (x[0] - ce1) / r1**5 \
         + 3 * m2 * x[2] * (x[0] - ce2) / r2**5

    u2_x = u1_y
    
    u2_y = 1 - m1 * (1 / r1**3 - 3 * x[1]**2 / r1**5) \
             - m2 * (1 / r2**3 - 3 * x[1]**2 / r2**5)

    u2_z = 3 * m1 * x[1] * x[2] / r1**5 + 3 * m2 * x[1] * x[2] / r2**5

    u3_x = u1_z
    u3_y = u2_z

    u3_z = - m1 * (1 / r1**3 - 3 * x[2]**2 / r1**5) \
           - m2 * (1 / r2**3 - 3 * x[2]**2 / r2**5)

    GMatrix = np.array([[u1_x, u1_y, u1_z],
                        [u2_x, u2_y, u2_z],
                        [u3_x, u3_y, u3_z]])

    return GMatrix

这给出了结果

[ 23.55077315 -0.39182483  3.67078856  4.77188265  4.55322364  0.54862501]
[ -8.519012   -0.71609406 -1.5344374  -2.12546806 -1.4395364  -0.23934585]
[ -0.37941138  0.11874396 -1.39417439 -0.10224713 -0.13959515  0.19607223]
[ 43.56974185 -1.72339855  6.85563491  8.62759039  8.39374083  0.9221739 ]
[-28.39640391 -0.10433561 -4.47605353 -5.56490582 -6.06643015 -0.69034888]

据我所知,这与 Matlab 的结果一致。

【讨论】:

  • 函数已经被反复检查到 ode 并且第一次迭代的结果匹配,所以我相信函数没有错。话虽如此,它们可能不是最 Pythonic 的。我能够测试您的函数版本是否也产生相同的结果,因此感谢您提供更清洁的版本。我还测试了你的有趣输入与我的输入,它没有任何区别,因此没有证据支持我的修正版本不正确,唯一可见的区别是将附加的solve_ivp中的x_n更改为y。我目前在工作
  • 关于函数 r.integrate 中的 ode 求解器的评论似乎最多执行一个内部步骤。要达到最后的时间 tf,你必须循环这个调用: while rt
  • 解决方案我确实要感谢您提供的内容,并希望它确实会导致正确的答案。需要进一步测试,一旦我有最终解决方案,我会发布它。
  • 我根据您在答案块中的 cmets 在帖子中添加了更多内容。不幸的是,这让我离解决方案更近了一步。不过我会说你提出了一些好的观点,并帮助我理解了集成方法。
  • 哇,你做到了,你是 100% 正确的。我非常感谢你,我相信如果我真的见到你,我欠你一杯啤酒。
猜你喜欢
  • 2012-08-19
  • 2013-04-09
  • 2013-05-20
  • 2017-03-02
  • 1970-01-01
  • 2020-02-14
  • 2021-04-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多