Table des matières Python

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

1. Introduction

Ce document traite de la résolution de l'équation de Poisson bidimensionnelle par la méthode des éléments finis. La page Équation de Poisson 1D : méthode des éléments finis développe le cas unidimensionel.

Soit Ω un domaine de 2 de frontière Ω . Soient deux fonctions a :Ω et s :Ω . On recherche une fonction u :Ω deux fois dérivable vérifiant l'équation différentielle

div(agradu)=-s(1)

Par exemple dans un problème de diffusion thermique, u désigne la température, a est la conductivité thermique et s est la source thermique. Dans un problème d'électrostatique u est le potentiel, a la permittivité électrique et s la densité volumique de charge. Cette équation aux dérivées partielles est plus générale que l'équation de Poisson mais se ramène à celle-ci dans un milieu homogène, c'est-à-dire lorsque a est uniforme :

2u=-sa(2)

On se place dans le cas de coordonnées cartésiennes, qui correspond à un problème invariant par translation dans la direction Z. On a donc :

grad u=uxux+uyuy(3) div v=vxx+vyy(4)

Pour que l'équation (1) admette une unique solution, il faut définir des conditions limites sur Ω .

2. Méthode des éléments finis

2.a. Formulation variationnelle

Pour une fonction f:Ω de classe C1, on a :

Ωfxdσ= Ωfnuxd(5)

n désigne le vecteur unitaire orthogonal à Ω et dirigé dans le sens sortant par rapport à Ω . La même égalité est évidemment valable pour la coordonnée y. Appliquons ce théorème à :

f=vaux(6)

On obtient :

Ωauxvxdσ+Ωvx(aux)dσ=Ωvauxnuxd(7)

et de même :

Ωauyvydσ+Ωvx(auy)dσ=Ωvauynuyd(8)

La somme de ces deux égalités conduit à :

Ωa(grad u)(grad v)dσ+Ωvdiv(agradu)dσ=Ωva(gradu)nd(9)

qui devient, en utilisant l'équation (1) :

Ωa(grad u)(grad v)dσ=Ωvsdσ+Ωva(gradu)nd(10)

Cette égalité est écrite dans le cas bidimensionnel, où Ω est une surface plane et Ω le contour de cette surface. dσ est donc l'élément de surface et d l'élément du contour. Le même résultat est bien sûr valable en tridimensionnel (Ω est alors un volume et Ω la surface qui le délimite).

Soit V l'ensemble des fonctions Ω: de carré sommable et dont le gradient est aussi de carré sommable sur Ω .

La formulation variationnelle du problème (la recherche de u) s'énonce ainsi [1] : on recherche une fonction uV telle que pour toute fonction vV on ait l'égalité (10). Cet énoncé est souvent appelé forme faible du problème de Poisson car les exigences sur la fonction u sont moindres que pour l'équation (1) (elle n'est plus nécessairement deux fois dérivable).

2.b. Conditions limites

Séparons la frontière du domaine en deux parties : la partie Ωd sur laquelle une condition limite de Dirichlet est imposée, la partie Ωn sur laquelle une condition limite de Neumann est imposée. L'égalité (10) s'écrit :

Ωa(grad u)(grad v)dσ=Ωvsdσ+Ωdva(gradu)nd+Ωnva(gradu)nd(11)

Comme il est montré dans Équation de Poisson 1D : méthode des éléments finis, la formulation variationnelle doit être modifiée en considérant les fonctions de V qui s'annulent sur Ωd . Notons V0 l'ensemble de ces fonctions. La formulation variationnelle devient : obtenir une fonction u telle que pour toute fonction vV0 :

Ωa(grad u)(grad v)dσ=Ωvsdσ+Ωnva(gradu)nd(12)

La condition de Neumann consiste à fixer la valeur de (gradu)n sur Ωn . Une variante de la condition de Neumann est la condition de Robin, qui consiste à écrire sur la frontière :

(gradu)n=K0(u-u0)+g0(13)

K0, u0 et g0 sont des constantes. Pour K0=0, on retrouve la condition de Neumann avec g0 la composante normale du gradient sur la frontière. Plus généralement, ces trois constantes peuvent dépendre du point de la frontière (auquel cas il ne s'agit plus de constantes). Dans ce document, on nommera condition de Neumann la condition définie par l'équation (13).

Finalement, l'égalité de la formulation variationnelle s'écrit :

Ωa(grad u)(grad v)dσ=Ωvsdσ+Ωnva(K0(u-u0)+g0)d(14)

2.c. Maillage triangulaire - Fonctions affines par morceaux

Le domaine Ω est subdivisé en triangles : chaque maille est un triangle et comporte donc trois nœuds. Une maille partage avec une maille voisine deux nœuds. La figure suivante montre un maillage triangulaire très simple pour un domaine rectangulaire (celui que nous utiliserons pour les tests) :

maillageRectangle-fig.svgFigure pleine page

Le maillage comporte P nœuds. Le nœud Ni a pour coordonnées (xi,yi). En général, les nœuds ne sont évidemment pas disposés sur une grille.

Les fonctions u et v du problème variationnel sont considérées dans l'ensemble, noté Vh, des fonctions continues et affines par morceaux sur le maillage (l'indice h désigne le maillage). Pour uhVh on a sur chaque maille : uh(x,y)=c0x+c1y+c2 . Si les mailles sont assez petites, les valeurs de uh aux nœuds sont censées être de bonnes approximations de u, la solution de l'équation (1) avec les conditions limites. La fonction uh est une solution du problème variationnel mais elle n'est évidemment pas une solution de (1).

Si l'on note c0,c1,c2 et c'0,c'1,c'2 les coefficients de uh pour deux mailles voisines, on a sur le côté commun à ces deux mailles, par continuité :

c0x+c1y+c2=c'0x+c'1y+c'2(15)

ce qui est l'équation de la droite passant par les deux nœuds communs. On a bien évidemment :

uh(Ni)=c0xi+c1yi+c2(16)

c0,c1,c2 sont les coefficients d'un des trois triangles qui partagent le nœud Ni.

On définit les fonctions affines par morceaux suivantes, pour i=1,P :

ϕi(Nj)=1,j=i(17) ϕi(Nj)=0,ji (18)

Autrement dit, la fonction ϕi vaut 1 sur le nœud Ni et 0 sur tous les autres nœuds. Ces fonctions constituent une base Vh. Pour toute fonction uhVh , on a en effet :

uh=i=1Puh(Ni)ϕi(19)

Le support de ϕi est constitué des triangles qui ont le nœud Ni en commun. La figure suivante montre le support de ϕ13 et de ϕ1 :

maillageRectangle2-fig.svgFigure pleine page

Pour un noeud Ni, la fonction ϕi s'annule sur le côté opposé de chaque triangle auxquels ce nœud appartient.

Considérons un nœud Ni et ses nœuds voisins. Le support de ϕi est constitué des triangles définis avec le nœud Ni et ces voisins. La figure suivante montre ces triangles :

noeud-fig.svgFigure pleine page

