3. Simulation de mouvement en mécanique#

Marc BUFFAT, Université Claude Bernard Lyon 1

%matplotlib inline
# bibliotheques de base
import matplotlib
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 14
from IPython.core.display import HTML

3.1. Mouvement du pendule double#

pendule double

double pendule

3.1.1. expérience: mouvement chaotique#

HTML('<iframe width="640" height="360" src="https://www.youtube.com/embed/AwT0k09w-jw" frameborder="0" allowfullscreen></iframe>')
/home/buffat/venvs/jupyter/lib/python3.10/site-packages/IPython/core/display.py:431: UserWarning: Consider using IPython.display.IFrame instead
  warnings.warn("Consider using IPython.display.IFrame instead")

3.1.2. modélisation#

import scipy.integrate as integrate
from   time import time
# from http://www.physics.usyd.edu.au/
# modifie par Marc BUFFAT 2014
# parametres
G =  9.8 # acceleration due to gravity, in m/s^2
L1 = 1.0 # length of pendulum 1 in m
L2 = 0.5 # length of pendulum 2 in m
M1 = 1.0 # mass of pendulum 1 in kg
M2 = 1.0 # mass of pendulum 2 in kg

# equation du mvt en theta, dtheta
# U = [theta1, dtheta1, theta2, dtheta2]
def F(U,t):
    """ second membre EDO DU/dt=F(U,t) """
    dydx    = np.zeros((4))
    dydx[0] = U[1]
    delta   = U[2]-U[0]
    den1    = (M1+M2)*L1 - M2*L1*np.cos(delta)*np.cos(delta)
    dydx[1] = (M2*L1*U[1]*U[1]*np.sin(delta)*np.cos(delta)
               + M2*G*np.sin(U[2])*np.cos(delta) + M2*L2*U[3]*U[3]*np.sin(delta)
               - (M1+M2)*G*np.sin(U[0]))/den1
    dydx[2] = U[3]
    den2    = (L2/L1)*den1
    dydx[3] = (-M2*L2*U[3]*U[3]*np.sin(delta)*np.cos(delta)
               + (M1+M2)*G*np.sin(U[0])*np.cos(delta)
               - (M1+M2)*L1*U[1]*U[1]*np.sin(delta)
               - (M1+M2)*G*np.sin(U[2]))/den2
    return dydx
# calcul energie du systeme
def Energie(U):
    Ec1=0.5*M1*(L1*U[1])**2
    Ec2=0.5*M2*((L1*U[1])**2+(L2*U[3])**2
              +2*L1*L2*np.cos(U[2]-U[0])*U[1]*U[3])
    Ep1=-M1*G*L1*np.cos(U[0])
    Ep2=-M2*G*(L2*np.cos(U[2])+L1*np.cos(U[0]))
    H  = Ec1+Ec2+Ep1+Ep2
    return np.array([Ec1,Ec2,Ep1,Ep2,H])
# positions et vitesses initiales
theta1 = np.pi
w1     = -1.5
theta2 = 0.0
w2  = 0.0
# initial state
U0 = np.array([theta1, w1, theta2, w2])
# temps de visualisation
dt = 0.05
tf = 20.0
N  = int(tf/dt)+1;
t  = np.linspace(0.0,tf,N)
#integrate your ODE using scipy.integrate.
debut = time()
y = integrate.odeint(F, U0, t)
cpu = time() - debut
# solution en X,Y
th1 = y[:,0]; th2 = y[:,2]

# tracer
print("Calcul en %.2g sec"%cpu)
print("Energie t=0",Energie(y[0,:]))
print("Energie t=",tf,Energie(y[-1,:]))
Calcul en 0.12 sec
Energie t=0 [ 1.125  1.125  9.8    4.9   16.95 ]
Energie t= 20.0 [13.81088792  7.62023666 -0.52980043 -3.95133817 16.94998599]
def tracesol(t,th1,th2):
    # tracer solution 
    fig = plt.figure(figsize=(12,6))
    ax1 = fig.add_subplot(121)
    ax1.plot(t,np.sin(th1))
    ax1.plot(t,np.sin(th2))
    ax1.set_title("sinus angle fct du temps")
    ax2 = fig.add_subplot(122)
    ax2.plot(np.sin(th1),np.sin(th2))
    ax2.set_title("espace des phases")
    ax2.axis('equal')
    plt.xlim(-1,1)
    plt.ylim(-1,1)
    return
