Esercizio: simulazione di meccanica orbitale 2D (python)


12

Solo un piccolo disclaimer in anticipo: non ho mai studiato astronomia o scienze esatte per quella materia (nemmeno l'IT), quindi sto cercando di colmare questa lacuna con l'autoeducazione. L'astronomia è una delle aree che ha catturato la mia attenzione e la mia idea di autoeducazione si basa sull'approccio applicato. Quindi, dritto al punto - questo è il modello di simulazione orbitale su cui sto lavorando casualmente quando ho tempo / umore. Il mio obiettivo principale è creare un sistema solare completo in movimento e la capacità di pianificare il lancio di veicoli spaziali su altri pianeti.

Sei libero di ritirare questo progetto in qualsiasi momento e divertirti a sperimentare!

aggiornare!!! (Nov10)

  • la velocità è ora deltaV corretta e dare movimento aggiuntivo ora calcola il vettore di somma della velocità
  • puoi posizionare tutti gli oggetti statici che desideri, su ogni oggetto unitario in movimento controlla i vettori di gravità da tutte le fonti (e verifica la collisione)
  • notevolmente migliorato le prestazioni dei calcoli
  • una correzione per tenere conto della mod interattiva in matplotlib. Sembra che questa sia l'opzione predefinita solo per ipython. Python3 regolare richiede esplicitamente tale affermazione.

Fondamentalmente è ora possibile "lanciare" un veicolo spaziale dalla superficie della Terra e tracciare una missione sulla Luna effettuando correzioni vettoriali deltaV tramite giveMotion (). Il prossimo in linea sta cercando di implementare la variabile temporale globale per abilitare il movimento simultaneo, ad esempio la Luna orbita attorno alla Terra, mentre il veicolo spaziale prova una manovra di gravità assistita.

Commenti e suggerimenti per miglioramenti sono sempre ben accetti!

Fatto in Python3 con libreria matplotlib

import matplotlib.pyplot as plt
import math
plt.ion()

G = 6.673e-11  # gravity constant
gridArea = [0, 200, 0, 200]  # margins of the coordinate grid
gridScale = 1000000  # 1 unit of grid equals 1000000m or 1000km

plt.clf()  # clear plot area
plt.axis(gridArea)  # create new coordinate grid
plt.grid(b="on")  # place grid

class Object:
    _instances = []
    def __init__(self, name, position, radius, mass):
        self.name = name
        self.position = position
        self.radius = radius  # in grid values
        self.mass = mass
        self.placeObject()
        self.velocity = 0
        Object._instances.append(self)

    def placeObject(self):
        drawObject = plt.Circle(self.position, radius=self.radius, fill=False, color="black")
        plt.gca().add_patch(drawObject)
        plt.show()

    def giveMotion(self, deltaV, motionDirection, time):
        if self.velocity != 0:
            x_comp = math.sin(math.radians(self.motionDirection))*self.velocity
            y_comp = math.cos(math.radians(self.motionDirection))*self.velocity
            x_comp += math.sin(math.radians(motionDirection))*deltaV
            y_comp += math.cos(math.radians(motionDirection))*deltaV
            self.velocity = math.sqrt((x_comp**2)+(y_comp**2))

            if x_comp > 0 and y_comp > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity))  # update motion direction
            elif x_comp > 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 90
            elif x_comp < 0 and y_comp < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_comp)/self.velocity)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_comp)/self.velocity)) + 270

        else:
            self.velocity = self.velocity + deltaV  # in m/s
            self.motionDirection = motionDirection  # degrees
        self.time = time  # in seconds
        self.vectorUpdate()

    def vectorUpdate(self):
        self.placeObject()
        data = []

        for t in range(self.time):
            motionForce = self.mass * self.velocity  # F = m * v
            x_net = 0
            y_net = 0
            for x in [y for y in Object._instances if y is not self]:
                distance = math.sqrt(((self.position[0]-x.position[0])**2) +
                             (self.position[1]-x.position[1])**2)
                gravityForce = G*(self.mass * x.mass)/((distance*gridScale)**2)

                x_pos = self.position[0] - x.position[0]
                y_pos = self.position[1] - x.position[1]

                if x_pos <= 0 and y_pos > 0:  # calculate degrees depending on the coordinate quadrant
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+90

                elif x_pos > 0 and y_pos >= 0:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))+180

                elif x_pos >= 0 and y_pos < 0:
                    gravityDirection = math.degrees(math.asin(abs(y_pos)/distance))+270

                else:
                    gravityDirection = math.degrees(math.asin(abs(x_pos)/distance))

                x_gF = gravityForce * math.sin(math.radians(gravityDirection))  # x component of vector
                y_gF = gravityForce * math.cos(math.radians(gravityDirection))  # y component of vector

                x_net += x_gF
                y_net += y_gF

            x_mF = motionForce * math.sin(math.radians(self.motionDirection))
            y_mF = motionForce * math.cos(math.radians(self.motionDirection))
            x_net += x_mF
            y_net += y_mF
            netForce = math.sqrt((x_net**2)+(y_net**2))

            if x_net > 0 and y_net > 0:  # calculate degrees depending on the coordinate quadrant
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce))  # update motion direction
            elif x_net > 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 90
            elif x_net < 0 and y_net < 0:
                self.motionDirection = math.degrees(math.asin(abs(x_net)/netForce)) + 180
            else:
                self.motionDirection = math.degrees(math.asin(abs(y_net)/netForce)) + 270

            self.velocity = netForce/self.mass  # update velocity
            traveled = self.velocity/gridScale  # grid distance traveled per 1 sec
            self.position = (self.position[0] + math.sin(math.radians(self.motionDirection))*traveled,
                             self.position[1] + math.cos(math.radians(self.motionDirection))*traveled)  # update pos
            data.append([self.position[0], self.position[1]])

            collision = 0
            for x in [y for y in Object._instances if y is not self]:
                if (self.position[0] - x.position[0])**2 + (self.position[1] - x.position[1])**2 <= x.radius**2:
                    collision = 1
                    break
            if collision != 0:
                print("Collision!")
                break

        plt.plot([x[0] for x in data], [x[1] for x in data])

Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(100.0, 100.0), radius=1.737, mass = 7.347e22)  # position not to real scale
Craft = Object(name="SpaceCraft", position=(49.0, 40.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)
Craft.giveMotion(deltaV=2000.0, motionDirection=90, time=60000)
plt.show(block=True)

Come funziona

Tutto si riduce a due cose:

  1. Creazione di oggetti come Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)con i parametri di posizione sulla griglia (1 unità di griglia è 1000 km di default ma anche questo può essere cambiato), raggio in unità di griglia e massa in kg.
  2. Dare all'oggetto deltaV come Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)ovviamente Craft = Object(...)deve essere creato in primo luogo, come indicato nel punto precedente. I parametri qui sono deltaVin m / s (nota che per ora l'accelerazione è istantanea), motionDirectionè la direzione del deltaV in gradi (dalla posizione corrente immagina un cerchio a 360 gradi attorno all'oggetto, quindi la direzione è un punto su quel cerchio) e infine il parametro timeè quanti secondi dopo che la traiettoria push deltaV dell'oggetto sarà monitorata. giveMotion()Inizio successivo dall'ultima posizione precedente giveMotion().

Domande:

  1. È un algoritmo valido per calcolare le orbite?
  2. Quali sono gli ovvi miglioramenti da apportare?
  3. Ho preso in considerazione la variabile "timeScale" che ottimizzerà i calcoli, in quanto potrebbe non essere necessario ricalcolare i vettori e le posizioni per ogni secondo. Qualche idea su come dovrebbe essere implementata o è generalmente una buona idea? (perdita di precisione rispetto a prestazioni migliorate)

Fondamentalmente il mio obiettivo è iniziare una discussione sull'argomento e vedere dove conduce. E, se possibile, imparare (o anche meglio - insegnare) qualcosa di nuovo e interessante.

Sentiti libero di sperimentare!

Prova a usare:

Earth = Object(name="Earth", position=(50.0, 100.0), radius=6.371, mass=5.972e24)
Moon = Object(name="Moon", position=(434.0, 100.0), radius=1.737, mass = 7.347e22)
Craft = Object(name="SpaceCraft", position=(43.0, 100.0), radius=1, mass=1.0e4)

Craft.giveMotion(deltaV=10575.0, motionDirection=180, time=322000)
Craft.giveMotion(deltaV=400.0, motionDirection=180, time=50000)

Con due ustioni - uno progrado in orbita terrestre e uno retrogrado in orbita lunare ho raggiunto un'orbita lunare stabile. Sono vicini ai valori teoricamente previsti?

Esercizio suggerito: provalo in 3 ustioni: orbita terrestre stabile dalla superficie terrestre, ustioni progradi per raggiungere la Luna, ustioni retrogrado per stabilizzare l'orbita attorno alla Luna. Quindi provare a minimizzare deltaV.

Nota: ho intenzione di aggiornare il codice con ampi commenti per coloro che non hanno familiarità con la sintassi di python3.


Un'ottima idea per autodidatta! Sarebbe possibile riassumere le tue formule per quelli di noi che non hanno familiarità con la sintassi di Python?

Certo, immagino. Farò commenti più ampi nel codice per coloro che vogliono raccoglierlo e riassumere la logica generale nella domanda stessa.
stati del

Dalla parte superiore della mia testa: considera l'utilizzo di un vettore per la velocità invece di trattare la velocità e la direzione in modo diverso. Dove dici "F = m * v" intendi "F = m * a"? Supponi che la Terra non si muova perché è molto più pesante dell'asteroide? Prendi in considerazione l'
idea di

Puoi dare movimento a qualsiasi oggetto, compresa la Terra. Ai fini del test ho incluso solo l'oggetto -> Relazione di terra nel circuito principale. Può essere facilmente convertito che ogni oggetto si riferisce a tutti gli altri oggetti che vengono creati. E ogni oggetto può avere il proprio vettore di movimento. Motivo per cui non l'ho fatto - calcoli molto lenti anche per 1 oggetto. Spero che il ridimensionamento delle unità di tempo dovrebbe aiutare molto, ma non sono ancora sicuro di come farlo nel modo giusto.
stati

1
OK. Un pensiero: fai la simulazione di due oggetti reali (ad esempio, Terra / Luna o Terra / Sole) e confronta i tuoi risultati con ssd.jpl.nasa.gov/?horizons per la precisione? Non sarà perfetto a causa delle perturbazioni di altre fonti, ma ti darà un'idea di precisione?
Barrycarter,

Risposte:


11

m1,m2

F=ma
a

F21=Gm1m2|r21|3r21

r21F12=F21r12=r21(x1,y1)(x2,y2)

r21=(x1x2y1y2).

e

|r|=(x1x2)2+(y1y2)2.
a=F/m

x1(t)=Gm2(x2x1)|r|3y1(t)=Gm2(y2y1)|r|3x2(t)=Gm1(x1x2)|r|3y2(t)=Gm1(y1y2)|r|3.

Insieme alle posizioni e alle velocità iniziali, questo sistema di equazioni differenziali ordinarie (ODE) comprende un problema di valore iniziale. L'approccio usuale è quello di scrivere questo come un sistema del primo ordine di 8 equazioni e applicare un metodo Runge-Kutta o multistep per risolverle.

Se applichi qualcosa di semplice come Eulero in avanti o Eulero all'indietro, vedrai la Terra spirale verso l'infinito o verso il sole, rispettivamente, ma questo è un effetto degli errori numerici. Se usi un metodo più accurato, come il classico metodo Runge-Kutta del 4 ° ordine, scoprirai che rimane vicino a un'orbita vera per un po 'ma alla fine continua all'infinito. L'approccio giusto consiste nell'utilizzare un metodo simplettico, che manterrà la Terra nell'orbita corretta, sebbene la sua fase rimarrà disattivata a causa di errori numerici.

Per il problema dei 2 corpi è possibile ricavare un sistema più semplice basando il sistema di coordinate attorno al centro di massa. Ma penso che la formulazione sopra sia più chiara se questa è una novità per te.


Questo richiederà del tempo per essere digerito.
Uniti

Sto ancora digerendo. Troppe parole sconosciute per me, ma in qualche modo sento che ad un certo punto ci arriverò. Per ora il mio algoritmo è abbastanza buono perché le cose funzionino semplicemente. Ma quando collegherò il movimento simultaneo, sarò costretto ad andare in letteratura e leggere su algoritmi adeguati. Dato che i limiti dell'hardware moderno sono molto più ampi, posso permettermi di scherzare con semplici equazioni. Non temo per molto.
stati del

In effetti i metodi simplettici sono di gran lunga i più precisi, ma penso che sia difficile per qualcuno che non ha alcuna preparazione scientifica implementarli. Invece puoi usare il metodo Euler molto semplice insieme alla correzione di Feynman. Non penso che tu abbia bisogno di qualcosa di più complesso di quello per scopi di auto-educazione.
chrispap,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.