Chapitre 6 : Simulation physique Alexandre Blondin Massé Laboratoire d informatique formelle Université du Québec à Chicoutimi 12 juin 2014 Cours 8INF802 Département d informatique et mathématique A. Blondin Massé (UQAC) 12 juin 2014 1 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 2 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 3 / 51
Simulations physiques Dans certaines situations, la simulation repose sur des principes physiques; Plusieurs outils de domaines mathématiques sont fondamentaux : Algèbre linéaire; Calcul différentiel et intégral; Équations différentielles; Analyse numérique. A. Blondin Massé (UQAC) 12 juin 2014 4 / 51
Bibliothèques Pour tous les langages de programmation populaire, il existe évidemment des bibliothèques offrant ce genre d outils; C++ : Box2D, Chipmunk, Havoc, Bullet, Vortex, etc. Java : JBox2D, dyn4j, JBullet, Falling2D, etc. C# : Farseer, Box2Dx, Box2D.Xna, JigLib, Jitter, Henge3D, etc. Python : PyODE, pymunk, Pygame, pybox2d, Panda3D, Matplotlib, etc. A. Blondin Massé (UQAC) 12 juin 2014 5 / 51
Scipy Scipy est un ensemble d outils logiciels facilitant la programmation scientifique; Il inclut plusieurs paquets, tels que Numpy, pour le calcul numérique; Matplotlib, pour créer des dessins et des animations; Sympy, pour le calcul symbolique, etc. A. Blondin Massé (UQAC) 12 juin 2014 6 / 51
Numpy Numpy est le noyau de Scipy qui prend en charge les calculs numériques; Il offre entre autres : Des tableaux multidimensionnels efficaces; Plusieurs fonctions pour manipuler les tableaux; La transformée de Fourier discrète; Des fonctions d algèbre linéaire; Des fonctions mathématiques de base. A. Blondin Massé (UQAC) 12 juin 2014 7 / 51
Matplotlib Matplotlib offre différents services de dessins et d animations; On peut entre autres créer Des graphiques de fonctions; Des histogrammes; Des graphiques en 3D; Des champs de vecteurs; Des diagrammes circulaires. A. Blondin Massé (UQAC) 12 juin 2014 8 / 51
Exemple (1/3) Animation d une vague : basic_animation.py; On importe les modules nécessaires : # Imports import numpy as np from matplotlib import pyplot as plt from matplotlib import animation On configure l image : # First set up the figure, the axis, and the plot element # we want to animate fig = plt.figure() ax = plt.axes(xlim=(0, 2), ylim=(-2, 2)) line, = ax.plot([], [], b, lw=2) A. Blondin Massé (UQAC) 12 juin 2014 9 / 51
Exemple (2/3) Pour créer une animation, il faut une fonction d initialisation : # initialization function: plot the background of each # frame def init(): line.set_data([], []) return line, Puis une fonction principale pour l animation : # animation function. This is called sequentially def animate(i): x = np.linspace(0, 2, 1000) y = np.sin(2 * np.pi * (x - 0.01 * i)) line.set_data(x, y) return line, A. Blondin Massé (UQAC) 12 juin 2014 10 / 51
Exemple (3/3) On lance l animation : # call the animator. blit=true means only re-draw # the parts that have changed. anim = animation.funcanimation(fig, animate, init_func=init, frames=200, interval=20, blit=false) # start animation plt.show() A. Blondin Massé (UQAC) 12 juin 2014 11 / 51
Simulations physiques simples En combinant Numpy, Scipy, Matplotlib, on peut créer en très peu de temps des simulations physiques complexes. On verra quelques modèles : Pendule simple; Pendule double; Des particules en collision; Une simulation de type montagne russe. A. Blondin Massé (UQAC) 12 juin 2014 12 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 13 / 51
Équations de Navier-Stokes Équation générale : ( ) v ρ t + v v = p + µ 2 v. où v est le champ de vitesse du fluide; ρ est la masse volumique du fluide; p est la pression; µ est la viscosité. A. Blondin Massé (UQAC) 12 juin 2014 14 / 51
Applet Voir http://www.escapemotions.com/experiments/ fluid_water_2/. A. Blondin Massé (UQAC) 12 juin 2014 15 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 16 / 51
Simulation Voir simple_pendulum.py A. Blondin Massé (UQAC) 12 juin 2014 17 / 51
État du système l θ Une extrémité est fixée; L autre est libre; On suppose que la ficelle est toujours pleinement tendue; On suppose qu il n y a aucune friction. Pour modéliser le problème, il faudrait connaître θ(t). A. Blondin Massé (UQAC) 12 juin 2014 18 / 51
Équation différentielle (1/4) θ l Avant de se lancer dans la programmation, il faut d abord décrire l équation différentielle qui modélise un pendule; Dans un premier temps, rappelons la seconde loi de Newton : F = ma. A. Blondin Massé (UQAC) 12 juin 2014 19 / 51
Équation différentielle (2/4) θ l Clairement, le vecteur vitesse de la boule est toujours perpendiculaire à la ficelle; Par conséquent, la composante de la force de gravité agissant de façon perpendiculaire à la ficelle est donnée par F = mg sin θ = ma = a = g sin θ. A. Blondin Massé (UQAC) 12 juin 2014 20 / 51
Équation différentielle (3/4) θ l Il nous reste à calculer l accélération angulaire; Or, le déplacement est donné par s = lθ; En dérivant deux fois, on trouve a = l d2 θ dt 2. A. Blondin Massé (UQAC) 12 juin 2014 21 / 51
Équation différentielle (4/4) θ l En recombinant, on obtient l équation différentielle : d 2 θ dt 2 + g sin θ = 0. l A. Blondin Massé (UQAC) 12 juin 2014 22 / 51
Résolution de l équation L équation d 2 θ dt 2 + g sin θ = 0. l est difficile à résoudre de façon exacte; Par contre, elle devient beaucoup plus simple si on utilise l approximation sin(θ) θ; On obtient alors une équation du premier ordre linéaire : d 2 θ dt 2 + g l θ = 0. Avec conditions initiales θ(0) = θ 0 et dθ/dt(0) = 0, la solution particulière est ( ) g θ(t) = θ 0 cos l t. A. Blondin Massé (UQAC) 12 juin 2014 23 / 51
La classe SimplePendulum (1/2) class SimplePendulum: """Simple Pendulum Class""" def init (self, init_angle=50, L=1.0, # length of pendulum in m G=9.8, # acceleration due to gravity, in m/s^2 origin=(0,0)): self.l = L self.g = G self.origin = origin self.time_elapsed = 0 self.init_angle = init_angle * np.pi / 180. self.angle = self.init_angle A. Blondin Massé (UQAC) 12 juin 2014 24 / 51
La classe SimplePendulum (2/2) def position(self): """compute the current x,y position of the pendulum""" x = np.cumsum([self.origin[0], self.l * np.sin(self. angle)]) y = np.cumsum([self.origin[1], -self.l * np.cos(self. angle)]) return (x, y) def step(self, dt): """execute one time step of length dt and update state """ self.time_elapsed += dt self.angle = self.init_angle * np.cos(np.sqrt( self.g / self.l) * self.time_elapsed) A. Blondin Massé (UQAC) 12 juin 2014 25 / 51
Initialisation #------------------------------------------------------------ # set up initial state and global variables pendulum = SimplePendulum(40) dt = 1./30 # 30 fps #------------------------------------------------------------ # set up figure and animation fig = plt.figure() ax = fig.add_subplot(111, aspect= equal, autoscale_on=false, xlim=(-2, 2), ylim=(-2, 2)) ax.grid() line, = ax.plot([], [], o-, lw=2) time_text = ax.text(0.02, 0.95,, transform=ax.transaxes) A. Blondin Massé (UQAC) 12 juin 2014 26 / 51
Fonctions d animation def init(): """initialize animation""" line.set_data([], []) time_text.set_text( ) return line, time_text def animate(i): """perform animation step""" global pendulum, dt pendulum.step(dt) line.set_data(*pendulum.position()) time_text.set_text( time = %.1f % pendulum.time_elapsed) return line, time_text A. Blondin Massé (UQAC) 12 juin 2014 27 / 51
Lancement de l animation # choose interval based on dt and the time to animate one step from time import time t0 = time() animate(0) t1 = time() interval = 1000 * dt - (t1 - t0) ani = animation.funcanimation(fig, animate, frames=300, interval=interval, blit=false, init_func=init) plt.show() A. Blondin Massé (UQAC) 12 juin 2014 28 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 29 / 51
Simulation Voir double_pendulum.py A. Blondin Massé (UQAC) 12 juin 2014 30 / 51
État du système À tout moment, le système est représenté par (0, 0) Les angles θ1 et θ 2 ; Les vitesses angulaires θ 1 et θ 2. Les coordonnées sont obtenues par : θ 1 l 1 (x 1, y 1 ) l 2 x 1 = l 1 sin(θ 1 ), y 1 = l 1 cos(θ 1 ), x 2 = l 1 sin(θ 1 ) + l 2 sin(θ 2 ), y 2 = l 1 cos(θ 1 ) l 2 cos(θ 2 ). θ 2 (x 2, y 2 ) A. Blondin Massé (UQAC) 12 juin 2014 31 / 51
Paramètres du système class DoublePendulum: """Double Pendulum Class init_state is [theta1, omega1, theta2, omega2] in degrees, where theta1, omega1 is the angular position and velocity of the first pendulum arm, and theta2, omega2 is that of the second pendulum arm """ def init (self, init_state = [120, 0, -20, 0], L1=1.0, # length of pendulum 1 in m L2=1.0, # length of pendulum 2 in m M1=1.0, # mass of pendulum 1 in kg M2=1.0, # mass of pendulum 2 in kg G=9.8, # acceleration due to gravity, in m/s^2 origin=(0, 0)): self.init_state = np.asarray(init_state, dtype= float ) self.params = (L1, L2, M1, M2, G) self.origin = origin self.time_elapsed = 0 self.state = self.init_state * np.pi / 180. A. Blondin Massé (UQAC) 12 juin 2014 32 / 51
Position des pendules def position(self): """compute the current x,y positions of the pendulum arms""" (L1, L2, M1, M2, G) = self.params x = np.cumsum([self.origin[0], L1 * sin(self.state[0]), L2 * sin(self.state[2])]) y = np.cumsum([self.origin[1], -L1 * cos(self.state[0]), -L2 * cos(self.state[2])]) return (x, y) A. Blondin Massé (UQAC) 12 juin 2014 33 / 51
Énergie du système def energy(self): """compute the energy of the current state""" (L1, L2, M1, M2, G) = self.params x = np.cumsum([l1 * sin(self.state[0]), L2 * sin(self.state[2])]) y = np.cumsum([-l1 * cos(self.state[0]), -L2 * cos(self.state[2])]) vx = np.cumsum([l1 * self.state[1] * cos(self.state[0]), L2 * self.state[3] * cos(self.state[2]) ]) vy = np.cumsum([l1 * self.state[1] * sin(self.state[0]), L2 * self.state[3] * sin(self.state[2]) ]) U = G * (M1 * y[0] + M2 * y[1]) K = 0.5 * (M1 * np.dot(vx, vx) + M2 * np.dot(vy, vy)) return U + K A. Blondin Massé (UQAC) 12 juin 2014 34 / 51
Équations différentielles (1/4) Les équations différentielles modélisant un système de deux pendules sont considérablement plus complexes; Dans un premier temps, il faut calculer le lagrangien : où L = K P. K = m 1θ 2 1 l2 1 + m 2[θ 2 1 l2 1 + θ 2 2 l2 2 + 2θ 1 l 1θ 2 l 2 cos(θ 1 θ 2 )] 2 P = (m 1 + m 2 )gl 1 cos θ 1 m 2 l 2 g cos θ 2 Puis les équations différentielles sont données par ( ) d L L = 0, i = 1, 2. dt θ i θ i A. Blondin Massé (UQAC) 12 juin 2014 35 / 51
Équations différentielles (2/4) On obtient alors le système suivant : θ 1 = θ 2 = [ m 2 l 1 θ 2 1 sin(θ 1 θ 2 ) cos(θ 1 θ 2 ) + gm 2 sin θ 2 cos(θ 1 θ 2 ) m 2 l 2 θ 2 2 sin(θ 1 θ 2 ) (m 1 + m 2 )g sin θ 1 ]/[l 1 (m 1 + m 2 ) ] m 2 l 1 cos 2 (θ 1 θ 2 ) [ m 2 l 2 θ 2 2 sin(θ 1 θ 2 ) cos(θ 1 θ 2 ) + g sin θ 1 cos(θ 1 θ 2 )(m 1 + m 2 ) ]/[ l 1 θ 1 2 sin(θ 1 θ 2 )(m 1 + m 2 ) g sin θ 2 (m 1 + m 2 ) l 2 (m 1 + m 2 ) ] m 2 l 2 cos 2 (θ 1 θ 2 ). Il s agit d équations d ordre deux, qu on peut convertir en un système d ordre un. A. Blondin Massé (UQAC) 12 juin 2014 36 / 51
Équations différentielles (3/4) Considérons le changement de variables : z 1 = θ 1 z 2 = θ 2 z 3 = θ 1 z 4 = θ 1 Alors z 1 = θ 1 z 2 = θ 2 z 3 = θ 1 z 4 = θ 1 A. Blondin Massé (UQAC) 12 juin 2014 37 / 51
Équations différentielles (4/4) On obtient ENFIN un système du premier ordre : z 1 = θ 1 z 2 = θ 2 [ z 3 = m 2 l 1 z4 2 sin(z 1 z 2 ) + gm 2 sin z 2 cos(z 1 z 2 ) z 4 = m 2 l 2 z 2 4 sin(z 1 z 2 ) (m 1 + m 2 )g sin z 1 ]/ [ ] l 1 (m 1 + m 2 ) m 2 l 1 cos 2 (z 1 z 2 ) [ m 2 l 2 z 2 4 sin(z 1 z 2 ) cos(z 1 z 2 ) + g sin(z 1 ) cos(z 1 z 2 )(m 1 + m 2 ) ]/ l 1 z4 2 sin(z 1 z 2 )(m 1 + m 2 ) g sin z 2 (m 1 + m 2 ) [ ] l 2 (m 1 + m 2 ) m 2 l 2 cos 2 (z 1 z 2 ). A. Blondin Massé (UQAC) 12 juin 2014 38 / 51
Écriture des équations en code def dstate_dt(self, state, t): """compute the derivative of the given state""" (M1, M2, L1, L2, G) = self.params dydx = np.zeros_like(state) dydx[0] = state[1] dydx[2] = state[3] cos_delta = cos(state[2] - state[0]) sin_delta = sin(state[2] - state[0]) num1 = M2 * L1 * state[1] * state[1] * sin_delta * cos_delta + M2 * G * sin(state[2]) * cos_delta + M2 * L2 * state[3] * state[3] * sin_delta - (M1 + M2) * G * sin(state[0]) den1 = (M1 + M2) * L1 - M2 * L1 * cos_delta * cos_delta dydx[1] = num1 / den1 num2 = (-M2 * L2 * state[3] * state[3] * sin_delta * cos_delta + (M1 + M2) * G * sin(state[0]) * cos_delta - (M1 + M2) * L1 * state[1] * state[1] * sin_delta - (M1 + M2) * G * sin(state[2])) den2 = (L2 / L1) * den1 dydx[3] = num2 / den2 return dydx A. Blondin Massé (UQAC) 12 juin 2014 39 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 40 / 51
Simulation Voir particle_box.py Chaque particule est représentée par un quadruplet (x, y, v x, v y ) indiquant la position et la vitesse; Il y a deux types de collisions à traiter : Collisions entre particules; Collisions avec mur. A. Blondin Massé (UQAC) 12 juin 2014 41 / 51
Mise à jour positions/vitesses Avec la numpy, la mise à jour des positions est très simple : # update positions self.state[:, :2] += dt * self.state[:, 2:] La mise à jour des vitesses tient compte de la gravité : # add gravity self.state[:, 3] -= self.m * self.g * dt Il reste à prendre en compte les collisions. A. Blondin Massé (UQAC) 12 juin 2014 42 / 51
Gestion des collisions avec les murs Il suffit de vérifier si les limites ont été dépassé; Ensuite, on inverse le signe de la composante du vecteur vitesse s il y a lieu : # check for crossing boundary crossed_x1 = (self.state[:, 0] < self.bounds[0] + self.size) crossed_x2 = (self.state[:, 0] > self.bounds[1] - self.size) crossed_y1 = (self.state[:, 1] < self.bounds[2] + self.size) crossed_y2 = (self.state[:, 1] > self.bounds[3] - self.size) self.state[crossed_x1, 0] = self.bounds[0] + self.size self.state[crossed_x2, 0] = self.bounds[1] - self.size self.state[crossed_y1, 1] = self.bounds[2] + self.size self.state[crossed_y2, 1] = self.bounds[3] - self.size self.state[crossed_x1 crossed_x2, 2] *= -1 self.state[crossed_y1 crossed_y2, 3] *= -1 A. Blondin Massé (UQAC) 12 juin 2014 43 / 51
Gestion des collisions entre particules (1/2) Lors d une collision élastique, les moments sont préservés : m 1 v 1 + m 2 v 2 = m 1 v 1 + m 2 v 2. De la même façon, l énergie cinétique est préservée : m 1 v 2 1 2 + m 2v 2 2 2 = m 1v 1 2 2 + m 2v 2 2. 2 Ces équations sont vérifiées pour chaque dimension (ce sont donc des scalaires). A. Blondin Massé (UQAC) 12 juin 2014 44 / 51
Gestion des collisions entre particules (1/2) Par conséquent, en combinant la conservation du moment et de l énergie cinétique, on en déduit v 1 = vmoy + m 2(v 2 v 1 ) m 1 + m 2, v 2 = vmoy + m 1(v 1 v 2 ) m 1 + m 2. A. Blondin Massé (UQAC) 12 juin 2014 45 / 51
Table des matières 1. Introduction 2. Mécanique des fluides 3. Pendule simple 4. Pendule double 5. Particules dans une boîte 6. Montagnes russes A. Blondin Massé (UQAC) 12 juin 2014 46 / 51
Simulation Voir roller_coaster.py A. Blondin Massé (UQAC) 12 juin 2014 47 / 51
Courbe paramétrée t = 1.5 t = 1.5 t = 1 t = 1 t = 0 Une paramétrisation possible est r(t) = (t, t 2 ). Problème : la vitesse ne doit pas dépendre de la paramétrisation. A. Blondin Massé (UQAC) 12 juin 2014 48 / 51
Abscisse curviligne l = 3.1521 l = 3.1521 l = 1.4790 l = 1.4790 l = 0 Il suffit de reparamétrer de sorte que le paramètre corresponde à la longueur d arc; Cette paramétrisation s appelle l abscisse curviligne. A. Blondin Massé (UQAC) 12 juin 2014 49 / 51
Physique des montagnes russes À tout moment, le vecteur vitesse est tangent à la courbe; Si k(t) est la pente de la courbe au point t, alors la norme du vecteur accélération est a = gk(t) 1 + k(t) 2. A. Blondin Massé (UQAC) 12 juin 2014 50 / 51
Mise à jour de l état de la balle Il suffit donc, à chaque étape, de mettre à jour la position et la vitesse comme suit : def step(self, dt): r""" Updates the position and velocity according to the given time difference """ self.time_elapsed += dt # update positions self.p += dt * self.v # update velocity k = self.orbit.p_to_k(self.p) if k is None: self.v += -self.g * dt else: self.v += -self.g * k * dt / np.sqrt(1 + k**2) A. Blondin Massé (UQAC) 12 juin 2014 51 / 51