Considérons un de ces triangles, dont les sommets sont notés 1,2,3, le sommet 1 étant le nœud Ni. Les coefficients c0,c1,c2 de ϕi sur ce triangle sont solutions des trois équations suivantes :

c0x1+c1y1+c2=1 c0x2+c1y2+c2=0 c0x3+c1y3+c2=0

Si detT désigne le déterminant de ce système, il est facile de démontrer que, si les sommets N1,N2,N3 sont ordonnés dans le sens direct, l'aire du triangle s'écrit :

ST=12(N1N2N1N3)uz=12detT(20)

Si les trois points ne sont pas alignés, ce système a un déterminant non nul. L'unique solulion peut être obtenue sans difficulté avec les règles de Cramer :

c0=y2-y3detT,c1 = x3-x2detT,c2=x2y3-x3y2detT(21)

Soit Vh,0 l'ensemble des fonctions affines par morceaux sur le maillage et qui s'annulent sur les nœuds qui appartiennent à la frontière Ωd (la partie de la frontière où une condition de Dirichlet est appliquée). Notons Nint les indices de tous les nœuds à l'exception de ces nœuds (indices des nœuds intérieurs). Pour toute fonction vVh,0 on a :

v=iNintv(Ni)ϕi(22)

La formulation variationnelle s'énonce comme suit. Trouver une fonction uhVh telle que pour toute fonction vVh,0 on ait :

Ωa(grad uh)(grad v)dσ=Ωvsdσ+Ωnva(K0(uh-u0)+g0)d(23)

Un énoncé équivalent est : trouver une fonction uhVh telle que :

Ωa(grad uh)(grad ϕi)dσ=Ωϕisdσ+Ωnϕia(K0(uh-u0)+g0)d,iNint(24)

2.d. Équations algébriques

Introduisons les composantes de uh sur la base :

uh=j=1Pξjϕj(25)

Les ξi sont les valeurs de uh aux nœuds, autrement dit les approximations de la solution u. Le report de cette décomposition dans (23) conduit à un système d'équations linéaires pour les composantes ξi :

j=1PξjΩa(gradϕj)(gradϕi)dσ =Ωϕisdσ +j=1PΩnϕiaK0ξjϕjd +Ωnϕi(ag0-K0u0)d,iNint(26)

