Table des matières Python

Équation de Poisson : méthode des éléments finis

1. Introduction

Ce document montre comment résoudre numériquement l'équation de Poisson (à deux dimensions) par la méthode des éléments finis.

Après avoir énoncé la formulation variationnelle du problème, nous verrons comment utiliser le logiciel FreeFEM à travers quelques exemples (d'électrostatique et de magnétostatique) puis comment traiter les résultats dans un script Python.

2. Formulation variationnelle

La résolution numérique d'une équation aux dérivées partielles par la méthode des éléments finis (M.E.F.) repose sur une formulation variationnelle du problème. L'équation considérée ici est l'équation de Poisson à deux dimensions, vérifiée par une fonction inconnue u(x,y) de classe C2 sur un domaine Ω (ouvert de R2 ) :

2ux2+2uy2=-f(x,y)(1)

f est une fonction donnée sur le domaine Ω.

La condition limite imposée sur le bord du domaine (Ω ) est de type Dirichlet :

u(x,y)=0surΩ(2)

La formulation variationnelle du problème de Poisson ([1],[2]) est obtenue en multipliant l'équation (1) par une fonction v(x,y) (fonction test) puis en intégrant sur un domaine Ω :

Ω(Δu)vdτ=-Ωfvdτ(3)

Δ désigne l'opérateur laplacien et l'élément de volume (ici le volume a deux dimensions)

Pour une fonction w(x,y) de classe C1 sur Ω, nous utilisons le théorème suivant (formule de Green) :

Ωwxdτ=Ωwnxds(4)

ds désigne l'élément de surface de Ω (ici la surface a une dimension) et nx la composante sur l'axe x du vecteur normal unitaire sortant de Ω. La même formule est évidemment valable pour la coordonnée y.

L'application de la formule de Green à w=uv conduit à la relation suivante (formule d'intégration par partie) :

Ωvuxdτ=-Ωuvxdτ+Ωuvnxds(5)

Appliquons cette relation à ux (à la place de u) :

Ωv2 ux2dτ=-Ωuxvxdτ+Ωuxvnxds(6)

La même relation étant valable pour la coordonnée y, la somme des deux relations conduit à :

ΩvΔudτ=-Ω(gradu)(gradv)dτ+Ω v(gradu)n ds(7)

Reprenons la relation (3) et supposons que la fonction test v s'annule sur Ω , comme la fonction u. L'application de la formule précédente conduit à :

Ω(gradu)(gradv)dτ=Ωfvdτ(8)

La fonction u recherchée est de classe C1 (et non pas de classe C2). La formulation variationnelle consiste à énoncer que l'égalité (8) doit être vérifiée pour toute fonction test v de classe C1.

3. Méthode des éléments finis

L'exposé détaillé de la méthode des éléments finis ([2]) sort du cadre de ce document mais nous donnons brièvement son principe.

La méthode des éléments finis (M.E.F.) repose sur la triangulation du domaine Ω, c'est-à-dire la subdivision en triangles. Le maillage est donc constitué de triangles. Chaqu'un des trois sommets d'un triangle est aussi le sommet d'un triangle voisin, ou éventuellement appartient au bord du domaine. L'avantage d'un maillage par triangulation, par comparaison aux grilles utilisées pour la méthode des différences finies, est la facilité avec laquelle le maillage s'ajuste aux formes complexes, par exemple des arcs de cercle. Par ailleurs, un maillage par triangles peut facilement être affiné en certains endroits car il est aisé de subdiviser un triangle en plusieurs triangles.

Soit Vh l'ensemble des fonctions continues sur Ω et affines sur chaque triangle, c'est-à-dire de la forme ax+by+c sur chaque triangle. Cet ensemble admet une base de fonctions aisée à définir ([2]). Si i désigne un nœud du maillage (i=1,2,N ), on note ϕi la fonction appartenant à Vh, vallant 1 sur ce nœud et 0 sur tous les autres. L'ensemble des fonctions ϕi constitue une base de Vh. En effet, pour toute fonction uh de Vh on a :

uh(x,y)=i=1Nuiϕi(x,y)(9)

ui est la valeur de uh au nœud i.

La solution approchée de l'équation de Poisson avec conditions limites est recherchée sous la forme d'une fonction uh continue et affine par morceaux (c.a.d. affine sur chaque triangle).

La M.E.F. consiste à rechercher les composantes ui de uh sur la base (ϕi) et à énoncer que la relation (8) doit s'appliquer :

Ω(graduh)(gradv)dτ=Ωfvdτ(10)

pour toute fonction v de Vh. En conséquence, cette relation doit aussi être vraie en remplaçant v par ϕi quel que soit le nœud i.

On a donc :

j=1NujΩ(gradϕj)(gradϕi)dτ=Ωfϕidτ(11)

pour i=1,2,N . On obtient ainsi un système de N équations linéaires pour les N inconnues ui :

j=1NAijui=bi(12)

avec les coefficients suivants :

Aij=Ω(gradϕj)(gradϕi)dτ(13)bi=Ωfϕidτ(14)

Les calculs des coefficients Aij et bi font intervenir une intégrale qui ne s'étend que sur quelques triangles voisins car le support de ϕi se limite aux triangles dont le nœud d'indice i est un sommet.

Le système est résolu numériquement soit par une méthode directe soit par une méthode itérative.

Le point important à retenir est que la fonction uh obtenue est une fonction continue affine par morceaux (c.a.d. affine sur chaque triangle) qui constitue une approximation de la fonction u(x,y) vérifiant l'équation de Poisson avec la condition limite. Le calcul fournit les valeurs aux sommets des triangles (les nœuds du maillage), qui sont les composantes ui de u sur la base de Vh. D'après la relation (9), la connaissance de ces valeurs suffit en effet à déterminer entièrement la fonction continue et affine sur chaque triangle.

