Note Cours Informatique Scientifique 2004
Note Cours Informatique Scientifique 2004
Note Cours Informatique Scientifique 2004
net/publication/251641551
CITATIONS READS
0 482
2 authors:
Some of the authors of this publication are also working on these related projects:
Efficient parallelization of projection-stabilized methods for the development of improved ROM of aero-thermal flows in buildings View project
Taylor Meshless Method, and Perfectly Matched Layer Method View project
All content following this page was uploaded by Frédéric Hecht on 01 July 2014.
26 mars 2004
Table des matières
1 Quelques éléments de syntaxe 4
1.1 Les déclarations du C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.2 Comment Compile et éditer de liens . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3 Quelques règles de programmation . . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4 Vérificateur d’allocation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2 Le Plan IR2 12
2.1 La classe R2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
5 Chaı̂nes et Chaı̂nages 37
5.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
5.2 Construction de l’image réciproque d’une fonction . . . . . . . . . . . . . . . . . 38
5.3 Construction des arêtes d’un maillage . . . . . . . . . . . . . . . . . . . . . . . . 38
5.4 Construction des triangles contenant un sommet donné . . . . . . . . . . . . . . 40
5.5 Construction de la structure d’une matrice morse . . . . . . . . . . . . . . . . . 41
5.5.1 Description de la structure morse . . . . . . . . . . . . . . . . . . . . . . 42
5.5.2 Construction de la structure morse par coloriage . . . . . . . . . . . . . . 42
6 Différentiation automatique 45
6.1 Le mode direct . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
6.2 Fonctions de plusieurs variables . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.3 Une bibliothèque de classes pour le mode direct . . . . . . . . . . . . . . . . . . 48
6.4 Principe de programmation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
6.5 Implémentation comme bibliothèque C++ . . . . . . . . . . . . . . . . . . . . . 49
7 Algèbre de fonctions 53
7.1 Version de base . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
7.2 Les fonctions C ∞ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
1 Quelques éléments de syntaxe
Il y a tellement de livres sur la syntaxe du C++qu’il me paraı̂t déraisonnable de réécrire un
chapitre sur ce sujet, je vous propose le livre de Thomas Lachand-Robert qui est disponible sur la
toile à l’adresse suivante http://www.ann.jussieu.fr/courscpp/, ou le cours C,C++
[?] plus moderne aussi disponible sur la toile http://casteyde.christian.free.fr/
cpp/cours ou bien sur d’utiliser le livre The C++, programming language [Stroustrup-1997]
Je veux décrire seulement quelques trucs et astuces qui sont généralement utiles comme les
déclarations des types de bases et l’algèbre de typage.
Donc à partir de ce moment je suppose que vous connaissez, quelques rudiments de la syntaxe
C++. Ces rudiments que je sais difficile, sont (pour le connaı̂tre il suffit de comprendre ce qui
est écrit après) :
– Les types de base, les définitions de pointeur et référence ( je vous rappelle qu’une référence
est défini comme une variable dont l’adresse mémoire est connue et cet adresse n’est pas
modifiable, donc une référence peut être vue comme une pointeur constant automatiquement
déférencé, ou encore donné un autre nom à une zone mémoire de la machine).
– L’écriture d’un fonction, d’un prototypage,
– Les structures de contrôle associée aux mots clefs suivants : if, else, switch, case,
default, while, for, repeat, continue, break.
– L’écriture d’une classe avec constructeur et destructeur, et des fonctions membres.
– Les passages d’arguments
– par valeur (type de l’argument sans &), donc une copie de l’argument est passée à la
fonction. Cette copie est créée avec le constructeur par copie, puis est détruite avec le
destructeur. L’argument ne peut être modifié dans ce cas.
– par référence (type de l’argument avec &) donc l’utilisation du constructeur par copie.
– par pointeur (le pointeur est passé par valeur), l’argument peut-être modifié.
– paramètre non modifiable (cf. mot clef const).
– La valeur retournée par copie (type de retour sans & ) ou par référence (type de retour
avec & )
– Polymorphisme et surcharge des opérateurs. L’appel d’une fonction est déterminée par son
nom et par le type de ses arguments, il est donc possible de créer des fonctions de même
nom pour des type différents. Les opérateurs n-naire (unaire n=1 ou binaire n=2) sont des
fonctions à n argument de nom operator ♣ (n-args ) où ♣ est l’un des 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 construc-
teur A(T) dans la classe A ou avec l’opérateur de conversion (cast en anglais) 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.
1.1 Les déclarations du C++
Les types de base du C++ sont respectivement : char, short, int, long, long long,
float, double, plus des pointeurs, ou des références sur ces types, des tableaux, des fonctions
sur ces types. Le tout nous donne une algèbre de type qui n’est pas triviale.
Voilà les principaux types généralement utilisé pour des types T,U :
déclaration Prototypage description du type en français
T * a T * un pointeur sur T
T a[10] T[10] un tableau de T composé de 10 variable de type T
T a(U) T a(U) une fonction qui a U retourne un T
T &a T &a une référence sur un objet de type T
const T a const T un objet constant de type T
T const * a T const * un pointeur sur objet constant de type T
T * const a T * const un pointeur constant sur objet de type T
T const * const a T const * const un pointeur constant sur objet constant
T * & a T * & une référence sur un pointeur sur T
T ** a T ** un pointeur sur un pointeur sur T
Listing 1 (a.hpp)
class A { public:
A() ; // constructeur de la class A
};
Listing 2 (a.cpp)
#include <iostream>
#include "a.hpp"
using namespace std ;
A::A()
{
cout << " Constructeur A par défaut " << this << endl ;
}
Listing 3 (tt.cpp)
#include <iostream>
#include "a.hpp"
using namespace std ;
int main(int argc,char ** argv)
{
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 :
Listing 4 (Makefile)
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 pro-
gramme.
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 vous considérez
Règle 1 absolue
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é.
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 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 retirer
toutes les assertions en compilant avec l’option -DNDEBUG, ou en définissant la macro du prepro-
cesseur #define NDEBUG, si vous voulez faire du filtrage avec des assertions, il suffit de définir les
macros suivante dans un fichier http://www.ann.jussieu/˜hecht/ftp/InfoSciO4/
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
#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
Règle 5 faut éditer les liens en ajoutant CheckPtr.o (le purify du pauvre) à 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.
Remarque 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.
class T { public:
int * p ; // un pointeur
T() {p=new int ;
cout << "Constructeur par defaut " << this
<< " p=" << p << "\n"}
T(const & T a) {
p=a.p ;
cout << "Constructeur par copie "<< this
<< " p=" << p << "\n"}
˜T() {
cout << "destructeur "<< this
<< " p=" << p << "\n" ;
delete p ;}
T & operator=(T & a) {
cout << "copie par affectation "<< this
<< "old p=" << p
<< "new p="<< a.p << "\n" ;
p=a.p ; }
};
remarque : bien sur vous pouvez et même vous devez tester ses deux classes,
avec le vérificateur d’allocation dynamique.
2 Le Plan IR2
Mais avant toute chose voilà quelques fonctions de base, qui sont définies pour tous les types
(le type de la fonction est un paramètre du patron (template en anglais).
using namespace std ; // pour utilisation des objet standard
typedef double R ; // définition de IR
// some usefull function
template<class T>
inline T Min (const T &a,const T &b) {return a < b ? a : b ;}
template<class T>
inline T Max (const T &a,const T & b) {return a > b ? a : b ;}
template<class T>
inline T Abs (const T &a) {return a <0 ? -a : a ;}
template<class T>
inline void Exchange (T& a,T& b) {T c=a ;a=b ;b=c ;}
template<class T>
inline T Max(const T &a,const T & b,const T & c)
{return Max(Max(a,b),c) ;}
template<class T>
inline T Min(const T &a,const T & b,const T & c)
{return Min(Min(a,b),c) ;}
class R2 { // la classe R2
public:
R x,y ;
R2 () :x(0),y(0) {} ; // constructeur par défaut
R2 (R a,R b):x(a),y(b) {} // constructeur standard
R2 (R2 a,R2 b):x(b.x-a.x),y(b.y-a.y) {} // bipoint
// pas de destructeur car pas de new
// donc les deux operateurs de copies fonctionnent
R2 operator+(R2 P) const {return R2(x+P.x,y+P.y) ;}
R2 operator+=(R2 P) {x += P.x ;y += P.y ;return *this ;}
R2 operator-(R2 P) const {return R2(x-P.x,y-P.y) ;}
R2 operator-=(R2 P) {x -= P.x ;y -= P.y ;return *this ;}
R2 operator-() const {return R2(-x,-y) ;}
R2 operator+() const {return *this ;}
R operator,(R2 P) const {return x*P.x+y*P.y ;} // produit scalaire
R operatorˆ (R2 P) const {return x*P.y-y*P.x ;} // determinant
R2 operator*(R c) const {return R2(x*c,y*c) ;}
R2 operator/(R c) const {return R2(x/c,y/c) ;}
R2 perp() const {return R2(-y,x) ;} // la perpendiculaire ⊥
};
inline R2 operator*(R c,R2 P) {return P*c ;}
2.1 La classe R2
Cette classe modélise le plan IR2 , de façon que les opérateurs classiques fonctionnent, c’est-à-
dire :
Un point P du plan est modélisé par ces 2 coordonnés x, y, 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
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 :
};
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 évitera
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
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 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.
Pour des raisons évidentes nous ne sommes pas allés plus loin que des combinaisons linéaires à
plus de deux termes. Toutes ces opérations sont faites sans aucune allocation et avec une seule
boucle.
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.
Régle : Toutes les classes ( KN <R>, KNM <R> , KNMK <R> ) se terminant par un sont
des classes sans aucune allocation mémoire (new ) ni libération (delete[], donc elle ne
travaillent que sur des tableaux préexistant comme dans des passages de paramètres, ou bien
comme pour donner un nom à une portion de tableau, de matrice, ... C’est a dire que ces classes
sont des sortes de référence (&).
#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,’.’) ; // la 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) ; // la 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.
#define CHECK_KN
#include "RNM.hpp"
#include "assert.h"
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 ;
}
Gi+1 = Gi + ρAH i
(Gi+1 , Gi+1 )C
γ=
(Gi , Gi )C
i+1
H = −CGi+1 + γH i
si (G , Gi+1 )C < ε stop
i+1
Listing 6 (GradConjugue.cpp)
#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] ;
}
10x10 :
10 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 11 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 18 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 37 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 74 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 135 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 226 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 353 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 522 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 739
GradienConjugue preconditionne par la diagonale
6 ro = 0.990712 ||g||ˆ2 = 1.4253e-24
solution : A*x= 10 : 1.60635e-15 1 2 3 4 5 6 7 8 9
∂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
i j i0 j 0
Mαβ = αw w + βw w
]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
...
4 Méthodes d’éléments finis P1 Lagrange
4.1 Le problème et l’algorithme
Le problème est de résoudre numériquement l’équation de la chaleur dans un domaine Ω de
IR2 .
Après utilisation de la formule de Green, et en multipliant par v, le problème peut alors s’écrire :
Calculer un+1
h ∈ Vh à partir de unh , où la donnée initiale u0h est interpolé P1 de u0 .
Z Z
∇uh ∇vh = f vh , ∀vh ∈ V0h , (3)
Ω Ω
Puis, notons, (wi )i=1,Ns les fonctions de base de Vh associées aux Ns sommets de Th de coor-
i i Ns
données
P (q )i=1,Nis , tel que w (qj ) = δij . Notons, Ui le vecteur de IR associé à u et tel que
u = i=1,Ns Ui w .
Sur un triangle K formé de sommets i, j, k tournants dans le sens trigonométrique. Notons,
i
HK le vecteur hauteur pointant sur le sommet i du triangle K et de longueur l’inverse de la
hauteur, alors on a :
(q j − q k )⊥
∇wi |K = HK i
= (5)
2 aireK
où l’opérateur ⊥ de IR2 est défini comme la rotation de π/2, ie. (a, b)⊥ = (−b, a).
Le programme d’éléments finis sans matrice
R
1. On peut calculer ui = Ω
wi f en utilisant la formule 4, comme suit :
(a) ∀i = 1, Ns ; ui = 0;
(b) ∀K ∈ Th ; ∀i sommet de K; ui = σi + f (bK )aireK /3; où bK est le
barycentre du triangle K.
2. Le produit y matrice A par un vecteur x est dans ce cas s’écrit
Algorithme 2 mathématiquement :
X Z X
(∇wi |K ).( ∇wj |K xj ) si i n’est pas sur le bord
yi = K
K∈Th j∈K
0 si i est sur le bord
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 ;}
};
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 une étiquette, et tester si
elle est nulle (pas de conditions aux limites).
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 :
Listing 10 (utilisation de la classe Vertex)
Les trois champs (x,y,lab) d’un sommet sont initialisés par (0.,0.,0) par
Remarque 3 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)
private:
BoundaryEdge(const BoundaryEdge &) ; // interdit la construction par copie
void operator=(const BoundaryEdge &) ; // interdit l’affectation par copie
};
BoundaryEdge E ;
E.set(v,ia,ib,lab) ; // construction d’une arête avec les sommets
// v[ia],v[ib] et étiquette lab
// (v est le tableau des sommets)
Vertex *vertices ;
Triangle *triangles ;
BoundaryEdge *bedges ;
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’ex-
plications :
• 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 étiquettes 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 sommets
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.
area=0 ;
assert(triangles && vertices) ;
cout << " Fin lecture : aire du maillage = " << area <<endl ;
}
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.
int ie = ... ;
BoundaryEdge & be= Th.begdes[ie] ; // référence de l’arête frontière ie
int ie1= Th(be[1]) ; // numéro du sommet 1 de l’arête frontière be
be.lab ; // label de l’arête frontière be
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include "sfem.hpp"
#include "RNM.hpp"
#include "GC.hpp"
KN<R> x(N),xe(N) ;
x=0 ; // donnee initial, il faut mettre les conditions aux limites.
}
else
{
cerr << "Erreur no converge du gradient conjuge " << endl ;
return 1 ; // pour que la commande unix retour une erreur
}
return 0 ;
}
4.2.7 Execution
Pour compiler et executer le programe, il faut entrer les lignes suivantes dans un fenêtre Shell
Unix :
"plot"
1
0.9
0.8
0.7
0.6
0.5
0.4
0.3
0.2
0.1
0
2
1.5
1
0.5
-2 0
-1.5
-1 -0.5
-0.5
0 -1
0.5 -1.5
1
1.5
2 -2
5 Chaı̂nes et Chaı̂nages
5.1 Introduction
Dans ce chapitre, nous allons décrire de manière formelle les notions de chaı̂nes et de chaı̂nages.
Nous présenterons d’abord les choses d’un point de vue mathématique, puis nous montrerons
par des exemples comment utiliser cette technique pour écrire des programmes très efficaces.
Rappelons qu’une chaı̂ne est un objet informatique composée d’une suite de maillons. Un
maillon, quand il n’est pas le dernier de la chaı̂ne, contient l’information permettant de trouver
le maillon suivant. Comme application fondamentale de la notion de chaı̂ne, nous commencerons
par donner une méthode efficace de construction de l’image réciproque d’une fonction.
Ensuite, nous utiliserons cette technique pour construire l’ensemble des arêtes d’un maillage,
pour trouver l’ensemble des triangles contenant un sommet donné, et enfin pour construire la
structure creuse d’une matrice d’éléments finis.
5.2 Construction de l’image réciproque d’une fonction
On va montrer comment construire l’image réciproque d’une fonction F . Pour simplifier l’ex-
posé, nous supposerons que F est une fonction entière de [0, n] dans [0, m] et que ses valeurs
sont stockées dans un tableau. Le lecteur pourra changer les bornes du domaine de définition
ou de l’image sans grand problème.
Voici une méthode simple et efficace pour construire F −1 (i) pour de nombreux i dans [0, n],
quand n et m sont des entiers raisonnables. Pour chaque valeur j ∈ Im F ⊂ [0, m], nous allons
construire la liste de ses antécédents. Pour cela nous utiliserons deux tableaux : int head F[m]
contenant les “têtes de listes” et int next F[n] contenant la liste des éléments des F −1 (i).
Plus précisément, si i1 , i2 ,...,ip ∈ [0, n], avec p ≥ 1, sont les antécédents de j, head F[j]=ip ,
next F[ip ]=ip−1 , next F[ip−1 ]=ip−2 , . . ., next F[i2 ]=i1 et next F[i1 ]= -1 (pour terminer la
chaı̂ne).
L’algorithme est découpé en deux parties : l’une décrivant la construction des tableaux next F
et head F, l’autre décrivant la manière de parcourir la liste des antécédents.
1. Construction :
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
Algorithme 3 for (int i=0 ;i<n ;i++)
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 construction 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
où nbe est le nombre d’arêtes (edges en anglais), nt le nombre de triangles et nv le nombre de
sommets (vertices en anglais).
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. Au-
trement 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 seront simultanées.
Construction rapide des arêtes d’un maillage Th
Preuve : la boucle for(int e=head minv[i] ;e !=end list ;e=next edge[e]) permet de par-
courir toutes des arêtes (i, j) orientées (i < j) ayant même i, et la ligne :
permet de chaı̂ner en tête de liste des nouvelles arêtes. Le nbe++ incrémente pour finir le nombre
d’arêtes.
Exercice 7 Il est possible de modifier l’algorithme précédent en supprimant le tableau next edge
et en stockant les chaı̂nages dans arete[i][0], mais à la fin, il faut faire une boucle de plus
sur les sommets pour reconstruire arete[.][0].
Exercice 8 Construire le tableau adj d’entier de taille 3 nt qui donne pour l’arete i du tri-
angle k.
– si cette arete est interne alors adj[i+3k]=ii+3kk où est l’arete ii du triangle kk, re-
marquons : ii=adj[i+3k]%3, kk=adj[i+3k]/3.
– sinon adj[i+3k]=-1.
Voici donc l’algorithme pour construire l’ensemble des triangles ayant un sommet en 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
Algorithme 6 for(j=0 ;j<3 ;j++) {
p = 3*k+j ;
i = Th(k,j) ;
next_p[p]=head_s[i] ;
head_s[i]= p ;}
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} ;
a[21] // ... valeurs des 21 coefficients de la matrice
#include "MatMorse.hpp"
MatriceMorseSymetrique::MatriceMorseSymetrique(const Mesh & Th)
{
int color=0, * mark ;
int i,j,jt,k,p,t ;
n = m = Th.nv ;
mark = new int [n] ;
// construction optimisée 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++ ;
Au passage, nous avons utilisé la fonction HeapSort qui implémente un petit algorithme de tri,
présenté dans [Knuth-1975], qui a la propriété d’ête toujours en nlog2 n (cf. code ci-dessous).
Noter que l’étape de tri n’est pas absolument nécessaire, mais le fait d’avoir des lignes triées
par indice de colonne permet d’optimiser l’accés à un coefficient de la matrice dans la structure
creuse.
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 ;}
}
}
}
Remarque : Si vous avez tout compris dans ces algorithmes, vous pouvez vous attaquer à la
plupart des problèmes de programmation.
6 Différentiation automatique
Les dérivées d’une fonction décrite par son implémentation numérique peuvent être calculées
automatiquement et exactement par ordinateur, en utilisant la différentiation automatique
(DA). La fonctionnalité de DA est très utile dans les programmes qui calculent la sensibilité
par rapport aux paramètres et, en particulier, dans les programmes d’optimisation et de design.
Nous nous proposons de calculer automatiquement sa dérivée J 0 (u) par rapport à la variable
u, au point u = 2.3.
Méthode : Dans le programme de calcul de J(u), chaque ligne de code sera précédée par son
expression différentiée (avant et non après à cause des instructions du type x = 2 ∗ x + 1) :
dx = du + du/(u ∗ u)
x = u − 1/u
dy = dx + du/u
y = x + log(u)
dJ = dx + dy
J=x+y
Ainsi avons nous associé à toute variable (par exemple x) une variable supplémentaire, sa
différentielle (dx). La différentielle devient la dérivée seulement une fois qu’on a spécifié la
variable de dérivation. La dérivée (dx/du) est obtenue en initialisant toutes les différentielles à
zéro en début de programme sauf la différentielle de la variable de dérivation (ex du) que l’on
initialise à 1.
La valeur de la dérivée J 0 (u)|u=2.3 est donc obtenue en exécutant le programme ci-dessus avec
les valeurs initiales suivantes : u = 2.3, du = 1 et dx = dy = 0.
Les structures de contrôle (boucles et tests) présentes dans le code de définition de la fonction
seront traitées de manière similaire. En effet, une instruction de test de type if où a est pré-
défini,
y = a;
if ( u>0) x = u ;
else x = 2*u ;
J=x+y ;
• le deuxième calcule
y=a ; x=2*u ; J=x+y ;
Les deux programmes sont réunis naturellement sous la forme d’un unique programme
dy=0 ; y=a ;
if (u>0) {dx=du ; x=u ;}
else {dx=2*du ; x=2*u ;}
dJ=dx+dy ; J=x+y ;
x=0 ;
for( int i=1 ; i<= 3 ; i++) x=x+i/u ;
cout << x << endl ;
dx=0 ;x=0 ;
dx=dx-du/(u*u) ; x=x+1/u ;
dx=dx-2*du/(u*u) ; x=x+2/u ;
dx=dx-3*du/(u*u) ; x=x+3/u ;
cout << x <<’\t’<< dx << endl ;
dx=0 ;x=0 ;
for( int i=1 ; i<= 3 ; i++)
{ dx=dx-i*du/(u*u) ; x=x+i/u ;}
cout << x <<’\t’<< dx << endl ;
Limitations :
• Si dans les exemples précédents la variable booléene qui sert de test dans l’instruction if
et/ou les limites de variation du compteur de la boucle for dépendent de u, l’implémentation
décrite plus haut n’est plus adaptée. Il faut remarquer que dans ces cas, la fonction définie par
ce type de programme n’est plus différentiable par rapport à la variable u, mais est différentiable
à droite et à gauche et les dérivées calculées comme ci-dessus sont justes.
sera exécuté pour les valeurs initiales : d1x1 = 1, d1x2 = 0, d2x1 = 0, d2x2 = 1.
Par rapport à la méthode décrite précédemment, nous allons remplacer chaque variable par un
tableau de dimension deux, qui va stocker la valeur de la variable et la valeur de sa différentielle.
Le programme modifié s’écrit :
float y[2],x[2],u[2] ;
// dx = 2 u du + 2 du (u+1)
x[1] = 2 * u[0] * u[1] + 2 * u[1] * (u[0] + 1) ;
// x = 2 u (u+1)
x[0] = 2 * u[0] * (u[0] + 1) ;
y[1] = x[1] + cos(u[0])*u[1] ;
y[0] = x[0] + sin(u[0]) ;
J[1] = x[1] * y[0] + x[0] * y[1] ;
// J = x * y
J[0] = x[0] * y[0] ;
L’étape suivante de l’implémentation (voir [?], chapitre 4) consiste à créer une classe C++ qui
contient comme données membres les tableaux introduits plus haut. Les opérateurs d’algèbre
linéaire classiques seront redéfinis à l’intérieur de la classe pour prendre en compte la structure
particulière des données membres. Par exemple, les opérateurs d’addition et multiplication sont
définis comme suit :
#include <iostream>
using namespace std ;
struct ddouble {
double val,dval ;
ddouble(double x,double dx): val(x),dval(dx) {}
ddouble(double x): val(x),dval(0) {}
};
int main()
{
ddouble x(2,1) ;
cout << f(2.0) << " x = 2.0, (x*x+1)ˆ2 " << endl ;
cout << f(ddouble(2,1)) << "2 (2x) (x*x+1) " << endl ;
return 0 ;
}
Mais de faite l’utilisation des (templates) permet de faire une utilisation recursive.
#include <iostream>
using namespace std ;
-- x = Newton 2 ( d = 1) ,1 ( d = 0) )
1 ( d = 0)
2.25 ( d = 1.5)
2.00694 ( d = 1.02315)
2.00001 ( d = 1.00004)
2 ( d = 1)
x = 1.41421 ( d = 0.353553) == 1.41421 ( d = 0.353553) = xe
7 Algèbre de fonctions
Bien souvent, nous aimerions pouvoir considérer des fonctions comme des données classiques,
afin de construire une nouvelle fonction en utilisant les opérations classiques +,-,*,/ ... Par
exemple, la fonction f (x, y) = cos(x) + sin(y).
Dans un premier temps, nous allons voir comment l’on peut construire et manipuler en C++l’algèbre
des fonctions C ∞ , définies sur IR à valeurs dans IR.
Listing 20 (fonctionsimple.cpp)
#include <iostream>
// pour la fonction pow
#include <math.h>
typedef double R ;
class Cvirt { public: virtual R operator()(R ) const =0 ;} ;
Dans cet exemple, la fonction f = cos2 + (sin ∗ sin + x4 ) sera définie par un arbre de classes
qui peut être représenté par :
Listing 21 (fonction.hpp)
#ifndef __FONCTION__
#define __FONCTION__
struct CVirt {
mutable CVirt *md ; // le pointeur sur la fonction dérivée
CVirt () : md (0) {}
virtual R operator () (R) = 0 ;
virtual CVirt *de () {return zero ;}
CVirt *d () {if (md == 0) md = de () ; return md ;}
static CVirt *zero ;
};
struct CVirt2 {
CVirt2 (): md1 (0), md2 (0) {}
virtual R operator () (R, R) = 0 ;
virtual CVirt2 *de1 () {return zero2 ;}
virtual CVirt2 *de2 () {return zero2 ;}
CVirt2 *md1, *md2 ;
CVirt2 *d1 () {if (md1 == 0) md1 = de1 () ; return md1 ;}
CVirt2 *d2 () {if (md2 == 0) md2 = de2 () ; return md2 ;}
static CVirt2 *zero2 ;
};
#endif
Maintenant, prenons l’exemple d’une fonction Monome qui sera utilisée pour construire les
fonctions polynômes. Pour construire effectivement ces fonctions, nous définissons la classe
CMonome pour modéliser x 7→ cxn et la classe CMonome2 pour modéliser x 7→ cxn1 y n2 .
En utilisant exactement la même technique, nous pouvons construire les classes suivantes :
CFunc une fonction C++de prototype R (*)(R) .
CComp la composition de deux fonctions f, g comme (x) → f (g(x)).
CComb la composition de trois fonctions f, g, h comme (x) → f (g(x), h(x)).
CFunc2 une fonction C++de prototype R (*)(R,R).
CComp2 la composition de f, g comme (x, y) → f (g(x, y)).
CComb2 la composition de trois fonctions f, g, h comme (x, y) → f (g(x, y), h(x, y))
Pour finir, nous indiquons juste la définition des fonctions globales usuelles :
Avec ces définitions, la construction des fonctions classiques devient très simple ; par exemple,
pour construire les fonctions sin, cos il suffit d’écrire :
Function Cos(cos),Sin(sin) ;
Cos.setd(-Sin) ; // définit la dérivée de cos
Sin.setd(Cos) ; // définit la dérivée de sin
Function d4thCos=Cos.d().d().d().d() ; // construit la dérivée quatrième
Références
[J. Barton, Nackman-1994] J. Barton, L. Nackman Scientific and Engineering, C++,
Addison-Wesley, 1994.
[Ciarlet-1978] P.G. Ciarlet , The Finite Element Method, North Holland.
n and meshing. Applications to Finite Elements, Hermès, Paris,
1978.
[Ciarlet-1982] P. G. Ciarlet Introduction à l’analyse numérique matricielle
et à l’optimisation, Masson ,Paris,1982.
[Ciarlet-1991] P.G. Ciarlet , Basic Error Estimates for Elliptic Problems, in
Handbook of Numerical Analysis, vol II, Finite Element methods
(Part 1), P.G. Ciarlet and J.L. Lions Eds, North Holland, 17-352,
1991.
[1] I. Danaila, F. hecht, O. Pironneau : Simulation numérique en
C++ Dunod, 2003.
[Dupin-1999] S. Dupin Le langage C++, Campus Press 1999.
[Frey, George-1999] P. J. Frey, P-L George Maillages, Hermes, Paris, 1999.
[George,Borouchaki-1997] P.L. George et H. Borouchaki , Triangulation de Delaunay
et maillage. Applications aux éléments finis, Hermès, Paris, 1997.
Also as
P.L. George and H. Borouchaki , Delaunay triangulation
and meshing. Applications to Finite Elements, Hermès, Paris,
1998.
[FreeFem++] F. Hecht, O. Pironneau, K. Othsuka FreeFem++ : Ma-
nual http ://www.freefem.org/
[Hirsh-1988] C. Hirsch Numerical computation of internal and external
flows, John Wiley & Sons, 1988.
[Koenig-1995] A. Koenig (ed.) : Draft Proposed International Standard for In-
formation Systems - Programming Language C++, ATT report
X3J16/95-087 ([email protected])., 1995
[Knuth-1975] D.E. Knuth , The Art of Computer Programming, 2nd ed.,
Addison-Wesley, Reading, Mass, 1975.
[Knuth-1998a] D.E. Knuth The Art of Computer Programming, Vol I : Fun-
damental algorithms, Addison-Wesley, Reading, Mass, 1998.
[Knuth-1998b] D.E. Knuth The Art of Computer Programming, Vol III : Sor-
ting and Searching, Addison-Wesley, Reading, Mass, 1998.
[Lachand-Robert] T. Lachand-Robert, A. Perronnet
(http://www.ann.jussieu.fr/courscpp/)
[Löhner-2001] R. Löhner Applied CFD Techniques, Wiley, Chichester, En-
gland, 2001.
[Lucquin et Pironneau-1996] B. Lucquin, O. Pironneau Introduction au calcul scientifique,
Masson 1996.
[Numerical Recipes-1992] W. H. Press, W. T. Vetterling, S. A. Teukolsky, B. P. Flannery :
Numerical Recipes : The Art of Scientific Computing, Cambridge
University Press, 1992.
[Raviart,Thomas-1983] P.A. Raviart et J.M. Thomas, Introduction à l’analyse
numérique des équations aux dérivées partielles, Masson, Paris,
1983.
[Richtmyer et Morton-1667] R. D. Richtmyer, K. W. Morton : Difference methods for initial-
value problems, John Wiley & Sons, 1967.
[Shapiro-1991] J. Shapiro A C++ Toolkit, Prentice Hall, 1991.
[Stroustrup-1997] B. Stroustrup The C++ Programming Language, Third Edi-
tion, Addison Wesley, 1997.
[Wirth-1975] N Wirth Algorithms + Dat Structure = program, Prentice-Hall,
1975.
[Aho et al -1975] A. V. Aho, R. Sethi, J. D. Ullman , Compilers, Principles,
Techniques and Tools, Addison-Wesley, Hardcover, 1986.
[Lex Yacc-1992] J. R. Levine, T. Mason, D. Brown Lex & Yacc, O’Reilly &
Associates, 1992.
[Campione et Walrath-1996] M. Campione and K. Walrath The Java Tutorial : Object-
Oriented Programming for the Internet, Addison-Wesley, 1996.
Voir aussi Integrating Native Code and Java Pro-
grams. http://java.sun.com/nav/read/Tutorial/
native1.1/index.html.
[Daconta-1996] C. Daconta Java for C/C++ Programmers, Wiley Computer
Publishing, 1996.
[Casteyde-2003] Christian Casteyde Cours de C/C++ http://casteyde.
christian.free.fr/cpp/cours