DYNODE est un Package d'interface avec des solveurs d'équations différentielles ordinaires. Il comporte des équations prédéfinies de systèmes dynamiques physiques. Il permet également de définir les équations différentielles par une fonction Python.
Les intersections des trajectoires avec des surfaces de l'espace des phases peuvent être obtenues pendant l'intégration, ce qui permet d'obtenir les sections de Poincaré.
DYNODE comporte 3 modules :
La compilation nécessite les bibliothèques suivantes :
La bibliothèque pthread est toujours présente sous Linux (threads POSIX)
CVODE est un solveur d'équations différentielles ordinaires faisant partie de la bibliothèque SUNDIALS. Il utilise une méthode à pas multiples à ordre variable explicite (Adams) ou implicite (BDF).
L'interface est fournie par la classe CVOde définie dans le module dynode.main.
Constructeur : ouverture d'un solveur, sélection d'une équation différentielle et de la méthode numérique.
Remarque : il est possibles de créer plusieurs instances de CVOde pour effectuer des calculs en parallèle (maximum 100) sur les processeurs multicœurs.
Affecter des valeurs aux constantes du système d'équations différentielles.
Choix de la condition initiale et des tolérances.
Intégration et récupération des valeurs sur une durée T (à partir du dernier point calculé), avec un intervalle de temps dt. La fonction retourne à la fin du calcul.
Démarrage de l'intégration. La fonction s'exécute de manière asynchrone, i.e. retourne après lancement du calcul sur un thread secondaire.
Remarque : cette fonction permet de lancer des calculs pour plusieurs instances de CVOde en parallèle. Ceci permet de maximiser l'utilisation des processeurs multicœurs.
Attendre la fin du calcul lancé par start_solve (indispendable avant d'en lancer un nouveau).
Obtention des données du calcul en cours, ou du dernier calcul.
Activer ou désactiver la recherche des racines.
Obtenir les points correspondant aux racines.
Définir le système différentiel par une fonction Python, lorsque l'argument equation de CVOde(equation,method,iteration) est égal à 0.
def equation(t,y): ydot=[0,0] ydot[0] = y[1] ydot[1] = -39.4784176044*math.sin(y[0]) return ydot
Définir le jacobian du système différentiel par une fonction Python, à utiliser conjointement avec la fonction set_eqn().
def jacobian(t,y): J=[[0,0],[0,0]] J[0][0] = 0.0 J[0][1] = 1.0 J[1][0] = -39.4784176044*math.cos(y[0]) J[1][1] = 0.0 return J
Définir les équations algébriques à résoudre par une fonction Python, à utiliser conjointement avec la fonction set_eqn().
def rootfn(t,y): g=[y[0],y[1]] return g
Modifier le nombre de pas maximal par séquence de calcul (i.e. pour chaque intervalle de temps dt défini dans solve ou dans start_solve. La valeur par défaut (500) peut être insuffisante pour les systèmes raides.
L'exemple suivant montre l'utilisation de l'interface dans le cas d'un système prédéfini (modèle de Lorenz).
On commence par importer les différents modules puis on ouvre un solveur en définissant le système à résoudre et la méthode numérique :
import dynode.main as dyn import numpy from pylab import * s=dyn.CVOde(dyn.OdeLorenz,dyn.OdeAdams,dyn.OdeFunctional)
On affecte les constantes. Les trois premières interviennent dans le système différentiel alors que la 4ième est la côte z du plan de coupe (section de Poincaré).
s.set_cst([10.0,28.0,8.0/3,27.0])
On active la recherche des racines (coupe de Poincaré z=27) :
s.root_find(1,dyn.OdeZeroCrossUp)
On fixe les conditions initiales et les tolérances :
s.init(0.0,[1.0,1.0,1.0],1e-6,[1e-7])
Enfin une intégration jusqu'à l'instant t=100 :
data1=s.solve(0.01,50.0)
On trace la trajectoire avec pylab (matplotlib) :
t=data1[0] x=data1[1] y=data1[2] z=data1[3] figure(1) plot(x,y)plot1.pdf
On récupère les points vérifiant la condition z=27 :
data2=s.get_roots(0,0) t=data2[0] x=data2[1] y=data2[2] z=data2[3] figure(2) plot(x,y,marker=".",linestyle="",markersize=1)plot2.pdf
s.close()
Le module graphique plot est accessible par différentes classes définies dans dynode.main.
La classe Plot (une seule instance) contrôle l'ouverture du module et le lancement de la boucle de rendu.
Lancement de la boucle de rendu des fenêtres graphiques. Cette fonction est asynchrone, i.e. retourne dès que la boucle est lancée.
Attente de la fin de la boucle de rendu, qui se fait lorsque l'utilisateur ferme l'une des fenêtres ou appuie sur la touche q.
La classe Figure permet de définir une figure, c'est-à-dire une fenêtre graphique. Les figures apparaissent lorsque la fonction Plot.show est appelée. Le nombre de figures est illimitée.
Définir la zone de l'espace rendue dans la fenêtre. Par défaut, la projection est orthoscopique.
Les fonctions suivantes de type set_xxx doivent être appelées avant le lancement de la boucle de rendu.
Acivation de la projection perspective avec positionnement de la caméra.
Définir la couleur de la lumière ambiante.
Définir la couleur de la lumière diffusée.
Attribuer des légendes aux axes X,Y,Z. Par défauts les légendes sont x,y, et z.
Les 3 fonctions suivantes permettent d'obtenir une copie de la figure sous forme d'une image pixmap générée lorsque l'utilisateur appuie sur la touche s. Une seule image peut être générée par figure. Si l'utilisateur n'utilise pas la touche s, l'image est générée à partir du dernier état de la figure (lorsque l'utilisateur appuie sur la touche q). Ces fonctions doivent être appelées après la fermeture de la boucle de rendu, i.e. après l'appel de Plot.wait.
Obtenir la taille de l'image.
Obtenir l'image sous forme d'une liste de valeurs RGB
Fournit une instance de la classe Image du module PIL (si celui-ci est installé).
L'image peut être ensuite enregistrée sous forme PNG par :
img.save("figure.png","PNG")
Les classes suivantes permettent de créer des objets graphiques dans une figure. Les instances doivent être créées avant l'exécution de Plot.show().
La classe Grid représente une grille de repérage, perpendiculaire à un des axes X,Y, ou Z.
Construction d'une grille attachée à une figure.
La classe Plot3d permet de tracer des points dans l'espace, éventuellement reliés par des traits pour former une courbe.
Constructeur.
La classe Plot2d génère des points dans le plan z=0.
Constructeur.
Effacer les points (ou la courbe). L'espace mémoire correspondant est libéré. Cette fonction peut être appelée pendant l'exécution de la boucle de rendu.
Surface de la forme z=f(x,y). Les z sont fournis sous forme d'un tableau. La surface générée est statique. Le chargement des grandes surfaces (plus de 100000 points) comporte un délai de quelques secondes.
Les classes suivantes génèrent des objets graphiques qui peuvent être déplacés (pour animation) pendant la boucle de rendu.
Constructeur d'un cube. Les attributs (width, solid et rgb) ne peuvent être modifiés après lancement de la boucle de rendu.
Positionne et oriente le cube. Cette fonction doit être appelée pendant l'exécution de la boucle de rendu, et permet d'animer le cube.
Constructeur.
Positionne la sphère. Cette fonction doit être appelée pendant l'exécution de la boucle de rendu, et permet d'animer la sphère.
Constructeur d'un cylindre (ou d'un cône).
On reprend les données calculées plus haut (système de Lorenz) pour tracer la trajectoire dans l'espace X,Y,Z et la section de Poincaré z=27.
plot=dyn.Plot() fig1=dyn.Figure() fig1.set_camera(300) fig2=dyn.Figure() a=50.0 b=40.0 fig1.viewbox(-a,a,-a,a,-a,a) fig2.viewbox(-a,a,-a,a,-a,a) g1=dyn.Grid(fig1,-b,b,10,-b,b,10,0,ticks=1,rgb=[1.0,0.5,1.0]) p1=dyn.Plot3d(fig1,data1[1],data1[2],data1[3],style=dyn.PlotStyleLines,rgb=[0,0,1.0],width=1.0) g2=dyn.Grid(fig2,-b,b,10,-b,b,10,0,ticks=1,rgb=[1.0,0.5,1.0]) p1=dyn.Plot3d(fig2,data2[1],data2[2],data2[3],style=dyn.PlotStylePoints,rgb=[0,0,1.0],width=2.0) plot.show() plot.wait() path="../../../../figures/numerique/dynode/pydynode/" img=fig1.get_image() img.save(path+"plot3.png","PNG") img=fig2.get_image() img.save(path+"plot4.png","PNG") plot.close()
Le module verlet est un solveur symplectique pour systèmes hamiltoniens utilisant la méthode de Stormer-verlet. Il traite les systèmes à hamiltoniens séparables, éventuellement dépendant du temps. Un champ magnétique (ou une force de Coriolis) peut être ajouté (système hamiltonien à champ magnétique).
Les sytèmes physiques concernés sont ceux dont les forces internes ne dépendent que des coordonnées, avec un champ extérieur dépendant éventuellement du temps.
L'interface avec le solveur est fournie par la classe Verlet, définie dans le module dynode.main.
Constructeur : ouverture d'un solveur et sélection d'une équation différentielle.
Remarque : il est possibles de créer plusieurs instances de la classe Verlet pour effectuer des calculs en parallèle (maximum 100) sur les processeurs multicœurs.
Affecter des valeurs aux constantes du système d'équations différentielles.
Choix de la condition initiale et du pas de temps.
Intégration et récupération des valeurs sur une durée T (à partir du dernier point calculé), avec un intervalle de temps dt. La fonction retourne à la fin du calcul.
Démarrage de l'intégration. La fonction s'exécute de manière asynchrone, i.e. retourne après lancement du calcul sur un thread secondaire.
Remarque : cette fonction permet de lancer des calculs pour plusieurs instances de Verlet en parallèle. Ceci permet de maximiser l'utilisation des processeurs multicœurs.
Attendre la fin du calcul lancé par start_solve (indispendable avant d'en lancer un nouveau).
Obtention des données du calcul en cours, ou du dernier calcul.
Activer ou désactiver la recherche des racines.
Obtenir les points correspondant aux racines.
Définir le système différentiel par des fonctions Python, lorsque l'argument equation de Verlet(equation) est égal à 0.
Voici un exemple de système défini par des fonctions : une particule (3 coordonnées cartésiennes) est soumise à une force centrale élastique.
def dKinetic(t,p): dq = p return dq def dPotential(t,q): dp = [0,0,0] dp[0] = q[0] dp[1] = q[1] dp[2] = q[2] return dp def energy(t,q,p): return 0.5*(p[0]*p[0]+p[1]*p[1]+p[2]*p[2])+0.5*(q[0]*q[0]+q[1]*q[1]+q[2]*q[2]);
Ajout d'un champ magnétique (ou d'une force de Coriolis), au système défini par set_eqn.
Lorsque le champ est uniforme, il doit être colinéaire à l'axe z. Voici par exemple une fonction définissant un champ magnétique uniforme :
def bfield(t,x): return [0,0,-1]
Définir les équations algébriques à résoudre par une fonction Python, à utiliser conjointement avec la fonction set_eqn().
def rootfn(t,q,p): return [q[0],q[2]]
L'exemple reprend les fonctions définies ci-dessus pour définir le système (oscillateur dans un champ magnétique uniforme).
import dynode.main as dyn solver=dyn.Verlet(0) solver.set_eqn(3,dKinetic,dPotential,energy) solver.set_bfield(dyn.BFieldUniform,bfield) solver.set_rootfn(2,rootfn) solver.init(0.0,[1.0,0.0,0.0],[0.0,1.0,1],0.01) data=solver.solve(0.1,100) t=data[0] x=data[1] y=data[2] z=data[3] e=data[7]
Recherche des racines sur une durée plus longue :
solver.root_find(1,0,1e-3) data=solver.solve(1.0,1000) roots1=solver.get_roots(0) roots2=solver.get_roots(1) solver.close()
Tracé de la trajectoire et des racines :
plot=dyn.Plot() fig1=dyn.Figure() fig1.set_camera(10) fig1.viewbox(-2.0,2.0,-2.0,2.0,-2.0,2.0) g1=dyn.Grid(fig1,-1.0,1.0,10,-1.0,1.0,10,0,ticks=1) p1=dyn.Plot3d(fig1,x,y,z,style=dyn.PlotStyleLines) fig2=dyn.Figure() fig2.set_camera(10) fig2.viewbox(-2.0,2.0,-2.0,2.0,-2.0,2.0) g2=dyn.Grid(fig2,-1.0,1.0,10,-1.0,1.0,10,0,ticks=1) p2=dyn.Plot3d(fig2,roots1[1],roots1[2],roots1[3],style=dyn.PlotStylePoints,width=2.0) p3=dyn.Plot3d(fig2,roots2[1],roots2[2],roots2[3],style=dyn.PlotStylePoints,width=2.0) plot.show() plot.wait() path="../../../../figures/numerique/dynode/pydynode/" img=fig1.get_image() img.save(path+"plot5.png","PNG") img=fig2.get_image() img.save(path+"plot6.png","PNG") plot.close()