# tracer solution 
tracesol(t,th1,th2)
../../_images/f054524a74978b7aa57f0e9c52c1b035f4c23c5f48a99bef228b34b1611ba9bd.png
# evolution energie
H=np.zeros((N,5))
for i in range(N):
    H[i,:]=Energie(y[i,:])
# tracer
fig = plt.figure(figsize=(14,6))
ax1 = fig.add_subplot(121)
ax1.plot(t,H[:,0],label="Ec1")
ax1.plot(t,H[:,1],label="Ec2")
ax1.plot(t,H[:,2],label="Ep1")
ax1.plot(t,H[:,3],label="Ep2")
ax1.plot(t,H[:,4],label="H")
ax1.set_title("energie fct du temps")
ax1.legend(loc=0) 
ax2 = fig.add_subplot(122)
ax2.plot(t,(H[:,4]-H[0,4])/H[0,4])
formatter = matplotlib.ticker.ScalarFormatter()
formatter.set_powerlimits((-3,3))
ax2.yaxis.set_major_formatter(formatter)
ax2.set_title("Erreur")
Text(0.5, 1.0, 'Erreur')
../../_images/74a9746dce50abe16c853602e9c5bf9c91e9f81a992f91a2d2017142f4b39294.png
# animation 
#from   JSAnimation import IPython_display
import matplotlib.animation as animation
# variables globales
line=None
time_text=None
time_template=None
x1=None; y1=None; x2=None; y2=None;
fig=None; ax1=None

def init():
    global line,time_text
    line.set_data([], [])
    time_text.set_text('')
    return line, time_text,

def animate(i):
    global line,time_text,time_template,x1,x2,y1,y2
    Xp=np.zeros(3); Yp=np.zeros(3);
    Xp[1:] = [x1[i], x2[i]]
    Yp[1:] = [y1[i], y2[i]]
    line.set_data(Xp,Yp)
    time_text.set_text(time_template%(i*dt))
    return line, time_text,

def init_anim(th1,th2):
    global fig,ax1,line,time_text,time_template,x1,y1,x2,y2
    x1 =  L1*np.sin(th1);      
    y1 = -L1*np.cos(th1)
    x2 =  L2*np.sin(th2) + x1; 
    y2 = -L2*np.cos(th2) + y1
    xmin=-1.2*(L1+L2); xmax=-xmin
    ymin=-1.2*(L1+L2); ymax=-ymin
    fig = plt.figure(figsize=(10,5))
    ax1 = fig.add_subplot(111, aspect='equal')
    ax1.set_axis_on()
    ax1.set_xlim((xmin,xmax))
    ax1.set_ylim((ymin,ymax))
    fig.set_facecolor("#ffffff")
    ax1.plot(x1,y1,'-r')
    ax1.plot(x2,y2,'-g')
    line, = ax1.plot([], [], 'o-b', lw=5, markersize=20)
    time_template = 't = %.1fs'
    time_text = ax1.text(0.05, 0.9, '', transform=ax1.transAxes, fontsize=20)
    return
# trace animation
fig = plt.figure(figsize=(14,6))
init_anim(th1,th2)
anim=animation.FuncAnimation(fig, animate,frames= th1.size,
        interval=15, blit=True, init_func=init, repeat = False)
<Figure size 1400x600 with 0 Axes>
../../_images/615dd2d515f9a4b5602d57728816aa540913552e80b2c29325b147a073a16107.png
HTML(anim.to_html5_video())

3.1.3. modes du pendule double fonction de \(\theta_1\)#

# positions et vitesses initiales
w1     = 0.0
theta2 = 0.0
w2     = 0.0
# temps de visualisation
dt = 0.05
tf = 20.0
N  = int(tf/dt)+1;
t  = np.linspace(0.0,tf,N)

3.1.4. cas lineaire#

theta1 = np.pi/20
# cd initiales
U0 = np.array([theta1, w1, theta2, w2])
y = integrate.odeint(F, U0, t)
# solution en X,Y
th1 = y[:,0]; th2 = y[:,2]
tracesol(t,th1,th2)
../../_images/0ecd5f70f904c70fa936f8f6be011daca1d847aa8eb28b082151910f16195ed5.png
# trace animation
fig = plt.figure(figsize=(14,6))
init_anim(th1,th2)
anim=animation.FuncAnimation(fig, animate,frames= th1.size,
        interval=15, blit=True, init_func=init, repeat = False)
