Exercice: simulation de mécanique orbitale 2D (python)

12

Juste un petit avertissement au préalable: je n'ai jamais étudié l'astronomie ni aucune science exacte d'ailleurs (pas même l'informatique), alors j'essaie de combler cette lacune par l'auto-éducation. L'astronomie est l'un des domaines qui a retenu mon attention et mon idée de l'auto-éducation est à la tête de l'approche appliquée. Donc, droit au but - c'est un modèle de simulation orbital sur lequel je travaille avec désinvolture lorsque j'ai le temps / l'humeur. Mon objectif principal est de créer un système solaire complet en mouvement et de pouvoir planifier des lancements de vaisseaux spatiaux sur d'autres planètes.

Vous êtes tous libres de reprendre ce projet à tout moment et de vous amuser à expérimenter!

mise à jour!!! (10 novembre)

  • la vitesse est maintenant deltaV appropriée et le mouvement supplémentaire calcule maintenant le vecteur somme de la vitesse
  • vous pouvez placer autant d'objets statiques que vous le souhaitez, à chaque fois que l'objet unité en mouvement vérifie les vecteurs de gravité de toutes les sources (et vérifie les collisions)
  • considérablement amélioré les performances des calculs
  • un correctif pour tenir compte du mod interactif dans matplotlib. Il semble que ce soit l'option par défaut uniquement pour ipython. Python3 normal requiert explicitement cette instruction.

Fondamentalement, il est désormais possible de "lancer" un vaisseau spatial à partir de la surface de la Terre et de tracer une mission vers la Lune en effectuant des corrections de vecteur deltaV via giveMotion (). Next in line essaie de mettre en œuvre une variable de temps globale pour permettre un mouvement simultané, par exemple la Lune en orbite autour de la Terre pendant que le vaisseau spatial essaie une manœuvre d'assistance à la gravité.

Les commentaires et suggestions d'amélioration sont toujours les bienvenus!

Fait en Python3 avec la bibliothèque 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)

Comment ça fonctionne

Tout se résume à deux choses:

  1. Création d'objet comme Earth = Object(name="Earth", position=(50.0, 50.0), radius=6.371, mass=5.972e24)avec des paramètres de position sur la grille (1 unité de grille est 1000 km par défaut mais cela peut aussi être modifié), rayon en unités de grille et masse en kg.
  2. Donner à l'objet un certain deltaV tel Craft.giveMotion(deltaV=8500.0, motionDirection=100, time=130000)qu'il doit évidemment Craft = Object(...)être créé en premier lieu comme mentionné au point précédent. Les paramètres ici sont deltaVen m / s (notez que pour l'instant l'accélération est instantanée), motionDirectionest la direction du deltaV en degrés (à partir de la position actuelle, imaginez un cercle de 360 ​​degrés autour de l'objet, donc la direction est un point sur ce cercle) et enfin le paramètre timeest le nombre de secondes après la trajectoire de poussée deltaV de l'objet sera surveillée. Après giveMotion()arrêt de début de la dernière position précédente giveMotion().

Des questions:

  1. Est-ce un algorithme valide pour calculer les orbites?
  2. Quelles sont les améliorations évidentes à apporter?
  3. J'ai envisagé la variable "timeScale" qui optimisera les calculs, car il pourrait ne pas être nécessaire de recalculer les vecteurs et les positions pour chaque seconde. Avez-vous des réflexions sur la façon de le mettre en œuvre ou est-ce généralement une bonne idée? (perte de précision vs performances améliorées)

Fondamentalement, mon objectif est de lancer une discussion sur le sujet et de voir où il mène. Et, si possible, apprenez (ou mieux - enseignez) quelque chose de nouveau et d'intéressant.

N'hésitez pas à expérimenter!

Essayez d'utiliser:

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)

Avec deux brûlures - une prograde en orbite terrestre et une rétrograde en orbite lunaire, j'ai atteint une orbite lunaire stable. Ces valeurs sont-elles proches des valeurs théoriquement attendues?

Exercice suggéré: essayez-le en 3 brûlures - orbite terrestre stable depuis la surface de la terre, brûlure prograde pour atteindre la lune, brûlure rétrograde pour stabiliser l'orbite autour de la lune. Essayez ensuite de minimiser deltaV.