Ce système comporte plus d'inconnues que d'équations : le nombre d'équations est égal aux nombres de nœuds à l'exception de ceux de la frontière où une condition de Dirichlet est appliquée alors que le nombre d'inconnues est égal aux nombre total de nœuds. Il faut donc enlever du système les ξi des nœuds qui sont sur Ωd . Pour cela, on écrit uh comme la somme d'une fonction de Vh,0 (qui s'annule sur la frontière de Dirichlet) et d'une fonction de Vh,d, l'ensemble des fonctions affines par morceaux qui vérifient précisément la condition de Dirichlet choisie :

uh=uh,0+uh,d(27)

Il peut être démontré que uh est indépendant du choix de uh,d [1]. On choisit donc la fonction la plus simple : celle qui s'annule sur tous les nœuds à l'exception de ceux où la condition de Dirichlet est appliquée. Notons Nd les indices des nœuds où une condition de Dirichlet est appliquée. On a :

uh=jNintξjϕj+kNdξkϕk(28)

Les ξk dans la seconde somme sont connues puisqu'il s'agit des valeurs de u sur les nœuds appartenant à la frontière où une condition de Dirichlet est appliquée.

On obtient un système d'équations pour les inconnues ξj,jNint :

jNintξjΩa(gradϕj)(gradϕi)dσ +kNdξkΩa(gradϕk)(gradϕi)dσ =Ωϕisdσ +jNintΩnϕiaK0ξjϕjd +kNdΩnϕiaK0ξkϕkd +Ωnϕia(g0-K0u0)d,iNint

Réécrivons ces équations en mettant à gauche la combinaison linéaire des inconnues :

jNintξjΩa(gradϕj)(gradϕi)dσ - jNintξjΩnϕiaK0ϕjd =-kNdξkΩa(gradϕk)(gradϕi)dσ+Ωϕisdσ +kNdΩnϕiaK0ξkϕkd +Ωnϕia(g0-K0u0)d,iNint

Posons :

Aij = Ωa(gradϕj)(gradϕi)dσ,(i,j)Nint(29) Rij=-ΩnaK0ϕiϕjd,(i,j)Nint(30) Bi = -kNdξkΩa(gradϕk)(gradϕi)dσ+Ωϕisdσ +kNdξkΩnϕiaK0ϕkd +Ωnϕia(g0-K0u0)d,iNint (31)

Le système d'équations linéaires s'écrit :

jNintAijξj+jNintRijξj=Bi,iNint(32)

2.e. Matrice du système

La matrice du système d'équations est M=A+R. La matrice A est indépendante des conditions limites. La matrice R est non nulle seulement si une condition de Robin (K00 ) est appliquée sur une partie de la frontière Ωn .

Pour ij , le coefficient Aij est non nul si les nœuds i et j ont une partie de leur support en commun, autrement dit si ce sont deux nœuds voisins (rappelons que les nœuds où une condition de Dirichlet est appliquée sont exclus). Pour évaluer approximativement l'intégrale sur un triangle T dont les sommets sont N1,N2,N3, on utilise la formule de quadrature du barycentre :

Tfdσf(N1+N2+N33)ST(33)

ST est l'aire du triangle. Considérons AijT , l'intégrale sur un des deux triangles dont un côté est constitué des nœuds i et j, le troisième étant noté Nk. Soient ci0,ci1,ci2 les coefficients qui définissent ϕi sur ce triangle et cj0,cj1,cj2 les coefficients qui définissent ϕj sur le même triangle.

noeud2-fig.svgFigure pleine page

On a :

(gradϕj)(gradϕi)=cj0ci0+cj1ci1(34)

La formule du barycentre donne :

AijT=a(Ni+Nj+Nk3)(cj0ci0+cj1ci1)ST(35)

En pratique, il sera plus simple d'attribuer une valeur de a à chaque nœud et on écrira donc :

AijT=13(ai+aj+ak)(cj0ci0+cj1ci1)ST(36)

Il faut aussi déterminer le coefficient sur la diagonale :

Aii = Ωa(gradϕi)2dσ(37)

Le support de la fonction à intégrer est constitué des triangles auquel le nœud Ni appartient. Pour un de ces triangles, l'intégrale vaut approximativement :

AiiT=13(ai+aj+ak)(ci02+ci12)ST(38)

Le remplissage de la matrice A se fait en parcourant tous les triangles du maillage. Pour chaque triangle :

  • Pour chaque nœud du triangle n'appartenant pas à Ωd , on ajoute AiiT à Mii.
  • Pour chaque paire de nœuds du triangle n'appartenant pas à Ωd , on ajoute AijT à Mij et à Mji.

Pour ij , Rij est non nul si les deux nœuds Ni et Nj sont voisins et s'ils appartiennent à une frontière où une condition de Robin est appliquée.

noeud3-fig.svgFigure pleine page

Le produit ϕiϕj est non nul sur le triangle constitué de ces deux nœuds et du nœud Nk. Rij est l'intégrale sur le segment NiNj de f=-aK0ϕiϕj . Les coefficients a et K0 seront dans la plupart des cas constants sur le segment ou peuvent être considérés comme tel. Si est l'abscisse sur le segment, on a :

f()=-aK0(d-)dij2(39)

dij est la distance entre les deux nœuds. On a donc :

Rij=-aK0dij20dij(dij-)d=-aK0dij6(40)

aK0 est la moyenne des valeurs sur le côté. Une valeur de K0 est attribuée au côté et la valeur moyenne de a est évaluée à partir des valeurs sur les deux nœuds. On a donc :

Rij=-112(ai+aj)K0,ijdij(41)

Il faut aussi déterminer le coefficient sur la diagonale :

Rii=-ΩnaK0ϕi2d(42)

La fonction à intégrer est non nulle sur les deux segments (notés 1 et 2) de la frontière auquel le nœud Ni appartient :

noeud4-fig.svgFigure pleine page

Notons a1,K1 et a2,K2 les valeurs des coefficients aux milieux des segments 1 et 2. On a :

Rii-a1K10d12d12d-a2K20d22d22d=-13a1K1d1-13a2K2d2(43)

Pour un triangle donné, le terme à considérer est une intégrale sur le côté du triangle situé sur la frontière :

RiiT=-16(ai+aj)K0,ijdij(44)

Le remplissage de la matrice R se fait en parcourant tous les triangles du maillage. Pour chaque triangle :

  • Pour chaque paires de nœuds du triangle appartenant à Ωn , on ajoute Rij à Mij et à Mji.
  • Pour chaque nœuds des paires de nœuds du triangle appartenant à Ωn , on ajoute RiiT à Mii.

Les matrices A et R sont symétriques. La ligne i de la matrice A comporte un coefficient diagonal et des coefficients non diagonaux correspondant aux nœuds voisins du nœud Ni. La ligne i de la matrice R comporte un coefficient diagonal et des coefficients non diagonaux correspondant aux nœuds voisins de Ni situés sur le bord Ωn .

Rappelons que tous les nœuds qui interviennent dans le calcul de ces deux matrices sont des nœuds intérieurs, c'est-à-dire qu'il faut exclure les nœuds où une condition de Dirichlet et appliquée.

2.f. Matrice colonne

Il faut expliciter les coefficients de la matrice colonne B. Le premier terme de Bi est :

B1,i=-kNdξkΩa(gradϕk)(gradϕi)dσ,iNint(45)

Pour iNint (nœud n'appartenant pas à Ωd ), il faut considérer les nœuds Nk appartenant à Ωd tels que le support de ϕk ait une partie commune avec celui de ϕi . B1,i est non nul si et seulement si Ni est voisin d'un nœud de la frontière de Dirichlet. La figure suivante montre un nœud de ce type et les triangles qui constituent la partie commune des supports.

noeud5-fig.svgFigure pleine page

Pour chaque triangle du maillage, on recherche les paires de nœuds contituées d'un nœud Ni n'appartenant pas à Ωd et d'un nœud Nk appartenant à Ωd . La contribution de cette paire à B1,i est :

B1,ip=-ξk13(ai+aj+ak)(ci0ck0+ci1ck1)ST(46)

Le deuxième terme de Bi est :

B2,i=Ωϕisdσ,iNint(47)

Il s'agit du terme qui contient la source de l'équation de Poisson. Pour iNint , il faut évaluer l'intégrale sur le support de ϕi , qui est constitué des triangles auxquels ce nœud appartient. Pour un de ces triangles, dont les sommets sont N1,N2,N3, la formule de quadrature du barycentre donne :

B2,is(N1+N2+N33)ϕi(N1+N2+N33)ST(48)

La fonction ϕi vaut 1/3 au barycentre. Pour la source, on considère plutôt la moyenne des valeurs aux sommets du triangle :

B2,is1+s2+s3313ST(49)

Pour chaque triangle du maillage et pour chaque nœuds du triangle (d'indice i) n'appartenant pas à Ωd , on ajoute B2,i à Bi.

Le troisième terme de Bi est :

B3,i=kNdξkΩnaK0ϕiϕkd,iNint(50)

Ce coefficient peut être non nul s'il existe un nœud Nk de la frontière de Dirichlet tel que ϕk soit non nul sur au moins un segment de la frontière de Neumann. Cela ne peut se rencontrer qu'à la jonction entre ces deux frontières, comme le montre la figure suivante :

noeud6-fig.svgFigure pleine page

L'intégrale se fait sur le segment NkNi. Le calcul est similaire à celui du coefficient Rij. On obtient :

B3,i=ξk12(ai+ak)K0,ikd6(51)

d est la distance NkNi.

Le quatrième terme de Bi est :

B4,i=Ωnϕia(g0-K0u0)d,iNint(52)

Ce terme contient la condition de Neumann (gradient g0) et le terme constant de la condition de Robin. Il est non nul si et seulement si le nœud Ni appartient à la frontière Ωn . Pour chaque triangle du maillage et pour chaque paire de nœuds de ce triangle appartenant à Ωn , la contribution de cette paire est, pour chacun des deux nœuds :

B4,ip=12(ai+aj)(g0,ij-K0,iju0,ij)12dij(53)

g0ij est la composante normale du gradient sur la frontière définie par les nœuds Ni et son voisin Nj, K0,ij et u0,ij les coefficients relatifs à la condition de Robin sur la même frontière.

3. Implémentation

Chaque nœud Ni est un objet qui contient les informations suivantes :

Un côté de triangle défini par un nœud de Dirichlet et un nœud de Neumann peut se voir affecté une condition de Neumann (s'il est sur Ωn ). Celle-ci est définie dans le triangle correspondant.

import numpy as np
import scipy.sparse as sparse
import scipy.sparse.linalg as linalg
from scipy.linalg import solve

DIR = 1
NEU = 2

class Noeud:
    def __init__(self,x,y,a,s,lim=0,ksi=0):
        self.xy = np.array([x,y])
        self.x = x 
        self.y = y
        self.a = a
        self.s = s
        self.lim = lim
        self.ksi = ksi
        self.indice = 0 # indice dans le maillage
        
                    

Chaque triangle est défini par trois nœuds. Les arguments du constructeur sont :

Dans le constructeur, l'aire du triangle et les coefficients des fonctions ϕi pour les trois sommets sont calculés.

class Triangle:
    def __init__(self,n1,n2,n3,bords=[False,False,False],g=[0,0,0],K0=[0,0,0],u0=[0,0,0]):
        self.noeud = [n1,n2,n3] 
        self.detT = n2.x*n3.y-n2.x*n1.y-n1.x*n3.y-n2.y*n3.x+n2.y*n1.x+n1.y*n3.x
        self.ST = 0.5*abs(self.detT)
        if self.detT<0:
            (n2,n3) = (n3,n2)
            self.noeud = [n1,n2,n3] 
            self.detT = n2.x*n3.y-n2.x*n1.y-n1.x*n3.y-n2.y*n3.x+n2.y*n1.x+n1.y*n3.x
        if self.detT ==0 : 
            print("Erreur de définition du triangle")
            print(n1.xy)
            print(n2.xy)
            print(n3.xy)
        self.phi = []
        self.phi.append(self.fonctionPhi(n1,n2,n3))
        self.phi.append(self.fonctionPhi(n2,n3,n1))
        self.phi.append(self.fonctionPhi(n3,n1,n2))
        self.a = (n1.a+n2.a+n3.a)/3
        self.s = (n1.s+n2.s+n3.s)/3
        self.paires = [(0,1,self.distance(n1,n2),bords[0],g[0],K0[0],u0[0]),(0,2,self.distance(n1,n3),bords[2],g[2],K0[2],u0[2]),(1,2,self.distance(n2,n3),bords[1],g[1],K0[1],u0[1])]
        self.xc = (n1.x+n2.x+n3.x)/3
        self.yc = (n1.y+n2.y+n3.y)/3
    def distance(self,ni,nj):
        return np.sqrt((ni.x-nj.x)**2+(ni.y-nj.y)**2)
    def fonctionPhi(self,n1,n2,n3):
        # fonction Phi relative au sommet n1
        c0 = (n2.y-n3.y)/self.detT
        c1 = (n3.x-n2.x)/self.detT 
        c2 = (n2.x*n3.y-n3.x*n2.y)/self.detT
        return [c0,c1,c2]
    
    def interpol(self,x,y):
        phi1 = self.phi[0]
        phi2 = self.phi[1]
        phi3 = self.phi[2]
        u1 = self.noeud[0].ksi*(phi1[0]*x+phi1[1]*y+phi1[2])
        u2 = self.noeud[1].ksi*(phi2[0]*x+phi2[1]*y+phi2[2])
        u3 = self.noeud[2].ksi*(phi3[0]*x+phi3[1]*y+phi3[2])
        return u1+u2+u3
    def cotePoint(self,i,j,x,y):
        # détermine de quel côté d'un côté le point P se trouve 
        xi = self.noeud[i].x 
        yi = self.noeud[i].y 
        xj = self.noeud[j].x 
        yj = self.noeud[j].y
        return (y-yi)*(xj-xi)-(x-xi)*(yj-yi)
    def contientPoint(self,x,y):
        # détermine si un point est dans le triangle
        return self.cotePoint(0,1,x,y)>=0 and self.cotePoint(1,2,x,y)>=0 and self.cotePoint(2,0,x,y)>=0
    
        
    def rectangle(self):
        #rectangle minimal contenant le triangle
        n1 = self.noeud[0]
        n2 = self.noeud[1]
        n3 = self.noeud[2]
        xmin = min(n1.x,n2.x,n3.x)
        xmax = max(n1.x,n2.x,n3.x)
        ymin = min(n1.y,n2.y,n3.y)
        ymax = max(n1.y,n2.y,n3.y)
        return [xmin,xmax,ymin,ymax]
    def matriceA(self,M):
        for i in range(3):
            ni = self.noeud[i]
            if ni.lim!=DIR:
                phi = self.phi[i]
                M[ni.indice,ni.indice] += self.a*(phi[0]*phi[0]+phi[1]*phi[1])*self.ST
        for p in self.paires:
            (i,j,d,b,g,K0,u0) = p
            ni = self.noeud[i]
            nj = self.noeud[j]
            if ni.lim!=DIR and nj.lim!=DIR:
                phi_i = self.phi[i]
                phi_j = self.phi[j] 
                A = self.a*(phi_i[0]*phi_j[0]+phi_i[1]*phi_j[1])*self.ST
                M[ni.indice,nj.indice] += A
                M[nj.indice,ni.indice] += A
                
                
    def matriceR(self,M):
        for p in self.paires:
            (i,j,d,b,g,K0,u0) = p
            ni = self.noeud[i]
            nj = self.noeud[j]
            if ni.lim==NEU and nj.lim==NEU and b:
                mii = -(ni.a+nj.a)*K0*d/6
                M[ni.indice,ni.indice] += mii
                M[nj.indice,nj.indice] += mii
                mij = -1/6*(ni.a+nj.a)*K0/2*d
                M[ni.indice,nj.indice] += mij
                M[nj.indice,ni.indice] += mij
    def matriceB1(self,B):
        for p in self.paires:
            (i,j,d,b,g,K0,u0) = p
            ni = self.noeud[i]
            nj = self.noeud[j]
            phi_i = self.phi[i]
            phi_j = self.phi[j] 
            if ni.lim==DIR and nj.lim!=DIR:
                B[nj.indice] += -ni.ksi*self.a*(phi_i[0]*phi_j[0]+phi_i[1]*phi_j[1])*self.ST
            if ni.lim!=DIR and nj.lim==DIR:
                B[ni.indice] += -nj.ksi*self.a*(phi_i[0]*phi_j[0]+phi_i[1]*phi_j[1])*self.ST
    def matriceB2(self,B):
        for i in range(3):
            ni = self.noeud[i]
            if ni.lim!=DIR:
                B[ni.indice] += self.s*self.ST/3
    def matriceB3(self,B):
        for p in self.paires:
            (i,j,d,b,g,K0,u0) = p
            ni = self.noeud[i]
            nj = self.noeud[j]
            if ni.lim==NEU and nj.lim==DIR and b:
                B[ni.indice] += nj.ksi*(ni.a+nj.a)*K0*d/12
            if ni.lim==DIR and nj.lim==NEU and b:
                B[nj.indice] += ni.ksi*(ni.a+nj.a)*K0*d/12
    def matriceB4(self,B):
        for p in self.paires:
            (i,j,d,b,g,K0,u0) = p
            ni = self.noeud[i]
            nj = self.noeud[j]
            if ni.lim==NEU and nj.lim==NEU and b:
                B4 = 0.5*(ni.a+nj.a)*(g-K0*u0)*0.5*d
                B[ni.indice] += B4
                B[nj.indice] += B4
                
                
    def completerMatrices(self,M,B):
        self.matriceA(M)
        self.matriceR(M)
        self.matriceB1(B)
        self.matriceB2(B)
        self.matriceB3(B)
        self.matriceB4(B)
                

Les fonctions matriceA et matriceR complètent la matrice M (par addition). Les fonctions matriceB1,matriceB2,matriceB3,matriceB4 complètent la matrice B. La fonction completerMatrices permet de générer la contibution d'un triangle aux deux matrices.

La classe ElementsFinis2D permet d'effectuer la résolution. Son constructeur prend en argument la liste des nœuds et la liste des triangles. La fonction solve construit les matrices par bouclage sur les triangles puis effectue la résolution du système, par défaut par la méthode itérative du gradient conjugué. Les valeurs de ξi obtenues sont affectées aux nœuds.

class ElementsFinis2D:
    def __init__(self,noeuds,triangles):
        self.triangles = triangles
        self.noeuds = []
        self.noeuds_dirichlet = []
        indice = 0
        indice_dirichlet = 0
        for n in noeuds:
            if n.lim!=DIR:
                n.indice = indice
                self.noeuds.append(n)
                indice += 1
            else:
                n.indice = indice_dirichlet
                self.noeuds_dirichlet.append(n)
                indice_dirichlet += 1   
    def creerMatrices(self):
        self.Nnoeuds = len(self.noeuds)
        self.M = np.zeros((self.Nnoeuds,self.Nnoeuds),dtype=np.float64)
        self.B = np.zeros(self.Nnoeuds,dtype=np.float64)
    def solve(self,solver="iterative"):
        self.creerMatrices()
        for t in self.triangles:
            t.completerMatrices(self.M,self.B)
        if solver=="iterative":
            M = sparse.bsr_array(self.M)
            ksi,exitCode = linalg.cg(M, self.B, atol=1e-5)
            print("exitCode : ",exitCode)
        else:
            ksi = solve(self.M,self.B)
        for i in range(self.Nnoeuds):
            self.noeuds[i].ksi = ksi[i]
                

4. Exemples

4.a. Exemple A

On considère le domaine carré Ω:[0,1]×[0,1] . La source est nulle (s=0) et le coefficient a est uniforme (le même partout). En x=0 et x=1 on applique des conditions limites de Dirichlet, avec respectivement les valeurs 0 et 1. En y=0 et y=1 on applique des conditions de Neumann avec g0=0. La solution est : u(x,y)=x (il s'agit en fait d'un problème unidimensionnel).

La structure du maillage est montré sur la figure suivante :

maillageRectangle3-fig.svgFigure pleine page

Les 4 nœuds des coins sont affectés de la condition de Dirichlet. Chaque nœud non situé sur un bord a 6 nœuds voisins.

On commence par définir les noeuds :

N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 0
for j in range(N):
    for i in range(N):
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=1)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
        noeuds.append(n)
        

puis les triangles :

triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        

Pour le maillage considéré, il est aisé de tracer u(x,y) car les nœuds sont disposés sur une grille. Dans le cas général, il faudra procéder à une interpolation dans les triangles (par la fonction affine par morceaux) pour obtenir les valeurs de u sur une grille.

elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig1fig1.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig2fig2.pdf

La même solution peut être obtenue en remplaçant la condition de Dirichlet en x=1 par la condition de Neumann u'(1)=1 :

N = 51
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 0
for j in range(N):
    for i in range(N):
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,1,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,1,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig3fig3.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig4fig4.pdf

4.b. Exemple B

On reprend l'exemple A avec une source uniforme et des conditions de Dirichlet nulle en x=0 et x=1. La solution est u(x,y)=s2(x-x2) .

N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 1
for j in range(N):
    for i in range(N):
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig5fig5.pdf
figure()
plot(x,U[N//2,:])
plot(x,s/2*(x-x*x),"k--")
grid()
                     
fig6fig6.pdf

4.c. Exemple C

On reprend l'exemple A avec une source uniforme et des conditions de Dirichlet nulle sur les quatre bords :

N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 1
for j in range(N):
    for i in range(N):
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig7fig7.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig8fig8.pdf

4.d. Exemple D

On reprend l'exemple A avec deux milieux différents : la moitié à droite du domaine a une valeur de a deux fois plus grande.

N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 0
a1 = 1
a2 = 2
for j in range(N):
    for i in range(N):
        if i < N//2 : a=a1
        else : a=a2
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=1)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig9fig9.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig10fig10.pdf

4.e. Exemple E

Un objet de forme carrée est introduit dans le domaine, avec une condition de Dirichlet sur ses bords. Les quatres bords du domaine ont une condition de Dirichlet. La figure suivante montre comment l'objet est défini :

maillageRectangle4-fig.svgFigure pleine page
N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 0
# bords de l'objet
e = 5
i1 = N//2-e 
i2 = N//2+e 
j1 = N//2-e 
j2 = N//2+e
for j in range(N):
    for i in range(N):
        if (i==i1 and j1<=j<=j2) or (i==i2 and j1<=j<=j2) or (j==j1 and i1<=i<=i2) or (j==j2 and i1<=i<=i2):
            n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=1)
        else:
            if i!=0 and i!=N-1 and j!=0 and j!=N-1:
                n = Noeud(x[i],y[j],a,s)
            else:  
                if i==0:
                    n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
                elif i==N-1:
                    n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
                if j==0 and i!=0 and i!=N-1:
                    n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
                elif j==N-1 and i!=0 and i!=N-1:
                    n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g))
        bords = [False,False,False]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig11fig11.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig12fig12.pdf

4.f. Exemple F

On reprend l'exemple A mais avec une condition de Robin sur les bords en y=0 et y=1 :

N = 50
x = np.linspace(0,1,N)
y = np.linspace(0,1,N)
noeuds = []
a = 1
s = 0
for j in range(N):
    for i in range(N):
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            n = Noeud(x[i],y[j],a,s)
        else:  
            if i==0:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=0)
            elif i==N-1:
                n = Noeud(x[i],y[j],a,s,lim=DIR,ksi=1)
            if j==0 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
            elif j==N-1 and i!=0 and i!=N-1:
                n = Noeud(x[i],y[j],a,s,lim=NEU)
        
        noeuds.append(n)
        