<Figure size 1400x600 with 0 Axes>
../../_images/ec56e9bded650e141d8b5b346105f4bde9ed8cdd3d04c8a35122d720fb5ac347.png
HTML(anim.to_html5_video())

3.1.5. cas non lineaire#

theta1 = np.pi/4
# cd initiales
U0 = np.array([theta1, w1, theta2, w2])
y = integrate.odeint(F, U0, t)
# solution en X,Y
th1 = y[:,0]; th2 = y[:,2]
tracesol(t,th1,th2)
../../_images/b0159f2fa014dfd0200f353056441966e89baaf24f2417abc2b183e4bbe33fcd.png
# trace animation
fig = plt.figure(figsize=(14,6))
init_anim(th1,th2)
anim=animation.FuncAnimation(fig, animate,frames= th1.size,
        interval=15, blit=True, init_func=init, repeat = False)
<Figure size 1400x600 with 0 Axes>
../../_images/cf6496b84d71a1c8f0b49d1ce9d034e09dad78623ddbcee999ec5f1e1be79c18.png
HTML(anim.to_html5_video())

3.1.6. cas chaotique#

theta1 = 0.8*np.pi/2
# cd initiales
U0 = np.array([theta1, w1, theta2, w2])
y = integrate.odeint(F, U0, t)
# solution en X,Y
th1 = y[:,0]; th2 = y[:,2]
tracesol(t,th1,th2)
../../_images/a2da499fe14939c32436c437a29495fa0b472f51113a721fdde8ede4beef5ba7.png
# trace animation
fig = plt.figure(figsize=(14,6))
init_anim(th1,th2)
anim=animation.FuncAnimation(fig, animate,frames= th1.size,
        interval=15, blit=True, init_func=init, repeat = False)
<Figure size 1400x600 with 0 Axes>
../../_images/76cda93454cc85cad5eab8b16f710311391318cc88c2700e288ae4f5d656443f.png
HTML(anim.to_html5_video())

3.2. Problème des 3 corps#

Système solaire

3.2.1. Modélisation#

# Animation problème des 3 corps
# (C) Marc BUFFAT
import matplotlib.pyplot as plt
from matplotlib import animation

# unites astronomiques
Lt =1.49e11 # distance terre soleil
Mt=6.0e24  # masse de la terre
Tt =3600*24*365.25 # annee terrestre
# constante gravitationnelle sans dimension
G=6.67e-11/(Lt**3/Mt/Tt**2)
print("Probleme sans dimension (UAS) G=",G, " avec Tref = 1 annee")
#
# calcul des Forces
# =================
def Force(X):
    """ calcul des forces gravitationnelles / masse """
    Fg=np.zeros(np.shape(X))
    X01=X[1,:]-X[0,:]
    X02=X[2,:]-X[0,:]
    X12=X[2,:]-X[1,:]
    r01 = np.sqrt(np.dot(X01,X01))
    a01 = G*M[0]*M[1]/r01**3
    r02 = np.sqrt(np.dot(X02,X02))
    a02 = G*M[0]*M[2]/r02**3
    r12 = np.sqrt(np.dot(X12,X12))
    a12 = G*M[1]*M[2]/r12**3
    Fg[0,:]=  a01/M[0]*X01 + a02/M[0]*X02
    Fg[1,:]= -a01/M[1]*X01 - a12/M[1]*X12
    Fg[2,:]= -a02/M[2]*X02 + a12/M[2]*X12
    return Fg
Probleme sans dimension (UAS) G= 0.00012048312217859223  avec Tref = 1 annee
# calcul energie du systeme
def Energie(U):
    X01=U[2:4]-U[0:2]
    X02=U[4:6]-U[0:2]
    X12=U[4:6]-U[2:4]
    r01 = np.sqrt(np.dot(X01,X01))
    r02 = np.sqrt(np.dot(X02,X02))
    r12 = np.sqrt(np.dot(X12,X12))
    U0_2 =  np.dot(U[6:8],U[6:8])
    U1_2 =  np.dot(U[8:10],U[8:10])
    U2_2 =  np.dot(U[10:12],U[10:12])
    # par unite de masse
    H0 = 0.5*M[0]*U0_2 - G*( M[0]*M[1]/r01 + M[0]*M[2]/r02)
    H1 = 0.5*M[1]*U1_2 - G*(-M[1]*M[0]/r01 - M[1]*M[2]/r12)
    H2 = 0.5*M[2]*U2_2 - G*(-M[2]*M[0]/r02 + M[2]*M[1]/r12)
    return np.array([H0,H1,H2])
