【问题标题】:Cannot get RK4 to solve for position of orbiting body in Python无法让 RK4 在 Python 中求解轨道体的位置
【发布时间】:2018-12-06 06:17:37
【问题描述】:

我正在尝试使用更大质量的物体不会移动的理想化来解决围绕更大质量物体运行的物体的位置。我正在尝试使用python中的四阶龙格库塔来解决笛卡尔坐标中的位置。

这是我的代码:

dt = .1
t = np.arange(0,10,dt)

vx = np.zeros(len(t))
vy = np.zeros(len(t))
x = np.zeros(len(t))
y = np.zeros(len(t))

vx[0] = 10 #initial x velocity
vy[0] = 10 #initial y velocity
x[0] = 10 #initial x position
y[0] = 0 #initial y position

M = 20

def fx(x,y,t): #x acceleration
     return -G*M*x/((x**2+y**2)**(3/2))

def fy(x,y,t): #y acceleration
     return -G*M*y/((x**2+y**2)**(3/2))

def rkx(x,y,t,dt): #runge-kutta for x

     kx1 = dt * fx(x,y,t)
     mx1 = dt * x
     kx2 = dt * fx(x + .5*kx1, y + .5*kx1, t + .5*dt)
     mx2 = dt * (x + kx1/2)
     kx3 = dt * fx(x + .5*kx2, y + .5*kx2, t + .5*dt)
     mx3 = dt * (x + kx2/2)
     kx4 = dt * fx(x + kx3, y + x3, t + dt)
     mx4 = dt * (x + kx3)

     return (kx1 + 2*kx2 + 2*kx3 + kx4)/6
     return (mx1 + 2*mx2 + 2*mx3 + mx4)/6

 def rky(x,y,t,dt): #runge-kutta for y

     ky1 = dt * fy(x,y,t)
     my1 = dt * y
     ky2 = dt * fy(x + .5*ky1, y + .5*ky1, t + .5*dt)
     my2 = dt * (y + ky1/2)
     ky3 = dt * fy(x + .5*ky2, y + .5*ky2, t + .5*dt)
     my3 = dt * (y + ky2/2)
     ky4 = dt * fy(x + ky3, y + ky3, t + dt)
     my4 = dt * (y + ky3)

     return (ky1 + 2*ky2 + 2*ky3 + ky4)/6
     return (my1 + 2*my2 + 2*my3 + my4)/6

for n in range(1,len(t)): #solve using RK4 functions
    vx[n] = vx[n-1] + fx(x[n-1],y[n-1],t[n-1])*dt
    vy[n] = vy[n-1] + fy(x[n-1],y[n-1],t[n-1])*dt
    x[n] = x[n-1] + vx[n-1]*dt
    y[n] = y[n-1] + vy[n-1]*dt

最初,无论我以哪种方式调整代码,我的 for 循环都会出现错误,“'float' 类型的对象没有 len()”(我不明白 float python 可能指的是什么to),或“使用序列设置数组元素”(我也不明白它的意思是什么)。我设法摆脱了错误,但我的结果是错误的。我得到了 10s 的 vx 和 vy 数组,一个从 10. 到 109. 的整数 x 数组,以及一个从 0. 到 99. 的整数数组。

我怀疑 fx(x,y,t) 和 fy(x,y,t) 或我将 runge-kutta 函数编码为与 fx 和 fy 一起使用的方式存在问题,因为我使用过其他功能的相同 runge-kutta 代码,它工作正常。

非常感谢任何帮助我找出我的代码为什么不起作用的帮助。谢谢。