4. Utilisation de FreeFem

Le logiciel FreeFEM permet de résoudre des problèmes par la M.E.F. Les calculs à effectuer sont définis dans un script écrit dans un langage spécifique facile à apprendre (très proche de C/C++). Les représentations graphiques des résultats sont affichées sur l'écran et peuvent être exportées au format EPS (encapsulated Postscript), qu'il est possible de convertir en format PNG et SVG avec un convertisseur en ligne ou bien un logiciel Adobe (Photoshop ou Illustrator). Cette solution d'exportation étant peu pratique, nous allons plutôt exporter des résultats dans des fichiers textes afin de les récupérer en Python.

La documentation de FreeFEM contient une description détaillée du langage, une introduction à la formulation variationnelle et de nombreux exemples.

Les auteurs de FreeFem ont écrit des fonctions pour Matlab et Octave (freefem_matlab_octave_plot) qui permettent de tracer les résultats de simulations dans ces deux logiciels. Nous utilisons le fichier de macros ffmatlib.idp qui peut être téléchargé dans freefem_matlab_octave_plot. Ce fichier contient quelques fonctions (en langage FreeFem) permettant d'exporter les données sous forme de fichier texte. Nous les reproduisons ci-dessous :

//Save finite element connectivity (j-th degree of freedom for i-th element)
macro ffSaveVh(Th, Vh, filename){
  {
  ofstream file(filename);
  file.precision(10);
  for (int i=0; i<Th.nt; i++){
    for (int j=0; j<Vh.ndofK; j++){
      file << Vh(i,j) << "\n";
    }
  }
  }
} //EOM

//Save FE Space data
macro ffSaveData(u, filename){
  {
  ofstream file(filename);
  file.precision(10);
  for (int j=0; j<u[].n; j++){
    file << u[][j] << endl;
  }
  }
} //EOM

macro ffSaveData2(u1, u2, filename){
  {
  ofstream file(filename);
  int datalen=u1[].n;
  if (u2[].n!=datalen){
    cout << "error: arguments must have same size" << endl;
    exit(1);
  }
  file.precision(10);
  for (int j=0; j<datalen; j++){
    file << u1[][j] << " " << u2[][j] << endl;
  }
  }
} //EOM

macro ffSaveData3(u1, u2, u3, filename){
  {
  ofstream file(filename);
  int datalen=u1[].n;
  if ((u2[].n!=datalen) | (u3[].n!=datalen)){
    cout << "error: arguments must have same size" << endl;
    exit(1);
  }
  file.precision(10);
  for (int j=0; j<datalen; j++){
    file << u1[][j] << " " << u2[][j] << " " << u3[][j] << endl;
  }
  }
} //EOM

macro ffSaveData4(u1, u2, u3, u4, filename){
  {
  ofstream file(filename);
  int datalen=u1[].n;
  if ((u2[].n!=datalen) | (u3[].n!=datalen) | (u4[].n!=datalen)){
    cout << "error: arguments must have same size" << endl;
    exit(1);
  }
  file.precision(10);
  for (int j=0; j<datalen; j++){
    file << u1[][j] << " " << u2[][j] << " " << u3[][j] << " " << u4[][j] << endl;
  }
  }
} //EOM
			   

5. Exemples

5.a. Champ magnétique créé par un courant rectiligne

Considérons un exemple simple de magnétostatique : le champ magnétique créé par un courant électrique uniforme dans un fil rectiligne infini de rayon a. Le fil est supposé parallèle à l'axe (Oz).

Sachant que divB= il existe un potentiel vecteur A tel que B=rotA . En magnétostatique, on impose la condition divA=0 . Le potentiel vecteur vérifie alors l'équation de Poisson vectorielle :

ΔA=-μ0J(15)

Pour cet exemple, le potentiel vecteur s'écrit A=Az(x,y)uz et l'équation de Poisson :

ΔAz=-μ0Jz(16)

La densité de courant est Jz=J (uniforme) dans le fil, nulle en dehors du fil. On prendra comme domaine Ω un disque centré sur l'axe (Oz). Sur le bord du disque, on pose Az=0. Ce problème modélise un fil dans l'espace infini dans la mesure où le rayon du domaine est assez grand par rapport au rayon du fil.

Le champ magnétique se calcule à partir du potentiel vecteur par :

Bx=Azy(17)By=-Azy(18)

Il faut remarquer que les lignes d'isovaleurs de Az sont des lignes de champ magnétique.