triangles = []
for j in range(N-1):
    for i in range(N-1):
        k = i+j*N
        bords = [False,False,False]
        K0 = [0,0,0]
        u0 = [0,0,0]
        if i==N-2 :
            bords=[False,True,False]
            g = [0,0,0]
        if j==0:
            bords=[True,False,False]
            g = [0,0,0]
            K0 = [-2,0,0]
        if j==0 and i==N-2:
            bords=[True,True,False]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+1],noeuds[k+1+N],bords=bords,g=g,K0=K0,u0=u0))
        bords = [False,False,False]
        K0 = [0,0,0]
        u0 = [0,0,0]
        if i==0:
            bords = [False,False,True]
            g = [0,0,0]
        if j==N-2 :
            bords = [False,True,False]
            g = [0,0,0]
            K0 = [0,-2,0]
        if i==0 and j==N-2:
            bords = [False,True,True]
            g = [0,0,0]
        triangles.append(Triangle(noeuds[k],noeuds[k+N+1],noeuds[k+N],bords=bords,g=g,K0=K0,u0=u0))
        
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
U = np.zeros((N,N),dtype=np.float64)
indice = 0
for j in range(N):
    for i in range(N):
        n = noeuds[indice]
        U[j,i] = n.ksi
        indice+= 1
from matplotlib.pyplot import *
figure()
contour(U,10,extent=(0,1,0,1))

                     
fig13fig13.pdf
figure()
plot(x,U[N//2,:])
grid()
                     
fig14fig14.pdf

5. Triangulation de Delaunay

5.a. Principe

Pour un ensemble de points, il existe au moins une triangulation, appelée triangulation de Delaunay, qui vérifie la propriété suivante : pour chaque triangle, le disque ouvert défini par le cercle qui passe par les trois sommets ne contient aucun point de l'ensemble. La triangulation de Delaunay est unique si l'ensemble ne contient pas quatre points situés sur un même cercle. Il existe un algorithme qui permet d'obtenir une triangulation de Delaunay. La fonction scipy.spatial.Delaunay implémente cet algorithme.

Une méthode simple de triangulation consiste à définir des points sur le domaine Ω , avec en particulier des points sur les frontières du domaine et sur les frontières des objets à l'intérieur du domaine. Ces points devront être espacés convenablement afin que les triangles obtenus par triangulation de Delaunay n'aient pas des angles trop petits (idéalement supérieurs à 30 degrés).

On traite comme exemple un domaine circulaire avec une objet de forme carré au milieu :

from scipy.spatial import Delaunay                       
theta = np.linspace(0,2*np.pi,20)
points = [[0,0]]
for R in [10,8,6]:
    for i in range(len(theta)):
        points.append([R*np.cos(theta[i]),R*np.sin(theta[i])])
a = 3
N = 5
xx = np.linspace(-a,a,N)
yy = np.linspace(-a,a,N)
for x in xx:
    points.append([x,-a])
    points.append([x,a])
for y in yy:
    points.append([-a,y])
    points.append([a,y])

points = np.array(points)
tri = Delaunay(points)
figure(figsize=(8,8))
triplot(points[:,0], points[:,1], tri.simplices)

            
fig15fig15.pdf

Les triangles sont donnés par tri.simplices, dont voici un échantillon :

print(tri.simplices[0:10])
--> array([[74, 59, 41],
       [63, 65,  0],
       [66, 45, 46],
       [42, 78, 41],
       [47, 48, 79],
       [48, 49, 79],
       [47, 64, 46],
       [64, 66, 46],
       [64, 47, 79],
       [66, 64,  0]], dtype=int32)

Chaque triangle est défini par les trois indices de ses sommets dans la liste des points. Nous pouvons facilement définir un objet de la classe Noeud pour chaque point. Un objet de la classe Triangle doit être défini pour chaque triangle. Il y a cependant une difficulté : il faut identifier les triangles dont au moins un côté se trouve sur une frontière et attribuer la condition limite de Neumann s'il s'agit de Ωn . Pour la condition de Dirichlet, il n'y a aucune difficulté puisque celle-ci est définie sur chaque nœud de Ωd .

Les nœuds situés sur une frontière Ωn sont facilement identifiables puisque l'appartenance à cette frontière est définie dès le début, mais il faut en plus définir les segments de Ωn . Considérons le cas du contour du domaine Ω et supposons qu'une condition de Neumann soit adoptée sur ce contour. Afin de repérer les points de ce contour dans les triangles, il suffit d'ajouter dans la classe Noeud un objet indiquant la frontière auquel le nœud appartient et l'indice du nœud dans cette frontière, les nœuds étant numérotés dans l'ordre de parcours de la frontière. Au moment de la création d'un triangle, on doit repérer les points du triangle qui appartiennent à chaque frontière et, pour une frontière donnée, repérer ceux qui sont consécutifs sur cet frontière. Ces deux points constituent un côté du triangle situé sur la frontière.

5.b. Implémentation

On définit une classe pour représenter une frontière de Neumann :

class Frontiere:
    def __init__(self,nom,g0,K0,u0):
        self.nom = nom
        self.g0 = g0
        self.K0 = K0 
        self.u0 = u0
        self.nbpoints = 0
    def nombrePoints(self,n):
        self.nbpoints = n
                    

Voici la nouvelle définition de la classe Noeud :

class Noeud:
    def __init__(self,x,y,a,s,lim=0,ksi=0,frontiere=None,indiceFrontiere=0):
        self.xy = np.array([x,y])
        self.x = x 
        self.y = y
        self.a = a
        self.s = s
        self.lim = lim
        self.ksi = ksi
        self.indice = 0 # indice dans le maillage
        self.frontiere = frontiere
        self.indiceFrontiere = indiceFrontiere
        

Lorsque lim==NEU, il faudra attribuer au nœud un objet de la classe Frontiere et l'indice du nœud sur cette frontière.

La fonction suivante permet de savoir si deux nœuds sont les deux sommets d'un segment appartenant à une frontière donnée :

def segment(n1,n2,frontiere):
    if n1.lim==NEU and n1.frontiere.nom == frontiere.nom and n2.lim==NEU and n2.frontiere.nom == frontiere.nom:
        if abs(n1.indiceFrontiere-n2.indiceFrontiere)==1:
            return (frontiere.g0,frontiere.K0,frontiere.u0)
        if abs(n1.indiceFrontiere-n2.indiceFrontiere)==frontiere.nbpoints-1:
            return (frontiere.g0,frontiere.K0,frontiere.u0)
    return None
         

La fonction suivante génère les triangles à partir de la liste tri.simplices :

def creerTriangles(noeuds,simplices,frontieres):
    # noeuds : liste de noeuds
    # simplices : liste de triangles 
    # frontieres : liste de frontieres
    triangles = []
    for t in simplices:
        n1 = noeuds[t[0]]
        n2 = noeuds[t[1]]
        n3 = noeuds[t[2]]
        f1 = f2 = f3 = (0,0,0)
        b1 = b2 = b3 = False
        for front in frontieres:
            s1 = segment(n1,n2,front)
            if s1!=None: 
                f1 = s1
                b1 = True
            s2 = segment(n2,n3,front)
            if s2!=None: 
                f2 = s2
                b2 = True
            s3 = segment(n3,n1,front)
            if s3!=None: 
                f3 = s3
                b3 = True
        
        tr = Triangle(n1,n2,n3,bords=[b1,b2,b3],g=[f1[0],f2[0],f3[0]],K0=[f1[1],f2[1],f3[1]],u0=[f1[2],f2[2],f3[2]])
        triangles.append(tr)
    return triangles
                
         

Lorsque la résolution est terminée, il peut être nécessaire de déterminer les valeurs de u sur une grille rectangulaire. Pour cela, on parcourt les triangles et, pour chacun, on repère les points de la grille qui sont dans ce triangle. La valeur de u sur chacun de ces points est calculée au moyen des trois fonctions ϕi relatives aux trois sommets du triangle (fonction interpol de la classe Triangle).

La figure suivante montre un triangle et une grille rectangulaire :

interpolation-fig.svgFigure pleine page

Le rectangle représenté en trait pointillé est le plus petit rectangle (de côtés parallèles aux axes de la grille) qui contient le triangle. On commence par identifier les points de la grille qui sont dans ce rectangle. La grille est définie par les points suivants :

xp=x0+pΔx,p=0,1Nx-1 yq=y0+pΔy,q=0,1,Ny-1

Les indices des points de la grille contenus dans le rectangle sont définis par :

E(xmin-x0Δx)pE(xmax-x0Δx)+1 E(ymin-y0Δy)qE(ymax-y0Δy)+1

Pour savoir si un point P de la grille est dans le triangle, on doit déterminer de quel côté il se trouve par rapport à chaque droite définie par deux sommets. Les sommets du triangle sont ordonnés dans le sens direct. Pour la droite NiNj, le point P se trouve à gauche de la droite parcourue dans le sens Ni vers Nj si :

(NiNjNiP)uz>0(54)

Le point P est à l'intérieur du triangle si et seulement si cette condition est vérifiée pour les trois côtés du triangle. La fonction contientPoint de la classe Triangle effectue ce test. Le rectangle contenant le triangle est donné par la fonction rectangle, sous la forme [xmin,xmax,ymin,ymax].

La fonction suivante effectue l'interpolation sur une grille :

def interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny):
    U = np.zeros((Nx,Ny),dtype=np.float64)
    Delta_x = (x1-x0)/(Nx-1)
    Delta_y = (y1-y0)/(Ny-1)
    for tr in triangles:
        rect = tr.rectangle()
        pmin = np.floor((rect[0]-x0)/Delta_x)
        pmax = np.floor((rect[1]-x0)/Delta_x)+1
        qmin = np.floor((rect[2]-y0)/Delta_y)
        qmax = np.floor((rect[3]-y0)/Delta_y)+1
        if pmin<pmax and qmin<qmax:
            for p in range(int(pmin),int(pmax)+1):
                for q in range(int(qmin),int(qmax)+1):
                    x = x0+p*Delta_x
                    y = y0+q*Delta_y
                    if tr.contientPoint(x,y):
                        U[q,p] = tr.interpol(x,y)
                        
    return U
            

5.c. Exemple A

Une source uniforme est présente sur tout le domaine et une condition de Dirichlet avec une valeur nulle est appliquée sur le contour. La liste des objets de la classe Noeud est générée en même temps que la liste des points.

points = [[0,0]]
noeuds = []
s = 1
a = 1
noeuds.append(Noeud(0,0,a,s))
theta = np.linspace(0,2*np.pi,40)
R = 10
for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=0))
for R in [9,8,7,6,5,4,2,1]:
    for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s))
