Code source de ensae_teaching_cs.special.corde

# -*- coding: utf-8 -*-
"""
Simulates a string which is falling but tied by its extremities. See :ref:`l-corde`.


:githublink:`%|py|6`
"""
import os
import math
from pyquickhelper.loghelper import fLOG
from ..helpers.pygame_helper import wait_event, empty_main_loop


[docs]class Point: """ définition d'un point : deux coordonnées et une masse :githublink:`%|py|15` """ __slots__ = "x", "y", "m"
[docs] def __init__(self, x, y, m): """ définit un point de la corde, de coordonnées (x,y) et de masse m :githublink:`%|py|21` """ self.x, self.y, self.m = float(x), float(y), float(m)
[docs] def deplace_point(self, dep, dt): """ déplace un point, le vecteur de déplacement est dp * dt où dep est aussi un point :githublink:`%|py|28` """ self.x += float(dep.x) * dt self.y += float(dep.y) * dt
[docs] def difference(self, p): """ retourne le vecteur qui relie deux points, retourne un point :githublink:`%|py|35` """ return Point(p.x - self.x, p.y - self.y, 0)
[docs] def norme(self): """ retourne la norme du vecteur (x,y) :githublink:`%|py|41` """ return math.sqrt(self.x * self.x + self.y * self.y)
[docs] def __str__(self): """ afficher le point :githublink:`%|py|47` """ return "(x,y) = (%4.2f,%4.2f) masse %f" % (self.x, self.y, self.m)
[docs]class ObjetMasseReliees: """ Définit un objet commun à une corde ou un pendule physiquement représenté comme un ensemble de masses reliées des élastiques. :githublink:`%|py|56` """
[docs] def __init__(self, nb, p1, p2, m, k, g, f, lo): """ :param nb: nombre de points :param p1: coordoonnées du premier point (fixe) :param p2: coordoonnées du dernier point (fixe) :param m: masse de la corde, répartie entre tous les points :param k: raideur de l'élastique :param g: intensité de l'apesanteur, valeur positive :param f: vitesse de freinage :param lo: longueur totale de la corde :githublink:`%|py|70` """ x1, y1 = p1[0], p1[1] x2, y2 = p2[0], p2[1] self.list = [] self.vitesse = [] for i in range(0, nb): x = x1 + float(i) * (x2 - x1) / float(nb - 1) y = y1 + float(i) * (y2 - y1) / float(nb - 1) self.list.append(Point(x, y, float(m) / nb)) self.vitesse.append(Point(0, 0, 0)) self.k = k * nb self.g = g self.lo = float(lo) / (nb - 1) self.f = f
[docs] def force_point(self, i): """ Calcule les forces qui s'exerce en un point, retourne un point *x, y*. :githublink:`%|py|89` """ raise NotImplementedError()
[docs] def iteration(self, dt): """ Calcule les déplacements de chaque point et les met à jour, on ne déplace pas les points situés aux extrémités, retourne la somme des vitesses et des accélérations au carré. :githublink:`%|py|97` """ raise NotImplementedError()
[docs]class Corde(ObjetMasseReliees): """ Définition d'une corde, une liste de masses reliées par des élastiques et attachées au deux extrémités. :githublink:`%|py|105` """
[docs] def force_point(self, i): """ calcule les forces qui s'exerce en un point, retourne un point x,y :githublink:`%|py|110` """ x, y = 0, 0 # poids y -= self.g * self.list[i].m # voisin de gauche dxdy = self.list[i].difference(self.list[i - 1]) d = dxdy.norme() if d > self.lo: dxdy.x = (d - self.lo) / d * dxdy.x dxdy.y = (d - self.lo) / d * dxdy.y x += self.k * dxdy.x y += self.k * dxdy.y # voisin de droite dxdy = self.list[i].difference(self.list[i + 1]) d = dxdy.norme() if d > self.lo: dxdy.x = (d - self.lo) / d * dxdy.x dxdy.y = (d - self.lo) / d * dxdy.y x += self.k * dxdy.x y += self.k * dxdy.y # freinage x += - self.f * self.vitesse[i].x y += - self.f * self.vitesse[i].y return Point(x, y, 0)
[docs] def iteration(self, dt): """ Calcule les déplacements de chaque point et les met à jour, on ne déplace pas les points situés aux extrémités, retourne la somme des vitesses et des accélérations au carré. :githublink:`%|py|141` """ force = [Point(0, 0, 0)] for i in range(1, len(self.list) - 1): xy = self.force_point(i) force.append(xy) force.append(Point(0, 0, 0)) # déplacement for i in range(1, len(self.list) - 1): self.vitesse[i].deplace_point(force[i], dt) self.list[i].deplace_point(self.vitesse[i], dt) d = 0 for f in force: d += self.vitesse[0].x ** 2 + f.x ** 2 d += self.vitesse[1].y ** 2 + f.y ** 2 return d
[docs]class Pendule(ObjetMasseReliees): """ Définition d'un pendule, une liste de masses reliées par des élastiques et attachées à une extrémités. Contribution de *Pascal Grandeau*. :githublink:`%|py|166` """
[docs] def force_point(self, i): """ calcule les forces qui s'exerce en un point, retourne un point x,y :githublink:`%|py|171` """ x, y = 0, 0 # poids y -= self.g * self.list[i].m # voisin de gauche dxdy = self.list[i].difference(self.list[i - 1]) d = dxdy.norme() if d > self.lo: dxdy.x = (d - self.lo) / d * dxdy.x dxdy.y = (d - self.lo) / d * dxdy.y x += self.k * dxdy.x y += self.k * dxdy.y # voisin de droite if i < len(self.list) - 1: dxdy = self.list[i].difference(self.list[i + 1]) d = dxdy.norme() if d > self.lo: dxdy.x = (d - self.lo) / d * dxdy.x dxdy.y = (d - self.lo) / d * dxdy.y x += self.k * dxdy.x y += self.k * dxdy.y # freinage x += - self.f * self.vitesse[i].x y += - self.f * self.vitesse[i].y return Point(x, y, 0)
[docs] def iteration(self, dt): """ Calcule les déplacements de chaque point et les met à jour, on ne déplace pas les points situés aux extrémités, retourne la somme des vitesses et des accélérations au carré :githublink:`%|py|203` """ force = [Point(0, 0, 0)] for i in range(1, len(self.list)): xy = self.force_point(i) force.append(xy) force.append(Point(0, 0, 0)) # déplacement for i in range(1, len(self.list)): self.vitesse[i].deplace_point(force[i], dt) self.list[i].deplace_point(self.vitesse[i], dt) d = 0 for _ in force: d += self.vitesse[0].x ** 2 + force[i].x ** 2 d += self.vitesse[1].y ** 2 + force[i].y ** 2 return d
[docs]def display_masses(corde, screen, pygame): """ affichage de la corde à l'aide du module pyagame :githublink:`%|py|226` """ y = screen.get_size()[1] color = (0, 0, 0) for p in corde.list: pygame.draw.circle( screen, color, (int(p.x), int(y - p.y)), int(p.m + 1)) for i in range(0, len(corde.list) - 1): pygame.draw.line(screen, color, (int(corde.list[i].x), int(y - corde.list[i].y)), (int(corde.list[i + 1].x), int(y - corde.list[i + 1].y)))
[docs]def pygame_simulation(pygame, first_click=False, folder=None, iter=1000, size=(800, 500), nb=10, m=40, k=0.1, g=0.1, f=0.02, dt=0.1, step=10, flags=0, model='corde', fLOG=fLOG): """ Simulation graphique. Simule la chute d'une corde suspendue à ces deux extrémités. :param pygame: module pygame (avoids importing in this file) :param first_click: starts the simulation after a first click :param folder: to save the simulation, an image per simulation :param iter: number of iterations to run :param fLOG: logging function :param nb: nombre de points :param m: masse de la corde, répartie entre tous les points :param k: raideur de l'élastique :param g: intensité de l'apesanteur, valeur positive :param f: vitesse de freinage :param dt: petit temps :param step: marche :param flags: see `pygame.display.set_mode <https://www.pygame.org/docs/ref/display.html#pygame.display.set_mode>`_ :param model: ``'corde'`` ou ``'pendule'`` :param fLOG: logging function La simulation ressemble à ceci dans le cas d'une corde : .. raw:: html <video autoplay="" controls="" loop="" height="400"> <source src="http://www.xavierdupre.fr/enseignement/complements/corde.mp4" type="video/mp4" /> </video> Ou cela dans le cas d'un pendule : .. raw:: html <video autoplay="" controls="" loop="" height="400"> <source src="http://www.xavierdupre.fr/enseignement/complements/pendule.mp4" type="video/mp4" /> </video> Pour lancer la simulation:: from ensae_teaching_cs.special.corde import pygame_simulation import pygame pygame_simulation(pygame, model='corde') :githublink:`%|py|287` """ # création de la corde nb = 10 dx = size[0] // 8 dy = size[1] // 8 if model == 'corde': c = Corde(nb, (dx, size[1] - dy), (size[0] - dx, size[1] - dy), m=m, k=k, g=g, f=f, lo=size[0]) elif model == 'pendule': c = Pendule(nb, (size[0] // 2, size[1] - dy), (size[0] - dx, size[1] - dy), m=m, k=k, g=g, f=f, lo=size[0] // 2) else: raise ValueError("Model '{}' is not recognized.".format(model)) pygame.init() white = 255, 255, 255 screen = pygame.display.set_mode(size, flags) # numéro d'itération it = 0 images = [] # continue tant que dep n'est pas proche de 0 dep = len(c.list) * (size[0] * size[0] + size[1] * size[1]) while dep > 1e-4 and it < iter: if it % step == 0: if it % (step * 10) == 0: fLOG("it={0}/{1} dep={2} #{3}".format(it, iter, dep, len(images))) empty_main_loop(pygame) screen.fill(white) display_masses(c, screen, pygame) pygame.display.flip() # on fait une pause dès la première itérations pour voir la corde # dans sa position initiale if it == 0 and first_click: wait_event(pygame) # " # unique instruction ajoutées par rapport à l'énoncé dep = c.iteration(dt) # " # on met à jour l'écran pygame.display.flip() if folder is not None and it % step == 0: images.append((it, screen.copy())) pygame.time.wait(2) # on incrémente le nombre d'itérations it += 1 if folder is not None: fLOG("saving images") for it, screen in images: fLOG("saving image:", it) image = os.path.join(folder, "image_%04d.png" % it) pygame.image.save(screen, image) # le programme est terminé, on fait une pause pour voir le résultat final if first_click: wait_event(pygame)