En prendra le terme de source (la fonction f dans l'énoncé ci-dessus) égal à 1 dans le fil, 0 en dehors. Il faudra donc multiplier le potentiel Az obtenu par μ0J .

Notons u=Az et explicitons l'équation (8) sous forme d'intégrales doubles :

(uxvx+uyvy)dxdy=fvdxdy(19)

Voici un script FreeFem (extension EDP) définissant les calculs à réaliser :

filRectiligne.edp
include "ffmatlib.idp"

real Rdomaine = 1.0; // rayon du domaine
real rayon = 0.02; // rayon du fil

// fonction source (mu0*J=1)
func real source(real &x, real &y){
	if (sqrt(x*x+y*y)<rayon) {return 1.0;}
	else {return 0;}
}

border C(t=0,2*pi){x=Rdomaine*cos(t);y=Rdomaine*sin(t);} // bord du domaine

int N=50;
mesh Th = buildmesh(C(N)); // triangulation
fespace Vh(Th,P1);
plot(Th, wait=true);
Vh u,v;

problem Poisson(u,v,eps=1.0e-6) = int2d(Th) (dx(u)*dx(v)+dy(u)*dy(v)) -int2d(Th)(source(x,y)*v)
	+on(C,u=0);
	
Poisson;
plot(u,wait=1);
int i;
real error = 0.01;
for (i = 0; i < 4; i++) { // boucle d'adaptation du maillage
	Poisson;
	cout << u[].min << " " << u[].max << endl;
	plot(u,wait=1);
	Th = adaptmesh(Th, u, err=error);
	plot(Th, wait=true); 
	u = u;
	error = error/2;
}

Vh Bx,By;
By = -dx(u);
Bx = dy(u);

plot(Th,wait=true);
plot(u,wait=true);

savemesh(Th,"filRectiligne.msh");
ffSaveVh(Th,Vh,"filRectiligne_vh.txt");
ffSaveData3(u,Bx,By,"filRectiligne_data.txt"); 
				  

Le fichier ffmatlib.idp doit être dans le même répertoire que le script. Pour exécuter FreeFEM sur ce script, ouvrir une console est exécuter :

freefem++ filRectiligne.edp

Chaque affichage graphique avec l'option wait=true attend un appuie sur Enter.

La fonction inconnue est le potentiel Az mais elle est notée u dans le script pour se conformer aux notations générales définies plus haut. La fonction test est notée v.

Le domaine est un disque délimité par le cercle défini par la commande border. La commande buildmesh génère un maillage avec un nombre N de sommets sur le bord. La commande plot(Th, wait=true) fournit une représentation graphique de ce maillage.

La fonction fespace permet de définir un espace d'éléments finis. Dans le cas présent, on définit Vh, l'espace des fonctions affines par triangle (P1) sur le maillage défini.

La fonction problem permet de définir le problème sous sa forme variationnelle (équation (8)). Les intégrales sur le domaine Ω sont définies par l'opérateur int2d(Th). Il faut remarquer que les deux intégrales doivent être définies séparément. Dans le premier terme, dx(u) désigne la dérivée partielle de u par rapport à x. La condition limite sur le bord du domaine est définie par +on(C,u=0).

La ligne Poisson; effectue une première résolution. La commande plot(u,wait=true) effectue le tracé des lignes d'isovaleurs de u (c'est-à-dire du potentiel vecteur). Le premier maillage défini est trop grossier pour traiter correctement la zone proche du fil (dont le rayon est très faible par rapport au rayon du domaine). Il faut donc réaliser une adaptation du maillage. La fonction adaptmesh effectue le calcul d'un nouveau maillage (par subdivision du précédent) en fonction de la fonction u déjà obtenue et d'une valeur d'erreur. Plusieurs adaptations avec une erreur de plus en plus petite sont en général nécessaires (ici 4). Pour finir, les composantes du champ magnétique sont calculées, sous forme de fonctions de Vh.

Le fichier filRectiligne.msh contient la définition du maillage : nombre de sommets, nombre de triangles, coordonnées des sommets puis indices des sommets des triangles. Dans cet exemple, il y a 5253 sommets et 10402 triangles.

Le fichier filRectiligne_data.txt contient les valeurs de u,Bx,By aux nœuds du maillage, qui constituent les composantes de ces fonctions sur la base (ϕi) .

Afin d'importer la définition du maillage et les valeurs de u,Bx,By aux nœuds du maillage, nous utilisons la class Python ci-dessous :

FreeFemData.py
import numpy as np

class FreeFemData:
    def __init__(self,name):
        self.name = name
        with open(name+".msh") as f:
            lines = f.readlines()
        line0 = lines[0].rstrip()
        data = line0.split(" ")
        self.nb_sommets  = int(data[0])
        self.nb_triangles = int(data[1])
        self.x_array = np.zeros(self.nb_sommets)
        self.y_array = np.zeros(self.nb_sommets)
        self.z_array = np.zeros(self.nb_sommets)
        j = 1
        for i in range(0,self.nb_sommets):
            line = lines[j].rstrip()
            data = line.split(" ")
            j += 1
            self.x_array[i] = float(data[0])
            self.y_array[i]= float(data[1])
            self.z_array[i] = float(data[2])
        self.triangles = np.zeros((self.nb_triangles,3),dtype=np.int64)
        for i in range(self.nb_triangles):
            line = lines[j].rstrip()
            data = line.split(" ")
            j += 1
            self.triangles[i,0] = int(data[0])-1
            self.triangles[i,1] = int(data[1])-1
            self.triangles[i,2] = int(data[2])-1

    def readData(self,n):
        self.data = np.zeros((n,self.nb_sommets))
        with open('%s_data.txt'%self.name) as f:
            lines = f.readlines()
        for i in range(0,self.nb_sommets):
            line = lines[i].rstrip()
            data = line.split(" ")
            for k in range(n):
                self.data[k,i] = float(data[k])
        return self.data
						 

Pour créer une instance de FreeFemData, on fournit la racine du nom des fichiers (ici filRectiligne). Les données obtenues sont :

  • nb_sommets : nombre de sommets.
  • nb_triangles : nombre de triangles.
  • x_array,y_array,z_array : tableaux contenant les coordonnées des sommets.
  • triangles : tableau contenant les triangles : sur chaque ligne un triangle défini par 3 indices.

La fonction readData permet de lire un fichier de données contenant n définitions de fonctions de Vh. Le résultat est renvoyé sous la forme d'un tableau de n lignes. La ligne i contient les valeurs de la fonction d'indice i aux nœuds du maillage.

Voyons comment traiter les données produites par le script filRectiligne.edp. Nous utilisons pour cela le module matplotlib.tri, qui comporte des fonctions permettant de gérer les maillages triangulaires.

On commence par lire le maillage :

import numpy as np
from matplotlib.pyplot import *
import matplotlib.tri as tri
from FreeFemData import FreeFemData

freeFemData = FreeFemData('filRectiligne')
						 

La fonction matplotlib.tri.Triangulation permet d'obtenir un maillage triangulaire à partir de celui fourni par FreeFem :

triang = tri.Triangulation(freeFemData.x_array,freeFemData.y_array,freeFemData.triangles)					 
						 