【问题讨论】:

  • 你不能在你的函数中使用两个 return 语句。如果您想从一个函数返回多个值,可以将它们放在listtuple 中。例如,假设您想从函数 myfunc(*args, **kwargs) 中返回 ab,您可以执行 return (a, b)。那么当你拨打myfunc时,你可以拨打value1, value2 = myfunc(*args, **kwargs)。您可以将其应用于函数 `**rkx** 和 rky

标签: python runge-kutta orbital-mechanics


【解决方案1】:

物理

牛顿定律为您提供二阶 ODE u''=F(u)u=[x,y]。使用v=[x',y'],您将获得一阶系统

u' = v
v' = F(u)

这是 4 维的,必须使用 4 维状态来解决。唯一可用的减少是使用开普勒定律,它允许将系统减少到角度的标量阶一 ODE。但这不是这里的任务。

但是为了得到正确的比例,对于半径为R 和角速度w 的圆形轨道,一个得到标识w^2*R^3=G*M,这意味着沿轨道的速度是w*R=sqrt(G*M/R) 和周期T=2*pi*sqrt(R^3/(G*M))。对于给定的数据,R ~ 10w ~ 1,因此G*M ~ 1000 用于接近圆形的轨道,因此对于M=20,这将需要在50200 之间的G,具有轨道周期大约2*pi ~ 6。 10的时间跨度可以代表一半到大约2或3个轨道。

欧拉法

您正确实现了 Euler 方法来计算代码最后一个循环中的值。它可能看起来不真实可能是因为欧拉方法不断增加轨道,因为它沿着切线移动到凸轨迹的外部。在您的实现中,可以看到 G=100 的这种向外螺旋。

这可以通过选择较小的步长来减少,例如dt=0.001

您应该选择积分时间作为完整轨道的一个很好的部分以获得可呈现的结果,使用上述参数您可以获得大约 2 个循环,这很好。

RK4 实现

你犯了几个错误。不知何故你失去了速度,位置更新应该基于速度。

那么您应该在fx(x + .5*kx1, y + .5*kx1, t + .5*dt) 停下来重新考虑您的方法,因为这与任何命名约定都不一致。一致、正确的变体是

fx(x + .5*kx1, y + .5*ky1, t + .5*dt) 

这表明您无法解耦耦合系统的集成,因为您需要 y 更新以及 x 更新。此外,函数值是加速度,因此更新速度。位置更新使用当前状态的速度。因此,该步骤应以

开头
 kx1 = dt * fx(x,y,t) # vx update
 mx1 = dt * vx        # x update
 ky1 = dt * fy(x,y,t) # vy update
 my1 = dt * vy        # y update

 kx2 = dt * fx(x + 0.5*mx1, y + 0.5*my1, t + 0.5*dt)
 mx2 = dt * (vx + 0.5*kx1)
 ky2 = dt * fy(x + 0.5*mx1, y + 0.5*my1, t + 0.5*dt)
 my2 = dt * (vy + 0.5*ky1)

等等

但是,如您所见,这已经开始变得笨拙了。将状态组装成一个向量,并为系统方程使用向量值函数

M, G = 20, 100
def orbitsys(u):
     x,y,vx,vy = u
     r = np.hypot(x,y)
     f = G*M/r**3
     return np.array([vx, vy, -f*x, -f*y]);

然后您可以使用 Euler 或 Runge-Kutta 步骤的食谱实现

def Eulerstep(f,u,dt): return u+dt*f(u)

def RK4step(f,u,dt):
    k1 = dt*f(u)
    k2 = dt*f(u+0.5*k1)
    k3 = dt*f(u+0.5*k2)
    k4 = dt*f(u+k3)
    return u + (k1+2*k2+2*k3+k4)/6

并将它们组合成一个集成循环

def Eulerintegrate(f, y0, tspan):
    y = np.zeros([len(tspan),len(y0)])
    y[0,:]=y0
    for k in range(1, len(tspan)):
        y[k,:] = Eulerstep(f, y[k-1], tspan[k]-tspan[k-1])
    return y


def RK4integrate(f, y0, tspan):
    y = np.zeros([len(tspan),len(y0)])
    y[0,:]=y0
    for k in range(1, len(tspan)):
        y[k,:] = RK4step(f, y[k-1], tspan[k]-tspan[k-1])
    return y

并根据您给定的问题调用它们

dt = .1
t = np.arange(0,10,dt)
y0 = np.array([10, 0.0, 10, 10])

sol_euler = Eulerintegrate(orbitsys, y0, t)
x,y,vx,vy = sol_euler.T
plt.plot(x,y)

sol_RK4 = RK4integrate(orbitsys, y0, t)
x,y,vx,vy = sol_RK4.T
plt.plot(x,y)

【讨论】:

  • 感谢您的帮助,LutzL。现在查看我的 RK 代码,我可以看到您正在谈论的几个错误。我修复了它们,现在它可以工作了。
  • 我认为使用反向欧拉可以摆脱增加轨道的问题。但无论如何,就集成时引入的错误而言,RK4 是一种更好的方法。
  • @SembeiNorimaki :然后你会遇到一个递减的轨道问题,因为隐式欧拉的行为就像显式欧拉在时间上倒退一样。交替显式和隐式步骤给出了辛的中点方法。 Verlet 集成的任何变体也将提供更好的结果。
  • @LutzL 你能发布完整的代码吗?我正在尝试将您的解决方案集成到 OPs 代码中,但我遇到了困难。包含此解决方案以运行的最小代码将不胜感激。谢谢
  • @SembeiNorimaki :完成,面向矢量的版本。要生成上面的欧拉图,只需在OP代码中加上G=100,就可以把RK4相关的程序删掉,因为它们什么都不做。在新代码末尾执行plt.grid(); plt.show() 应该会显示类似的图。
【解决方案2】:

您没有在任何地方使用rkxrky 函数! 您应该使用函数定义末尾的两个return return [(kx1 + 2*kx2 + 2*kx3 + kx4)/6, (mx1 + 2*mx2 + 2*mx3 + mx4)/6](正如@eapetcho 指出的那样)。另外,我不清楚您对 Runge-Kutta 的实施。

您有dv/dt,因此您求解v,然后相应地更新r

for n in range(1,len(t)): #solve using RK4 functions
    vx[n] = vx[n-1] + rkx(vx[n-1],vy[n-1],t[n-1])*dt
    vy[n] = vy[n-1] + rky(vx[n-1],vy[n-1],t[n-1])*dt
    x[n] = x[n-1] + vx[n-1]*dt
    y[n] = y[n-1] + vy[n-1]*dt

这是我的代码版本

import numpy as np

#constants
G=1
M=1
h=0.1

#initiating variables
rt = np.arange(0,10,h)
vx = np.zeros(len(rt))
vy = np.zeros(len(rt))
rx = np.zeros(len(rt))
ry = np.zeros(len(rt))

#initial conditions
vx[0] = 10 #initial x velocity
vy[0] = 10 #initial y velocity
rx[0] = 10 #initial x position
ry[0] = 0 #initial y position

def fx(x,y): #x acceleration
     return -G*M*x/((x**2+y**2)**(3/2))

def fy(x,y): #y acceleration
     return -G*M*y/((x**2+y**2)**(3/2))

def rk4(xj, yj):
    k0 = h*fx(xj, yj)
    l0 = h*fx(xj, yj)

    k1 = h*fx(xj + 0.5*k0 , yj + 0.5*l0)
    l1 = h*fy(xj + 0.5*k0 , yj + 0.5*l0)

    k2 = h*fx(xj + 0.5*k1 , yj + 0.5*l1)
    l2 = h*fy(xj + 0.5*k1 , yj + 0.5*l1)

    k3 = h*fx(xj + k2, yj + l2)
    l3 = h*fy(xj + k2, yj + l2)

    xj1 = xj + (1/6)*(k0 + 2*k1 + 2*k2 + k3)
    yj1 = yj + (1/6)*(l0 + 2*l1 + 2*l2 + l3)
    return (xj1, yj1)

for t in range(1,len(rt)):
    nv = rk4(vx[t-1],vy[t-1])
    [vx[t],vy[t]] = nv
    rx[t] = rx[t-1] + vx[t-1]*h
    ry[t] = ry[t-1] + vy[t-1]*h

我怀疑 fx(x,y,t) 和 fy(x,y,t) 存在问题

就是这样,我刚刚检查了fx=3fy=y 的代码,我得到了一个不错的轨迹。

这是ryrx 的情节:

【讨论】:

  • 行星运动的牛顿定律是二阶DE,需要4个状态变量,2个位置,2个速度。
  • 您的“更正”只是用另一个错误替换了一个错误。 ODE 是u''=F(u),其中u=[x,y]。对于v=[vx,vy],一阶系统是u'=v; v'=F(u),它不能解耦成一个独立的二维系统(至少在不调用开普勒定律的情况下不能)。
  • @LutzL 先生,编辑“更正”不是根据您的建议进行的,我只是试图在最后一个循环中更正 OP 的实现。我不明白您在第一条评论中要指出什么,因为我只是在考虑代码。我没有注意问题背后的物理学。您的回答在物理和实现方面都更有见地。同时也指出了两种数值方法的区别。
  • 除了最后一个循环是正确的,只是不是标题和问题中声称的方法。原则上,您的更正会生成一个有效的代码,适用于u'=v; v'=F(v); 系统,例如具有空气摩擦的弹道学。但即便如此,位置积分也只有 1 阶正确。
  • 谢谢你,SamD97。我现在意识到我根本没有使用我的 RK 功能......多么愚蠢。这次我真正实现了它们并让它们工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 2021-06-13
相关资源
最近更新 更多