Remarque: je prévois de mettre à jour le code avec des commentaires détaillés pour ceux qui ne connaissent pas la syntaxe python3.

territoire de l'État
la source
Une très bonne idée d'auto-apprentissage! Serait-il possible de résumer vos formules pour ceux d'entre nous qui ne connaissent pas la syntaxe Python?
Bien sûr, je suppose. Je ferai des commentaires plus approfondis dans le code pour ceux qui veulent le reprendre et résumer la logique générale dans la question elle-même.
espace d'états
Du haut de ma tête: envisagez d'utiliser un vecteur pour la vitesse au lieu de traiter la vitesse et la direction différemment. Où vous dites "F = m * v" voulez-vous dire "F = m * a"? Vous supposez que la Terre ne bouge pas parce qu'elle est beaucoup plus lourde que l'astéroïde? Pensez à consulter github.com/barrycarter/bcapps/blob/master/bc-grav-sim.pl
barrycarter
Vous pouvez donner un mouvement à n'importe quel objet, y compris la Terre. À des fins de test, j'ai inclus uniquement l'objet -> Relation Terre dans la boucle principale. Il peut être facilement converti que chaque objet se rapporte à tous les autres objets qui sont créés. Et chaque objet peut avoir son propre vecteur de mouvement. Raison pour laquelle je ne l'ai pas fait - calculs très lents même pour 1 objet. J'espère que la mise à l'échelle des unités de temps devrait aider beaucoup, mais je ne sais toujours pas comment le faire correctement.
Statespace
1
D'ACCORD. Une pensée: faites la simulation pour deux objets réels (par exemple, Terre / Lune ou Terre / Soleil) et comparez vos résultats à ssd.jpl.nasa.gov/?horizons pour plus de précision? Ce ne sera pas parfait à cause des perturbations d'autres sources, mais cela vous donnera une idée de la précision?
barrycarter

Réponses:

11

m1,m2

F=ma
a

F21=Gm1m2|r21|3r21

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

r21=(x1x2y1y2).

et

|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.

Avec les positions et vitesses initiales, ce système d'équations différentielles ordinaires (ODE) comprend un problème de valeur initiale. L'approche habituelle consiste à écrire ceci comme un système de premier ordre de 8 équations et à appliquer une méthode de Runge-Kutta ou à plusieurs étapes pour les résoudre.

Si vous appliquez quelque chose de simple comme Euler avant ou Euler arrière, vous verrez la Terre en spirale vers l'infini ou vers le soleil, respectivement, mais c'est un effet des erreurs numériques. Si vous utilisez une méthode plus précise, comme la méthode Runge-Kutta classique du 4e ordre, vous constaterez qu'elle reste proche d'une véritable orbite pendant un certain temps mais finit par s'éteindre à l'infini. La bonne approche consiste à utiliser une méthode symplectique, qui maintiendra la Terre sur l'orbite correcte - bien que sa phase soit toujours désactivée en raison d'erreurs numériques.

Pour le problème à 2 corps, il est possible de dériver un système plus simple en basant votre système de coordonnées autour du centre de masse. Mais je pense que la formulation ci-dessus est plus claire si c'est nouveau pour vous.

David Ketcheson
la source
Cela prendra un certain temps à digérer.
espace d'états
Digestion toujours. Trop de mots inconnus pour moi mais d'une certaine manière je sens qu'à un moment donné j'y arriverai. Pour l'instant, mon propre algorithme est assez bon pour que les choses fonctionnent simplement. Mais quand je brancherai le mouvement simultané - je serai forcé d'accéder à la littérature et de lire sur les algorithmes appropriés. Étant donné que les limitations du matériel moderne sont beaucoup plus lâches, je peux me permettre de jouer avec des équations simples. Mais je n'ai pas peur longtemps.
espace d'états
En effet, les méthodes symplectiques sont de loin les plus précises mais je pense qu'il est difficile pour quelqu'un sans formation scientifique de les mettre en œuvre. Au lieu de cela, vous pouvez utiliser la méthode Euler très simple avec la correction de Feynman. Je ne pense pas que vous ayez besoin de quelque chose de plus complexe que cela à des fins d'auto-éducation.
chrispap