Voici comment obtenir une représentation graphique de ce maillage :

fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
						 
plotAplotA.pdf

L'adaptation du maillage a permis d'obtenir un maillage très fin au centre (au voisinage du fil). Voici un détail de la zone centrale, avec un tracé du cercle délimitant le fil :

rayon = 0.02
theta = np.linspace(0,2*np.pi,1000)
x = rayon*np.cos(theta)
y = rayon*np.sin(theta)
fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot(x,y,'r-')
ax0.set_xlim(-rayon*2,rayon*2)
ax0.set_ylim(-rayon*2,rayon*2)
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
						 
plotBplotB.pdf

La fonction tricontour permet de tracer des lignes d'isovaleurs de Az :

data = freeFemData.readData(3) 
Az_array = data[0,:]
Bx_array = data[1,:] 
By_array = data[2,:]

fig1,ax1 = subplots(figsize=(10,10))
ax1.set_aspect('equal')
ct = ax1.tricontour(triang,Az_array,levels=40)
fig1.colorbar(ct)
ax1.set_xlabel('x',fontsize=16)
ax1.set_ylabel('y',fontsize=16)		 
						 
plotCplotC.pdf

Cherchons à présent à tracer le champ magnétique sur l'axe (Ox), qui en principe ne doit comporter que la composante By. La méthode des éléments finis fournit des valeurs sur les sommets des triangles. Il faut donc procéder à une interpolation pour obtenir les valeurs de Bz en des points répartis sur une droite. Dans le cas présent, l'espace Vh est celui des fonctions continues affines par triangle. Il faut donc procéder à une interpolation linéaire. La fonction matplotlib.tri.LinearTriInterpolator génère une fonction d'interpolation à partir d'un maillage triangulaire et d'un ensemble de valeurs sur les nœuds de ce maillage.

Bx_inter = tri.LinearTriInterpolator(triang, Bx_array)
By_inter = tri.LinearTriInterpolator(triang, By_array)
x = np.linspace(-1,1,1000)
y = np.ones(len(x))*0
By = By_inter(x,y)
figure(figsize=(8,6))
plot(x,By)
xlabel('x',fontsize=16)
ylabel('By',fontsize=16)
grid()
						   
plotDplotD.pdf

Comme prévu, le champ By présente un profil affine à l'intérieur du fil et décroît à l'extérieur proportionnellement à l'inverse de la distance à l'axe. La calcul a été fait avec μ0Jz=1 . L'intensité du champ magnétique à la surface du fil est donc théoriquement égal à a/2. Voici la courbe montrant les valeurs de Az sur la droite (Ox).

Az_inter = tri.LinearTriInterpolator(triang, Az_array)
figure(figsize=(8,6))
plot(x,Az_inter(x,y))
xlabel('x',fontsize=16)
ylabel('Az',fontsize=16)
grid()
							
plotEplotE.pdf

5.b. Champ électrique créé par un conducteur chargé

Ce deuxième exemple n'est pas un problème de Poisson à proprement parler mais un problème de Laplace.

Une plaque métallique a une épaisseur e dans la direction (Ox), une largeur L dans la direction (Oy) et une longueur infinie dans la direction (Oz). Elle est chargée et son potentiel est donc V1 par rapport au potentiel à l'infini. Le potentiel est V(x,y). On a en tout point de l'espace en dehors du conducteur :

ΔV=0(20)

Le bord Ω est constitué de deux parties. La première est la surface du conducteur, sur laquelle on a la condition limite V=V1. La seconde est la surface fermée qui englobe le conducteur, sur laquelle on a la condition limite V=0.

Ce problème est très voisin du problème de Poisson. La différence réside dans la condition limite V=V1 sur la surface du conducteur.

Si u désigne le potentiel V, la forme variationnelle donnée par l'équation (8) est toujours valable car la fonction test v est choisie s'annulant sur toute la frontière Ω , y compris sur le conducteur.

