Cours M2 Maillage
Cours M2 Maillage
Cours M2 Maillage
Frédéric Hecht
Université Pierre et Marie Curie, Paris
20 mars 2006
2
Table des matières
3 Algorithmes 45
3.1 Chaînes et Chaînages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
4 TABLE DES MATIÈRES
3.1.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
3.1.2 Construction de l’image réciproque d’une fonction . . . . . . . . . . . 45
3.1.3 Construction des arêtes d’un maillage . . . . . . . . . . . . . . . . . . 46
3.1.4 Construction des triangles contenant un sommet donné . . . . . . . . 49
3.1.5 Construction de la structure d’une matrice morse . . . . . . . . . . . 50
3.2 Algorithmes de trie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.3 Algorithmes de recherche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
3.3.1 Arbre quaternaire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
3.4 Parcours récurcif d’un maillage . . . . . . . . . . . . . . . . . . . . . . . . . 54
opérateurs du C++ :
+ - * / % ^ & | ~ ! = < > +=
-= *= /= %= ^= &= |= « » «= »= == != <=
>= && || ++ - ->* , -> [] () new new[] delete delete[]
(T)
où (T est un type), et où (n-args ) est la déclaration classique des n arguments.
Remarque si opérateur est défini dans une classe alors le premier argument est la classe
elle même et donc le nombre d’arguments est n − 1.
– Les règles de conversion d’un type T en A par défaut qui sont génèrées à partir d’un
constructeur A(T) dans la classe A ou avec l’opérateur de conversion operator (A)
() dans la classe T , operator (A) (T) hors d’une classe. De plus il ne faut pas
oublier que C++ fait automatiquement un au plus un niveau de conversion pour trouver
la bonne fonction ou le bon opérateurs.
– Programmation générique de base (c.f. template). Exemple d’écriture de la fonction min
générique suivante template<class T> T & min(T & a,T & b){return a<b ? a :b ;}
– Pour finir, connaître seulement l’existence du macro générateur et ne pas l’utiliser.
R:
R (**data)(R) = new (R (*[ldata])(R)) ;
class A { public:
A() ; // constructeur de la class A
};
#include <iostream>
#include "a.hpp"
using namespace std ;
A::A()
{
cout « " Constructeur A par défaut " « this « endl ;
}
#include <iostream>
#include "a.hpp"
using namespace std ;
int main(int argc,char ** argv)
8 CHAPITRE 1. QUELQUES ÉLÉMENTS DE SYNTAXE
{
for(int i=0 ;i<argc ;++i)
cout « " arg " « i « " = " « argv[i] « endl ;
A a[10] ; // un tableau de 10 A
return 0 ; // ok
}
les deux compilations et l’édition de liens qui génère un executable tt dans une fenêtre
Terminal, avec des commandes de type shell ; sh, bash tcsh, ksh, zsh, ...
, sont obtenues avec les trois lignes :
CPP=g++
CPPFLAGS= -g
LIB=
%.o:%.cpp
(caractere de tabulation -->/) $(CPP) -c $(CPPFLAGS) $^
tt: a.o tt.o
(caractere de tabulation -->/) $(CPP) tt.o a.o -o tt
clean:
(caractere de tabulation -->/) rm *.o tt
# les dependences
#
a.o: a.hpp # il faut recompilé a.o si a.hpp change
tt.o: a.hpp # il faut recompilé tt.o si a.hpp change
Pour l’utilisation :
– pour juste voir les commandes exécutées sans rien faire :
[brochet:P6/DEA/sfemGC] hecht% make -n tt
g++ -c tt.cpp
g++ -c a.cpp
g++ -o tt tt.o a.o
– pour vraiment compiler
[brochet:P6/DEA/sfemGC] hecht% make tt
g++ -c tt.cpp
g++ -c a.cpp
g++ -o tt tt.o a.o
– pour recompiler avec une modification du fichier a.hpp via la command touch qui change
la date de modification du fichier.
[brochet:P6/DEA/sfemGC] hecht% touch a.hpp
[brochet:P6/DEA/sfemGC] hecht% make tt
g++ -c tt.cpp
g++ -c a.cpp
g++ -o tt tt.o a.o
remarque : les deux fichiers sont bien à recompiler car il font un include du fichier a.hpp.
– pour nettoyer :
[brochet:P6/DEA/sfemGC] hecht% touch a.hpp
[brochet:P6/DEA/sfemGC] hecht% make clean
rm *.o tt
Remarque : Je vous conseille très vivement d’utiliser un Makefile pour compiler vous
programme.
absolue Dans une classe avec des pointeurs et avec un destructeur, il faut
que les deux opérateurs de copie (création et affection) soient définis. Si
Règle 1.1
vous considérez que ces deux opérateurs ne doivent pas exister alors les
déclarez en privé sans les définir.
Dans ce cas les deux opérateurs de copies ne sont pas programmer pour qu’une erreur à
l’édition des liens soit généré.
Par contre dans ce cas, il faut programmer les deux opérateurs construction et affectation
par copie.
Effectivement, si vous ne définissez ses opérateurs, il suffit d’oublier une esperluette (&) dans
un passage argument pour que plus rien ne marche, comme dans l’exemple suivante :
Le pire est que ce programme marche sur la plupart des ordinateurs et donne le résultat
jusqu’au jour où l’on ajoute du code entre les 2 get (2 ou 3 ans après), c’est terrible mais ça
marchait !...
Effectivement, retourne du référence sur une variable local implique que l’on retourne l’adresse
mémoire de la pile, qui n’est libéré automatique en sortie de fonction, qui est invalide hors
de la fonction. mais bien sur le programme écrire peut marche avec de la chance.
Il ne faut jamais faire ceci :
Mais vous pouvez retourner une référence définie à partir des arguments, ou à partir de
variables static ou global qui sont rémanentes.
Si, dans un programme, vous savez qu’un expression logique doit être vraie,
Règle 1.3
alors Vous devez mettre une assertion de cette expression logique.
Ne pas penser au problème du temps calcul dans un premier temps, il est possible de re-
tirer toutes les assertions en compilant avec l’option -DNDEBUG, ou en définissant la macro
du preprocesseur #define NDEBUG, si vous voulez faire du filtrage avec des assertions, il suf-
fit de définir les macros suivante dans un fichier ftp://www.freefem.org/snc++/
assertion.hpp qui active les assertions
#ifndef ASSERTION_HPP_
#define ASSERTION_HPP_
// to compile all assertion
// #define ASSERTION
// to remove all the assert
// #define NDEBUG
#include <assert.h>
#define ASSERTION(i) 0
#ifdef ASSERTION
#undef ASSERTION
#define ASSERTION(i) assert(i)
#endif
// 4 small function to test if you are inside a segment
[a,b[,[a,b],]a,b],]a,b[
12 CHAPITRE 1. QUELQUES ÉLÉMENTS DE SYNTAXE
template<class T> inline bool Inside_of(const T & a,const T & i,const T & b)
{return (a <= i) && ( i < b) ;}
template<class T> inline bool Inside_oo(const T & a,const T & i,const T & b)
{return (a <= i) && ( i <= b) ;}
template<class T> inline bool Inside_fo(const T & a,const T & i,const T & b)
{return (a > i) && ( i <= b) ;}
template<class T> inline bool Inside_ff(const T & a,const T & i,const T & b)
{return (a > i) && ( i < b) ;}
#endif
comme cela il est possible de garder un niveau d’assertion avec assert. Pour des cas plus
fondamentaux et qui sont négligeables en temps calcul. Il suffit de définir la macro ASSERTION
pour que les testes soient effectués sinon le code n’est pas compilé et est remplacé par 0.
Il est fondamental de vérifier les bornes de tableaux, ainsi que les autres bornes connues.
Aujourd’hui je viens de trouver une erreur stupide, un déplacement de tableau dû à l’échange
de 2 indices dans un tableau qui ralentissait très sensiblement mon logiciel (je n’avais respecté
cette règle).
Exemple d’une petite classe qui modélise un tableau d’entier
Une fois toutes les erreurs de compilation et d’édition des liens corrigées,
il faut éditer les liens en ajoutant CheckPtr.o (le purify du pauvre)
Règle 1.5
à la liste des objets à éditer les liens, afin de faire les vérifications des
allocations.
Corriger tous les erreurs de pointeurs bien sûr, et les erreurs assertions avec le débogueur.
1.4. VÉRIFICATEUR D’ALLOCATION 13
Remarque 1.1 Ce code marche bien si l’on ne fait pas trop d’allocations, destructions dans
le programme, car le nombre d’opérations pour vérifier la destruction d’un pointeur est en
nombre de pointeurs alloués. L’algorithme est donc proportionnel au carré du nombre de
pointeurs alloués par le programme. Il est possible d’améliorer l’algorithme en triant les
pointeurs par adresse et en faisant une recherche dichotomique pour la destruction.
class T { public:
T() { cout « "Constructeur par défaut " « this « "\n"}
T(const & T a) { cout «"Constructeur par copie "
« this « "\n"}
~T() { cout « "destructeur "« this « "\n"}
T & operator=(T & a) {cout « " copie par affectation :"
« this « " = " « &a « endl ;}
};
Puis tester, cette classe faisant un programme qui appelle les 4 fonctions
suivantes, et qui contient une variable globale de type T.
remarque : bien sur vous pouvez et même vous devez tester ses deux
classes, avec le vérificateur d’allocation dynamique.
Chapitre 2
2.1.1 La classe R2
// Définition de la class R2
// sans compilation sépare toute les fonctions
// sous défini dans ce R2.hpp avec des inline
//
// remarque la fonction abort est déclaré dans #include <cstdlib>
// The class R2
class R2 {
// utilisation de friend pour definir de fonction exterieur a
// la class dans la class
friend std::ostream& operator «(std::ostream& f, const R2 & P )
{ f « P.x « ’ ’ « P.y ; return f ; }
friend std::istream& operator »(std::istream& f, R2 & P)
{ f » P.x » P.y ; return f ; }
public:
R x,y ; // declaration des membre
// les 3 constructeurs --
R2 () :x(0.),y(0.) {} // rappel : x(0), y(0) sont initialiser
R2 (R a,R b):x(a),y(b) {} // via le constructeur de double
R2 (const R2 & a,const R2 & b):x(b.x-a.x),y(b.y-a.y) {}
}; // fin de la class R2
– dans le cas d’un opérateur défini hors d’une classe le nombre de paramètres est donné par
le type de l’opérateur uniare (+ - * ! [] etc.. : 1 paramètre), binaire ( + - * / | & ||
&&ˆ == <= >= < > etc.. 2 paramètres), n-aire (() : n paramètres).
– ostream, istream sont les deux types standards pour respectivement écrire et lire dans
un fichier ou sur les entrées sorties standard. Ces types sont définis dans le fichier « ios-
tream »inclue avec l’ordre #include<iostream> qui est mis en tête de fichier ;
– les deux opérateurs « et » sont les deux opérateurs qui généralement et respectivement
écrivent ou lisent dans un type ostream, istream, ou iostream.
Cette classe modélise le plan IR2 , pour que les opérateurs classiques fonctionnent, c’est-à-
dire :
Un point P du plan définit via modélisé par ces 2 coordonnées x, y, et nous pouvons écrire
des lignes suivantes par exemple :
R2 P(1.0,-0.5),Q(0,10) ;
R2 O,PQ(P,Q) ; // le point O est initialiser à 0,0
R2 M=(P+Q)/2 ; // espace vectoriel à droite
R2 A = 0.5*(P+Q) ; // espace vectoriel à gauche
R ps = (A,M) ; // le produit scalaire de R2
R pm = A^M ; // le deteminant de A,M
// l’aire du parallélogramme formé par A,M
R2 B = A.perp() ; // B est la rotation de A par π/2
R a= A.x + B.y ;
A = -B ;
A += M ; // ie. A = A + M ;
A -= B ; // ie. A = -A ;
double abscisse= A.x ; // la composante x de A
double ordonne = A.y ; // la composante y de A
A.y= 0.5 ; // change la composante de A
A(1) =5 ; // change la 1 composante (x) de A
A(2) =2 ; // change la 2 composante (y) de A
A[0] =10 ; // change la 1 composante (x) de A
A[1] =100 ; // change la 2 composante (y) de A
cout « A; // imprime sur la console A.x et A.x
cint » A ; // vous devez entrer 2 double à la console
18 CHAPITRE 2. EXEMPLES DE CLASSE C++
Le but de cette exercice de d’affiche graphiquement les bassins d’attraction
de la méthode de Newton, pour la résolution du problème z p − 1 = 0, où
p = 3, 4, ....
Rappel : la méthode de Newton s’écrit :
f (zn )
Soit z0 ∈ C, zn+1 = zn − ;
f 0 (zn )
Cette classe ne fonctionne pas car le constructeur par copie par défaut fait une copie bit à
bit et donc le pointeur v est perdu, il faut donc écrire :
};
20 CHAPITRE 2. EXEMPLES DE CLASSE C++
Il faut faire attention dans les paramètres d’entrée et de sortie des opérateurs. Il est clair que
l’on ne veut pas travailler sur des copies, mais la sortie est forcément un objet et non une
référence sur un objet car il faut allouer de la mémoire dans l’opérateur et si l’on retourne
une référence aucun destructeur ne sera appelé et donc cette mémoire ne sera jamais libérée.
Pour des raisons optimisation nous ajoutons un nouveau constructeur A(int,K*) qui évi-
tera de faire une copie du tableau.
Pour la version à gauche, il faut définir operator* extérieurement à la classe car le terme
de gauche n’est pas un vecteur.
Maintenant regardons ce qui est exécuté dans le cas d’une expression vectorielle.
int n=100000 ;
A a(n),b(n),c(n),d(n) ;
... // initialisation des tableaux a,b,c
d = (a+2.*b)+c*2.0 ;
voilà le pseudo code généré avec les 3 fonctions suivantes : add(a,b,ab), mulg(s,b,sb),
muld(a,s,as), copy(a,b) où le dernier argument retourne le résultat.
A a(n),b(n),c(n),d(n) ;
A t1(n),t2(n),t3(n),t4(n) ;
muld(2.,b),t1) ; // t1 = 2.*b
add(a,t1,t2) ; // t2 = a+2.*b
2.2. LES CLASSES TABLEAUX 21
mulg(c,2.,t3) ; // t3 = c*2.
add(t2,t3,t4) ; // t4 = (a+2.*b)+c*2.0 ;
copy(t4,d) ; // d = (a+2.*b)+c*2.0 ;
Nous voyons que quatre tableaux intermédiaires sont créés ce qui est excessif. D’où l’idée de
ne pas utiliser toutes ces possibilités car le code généré sera trop lent.
Remarque 2.1 Il est toujours possible de créer des classes intermédiaires pour des opérations
prédéfinies afin obtenir se code générer :
A a(n),b(n),c(n),d(n) ;
for (int i ;i<n ;i++)
d[i] = (a[i]+2.*b[i])+c[i]*2.0 ;
Ce cas me paraît trop compliquer, mais nous pouvons optimiser raisonnablement toutes les
combinaisons linéaires à 2 termes.
Mais, il est toujours possible d’éviter ces problèmes en utilisant les opérateurs +=, -=,
*=, /= ce que donnerai de ce cas
A a(n),b(n),c(n),d(n) ;
for (int i ;i<n ;i++)
d[i] = (a[i]+2.*b[i])+c[i]*2.0 ;
D’où l’idée de découper les classes vecteurs en deux types de classe les classes de terminer
par un _ sont des classes sans allocation (cf. new), et les autres font appel à l’allocateur new
et au déallocateur delete. De plus comme en fortran 90, il est souvent utile de voir une
matrice comme un vecteur ou d’extraire une ligne ou une colonne ou même une sous-matrice.
Pour pouvoir faire tous cela comme en fortran 90, nous allons considère un tableau comme
un nombre d’élément n, un incrément s et un pointeur v et du tableau suivant de même type
afin de extraire des sous-tableau.
De plus nous avons défini, les tableaux 1,2 ou 3 indices, il est possible extraire une partie
d’un tableau, une ligne ou une colonne.
#include<RNM.hpp>
....
typedef double R ;
KNM<R> A(10,20) ; // un matrice
. . .
KN_<R> L1(A(1,’.’) ; // la ligne 1 de la matrice A ;
KN<R> cL1(A(1,’.’) ; // copie de la ligne 1 de la matrice A ;
KN_<R> C2(A(’.’,2) ; // la colonne 2 de la matrice A ;
KN<R> cC2(A(’.’,2) ; // copie de la colonne 2 de la matrice A ;
KNM_<R> pA(FromTo(2,5),FromTo(3,7)) ; // partie de la matrice A(2:5,3:7)
// vue comme un matrice 4x5
KNM B(n,n) ;
B(SubArray(n,0,n+1)) // le vecteur diagonal de B ;
KNM_ Bt(B.t()) ; // la matrice transpose sans copie
Pour l’utilisation, utiliser l’ordre #include "RNM.hpp", et les flags de compilation -DCHECK_KN
ou en définisant la variable du preprocesseur cpp du C++ avec l’ordre #defined CHECK_KN,
avant la ligne include.
Les définitions des classes sont faites dans 4 fichiers RNM.hpp, RNM_tpl.hpp, RNM_op.hpp,
RNM_op.hpp.
Pour plus de détails voici un exemple d’utilisation assez complet.
namespace std
#define CHECK_KN
#include "RNM.hpp"
#include "assert.h"
b =a ;
c=5 ;
b *= c ;
cout « " A(5,’.’)[1] " « A(5,’.’)[1] « " " « " A(5,1) = "
« A(5,1) « endl ;
cout « " A(’.’,5)(1) = "« A(’.’,5)(1) « endl ;
cout « " A(SubArray(3,2),SubArray(2,4)) = " « endl ;
cout « A(SubArray(3,2),SubArray(2,4)) « endl ;
A(SubArray(3,2),SubArray(2,4)) = -1 ;
A(SubArray(3,2),SubArray(2,0)) = -2 ;
cout « A « endl ;
Rnmk B(3,4,5) ;
for (int i=0 ;i<B.N() ;i++) // ligne
for (int j=0 ;j<B.M() ;j++) // colonne
for (int k=0 ;k<B.K() ;k++) // ....
B(i,j,k) = 100*i+10*j+k ;
cout « " B = " « B « endl ;
cout « " B(1 ,2 ,’.’) " « B(1 ,2 ,’.’) « endl ;
cout « " B(1 ,’.’,3 ) " « B(1 ,’.’,3 ) « endl ;
cout « " B(’.’,2 ,3 ) " « B(’.’,2 ,3 ) « endl ;
cout « " B(1 ,’.’,’.’) " « B(1,’.’ ,’.’) « endl ;
cout « " B(’.’,2 ,’.’) " « B(’.’,2 ,’.’) « endl ;
cout « " B(’.’,’.’,3 ) " « B(’.’,’.’,3 ) « endl ;
Rnmk Bsub(B(FromTo(1,2),FromTo(1,3),FromTo(0,3))) ;
B(SubArray(2,1),SubArray(3,1),SubArray(4,0)) = -1 ;
B(SubArray(2,1),SubArray(3,1),SubArray(4,0)) += -1 ;
cout « " B = " « B « endl ;
cout « Bsub « endl ;
return 0 ;
}
2.2. LES CLASSES TABLEAUX 25
Gi+1 = Gi + ρAH i
(Gi+1 , Gi+1 )C
γ=
(Gi , Gi )C
i+1
H = −CGi+1 + γH i
si (Gi+1 , Gi+1 )C < ε stop
R g2p=g2 ;
g2 = (Cg,g) ;
cout « iter « " ro = " « ro « " ||g||^2 = " « g2 « endl ;
if (g2 < reps2) {
cout « iter « " ro = " « ro « " ||g||^2 = " « g2 « endl ;
return 1 ; // ok
}
R gamma = g2/g2p ;
h *= gamma ;
h -= Cg ; // h = -Cg * gamma* h
}
cout « " Non convergence de la méthode du gradient conjugue " «endl ;
return 0 ;
}
// la matrice Identite -------------
template <class R>
class MatriceIdentite: VirtualMatrice<R> { public:
typedef VirtualMatrice<R>::plusAx plusAx ;
MatriceIdentite() {} ;
void addMatMul(const KN_<R> & x, KN_<R> & Ax) const { Ax+=x ; }
plusAx operator*(const KN<R> & x) const {return plusAx(this,x) ;}
};
Test du gradient conjugué Pour finir voilà, un petit programme pour le testé sur cas diffé-
rent. Le troisième cas étant la résolution de l’équation au différencielle 1d −u00 = 1 sur [0, 1]
avec comme conditions aux limites u(0) = u(1) = 0, par la méthode de l’élément fini. La
solution exact est f (x) = x(1 − x)/2, nous verifions donc l’erreur sur le resultat.
#include <fstream>
#include <cassert>
#include <algorithm>
#define KN_CHECK
#include "RNM.hpp"
#include "GC.hpp"
typedef double R ;
class MatriceLaplacien1D: VirtualMatrice<R> { public:
MatriceLaplacien1D() {} ;
void addMatMul(const KN_<R> & x, KN_<R> & Ax) const ;
plusAx operator*(const KN<R> & x) const {return plusAx(*this,x) ;}
};
// CL
Ax[0]=x[0] ;
Ax[n_1]=x[n_1] ;
}
return 0 ;
∂u
− ∆u = f ; d ans ]0, L[
∂t
pour t = 0, u(x, 0) = uO (x) et u(0, t) = u(L, t) = 0
en utilisant un θ schéma pour discrétiser en temps, c’est à dire que
un+1 − un
− ∆ θun+1 + (1 − θ)un = f ;
dans ]0, L[
∆t
où un est une approximation de u(x, n∆t), faite avec des éléménts finis
P1 , avec un maillage régulier de ]0, L| en M éléments. les fonctions élé-
mentaires seront noté wi , avec pour i = 0, ..., M , avec xi = i/N
L
x − xi x − xi
wi |]xi−1 ,xi [∪]0,L[ = , wi |]xi ,xi+1 [∪]0,L[ = , wi |]0,L[\]xi−1 ,xi+1 [ = 0
xi−1 − xi xi+1 − xi
C’est a dire qu’il faut commencer par construire du classe qui modélise la
matrice Z !
0 0
Mαβ = αwi wj + βwi wj
]O,L[
i=1...M,j=1...M
#include<sstream>
#include<ofstream>
#include<iostream>
....
stringstream ff ;
ff « "sol-"« temps « ends ;
cout « " ouverture du fichier" «ff.str.c_str()« endl ;
{
ofstream file(ff.str.c_str()) ;
for (int i=0 ;i<=M ;++i)
file «x[i]« endl ;
} // fin bloque => destruction de la variable file
// => fermeture du fichier
...
30 CHAPITRE 2. EXEMPLES DE CLASSE C++
par abus de langage, on confondra souvent un cycle et son vecteur associé. On notera par
V (G) l’espace vectoriel engendré par les cycles de G .
Démonstration. Soit H = (X, H) un graphe connexe tel que pour tout sous ensemble H 0 de
H et différent de H, le graphe (X, H 0 ) est non connexe (il en existe car le graphe (X, G) est
connexe, et les ensembles G et X sont finis). II est alors clair que H est un arbre d’après la
propriété v) du théorème A.2.
où Is+ (resp. Is− ) est l’ensemble des arcs (x, y) de G tel que s soit égal à x (resp. y) ce qui
est identique à l’ensemble des arcs partant (resp. arrivant ) de s.
Remarque 2.2 L’équation (2.2) nous dit que pour un flot tout ce qui entre en un sommet en
ressort.
Proposition A.1. l’ensemble des flots d’un graphe sans cycle est réduit à {0}.
Démonstration. Faisons une démonstration par récurrence sur l’ordre de G (nombre de
sommets de G ). Si l’ordre de G est égal à un, on a trivialement la propriété. On suppose la
propriété vraie pour tout graphe sans cycle d’ordre n.
Soit G = (X, G) un graphe sans cycle d’ordre n + 1 avec m arc. Soit α = (αi )i∈G un flot de
G. Comme G est sans cycle, le théorème A.1 nous dit que m − n + 1 < 0, ce qui prouve
qu’il existe un sommet j de G contenu dans une seule arête k de G. D’où d’après (2.2) ,
la composante αk est nulle. On en déduit que (αi )i∈G\{k} est un flot de (X\{j}, G\{k}). Il
suffit d’appliquer l’hypothèse de récurrence pour montrer la propriété.
l’espace vectoriel des flots est égal à l’espace vectoriel V (G) engendré par
Théorème 2.5
les cycles du graphe G
Démonstration. On peut supposer G connexe car sinon on travaillera sur les composantes
connexes de G. Si G est connexe, il existe un arbre maximal H = (X, H) de G = (X, G).
Soient les cycles (γ i ) associés à l’arbre pour i ∈ G\H (cf. théorème A.4). Il est clair que les
32 CHAPITRE 2. EXEMPLES DE CLASSE C++
vecteurs (γji ) j∈G de IRG sont des flots. Donc tout vecteur de V (G ) est un flot. Soit (αi )i∈G
un flot, on construit le flot (βj )j∈G suivant
X
βj = αj − αj γji . (2.3)
i∈G\H
Comme par construction βj = 0 pour tout j ∈ G\H ,on en déduit que le vecteur (βj )j∈H est
un flot de H. La proposition précédente et le théorème A.2 i) nous montrent que βj est nul
pour tout j dans H. D’où tout flot est somme de cycles ce qui prouve l’autre inclusion.
Pour construire des relévements des conditions aux limites, ou pour retrouver la pression
nous sommes amenés à introduire les deux problèmes abstrait duaux suivant :
Soit G = (X, G) un graphe connexe composé de n sommets et de m arêtes.
A chaque arc j on associe le vecteur ej = (eji )i∈X de IRX tel que
0
si i n’est pas un sommet de l’arc j ;
j
ei = +1 si l’arc j est de la forme (i, k) (k ∈ X) ; (2.4)
−1 si l’arc j est de la forme (k, i) (k ∈ X).
#include <cassert>
#include <iostream>
using namespace std ;
// cas 1 avec des copie de sommet
// cas 2 sans copie de sommet
return f ;}
// cas 1 void Set( int i,Sommet & a, Sommet & b) numero=i ;extremite[0]=a ;
extremite[1]=b ;
void Set(int i,Sommet & a, Sommet & b) {numero=i ;extremite[0]=&a ; extremite[1]=&b ;}
// pas d’operateurs de copies, c’est une class simple
void MiseAJour() ;
Graphe(int ) ; // un graphe c
Graphe(const char * filename) ; // un graphe lue dans filename
~Graphe()
{
cout « " delete Graphe: nb sommets " « n « " , nb arcs " « m « endl ;
delete [] s ;
delete [] a ;
34 CHAPITRE 2. EXEMPLES DE CLASSE C++
#include <cassert>
#include <iostream>
using namespace std ;
// cas 1 avec des copie de sommet
// cas 2 sans copie de sommet
// cas 1 void Set( int i,Sommet & a, Sommet & b) numero=i ;extremite[0]=a ;
extremite[1]=b ;
void Set(int i,Sommet & a, Sommet & b) {numero=i ;extremite[0]=&a ; extremite[1]=&b ;}
// pas d’operateurs de copies, c’est une class simple
void MiseAJour() ;
Graphe(int ) ; // un graphe c
Graphe(const char * filename) ; // un graphe lue dans filename
~Graphe()
{
cout « " delete Graphe: nb sommets " « n « " , nb arcs " « m « endl ;
delete [] s ;
delete [] a ;
}
la solution
#include "Graphe.hpp"
#include <fstream>
36 CHAPITRE 2. EXEMPLES DE CLASSE C++
int couleur=0 ;
for (int s=0 ;s<G.n ;++s)
G.s[s].couleur = 0 ;
return 0 ;}
2.5. MAILLAGE ET TRIANGULATION 2D 37
class Label {
friend ostream& operator «(ostream& f,const Label & r )
{ f « r.lab ; return f ; }
friend istream& operator »(istream& f, Label & r )
{ f » r.lab ; return f ; }
public:
int lab ;
Label(int r=0):lab(r){}
int onGamma() const { return lab ;}
};
38 CHAPITRE 2. EXEMPLES DE CLASSE C++
Cette classe n’est pas utilisée directement, mais elle servira dans la construction des classes
pour les sommets et les triangles. Il est juste possible de lire, écrire un label, et tester si elle
est nulle.
Listing 2.4 (utilisation de la classe Label)
Label r ;
cout « r ; // écrit r.lab
cin » r ; // lit r.lab
if(r.onGamma()) { ..... } // à faire si la r.lab != 0
Nous pouvons utiliser la classe Vertex pour effectuer les opérations suivantes :
Les trois champs (x,y,lab) d’un sommet sont initialisés par (0.,0.,0)
Remarque 2.3 par défaut, car les constructeurs sans paramètres des classes de base sont
appelés dans ce cas.
R2 Edge(int i) const {
ASSERTION(i>=0 && i <3) ;
return R2(*vertices[(i+1)%3],*vertices[(i+2)%3]) ;} // vecteur arête
opposé au sommet i
private:
Triangle(const Triangle &) ; // interdit la construction par copie
void operator=(const Triangle &) ; // interdit l’affectation par copie
};
Triangle T ;
T.set(v,ia,ib,ic,lab) ; // construction d’un triangle avec les sommets
// v[ia],v[ib],v[ic] et l’étiquette lab
// (v est le tableau des sommets)
Vertex *vertices ;
Triangle *triangles ;
private:
Mesh(const Mesh &) ; // interdit la construction par copie
void operator=(const Mesh &) ; // interdit l’affectation par copie
};
Avant de voir comment utiliser cette classe, quelques détails techniques nécessitent plus
d’explications :
• Pour utiliser les opérateurs qui retournent un numéro, il est fondamental que leur argument
soit un pointeur ou une référence ; sinon, les adresses des objets seront perdues et il ne
sera plus possible de retrouver le numéro du sommet qui est donné par l’adresse mémoire.
• Les tableaux d’une classe sont initialisés par le constructeur par défaut qui est le construc-
teur sans paramètres. Ici, le constructeur par défaut d’un triangle ne fait rien, mais les
constructeurs par défaut des classes de base (ici les classes Label, Vertex) sont ap-
pelés. Par conséquent, les labels de tous les triangles sont initialisées et les trois champs
(x,y,lab) des sommets sont initialisés par (0.,0.,0). Par contre, les pointeurs sur som-
mets sont indéfinis (tout comme l’aire du triangle).
Tous les problèmes d’initialisation sont résolus une fois que le constructeur avec arguments
est appelé. Ce constructeur va lire le fichier .msh contenant la triangulation.
{ // lecture du maillage
int i,i0,i1,i2,ir ;
ifstream f(filename) ;
if( !f) {cerr « "Mesh::Mesh Erreur a l’ouverture - fichier " « filename«endl ;
exit(1) ;}
cout « " Lecture du fichier \"" «filename«"\""« endl ;
f » nv » nt » neb ;
cout « " Nb de sommets " « nv « " " « " Nb de triangles " « nt
« " Nb d’aretes frontiere " « neb « endl ;
assert(f.good() && nt && nv) ;
area=0 ;
assert(triangles && vertices) ;
L’utilisation de la classe Mesh pour gérer les sommets, les triangles et les arêtes frontières
devient maintenant très simple et intuitive.
Algorithmes
int Not_In_Im_F = -1 ;
for (int j=0 ;j<m ;j++)
head_F[j]=Not_In_Im_F ; // initialement, les listes
// des antécédents sont vides
for (int i=0 ;i<n ;i++)
Algorithme 3.1 j=F[i],next_F[i]=head_F[j],head_F[j]=i ; // chaînage amont
Dans certaines applications, il peut être utile de construire la liste des arêtes du maillage,
c’est-à-dire l’ensemble des arêtes de tous les éléments. La difficulté dans ce type de construc-
tion réside dans la manière d’éviter – ou d’éliminer – les doublons (le plus souvent une arête
appartient à deux triangles).
Nous allons proposer deux algorithmes pour déterminer la liste des arêtes. Dans les deux
cas, nous utiliserons le fait que les arêtes sont des segments de droite et sont donc définies
complètement par la donnée des numéros de leurs deux sommets. On stockera donc les arêtes
dans un tableau arete[nbex][2] où nbex est un majorant du nombre total d’arêtes. On
pourra prendre grossièrement nbex = 3*nt ou bien utiliser la formule d’Euler en 2D
La première méthode est la plus simple : on compare les arêtes de chaque élément du maillage
avec la liste de toutes les arêtes déjà répertoriées. Si l’arête était déjà connue on l’ignore,
sinon on l’ajoute à la liste. Le nombre d’opérations est nbe ∗ (nbe + 1)/2.
Avant de donner le première algorithme, indiquons qu’on utilisera souvent une petite routine
qui échange deux paramètres :
template<class T> inline void Exchange (T& a,T& b) {T c=a ;a=b ;b=c ;}
Cet algorithme trivial est bien trop cher dès que le maillage a plus de 500 sommets (plus
de 1.000.000 opérations). Pour le rendre de l’ordre du nombre d’arêtes, on va remplacer la
boucle sur l’ensemble des arêtes construites par une boucle sur l’ensemble des arêtes ayant
le même plus petit numéro de sommet. Dans un maillage raisonnable, le nombre d’arêtes
incidentes sur un sommet est petit, disons de l’ordre de six, le gain est donc important : nous
obtiendrons ainsi un algorithme en 3 × nt.
Pour mettre cette idée en oeuvre, nous allons utiliser l’algorithme de parcours de l’image
réciproque de la fonction qui à une arête associe le plus petit numéro de ses sommets. Autre-
ment dit, avec les notations de la section précédente, l’image par l’application F d’une arête
sera le minimum des numéros de ses deux sommets. De plus, la construction et l’utilisation
des listes, c’est-à-dire les étapes 1 et 2 de l’algorithme 3.1 seront simultanées.
48 CHAPITRE 3. ALGORITHMES
Construction rapide des arêtes d’un maillage Th
Voici donc l’algorithme pour construire l’ensemble des triangles ayant un sommet en com-
mun :
Construction de l’ensemble des triangles ayant un sommet commun
Préparation :
int end_list=-1,
int *head_s = new int[Th.nv] ;
int *next_p = new int[Th.nt*3] ;
int i,j,k,p ;
for (i=0 ;i<Th.nv ;i++)
head_s[i] = end_list ;
for (k=0 ;k<Th.nt ;k++) // forall triangles
for(j=0 ;j<3 ;j++) {
Algorithme 3.4 p = 3*k+j ;
i = Th(k,j) ;
next_p[p]=head_s[i] ;
head_s[i]= p ;}
1
Noter au passage que c’est ainsi que C++ traite les tableaux à double entrée : un tableau T[n][m] est
stocké comme un tableau à simple entrée de taille n*m dans lequel l’élément T[k][j] est repéré par l’indice
p(k,j) = k*m+j.
50 CHAPITRE 3. ALGORITHMES
class MatriceMorseSymetrique {
int n,nbcoef ; // dimension de la matrice et nombre de coefficients non
nuls
int *ligne,* colonne ;
double *a ;
MatriceMorseSymetrique(Maillage & Th) ; // constructeur
}
n=10,nbcoef=20,
ligne[-1:9] = {-1,0,1,3,6,8,11,14,17,19,20} ;
colonne[21] ={0, 1, 1,2, 1,2,3, 2,4, 3,4,5, 2,4,6,
5,6,7, 4,8, 9} ;
3.1. CHAÎNES ET CHAÎNAGES 51
Nous allons maintenant construire la structure morse d’une matrice symétrique à partir
de la donnée d’un maillage d’éléments finis P1 . Pour construire la ligne i de la matrice,
il faut trouver tous les sommets j tels que i, j appartiennent à un même triangle. Ainsi,
pour un noeud donné i, il s’agit de lister les sommets appartenant aux triangles contenant
i. Le premier ingrédient de la méthode sera donc d’utiliser l’algorithme 3.4 pour parcourir
l’ensemble des triangles contenant i. Mais il reste une difficulté : il faut éviter les doublons.
Nous allons pour cela utiliser une autre technique classique de programmation qui consiste à
“colorier” les coefficients déjà répertoriés : pour chaque sommet i (boucle externe), on effectue
une boucle interne sur les triangles contenant i puis on balaie les sommets j de ces triangles
en les coloriant pour éviter de compter plusieurs fois les coefficients aij correspondant. Si
on n’utilise qu’une couleur, on doit “démarquer” les sommets avant de passer à un autre i.
Pour éviter cela, on va utiliser plusieurs couleurs, et on changera de couleur de marquage à
chaque fois qu’on changera de sommet i dans la boucle externe.
52 CHAPITRE 3. ALGORITHMES
Construction de la structure d’une matrice morse
#include "MatMorse.hpp"
MatriceMorseSymetrique::MatriceMorseSymetrique(const Mesh &
Th)
{
int color=0, * mark ;
int i,j,jt,k,p,t ;
n = m = Th.nv ;
ligne.set(new int [n+1],n+1) ;
mark = new int [n] ;
// construction optimisee de l’image réciproque de Th(k,j)
int end_list=-1,*head_s,*next_p ;
head_s = new int [Th.nv] ;
next_p = new int [Th.nt*3] ;
p=0 ;
for (i=0 ;i<Th.nv ;i++)
head_s[i] = end_list ;
for (k=0 ;k<Th.nt ;k++)
for(j=0 ;j<3 ;j++)
next_p[p]=head_s[i=Th(k,j)], head_s[i]=p++ ;
Remarque : Si vous avez tout compris dans ces algorithmes, vous pouvez vous attaquer à la
plupart des problèmes de programmation.
template<class T>
void HeapSort(T *c,long n) {
c- ; // because fortran version aray begin at 1 in the routine
register long m,j,r,i ;
register T crit ;
if( n <= 1) return ;
m = n/2 + 1 ;
r = n;
while (1) {
if(m <= 1 ) {
crit = c[r] ;
c[r-] = c[1] ;
if ( r == 1 ) { c[1]=crit ; return ;}
} else crit = c[-m] ;
j=m ;
while (1) {
i=j ;
j=2*j ;
if (j>r) {c[i]=crit ;break ;}
if ((j<r) && c[j] < c[j+1])) j++ ;
if (crit < c[j]) c[i]=c[j] ;
else {c[i]=crit ;break ;}
}
}
}
Partant du triangle K,
Pour les 3 arêtes (ai , bi ), i = 0, 1, 2 du triangle K, tournant dans le sens
trigonométrique, calculer l’aire des 3 triangles (ai , bi , p) si le trois aires
Algorithme 3.6
sont positives alors p ∈ K (stop), sinon nous choisirons comme nouveau
triangle K l’un des triangles adjacent à l’une des arête associée à une aire
négative (les ambiguïtés sont levées aléatoirement).
54 CHAPITRE 3. ALGORITHMES
Maintenant il faut initialiser cette algorithme, il faut une méthode pour trouver, rapidement
un triangle proche d’un point donné.
Il y a deux méthodes :
– Si le point est arbitraire : il suffit de construire un arbre quaternaire, et mettre tous les
points du maillage dans cette arbre, et associe a chaque sommet du maillage un triangle
le contenant.
– Si les points recherche sont dans un autre maillage, alors l’on peut remarquer que les som-
mets d’un triangle (ou élément) sont généralement proche, et il suffit utiliser un parcours
récurcif d’un maillage
Soit B0 une boite carre contenant tous les points du maillage. On applique
la procédure récursive suivante à B0 .
Soit Bα la boite indicée par α qui est une chaîne de 0,1,2 ou 3 (exemple
α = 011231) la longueur de la chaîne est note l(α).
procedure appliqué à la boite Bα :
Algorithme 3.7
– la boite Bα contient moins de 5 points, on les stockes dans la boite et
on a fini,
– sinon on découpe la boite Bα en 4 sous boites égale noté
Bα0 , Bα1 , Bα2 , Bα3 (on stocke les 4 pointeurs vers ces boites dans la
boite), et on applique la procédure au 4 nouvelles boites.
Nous nous proposons de présenter dans ce chapitre les notions théoriques et pratiques né-
cessaires pour écrire un générateur de maillage (mailleur) bidimensionnel de type Delaunay-
Voronoï, simple et rapide.
4.1.2 Introduction
Commençons par définir la notion de maillage simplexial.
De plus, T0,h désignera l’ensemble des sommets de Td,h et T1,h l’ensemble des arêtes de Td,h et
l’ensemble de faces serat Td−1,h . Le bord ∂Td,h du maillage Td,h est défini comme l’ensemble
des faces qui ont la propriété d’appartenir à un unique d-simplex de Td,h . Par conséquent,
∂Td,h est un maillage du bord ∂Oh de Oh . Par abus de langage, nous confondrons une arête
d’extrémités (a, b) et le segment ouvert ]a, b[, ou fermé [a, b].
Théorème 4.1 Pour tout ouvert polygonal Oh de IR2 , il existe un maillage de cet ouvert
sans sommet interne.
L’ensemble S des sommets de ce maillage sont les points anguleux du bord ∂Oh .
La démonstration de ce théorème utilise le lemme suivant :
Dans un ouvert polygonal O connexe qui n’est pas un triangle, il existe
Lemme 4.1
deux points anguleux α, β tels que ]α, β[⊂ O.
4.1. BASES THÉORIQUES 57
Preuve : il existe trois points anguleux a, b, c consécutifs du bord ∂O tel que l’ouvert soit
c < π, et notons T = C({a, b, c}) le
localement du côté droite de ]a, b[ et tel que l’angle abc
◦
triangle fermé formé de a, b, c et T le triangle ouvert.
c
c
c0
b a c0
b
Il y a deux cas :
◦
– T ∩S = ∅ alors ]a, c[⊂ Oh et il suffit de pendre ]α, β[=]a, c[.
◦
– Sinon, soit C le convexifié de cette intersection T ∩S. Par construction, ce convexifié C est
◦
inclus dans le triangle ouvert T . Soit le point anguleux c0 du bord du convexifié le plus
proche de b ; il sera tel que ]b, c0 [⊂ Oh , et il suffit de prendre ]α, β[=]b, c0 [.
2 3
1
4
6
5
Figure 4.2 – Diagramme de Voronoï : les ensembles des points de IR2 plus proches de xi que
des autres points xj .
2 3
1
4
6
5
Figure 4.3 – Maillage de Delaunay : nous relions deux points xi et xj si les diagrammes V i
et V j ont un segment en commun.
60 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
Ces polygones, qui sont des intersections finies de demi-espaces, sont convexes. De plus, les
k
sommets v k de ces polygones sont à égale distance des points {xij /j = 1, ..., nk } de S, où le
nombre nk est généralement égal (le cas standard) ou supérieur à 3. À chacun de ces sommets
k
v k , nous pouvons associer le polygone convexe construit avec les points {xij , j = 1, ..., nk }
en tournant dans le sens trigonométrique. Ce maillage est généralement formé de triangles,
sauf si il y a des points cocycliques (voir figure 4.3 où nk > 3).
2 3
1
4
8
9
6
5
Figure 4.4 – Propriété de la boule vide : le maillage de Delaunay et les cercles circonscrits
aux triangles.
Un maillage Td,h de Delaunay est tel que : pour tout triangle T du maillage,
le disque ouvert D(T ) correspondant au cercle circonscrit à T ne contient
aucun sommet (propriété de la boule vide). Soit, formellement :
Théorème 4.2
D(T ) ∩ T0,h = ∅. (4.8)
Preuve : Montrons par l’absurde que si le maillage est de Delaunay alors il vérifie la propriété
de la boule vide. Soit xp un point de S dans la boule ouvert de d’élément K = {xk1 , ...} de
centre c, on a c ∈ V k1 et ||c − xp || < ||c − xk1 || ce qui est en contradiction avec la définition
des V k
Réciproquement : soit un maillage vérifiant (4.8).
Commençons par montrer la propriété (a) suivante : toutes les arêtes (xi , xj ) du maillage
sont telles que l’intersection V i ∩ V j contient les centres des cercles circonscrits aux triangles
contenant ]xi , xj [.
Soit une arête (xi , xj ) ; cette arête appartient au moins à un triangle T . Notons c le centre
du cercle circonscrit à T et montrons par l’absurde que c appartient à V i et à V j .
Si c n’est pas dans V i , il existe un xk tel que ||c − xk || < ||c − xi || d’après (4.7), ce qui
implique que xk est dans le cercle circonscrit à T , d’où la contradiction avec l’hypothèse.
Donc, c est dans V i et il y en va de même pour V j , ce qui démontre la propriété (a).
Il reste deux cas à étudier : l’arête est frontière ou est interne.
– si l’arête (xi , xj ) est frontière, comme le domaine est convexe, il existe un point c0 sur la
médiatrice de xi et xj suffisamment loin du domaine dans l’intersection de V i et V j et tel
que c0 ne soit pas un centre de cercle circonscrit de triangle,
– si l’arête (xi , xj ) est interne, elle est contenue dans un autre triangle T 0 et V i ∩ V j contient
aussi c0 , le centre du cercle circonscrit à T 0 .
Dans tous les cas, c et c0 sont dans V i ∩ V j et comme V i et V j sont convexes, l’intersection
V i ∩ V j est aussi convexe. Donc le segment [c, c0 ] est inclus dans V i ∩ V j .
Maintenant, il faut étudier les deux cas : c = c0 ou c 6= c0 .
– si le segment [c, c0 ] n’est pas réduit à un point, alors l’arête (xi , xj ) est dans le maillage
Delaunay ;
– si le segment [c, c0 ] est réduit à un point, alors nous sommes dans cas où l’arête (xi , xj ) est
interne et c0 est le centre de D(T 0 ). Les deux triangles T, T 0 contenant l’arête (xi , xj ) sont
cocycliques et l’arête n’existe pas dans le maillage de Delaunay strict.
Pour finir, les arêtes qui ne sont pas dans le maillage de Delaunay sont entre des triangles
cocycliques. Il suffit de remarquer que les classes d’équivalence des triangles cocycliques d’un
même cercle forment un maillage triangulaire de polygones du maillage de Delaunay strict.
Cette démonstration est encore valide en dimension d quelconque, en remplaçant les arêtes
par des hyperfaces qui sont de codimension 1.
Il est possible d’obtenir un maillage de Delaunay strict en changeant la
propriété de la boule vide définie en (4.8) par la propriété stricte de la
boule vide, définie comme suit :
B. Delaunay a montré que l’on pouvait réduire cette propriété au seul motif formé par deux
triangles adjacents.
62 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
[Delaunay] Si le maillage Td,h d’un domaine convexe est tel que tout sous-
maillage formé de deux triangles adjacents par une arête vérifie la pro-
Lemme 4.2
priété de la boule vide, alors le maillage Td,h vérifie la propriété globale de
la boule vide et il est de Delaunay.
D1 ∩ P + ⊂ D2 et D2 ∩ P − ⊂ D1 ,
ou
D2 ∩ P + ⊂ D1 et D1 ∩ P − ⊂ D2 ,
où Di est le disque associé au cercle Ci , pour i = 1, 2.
C1 C2
P−
D P+
Preuve du lemme de Delaunay: nous faisons une démonstration par l’absurde qui est quelque
peu technique.
Supposons que le maillage ne vérifie pas la propriété de la boule vide. Alors il existe un
triangle T ∈ Td,h et un point xi ∈ T0,h , tel que xi ∈ D(T ).
Soit a un point interne au triangle T tel que Taj ∩]a, xi [= ∅ (il suffit de déplacer un petit
peu le point a si ce n’est pas le cas). Le segment ]a, xi [ est inclus dans le domaine car il est
convexe. Nous allons lui associer l’ensemble des triangles Taj , j = 0, .., ka qui intersectent le
segment]a, xi [. Cette ensemble est une une chaîne de triangles Taj pour j = 0, .., ka c’est à dire
que les triangles Taj et Taj+1 sont adjacents par une arête à cause de l’hypothèse Taj ∩]a, xi [= ∅.
Nous pouvons toujours choisir le couple (T, xi ) tel que xi ∈ D(T ) et xi 6∈ T tel que le cardinal
k de la chaîne soit minimal. Le lemme se résume à montrer que k = 1.
Soit xi1 (resp. xi0 ) le sommet de T 1 (resp. T 0 ) opposé à l’arête T 0 ∪ T 1 .
– Si xi0 est dans D(T 1 ) alors k = 1 (la chaîne est T 1 , T 0 ).
– Si xi1 est dans D(T 0 ) alors k = 1 (la chaîne est T 0 , T 1 )
4.1. BASES THÉORIQUES 63
– Sinon, les deux points xi0 et xi1 sont de part et d’autre de la droite D définie par l’inter-
section des deux triangles T 0 et T 1 , qui est aussi la droite d’intersection des deux cercles
C(T 0 ) et C(T 1 ). Cette droite D définit deux demi-plans P 0 et P 1 qui contiennent, respec-
tivement, les points xi0 et xi1 . Pour finir, il suffit d’utiliser l’alternative 1 avec les cercles
C(T 0 ) et C(T 1 ). Comme xi0 n’est dans D(T 1 ), alors D(T 0 ) est inclus dans D(T 1 ) ∩ P 0 .
Mais, xi ∈ C(T 0 ) par hypothèse, et comme xi n’est pas dans le demi-plan P 1 car le segment
]a, xi [ coupe la droite D, on a xi ∈ C(T 0 ) ⊂ C(T 1 ), ce qui impliquerait que le cardinal de
la nouvelle chaîne est k − 1 et non k d’où la contradiction avec l’hypothèse de k minimal.
Pour finir, il suffit de remarque que pour tout d-simplex (q 0 , ..., q d ) de l’intersection du graphe
de la fonction f (x) = ||x||2 avec l’hyperplan affine de IRd+1 passant par Ff (q 0 ), ..., Ff (q d ) est
l’image du la sphere inscrite de (q O , ..., q d ) par Ff (à demontrer en exercice en utilisant la
puissance d’un point par un cercle) et donc on a (4.10) si et seulement si b est dans la sphère
inscrit de Ta , ce qui acheve la démonstration. Ceci est equivalent aussi le d + 1 simplex
(Ff (p1 ), ..., Ff (pd ), Ff (a), Ff (b)) est positif.
Nous avons retrouver la formule [George,Borouchaki-1997, equation (1.13)] qui décrit la boule
circonscrite au d-simplex positif (q 0 , . . . , q d ). le point b est dans la boule si et seulement si
q0 . . . q d
b 1
1 1
.. .. ..
. ... . .
det qd 0 d
bd ≥ 0 (4.11)
0 2 ... qd
||q || . . . ||q 0 ||2 ||b||2
1 ... 1 1
64 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
En fait, nous avons montré que nous pouvons réduire cette propriété au seul motif formé
par deux triangles adjacents. De plus, comme un quadrilatère non-convexe maillé en deux
triangles vérifie la propriété de la boule vide, il suffit que cette propriété soit vérifiée pour
toutes les paires de triangles adjacents formant un quadrilatère convexe.
b b
2 2
1 1
a a
Figure 4.6 – Échange de diagonale d’un quadrilatère convexe selon le critère de la boule
vide.
Nous ferons un échange de diagonale [sa , sb ] dans un quadrilatère convexe de coordonnées
s1 , sa , s2 , sb (tournant dans le sens trigonométrique) si le critère de la boule vide n’est pas
vérifié comme dans la figure 4.6.
Le critère de la boule vide dans un quadrilatère convexe s1 , sa , s2 , sb en
[s1 , s2 ] est équivalent à l’inégalité angulaire (propriété des angles inscrits
Lemme 4.3 dans un cercle) :
s\ \
1 sa sb < s 1 s2 sb .
Preuve : comme la cotangente est une fonction strictement décroissante entre ]0, π[ , il suffit
de vérifier :
(s1 − sa , sb − sa ) (s1 − s2 , sb − s2 )
cotg(s\
1 sa sb ) = > = cotg(s\
1 s2 sb ),
det(s1 − sa , sb − sa ) det(s1 − s2 , sb − s2 )
où (., .) est le produit scalaire de IR2 et det(., .) est le déterminant de la matrice formée avec
les deux vecteurs de IR2 .
Ou encore, si l’on veut supprimer les divisions, on peut utiliser les aires des triangles aire1ab
et aire12b . Comme
det(s1 − sa , sb − sa ) = 2 × aire1ab et det(s1 − s2 , sb − s2 ) = 2 × aire12b ,
le critère d’échange de diagonale optimisé est
aire12b (s1 − sa , sb − sa ) > aire1ab (s1 − s2 , sb − s2 ). (4.12)
Maintenant, nous avons théoriquement les moyens de construire un maillage passant par les
points donnés, mais généralement nous ne disposons que des points de la frontière ; il va
falloir donc générer ultérieurement les points internes du maillage.
Par construction, le maillage de Delaunay n’impose rien sur les arêtes. Il peut donc arriver
que ce maillage ne respecte pas la discrétisation de la frontière, comme nous pouvons le
remarquer sur la figure 4.8. Pour éviter ce type de maillage, nous pouvons suivre deux
pistes :
4.1. BASES THÉORIQUES 65
a
b
Nous dirons qu’une arête (a, b) coupe une autre arête (a0 , b0 ) si ]a, b[∩]a0 , b0 [= p 6= ∅.
Théorème 4.4 Alors, il existe une suite finie d’échanges de diagonale de quadrilatère
ab
convexe, qui permet d’obtenir un nouveau maillage Td,h contenant l’arête
(a, b).
Nous avons de plus la propriété de localité optimale suivante : toute arête
du maillage Td,h ne coupant pas ]a, b[ est encore une arête du nouveau
ab
maillage Td,h .
Preuve : nous allons faire un démonstration par récurrence sur le nombre mab (Td,h ) d’arêtes
du maillage Td,h coupant l’arête (a, b).
Soit T i , pour i = 0, ..., mab (Td,h ), la liste des triangles coupant ]a, b[ tel que les traces des T i
sur ]a, b[ aillent de a à b pour i = 0, ..., n.
i−1 i
Comme ]a, b[∩T0,h = ∅, l’intersection de T et T est une arête notée [αi , βj ] qui vérifie
def i−1 i + −
[αi , βj ] = T ∩T , avec αi ∈ Pab et βi ∈ Pab , (4.14)
+ −
où Pab et Pab sont les deux demi-plans ouverts, définis par la droite passant par a et b.
r
Nous nous placerons dans le maillage restreint Td,ha,b formé seulement de triangles T i pour
r
i = 0, ..., mab (Td,h ) = mab (Td,ha,b ) pour assurer le propriété de localité. De plus, le nombre de
a,b r
triangles Ntab de Td,h est égal à mab (Td,ha,b ) + 1.
rab
• Si mab (Td,h ) = 1, on fait l’observation que le quadrilatère formé par les deux triangles
contenant l’unique arête coupant (a, b) est convexe, et donc il suffit d’échanger les arêtes
du quadrilatère.
• Sinon, supposons vraie la propriété pour toutes les arêtes (a, b) vérifiant (4.13) et telles
rab
que mab (Td,h ) < n et ceci pour tous les maillages possibles.
r
Soit une arête (a, b) vérifiant (4.13) et telle que mab (Td,ha,b ) = n. Soit αi+ le sommet αi pour
i = 1, ..., n le plus proche du segment [a, b]. Nous remarquerons que les deux inclusions
suivantes sont satisfaites :
◦ ◦
z }| { z }| {
i+ −1 n
[ i [ i
]a, αi+ [⊂ T et ]αi+ , b[⊂ T . (4.15)
i=0 i=i+
Les deux arêtes ]a, αi+ [ et ]αi+ , b[ vérifient les hypothèses de récurrence, donc nous pouvons
r
les forcer par échange de diagonales, car elles ont des supports disjoints. Nommons Td,ha,b+ le
r
maillage obtenu après forçage de ces deux arêtes. Le nombre de triangles de Td,ha,b+ est égal
r
à n + 1 et, par conséquent, mab (Td,ha,b+ ) ≤ n.
Il nous reste à analyser les cas suivants :
r
• si mab (Td,ha,b+ ) < n, nous appliquons l’hypothèse de récurrence et la démonstration est
finie ;
4.1. BASES THÉORIQUES 67
r r
• si mab (Td,ha,b+ ) = n , nous allons forcer (a, b) dans le maillage Td,ha,b+ et utiliser la même
r
méthode. Les T i seront maintenant les triangles de Td,ha,b+ et les α+ en β + seront définis par
(4.14). Nous avons traité le demi-plan supérieur, traitons maintenant la partie inférieure.
Soit i− l’indice tel que le sommet βi++ soit le plus proche du segment ]a, b[ des sommets
βi+ . Nous forçons les deux arêtes ]a, βi− [ et ]βi− , b[ en utilisant les mêmes arguments que
précédemment.
Nous avons donc un maillage local du quadrilatère a, αi+ , b, βi+− qui contient l’arête ]a, b[ et
qui ne contient aucun autre point du maillage. Il est donc formé de deux triangles T, T 0 tels
0
que ]a, b[⊂ T ∪ T , ce qui nous permet d’utiliser une dernière fois l’hypothèse de récurrence
(n = 1) pour finir la démonstration.
0 0
Soit deux maillages Td,h et Td,h ayant les mêmes sommets (T0,h = T0,h ) et le
0
même maillage du bord ∂Td,h = ∂Td,h . Alors, il existe une suite d’échanges
Théorème 4.5
de diagonales de quadrilatères convexes qui permet de passer du maillage
0
Td,h au maillage Td,h .
0
Preuve : il suffit de forcer toutes les arêtes du maillage Td,h dans Td,h .
Pour finir cette section, donnons un algorithme de forçage d’arête très simple (il est dû à
Borouchaki [George,Borouchaki-1997, page 99]).
Si l’arête (sa , sb ) n’est pas une arête du maillage de Delaunay, nous retour-
nons les diagonales (sα , sβ ) des quadrangles convexes sα , s1 , sβ , s2 formés
de deux triangles dont la diagonale ]sα , sβ [ coupe ]sa , sb [ en utilisant les
Algorithme 4.1 critères suivants :
– si l’arête ]s1 , s2 [ ne coupe pas ]sa , sb [, alors on fait l’échange de diago-
nale ;
– si l’arête ]s1 , s2 [ coupe ]sa , sb [, on fait l’échange de diagonale de manière
aléatoire.
Comme il existe une solution au problème, le fait de faire des échanges de diagonales de
manière aléatoire va permettre de converger, car, statistiquement, tous les maillages possibles
sont parcourus et ils sont en nombre fini.
si nous voulons que la longueur dans le plan tangent en x d’un segment donne le nombre de
pas, il suffit d’introduire la longueur remmanienne suivante (on divise par h(x)) :
Z 1 p 0
h def (γ (t), γ 0 (t))
l = dt (4.18)
t=0 h(γ(t))
Les deux méthodes de construction de N (a, b) sont équivalentes dans le cas où h(x) est
indépendant de x. Regardons ce qui change dans le cas d’une fonction h affine sur le segment
[0, l]. Si
h(t) = h0 + (hl − h0 )t, t ∈ [0, 1],
nous obtenons
Z t −1
(hl − h0 ) 2 −1
N1 (0, t) = [h0 + (hl − h0 )x]dx = (h0 t + t) , (4.20)
0 2
1 hl − h0
N2 (0, t) = ln 1 + t . (4.21)
hl − h0 h0
Par construction, N2 vérifie la relation de Chasle :
où b est un point entre a et b, alors que N1 ne vérifie clairement pas cette relation. C’est la
raison pour laquelle nous préférons l’expression (4.21) pour la fonction N . De plus, si nous
voulons avoir un nombre optimal de points tels que N2 (0, t) = i, ∀i ∈ 1, 2, .. dans (4.21), ces
l −h0 )
points seront alors distribués suivant une suite géométrique de raison ln(h
hl −h0
.
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
namespace bamg {
using namespace std ;
public:
R x,y ;
P2 () :x(0),y(0) {} ;
P2 (R a,R b) :x(a),y(b) {}
P2<R,RR> operator+(const P2<R,RR> & cc) const
{return P2<R,RR>(x+cc.x,y+cc.y) ;}
P2<R,RR> operator-(const P2<R,RR> & cc) const
{return P2<R,RR>(x-cc.x,y-cc.y) ;}
P2<R,RR> operator-() const{return P2<R,RR>(-x,-y) ;}
RR operator,(const P2<R,RR> & cc) const // produit scalaire
{return (RR) x* (RR) cc.x+(RR) y* (RR) cc.y ;}
P2<R,RR> operator*(R cc) const
{return P2<R,RR>(x*cc,y*cc) ;}
P2<R,RR> operator/(R cc) const
{return P2<R,RR>(x/cc,y/cc) ;}
P2<R,RR> operator+=(const P2<R,RR> & cc)
{x += cc.x ;y += cc.y ;return *this ;}
P2<R,RR> operator/=(const R r)
{x /= r ;y /= r ;return *this ;}
P2<R,RR> operator*=(const R r)
{x *= r ;y *= r ;return *this ;}
P2<R,RR> operator-=(const P2<R,RR> & cc)
{x -= cc.x ;y -= cc.y ;return *this ;}
};
Il nous faut choisir les classes que nous allons utiliser pour programmer le générateur de
maillage. Premièrement, il faut décider comment définir un maillage. Il y a clairement deux
possibilités : une liste de triangles ou une liste d’arêtes. Les deux choix sont valables, mais
ici nous ne traiterons que le cas d’une liste de triangles.
Ensuite, pour construire le maillage, la partie la plus délicate est de définir les objets infor-
matiques qui nous permettrons d’implémenter les algorithmes théoriques de manière simple
et efficace.
Nous choisissons de définir :
• un sommet par :
– les coordonnées réelles et entières (pour éviter les erreurs d’arrondi),
– la taille de la maille associée h,
– le numéro de label associé,
72 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
#include <cassert>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <ctime>
#include "R2.hpp"
namespace bamg {
using namespace std ;
// /////////////////////////////////////////////////////////////////////////
class Vertex {public:
I2 i ; // allow to use i.x, and i.y in long int
R2 r ; // allow to use r.x, and r.y in double
R h; // Mesh size
int Label ;
// /////////////////////////////////////////////////////////////////////////
class TriangleAdjacent {
public:
Triangle * t ; // le triangle
int a ; // le numéro de l’arête
// ///////////////////////////////////////////////////////////////////////
class Triangle {
friend class TriangleAdjacent ;
private: // les arêtes sont opposées à un sommet
Vertex * ns [3] ; // les 3 sommets
Triangle * at [3] ; // nu triangle adjacent
short aa[3] ; // les no des arêtes dans le triangle (mod 4)
// on utilise aussi aa[i] pour marquer l’arête i (i=0,1,2)
// si (aa[i] & 4 ) => arête bloquée (lock) marque de type 0
// si (aa[i] & 2n+2 => arête marquée de type n (n=0..7)
// la marque de type 1 était pour déja basculé (markunswap)
// (aa[i] & 1020 ) l’ensemble des marques
// 1020 = 1111111100 (en binaire)
// ----------------------------------
public:
Icoor2 det ; // det. du triangle (2 fois l’aire des coor. entières)
Triangle() {}
inline void Set(const Triangle &,const Triangles &,Triangles &) ;
inline int In(Vertex *v) const
{ return ns[0]==v || ns[1]==v || ns[2]==v ;}
void SetAdjAdj(short a)
{ a &= 3 ;
Triangle *tt=at[a] ;
aa [a] &= 1015 ; // supprime les marques sauf « swap »
// (1015 == 1111110111 en binaire)
register short aatt = aa[a] & 3 ;
if(tt){
tt->at[aatt]=this ;
tt->aa[aatt]=a + (aa[a] & 1020 ) ;}} // copie toutes les
marques
void SetTriangleContainingTheVertex()
{ if (ns[0]) (ns[0]->t=this,ns[0]->vint=0) ;
if (ns[1]) (ns[1]->t=this,ns[1]->vint=1) ;
if (ns[2]) (ns[2]->t=this,ns[2]->vint=2) ;}
// ///////////////////////////////////////////////////////////////////////
class Triangles {
public:
int nbvx,nbtx ; // nombre max de sommets, de triangles
R2 pmin,pmax ; // extrema
R coefIcoor ; // coef pour les coor. entière
Triangle * triangles ;
// end of variable
Triangles(int i) ; // constructeur
~Triangles() ;
void SetIntCoor() ; // construction des coor. entières
void RandomInit() ; // construction des sommets aléatoirement
// sauce C++
const Vertex & operator() (int i) const
{ return vertices[i] ;} ;
Vertex & operator()(int i)
{ return vertices[i] ;} ;
const Triangle & operator[] (int i) const
{ return triangles[i] ;} ;
Triangle & operator[](int i)
{ return triangles[i] ;} ;
// transformation des coordonnées ...
I2 toI2(const R2 & P) const {
return I2( (Icoor1) (coefIcoor*(P.x-pmin.x))
,(Icoor1) (coefIcoor*(P.y-pmin.y)) ) ;}
void ReMakeTriangleContainingTheVertex() ;
4.3. PROGRAMME C++ 77
// ///////////////////////////////////////////////////////////////////////
// Construit l’ Adjacent
inline TriangleAdjacent TriangleAdjacent::Adj() const
{ return t->Adj(a) ;}
// sommet de l’arête
inline Vertex * TriangleAdjacent::EdgeVertex(const int & i) const
{return t->ns[VerticesOfTriangularEdge[a][i]] ; }
// det du triangle
inline Icoor2 & TriangleAdjacent::det() const
{ return t->det ;}
// Construit l’adjacent
inline TriangleAdjacent Adj(const TriangleAdjacent & a)
{ return a.Adj() ;}
// Optimisation de Delaunay
int inline Vertex::DelaunayOptim(int i)
{
int ret=0 ;
if ( t && (vint >= 0 ) && (vint <3) )
{
ret = t->DelaunayOptim(vint) ;
if( !i)
{
t =0 ; // pour supprimer les optimisation future
vint= 0 ; }
}
return ret ;
}
Les algorithmes théoriques présentés dans ce chapitre sont implémentés dans le fichier
Mesh.cpp sous la forme des fonctions suivantes :
swap la fonction qui fait un échange de diagonale sans aucun test ;
ForceEdge la fonction qui force l’arête [a, b] dans le maillage ;
SwapForForcingEdge la fonction qui fait un échange diagonale pour forcer une arête ;
Triangle::DelaunaySwap la fonction qui fait l’échange de diagonale pour rendre le
quadrilatère de Delaunay ;
Triangles::Add la fonction qui ajoute un sommet s à un triangle (qui peut être dégé-
néré) ;
4.3. PROGRAMME C++ 79
#include "Mesh.hpp"
namespace bamg {
int verbosity=10 ; // niveau d’impression
// /////////////////////////////////////////////////////////////////////////
// \ | / \ t1 /
// \ | / as2 \ a1/
// \ | / \ /
// sa sa
// -----------------------------------------
int as1 = NextEdge[a1] ;
int as2 = NextEdge[a2] ;
// int ap1 = PreviousEdge[a1] ;
// int ap2 = PreviousEdge[a2] ;
(*t1)(VerticesOfTriangularEdge[a1][1]) = s2 ; // avant sb
(*t2)(VerticesOfTriangularEdge[a2][1]) = s1 ; // avant sa
// mise a jour des 2 adjacences externes
TriangleAdjacent taas1 = t1->Adj(as1), taas2 = t2->Adj(as2),
tas1(t1,as1), tas2(t2,as2), ta1(t1,a1), ta2(t2,a2) ;
// externe haut gauche
taas1.SetAdj2(ta2,taas1.GetAllFlag_UnSwap()) ;
// externe bas droite
taas2.SetAdj2(ta1, taas2.GetAllFlag_UnSwap()) ;
// interne
tas1.SetAdj2(tas2) ;
t1->det = det1 ;
t2->det = det2 ;
t1->SetTriangleContainingTheVertex() ;
t2->SetTriangleContainingTheVertex() ;
} // end swap
// /////////////////////////////////////////////////////////////////////////
assert ( (detsa < 0) && (detsb >0) ) ; // [a,b] coupe la droite va,bb
Icoor2 ndet1 = bamg::det(s1,sa,s2) ;
Icoor2 ndet2 = detT - ndet1 ;
if (ToSwap) NbSwap++,
bamg::swap(t1,a1,t2,a2,&s1,&s2,ndet1,ndet2) ;
int ret=1 ;
if (ToSwap)
if (dets2 < 0) { // haut
dets1 = (ToSwap ? dets1 : detsa) ;
detsa = dets2 ;
tt1 = Previous(tt2) ;}
else if (dets2 > 0){ // bas
dets1 = (ToSwap ? dets1 : detsb) ;
detsb = dets2 ;
if( !ToSwap) tt1 = Next(tt2) ;
}
else { // on a enfin fini le forçage
tt1 = Next(tt2) ;
ret =0 ;}
}
return ret ;
}
82 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
// /////////////////////////////////////////////////////////////////////////
int NbSwap =0 ;
assert(a.t && b.t) ; // les 2 sommets sont dans le maillage
int k=0 ;
taret=TriangleAdjacent(0,0) ; // erreur
TriangleAdjacent tta(a.t,EdgesVertexTriangle[a.vint][0]) ;
Vertex *v1, *v2 = tta.EdgeVertex(0),*vbegin =v2 ;
// on tourne autour du sommet a dans le sens trigo.
tta.SetLock() ;
taret=tta ;
a.DelaunayOptim(1) ;
b.DelaunayOptim(1) ;
return NbSwap ;
}
// /////////////////////////////////////////////////////////////////////////
Vertex *sa=t1->ns[VerticesOfTriangularEdge[a1][0]] ;
Vertex *sb=t1->ns[VerticesOfTriangularEdge[a1][1]] ;
Vertex *s1=t1->ns[OppositeVertex[a1]] ;
Vertex *s2=t2->ns[OppositeVertex[a2]] ;
int OnSwap = 0 ;
// si 2 triangles infini (bord) => detT = -2 ;
if (sa == 0) { // les deux triangles sont frontières
det2=bamg::det(s2->i,sb->i,s1->i) ;
OnSwap = det2 >0 ;}
else if (sb == 0) { // les deux triangles sont frontières
det1=bamg::det(s1->i,sa->i,s2->i) ;
OnSwap = det1 >0 ;}
else if(( s1 != 0) && (s2 != 0) ) {
det1 = bamg::det(s1->i,sa->i,s2->i) ;
det2 = detT - det1 ;
OnSwap = (Abs(det1) + Abs(det2)) < detA ;
Icoor2 detMinNew=Min(det1,det2) ;
if ( ! OnSwap &&(detMinNew>0)) {
OnSwap = detMin ==0 ;
if ( ! OnSwap) {
while (1)
{
// critère de Delaunay pure isotrope
Icoor2 xb1 = sb->i.x - s1->i.x,
x21 = s2->i.x - s1->i.x,
yb1 = sb->i.y - s1->i.y,
y21 = s2->i.y - s1->i.y,
xba = sb->i.x - sa->i.x,
x2a = s2->i.x - sa->i.x,
yba = sb->i.y - sa->i.y,
84 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
break ;
}
} // OnSwap
} // ( ! OnSwap &&(det1 > 0) && (det2 > 0) )
}
if( OnSwap )
bamg::swap(t1,a1,t2,a2,s1,s2,det1,det2) ;
return OnSwap ;
}
// /////////////////////////////////////////////////////////////////////////
int nbd0 =0 ;
int izerodet=-1,iedge ; // izerodet = arête contenant le sommet s
Icoor2 detOld = t->det ;
if ( ( infv <0 ) && (detOld <0) || ( infv >=0 ) && (detOld >0) )
{
cerr « " infv " « infv « " det = " « detOld « endl ;
MeshError(3) ; // il y a des sommets confondus
4.3. PROGRAMME C++ 85
if ( !det3) {
det3 = det3local ; // alloc
if ( infv<0 ) {
det3[0]=bamg::det(s ,s1,s2) ;
det3[1]=bamg::det(s0,s ,s2) ;
det3[2]=bamg::det(s0,s1,s ) ;}
else {
// one of &s1 &s2 &s0 is NULL so (&si || &sj) <=> !&sk
det3[0]= &s0 ? -1 : bamg::det(s ,s1,s2) ;
det3[1]= &s1 ? -1 : bamg::det(s0,s ,s2) ;
det3[2]= &s2 ? -1 : bamg::det(s0,s1,s ) ;}}
if ( !det3[0]) izerodet=0,nbd0++ ;
if ( !det3[1]) izerodet=1,nbd0++ ;
if ( !det3[2]) izerodet=2,nbd0++ ;
tt[0]= t ;
tt[1]= &triangles[nbt++] ;
tt[2]= &triangles[nbt++] ;
if (nbt>nbtx) {
cerr « " No enougth triangles " « endl ;
MeshError(999) ; // pas assez de triangle
}
*tt[1]= *tt[2]= *t ;
(* tt[0])(OppositeVertex[0])=&s ;
(* tt[1])(OppositeVertex[1])=&s ;
(* tt[2])(OppositeVertex[2])=&s ;
tt[0]->det=det3[0] ;
tt[1]->det=det3[1] ;
86 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
tt[2]->det=det3[2] ;
tt[i0]->SetAdj2(i2,tt[i2],i0) ;
tt[i1]->SetAdj2(i0,tt[i0],i1) ;
tt[i2]->SetAdj2(i1,tt[i1],i2) ;
tt[0]->SetTriangleContainingTheVertex() ;
tt[1]->SetTriangleContainingTheVertex() ;
tt[2]->SetTriangleContainingTheVertex() ;
if ( !rswap)
{
cout « " Pb swap the point s is on a edge =>swap " « iedge « endl ;
MeshError(98) ;
}
}
}
// /////////////////////////////////////////////////////////////////////////
void Triangles::Insert()
{
NbUnSwap=0 ;
if (verbosity>2)
cout « " - Insert initial " « nbv « " vertices " « endl ;
SetIntCoor() ;
int i ;
Vertex** ordre=new (Vertex* [nbvx]) ;
MeshError(998) ; }
triangles[0].SetTriangleContainingTheVertex() ;
triangles[1].SetTriangleContainingTheVertex() ;
nbtf = 2 ; // ici, il y a deux triangles frontières invalide
int NbSwap=0 ;
Triangle * tcvi=triangles ;
if (verbosity>3)
cout « " NbSwap of insertion " « NbSwap
« " NbSwap/Nbv " « (float) NbSwap / (float) nbv
« " NbUnSwap " « NbUnSwap « " Nb UnSwap/Nbv "
88 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
// /////////////////////////////////////////////////////////////////////////
void Triangles::RandomInit()
{
nbv = nbvx ;
for (int i = 0 ; i < nbv ; i++)
{
vertices[i].r.x= rand() ;
vertices[i].r.y= rand() ;
vertices[i].Label = 0 ;
}
}
// /////////////////////////////////////////////////////////////////////////
Triangles::~Triangles()
{
if(vertices) delete [] vertices ;
if(triangles) delete [] triangles ;
PreInit(0) ; // met to les sommets à zèro
// /////////////////////////////////////////////////////////////////////////
void Triangles::SetIntCoor()
{
pmin = vertices[0].r ;
pmax = vertices[0].r ;
int Nberr=0 ;
for (i=0 ;i<nbt ;i++)
{
Vertex & v0 = triangles[i][0] ;
Vertex & v1 = triangles[i][1] ;
Vertex & v2 = triangles[i][2] ;
if ( &v0 && &v1 && &v2 ) // un bon triangle ;
{
triangles[i].det= det(v0,v1,v2) ;
if (triangles[i].det <=0 && Nberr++ <10)
{
if(Nberr==1)
cerr « "+++ Fatal Error "
« "(SetInCoor) Error : area of Triangle < 0\n" ;
}
}
else
triangles[i].det= -1 ; // le triangle est dégénéré ;
}
if (Nberr) MeshError(899) ;
}
// /////////////////////////////////////////////////////////////////////////
int Triangle::DelaunayOptim(short i)
{
// nous tournons dans le sens trigonométrique
int NbSwap =0 ;
Triangle *t = this ;
int k=0,j =OppositeEdge[i] ;
int jp = PreviousEdge[j] ;
// initialise tp, jp avec l’arête précédente de j
Triangle *tp= at[jp] ;
jp = aa[jp]&3 ;
do {
while (t->DelaunaySwap(j))
{
NbSwap++ ;
assert(k++<20000) ;
t= tp->at[jp] ;
j= NextEdge[tp->aa[jp]&3] ;
} // on a fini ce triangle
tp = t ;
jp = NextEdge[j] ;
t= tp->at[jp] ;
j= NextEdge[tp->aa[jp]&3] ;
} while( t != this) ;
return NbSwap ;
}
// /////////////////////////////////////////////////////////////////////////
if (inbvx) {
vertices=new Vertex[nbvx] ;
assert(vertices) ;
triangles=new Triangle[nbtx] ;
assert(triangles) ;}
else {
vertices=0 ;
triangles=0 ;
nbtx=0 ;
}
}
// /////////////////////////////////////////////////////////////////////////
jj=0 ;
detop = det(*(*t)(VerticesOfTriangularEdge[jj][0]),
*(*t)(VerticesOfTriangularEdge[jj][1]),B) ;
4.3. PROGRAMME C++ 91
while(t->det > 0 )
{
assert( kkkk++ < 2000 ) ;
j= OppositeVertex[jj] ;
if (k==0)
break ;
if (k == 2 && BinaryRand())
Exchange(ii[0],ii[1]) ;
assert ( k < 3) ;
TriangleAdjacent t1 = t->Adj(jj=ii[0]) ;
if ((t1.det() < 0 ) && (k == 2))
t1 = t->Adj(jj=ii[1]) ;
t=t1 ;
j=t1 ;
detop = -dete[OppositeVertex[jj]] ;
jj = j ;
}
return t ;
}
} // ----- end namespace -------
#include <cassert>
#include <fstream>
#include <iostream>
using namespace std ;
#include "Mesh.hpp"
92 CHAPITRE 4. CONSTRUCTION D’UN MAILLAGE BIDIMENSIONNEL
Triangles Th(nbv) ;
Th.RandomInit() ;
Th.Insert() ;
TriangleAdjacent ta(0,0) ;
GnuPlot(Th,"Th0.gplot") ;
int nbswp = ForceEdge(Th(na),Th(nb),ta) ;
if(nbswp<0) { cerr « " -Impossible de force l’arête " « endl ;}
else {
cout « " Nb de swap pour force l’arete [" « na « " " « nb
« "] =" « nbswp « endl ;
GnuPlot(Th,"Th1.gplot") ; }
return(0) ;
}
Soit Rot un rotation du plan d’angle 11◦ , écrire un programme qui maille
le carré Rot(]0, 1[2 ) découpé en 10 × 10 mailles. Calculer la surface des
triangles, expliquer le résultat (remarquer que le domaine à mailler n’est
pas convexe à cause de la représentation flottante des nombres dans l’or-
dinateur).
Exercice 4.5
Ajouter le forçage de la frontière et utiliser l’algorithme (4.2)de coloriage
des sous-domaines pour supprimer tous les triangles qui sont hors du do-
maine.
Vérifier l’utilité des coordonnées entières sur cet exemple.
4.3. PROGRAMME C++ 93
Faire une étude de complexité de l’algorithme, premièrement avec les
points générés aléatoirement, deuxièmement avec des points générés sur
Exercice 4.6 un même cercle. Trouver les parties de l’algorithme de complexité plus
que linéaire.
échange de diagonale, 64
coloriage, 67
complexité de d’algorithme, 93
composante connexe, 68
composantes connexes, 67
convexifié, 55
coordonnées entières, 70
Critère de boule vide, 60
Delaunay-Voronoï, 55
Diagramme de Voronoï, 59
erreurs d’arrondi, 70
forçage d’arête, 67
forçage de la frontière, 65
générateur de maillage, 71
h(), 68, 71
longueur Remmanienne, 69
maillage, 55
frontière, 57
triangulaire, 56
Maillage de Delaunay, 59
points internes, 68
sous-domaines, 58
taille de la maille, 71
taille de maillage, 68
tar.gz, 7