Не удается заставить RK4 определить положение вращающегося тела в Python

Я пытаюсь найти положение тела, вращающегося вокруг гораздо более массивного тела, используя идеализацию, согласно которой более массивное тело не двигается. Я пытаюсь найти положение в декартовых координатах, используя Рунге-Кутта 4-го порядка в 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 ()» (я не понимал, на что может ссылаться python с плавающей точкой), или «установка элемента массива с последовательностью» (я тоже не понял, о какой последовательности идет речь). Мне удалось избавиться от ошибок, но мои результаты ошибочны. Я получаю массивы vx и vy из 10, массив x целых чисел от 10 до 109 и массив y целых чисел от 0 до 99.

Я подозреваю, что есть проблемы с fx (x, y, t) и fy (x, y, t) или с тем, как я закодировал функции runge-kutta для работы с fx и fy, потому что я использовал тот же самый runge -kutta код для других функций, и он отлично работает.

Я очень ценю любую помощь в выяснении, почему мой код не работает. Спасибо.


person corgiworld    schedule 06.12.2018    source источник
comment
Вы не можете использовать в своей функции два оператора return. Если вы хотите вернуть более одного значения из функции, вы можете поместить их в list или tuple. Например, предположим, что вы хотите вернуть a и b из своей функции myfunc(*args, **kwargs), вы можете сделать return (a, b). Затем, когда вы вызываете myfunc, вы можете сделать value1, value2 = myfunc(*args, **kwargs). Вы можете применить это к функциям `** rkx ** и rky   -  person eapetcho    schedule 06.12.2018


Ответы (2)


Физика

Закон Ньютона дает вам ОДУ второго порядка u''=F(u) с u=[x,y]. Используя v=[x',y'], вы получаете систему первого порядка

u' = v
v' = F(u)

которая является 4-мерной и должна быть решена с использованием 4-мерного состояния. Единственная доступная редукция - это использование законов Кеплера, которые позволяют уменьшить систему до скалярного порядка одного ОДУ для угла. Но задача не в этом.

Но чтобы получить правильные масштабы, для круговой орбиты радиуса R с угловой скоростью w получается тождество w^2*R^3=G*M, которое подразумевает, что скорость по орбите равна w*R=sqrt(G*M/R) с периодом T=2*pi*sqrt(R^3/(G*M)). С приведенными данными R ~ 10, w ~ 1, таким образом G*M ~ 1000 для орбиты, близкой к круговой, поэтому с M=20 для этого потребуется G между 50 и 200 с периодом обращения около 2*pi ~ 6. Временной интервал в 10 может соответствовать от половины до 2 или 3 витков.

Метод Эйлера

Вы правильно реализовали метод Эйлера для вычисления значений в последнем цикле вашего кода. То, что это может выглядеть нефизическим, может быть связано с тем, что метод Эйлера непрерывно увеличивает орбиту, поскольку он перемещается за пределы выпуклых траекторий, следующих по касательной. В вашей реализации эту внешнюю спираль можно увидеть на G=100.

введите описание изображения здесь

Эффект можно уменьшить, выбрав меньший размер шага, например dt=0.001.

введите описание изображения здесь

Вы должны выбрать время интегрирования, чтобы оно составляло значительную часть полной орбиты, чтобы получить презентабельный результат, с указанными выше параметрами вы получите около 2 петель, что хорошо.

Внедрение РК4

Вы допустили несколько ошибок. Как-то вы потеряли скорости, обновления позиции должны основываться на скоростях.

Затем вам следовало остановиться на 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]);

Затем вы можете использовать кулинарную реализацию шага Эйлера или Рунге-Кутты.

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)
person Lutz Lehmann    schedule 06.12.2018
comment
Спасибо за помощь, LutzL. Просматривая свой RK-код, я вижу несколько ошибок, о которых вы говорите. Я исправил их, и теперь он работает. - person corgiworld; 07.12.2018
comment
Я думаю, что, используя обратный Эйлер, вы можете избавиться от проблемы увеличения орбиты. Но в любом случае RK4 - лучший метод с точки зрения ошибки, вносимой при интеграции. - person Sembei Norimaki; 07.12.2018
comment
@SembeiNorimaki: Тогда вы получите проблему убывающей орбиты, поскольку неявный Эйлер ведет себя как явный Эйлер в обратном направлении. Чередование явных и неявных шагов дает симплектический метод средней точки. Любой вариант интеграции Verlet также даст лучшие результаты. - person Lutz Lehmann; 07.12.2018
comment
@LutzL, не могли бы вы опубликовать полный код? Я пытаюсь интегрировать ваше решение в код OP, но у меня возникают трудности. Был бы очень признателен минимальный код, включающий это решение для запуска. Спасибо - person Sembei Norimaki; 13.12.2018
comment
@SembeiNorimaki: готово, для векторно-ориентированной версии. Чтобы создать графики Эйлера, приведенные выше, просто возьмите код OP и добавьте G=100, можно вырезать процедуры, связанные с RK4, поскольку они ничего не делают. Выполнение plt.grid(); plt.show() в конце нового кода должно показать похожие графики. - person Lutz Lehmann; 13.12.2018

Вы нигде не используете rkx, rky функции! В конце определения функции есть два return, которые вы должны использовать return [(kx1 + 2*kx2 + 2*kx3 + kx4)/6, (mx1 + 2*mx2 + 2*mx3 + mx4)/6] (как указано @eapetcho). Также мне непонятна ваша реализация Рунге-Кутты.

У вас есть 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=3 и fy=y и получил хорошую траекторию.

Вот график ry против rx:

fx = 3, fy = y траектория

person Cheesebread    schedule 06.12.2018
comment
Закон Ньютона для движения планеты - это ДУ второго порядка, вам нужны 4 переменных состояния, 2 для положения и 2 для скорости. - person Lutz Lehmann; 06.12.2018
comment
Ваше исправление просто заменило одну ошибку другой. ODE - это u''=F(u), где u=[x,y]. С v=[vx,vy] системой первого порядка является u'=v; v'=F(u), который не может быть разделен на независимую двумерную систему (по крайней мере, без применения законов Кеплера). - person Lutz Lehmann; 06.12.2018
comment
Сэр @LutzL, «Исправление» редактирования не было сделано на основе вашего предложения, я просто пытался исправить реализацию OP в последнем цикле. Я не мог понять, на что вы пытались указать в своем первом комментарии, так как просто думал о коде. Я не обратил внимания на физику вопроса. Ваш ответ гораздо более информативен как с точки зрения физики, так и с точки зрения реализации. Это также указывает на разницу между двумя численными методами. - person Cheesebread; 06.12.2018
comment
За исключением того, что последний цикл был правильным как есть, только не метод, заявленный в заголовке и вопросе. В принципе, ваша поправка делает правильный код для системы u'=v; v'=F(v);, как в баллистике с воздушным трением. Но даже в этом случае интеграция позиций верна только для первого порядка. - person Lutz Lehmann; 06.12.2018
comment
Спасибо, SamD97. Теперь я понимаю, что вообще не использовал свои RK-функции ... как глупо. На этот раз я реализовал их по-настоящему и заставил их работать. - person corgiworld; 07.12.2018