Nous choisissons un domaine circulaire (comme dans l'exemple précédent). Ce choix permet d'éviter les effets de coins que l'on rencontre avec des domaines rectangulaires. En effet, le potentiel à grande distance de la plaque doit présenter une quasi invariance par rotation autour de l'axe (Oz).

Voici un script FreeFem définissant les calculs à réaliser :

plaqueConductrice.edp
include "ffmatlib.idp"
real Rdomaine = 200;
real e = 1; //épaisseur de la plaque
real L = 50; // largeur de la plaque
real e2 = e/2;
real L2 = L/2;
int C1 = 99;

border C(t=0,2*pi){x=Rdomaine*cos(t);y=Rdomaine*sin(t);}
border C11(t=0, 1){ x=e2;  y=L2-t*L;  label=C1;}
border C12(t=0, 1){ x=e2-e*t;  y=-L2;  label=C1;}
border C13(t=0, 1){ x=-e2;  y=-L2+t*L; label=C1;}
border C14(t=0, 1){ x=-e2+e*t;  y=L2; label=C1;} 
mesh Th = buildmesh(C(100)+C11(5)+C12(20)+C13(5)+C14(20));
fespace Vh(Th,P1);
Vh u,v;
plot(Th, wait=1);

problem Laplace(u,v,eps=1.0e-6) = int2d(Th) (dx(u)*dx(v)+dy(u)*dy(v))
	+on(C,u=0)
	+on(C1,u=1);
Laplace;
int i;
real error = 0.01;
for (i = 0; i < 4; i++) {
	Laplace;
	cout << u[].min << " " << u[].max << endl;
	plot(u,wait=1);
	Th = adaptmesh(Th, u, err=error);
	plot(Th, wait=1);
	u = u;
	error = error/2;
}
Vh Ex,Ey;
Ex = -dx(u);
Ey = -dy(u);

plot(Th,wait=true); 
plot(u,wait=true);
plot(u,[Ex,Ey]);
savemesh(Th,"plaqueConductriceChargee.msh");
ffSaveVh(Th,Vh,"plaqueConductriceChargee_vh.txt");
ffSaveData3(u,Ex,Ey,"plaqueConductriceChargee_data.txt");
					

Les bords du conducteur sont définis segment par segment. La fonction buildmesh construit le maillage avec le cercle C et les bords du conducteur comme frontières. La formulation variationnelle donnée est la même que précédemment (sans le terme de source) mais on précise la condition limite sur le conducteur par +on(C1,u=1).

Voici une vue globale du maillage obtenu après adaptation :

freeFemData = FreeFemData('plaqueConductriceChargee')
triang = tri.Triangulation(freeFemData.x_array,freeFemData.y_array,freeFemData.triangles)
Rdomaine = 100
e = 1 # épaisseur de la plaque
L = 50 # largeur de la plaque
e2=e/2
L2=L/2					 
fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([-e2,e2,e2,-e2,-e2],[-L2,-L2,L2,L2,-L2],'r-')
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
						 
plotFplotF.pdf

et un détail au voisinage d'un bord de la plaque :

fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([-e2,e2,e2,-e2,-e2],[-L2,-L2,L2,L2,-L2],'r-')
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
ax0.set_xlim(-10,10)
ax0.set_ylim(15,35)
						 
plotGplotG.pdf

L'adaptation du maillage a conduit à une forte densité de triangles au voisinage du bord.

Voici un tracé de lignes équipotentielles :

data = freeFemData.readData(3)
V_array = data[0,:]
Ex_array = data[1,:]
Ey_array = data[2,:]

fig1,ax1 = subplots(figsize=(10,10))
ax1.set_aspect('equal')
ct = ax1.tricontour(triang,V_array,levels=40)
fig1.colorbar(ct)
ax1.plot([-e2,e2,e2,-e2,-e2],[-L2,-L2,L2,L2,-L2],'r-')
ax1.set_xlabel('x',fontsize=16)
ax1.set_ylabel('y',fontsize=16)							 
						 
plotHplotH.pdf

Voici les lignes équipotentielles au voisinage d'un bord :

fig1,ax1 = subplots(figsize=(10,10))
ax1.set_aspect('equal')
ct = ax1.tricontour(triang,V_array,levels=40)
fig1.colorbar(ct)
ax1.plot([-e2,e2,e2,-e2,-e2],[-L2,-L2,L2,L2,-L2],'r-')
ax1.set_xlabel('x',fontsize=16)
ax1.set_ylabel('y',fontsize=16)	
ax1.set_xlim(-10,10)
ax1.set_ylim(15,35)		 
						 
plotIplotI.pdf

Champ électrique sur l'axe (Ox) :

Ex_inter = tri.LinearTriInterpolator(triang, Ex_array)
Ey_inter = tri.LinearTriInterpolator(triang, Ey_array)
x = np.linspace(e2,Rdomaine,1000)
y = np.ones(len(x))*0
Ex = Ex_inter(x,y)
figure(figsize=(8,6))
plot(x,Ex)
xlabel('x',fontsize=16)
ylabel('Ex',fontsize=16)
grid()
ylim(0,0.1)
						   
plotKplotK.pdf

et sur l'axe (Oy) :

y = np.linspace(L2,Rdomaine,1000)
x = np.ones(len(y))*0
Ey = Ey_inter(x,y)
figure(figsize=(8,6))
plot(y,Ey)
xlabel('y',fontsize=16)
ylabel('Ey',fontsize=16)
grid()
ylim(0,0.1)
						   
plotLplotL.pdf

5.c. Conducteurs chargés en symétrie axiale

Considérons un problème d'électrostatique présentant une symétrie axiale, c'est-à-dire une invariance par rotation autour de l'axe (Oz). Les coordonnées cylindriques (r,z) permettent de définir un problème bidimensionnel puisque V(r,z).

L'équation de Poisson prend la forme suivante (le potentiel est noté u) :

1rr(rur)+2uz2=-f(z,r)(21)

Les conditions limites sur Ω sont u=0 sur la surface englobante et u=Vc sur la surface d'un conducteur dont le potentiel est Vc (par rapport au potentiel à l'infini).

La fonction test v est supposée s'annuler sur Ω . La formulation variationnelle prend donc la même forme que précédemment :

Ω(gradu)(gradv)dτ=Ωfvdτ(22)

Cependant, on doit ici raisonner sur un domaine Ω de R3 présentant une symétrie axiale. L'intégrale sur ce volume s'écrit sous la forme d'intégrales doubles sur (r,z) :

(urvr+uzvz)2πrdrdz=fv2πrdrdz(23)

Le domaine de calcul est un rectangle défini par -Rm< r< Rm et -Zm< z< Zm . La condition limite u=0 est imposée sur les bords de ce rectangle.

Le potentiel présente une symétrie par rapport à l'axe (Oz) : V(z,r)=V(z,-r). La résolution du même problème par la méthode des différences finies (Discrétisation de l'équation de Poisson en géométrie axiale) permet de définir un domaine rectangulaire dont un bord coïncide avec l'axe (Oz) et une condition limite spécifique est définie pour les points de cet axe. Pour la mise en œuvre de la méthode des éléments finis, il est plus simple de définir un domaine symétrique contenant aussi des valeurs négatives de r. Il faudra évidemment définir des conducteurs symétriques par rapport à l'axe (Oz). Le terme r dans l'intégrale doit être remplacé par sa valeur absolue car l'intégrale s'étend aux valeurs négatives de r.

Voici un exemple, avec deux disques formant un condensateur :

