Я пытаюсь понять, как рассчитать орбиты тел Солнечной системы в рамках n тел, основываясь на парном гравитационном взаимодействии между объектами. В настоящее время я рассматриваю 44 объекта (солнце, планеты, большие луны и большие астероиды).
Я начинаю с векторов состояния (положения и скорости) каждого из объектов с Солнцем в центре, полученных из telnet ssd.jpl.nasa.gov 6775
(JPL Horizons) 01 января 2017 года в 00:00 UTC, и хотел бы, чтобы система развивалась в течение 43:44, 01- Июль-2017 в 00:00ч.
Я написал программу для этого на Java, и до сих пор результаты не кажутся даже достаточно близкими к тому, чем они должны быть, по сравнению с векторами состояния, полученными из Horizons. После каждых 2-секундных временных шагов рассчитываются суммарные гравитационные силы, воздействующие на каждое тело от всех остальных, а затем за один раз обновляются все скорости и положения на основе ускорений от этих суммарных сил. Затем я сравниваю окончательные обновленные векторы положения из приложения с данными, полученными от Horizons после поправки на обновленное положение Солнца.
Сравнение показывает, что положение Земли и внешних планет имеет ошибку положения менее 50 км (на самом деле, чем дальше планеты, тем меньше 10 км). Где у Меркурия погрешность 250км. А спутники Юпитера и Сатурна разнесены на 50 000–300 000 км!
В своем приложении я не делаю различий между Солнцем, планетами и лунами, поэтому я не уверен, почему для лун должно быть так много ошибок. Я попытался уменьшить размер шага с 2 секунд до 0,25 секунды, но существенного улучшения не произошло.
Какие могут быть проблемы, которые я должен исследовать здесь? Есть ли вещи, которые явно нуждаются в улучшении прямо сейчас? Или, возможно, есть тесты, которые я могу сделать, чтобы помочь диагностировать основные источники ошибок?
РЕДАКТИРОВАТЬ: Вот суть метода расчета, как запрошено в комментариях:
while (nowT < endT) {
doOneStep(step, nowT)
nowT += stepT
}
allItemLinks
это коллекции ItemLink
- связи между объектами. в этом случае гравитационная связь между всеми парами объектов. Для n
объектов будут n.(n+1)/2
ссылки
doOneStep(double deltaT, double nowT){
initForces fo all items to 0,0,0
for each ItemLink **allItemLinks**)
inf.evalForce(deltaT, false)
updatePosAndVel(deltaT, nowT, true)
}
В ItemLink:
evalForce(double deltaT, boolean bFinal) {
addGravityEffect(deltaT);
}
boolean addGravityEffect(double deltaT) {
rVector = item2.pos - item1.pos
double gF = G.mq.m2/r2
fVector = gF in rVector direction
item1.addForce(fVector)
similarly for item2 to item1
}
allItems
представляет собой набор объектов Item (Солнце, планеты и луны)
void updatePosAndVel(double deltaT, double nowT) {
for each Item of **allItems** updatePandV(deltaT, nowT);
}
В элементе:
netForce, nowAcc, effectiveAcc, deltaV, newPos etc.
все Vector3d
updatePAndV(double deltaT, double nowT, boolean bFinal){
nowAcc = netForce / mass
effectiveAcc = mean of lastAcc and nowAcc
deltaV = effectiveAcc * deltaT
meanV ...
newPos = oldPos + meanV * deltaT
}
Работает не с гравитационными полями, а с прямыми силами за счет межобъектной гравитации.
С помощью приведенного выше кода я могу получить стабильные орбиты. Даже время обращения лун хорошо. Получите красивый набор Сатурна с циклоидальным движением лун и набор Урана с спиральным движением лун вокруг Урана. Я не знаю, как отправить файлы или изображения для этого обсуждения
Помимо числовых проблем, «С Солнцем в центре» может быть частью вашей проблемы. Получите все данные от Horizons относительно барицентра Солнечной системы, а не Солнца, которое движется относительно барицентра. Этот барицентр является инерциальной системой отсчета, а центр Солнца — нет. Также убедитесь, что вы вводите начальное положение и скорость Солнца и позволяете ему двигаться, если вы еще этого не сделали.
Я опубликую численные методы в этом ответе и полномасштабный расчет солнечной системы (включая теорию относительности и возможные эффекты сплюснутой формы Солнца) в качестве второго ответа. Слишком много, чтобы поместить все это в один ответ.
Метод, который вы описываете, выглядит как метод Эйлера или то, что можно назвать методом Эйлера вперед . На каждом временном шаге вы вычисляете все силы и их результирующие чистые ускорения , а затем просто увеличивайте скорость от и все позиции от . Вам нужны абсурдно крошечные временные шаги, чтобы приблизиться к этому. Вы упомянули 2 секунды и 250 миллисекунд, что немного меньше для шкалы времени Солнечной системы.
В приведенном ниже сценарии я написал метод Эйлера вперед и еще два метода низкого порядка Рунге-Кутты , обычно называемые RK2 и RK4. Для каждого метода рассчитывается упрощенная (фиктивная) орбита Меркурия вокруг фиксированного Солнца за 100 дней с количеством итераций от 10 до 10 000. Кроме того, для каждого я использую решатель библиотеки SciPyodeint()
с относительным допуском точности 1E-12 на шаг. odeint — это оболочка Python lsoda
из библиотеки FORTRAN odepack
.
Вы можете видеть, что даже RK4 соглашается с odeint
уровнем метров через 100 дней. , если вы используете временной шаг около 15 минут, а метод Эйлера вперед (то, что вы используете) потребует абсурдного количества шагов, чтобы хотя бы приблизиться к этому.
Численные методы - не единственная проблема здесь, я опубликую второй ответ через несколько дней с остальным, что вам нужно. Я могу сделать это под отдельным вопросом, а не публиковать два ответа на один и тот же вопрос .
Но это должно заставить вас начать либо кодировать RK4 в java, либо искать числовую библиотеку java, такую как Apache, упомянутая в комментариях.
Самый простой стандартный способ решить орбитальную задачу с помощью решателя ОДУ — поместить все декартовы координаты и скорости в один длинный вектор, назовем его , и напишите одну функцию для производной по времени:
Итак, если бы у вас было два тела и три измерения, вектор было бы:
с шестью элементами на тело. производная от является , и производная от это ускорение из-за всех остальных тел.
Скажем, у вас есть одно тело в центральной силе со стандартным гравитационным параметром . Скорость изменения положения - это просто скорость,
а скорость изменения скорости есть ускорение, вызванное силой;
Если у вас было несколько тел, будет расстояние между парами тел, и для каждого тела вы суммируете все остальные, как вы уже описали.
Итак, если вы написали , это было бы:
Метод Euler Forward, который, как я полагаю, вы используете, просто повторяет
с временными шагами . Улучшенный метод RK2 (показан ниже) будет записан так:
а вездесущий метод RK4 записывается как
Вот раздел, который показывает сходство и различия между методом Эйлера Форвард (самый простой метод Рунге-Кутта) и двумя методами РК более высокого порядка. Это написано для ясности, а не скорости явно.
def deriv(t, X):
x, v = X.reshape(2, -1)
acc = -GMs * x * ((x**2).sum())**-1.5
return np.hstack((v, acc))
def derivv(X, t):
return deriv(t, X) # historical reasons
def RK_all(F, t0, X0, n, h, method=None):
hov2, hov6 = h/2.0, h/6.0
t, X = t0, X0
answer, time = [], []
answer.append(X)
time.append(t)
for i in range(n):
k1 = F(t, X )
k2 = F(t + hov2, X + hov2 * k1)
k3 = F(t + hov2, X + hov2 * k2)
k4 = F(t + h, X + h * k3)
if method == 'EulerFwd':
X = X + h*k1 # X + h*F(t, X)
elif method == 'RK2':
X = X + h*k2
elif method == 'RK4':
X = X + hov6*(k1 + 2.*(k2+k3) + k4)
else:
pass
t += h
answer.append(X)
time.append(t)
return np.array(time), np.array(answer)
import numpy as np
import matplotlib.pyplot as plt
from scipy.integrate import odeint as ODEint
GMs = 1.327E+20 # approx
X0 = np.array([-2.094E+10, 4.303E+10, 5.412E+09,
-5.328E+04, -2.011E+04, 3.243E+03]) # approx
methodnames = ['RK4', 'RK2', 'EulerFwd']
niterations = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000]
Time = 100*24*3600. # total time
methdict = dict()
for methodname in methodnames:
times, answers, ODEanswers, posdevs = [], [], [], []
for n in niterations:
h = Time/float(n)
t0 = 0.0
time, answer = RK_all(deriv, t0, X0, n, h, method=methodname)
# recalculate using library ODE solver for same times, to compare
ODEanswer, info = ODEint(derivv, X0, time,
rtol=1E-12, full_output=True)
posdev = np.sqrt((((answer - ODEanswer)[:,:3])**2).sum(axis=1))
times.append(time)
answers.append(answer)
ODEanswers.append(ODEanswer)
posdevs.append(posdev)
methdict[methodname] = (times, answers, ODEanswers, posdevs)
if 1 == 1:
plt.figure()
for i, meth in enumerate(methodnames):
plt.subplot(1, 3, i+1)
for time, answer, ODEanswer, posdev in zip(*methdict[meth]):
x, y, z = answer.T[:3]
plt.plot(x, y)
plt.ylim(-2.8E+11, 0.8E+11)
plt.xlim(-1.2E+11, 0.8E+11)
plt.title(meth, fontsize=16)
plt.plot([0],[0], 'ok')
plt.show()
if 1 == 1:
plt.figure()
for i, meth in enumerate(methodnames):
plt.subplot(1, 3, i+1)
for time, answer, ODEanswer, posdev in zip(*methdict[meth]):
plt.plot(time/(24*3600.), posdev)
plt.yscale('log')
plt.ylim(1E-01, 1E+12)
plt.title(meth+' vs odeint', fontsize=16)
plt.suptitle('RKmethod - odeint (meters) vs time (days)', fontsize=18)
plt.xticks([0, 20, 40, 60, 80, 100])
plt.show()
scipy.odeint
или «scipy.ode», это лучше.
СФ.
СФ.
Полигном
ооо
вишу
вишу
пользователь7073
ооо
ооо
ооо
вишу
вишу
ооо
ооо
вишу
ооо
X
в моем скрипте Python, длявишу
вишу
ооо
ооо
вишу
вишу