L = 3
N = 10
xx = np.linspace(-L,L,N)
yy = np.linspace(-L,L,N)
for x in xx:
    points.append([x,-L])
    noeuds.append(Noeud(x,-L,a,s))
    points.append([x,L])
    noeuds.append(Noeud(x,L,a,s))
for y in yy:
    points.append([-L,y])
    noeuds.append(Noeud(-L,y,a,s))
    points.append([L,y])
    noeuds.append(Noeud(L,y,a,s))

points = np.array(points)
tri = Delaunay(points)

figure(figsize=(8,8))
triplot(points[:,0], points[:,1], tri.simplices)
   
                    
fig16fig16.pdf

Voici le calcul puis le tracé des lignes d'isovaleurs de u au moyen de la fonction matplotlib.pyplot.tricontour :

triangles = creerTriangles(noeuds,tri.simplices,[])
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi
    
figure(figsize=(8,8))
tricontour(points[:,0], points[:,1], tri.simplices,U,levels=10)

                     
fig17fig17.pdf

Voici l'utilisation de la fonction interpolGrille :

Nx,Ny = 100,100
x0,y0 = -10,-10
x1,y1 = 10,10 
U = interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny)
figure(figsize=(8,8))
contour(U,levels=10,extent=(-10,10,-10,10))
                      