conducteursAxial.edp
include "ffmatlib.idp"
real Zm = 100;
real Rm = 100;
real e = 1; //épaisseur des disques
real R = 25; // rayon des disques
real e2 = e/2;
real z1 = -5-e2; // position du premier disque
real z2 = 5+e2; // position du second disque
int C = 10;
border Ca(t=0,1){x=-Zm+2*Zm*t;y=-Rm; label=C;}
border Cb(t=0,1){x=Zm;y=-Rm+2*Rm*t; label=C;}
border Cc(t=0,1){x=Zm-2*Zm*t;y=Rm; label=C;}
border Cd(t=0,1){x=-Zm;y=Rm-2*Rm*t; label=C;}
int C1=11;
border C1a(t=0, 1){ x=z1+e2;  y=R-t*2*R; label=C1;}
border C1b(t=0, 1){ x=z1+e2-e*t;  y=-R;  label=C1;}
border C1c(t=0, 1){ x=z1-e2;  y=-R+t*2*R; label=C1;}
border C1d(t=0, 1){ x=z1-e2+e*t;  y=R; label=C1;} 
int C2=12;
border C2a(t=0, 1){ x=z2+e2;  y=R-t*2*R; label=C2;}
border C2b(t=0, 1){ x=z2+e2-e*t;  y=-R;  label=C2;}
border C2c(t=0, 1){ x=z2-e2;  y=-R+t*2*R; label=C2;}
border C2d(t=0, 1){ x=z2-e2+e*t;  y=R; label=C2;} 
int N=20;
mesh Th = buildmesh(Ca(N)+Cb(N)+Cc(N)+Cd(N)+C1a(10)+C1b(3)+C1c(10)+C1d(3)+C2a(10)+C2b(3)+C2c(10)+C2d(3));

plot(Th, wait=1);
fespace Vh(Th,P1);
Vh u,v;

// x : variable z, y : variable r 
problem Laplace(u,v,eps=1.0e-6) = int2d(Th) ((dx(u)*dx(v)+dy(u)*dy(v))*abs(y))
	+on(C,u=0)
	+on(C1,u=0.5)
	+on(C2,u=-0.5);
Laplace;
int i;
real error = 0.01;
for (i = 0; i < 4; i++) {
	Laplace;
	cout << u[].min << " " << u[].max << endl;
	plot(u,wait=1);
	Th = adaptmesh(Th, u, err=error);
	plot(Th, wait=1);
	u = u;
	error = error/2;
}
Vh Ex,Ey;
Ex = -dx(u);
Ey = -dy(u); 

plot(Th,wait=true); 
plot(u,wait=true);
plot(u,[Ex,Ey]);
savemesh(Th,"conducteursAxial.msh");
ffSaveVh(Th,Vh,"conducteursAxial_vh.txt");
ffSaveData3(u,Ex,Ey,"conducteursAxial_data.txt"); 
					

Voici une vue globale du maillage obtenu après adaptation :

freeFemData = FreeFemData('conducteursAxial')
triang = tri.Triangulation(freeFemData.x_array,freeFemData.y_array,freeFemData.triangles)
Zm=100
Rm=100
e=1
e2=e/2
R=25
z1=-5-e2
z2=5+e2	 
fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([z1-e2,z1+e2,z1+e2,z1-e2,z1-e2],[-R,-R,R,R,-R],'r-')
ax0.plot([z2-e2,z2+e2,z2+e2,z2-e2,z2-e2],[-R,-R,R,R,-R],'r-')
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
						 
plotMplotM.pdf

et un détail au voisinage des bords des plaques :

fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([z1-e2,z1+e2,z1+e2,z1-e2,z1-e2],[-R,-R,R,R,-R],'r-')
ax0.plot([z2-e2,z2+e2,z2+e2,z2-e2,z2-e2],[-R,-R,R,R,-R],'r-')
ax0.set_xlabel('x',fontsize=16)
ax0.set_ylabel('y',fontsize=16)
ax0.set_xlim(-10,10)
ax0.set_ylim(15,35)
						 
plotNplotN.pdf

Voici un tracé de lignes équipotentielles :

data = freeFemData.readData(3)
V_array = data[0,:]
Ex_array = data[1,:]
Ey_array = data[2,:]

fig1,ax1 = subplots(figsize=(10,10))
ax1.set_aspect('equal')
ct = ax1.tricontour(triang,V_array,levels=20)
fig1.colorbar(ct)
ax1.plot([z1-e2,z1+e2,z1+e2,z1-e2,z1-e2],[-R,-R,R,R,-R],'r-')
ax1.plot([z2-e2,z2+e2,z2+e2,z2-e2,z2-e2],[-R,-R,R,R,-R],'r-')
ax1.set_xlabel('z',fontsize=16)
ax1.set_ylabel('r',fontsize=16)
ax1.set_xlim(-50,50)
ax1.set_ylim(-50,50)						 
						 
plotOplotO.pdf

Champ électrique sur l'axe (Ox) :

Ex_inter = tri.LinearTriInterpolator(triang, Ex_array)
Ey_inter = tri.LinearTriInterpolator(triang, Ey_array)
x = np.linspace(-Zm,Zm,1000)
y = np.ones(len(x))*0
Ex = Ex_inter(x,y)
figure(figsize=(8,6))
plot(x,Ex)
xlabel('z',fontsize=16)
ylabel('Ez',fontsize=16)
grid()
						   
plotPplotP.pdf

5.d. Problème de magnétostatique en symétrie axiale

L'équation de Poisson pour le potentiel vecteur s'écrit :

ΔA=-μ0J(24)

ou bien (puisque divA=0 ) :

rot(rotA)=μ0J(25)

Pour un problème à symétrie axiale d'axe (Oz), on a A=Aθ(r,z)uθ et J=Jθ(r,z)uθ . L'équation aux dérivées partielles s'écrit :

r(1r(rAθ)r)+2Aθz2=-μ0Jθ(26)

La discrétisation de cette équation pour la méthode de différences finies est exposée dans Discrétisation de l'équation de Poisson vectorielle.