# integration avec ODEint
def F(U,t):
    """ second membre EDO DU/dt=F(U,t) """
    X  = np.reshape(U[0:6],(3,2))
    Fg = Force(X)
    dU = np.zeros(12)
    dU[0:6] = U[6:]
    dU[6: ] = np.reshape(Fg,6)
    return dU
# parametres sans dimension masse, position et vitesse initiale
# soleil
m0 = 1.99e30/Mt
r0 = 0.
# terre
m1 = 1.
r1 = 1.
# jupiter
m2 = 1.9e27/Mt
r2 = 7.75e11/Lt
# calcul des CI (systeme a 2 corps soleil + planete)
# vitesse de rotation du systeme a 2 corps
# terre
R1=r0+r1
omega1=np.sqrt(G*(m0+m1)/(R1**3))
t1=2*np.pi/omega1    # periode
mu1 = m1/(m0+m1)
C1=mu1*R1 # centre de gravite
# Jupiter
R2=r0+r2
omega2=np.sqrt(G*(m0+m2)/(R2**3))
t2=2*np.pi/omega2
mu2 = m2/(m0+m2)
C2=mu2*R2
# d'ou les CI
u1=0.
v1=omega1*(R1-C1)
u2=0.
v2=omega2*(R2-C2)
u0=0.
v0=-omega2*C2 - omega1*C1
rr2 = r2*1.1

3.2.2. utilisation solveur odeint#

# cdts initiale: position puis vitesse
M =np.array([m0,m1,m2])
U0=np.array([0., 0.,r1,0,r2,0,u0, v0,u1,v1,u2,v2])
N=500
t = t2  # temps d'integration
T = np.linspace(0,t,N)
debut = time()
U = integrate.odeint(F, U0, T)
cpu = time() - debut
print("position finale :",U[-1,0:6])
print("tcpu odeint :",cpu)
position finale : [ 1.41914415e-06  2.68764184e-06  5.29299130e-01 -8.48970806e-01
  5.20134234e+00 -1.33990745e-04]
tcpu odeint : 0.053229570388793945
plt.figure(figsize=(12,6))
plt.subplot(1,2,1)
plt.plot(T,U[:,0],label='soleil')
plt.plot(T,U[:,2],label='terre')
plt.plot(T,U[:,4],label='jupiter')
plt.legend(loc=0)
plt.subplot(1,2,2)
plt.axis('equal')
plt.plot(U[:,0],U[:,1])
plt.plot(U[:,2],U[:,3])
plt.plot(U[:,4],U[:,5])
plt.scatter(U[-1,0:6:2],U[-1,1:6:2],c=['y','b','r'],marker='o',s=[100,60,80])
<matplotlib.collections.PathCollection at 0x7f437c0b5030>
../../_images/5d0d6d430d4bb1b1be43e953476c7527fd9725705d1a2aefcfb1c65cded1b3ba.png
# energie
print("Energie t=0",Energie(U[0,:]))
print("Energie t=",tf,Energie(U[-1,:]))
H=np.zeros((N,3))
for i in range(N):
    H[i,:]=Energie(U[i,:])
Energie t=0 [-2471.63103969    59.94937419  3648.102448  ]
Energie t= 20.0 [-2471.62133625    59.92153231  3648.10394574]
plt.figure(figsize=(10,8))
plt.title("Erreur en %")
plt.plot(T,(H[:,0]-H[0,0])/H[0,0],label='soleil')
plt.plot(T,(H[:,1]-H[0,1])/H[0,1],label='terre')
plt.plot(T,(H[:,2]-H[0,2])/H[0,2],label='jupiter')
plt.legend(loc=0)
<matplotlib.legend.Legend at 0x7f437c1460e0>
../../_images/abce9b3cd15b88506da41a773e837f58640e52316019ec1727d70055aa53a931.png

L’erreur est faible, mais peut devenir importante pour des intégrations sur plusieurs milliers d’années. En astro-physique on utilise donc des algorithmes spécifiques conservant l’énergie (mécanique celeste)

solution avec solver implicite BDF (lsoda)

3.3. FIN#