Estoy tratando de entender cómo calcular las órbitas de los cuerpos del sistema solar en un marco de n-cuerpos, basado en la interacción gravitatoria por pares entre los objetos. Actualmente, estoy considerando 44 objetos (sol, planetas, lunas principales y asteroides principales).
Estoy comenzando con los vectores de estado (posición y velocidad) de cada uno de los objetos con el Sol como centro obtenidos de telnet ssd.jpl.nasa.gov 6775
(JPL Horizons) el 01 de enero de 2017 a las 00:00 UTC y me gustaría dejar que el sistema evolucione durante 4344h, 01- Julio-2017 a las 00:00h.
He escrito un programa para hacer esto en Java, y hasta ahora los resultados no parecen estar ni siquiera razonablemente cerca de lo que deberían ser, en comparación con los vectores de estado obtenidos de Horizons. Después de cada paso de tiempo de 2 segundos, se calculan las fuerzas gravitatorias netas sobre cada cuerpo de todos los demás, y luego, de una sola vez, todas las velocidades y posiciones se actualizan en función de las aceleraciones de esas fuerzas netas. Luego comparo los vectores de posición actualizados finales de la aplicación con los datos obtenidos de Horizons después de corregir la posición actualizada del Sol.
La comparación muestra que las posiciones de la Tierra y los planetas exteriores tienen un error de posición de menos de 50 km (de hecho, los planetas más lejanos tienen menos de 10 km). Donde en cuanto a Mercurio el error es de 250 km. ¡Y las lunas de Júpiter y Saturno están desviadas entre 50 000 y 300 000 km!
En mi aplicación, no estoy diferenciando entre el Sol, los planetas y las lunas, por lo que no estoy seguro de por qué debería haber tantos más errores para las lunas. He intentado disminuir el tamaño del paso, de 2 segundos a 0,25 segundos, pero no hay una mejora significativa.
¿Cuáles podrían ser los problemas que debería investigar aquí? ¿Hay cosas que claramente necesitan mejorar de inmediato? ¿O tal vez hay pruebas que puedo hacer para ayudar a diagnosticar las principales fuentes de error?
EDITAR: Aquí está la esencia del método de cálculo solicitado en los comentarios:
while (nowT < endT) {
doOneStep(step, nowT)
nowT += stepT
}
allItemLinks
son colecciones de ItemLink
- enlaces entre objetos. en este caso Enlace de gravedad entre todos los pares de objetos. Para n
los objetos, habrá n.(n+1)/2
enlaces .
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)
}
En 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
es una colección de objetos Item (Sol, planetas y lunas)
void updatePosAndVel(double deltaT, double nowT) {
for each Item of **allItems** updatePandV(deltaT, nowT);
}
En artículo:
netForce, nowAcc, effectiveAcc, deltaV, newPos etc.
son todos 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
}
No trabaja en campos de gravitación sino con fuerzas directas debido a la gravedad entre objetos.
Con el código anterior, puedo obtener órbitas estables. Incluso los tiempos de órbita de las lunas son buenos. Consigue una buena configuración de Saturno con los movimientos cicloidales de las lunas y la configuración de Urano con el movimiento helicoidal de las lunas alrededor de Urano. No se como enviar archivos o imagenes para esta discusion
Aparte de los problemas numéricos, "Con el Sol como centro" puede ser parte de su problema. Obtenga todos los datos de Horizons en relación con el Baricentro del Sistema Solar, no el Sol, que se mueve en relación con el baricentro. Ese baricentro es un marco de referencia inercial, mientras que el centro del Sol no lo es. También asegúrese de colocar la posición inicial y la velocidad del Sol y dejar que se mueva, si aún no lo ha hecho.
Publicaré los métodos numéricos en esta respuesta y el cálculo completo del sistema solar (incluida la relatividad y los posibles efectos de la forma achatada del Sol) como una segunda respuesta. Hay demasiado para ponerlo todo en una respuesta.
El método que describe se parece al método de Euler o lo que se puede llamar el método Euler Forward . En cada paso de tiempo usted calcula todas las fuerzas y sus aceleraciones netas resultantes , y luego simplemente incrementar las velocidades por y todas las posiciones por . Necesitas pasos de tiempo absurdamente pequeños para acercarte a esto. Usted mencionó 2 segundos y 250 milisegundos, un poco corto para las escalas de tiempo del sistema solar.
En el siguiente script, he escrito el método Euler Forward y otros dos métodos de orden bajo de Runge-Kutta , generalmente llamados RK2 y RK4. Para cada método, se calcula una órbita simplificada (falsa) de Mercurio alrededor de un Sol fijo durante 100 días, con un número de iteraciones que varía de 10 a 10 000. Además, para cada uno, uso el solucionador de la biblioteca SciPyodeint()
con una tolerancia de precisión relativa de 1E-12 por paso. odeint es un contenedor de python lsoda
de la biblioteca FORTRAN odepack
.
Puede ver que incluso RK4 está de acuerdo con odeint
el nivel de metros después de 100 días si usa un paso de tiempo de aproximadamente 15 minutos, y el método Euler Forward (lo que usa) necesitará una cantidad absurda de pasos para siquiera acercarse a esto.
Las técnicas numéricas no son el único problema aquí, publicaré una segunda respuesta en unos días con el resto de lo que necesita. Puedo hacerlo bajo una pregunta separada en lugar de publicar dos respuestas a la misma pregunta .
Pero esto debería ayudarlo a comenzar a codificar RK4 en Java o buscar una biblioteca numérica de Java como la de Apache mencionada en los comentarios.
La forma estándar más sencilla de resolver un problema orbital usando un solucionador ODE es poner todas las coordenadas y velocidades cartesianas en un vector largo, llámelo , y escribe una sola función para la derivada del tiempo:
Entonces, si tuvieras dos cuerpos y tres dimensiones, el vector sería:
teniendo seis elementos por cuerpo. el derivado de es , y la derivada de es la aceleración debido a todos los otros cuerpos.
Digamos que tienes un cuerpo en una fuerza central con un parámetro gravitacional estándar . La tasa de cambio de posición es solo la velocidad,
y la razón de cambio de la velocidad es la aceleración, debida a la fuerza;
Si tuviera varios cuerpos, el sería la distancia entre pares de cuerpos y para cada cuerpo sumarías todos los demás como ya lo describiste.
Así que si escribiste , podría ser:
El método Euler Forward que creo que está utilizando simplemente itera
con pasos de tiempo de . El método RK2 mejorado (que se muestra a continuación) se escribiría:
y el omnipresente método RK4 se escribe como
Aquí está la sección que muestra la similitud y las diferencias entre el método Euler Forward (el método Runge-Kutta más simple) y dos métodos RK de orden superior. Esto está escrito para mayor claridad, no para acelerar, obviamente.
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
o 'scipy.ode', es mejor.
SF.
SF.
polignomo
UH oh
Vishu
Vishu
usuario7073
UH oh
UH oh
UH oh
Vishu
Vishu
UH oh
UH oh
Vishu
UH oh
X
en mi script de python, paraVishu
Vishu
UH oh
UH oh
Vishu
Vishu