Pour la méthode des éléments finis, nous devons établir une formulation variationnelle. Il faut faire attention au fait que l'opérateur de dérivation seconde appliqué à Aθ dans l'équation ci-desssus n'est pas l'opérateur laplacien. Cette équation est donc différente de celle considérée au paragraphe précédent.

Considérons une fonction test vectorielle v . L'application de la formule d'intégration par parties (5) conduit à :

Ωvx(Azy-Ayz)dτ=-Ω(Azvxy-Ayvxz)dτ+Ωvx(Azny-Aynz)ds(27)

La même relation est valable par permutation circulaire de (x,y,z). La somme des trois relations obtenues conduit à :

ΩvrotAdτ=ΩArotvdτ+Ωv(An)ds (28)

Appliquons cette relation à rotA à la place de A :

Ωv(rotrotA)dτ=ΩrotArotvdτ+Ωv(rotAn)ds (29)

La fonction test v s'annule sur le bord Ω . On obtient donc la formulation variationnelle suivante :

ΩrotArotvdτ=μ0ΩvJdτ(30)

pour toute fonction v de classe C1 sur Ω qui s'annule sur Ω .

Ce résultat est valable pour un problème de magnétostatique en général. Pour un problème axisymétrique, le rotationnel s'écrit :

rotA=-Aθzur+1rr(rAθ)uz=-Aθzur+(Aθr+Aθr)uz(31)

et nous obtenons, pour v=vθ(r,z)uθ :