fig18fig18.pdf

Le fait de disposer des valeurs dur une grille permet de tracer un profil sur une droite parallèle à un des axes :

figure()
plot(np.linspace(-10,10,Nx),U[Ny//2,:])
xlabel("x")
ylabel("u(x,0)")
                      
fig19fig19.pdf

5.d. Exemple B

La source est nulle mais une condition de Dirichlet de valeur 1 est mise sur l'objet carré situé au centre :

points = [[0,0]]
noeuds = []
s = 0
a = 1
noeuds.append(Noeud(0,0,a,s))
theta = np.linspace(0,2*np.pi,30)
R = 10
for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=0))
for R in [9,8,7,6,5,4,2,1]:
    for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s))
L = 3
N = 10
xx = np.linspace(-L,L,N)
yy = np.linspace(-L,L,N)
for x in xx:
    points.append([x,-L])
    noeuds.append(Noeud(x,-L,a,s,lim=DIR,ksi=1))
    points.append([x,L])
    noeuds.append(Noeud(x,L,a,s,lim=DIR,ksi=1))
for y in yy:
    points.append([-L,y])
    noeuds.append(Noeud(-L,y,a,s,lim=DIR,ksi=1))
    points.append([L,y])
    noeuds.append(Noeud(L,y,a,s,lim=DIR,ksi=1))

