Рунге – Кутта RK4 не лучше Верле?

Я просто тестирую в игре несколько схем интеграции орбитальной динамики. Я взял RK4 с постоянным и адаптивным шагом здесь http://www.physics.buffalo.edu/phy410-505/2011/topic2/app1/index.html

и я сравнил его с простой интеграцией верлета (и Эйлера, но у него очень и очень низкая производительность). Не похоже, что RK4 с постоянным шагом лучше верлета. RK4 с адаптивным шагом лучше, но не настолько. Интересно, а я что-то не так делаю? Или в каком смысле сказано, что RK4 намного превосходит верлет?

Считается, что Force оценивается 4x за шаг RK4, но только 1x за шаг verlet. Поэтому, чтобы получить такую ​​же производительность, я могу установить time_step в 4 раза меньше для verlet. При уменьшении временного шага в 4 раза верлет более точен, чем RK4 с постоянным шагом, и почти сравним с RK4 с дополнительным шагом.

См. Изображение: https://lh4.googleusercontent.com/-I4wWQYV6o4g/UW5pK93WPVI/AAAAAAAAA7I/PHSsp2nEjx0/s800/kepler.png

10T означает 10 орбитальных периодов, следующее число 48968,7920,48966 - количество необходимых оценок силы.

код Python (с использованием pylab) следующий:

from pylab import * 
import math

G_m1_plus_m2 = 4 * math.pi**2

ForceEvals = 0

def getForce(x,y):
    global ForceEvals
    ForceEvals += 1
    r = math.sqrt( x**2 + y**2 )
    A = - G_m1_plus_m2 / r**3
    return x*A,y*A

def equations(trv):
    x  = trv[0]; y  = trv[1]; vx = trv[2]; vy = trv[3];
    ax,ay = getForce(x,y)
    flow = array([ vx, vy, ax, ay ])
    return flow

def SimpleStep( x, dt, flow ):
    x += dt*flow(x)

def verletStep1( x, dt, flow ):
    ax,ay = getForce(x[0],x[1])
    vx   = x[2] + dt*ax; vy   = x[3] + dt*ay; 
    x[0]+= vx*dt;        x[1]+= vy*dt;
    x[2] = vx;        x[3] = vy;

def RK4_step(x, dt, flow):    # replaces x(t) by x(t + dt)
    k1 = dt * flow(x);     
    x_temp = x + k1 / 2;   k2 = dt * flow(x_temp)
    x_temp = x + k2 / 2;   k3 = dt * flow(x_temp)
    x_temp = x + k3    ;   k4 = dt * flow(x_temp)
    x += (k1 + 2*k2 + 2*k3 + k4) / 6

def RK4_adaptive_step(x, dt, flow, accuracy=1e-6):  # from Numerical Recipes
    SAFETY = 0.9; PGROW = -0.2; PSHRINK = -0.25;  ERRCON = 1.89E-4; TINY = 1.0E-30
    scale = flow(x)
    scale = abs(x) + abs(scale * dt) + TINY
    while True:
        x_half = x.copy();  RK4_step(x_half, dt/2, flow); RK4_step(x_half, dt/2, flow)
        x_full = x.copy();  RK4_step(x_full, dt  , flow)
        Delta = (x_half - x_full)
        error = max( abs(Delta[:] / scale[:]) ) / accuracy
        if error <= 1:
            break;
        dt_temp = SAFETY * dt * error**PSHRINK
        if dt >= 0:
            dt = max(dt_temp, 0.1 * dt)
        else:
            dt = min(dt_temp, 0.1 * dt)
        if abs(dt) == 0.0:
            raise OverflowError("step size underflow")
    if error > ERRCON:
        dt *= SAFETY * error**PGROW
    else:
        dt *= 5
    x[:] = x_half[:] + Delta[:] / 15
    return dt    

def integrate( trv0, dt, F, t_max, method='RK4', accuracy=1e-6 ):
    global ForceEvals
    ForceEvals = 0
    trv = trv0.copy()
    step = 0
    t = 0
    print "integrating with method: ",method," ... "
    while True:
        if method=='RK4adaptive':
            dt = RK4_adaptive_step(trv, dt, equations, accuracy)
        elif method=='RK4':
            RK4_step(trv, dt, equations)
        elif method=='Euler':
            SimpleStep(trv, dt, equations)
        elif method=='Verlet':
            verletStep1(trv, dt, equations)
        step += 1
        t+=dt
        F[:,step] = trv[:]
        if t > t_max:
            break
    print " step = ", step


# ============ MAIN PROGRAM BODY =========================

r_aphelion   = 1
eccentricity = 0.95
a = r_aphelion / (1 + eccentricity)
T = a**1.5
vy0 = math.sqrt(G_m1_plus_m2 * (2 / r_aphelion - 1 / a))
print " Semimajor axis a = ", a, " AU"
print " Period T = ", T, " yr"
print " v_y(0) = ", vy0, " AU/yr"
dt       = 0.0003
accuracy = 0.0001

