Skip to article frontmatterSkip to article content

pour faire cette activité sur votre ordi localement, commencez par télécharger le zip

dans ce TP on vous invite à écrire un simulateur de la trajectoire de n corps qui interagissent entre eux au travers de leurs masses, pour produire des sorties de ce genre

on suppose:

imports

on pourra utiliser le mode ipympl de matplotlib

import numpy as np
import matplotlib.pyplot as plt

%matplotlib ipympl

initialisation aléatoire

en fixant arbitrairement des limites dans l’espace des positions, des vitesses et des masses, la fonction init_problem() tire au hasard une configuration de départ pour la simulation

# les bornes pour le tirage au sort initial
mass_max = 3.
    
x_min, x_max = -10., 10.
y_min, y_max = -10., 10.

speed_max = 1.
# votre code

def init_problem(N):
    """
    retourne un tuple masses, positions, speeds
    de formes resp.   (N,)    (2, N)     (2, N)
    tiré au sort dans les bornes définies ci-dessus
    """
    return None, None, None
# pour tester

# normalement vous devez pouvoir faire ceci

masses, positions, speeds = init_problem(10)

# et ceci devrait afficher OK
try:
    masses.shape == (10,) and positions.shape == speeds.shape == (2, 10)
    print("OK")
except:
    print("KO")
KO

initialisation reproductible

par commodité on vous donne la fonction suivante qui crée 3 objets:

# for your convenience

def init3():
    # first element is sun-like: heavy, at the center, and no speed
    masses = np.array([3, 1, 1], dtype=float)
    positions = np.array([
        [0, 5, -5], 
        [0, 1, -1]], dtype=float)
    speeds = np.array([
        [0, -1, 1], 
        [0, 0, 0]], dtype=float)
    return masses, positions, speeds

les forces

à présent, on va écrire une fonction qui va calculer les influences de toutes les particules entre elles, suivant la loi de Newton

Fi=j=1jiNGmimjrjrirjri3\vec{F}_i = \sum_{\substack{j=1 \\ j \neq i}}^N G \, m_i m_j \, \frac{\vec{r}_j - \vec{r}_i}{\lvert \vec{r}_j - \vec{r}_i \rvert^3}

pour cela on se propose d’écrire la fonction suivante

# votre code

def forces(masses, positions, G=1.0):
    """
    returns an array of shape (2,  N)
    that contains the force felt by each mass from all the others
    G is the Newton constant and by default is set to 1 
    (we have abstract units anyway)
    """
    pass
# pour tester, voici les valeurs attendues avec la config prédéfinie

masses, positions, speeds = init3()

f = forces(masses, positions)

# should be true
# np.all(np.isclose(f, np.array([
#     [ 0.        , -0.12257258,  0.12257258],
#     [ 0.        , -0.02451452,  0.02451452]])))

le simulateur

à présent il nous reste à utiliser cette brique de base pour “faire avancer” le modèle depuis son état initial et sur un nombre fixe d’itérations

cela pourrait se passer dans une fonction qui ressemblerait à ceci

# votre code

def simulate(masses, positions, speeds, dt=0.1, nb_steps=100):
    """
    should return the positions across time
    so an array of shape (nb_steps, 2, N)
    optional dt is the time step
    """
    pass
# pour tester

SMALL_STEPS = 4

s = simulate(masses, positions, speeds, nb_steps=SMALL_STEPS)

try:
    if s.shape == (SMALL_STEPS, 2, 3):
        print("shape OK")
except Exception as exc:
    print(f"OOPS {type(exc)} {exc}")
OOPS <class 'AttributeError'> 'NoneType' object has no attribute 'shape'
# pour tester: should be true

# first step
# positions1 = s[1]

# np.all(np.isclose(positions1, np.array([
#     [ 0.        ,  4.89877427, -4.89877427],
#     [ 0.        ,  0.99975485, -0.99975485]
# ])))

dessiner

ne reste plus qu’à dessiner; quelques indices potentiels:

# votre code

def draw(simulation, masses, colors=None, scale=10.):
    """
    takes as input the result of simulate() above,
    and draws the nb_steps positions of each of the N bodies
    ideally it should return a matplotlib Axes object

    one can provide a collection of N colors to use for each body
    if not provided this is randomized

    also the optional scale parameter is used as a constant
    multiplier to obtain the final size of each dot on the figure
    """
    pass

un jeu de couleurs

# for convenience

colors3 = np.array([
    [32, 32, 32],
    (228, 90, 146),
    (111, 0, 255),
]) / 255

on assemble le tout

pour commencer et tester, on se met dans l’état initial reproductible

# décommentez ceci pour tester votre code

# masses, positions, speeds = init3()
# draw(simulate(masses, positions, speeds), masses, colors3)

et avec ces données vous devriez obtenir plus ou moins une sortie de ce genre
mais voyez aussi la discussion ci-dessous sur les diverses stratégies possibles

après vous avez le droit de vous enhardir avec des scénarii plus compliqués par exemple avec ce code

m5, p5, s5 = init_problem(5)
sim5 = simulate(m5, p5, s5, nb_steps=1000)
draw(sim5, m5, scale=3);
plt.savefig("random5.png")

j’ai pu obtenir ceci




partie optionnelle

option 1: la 3D

modifiez votre code pour passer à une simulation en 3D

option 2: un rendu plus interactif

le rendu sous forme de multiples scatter plots donne une idée du résultat mais c’est très améliorable
voyez un peu si vous arrivez à produire un outil un peu plus convivial pour explorer les résultats de manière interactive; avec genre

voici une possibilité avec matplotlib; mais cela dit ne vous sentez pas obligé de rester dans Jupyter Lab ou matplotlib, il y a plein de technos rigolotes qui savent se décliner sur le web, vous avez l’embarras du choix...

Loading...

plusieurs stratégies

Pour les curieux, vous avez sans doute observé qu’il y a plusieurs façons possibles d’écrire la fonction simulate()

dans ce qui suit, on note l’accélération aa, la vitesse ss et la position pp

Bref, vous voyez qu’il y a énormément de liberté sur la façon de s’y prendre
Ce qui peut expliquer pourquoi vous n’obtenez pas la même chose que les illustrations avec pourtant les mêmes données initiales

D’autant que, c’est bien connu, ce problème des n-corps est l’exemple le plus célèbre de problème instable, et donc la moindre divergence entre deux méthodes de calcul peut entrainer de très sérieux écarts sur les résultats obtenus

Voici d’ailleurs les résultats obtenus avec ces deux approches alternatives, et vous pouvez constater qu’effectivement les résultats sont tous très différents !

approche 1bis

Loading...

approche 2

Loading...