points = np.array(points)
tri = Delaunay(points) 
                    
triangles = creerTriangles(noeuds,tri.simplices,[])
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi
figure(figsize=(8,8))
tricontour(points[:,0], points[:,1], tri.simplices,U,levels=10)

                     
fig20fig20.pdf

5.e. Exemple C

Une condition de Neumann avec un gradient -1 est placée sur le contour. Une condition de Dirichlet de valeur 1 est mise sur l'objet carré situé au centre.

bord = Frontiere("bord",-1,0,0)
frontieres = [bord]
points = [[0,0]]
noeuds = []
s = 0
a = 1
noeuds.append(Noeud(0,0,a,s))
theta = np.linspace(0,2*np.pi,30)
R = 10
for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s,lim=NEU,frontiere=bord,indiceFrontiere=i))
bord.nombrePoints(len(theta)-1)
for R in [9,8,7,6,5,4,2,1]:
    for i in range(len(theta)-1):
        x = R*np.cos(theta[i])
        y = R*np.sin(theta[i])
        points.append([x,y])
        noeuds.append(Noeud(x,y,a,s))
L = 3
N = 10
xx = np.linspace(-L,L,N)
yy = np.linspace(-L,L,N)
for x in xx:
    points.append([x,-L])
    noeuds.append(Noeud(x,-L,a,s,lim=DIR,ksi=1))
    points.append([x,L])
    noeuds.append(Noeud(x,L,a,s,lim=DIR,ksi=1))
for y in yy:
    points.append([-L,y])
    noeuds.append(Noeud(-L,y,a,s,lim=DIR,ksi=1))
    points.append([L,y])
    noeuds.append(Noeud(L,y,a,s,lim=DIR,ksi=1))

points = np.array(points)
tri = Delaunay(points) 

triangles = creerTriangles(noeuds,tri.simplices,frontieres)
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi
figure(figsize=(8,8))
tricontour(points[:,0], points[:,1], tri.simplices,U,levels=10)

                     
fig21fig21.pdf

Voici l'utilisation de la fonction interpolGrille :

Nx,Ny = 100,100
x0,y0 = -10,-10
x1,y1 = 10,10 
U = interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny)
figure(figsize=(8,8))
contour(U,levels=10,extent=(-10,10,-10,10))
                      