#                 x        y     vx  vy
trv0 = array([ r_aphelion, 0,    0, vy0 ])             

def testMethod( trv0, dt, fT, n, method, style ):
    print " "
    F = zeros((4,n));
    integrate(trv0, dt, F, T*fT, method, accuracy);
    print "Periods ",fT," ForceEvals ",  ForceEvals
    plot(F[0],F[1], style ,label=method+" "+str(fT)+"T "+str(  ForceEvals ) ); 

testMethod( trv0, dt, 10, 20000  , 'RK4', '-' )
testMethod( trv0, dt, 10, 10000  , 'RK4adaptive', 'o-' )
testMethod( trv0, dt/4, 10, 100000, 'Verlet', '-' )
#testMethod( trv0, dt/160, 2, 1000000, 'Euler', '-' )

legend();
axis("equal")
savefig("kepler.png")
show();

person Prokop Hapala    schedule 17.04.2013    source источник


Ответы (3)


Не знаю, отвечу ли я на ваши конкретные вопросы, но вот мои мысли.

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

Для орбитальной механики вы также можете попробовать адаптивный интегратор RK7 (8), многошаговый метод Адамса-Башфорта или метод Гаусса Джексона. Вот документ, демонстрирующий некоторые из этих методов: http://drum.lib.umd.edu/bitstream/1903/2202/7/2004-berry-healy-jas.pdf

И, наконец, если ваша модель силы всегда будет простой центральной силой, как в этом примере, взгляните на уравнение Кеплера. Решение - точное, быстрое, и вы можете перейти к произвольному времени.

person siritinga    schedule 18.04.2013
comment
Привет, спасибо за ответ и статью, я планирую разместить там немного больше модели comppelx force (больше планет), но все же RK4addaptive максимально в 6 раз быстрее, чем простой verlet при этой точности. Работа выглядит интересной, но я не уверен, что хочу тратить много времени на ее реализацию самостоятельно. Я скорее искал какой-то код. Я нашел некоторые, но мне не удалось скомпилировать этот burtleburtle.net/bob/java /orbit/index.html unige.ch/~hairer/software.html code.google.com/p/exoflight/source/browse/trunk/Exoflight/src/ - person Prokop Hapala; 19.04.2013

Я знаю, что этот вопрос уже довольно старый, но на самом деле это не имеет ничего общего с «превосходством» одного из этих методов над другим или с вашим программированием их - они просто хороши в разных вещах. (Так что нет, этот ответ на самом деле будет не о коде. И даже не о программировании. На самом деле, это больше о математике ...)

Семейство решателей Рунге-Кутта довольно хорошо решает практически любую проблему с довольно хорошей точностью и, в случае адаптивных методов, производительностью. Однако они не являются симплектическими, что вкратце означает, что они не экономят энергию в проблеме.

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

Ваша проблема - энергосбережение; после произвольного числа оборотов полная энергия планетарного тела (кинетическая + потенциальная) должна быть такой же, как и при начальных условиях. Это будет верно с интегратором Верле (по крайней мере, как среднее с временным окном), но не будет с интегратором из семейства RK - со временем решатели RK будут создавать ошибку, которая не уменьшается из-за энергии. потеряны при численном интегрировании.

Чтобы убедиться в этом, попробуйте сэкономить общую энергию в системе на каждом временном шаге и нанесите ее на график (возможно, вам придется сделать гораздо больше десяти оборотов, чтобы заметить разницу). Метод RK будет иметь постоянно убывающую энергию, в то время как метод Верле будет давать колебания около постоянной энергии.

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

person Tomas Aschan    schedule 14.08.2013

Хорошо, наконец, я использовал адаптивный метод Рунге-Кутта-Фельберга (RKF45). Интересно, что это быстрее (требуется меньший шаг), когда я прошу более высокую точность (оптимум 1E-9), потому что при более низкой точности (‹1e-6) решение нестабильно, и много итераций теряются из-за отброшенных шагов ( шаги, которые были слишком длинными и неточными). Когда я прошу еще лучшую точность (1E-12), требуется больше шагов, потому что временные шаги короче.

Для круговых орбит прецизионность может быть определена до (1e-5) с увеличением скорости до 3 раз, однако, хотя мне нужно моделировать сильно эксцентричные (эллиптические орбиты), я предпочитаю сохранять безопасную точность 1E-9.

Вот код, если кто-нибудь решит аналогичную проблему http://www.openprocessing.org/sketch/96977 он также показывает, сколько оценок силы требуется для моделирования одной единицы времени траекторной длины.

person Prokop Hapala    schedule 22.04.2013