(Aθzvθz+(Aθr+Aθr)(vr+vr)2πrdrdz=μ0vθJθ2πrdrdz(32)

Le domaine de calcul est défini par -Rm< r< Rm et -Zm< z< Zm .

Considérons comme exemple une bobine axisymétrique d'axe (Oz). Ce problème est traité par la méthode des différences finies dans Bobine épaisse.

bobineAxial.edp
include "ffmatlib.idp"
real Zm = 100;
real Rm = 100;
real a = 8; // rayon moyen
real e = 4; // épaisseur
real L = 50; // longueur
real e2 = e/2;
real L2 = L/2;
int C = 10;
//border Ca(t=0,1){x=-Zm+2*Zm*t;y=0; label=C;}
//border Cb(t=0,1){x=Zm;y=Rm*t; label=C;}
//border Cc(t=0,1){x=Zm-2*Zm*t;y=Rm; label=C;}
//border Cd(t=0,1){x=-Zm;y=Rm*(1-t); label=C;}

border Ca(t=0,1){x=-Zm+2*Zm*t;y=-Rm; label=C;}
border Cb(t=0,1){x=Zm;y=-Rm+2*Rm*t; label=C;}
border Cc(t=0,1){x=Zm-2*Zm*t;y=Rm; label=C;}
border Cd(t=0,1){x=-Zm;y=Rm-2*Rm*t; label=C;}

int N=20;
mesh Th = buildmesh(Ca(N)+Cb(N)+Cc(N)+Cd(N));

plot(Th, wait=1);
fespace Vh(Th,P1);
Vh u,v;

func real source(real &x, real &y){
	if ((abs(x)<L2)&&(abs(y-a)<e2)) {return 1.0;} 
	else if ((abs(x)<L2)&&(abs(y+a)<e2)) {return -1.0;} 
	else {return 0;}
}
  
// x : variable z, y : variable r 
// u : A_theta
problem Poisson(u,v,eps=1.0e-6) = int2d(Th) ((dx(u)*dx(v)+(u/y+dy(u))*(v/y+dy(v)))*abs(y)) -int2d(Th) (source(x,y)*v*abs(y))
	+on(C,u=0);
	 
	
Poisson; 
int i;
real error = 0.01;
for (i = 0; i < 4; i++) {
	Poisson;
	cout << u[].min << " " << u[].max << endl;
	plot(u,wait=1);
	Th = adaptmesh(Th, u, err=error);
	plot(Th, wait=1);
	u = u;
	error = error/2;
}
Vh Br,Bz; 
Br=-dx(u);  
Bz=u/y+dy(u);

plot(Th,wait=true);  
plot(u,wait=true); 
 
savemesh(Th,"bobineAxial.msh");
ffSaveVh(Th,Vh,"bobineAxial_vh.txt");    
ffSaveData3(u,Br,Bz,"bobineAxial_data.txt"); 
					

Voici une vue globale du maillage :

freeFemData = FreeFemData('bobineAxial')
triang = tri.Triangulation(freeFemData.x_array,freeFemData.y_array,freeFemData.triangles)  
Zm=100
Rm=100 
a=8 
e=4
e2=e/2
L=50
L2=L/2
fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([-L2,L2,L2,-L2,-L2],[a-e2,a-e2,a+e2,a+e2,a-e2],'r-')
ax0.plot([-L2,L2,L2,-L2,-L2],[-a-e2,-a-e2,-a+e2,-a+e2,-a-e2],'r-')
ax0.set_xlabel('z',fontsize=16)
ax0.set_ylabel('r',fontsize=16)
						 
plotQplotQ.pdf

et un détail du voisinage de la section de la bobine :

fig0,ax0 = subplots(figsize=(10,10))
ax0.set_aspect('equal')
ax0.triplot(triang,color='0.8')
ax0.plot([-L2,L2,L2,-L2,-L2],[a-e2,a-e2,a+e2,a+e2,a-e2],'r-')
ax0.plot([-L2,L2,L2,-L2,-L2],[-a-e2,-a-e2,-a+e2,-a+e2,-a-e2],'r-')
ax0.set_xlabel('z',fontsize=16)
ax0.set_ylabel('r',fontsize=16)
ax0.set_xlim(-L,L)  
ax0.set_ylim(-L,L)
						 
plotRplotR.pdf

Voici les lignes d'isovaleurs de Aθ :

data = freeFemData.readData(3)
Atheta_array = data[0,:]
Br_array = data[1,:] 
Bz_array = data[2,:] 

fig1,ax1 = subplots(figsize=(10,10))
ax1.set_aspect('equal')
ct = ax1.tricontour(triang,Atheta_array,levels=10)
fig1.colorbar(ct)
ax1.plot([-L2,L2,L2,-L2,-L2],[a-e2,a-e2,a+e2,a+e2,a-e2],'r-')
ax1.plot([-L2,L2,L2,-L2,-L2],[-a-e2,-a-e2,-a+e2,-a+e2,-a-e2],'r-')
ax1.set_xlabel('z',fontsize=16)
ax1.set_ylabel('r',fontsize=16)
ax1.set_xlim(-L,L)
ax1.set_ylim(-L,L)
						 
plotSplotS.pdf

et le champ magnétique au voisinage de l'axe de la bobine :

Br_inter = tri.LinearTriInterpolator(triang, Br_array)
Bz_inter = tri.LinearTriInterpolator(triang, Bz_array)
x = np.linspace(-Zm,Zm,1000)
y = np.ones(len(x))*1
Bz = Bz_inter(x,y)
figure(figsize=(8,6))
plot(x,Bz) 
xlabel('z',fontsize=16)
ylabel('Bz',fontsize=16)
grid()
						 
plotTplotT.pdf

et le champ en z=0 en fonction de r :

y = np.linspace(-Rm,Rm,1000)
x = np.ones(len(y))*0
Bz = Bz_inter(x,y)
figure(figsize=(8,6))
plot(y,Bz)
xlabel('r',fontsize=16)
ylabel('Bz',fontsize=16)
xlim(-40,40)
plot([a-e2,a-e2],[0,4],'r-')
plot([a+e2,a+e2],[0,4],'r-')
plot([-a-e2,-a-e2],[0,4],'r-')
plot([-a+e2,-a+e2],[0,4],'r-')
grid()
						 
plotUplotU.pdf

Théoriquement, pour une bobine très longue d'épaisseur e, on doit avoir au centre Bz=μ0Je soit ici Bz=e puisque le calcul a été fait avec μ0J=1 .

La courbe précédente est obtenue à partir des dérivées calculées dans FreeFEM. Il est aussi possible de construire une fonction d'interpolation de Aθ et de calculer les dérivées dans le script Python :

Atheta_inter = tri.LinearTriInterpolator(triang, Atheta_array)
y = np.linspace(-Rm,Rm,500)
x = np.ones(len(y))*0
(Gz,Gr) = Atheta_inter.gradient(x,y)
Atheta = Atheta_inter(x,y)
Bz = Atheta/y + Gr
figure(figsize=(8,6))
plot(y,Bz)
xlim(-40,40)
plot([a-e2,a-e2],[0,4],'r-')
plot([a+e2,a+e2],[0,4],'r-')
plot([-a-e2,-a-e2],[0,4],'r-')
plot([-a+e2,-a+e2],[0,4],'r-')
grid()
xlabel('r',fontsize=16)
ylabel('Bz',fontsize=16)
					 	  
plotVplotV.pdf

x = np.linspace(-Zm,Zm,1000)
y = np.ones(len(x))*1
(Gz,Gr) = Atheta_inter.gradient(x,y)
Atheta = Atheta_inter(x,y)
Bz = Atheta/y + Gr
figure(figsize=(8,6))
plot(x,Bz)
xlim(-100,100)
grid()
xlabel('z',fontsize=16)
ylabel('Bz',fontsize=16)
			 	  
plotWplotW.pdf

La mauvaise qualité de ces courbes est due au fait que le calcul de la composante Bz=Aθr+Aθr fait intervenir une dérivée, dont l'évaluation est soumise à une augmentation du bruit présent dans Aθ . En comparaison, les dérivées calculées sur une grille rectangulaire dans la méthode des différences finies (Bobine épaisse) soit de bien meilleure qualité.

Voici le tracé de Aθ en fonction de r pour z=0 :

figure(figsize=(8,6))
y = np.linspace(-Rm,Rm,500)
x = np.ones(len(y))*0
Atheta = Atheta_inter(x,y)
figure(figsize=(8,6))
plot(y,Atheta)
xlabel('r',fontsize=16)
ylabel(r"$A_{\theta}$",fontsize=16)
grid()
						  
plotXplotX.pdf

Calculons Aθr directement à partir de cette courbe :

from scipy.signal import convolve
dy = y[1]-y[0]
dAtheta_dr = convolve(Atheta,[1,-1],mode='same') / dy
figure(figsize=(8,6))
plot(y,dAtheta_dr)
xlabel('r',fontsize=16)
ylabel(r"$\frac{\partial A_{\theta}}{\partial r}$",fontsize=16)
grid()
						 
plotYplotY.pdf

Bien que la courbe de Aθ semble lisse, elle comporte un léger bruit qui est nettement amplifié dans l'opération de dérivation.

Voici la composante Bz :

Bz = Atheta/y + dAtheta_dr
figure(figsize=(8,6))
plot(y,Bz)
xlim(-40,40)
plot([a-e2,a-e2],[0,4],'r-')
plot([a+e2,a+e2],[0,4],'r-')
plot([-a-e2,-a-e2],[0,4],'r-')
plot([-a+e2,-a+e2],[0,4],'r-')
grid()
xlabel('r',fontsize=16)
ylabel('Bz',fontsize=16)
						 
plotZplotZ.pdf
Creative Commons LicenseTextes et figures sont mis à disposition sous contrat Creative Commons.
Références
[1]  G. Allaire,  Analyse numérique et optimisation,  (Edition de l'école polytechnique, 2006)
[2]  M.G. Larson, F. Bengzon,  The finite element method,  (Springer-Verlag, 2013)