fig22fig22.pdf
figure()
plot(np.linspace(-10,10,Nx),U[Ny//2,:],".")
xlabel("x")
ylabel("u(x,0)")
                      
fig23fig23.pdf
figure()
plot(np.linspace(-10,10,Ny),U[:,Nx//2],".")
xlabel("y")
ylabel("u(0,y)")
                      
fig24fig24.pdf

La valeur sur le bord du domaine n'est pas nulle mais elle reste évidemment nulle dans les parties de la grille hors du maillage. Pour les deux courbes ci-dessus, les deux points extrêmes ne sont pas dans le maillage.

5.f. Exemple D

Le domaine est rectangulaire et les points sont disposés sur un grille. Deux objets circulaires sont placés avec une condition de Dirichlet sur chacun d'eux. Une condition de Dirichlet est appliquée sur les bords. Il s'agit du problème d'électrostatique comportant deux conducteurs cylindriques parallèles avec une différence de potentiel entre eux.

points = []
noeuds = []
s = 0
a = 1
x1,y1 = 0,3
x2,y2 = 0,-3
R1 = 1
N = 51
xx = np.linspace(-10,10,N)
yy = np.linspace(-10,10,N)
for i in range(N):
    for j in range(N):
        x = xx[i]
        y = yy[j]
        points.append([x,y])
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            noeuds.append(Noeud(x,y,a,s))
        else:
            noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=0))


theta = np.linspace(0,2*np.pi,20)
for i in range(len(theta)-1):
    x = x1+R1*np.cos(theta[i])
    y = y1+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=1))
    
for i in range(len(theta)-1):
    x = x2+R1*np.cos(theta[i])
    y = y2+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=-1))
        
points = np.array(points)
tri = Delaunay(points)
figure(figsize=(8,8))
triplot(points[:,0], points[:,1], tri.simplices)    
                    
fig25fig25.pdf
triangles = creerTriangles(noeuds,tri.simplices,frontieres)
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi
figure(figsize=(8,8))
tricontour(points[:,0], points[:,1], tri.simplices,U,levels=10)             
                     
fig26fig26.pdf
Nx,Ny = 100,100
x0,y0 = -10,-10
x1,y1 = 10,10 
U = interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny)
figure(figsize=(8,8))
contour(U,levels=10,extent=(-10,10,-10,10))
                      
fig27fig27.pdf
figure()
plot(np.linspace(-10,10,Ny),U[:,Nx//2])
xlabel("y")
ylabel("u(0,y)")
                      
fig28fig28.pdf

Le maillage n'est pas optimal : certains triangles au voisinage des cercles ont un angle trop petit. Pourtant, le résultat semble satisfaisant.

Si l'on réduit la distance entre les deux cercles, il faut aussi réduire la grille principale car la variation de u entre les deux cercles augmente :

points = []
noeuds = []
s = 0
a = 1
x1,y1 = 0,1.5
x2,y2 = 0,-1.5
R1 = 1
N = 71
xx = np.linspace(-10,10,N)
yy = np.linspace(-10,10,N)
for i in range(N):
    for j in range(N):
        x = xx[i]
        y = yy[j]
        points.append([x,y])
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            noeuds.append(Noeud(x,y,a,s))
        else:
            noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=0))


theta = np.linspace(0,2*np.pi,20)
for i in range(len(theta)-1):
    x = x1+R1*np.cos(theta[i])
    y = y1+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=1))
    
for i in range(len(theta)-1):
    x = x2+R1*np.cos(theta[i])
    y = y2+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=-1))
        
points = np.array(points)
tri = Delaunay(points)

figure(figsize=(8,8))
triplot(points[:,0], points[:,1], tri.simplices)    
                    
fig29fig29.pdf
triangles = creerTriangles(noeuds,tri.simplices,frontieres)
elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi
figure(figsize=(8,8))
tricontour(points[:,0], points[:,1], tri.simplices,U,levels=10)             
                     
fig30fig30.pdf
Nx,Ny = 100,100
x0,y0 = -10,-10
x1,y1 = 10,10 
U = interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny)
figure(figsize=(8,8))
contour(U,levels=10,extent=(-10,10,-10,10))
                      
fig31fig31.pdf
figure()
plot(np.linspace(-10,10,Ny),U[:,Nx//2])
xlabel("y")
ylabel("u(0,y)")
                      
fig32fig32.pdf

5.g. Exemple E

On reprend la configuration précédente mais avec une condition de Neumann sur chaque cercle. Pour un problème d'électrostatique, cela correspond à deux corps isolants cylindriques avec une densité de charge uniforme sur leur surface.

cercle1 = Frontiere("cercle1",-1,0,0)
cercle2 = Frontiere("cercle2",1,0,0)
frontieres = [cercle1,cercle2]
points = []
noeuds = []
s = 0
a = 1
x1,y1 = 0,3
x2,y2 = 0,-3
R1 = 1
N = 51
xx = np.linspace(-10,10,N)
yy = np.linspace(-10,10,N)
for i in range(N):
    for j in range(N):
        x = xx[i]
        y = yy[j]
        points.append([x,y])
        if i!=0 and i!=N-1 and j!=0 and j!=N-1:
            noeuds.append(Noeud(x,y,a,s))
        else:
            noeuds.append(Noeud(x,y,a,s,lim=DIR,ksi=0))


theta = np.linspace(0,2*np.pi,20)
for i in range(len(theta)-1):
    x = x1+R1*np.cos(theta[i])
    y = y1+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=NEU,frontiere=cercle1,indiceFrontiere=i))
cercle1.nombrePoints(len(theta)-1)
for i in range(len(theta)-1):
    x = x2+R1*np.cos(theta[i])
    y = y2+R1*np.sin(theta[i])
    points.append([x,y])
    noeuds.append(Noeud(x,y,a,s,lim=NEU,frontiere=cercle2,indiceFrontiere=i))
cercle2.nombrePoints(len(theta)-1)  
points = np.array(points)
tri = Delaunay(points)
triangles = creerTriangles(noeuds,tri.simplices,frontieres)

elem = ElementsFinis2D(noeuds,triangles)
elem.solve()
figure(figsize=(8,8))
U = np.zeros(len(noeuds))
for i in range(len(noeuds)):
    U[i] = noeuds[i].ksi

Nx,Ny = 100,100
x0,y0 = -10,-10
x1,y1 = 10,10 
U = interpolGrille(triangles,x0,x1,y0,y1,Nx,Ny)
figure(figsize=(8,8))
contour(U,levels=10,extent=(-10,10,-10,10))
                      
fig33fig33.pdf
figure()
plot(np.linspace(-10,10,Ny),U[:,Nx//2])
xlabel("y")
ylabel("u(0,y)")
                      
fig34fig34.pdf
Creative Commons LicenseTextes et figures sont mis à disposition sous contrat Creative Commons.
Références
[1]  M.G. Larson, F. Bengzon,  The finite element method,  (Springer-Verlag, 2013)