Notes Dec Ours MN 406
Notes Dec Ours MN 406
Notes Dec Ours MN 406
éléments finis
Frédéric Hecht, Cours MN4006 2013-2014, Master 2
Laboratoire Jacques-Louis Lions, Université Pierre et Marie Curie
30 avril 2015
1
Table des matières
1 L’ordinateur 6
1.1 Comment ça marche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2 Les nombres dans l’ordinateur . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.1 Les entiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.2.2 Les réels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
2
2.6 Connexion au machine de l’ufr . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3 Présentation du langages C 27
3.1 introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.1 Historique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.2 Avantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1.3 Désavantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.2 Un exemple assez complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
5 Exemples 54
6 Exemples 55
6.1 Le Plan IR2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.1.1 La classe R2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
6.1.2 Utilisation de la classe R2 . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.2 Les classes tableaux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59
6.2.1 Version simple d’une classe tableau . . . . . . . . . . . . . . . . . . . . . 59
6.2.2 les classes RNM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.2.3 Exemple d’utilisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.2.4 Un resolution de systéme linéaire avec le gradient conjugué . . . . . . . . 67
6.2.5 Gradient conjugué préconditionné . . . . . . . . . . . . . . . . . . . . . . 68
6.2.6 Test du gradient conjugué . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.2.7 Sortie du test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
3
7.3 Espace affine, convexifié, et simplexe . . . . . . . . . . . . . . . . . . . . . . . . 74
7.4 Formule d’intégration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.4.1 Formule d’intégration sur le triangle . . . . . . . . . . . . . . . . . . . . . 77
7.5 Le maillage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
7.6 Condition aux Limites de type Dirichlet . . . . . . . . . . . . . . . . . . . . . . 79
7.6.1 Méthode de pénalisation exacte . . . . . . . . . . . . . . . . . . . . . . . 80
7.6.2 Condition de Dirichlet dans les méthodes de minimisation (GC, GMRES,
...) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.7 Le problème et l’algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.8 Maillage et Triangulation 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.9 Les classes de Maillages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.9.1 La classe Label . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.9.2 La classe Vertex2 (modélisation des sommets 2d) . . . . . . . . . . . . 85
7.9.3 La classe Triangle (modélisation des triangles) . . . . . . . . . . . . . 86
7.9.4 La classe Seg (modélisation des segments de bord) . . . . . . . . . . . . 88
7.9.5 La classe Mesh2 (modélisation d’un maillage 2d) . . . . . . . . . . . . . 89
7.10 Le programme quasi générique . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92
7.11 Construction avec des matrices creuse . . . . . . . . . . . . . . . . . . . . . . . . 94
9 Algorithmique 103
9.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.2 Complexité algorithmique . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
9.3 Base, tableau, couleur . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
9.3.1 Décalage d’un tableau . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.3.2 Renumérote un tableau . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
9.4 Construction de l’image réciproque d’une fonction . . . . . . . . . . . . . . . . . 106
4
9.5 Tri par tas (heap sort) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
9.6 Construction des arêtes d’un maillage . . . . . . . . . . . . . . . . . . . . . . . . 108
9.7 Construction des triangles contenant un sommet donné . . . . . . . . . . . . . . 111
9.8 Construction de la structure d’une matrice morse . . . . . . . . . . . . . . . . . 113
9.8.1 Description de la structure morse . . . . . . . . . . . . . . . . . . . . . . 113
9.8.2 Construction de la structure morse par coloriage . . . . . . . . . . . . . . 114
9.8.3 Le constructeur de la classe SparseMatrix . . . . . . . . . . . . . . . . 116
5
1 L’ordinateur
Dans un ordinateur, le Codage binaire est utilisé. Pour trouver la représentation binaire d’un
nombre, on le décompose en somme de puissances de 2. Par exemple avec le nombre dont la
représentation décimale est 59 :
59 = 1 ⇥ 32 + 1 ⇥ 16 + 1 ⇥ 8 + 0 ⇥ 4 + 1 ⇥ 2 + 1 ⇥ 1
59 = 1 ⇥ 25 + 1 ⇥ 24 + 1 ⇥ 23 + 0 ⇥ 22 + 1 ⇥ 21 + 1 ⇥ 20
59 = 111011 en binaire
Avec n bits, ce système permet de représenter les nombres entre 0 et 2n 1 ,donc un nombre
k l’ecrit comme
n 1
X
k= bi 2 i , bi 2 {0, 1}
i=0
Représentation des entiers négatifs Pour compléter la représentation des entiers, il faut pouvoir
écrire des entiers négatifs. On a introduit la représentation par complément à deux. Celle-ci
consiste à réaliser un complément à un de la valeur, puis d’ajouter 1 au résultat. Par exemple
pour obtenir 5 :
000101 codage de 5 en binaire sur 6 bits
111010 complément à un
111011 on ajoute 1 : représentation de 5 en complément à deux sur 6 bits
Avec n bits, ce système permet de représenter les nombres entre 2n 1 et 2n 1 1, où tous les
calculs se font dans l’anneau Z/2n Z, et les nombres négatifs ont le bit de poids fort (bn 1 ) égal
à 1.
Les nombres à virgule flottante sont les nombres des valeurs réels, ce sont des approximations
de nombres réels. Les nombres à virgule flottante possèdent un signe s (dans { 1, 1}), une
mantisse entière m (parfois appelée significande) et un exposant e. Un tel triplet représente
un réel s.m.be où b est la base de représentation (parfois 2, mais aussi 16 pour des raisons de
rapidité de calculs, ou éventuellement toute autre valeur). En faisant varier e, on fait ⌧ flotter
la virgule décimale. Généralement, m est d’une taille fixée. Ceci s’oppose à la représentation
6
dite en virgule fixe, où l’exposant e est fixé. Les di↵érences de représentation interne des nombres
flottants d’un ordinateur à un autre obligeaient à reprendre finement les programmes de calcul
scientifique pour les porter d’une machine à une autre jusqu’à ce qu’un format normalisé soit
proposé par l’IEEE.
Les flottants IEEE peuvent être codés sur 32 bits (⌧ simple précision ) ou 64 bits (⌧ double
précision ). La répartition des bits est la suivante :
Encodage Signe Exposant Mantisse Valeur d’un nombre
Simple précision 32 bits 1 bit 8 bits 23 bits ( 1)s ⇥ (1, M ) ⇥ 2E 127
Double précision 64 bits 1 bit 11 bits 52 bits ( 1)s ⇥ (1, M ) ⇥ 2E 1023
Grande précision 80 bits 1 bit 15 bits 64 bits ( 1)s ⇥ (1, M ) ⇥ 2E 16383
Précautions d’emploi
Exemple de la programmation de la série un des 1,
u0 = 0, un = un + 1,
si les un sont des nombres flottants, il existe un grand nombre flottant K telle que 1. soit
negligable par rapport K donc K + 1 est égal à K, comme la suite est croissante et majorée,
elle est donc convergente.
#include <iostream>
using namespace std;
float x=0,x1=1;
while( x < x1)
{
x=x1;
x1=x+1;
}
cout << " lim de series des 1 en (float) =" << x << endl;
return 0;
}
Exercice 1. Oui, mais quel est la loi du groupe (R, +) qui n’est pas vérifiée.
Faire le même type de test avec les autre type de nombre : int, short,
Exercice 2.
char, long, long, long, double
Les calculs en virgule flottante sont pratiques, mais présentent trois désagréments :
— leur précision limitée, qui se traduit par des arrondis qui peuvent s’accumuler de façon
gênante. Pour cette raison, les travaux de comptabilité ne sont pas e↵ectués en virgule
flottante, même pour la fortune personnelle de Bill Gates, car tout doit y tomber juste
au centime près.
7
— une quantisation qui peut être gênante autour du zéro : une représentation flottante
possède une plus petite valeur négative, une plus petite valeur positive, et entre les deux
le zéro... mais aucune autre valeur ! Que rendre comme résultat quand on divise la plus
petite valeur positive par 2 ? 0 est une réponse incorrecte, garder la valeur existante une
erreur aussi.
— des e↵ets de ⌧ bruit discret sur les trois derniers bits lorsque l’exposant est une
puissance de 16 et non de 2
Il est par exemple tentant de réorganiser des expressions en virgule flottante comme on le
ferait d’expressions mathématiques. Cela n’est cependant pas anodin, car les calculs en virgule
flottante, contrairement aux calculs sur les réels, ne sont pas associatifs. Par exemple, dans un
calcul en flottants IEEE double précision, (1050 + 1) 1050 ne donne pas 1, mais 0. La raison
est que 1050 + 1 est approximé par 1050 . Une configuration binaire est en général réservée à la
représentation de la ⌧ valeur NaN (⌧ not a number ), qui sera par exemple le résultat de la
tentative de division flottante d’un nombre par zéro. NaN combiné avec n’importe quel nombre
(y compris NaN), donne NaN.
8
dans un fichier. L’ensemble de ces nouvelles commandes définit alors une nouvelle commande
(de nom le nom du fichier) et ainsi de suite... Chaque utilisateur construit progressivement son
environnement personnel pour répondre à l’ensemble de ses besoins.
Un fichier est formé d’informations (texte, instructions, commandes, images, sons, ...) re-
groupées sous un NOM et généralement conservées entre 2 exécutions. À ce fichier sont as-
sociés des DROITS D’ACCÈS qui permettent de l’utiliser en lecture et/ou en écriture et/ou
en exécution. Pour éviter que les fichiers n’apparaissent sur un seul niveau, la notion de
RÉPERTOIRE ou DOSSIER (directory en anglais) permet la hiérarchisation des fichiers en une
ARBORESCENCE. Répertoires (nœuds communs à plusieurs branches de l’arbre) et fichiers
(feuilles de l’arbre) constituent un arbre dont la racine (le répertoire initial) a pour nom /
Le caractère / en début d’un nom de fichier ou répertoire désigne le disque par défaut de
l’ordinateur. Souvent, il s’agit de l’unique disque ”dur” de la station de travail ou de l’ordinateur.
De même que pour les fichiers, les DROITS D’ACCÈS d’un répertoire permettent ou non la
lecture de ses fichiers ou sous-répertoires, l’ajout ou non de nouveaux fichiers ou sous-répertoires
aux utilisateurs habilités. Les unités périphériques (clavier, souris, modems, ...) envoient en
mémoire soit centrale notée MC, soit secondaire (disque ou clef USB) notée MS, des informations
(caractères tapés au clavier, mouvement de la souris, ...) regroupées sous la forme d’un fichier
dit d’ENTRÉE.
En sens inverse, des informations contenues en mémoire et regroupées sous la forme d’un fi-
chier dit de SORTIE peuvent être transmises aux unités périphériques (affichage sur l’écran
ou l’imprimante, transfert de la MC sur MS, envoie sur une ligne téléphonique, ... ). De façon
générale, les fichiers d’ENTRÉES-SORTIES ne sont pas conservés après leur transfert.
Tous les travaux de l’usager sont réalisés par des processus. Un PROCESSUS est l’exécution
d’un programme particulier appelé ”SHELL” qui est en fait un interpréteur de commandes.
Une commande frappée est interprétée par le processus qui réserve tout ce qui est nécessaire à
son exécution (mémoire centrale, fichiers, ... ) et l’exécute.
Au démarrage du système, un processus est créé. Un processus, dit alors père, peut demander
la création d’un nouveau processus dit processus fils. Un processus fils est créé par copie du
processus père. Le processus père a la possibilité d’attendre la fin de l’exécution du processus
fils. Un processus peut aussi se remplacer lui-même par un autre code exécutable.
Un processus peut être exécuté en interactif ou en tâche de fond (& doit être ajouté à la fin de
la commande).
Les commandes peuvent être tapées au clavier ou lues dans un fichier. Le nom d’un fichier
contenant des commandes exécutables apparaı̂t ainsi comme une nouvelle commande. Il est
9
ainsi possible de créer un environnement adapté aux besoins. Il est aussi possible d’enchaı̂ner
des processus, les résultats du précédent servant de données au suivant grâce à la notion de
TUBE ou CANAL (pipe en anglais).
Les réseaux sous unix sont principalement de type IP ( Internet Protocol). Le réseau est d’un
point de vue information des découpé est 6 couches, la couche 1 correspondant a la partie
support ⌧ hardware (le type de fils paire torsadés, ondes radio, fibres optiques, IRD, ...),
jusqu’à la couche logiciel finale correspondant au programme utilisé, par exemple : netscape,
ssh, ...
De faite pour utiliser le réseau IP, il faut connaı̂tre le nom de la machine de l’on veut atteindre
par exemple www.yahoo.fr ou ftp.jussieu.fr et quel type de service vous voulez utiliser
ftp 21/tcp
telnet 23/tcp
smtp 25/tcp mail
nameserver 42/tcp name # IEN 116
whois 43/tcp nicname
bootp 67/udp # bootp server
finger 79/tcp
pop2 109/tcp postoffice
pop3 110/tcp pop
nntp 119/tcp readnews untp # USENET News Transfer Protocol
ntp 123/udp
snmp 161/udp
exec 512/tcp
biff 512/udp comsat
login 513/tcp
who 513/udp whod
shell 514/tcp cmd # no passwords used
syslog 514/udp
printer 515/tcp spooler # line printer spooler
talk 517/udp
ntalk 518/udp
route 520/udp router routed
timed 525/udp timeserver
Si vous voulez utiliser l’un de ces services il faut utiliser un client sur votre ordinateur (par
exemple : ftp ftp.inria.fr) qui va envoyer une requête sur la machine ftp.inria.fr sur le port ftp,
là le client ftpd qui tourne sur la ftp.inria.fr répondra, et vous pourrez vous connecter.
Quelques mots clefs, le DNS (domain name server) la partie logiciel qui permet de trou-
ver l’adresse IP (4 octets noté a.b.c.b, où a,b,c et d sont des nombre entier compris entre
0 et 255, exemple : 134.157.2.1) d’une machine à partir du nom de la machine (exemple :
emeu.ann.jussieu.fr).
10
Des ordinateurs serveurs DNS du réseau jussieu.fr ont pour numéro IP 134.157.2.1,
134.157.0.129, 134.157.8.84 .
Le routeur est un composant qui permet de passer d’un réseau à un autre. Le routeur du
laboratoire d’analyse numérique est 134.157.2.254, ce routeur est aussi un serveur de temps
via le protocole ntp (net time protocole).
Le courrier électronique est quelque chose d’assez compliqué. Dès que vous avez un compte
sur une machine UNIX vous disposez d’une boı̂te aux lettres sur cette machine si le service
sendmail (smtp) est activé. Le problème est que généralement vous aurez des comptes sur
chacune des machines de la salle informatique. Pratiquement, tous les courriers arriveront sur
une machine unique que l’on appelera serveur, et donc généralement, vous allez vouloir lire
votre courrier d’une autre machine. Pour cela il faudra utiliser un service IP pour lire courrier
sur le serveur ( POP3 ou IMAP) ou bien sinon il faudra vous connecter e↵ectivement sur le
serveur via ssh. Maintenant, il faut définir la machine qui va envoyer le courrier (généralement
votre machine, cette machine doit être sur le même reseau (à jussieu cette machine peut être
shiva.ccr.jussieu.fr).
Pour lire votre courrier électronique, vous pouvez utiliser firefox, mail.app, ... (les
commandes mail, mailx, Mail sont fortement déconseillées car elles ne comprennent pas les
attachements, le metamail (mime), les caractères accentués).
Si possible utiliser le protocol APOP (plutôt que POP) qui crypter votre mot de passe au
moment de votre authentification sinon votre mot de passe passera en clair sur le réseau.
Ici le mot ⌧ computer sera un nom de machine du réseau, par exemple : ibm1.cicrp.jussieu.fr
ou un numero IP 128.93.16.18.
Version sécurisée, les informations qui passent sur le réseau, sont cryptées.
— ssh nom de machine -l user connection a un machine, version international (la
clef de cryptage est de 1024 bits)
— scp [-R] user@computer:name1 name2 copiez un fichier ou un directory de l’uti-
lisateur user du computer dans name2 sur votre ordinateur, avec l’option -R le direc-
tory est copié récursivement. Remarque le mot de passe est demandé à chaque utilisation.
— scp [-R] name2 user@nom de machine:name1 réciproque.
Les commandes réseaux
— ftp computer ouvre un session de transfért de fichier sur la machine computer,
— firefox ouvre un broutteur de page html, de news, et lecteur de courrier électronique.
11
2.3 COMMENT UTILISER UNIX EN INTERACTIF :
Un utilisateur d’Unix est reconnu par son User-Id (par exemple p6dean12). De plus, il appartient
à un ou plusieurs groupes repérés par un Group-ID (par exemple p6dean).
Ces 2 identificateurs ont dû être enregistrés par l’administrateur du système avant toute ten-
tative de connexion.
La commande id ou id UserId retourne les 2 noms déclarés.
La commande newgrp AutreGroupe permet le changement de groupe d’un utilisateur.
2.3.1 LA CONNEXION
Après avoir allumé l’ordinateur, une fenêtre s’ouvre sur l’écran avec 2 cases : Nom de l’Usager
et Mot de Passe.
À l’aide de la souris, le curseur doit être positionné dans la première case.
Ensuite, il faut taper votre nom d’usager et le valider par la frappe de la touche <Entrée>.
Ce nom doit être connu du système, c’est à dire avoir été enregistré par l’administrateur du
système.
Si la frappe est incorrecte ou bien si le nom est inconnu, il apparaı̂tra le texte ⌧ login incorrect
et vous n’aurez plus qu’à recommencer...
Dans le cas contraire, votre mot de passe (8 caractères sont souhaités) doit ensuite être frappé
et validé par la frappe de la touche <Entrée>. L’affichage de ces caractères est annihilé pour
des raisons évidentes de sécurité. Une fenêtre s’ouvre sur votre écran. Derrière le curseur, il
est alors possible de taper une commande UNIX ... Pour terminer la session, il faut cliquer en
dehors de toute fenêtre, choisir l’option Fin de Session et confirmer la demande de sortie de
mwm.
(Obsolete car les password réseau gère par ldap sont a chanter sur le web.)
Il faut taper
passwd NomUsager.
Il apparaı̂tra ensuite
ATTENTION : N’oubliez pas ce nouveau mot de passe sous peine d’avoir à relancer la procédure
d’octroi d’un nouveau mot de passe ce qui demande du temps.
12
De plus, la législation réprime tout accès ou tentative d’accès de personnes non autorisées à un
ordinateur. Il est absolument nécessaire de protéger votre mot de passe sous peine de risquer
des poursuites dues à l’utilisation frauduleuse de votre nom par une tierce personne. Votre mot
de passe équivaut à votre signature. Il est donc recommandé de choisir un mot difficile à deviner
et surtout de ne jamais stocker ce nom sur papier. A vous de le mémoriser !
Dans tout ce qui suit, un terme entre < > désigne une touche du clavier.
Le caractère haut d’une touche du clavier s’obtient en gardant enfoncée la touche <Shift> et
en frappant la touche.
Le caractère bas à droite d’une touche (par exemple : \ ...) s’obtient en gardant enfoncée la
touche <AltGr> et en frappant la touche.
<Backspace> e↵ace le caractère qui précède le curseur qui recule d’un caractère
<Char Del> e↵ace le caractère du curseur sans bouger de place
Attention :
- Toute ligne est à terminer (valider) par la frappe de la touche <Entrée> ;
- Pour taper une commande sur plusieurs lignes, il faut terminer chaque ligne, exceptée la
dernière, par un caractère \, SANS AUCUN AUTRE CARACTÈRE DERRIÈRE, et surtout
pas un caractère ”blanc”. En e↵et, le caractère \ neutralise le caractère de fin de ligne <Entrée>
placé derrière. S’il existe un caractère ”blanc” invisible, \ le supprime et non pas celui de fin
de ligne et cela entraı̂ne une erreur car les arguments de la commande sur les lignes suivantes
ne sont pas transmis.
Une FENÊTRE ouverte sur l’écran comprend plusieurs parties, une barre d’en tête, et une
partie centrale.
Avec une sourie à 3 boutons :
- le bouton gauche, fait apparaı̂tre un menu pour gérer la fenêtre, notamment, la déplacer ou
la détruire.
- le bouton droite réduit la fenêtre à une icône. (Il suffit pour régénérer la fenêtre à partir de
l’icône, de cliquer 2 fois rapidement l’icône).
- le bouton milieu à l’extrême droite augmente la taille de la fenêtre jusqu’à lui faire occuper
tout l’écran. + Une fenêtre avec 2 parties :
— la partie basse ou ligne courante permet l’entrée des COMMANDES UNIX
— la partie haute contient les RÉSULTATS des commandes déjà exécutées
La taille et la position de la fenêtre peuvent être modifiées comme suit :
— - positionner avec la souris le curseur dans un des 4 coins du cadre de la fenêtre,
— - appuyer sur le bouton gauche de la souris et déplacer la souris sans relâcher le bouton,
— - relâcher le bouton à l’endroit choisi.
Pour faire apparaı̂tre une fenêtre cachée, il faut déplacer la fenêtre qui la masque.
<Ctrl>D sur la ligne courante d’une fenêtre détruit la fenêtre (ferme le shell).
13
Pour créer une nouvelle fenêtre (en fait, un processus qui exécute la commande xterm !), enfoncer
la touche droite ou gauche de la souris en dehors de toute fenêtre et après déplacement cliquer
l’option Nouvelle Fenêtre. Quelques instants plus tard, une nouvelle fenêtre s’ouvre. Une
autre méthode consiste à taper l’instruction
Alors une fenêtre, avec ascenseur, pouvant contenir au plus 10000 lignes, avec un curseur rouge,
un titre NomTitre, un fond noir et le texte écrit en blanc selon la fonte normale ou grasse formée
de 9 caractères de large sur 15 de haut avec 50 lignes de 80 caractères s’ouvre en haut et à
gauche sur l’écran. D’autres fontes sont très souvent disponibles (7x14, 8x13, 10x20, ...).
Le nom du disque serveur de référence est noté / et sert de racine de l’arbre. Tout fichier
(file) ou répertoire (directory) a un NOM dans l’arbre qui l’identifie. Un nom de fichier peut
comprendre un SUFFIXE séparé du nom par un .
exemples : sp.f /home/p6dean/tri.f /users/dupont/.profile
Ce dernier est un fichier exécuté lors de l’initialisation de la session interactive.
Le suffixe suit des conventions : .f ou .F pour fortran, .c pour C, .pas pour pascal, .tex pour
TEX, ... /usr/local/mefisto est un répertoire c’est à dire en fait la liste des noms de ses
sous-répertoires et de ses fichiers. /sbin contient la plupart des commandes UNIX de base
pour le démarrage de l’ordinateur ;
/dev contient les fichiers d’emploi des périphériques (lecteurs de bandes magnétiques, ”dri-
vers”, ...) ;
/tmp contient les fichiers temporaires nécessités par le système ;
/etc contient des utilitaires d’administration du système ;
/usr contient des répertoires d’utilitaires pour les utilisateurs
/usr/bin contient la plupart des commandes UNIX ;
/usr/man contient les 8 chapitres de la documentation en ligne (cf man) ;
/usr/lib contient les librairies X, Motif, ... ;
/home contient les répertoires des Utilisateurs. À l’initialisation de la session interactive, une
position dans l’arbre est fixée. Il s’agit du RÉPERTOIRE PERSONNEL (HOME directory) de
l’usager. Si son nom est dupont alors son répertoire personnel a pour nom /home/p6dean/dupont
Ce répertoire est aussi, à cet instant, le RÉPERTOIRE COURANT, encore dit répertoire de
travail ( WORKING Directory ).
De là, pour atteindre un fichier ou répertoire (navigation dans l’arbre), il faut taper
- soit, le NOM ABSOLU qui débute par la racine ;
14
par exemple : /home/p6dean/dupont/.profile
- soit, le NOM RELATIF au répertoire courant ;
par exemple : .profile
désigne le même fichier si le répertoire courant est le HOME directory. . désigne le nom du
répertoire courant par exemple : /home/p6dean/dupont
.. désigne le nom du répertoire père du répertoire courant par exemple :
le répertoire père de /home/p6dean/dupont est /home/p6dean
Une COMMANDE est constituée d’un NOM de COMMANDE suivi de 0 ou plusieurs mots
séparés par au moins un caractère blanc ou tabulation.
NomCommande {-option} {paramètre} {redirection}
Les caractères { et } ne sont là que pour indiquer que le texte inclus peut être présent, répété
ou absent.
(Dans les lignes qui suivent fic désigne un nom quelconque de fichier et rep un nom quelconque
de répertoire) ls rep liste les fichiers et répertoires du répertoire rep
ls -l rep liste les fichiers et répertoires du répertoire rep avec ses droits d’accès et d’autres
informations (taille, date de dernière utilisation, ... )
ls -a rep liste les fichiers et notamment ceux débutant par le caractère . (à ne pas confondre
avec l’abréviation pour le répertoire courant) et les répertoires du répertoire rep avec d’autres
informations.
ls -R rep liste les fichiers et sous-répertoires du répertoire rep mais aussi des sous-sous-...-
répertoires du répertoire rep
ls -rt rep liste les fichiers et sous-répertoires du répertoire rep dans l’ordre inverse de la
date de dernière modification. Le caractère > redirige les sorties.
ls -1 >fic
envoie les noms des fichiers et répertoires du répertoire courant, 1 par ligne, dans le fichier de
nom fic et non pas sur l’écran ! Les sorties sont dites redirigées dans le fichier fic. Si le fichier
fic existait son ancien contenu est perdu.
ls -1 >>fic e↵ectue la même opération mais en ajoutant les noms à la suite du contenu
actuel du fichier fic s’il existe. cat fic1 fic2 fic3 >fic concatène les fichiers fic1, fic2, fic3 dans
le fichier fic.
En l’absence de >fic le fichier concaténé apparaı̂t sur l’écran.
Attention de faite, il y a 2 types canaux de sortie sous unix le ⌧ standard output et le ⌧ error
output qui ont respectivement comme numéro 1 et 2, si vous voulez stocker dans un fichier
les erreurs des compilations il faut rediriger le canal 2 et non le canal 1 comme dans
make essai 2>list.err
15
Mais si vous voulez rediriger les deux canaux dans le canal 1 que vous pouvez ajouter 2>&1 ,
ce qui donne la commande suivante
make essai 2>&1 1>list.err
mais vous ne voyez plus rien, alors utilise la command tee qui duplique le standard input dans
un fichier et dans le standard output, comme suit :
make essai 2>&1 | tee list.err
Pour plus de detail
man sh
cat >fic permet l’entrée au clavier du texte d’un fichier, à terminer par la frappe des touches
<Ctrl> <D>.
Mais attention, il ne faut pas se tromper car à part la touche <Backspace>, rien n’est prévu
pour faire des modifications (Pour cela, employer un éditeur de textes ed, vi, ...). De même, le
caractère < redirige les entrées. Par exemple
mail AdresseElectronique < lettre
envoie à l’adresse électronique le contenu du fichier lettre (de dernière ligne contenant en
première colonne le caractère . et rien d’autre) du répertoire courant (obsolète) utiliser pine
ou netscape .
Si le choix de l’une des options d’une commande vous pose des problèmes, il est possible de lire
la documentation en ligne en tapant
man NomCommande {numéro 1 à 8 du chapitre}
Par exemple : man ls 1
Le texte de la documentation apparaı̂t et il suffit de parcourir le texte en s’aidant des touches
marquées d’une flèche (4 sens) pour obtenir les renseignements recherchés. Il existe 8 chapitres
dans cette documentation en ligne :
1 : Les commandes accessibles à l’usager
2 : L’interface entre UNIX et le langage C
3 : La bibliothèque C et macros et fonctions mathématiques
4 : Les caractéristiques des fichiers système associés aux périphériques
5 : Les formats des fichiers système
6 : Les jeux
7 : Les bibliothèques de macros pour la manipulation des documents
8 : Les commandes de maintenance du système
16
— rmdir rep ( Remove Directory ) détruit le répertoire rep sous la condition qu’il soit
vide de tout fichier et sous-répertoire
— cp fic1 fic2 ... ficn rep ( Copy ) fait une copie des fichiers fic1 fic2 ... ficn dans le
répertoire rep sans détruire les fichiers fic1 fic2 ... ficn initiaux
— mv rep1 rep2 ( Move ) copie tous les fichiers et répertoires du répertoire rep1 dans le
répertoire rep2, puis détruit le répertoire rep1
— mv fic1 fic2 ... ficn rep ( Move ) déplace les fichiers fic1 fic2 ... ficn dans le répertoire rep
et détruit les fichiers fic1 fic2 ... ficn initiaux (GARE aux ERREURS si rep est oublié !
ficn contient fic n-1 et fic1 fic2 ... fic n-1 sont détruits !)
— rm fic ( Remove ) détruit le fichier fic
— rm -R rep ( Remove ) détruit toute la branche issue du répertoire rep et le répertoire
rep lui-même. A utiliser avec discernement !
— lpr -Plaser1 fic imprime le fichier fic sur l’imprimante de nom laser1.
— more fic affiche le contenu du fichier fic.
Pour passer à la page suivante taper un espace. Attention, pas de remontée possible.
Pour terminer, taper :q
Pour spécifier plusieurs noms de fichiers ou répertoires, il est possible d’employer la ”STAR-
CONVENTION” c’est à dire les caractères JOKER encore dits métacaractères (neutralisables
par \) :
— * remplace n’importe quel nom jusqu’au prochain blanc ou . ou / possible
exemple :
— *.f désigne tous les fichiers du répertoire courant suffixés par f
— *.* désigne tous les fichiers ayant un suffixe
— ? remplace n’importe quel caractère
— ! indique NON de ce qui suit
exemple :
sp ?.f désigne tous les fichiers dont le nom commence par les 2 caractères sp, suivi d’un
caractère et de suffixe .f .
— [a-z] remplace n’importe quel caractère a ou b ou ... ou z
— [!a-w] remplace n’importe quel caractère di↵érent de a ou b ou ... ou w
— [0-9] remplace n’importe quel caractère 0 ou 1 ou ... ou 9
Avant utilisation, il est prudent d’afficher le résultat de l’analyse des ces caractères JOKER à
l’aide de la commande echo :
echo NomAvecCaractèresJOKER affiche tous les noms ainsi désignés
Une commande peut souvent être stoppée par<Ctrl> C
Les Droits d’accès d’un fichier ou répertoire peuvent être listés par la commande ls -l
et redéfinis par leur propriétaire à l’aide de la commande
chmod -R rwxrwxrwx rep ou fic
chmod mode rep ou fic
où r vaut 0 ou 4, w vaut 0 ou 2, x vaut 0 ou 1, sont à sommer pour donner l’autorisation en
lecture (r=4), en écriture(w=2), en exécution (x=1), pour les 3 cas :
17
- le premier groupe rwx est pour le propriétaire ;
- le second groupe rwx est pour le groupe ;
- le troisième groupe rwx est pour tous les autres utilisateurs ;
- -R signifie récursivement c’est à dire dans les sous-répertoires aussi ;
- mode vaut
. en premier caractère u (utilisateur) ou g (groupe) ou o (autres) ou a (à la fois ugo)
. en second caractère + ou - ou = pour ajouter, supprimer ou définir une valeur ;
. en troisième caractère r pour lecture, w pour écriture et x pour exécution.
Exemples :
chmod 750 fic1
donne les droits de lecture-écriture-exécution au propriétaire, lecture-exécution (pas en écriture !)
aux membres du même groupe et aucun accès aux autres utilisateurs du fichier fic1.
chmod o-w MonFichier
retire la possibilité d’écrire aux autres (excepté le propriétaire et les membres du même groupe)
et donc de modifier le fichier MonFichier.
chmod a+rx MaProc
permet à tout le monde la lecture et l’exécution du fichier MaProc.
La recherche des occurrences d’un MOT dans le contenu des fichiers d’une liste de fichiers
(générés à partir de caractères JOKER) s’obtient par la frappe de
grep MOT NomsDesFichiers affiche les lignes contenant au moins une fois MOT dans le
contenu des fichiers d’une liste de fichiers
grep -n MOT NomsDesFichiers affiche les lignes et leur numéro contenant au moins une
fois MOT dans le contenu des fichiers NomsDesFichiers
grep -i MOT NomsDesFichiers affiche les lignes contenant au moins une fois MOT sans
se préoccuper de l’aspect minuscule ou majuscule dans le contenu des fichiers NomsDesFichiers
NomsDesFichiers peut être défini avec des caractères JOKER. find rep -name MOT -print
affiche les noms des fichiers du répertoire rep dont chaque nom contient MOT . Attention, si
vous voulez utilisez la ”STAR-CONVENTION”, il faut mettre entre quote ”MOT” sinon le
shell va interprète le mot comme plusieurs mots. Exemple pour trouver vous fichiers .f. cd ;
find . -name "*.f" -print Ici aussi, MOT peut être décrit avec des caractères JOKER.
wc fic donne le nombre de lignes, mots et caractères du fichier. grep MOT *.f | wc
équivaut à
grep MOT *.f >fic ; wc fic ; rm fic
Le caractère séparateur ; permet la frappe de plusieurs commandes sur une même ligne.
18
Le TUBE ou CANAL | permet d’employer les sorties de la première commande comme entrées
de la seconde sans avoir à utiliser un fichier auxiliaire.
diff NomFichier1 NomFichier2 affiche toutes les di↵érences entre les contenus des fichiers
NomFichier1 et NomFichier2
19
Pour compresser automatiquement le fichier archive pendant sa création, on utilise l’option z
(gzip).
Syntaxe : tar zcvf fichier.tar.gz motif
Exemple : tar zcvf tpc.tar.gz *.c
Et de façon similaire, pour décompresser une archive et en extraire les fichiers :
Syntaxe : tar zxvf fichier.tar.gz
Exemple :tar zxvf tpc.tar.gz
L’ordre des options n’a pas d’importance, en revanche, celui de fichier et motif en a.
Les fichiers compressés avec gzip ont .gz pour extention.
Contenu d’une archive
Pour visualiser la liste des noms des fichiers contenus dans une archive, on utilise l’option t.
Syntaxe : tar tf fichier.tar
Exemple : tar tf tpc.tar
Si le fichier est compressé avec gzip, on rajoute l’option z.
Syntaxe : tar zt fichier.tar.gz
Exemple : tar zt tpc.tar.gz !
20
Le taux de compression est parfois étonnant sur les fichiers textes, mais, moins important sur
les fichiers binaires.Il est souvent possible de coupler tar et gunzip ou l’opération inverse en
ajoutant simplement l’option z à celles de la commande tar.
Par exemple sous LINUX :
tar -xzvf rep.tar.gz
décompresse et copie dans les répertoires les di↵érents fichiers.
La commande
ln -s AncienNom NomLié
permet de déplacer facilement un fichier ou un répertoire utilisé seulement selon son NomLié.
Exemple :
ln -s /home/p6dean/p6dean00/MonLogiciel /usr/local/MonLogiciel
permet l’écriture de procédures avec la variable d’environnement
MonLogiciel=/usr/local/MonLogiciel
mais aussi de placer ce logiciel n’importe où dans l’arbre des répertoires.
ls -l NomLié
redonne l’AncienNom véritable.
rm NomLié
ne détruit pas le fichier mais seulement le lien, c’est-à-dire que le NomLié n’existe plus.
Cet éditeur est toujours présent sous Unix et très utile lorsque X n’est pas actif. De plus, il sert
à manipuler les commandes de l’historique de la connexion.
(Au CICRP, l’éditeur nedit (ou nedit) (taper nedit &) sera préféré car il emploie plus inten-
sivement la souris et le clavier pour rectifier le texte, les menus pour éditer, sauvegarder, ouvrir
les fichiers, obtenir une aide en ligne, ... ) vi NomFichier charge le fichier et l’affiche sur
l’écran
2 modes : le mode commande (pour déplacer le curseur ou modifier le texte) et le mode texte
(pour sa saisie)
21
:q sortie de vi
:q! sortie forcée de vi sans sauvegarde
:w sauvegarde du fichier sur lui-même
:w AutreFichier sauvegarde dans AutreFichier
22
dd supprime la ligne support du curseur
3dd supprime la ligne courante et les 2 lignes suivantes
D supprime le reste de la ligne courante à partir du curseur
dw supprime le mot courant
23
fin est un numéro de ligne ou . la ligne courante ou comme ci-dessus ou $ la dernière ligne du
texte
sans précision de ligne, la ligne courante est utilisée
:début,fin s/AncienneChaine/NouvelleChaine/
cette requête substitue pour les lignes début à fin l’ AncienneChaine par la NouvelleChaine
u restitue l’état antérieur des lignes avant la dernière substitution
Dans la définition d’une chaı̂ne :
. désigne n’importe quel caractère
* désigne la répétition (voire 0 fois) du caractère précédent
/.*;/ désigne n’importe quelle chaı̂ne terminée par ;
(Dans le shell * suffit pour désigner ici .*)
ˆ désigne le caractère fictif qui précède le premier de la ligne
$ désigne le caractère fictif qui suit le dernier de la ligne
[a-z] désigne n’importe quelle lettre minuscule (une seule)
[ˆ 0-9] désigne tout caractère di↵érent d’un chi↵re 0 à 9
ˆ désigne le complémentaire de l’ensemble des caractères qui suit
sauf s’il n’est pas le premier caractère et il désigne alors le caractère lui-même
\ neutralise le caractère spécial (.\ $ * ˆ [ ] ) qui le suit
:s/1\ .2\ ..*/3/ substitue 1.2. et le reste de la ligne par 3
:s/a+3/(&)/ encadre de parenthèses les occurrences de a+3
& désigne la chaı̂ne de caractères qui précède
:/\ (chaı̂ne1\)caractère ou non\(chaı̂ne2\) .../
définit 2 ou plusieurs sous-chaı̂nes limitées par \( et \) et utilisables ensuite avec \ 1 ou \ 2
Exemple :
abcdefg hijklmno pqrstuv wxyz
:s/\ (.* \ ) \ (.* \ ) \ (.* \ ) \ (.*\ )/\ 3 \ 1\ .\ 4 ;\ 2/
donne
pqrstuv abcdefg .wxyz ;hijklmno
:début,fin g/chaı̂ne/requête fait subir à toute ligne contenant chaı̂ne la requête qui
suit
:.,.5g /ˆ a/s/a/A/ substitue sur la ligne courante et ses 5 suivantes le premier caractère
a de la ligne en A
Si plusieurs requêtes s’avèrent nécessaires, avant de frapper la touche entrée, il faut frapper le
caractère \ pour annihiler la fin de ligne v en lieu et place de g applique les requêtes sur les
lignes ne contenant pas chaı̂ne
24
:r fichier ajoute le texte du fichier à partir de la position du curseur
25
- PATH contient la liste des répertoires où les commandes doivent être recherchées.
- CDPATH contient la liste des répertoires atteignables par la commande cd. Pour initialiser ou
modifier ces variables, ou en créer d’autres, il peut être judicieux d’ajouter des commandes aux
fichiers de démarrage. Leur liste partielle s’obtient par la commande
ls -a il apparaı̂t
Le listage à l’aide de la commande cat des fichiers .profile et .kshrc donne Pour modifier son
environnement initial, il suffit de modifier le fichier .profile du répertoire personnel. Pour
modifier son environnement lors de l’ouverture d’un processus ksh, il suffit de modifier le fichier
.kshrc du répertoire personnel. Attention à la di↵érence entre les 2 appels :
.profile est exécuté une seule fois avant appel de ksh ;
.kshrc est exécuté lors du démarrage à chaque appel de /bin/ksh .
26
2.6 Connexion au machine de l’ufr
3 Présentation du langages C
3.1 introduction
3.1.1 Historique
Dans les dernières années, aucun langage de programmation n’a pu se vanter d’une croissance
en popularité comparable à celle de C et de son jeune frère C++. L’étonnant dans ce fait est
que le langage C n’est pas un nouveau-né dans le monde informatique, mais qu’il trouve ses
sources en 1972 dans les ’Bell Laboratories’ : Pour développer une version portable du système
d’exploitation UNIX, Dennis M. Ritchie a conçu ce langage de programmation structuré, mais
⌧ très près de la machine.
K&R-C
En 1978, le duo Brian W. Kernighan / Dennis M. Ritchie a publié la définition classique du
langage C (connue sous le nom de standard K&R-C ) dans un livre intitulé ’The C Programming
Language’.
ANSI-C
Le succès des années qui suivaient et le développement de compilateurs C par d’autres maisons
ont rendu nécessaire la définition d’un standard actualisé et plus précis. En 1983, le ’Ameri-
can National Standards Institute’ (ANSI) chargeait une commission de mettre au point ’une
définition explicite et indépendante de la machine pour le langage C’, qui devrait quand même
conserver l’esprit du langage. Le résultat était le standard ANSI-C. La seconde édition du livre
’The C Programming Language’, parue en 1988, respecte tout à fait le standard ANSI-C et elle
est devenue par la suite, la ’bible’ des programmeurs en C.
C++
En 1983 un groupe de développeurs de AT&T sous la direction de Bjarne Stroustrup a créé
le langage C++. Le but était de développer un langage qui garderait les avantages de ANSI-
C (portabilité, efficience) et qui permettrait en plus la programmation orientée objet. Depuis
1990 il existe une ébauche pour un standard ANSI-C++. Entre-tempsAT&T a développé deux
compilateurs C++ qui respectent les nouvelles déterminations de ANSI et qui sont considérés
comme des quasi-standards (AT&T-C++ Version 2.1 [1990] et AT&T-C++ Version 3.0 [1992]),
la version c++11 ISO/IEC 14882 :[2011].
3.1.2 Avantages
Le grand succès du langage C s’explique par les avantages suivants ; C est un langage :
(1) universel : C n’est pas orienté vers un domaine d’applications spéciales, comme par
exemple FORTRAN (applications scientifiques et techniques) ou COBOL (applications
commerciales ou traitant de grandes quantités de données).
(2) compact : C est basé sur un noyau de fonctions et d’opérateurs limité, qui permet la
formulation d’expressions simples, mais efficaces.
27
(3) moderne : C est un langage structuré, déclaratif et récursif ; il o↵re des structures de
contrôle et de déclaration comparables à celles des autres grands langages de ce temps
(FORTRAN, ALGOL68, PASCAL).
(4) près de la machine : comme C a été développé en premier lieu pour programmer le
système d’exploitation UNIX, il o↵re des opérateurs qui sont très proches de ceux du
langage machine et des fonctions qui permettent un accès simple et direct aux fonctions
internes de l’ordinateur (p.ex : la gestion de la mémoire).
(5) rapide : comme C permet d’utiliser des expressions et des opérateurs qui sont très proches
du langage machine, il est possible de développer des programmes efficients et rapides.
(6) indépendant de la machine : bien que C soit un langage près de la machine, il peut
être utilisé sur n’importe quel système en possession d’un compilateur C. Au début C
était surtout le langage des systèmes travaillant sous UNIX, aujourd’hui C est devenu le
langage de programmation standard dans le domaine des micro-ordinateurs.
(7) portable : en respectant le standard ANSI-C, il est possible d’utiliser le même programme
sur tout autre système (autre hardware, autre système d’exploitation), simplement en le
recompilant.
(8) extensible : C ne se compose pas seulement des fonctions standard ; le langage est animé
par des bibliothèques de fonctions privées ou livrées par de nombreuses maisons de
développement.
3.1.3 Désavantages
Évidemment, rien n’est parfait. Jetons un petit coup d’oeil sur le revers de la médaille :
Cette notation est très pratique, mais plutôt intimidante pour un débutant. L’autre
variante, plus près de la notation en Pascal, est plus lisible, mais elle ne profite pas des
avantages du langage C :
28
else
printf(" ");
}
Exemple 2
La fonction copietab() copie les éléments d’une chaı̂ne de caractères T[] dans une autre
chaı̂ne de caractères S[]. Voici d’abord la version ’simili-Pascal’ :
Cette définition de la fonction est valable en C, mais en pratique elle ne serait jamais
programmée ainsi. En utilisant les possibilités de C, un programmeur expérimenté préfère
la solution suivante :
29
(3) discipline de programmation (Les dangers de C) : C est un langage près de la machine,
donc dangereux et bien que C soit un langage de programmation structuré, il ne nous
force pas à adopter un certain style de programmation (comme p.ex. Pascal). Dans
un certain sens, tout est permis et la tentation de programmer du ’code spaghetti’ est
grande. (Même la commande ’goto’, si redoutée par les puristes ne manque pas en C). Le
programmeur a donc beaucoup de libertés, mais aussi des responsabilités : il doit veiller
lui-même à adopter un style de programmation propre, solide et compréhensible.
Voilà un exemple amusant et illisible de program C qui calcul beaucoup de décimales de PI.
int a=10000,b,c=2800,d,e,f[2801],g;main(){for(;b-c;)f[b++]=a/5;for(;d=0,g=c*2;c
-=14,printf("%.4d",e+d/a),e=d%a)for(b=c;d+=f[b]*a,f[b]=d%--g,d/=g--,--b;d*=b);}
3141592653589793238462643383279502884197169399375
1058209749445923078164062862089986280348253421170
6798214808651328230664709384460955058223172535940
8128481117450284102701938521105559644622948954930
3819644288109756659334461284756482337867831652712
0190914564856692346034861045432664821339360726024
9141273724587006606315588174881520920962829254091
7153643678925903600113305305488204665213841469519
4151160943305727036575959195309218611738193261179
3105118548074462379962749567351885752724891227938
1830119491298336733624406566430860213949463952247
3719070217986094370277053921717629317675238467481
8467669405132000568127145263560827785771342757789
6091736371787214684409012249534301465495853710507
9227968925892354201995611212902196086403441815981
3629774771309960518707211349999998372978049951059
7317328160963185
Un autre programme qui calcul une aproximation de ⇡ avec un programme plus joli, mais tout
aussi illisble :
#define _ -F<00||--F-OO--;
int F=00,OO=00;main(){F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO()
{
_-_-_-_
_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
30
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_-_-_-_-_
_-_-_-_-_-_-_-_
_-_-_-_
}
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/* definition des structures */
#include <math.h>
#include "lesson.h"
31
char str[80]; /* une chaı̂ne de caractères est un tableau
de caractères se terminant par le caractère \0 */
list * head, *p; /* de pointeur sur un type list définit par un typedef dans
lesson.h */
printf("Hello World\n");
/* */
Pi = 4* atan(1);
l= 10L;
f=10.0;
i = (argc>=2 ? atoi(argv[1]) : 100) ;
dt = allocation_dynamique(i);
/* le taille de type de base */
printf(" Size of char %d, int %d , float %d, double %d , long %d "
", long long %d\n",
sizeof(char), sizeof(int),sizeof(float),
sizeof(double),sizeof(long),sizeof(long long));
printf(" Size of ,tabd %d, p2i %d, p2d %d \n",sizeof(tabd),
sizeof(p2i),sizeof(p2d));
for (i=0;i<5;i=i+1)
routine(i);
for (i= 0;i<10;i++)
tabi[i]=i*i;
for (i= 0;i<10;i++)
tabd[i]=cos(i*Pi);
/* construction de la liste */
head=0; /* liste vide */
for (i=1;i<=5;i++)
head=NouvelleFeuille(i,head); /* ajoute en tête de liste */
initilisation_tab_func();
printf(" d = %f \n",d);
32
/* verification des variables global static */
file = "lesson.c";
p_file_r("lesson.c");
p_file_l("lesson.c");
copy_chaine(str,"CouCou ?");
printf(" ’%s’\n",str);
free(dt); /* liberation de la memoire alloue */
/* lecture formaté */
printf(" entrez un entier et un double ? ");
scanf("%d%lf",&i,&d);
printf("\n i = %d d= %lf\n",i,d);
return 0;
}
void operateurs()
{
double a=1,b=2,c;
long i=3,j=4,k=8;
double *p, tab[10];
c = (a*b) - (a/b) - 1 ;
c = pow(a,10); /* puissance */
i=10;
j=3;
k= i%3; /* reste de la division signé */
/*
expression de comparaison
a < b , a > b, a <= b, a >= b
a == b; a != b différent
expression boolean:
faux <=> valeur nulle de tout type
vraie <=> valeur non nulle de tout type
! (non),
&& (et), le membre de droit n’est pas évalué si membre de gauche est faux
|| (ou), le membre de droit n’est pas évalué si membre de gauche est vrai
operateur sur le cha{ ˆı}ne de bits
& (et) ,| (ou inclusif) ,ˆ (ou exclusif), ˜ (complement (non))
<< (lshift), >> (lshift)
33
/* attention à l’arithmétique des pointeurs,
unité est la taille de l’objet pointé */
p=tab; /* p pointe sur tab[0] */
*p = 10.1; /* défini la valeur pointe par le pointeur */
++p; /* pointe sur tab[1] */
*++p=3; /* tab[2] = 2.1 <=> (*tab+2) = 2.0 */
p=&c; /* &d adresse de c <=> le pointeur sur c */
}
void des_types ()
{
int i;
int *pi; /* pointeur sur un int */
int i5[] /* un tableau de 5 int initialiser*/
= {0,1,2,3,4};
int i2x5[2][5] /* un tableau de 2x5 int */
={ {11,12,13,14,15},
{21,22,23,24,25}} ;
int (*pi5)[5]; /* un pointeur sur un tableau de 5 entier */
int *ip5[5]; /* un tableau de 5 pointeur sur des entiers */
for (i=0;i<5;i++)
ip5[i]= &(i2x5[1][5]); /* initialisation du tableau de pointeur */
i=1;
*pi += 2; /* *pi et i sont la même mémoire donc */
assert( i==3); /* génère une erreur à l’execution si l’assertion est fausse*/
}
/* ˆı}ne de caractères*/
exemple de code pour copie une cha{
void copy_chaine(char * dst,char * src)
{ while (*dst++=*src++); }
le fichier lesson.h
/* dans le .h */
34
void routine(int i);
void initilisation_tab_func();
/* une global static est local a l’unité de compilation */
static char * file;
le fichier routines.c
#include <stdio.h>
#include <stdlib.h>
#include "lesson.h"
void initilisation_tab_func()
{
tab_func[0]=Add;
tab_func[1]=Mul;
file = "routines.c";
}
void routine(int i)
35
{
if (i) printf("routine True %d \n",i);
else printf("routine false %d \n",i);
switch (i) {
case 0: printf(" 0,"); break;
case 1: printf(" 1,");
case 2: printf(" 2,"); break;
case 3:
case 4: printf(" 3 ou 4,"); break;
default: printf (" autre cas \n");
}
}
OBJ=lesson.o routines.o
all: lesson westley pi
lesson: $(OBJ)
<tabulation>$(CC) -o lesson $(OBJ) -lm
pour compile les 3 exemples lesson westley pi, taper la commande shell. make, ou pour
compile seulement le programme ⌧ lesson taper la commande shell. make lesson. le résultat
des commandes
hecht@kiwi:/disc2/hecht/lecon_de_C$ ls
Makefile lesson lesson.c lesson.h pi.c routine.c routines.c westley.c
hecht@kiwi:/disc2/hecht/lecon_de_C$ make
cc -c -o lesson.o lesson.c
cc -c -o routines.o routines.c
cc -o lesson lesson.o routines.o -lm
cc westley.c -o westley
cc pi.c -o pi
hecht@kiwi:/disc2/hecht/lecon_de_C$
36
et l’execution de la commande donne :
hecht@kiwi:/disc2/hecht/lecon_de_C$ ./lesson
Hello World
Size of char 1, int 4 , float 4, double 8 , long 4 , long long 8
Size of ,tabd 80, p2i 4, p2d 4
routine false 0
0,routine True 1
1, 2,routine True 2
2,routine True 3
3 ou 4,routine True 4
3 ou 4,n’27 5 Value 5
n’27 4 Value 4
n’27 3 Value 3
n’27 2 Value 2
n’27 1 Value 1
Somme en double 5.000000
d = 15.000000
p_file_r: dans lesson.c file= routines.c
p_file_l: dans p_file_r file= lesson.c
p_file_l: dans lesson.c file= lesson.c
’CouCou ?’
entrez un entier et un double ? 123 123.02
i = 123 d= 123.020000
hecht@kiwi:/disc2/hecht/lecon_de_C$
37
4 C++, 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. Pour avoir une liste à jour lire la page http://www.developpez.com/c/cours/.
Bien sur, vous pouvez 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 les connaı̂tre, il suffit de comprendre ce qui
est écrit après) :
— le lexique infomatique à connaitre par coeur :
Mémoire Répertoire
Octet Make
Type Editeur de texte
Adresse Operateur de copie
Ecriture mémoire A↵ectation par copie
Lecture mémoire Conversion
A↵ectation Evaluation
Fonction Execution
Paramètre Executable
Pointeur Shell script
Référence Processus
Argument Entre-Sortie
Variable Fichier
Polymorphisme Dynamique
Surcharge d’opérateur Statique
Méthode Iteration
Objet Boucle
Classe Recursivité
Instance Implementation
Opérateur Généricité
Conversion Hérédité
Sémantique Algorithme
Mémoire cache Pile
Mémoire secondaire Debugger
Terminal Compilateur
Bibliothèque Préprocesseur
Edition de lien Complexité
Compilation url
38
Exemple de phase à comprendre : Une variable locale et dynamique d’une fonction
n’existe en mémoire que pendant l’exécution la fonction, si la fonction est récursive cette
fonction peux être plusieurs fois en mémoire comme cette variable, lorsqu’une variable
locale statique d’une fonction existe toujours pendant le temps d’exécution et est unique.
— 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 comme ⌧ 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 méthodes 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 di↵é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 une expression de 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 (⌧ cast en Anglais) 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 conver-
sion 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;}
39
Les types de base du C++ sont respectivement : bool, 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 et les constantes (objet non modifiable). 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
T &&a T &a une référence sur une rvalue de type T qui ne sera jamais util
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
Pour l’utilisation des pointeurs sur des membres de classe C, ( voir le point [Stroustrup-1997,
C.12, page 853, ] pour plus de details).
Où C est une classe avec les deux membres func T2U et data T.
40
les fonctions template, Voilà, un exemple complète avec trois fichiers a.hpp,a.cpp,tt.hpp,
et un Makefile dans http://www.ann.jussieu.fr/˜hecht/ftp/cpp/l1/a.tar.gz
.
Remarque, pour déarchiver un fichier xxx.tar.gz, il suffit d’entrer dans une fenêtre shell
tar zxvf xxx.tar.gz.
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 :
41
Pour faire les trois choses en même temps, entrez :
CXX=g++
CXXFLAGS= -g
LIB=
%.o:%.cpp
(caractere de tabulation -->/) $(CXX) -c $(CXXFLAGS) $ˆ
tt: a.o tt.o
(caractere de tabulation -->/) $(CXX) 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 :
42
— 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.
Ecrire un Makefile pour compile et tester tous les programmes http://
Exercice 3.
www.ann.jussieu.fr/˜hecht/ftp/cpp/l1/exemple_de_base
43
44
4.3 Compréhension des constructeurs, destructeurs et des passages d’argu-
ments
Faire une classe T avec un constructeur par copie et le destructeur, et la copie
par a↵ectation, et copie par déplacement : qui imprime quelque chose comme
par exemple :
class T { public:
T() { cout << "Constructeur par défaut " << this << "\n"}
T(const T & a) { cout <<"Constructeur par copie "
<< this << "\n"}
T(T && a) { cout <<"Constructeur par déplacement "
<< this << " = " << &a << "\n"}
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:
Exercice 4. 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(T && a) {
p=a.p;
a.p=0;
cout << "Constructeur par move "<< this
<< " p=" << p << "\n"}
˜T() {
cout << "destructeur "<< this
<< " p=" << p << "\n";
delete p;}
T & operator=(T & a) {
cout << "copie par affectation "<< this
<< "old p=" << p45
<< "new p="<< a.p << "\n";
delete p;
4.4 Quelques règles de programmation
Malheureusement, il est très facile de faire des erreurs de programmation, la syntaxe du C++
n’est pas toujours simple à comprendre et comme l’expressibilité du langage est très grande, les
possibilités d’erreur sont innombrables. Mais avec un peu de rigueur, il est possible d’en éviter
un grand nombre.
La plupart des erreurs sont dû à des problèmes des pointeurs (débordement de tableau, des-
truction multiple, oublie de destruction), retour de pointeur sur des variable locales.
Voilà quelques règles à respecté.
Dans une classe avec des pointeurs et avec un destructeur, il faut que les deux
opérateurs de copie (création et a↵ection) 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é.
Par contre dans ce cas, il faut programmer les deux opérateurs construction et a↵ectation par
copie.
E↵ectivement, 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 :
46
long & GetOk(Bug & a,int i){ return a.p[i];} // ok
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 !...
Dans une fonction, ne jamais retournez de référence ou le pointeur sur une
Règle 2.
variable locale
E↵ectivement, retourner une référence sur une variable local implique que l’on retourne l’adresse
mémoire de la pile, qui est libéré automatique en sortie de fonction, et qui est donc 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.fr/˜hecht/ftp/cpp/
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
#ifndef ASSERTION
#define ASSERTION(i) 0
#else
#include <cassert>
#undef ASSERTION
#define ASSERTION(i) assert(i)
#endif
#endif
47
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 e↵ectué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.
48
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.
49
==8133==
--8133-- run: /usr/bin/dsymutil "./Amain"
UNKNOWN task message [id 3229, to mach_task_self(), reply 0x2703]
UNKNOWN task message [id 3229, to mach_task_self(), reply 0x2703]
UNKNOWN task message [id 3414, to mach_task_self(), reply 0x2703]
--8133-- WARNING: unhandled syscall: unix:357
--8133-- You may be able to write your own handler.
--8133-- Read the file README_MISSING_SYSCALL_OR_IOCTL.
--8133-- Nevertheless we consider this a bug. Please report
--8133-- it at http://valgrind.org/support/bug_reports.html.
arg 0 = ./Amain
...
==8133==
p = 0x100000000
==8133== Conditional jump or move depends on uninitialised value(s)
==8133== at 0x1000011A1: main (Amain.cpp:20)
==8133==
==8133== Use of uninitialised value of size 8
==8133== at 0x1000011A7: main (Amain.cpp:21)
==8133==
*p = -17958193
p = 0x7fff5fbff974
*p = 10
p = 0x7fff5fbff970
*p = 20
p = 0x101000040
*p = 100
0
==8133==
==8133== HEAP SUMMARY:
==8133== in use at exit: 0 bytes in 0 blocks
==8133== total heap usage: 5 allocs, 5 frees, 3,204 bytes allocated
==8133==
==8133== All heap blocks were freed -- no leaks are possible
==8133==
==8133== For counts of detected and suppressed errors, rerun with: -v
==8133== Use --track-origins=yes to see where uninitialised values come from
==8133== ERROR SUMMARY: 21 errors from 5 contexts (suppressed: 0 from 0)
MBA-de-FH:s5 hecht$ 0
50
p print la valeur d’une variable, expression ou tableau : p x ou p *v@100 (affiche les
100 valeur du tableau défini par le pointeur v).
where montre la pile des appels
up monte dans la pile des appels
down descend dans la pile des appels
l listing de la fonction courante
l 10 listing à partir de la ligne 10.
info functions affiche toutes les fonctions connues, et info functions tyty n’af-
fiche que les fonctions dont le nom qui contient la chaı̂ne tyty,
info variables même chose mais pour les variables.
b main.cpp:100 défini un point d’arrêt en ligne 100 du fichier main.cpp
b zzz défini un point d’arrêt à l’entrée de la fonction zzz
b A::A() défini un point d’arrêt à l’entrée du constructeur par défaut de la classe A.
d 5 détruit le 5e point d’arrêt.
watch défini une variable à tracer, le programme va s’arrêter quand cette variable va
changer.
help pour avoir plus d’information en anglais.
Une petite ruse bien pratique pour débogger, un programme avant la fin (exit). Pour cela
ajouter une fonction zzzz et utiliser la fonction atexit du système. C’est à dire qui votre
programme doit ressembler à :
#include <cstdlib>
void zzzz(){} // fonction vide qui ne sert qu’à débogger
int main(int argc,char **argv)
{
atexit(zzzz); // pour que la fonction zzzz soit exécutée
// avant la sortie en exit
b zzzz
51
GNU gdb 6.3.50-20050815 (Apple version gdb-563)
.... bla bla ...
There is absolutely no warranty for GDB. Type "show warranty" for details.
This GDB was configured as "i386-apple-darwin"...Reading symbols
for shared libraries .... done
(gdb) b main
Breakpoint 1 at 0x2c79: file Amain.cpp, line 16.
(gdb) b zzzz()
Breakpoint 2 at 0x2c36: file Amain.cpp, line 11.
(gdb) b Amain.cpp:19
Breakpoint 3 at 0x2c8e: file Amain.cpp, line 19.
(gdb) run
Starting program: /Users/hecht/work/Cours/InfoBase/l4/mainA2
Reading symbols for shared libraries . done
Breakpoint 1, main () at Amain.cpp:16
16 atexit(zzzz);
(gdb) c
Continuing.
Breakpoint 3, main () at Amain.cpp:19
19 A a(n),b(n),c(n),d(n);
(gdb) s on fait un step: on entre dans le constructeur
A::A (this=0xbffff540, i=100000) at A2.hpp:11
11 A(int i) : n(i),v(new K[i]) { assert(v);} // constructeur
(gdb) finish on sort du constructeur
Run till exit from #0 A::A (this=0xbffff540, i=100000) at A2.hpp:11
0x00002ca0 in main () at Amain.cpp:19
19 A a(n),b(n),c(n),d(n);
(gdb) n on fait un pas sans entrer dans les fonctions
21 for(int i=0;i<n;++i)
(gdb) l Affichage du source de la fonction autour du point courant
16 atexit(zzzz);
17
18 int n=100000;
19 A a(n),b(n),c(n),d(n);
20 // initialisation des tableaux a,b,c
21 for(int i=0;i<n;++i)
22 {
23 a[i]=cos(i);
24 b[i]=log(i);
25 c[i]=exp(i);
(gdb) suite de l’Affichage du source
26 }
27 d = (a+2.*b)+c*2.0;
28 }
(gdb) b 27 defini un point d’arrêt en ligne 27 après la fin de boucle
Breakpoint 4 at 0x2d50: file Amain.cpp, line 27.
(gdb) c continue
Continuing.
Breakpoint 4, main () at Amain.cpp:27
27 d = (a+2.*b)+c*2.0;
52
(gdb) d 4 supprime le point d’arrêt n°4
(gdb) p d affiche la valeur de d
$1 = {
n = 100000,
v = 0x4c9008
}
(gdb) p d.v affiche la valeur de d.v
$2 = (double *) 0x4c9008
(gdb) p *d.v@10 affiche les 10 valeurs pointées par d.v
$4 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
(gdb) p *a.v@10 affiche les 10 valeurs pointées par a.v
$5 = {1, 0.54030230586813977, -0.41614683654714241, -0.98999249660044542,
-0.65364362086361194, 0.28366218546322625, 0.96017028665036597,
0.7539022543433046, -0.14550003380861354,
-0.91113026188467694}
(gdb) p *b.v@10 affiche les 10 valeurs pointées par b.v
$6 = {-inf, 0, 0.69314718055994529, 1.0986122886681098, 1.3862943611198906,
1.6094379124341003, 1.791759469228055, 1.9459101490553132, 2.0794415416798357,
2.1972245773362196} on remarquera que log(0) == -inf et ne génère par d’erreur
(gdb) p *c.v@10 affiche les 10 valeurs pointées par c.v
$7 = {1, 2.7182818284590451, 7.3890560989306504, 20.085536923187668,
54.598150033144236, 148.4131591025766, 403.42879349273511,
1096.6331584284585, 2980.9579870417283, 8103.0839275753842}
(gdb) c
Continuing.
Breakpoint 2, zzzz () at Amain.cpp:11
11 cout << " At exit " << endl;
(gdb) c
Continuing.
At exit
CheckPtr:Max Memory used 6250.000 kbytes Memory undelete
0
Program exited normally.
(gdb) quit On sort de gdb
53
5 Exemples
54
6 Exemples
6.1 Le Plan IR2
Voici une modélisation de IR2 disponible à http://www.ann.jussieu.fr/˜hecht/ftp/
cpp/l1/R2.hpp qui permet de faire des opérations vectorielles et qui définit le produit scalaire
de deux points A et B, le produit scalaire sera défini par (A, B).
6.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 {
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) {}
55
R2 perp() const {return R2(-y,x);} // la perpendiculaire
}; // fin de la class R2
56
— Si un opérateur ou une methode d’une classe ne modifie pas la classe alors il est conseillé
de dire au compilateur que cette fonction est ⌧ constante en ajoutant le mots clef const
après la définition des paramètres.
— 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 classique 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.
— Il y a bien autre façon d’écrire ces classes. Dans l’archive http://www.ann.jussieu.
fr/˜hecht/ftp/cpp/R2.tgz , vous trouverez trois autres méthodes d’écriture de
cette petite classe.
v1) Avec compilation sépare où toutes les mèthodes et fonctions sont prototypées dans
le fichier R2-v1.hpp et elles sont défini dans le fichier R2-v1.cpp
v2) Sans compilation sépare où toutes les méthodes et fonctions sont définies dans le
fichier R2-v2.hpp.
v3) Version avec des champs privés qui interdisent utilisation direct de x et y hors de
la classe, il faut passer par les méthodes X() et Y()
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 determinant 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
57
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 2 C, zn+1 = zn ;
f 0 (zn )
58
6.2 Les classes tableaux
Nous commencerons sur une version didactique, nous verrons la version complète qui est dans
le fichier ⌧ tar compressé http://www.ann.jussieu.fr/˜hecht/ftp/cpp/RNM-v3.
tar.gz.
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 :
59
A operator+(const &a) const ; // addition
A operator*(K &a) const ; // espace vectoriel à droite
};
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.
60
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èrer 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.
61
La version est dans le fichier ⌧ tar compressé http://www.ann.jussieu.fr/˜hecht/
ftp/cpp/RNM-v3.tar.gz.
Nous voulons faire les opérations vectorielles classiques sur A, C,D tableaux de type KN<R>
suivante par exemples :
A = B; A += B ; A -= B ; A = 1.0; A = 2*.C ;
A = A+B; A = 2.0*C+ B; C = 3.*A-5*B;
A /= C; A *=C; // .. non standard ..
R c = A[i];
A[j] = c;
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.
Attention, il y a deux types de classe les classes avec un _ en fin de nom, et les autre sans. Les
classes avec un _ en fin de nom, ne font aucune opération d’allocation ou destruction de mémoire
dynamique, c’est-à-dire qui n’y a aucun opérateur new ou delete dans le code associé. Ces
classes tableaux peuvent être vue comme une généralisation de la notion de références sur un
tableau. Elle permet de donner un nom a une tranche d’un tableau, comme par exemple :
template<class R>
class KN_: public ShapeOfArray {
protected:
R *v; // le pointeur du tableau v[i ⇥ s], i 2 [0..N ()[
public:
typedef R K; // type of data
// les constructeurs a partir d’un pointeur
KN_(R *u,long nn); // pointeur + n
KN_(R *u,long nn,long s); // pointeur + n + valeurs avec un pas de s
KN_(const KN_<R> & u); // le constructeur par copy
// des méthodes
long N() const {return n;} // indice dans ı 2 [0..N ()[
bool unset() const { return !v;}
void set(R * vv,int nn,int st=1,int nx=-1) {v=vv;n=nn;step=st;next=nx;}
long size() const{return step?n*step:n;} // taille alloué
62
R max() const ; // max v[i s]
i2[0..N ()[
X
R sum() const ; // v[i s]
i2[0..N ()[
X
double norm() const ; // |v[i s]|2
i2[0..N ()[
⇣ X ⌘1/2
double l2() const ; // v[i s]2
i2[0..N ()[
X
double l1() const ; // |v[i s]|
i2[0..N ()[
double linfty() const ; // max |v[i s]|
i2[0..N ()[
⇣ X ⌘1/p
double lp(double p) const ; // v[i s]p
i2[0..N ()[
.
KN_ operator()(const SubArray & sa) const // la fonction pour prendre un
{ return KN_(*this,sa);} // sous tableau (SubArray(N,debut=0,pas=1))
.... etc
};
template<class R>
class KN :public KN_<R> { public:
typedef R K;
KN() : KN_<R>(0,0) {} // constructeur par défaut tableau le longueur nulle
KN(long nn) ; // construit un KN de longueur nn
KN(long nn, R * p) ; // construit un KN de longueur nn initiales le tableau p
KN(long nn,R (*f)(long i) ) ; // construit KN (f (i))i=[0..nn[
KN(long nn,const R & a) ; // construit un KN (a)i=[0..nn[
template<class S> KN(const KN_<S> & s); // construit par copie
template<class S>
KN(const KN_<S> & s,R (*f)(S )); // construit (f (s[i]))i=[0..s.N ()[
KN(const KN<R> & u); // construit par copie
void resize(long nn) ; // pour redimensionner le tableau
˜KN(){delete [] this->v;} // le destructeur
...
};
63
Et voilà de la classe VirtualMatrice qui modélise, une matrice. Une matrice sera une classe
dérive de VirtualMatrice, qui aura définit la méthode void addMatMul( const KN_<R> & x, K
qui ajoute à y le produit matrice vecteur.
template<class R>
struct VirtualMatrice { public:
virtual void addMatMul(const KN_<R> & x, KN_<R> & y) const =0;
...
// pour stocker les données de l’opération += A*x
struct plusAx { const VirtualMatrice * A; const KN_<R> & x;
plusAx( const VirtualMatrice * B,const KN_<R> & y) :A(B),x(y) {} };
virtual ˜VirtualMatrice(){}
};
#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"
64
using namespace std;
// definition des 6 types de base des tableaux a 1,2 et 3 parametres
typedef double R;
typedef KN<R> Rn;
typedef KN_<R> Rn_;
typedef KNM<R> Rnm;
typedef KNM_<R> Rnm_;
typedef KNMK<R> Rnmk;
typedef KNMK_<R> Rnmk_;
R f(int i){return i;}
R g(int i){return -i;}
int main()
{
const int n= 8;
cout << "Hello World, this is RNM use!" << endl << endl;
Rn a(n,f),b(n),c(n);
b =a;
c=5;
b *= c;
65
cout << "CopyAi3 = " << CopyAi3;
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;
66
// copie du sous tableaux
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 (Gi+1 , Gi+1 )C < " stop
67
p
Théorème 6.1. Notons EC (x) = (Ax x, x x)C , l’erreur dans la norme de A precondi-
tionné, où x est la solution du problème. Alors l’erreur à l’intération k un gradient conjugué
est majoré :
p !k
K C (A) 1
EC (xk ) 2 p EC (x0 )
KC (A) + 1
C
C C
où KC (A) est le conditionnement de la matrice CA, c’est à dire KC (A) = 1
C où 1 (resp. n)
n
est la plus petite (resp. grande) valeur propre de matrice CA .
Le démonstration est technique et est faite dans [Lascaux et Théodor, chap 8.3]. Voila comment
écrire un gradient conjugué avec ces classes.
68
class MatriceIdentite: VirtualMatrice<R> { public:
typedef VirtualMatrice<R>::plusAx plusAx;
MatriceIdentite(int n):VirtualMatrice<R> (n) {};
void addMatMul(const KN_<R> & x, KN_<R> & Ax) const { Ax+=x; }
plusAx operator*(const KN<R> & x) const {return plusAx(this,x);}
};
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(int n) :VirtualMatrice<R>(n) {};
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];
}
69
{
int n=10;
Rnm A(n,n),C(n,n),Id(n,n);
A=-1;
C=0;
Id=0;
Rn_ Aii(A,SubArray(n,0,n+1)); // la diagonal de la matrice A sans copy
Rn_ Cii(C,SubArray(n,0,n+1)); // la diagonal de la matrice C sans copy
Rn_ Idii(Id,SubArray(n,0,n+1)); // la diagonal de la matrice Id sans copy
for (int i=0;i<n;i++)
Cii[i]= 1/(Aii[i]=n+i*i*i);
Idii=1;
cout << A;
Rn x(n),b(n),s(n);
for (int i=0;i<n;i++) b[i]=i;
cout << "GradienConjugue preconditionne par la diagonale " << endl;
x=0;
GradienConjugue(A,C,b,x,n,1e-10);
s = A* x ;
cout << " solution : A*x= " << s << endl;
cout << "GradienConjugue preconditionnee par la identity " << endl;
x=0;
GradienConjugue(A,MatriceIdentite<R>(n),b,x,n,1e-6);
s = A* x ;
cout << s << endl;
}
{
cout << "GradienConjugue laplacien 1D par la identity " << endl;
int N=100;
Rn b(N),x(N);
R h= 1./(N-1);
b= h;
b[0]=0;
b[N-1]=0;
x=0;
R t0=CPUtime();
GradienConjugue(MatriceLaplacien1D(n),MatriceIdentite<R>(n) ,b,x,N,1e-5);
cout << " Temps cpu = " << CPUtime() - t0<< "s" << endl;
R err=0;
for (int i=0;i<N;i++)
{
R xx=i*h;
err= max(fabs(x[i]- (xx*(1-xx)/2)),err);
}
cout << "Fin err=" << err << endl;
}
return 0;
}
70
6.2.7 Sortie du test
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
71
Modifier, l’exemple GradConjugue.cpp, pour résoudre le problème sui-
vant, trouver u(x, t) une solution
@u
u = f; dans ]0, L[
@t
pour t = 0, u(x, 0) = u0 (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 !
M↵ = ↵wi wj + (wi )0 (wj )0
]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 de bloc => destruction de la variable file
// => fermeture du fichier
...
72
7 Méthodes d’éléments finis P1 Lagrange
7.1 Formules de Green
Grâce à la densité de D(⌦) dans H 1 (⌦), on peut démontrer la formule de Green pour les
fonctions de H 1 (⌦) :
Z Z Z
1 1 @v @u
8u 2 H (⌦) , 8v 2 H (⌦) , u dx = v dx + u v ni d , (1)
⌦ @xi ⌦ @xi @⌦
où ni est la ième composante de n la normale extérieur unitaire, et est la mesure du bord.
En utilisant les notations du points ?? page ??, et toujours grâce à la densité, et en sommant
sur les composantes, pour les fonctions vectorielles de H(div, ⌦) = {v 2 L2 (⌦)d /r.v 2 L2 (⌦)},
on peut écrire la formule de Stokes avec le gradient r et la divergence r. :
Z Z Z
1
8u 2 H (⌦) , 8v 2 H(div, ⌦) , u r.v dx = (ru).v dx + u(v.n) d , (2)
⌦ ⌦ @⌦
La formule précédente avec v et ru, nous donne donc :
Z Z Z
2 1 @u
8u 2 H (⌦) , 8v 2 H (⌦) , ( u) v dx = ru.rv dx + v d . (3)
⌦ ⌦ @⌦ @n
73
7.3 Espace affine, convexifié, et simplexe
Définition
Pn 7.1. Dans un espace affine réel A, le barycentre de ( i , Pi )i=1,n 2 (R ⇥ A)n telle que
i=1 i 6= 0 est l’unique point G = bar(( i , Pi )i=1,n ) 2 A telle que
n
X X n
! !
8O 2 A, i OP i = ( i )OG.
i=1 i=0
Définition 7.2. Par définition, si f est une fonction affine de A 7! B , alors cette fonction
commute avec les barycentres, c’est-à-dire f (bar(( i , Pi )i=1,n )) = bar(( i , f (Pi ))i=1,n ), ou si
l’espace affine est plongé dans un espace vectoriel, on a
n
X n
X n
X
si i = 1, alors f( i xi ) = i f (xi ) (9)
i=1 i=1 i=1
Définition 7.3. Le convexifié d’un ensemble S de points de IRd est noté C(S) est le plus petit
convexe contenant S et si l’ensemble est fini (i.e. S = {xi , i = 1, .., n}) alors nous avons :
( n n
)
X X
C(S) = i
ix : 8( i )i=1,..,n 2 IRn+ , tel que i = 1
i=1 i=1
.
Définition 7.4. Un k-simplexe (P 0 , ..., P k ) est le convexifié des k + 1 points de IRd affine inde-
pendant (donc k d)
— un sommet est un 0-simplexe,
— une arête ou un segment est un 1-simplexe,
— un triangle est un 2-simplexe,
— un tétraèdre est un 3-simplexe ;
La mesure signée d’un d-simplexe K = (P 0 , ..., P d ) en dimension d est donnée par
P10 . . . P1d
1 ! ! 1d .. ..
mes(P 0 , ..., P d ) = det P 0 P 1 ... P 0 P d = det . 0 . . . .
d! d! Pd . . . Pdd
1 ... 1
74
— des hyperfaces seront l’ensemble des (d + 1) hyperfaces ((d 1)-simplexe) ,
Définition 7.5. Les coordonnées barycentriques d’un d-simplex K = (P 0 , ..., P d ) sont des d + 1
fonctions affines de Rd dans R noté K i , j = 0, ..., d et sont défini par
d
X d
X
8x 2 R ; d
x= K i
i (x)P ; et K
i (x) =1 (10)
i=0 i=0
K j
on a i (P ) = ij où ij est le symbole de Kroneker. Et les formules de Cramers nous donnent :
K 1d+i ni,1 n! !
r i (x) = P P i,2 ^ ... ^ P ni,1 P ni,d
|K|d!
où |K| est la mesure signée de K, les (ni,j )dj=1 pour i fixé est la suite croissante des nombres
sans l’élément i, c’est-à-dire (ni,1 , ..., ni,d ) = (0, ..., i 1, i + 1, ..., d), où le produit vectoriel est
l’application (d 1)-linéaire alternée (Rd )d 1 7! Rd qui est la généralisation du produit vectoriel
dans Rd , et qui est définie par la formule suivante :
8y 2 Rd ; x1 ^ ... ^ xd 1 .y = det|x1 , ..., xd 1 , y|
Démonstration.
P10 . . . P1i 1
y1 P1i+1 ... P1d
1d .. .. .. .. ..
D K (x)y = det . ... . . . ... .
i
|K|d! Pd0 . . . Pdi 1 i+1
y d Pd ... Pdd
1 ... 1 0 1 ... 1
P10 . . . P1i 1
P1i+1 . . . P1d y1
1d d+i .. .. .. .. ..
= det . 0 . . . . . ... . .
|K|d! Pd . . . Pdi 1
Pdi+1
... d
Pd yd
1 ... 1 1 ... 1 0
n n
P1 i,1 . . . P1 i,d y1
1d d+i .. .. ..
= det . ... . .
ni,1 ni,d
|K|d! Pd ... Pd yd
1 ... 1 0
n n n! n n!
P1 i,1 P1 i,1 P1 i,2 . . . P1 i,1 P1 i,d y1
1i .. .. .. ..
= det . . ... . .
|K|d! n n n! n n!
Pd i,1 Pd i,1 Pd i,2 . . . Pd i,1 Pd i,d yd
1 0 ... 0 0
1i+d ni,1 n! !
= P P i,2 ^ ... ^ P ni,1 P ni,d .y
|K|d!
75
Donc si d = 2 nous avons donc
1i ni,1 n!
r K
i (x) = P P i,2 ?
2|K|
! !
où P Q? est la rotation de ⇡/2 du vecteur P Q. Ce qui donne
K +1 ! 1 ! +1 !
r 0 (x) = P 1 P 2? , r K
1 (x) = P 0 P 2? , r K
2 (x) = P 0 P 1?
2|K| 2|K| 2|K|
ce que l’on peut réécrire comme avec la convention P 3 = P 0 et P 4 = P 1 :
+1 !
r K
i (x) = (P i+1 P i+2 )? , (11)
2|K|
Et si d = 3 alors on a
K 1i+1 ni,1 n! !
r i (x) = P P i,2 ^ P ni,1 P ni,3
6|K|
avec la numérotation des sommets des faces suivante : (n0,j )j=1,2,3 = (1, 2, 3), (n1,j )j=1,2,3 =
(0, 2, 3), (n2,j )j=1,2,3 = (0, 1, 3), et (n3,j )j=1,2,3 = (0, 1, 2).
Pour supprimer le 1i+1 il suffit d’utiliser la numérotation des sommets des faces suivante :
Ce qui donne :
K 1 ! !
r i (x) = P ñi,1 P ñi,2 ^ P ñi,1 P ñi,3 (14)
6|K|
d
X
K̂ K̂
0 (x̂) =1 x̂i , et j (x̂) = x̂j pour j = 1, · · · , d (15)
i=1
Démonstration. Il suffit juste de remarquer que comme f est affine, elle commute avec les
barycentres et que x est le barycentres de ( K i 0 d
i (x), P ), i = 0, · · · , d car les (P , ..., P forme une
base affine de A
76
7.4 Formule d’intégration
Let D be a N -dimensional bounded domain. For an arbitrary polynomials f of degree r, if we
can find particular points ⇠~j , j = 1, · · · , J in D and constants !j such that
Z XL
f (~x) = |D| !` f (⇠~` ) (17)
D `=1
then we have the error estimation (see Crouzeix-Mignot (1984)), and then there exists a constant
C > 0 such that,
Z XL
f (~x) |D| !` f (⇠~` ) C|D|hr+1 (18)
D `=1
77
Et donc Z Z
1 1 + ij
i = |K| , et i j = |K| (20)
K (d + 1) K (d + 1)(d + 2)
si n1 = 0 c’est vrai, puis il suffit de faire une récurrence sur n0 ,pour tout n1 . L’héritage,
quelque soit n1 >= 0, on suppose la propriété vraie pour n0 quelque soit n1 >= 0 en
intégrant par partie et en utilisant l’héritage pour n1 + 1 et n0 on a :
Z 1 Z 1
n1 n0 +1 n0 + 1 n0 + 1 (n1 + 1)!n0 ! n1 !(n0 + 1)!
x (1 x) = xn1 +1 (1 x)n0 = =
0 n1 + 1 0 n1 + 1 (2 + n0 + n1 )! (2 + n0 + n1 )!
K̂d
En appliquant l’hypothèse de récurrence et en remarquons que d = xd on a :
Z d
Y Qd 1 Z 1
(d 1)! ni ! Pd 1
ni
i = |K̂d 1 | Pi=0
d 1
xnd d (1 xd ) d 1+ i=0 ni
dxd (24)
K̂d i=0 (d 1+ i=0 ni )! 0
Il suffit appliquer la formule dans le cas d = 1, et |Kd | = |Kd 1 |/d pour avoir
Z d
Y Q P
ni d(d 1)! di=01 ni ! nd !(d 1 + di=01 ni )!
i = |K̂d | P P (25)
K̂d i=0 (d 1 + di=01 ni )! (d + di=0 ni )!
78
7.5 Le maillage
Commençons par définir la notion de maillage simplexial.
Définition 7.8. Un maillage simplexial Td,h d’un ouvert polygonal Oh de IRd est un ensemble
de d simplex K k de IRd pour k = 1, Nt (triangle si d = 2 et tétraèdre si d = 3), tel que
i j
l’intersection de deux d-simplex distincts K , K de Td,h soit :
— l’ensemble vide,
— ou p-simplex commun à K et K 0 avec p d
def [
Oh = K (26)
K2Td,h
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].
Où g est la représentation de condition aux limites de Dirichlet, V0 est l’espace discret des
fonctions avec limites de Dirichlet homogène (nulle), a la forme bilinéaire du problème et l la
forme linéaire.
On notera V l’espace discret associé sans condition aux limites de Dirichlet, et soit B = {wi , 2
I} la base associer de V , Pde plus la base B0 = {wi , 2 I0 } de V0 est telle que I0 ⇢ I.
Généralement, on a g = j2I\I0 gj wi et gi = 0 pour i 2 I0 , notre problème est trouver le
P
vecteur uj tel que u = j2I uj wj and
X 9
8i 2 I0 a(wj , wi )uj = l(wi ) =
j2I (28)
;
8i 2 I \ I0 ui = g i
Nous pouvons encore écrire notre problème comme suit :
(AU = B)i , i 2 I0
(29)
(U = G)i , i2/ I0
où A = (aij ) avec aij = a(wj , wi ) et où B = (bi ) avec bi = l(wi ), et G = (gi ).
79
La méthode mathématique est résoudre le problème le suivant trouver les ui pour i 2 I0 , telle
que X X
a(wj , wi )uj = l(wi ) a(wj , wi )gj (30)
j2I0 j2I\I0
La seule difficulté est cette méthode de résolution est qu’elle dur à programmer. Donc voici des
méthodes facile à programmer.
||I0 (U" U )|| ||(I0 AI0 + I ) 1 || ||A|| ||I (U" G)|| (35)
d’où
||U" U || ||I0 (U" U )|| + ||I (U" U )|| C0 ||I (U" G)|| (36)
Mais maintenant, à partir de (33) en mutilipiant à gauche par I et comme I I0 = 0, on a :
80
ce qui prouve l’inégalité (34).
Numériquement les nombres ont seulement 16 chi↵res significatifs en double précision, c’est-à-
dire que si l’on prend 1" = tgv = 1030 nous travaillons dans des espaces de nombre indépendant
est l’erreur est de l’ordre de 10 30 . Cela fonctionne numériquement car la pénalisation n’apparait
que sur la diagonal.
Niveau implémentation, il suffit d’ajoute les lignes suivantes :
Pour i 2 I \ I0 faire
a_ii = tgv ;
b_i = tgv * g_i
Attention dans les méthodes itérative de types gradiant conjugue, la première étape de l’al-
gorithme défini les conditions ou limites, si le vecteur initiale ne contient pas déjà les valeurs
de c’est condition aux limites, il donc faire plus de 1 itérations, voir modification de Gradient
conjugue comme suit :
7.6.2 Condition de Dirichlet dans les méthodes de minimisation (GC, GMRES, ...)
Si la méthode est base sur la minimisation de ||Ax b||C , alors généralement et si la méthode
n’utilise que le produit matrice vecteur , alors
il suffit d’ecrire comme produit matrice vecteur le produit I0 Ax et de imposer comme second
membre I0 B. la condition aux limite sera donné dans le vecteur d’initialisation de l’algorithme,
ce qui est très simple a programmer.
81
Après utilisation de la formule de Green, et en multipliant par v, le problème peut alors s’écrire :
Calculer un+1
h 2 Vh à partir de unh , où la donnée initiale u0h est interpolé P1 de u0 .
Z Z
ruh rvh = f vh , 8vh 2 V0h , (42)
⌦ ⌦
où l’opérateur ? de IR2 est défini comme la rotation de ⇡/2, ie. (a, b)? = ( b, a).
82
Le programme d’éléments finis sans matrice
K
sur l’espace des fonctions P1 (K) dans la base i ,i = 0, 1, 2.
2. Pour la méthde du gradient conjugue nous avons juste besoin de la
méthode addMatMul qui ajoute à y le produit matrice vecteur Ax,
c’est à dire :
y+ = M↵, x
Ce cas s’écrit mathématiquement :
Algorithme 2. Pour K 2 Td,h , pour i = 0, 1, 2 et pour j = 0, 1, 2 faire :
⇢
MijK xiK si iK
i 62
yi K += j
i
0 sinon
R
3. On peut calculer b = ( ⌦ wi fh )i = M1,0 fh en utilisant la formule où
fh estPle vecteur de la fonction fh qui est l’interpolé de f , c’est a dire
fh = i f (xi )wi et fh = (f (qi ))i .
4. La matrice A est simplement M0,1 .
Ax = b
83
trois références sur des sommets car il est impossible initialiser des références par défaut). Les
deux sont possibles, mais les pointeurs sont plus efficaces pour faire des calculs, d’où le choix de
trois pointeurs pour définir un triangle. Maintenant les sommets seront stockés dans un tableau
donc il est inutile de stocker le numéro du sommet dans la classe qui définit un sommet, nous
ferons une di↵érence de pointeur pour retrouver le numéro d’un sommet, ou du triangle du
maillage.
Un maillage (classe de type Mesh) contiendra donc un tableau de triangles (classe de type
Triangle) et un tableau de sommets (classe de type Vertex2), bien sur le nombre de tri-
angles (nt) , le nombre de sommets (nv), de plus il me paraı̂t naturel de voir un maillage
comme un tableau de triangles et un triangle comme un tableau de 3 sommets.
Remarque : Les sources de ces classes et méthodes sont disponible dans l’archive
http://www.ann.jussieu.fr/˜hecht/ftp/cpp/EF.tar.bz2
avec un petit exemple.
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 un label, et tester si elle est
nulle.
Listing 8: (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
84
7.9.2 La classe Vertex2 (modélisation des sommets 2d)
Il est maintenant naturel de définir un sommet comme un point de IR2 et un numéro logique
Label. Par conséquent, la classe Vertex2 va dériver des classes R2 et Label tout en héritant
leurs données membres et méthodes :
Nous pouvons utiliser la classe Vertex2 pour e↵ectuer les opérations suivantes :
Les trois champs (x,y,lab) d’un sommet sont initialisés par (0.,0.,0) par
Remarque 4. défaut, car les constructeurs sans paramètres des classes de base sont appelés
dans ce cas.
85
7.9.3 La classe Triangle (modélisation des triangles)
Un triangle sera construit comme un tableau de trois pointeurs sur des sommets, plus un
numéro logique (label). Nous rappelons que l’ordre des sommets dans la numérotation locale
({0, 1, 2}) suit le sens trigonométrique. La classe Triangle contiendra également une donnée
supplémentaire, l’aire du triangle (area), et plusieurs fonctions utiles :
— Edge(i) qui calcule le vecteur ⌧arête du triangle opposée au sommet local i ;
— H(i) qui calcule directement le gradient de la i coordonnée barycentrique i par la
formule :
(q j q k )?
r i | K = HK i
= (44)
2 aireK
où l’opérateur ? de IR2 est défini comme la rotation de ⇡/2, ie. (a, b)? = ( b, a), et où
les q i , q j , q k sont les coordonnées des 3 sommets du triangle.
// Transformation: K̂ 7! K
R2 operator()(const R2 & Phat) const {
const R2 &A =*vertices[0];
86
const R2 &B =*vertices[1];
const R2 &C =*vertices[2];
return (1-Phat.x- Phat.y)* A + Phat.x *B +Phat.y*C ;}
private:
Triangle(const Triangle &); // pas de construction par copie
void operator=(const Triangle &); // pas affectation par copy
public: // -- Ajoute 2007 pour un ecriture generique 2d 3d --
R mesure() const {return area;}
static const int nv=3;
};
87
7.9.4 La classe Seg (modélisation des segments de bord)
On fait la même type de classe que les triangles mais juste avec deux sommets, on utilisera
cette classe pour calculer les intégrales pour les conditions aux limites de type Neumann,
// Transformation: [0, 1] 7! K
R2 operator()(const R & Phat) const {
const R2 &A =*vertices[0];
const R2 &B =*vertices[1];
return (1-Phat)* A + Phat *B ;}
private:
Seg(const Seg &); // pas de construction par copie
void operator=(const Seg &); // pas affectation par copy
public:
R mesure() const {return l;}
static const int nv=2;
};
88
double a = K.l ; // la longueur de K
(Label) K ; // le label du triangle Seg
R2 G(T(0.5))); // le barycentre de K
Seg K;
K.init(v,ia,ib,lab); // initialisation d’un Seg avec les sommets
// v[ia],v[ib] et l’étiquette lab
// (v est le tableau des sommets)
class Mesh2
{
public:
typedef Triangle Element;
typedef Seg BorderElement;
typedef R2 Rd;
typedef Vertex2 Vertex;
int nv,nt, nbe;
R area,peri;
Vertex2 *vertices;
Triangle *triangles;
Seg * borderelements;
Triangle & operator[](int i) const {return triangles[CheckT(i)];}
Vertex2 & operator()(int i) const {return vertices[CheckV(i)];}
Seg & be(int i) const {return borderelements[CheckBE(i)];}
Mesh2(const char * filename); // read on a file
// to get numbering:
int operator()(const Triangle & t) const {return CheckT(&t - triangles);}
int operator()(const Triangle * t) const {return CheckT(t - triangles);}
int operator()(const Vertex2 & v) const {return CheckV(&v - vertices);}
int operator()(const Vertex2 * v) const{return CheckV(v - vertices);}
int operator()(const Seg & v) const {return CheckBE(&v - borderelements);}
int operator()(const Seg * v) const{return CheckBE(v - borderelements);}
89
Mesh2(const Mesh2 &); // pas de construction par copie
void operator=(const Mesh2 &); // pas affectation par copy
};
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 argu-
ment 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, Vertex2) 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
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.
#include <cassert>
#include <fstream>
#include <iostream>
#include "ufunction.hpp"
#include "Mesh2d.hpp"
90
assert(f.good());
}
for (i=0;i<nt;i++)
{
f >> iv[0] >> iv[1] >> iv[2] >> ir;
assert(f.good() && iv[0]>0 && iv[0]<=nv && iv[1]>0
&& iv[1]<=nv && iv[2]>0 && iv[2]<=nv);
for (int v=0;v<3;++v) iv[v]--;
triangles[i].init(vertices,iv,ir);
area += triangles[i].area;
}
for (i=0;i<nbe;i++)
{
f >> iv[0] >> iv[1] >> ir;
assert(f.good() && iv[0]>0 && iv[0]<=nv && iv[1]>0 && iv[1]<=nv);
for (int v=0;v<2;++v) iv[v]--;
borderelements[i].init(vertices,iv,ir);
peri += borderelements[i].l;
}
cout << " End of read: area = " << area << " perimeter: " << peri << endl;
}
L’utilisation de la classe Mesh pour gérer les sommets, les triangles devient maintenant très
simple et intuitive.
Listing 17: (utilisation de la classe Mesh2)
91
7.10 Le programme quasi générique
les sources sont dans http://www.ann.jussieu.fr/˜hecht/ftp/EF2D.tgz
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <map>
#include "ufunction.hpp"
#include "Mesh2d.hpp"
#include "RNM.hpp"
#include "GC.hpp"
#include "cputime.h"
// calcul de la matrice M K
void MatElement(const Element & K,R alpha,R beta,R matK[Element::nv][Element::nv])
{
const int nve = Element::nv;
const int d = Rd::d;
double clilj = 1./((d+2)*(d+1)); // formule magique
Rd Gradw[nve];
K.Gradlambda(Gradw);
R cgg=K.mesure()*beta, cuu=K.mesure()*alpha*clilj;
for(int i=0;i<nve;++i)
for(int j=0;j<=i;++j)
matK[j][i] = matK[i][j] = cgg*(Gradw[i],Gradw[j])+cuu* ( 1. + (i==j) );
}
92
MatLap(const Mesh & T,R a,R b) : Th(T),alpha(a),beta(b) {};
void addMatMul(const KN_<R> & x, KN_<R> & Ax) const
{
const int nve = Element::nv;
for(int i=0;i<nve;++i)
if ( ! K[i].onGamma() ) Ax[iK[i]] += matKx[i];
}
}
plusAx operator*(const KN<R> & x) const {return plusAx(this,x);}
};
assert(argc>1);
Mesh Th(argv[1]);
KN<R> x(Th.nv);
x=0.; // donne initial avec les conditions aux limites.
int ii=0;
for (int k=0;k<Th.nv;k++)
if(Th(k).onGamma()) ii++;
for (int k=0;k<Th.nv;k++)
93
e[k]=g(Th(k));
cout << " nb sommet on gamma = " << ii << endl;
R cpu0= CPUtime();
R eps;
int res:
res=GradienConjugue(A,Id, b,x,Th.nv,eps=1e-10);
R cpu1=CPUtime()-cpu0;
cout << " CPU = " << cpu1 << " second " << endl;
e -= x;
cout << "err= " << e.min() << " " << e.max() << endl;
{
ofstream f("x.sol");
f << x << endl;
}
return 0;
}
Si l’on veut utiliser la méthode GMRES pour la résolution, il suffit de remplace la ligne
GradienConjugue(A,Id, b,x,Th.nv,eps=1e-10);
par
94
paire d’objets quelconque avec de plus l’ordre lexicographique défini par défaut, et la fonction
make_pair(i,j) que crée des paires de i, j.
D’où le code suivant pour définir des matrices creuse de type map après avoir ajoute #include <map>
en tête de programme.
void BuildMatMap(MatriceCreuse &M, const Mesh & Th, const R alpha,const R beta)
{
typedef Mesh::Element Element;
const int nve=Element::nv;
int iK[nve];
R matK[nve][nve];
for (int k=0;k<Th.nt;k++)
{
const Element & K(Th[k]);
for(int i=0;i<nve;i++)
iK[i]=Th(K[i]);
MatElement(K,alpha,beta,matK);
// assemblage
for (int i=0;i<nve;++i)
for (int j=0;j<nve;++j)
if(fabs(matK[i][j])>1e-30)
M[make_pair(iK[i],iK[j])] += matK[i][j];
}
}
95
http://www.ann.jussieu.fr/˜hecht/ftp/EF2D.tgz.
Mais, malheuresement, ce code n’est pas très éfficace car le parcours d’une map est assez cher
nlog(n) est donc, nous allons optimiser le programme, en construisant une vrai classe matrice
creuse de nom SparseMatrix.
Nous allons faire une version simple mais très efficace, nous allons simplement stockes trois
tableaux i, j, a de taille le nombre de coefficients non nulle note nbcoef. On a donc pour une
matrice aij simplement ai[k],j[k] = a[k].
les membres et méthode de la classe SparseMatrix sont :
template<class R>
class SparseMatrix: public VirtualMatrice<R>
{ // A sparse matrice n ⇥ m
public:
int n,m;
int nbcoef; // nombre de trem non nulle
int *i; // tableau des indices des lignes
int *j; // tableau des indices des colonnes
R *a; // tableau dev valeurs des coef
template<class Mesh>
SparseMatrix(Mesh & Th); // un constructeur a partir d’un maillage
96
˜SparseMatrix() { delete [] i; delete [] j; delete [] a;}
private:
SparseMatrix(const SparseMatrix &);
void operator=(const SparseMatrix &);
};
template<class R>
SparseMatrix<R>::SparseMatrix(int nn,int mm, map<pair<int,int>,R> M)
:
VirtualMatrice<R>(nn,mm),
n(nn),
m(mm),
nbcoef(M.size()),
i(new int[nbcoef]),
j(new int[nbcoef]),
a(new R[nbcoef])
{
R cmm=0;
int k=0;
for (typename map<pair<int,int>,R>::const_iterator p=M.begin();
p != M.end();
++p, ++k)
{
this->i[k]=p->first.first;
this->j[k]=p->first.second;
this->a[k]=p->second;
// assert(i[k]<n && j[k] <m && i[k]>=0 && j[k]>=0);
cmm=max(cmm,this->a[k]);
}
cout << " nb coef = " << nbcoef << " c max = "<< cmm << endl;
assert(k==this->nbcoef);
}
Et voilà la fonction qui recherche le pointeur sur coefficient ii, jj par dichotomie si il existe.
Mais bien sur il est supposé que les éléments sont triés en i, j de manière lexicographique, et
donc cette algorithme est en o(log2 (nbcoef )).
template<class R>
R * SparseMatrix<R>::pcoef(int ii,int jj)
{
// pas de probleme avec les bornes car
// on supprime le milieu.
int k0=0,k1=nbcoef-1;
while (k0<=k1)
{
int km=(k0+k1)/2;
int aa=0;
if ( i[km] > ii)
aa=-1;
else if (i[km]<ii)
aa=1;
97
else if ( j[km] > jj)
aa=-1;
else if (j[km]<jj)
aa=1;
if(aa<0) k1=km-1;
else if (aa>0) k0=km+1;
else { return a+km; }
}
return 0;
}
La construction avec un maillage sera faite dans le chapitre suivant voir la section 9.8.3 page
116.
98
8 Problème Non Linéaire
Le but de ce chapitre est de donnée les algorithme de bases pour résoudre des problèmes non
linéaire en grand dimension.
Ce problème pourra s’écrire généralement sous la forme : trouver u dans U tel que F (u) = 0
où F est une fonctionnelle de U 7! V et où U et V sont des espaces de Hilbert (souvent de
dimension fini).
Dans de nombreux cas le problème correspond à un problème de minimisation c’est à dire :
trouver u dans U tel que u = arg min J où J : U 7! R est la fonctionnelle à minimiser avec en
plus de containte ou non.
Ces algorithmes sont les méthodes de point fixe, les méthode de type gradient pour les problèmes
de minimisation et pour finir la méthode de Newtow. La plupart de ces méthode utilisent le
gradient ou la di↵érentielle, donc nous commenceront par introduire ces notions.
Remarque 8. Si U est un espace de fonctions réelle et si F (u) = f (u) où f est une fonction
réelle C 1 alors DF (u)h = f 0 (u)h, mais attention il n’est pas toujours évidant de définir l’espace
V associer, c’est souvent pour cela que l’on parle de calcul formel.
Théorème 8.2. Soit une fonction linéaire continue F : U 7! V , la di↵érentielle est D(F )(u) = F
et 8h 2 U, DF (u)h = F (h)
Q
Théorème 8.3. Soit une fonction n linéaire continue F : i Ui 7! V notons Fi (ui ) = F (u0 , · · · , ui , · · · , un )
où les uj pour j 6= i sont des constantes définissant Fi alors Fi est linéaire et l’on peut appliquer
le théorème précèdent , et DFi (ui ) = Fi .
P
Si G(u) = F (u, · · · , u) alors DG(u)h = i F (u, · · · , h, · · · , u) , où h remplace le ième para-
meter de F .
99
R
Les opérateurs linéaires classique intervenant dans les formules sont, , @,...
En appliquant, les règles précédente à Si A est un application linéaire symétrique dans une
espace de Hilbert munis du produit scalaire (., .) pour
1
J(u) = (Au, u) (b, u)
2
alors on a :
DJ(u)v = (Au, v) b(v), rJ(u) = Au b
De même pour une fonctionnelle plus compliqué :
Z
p
J(u) = 1 + ru.ru dx
⌦
on a donc formellement Z
ru.rh
DJ(u)h = p dx
⌦ 1 + ru.r
et la di↵érentielle seconde est
Z
(rh .rh1 ) (rh2 .ru)(rh1 .ru)
2
D J(u)(h1 , h2 ) = p 2 p 3 dx
⌦ 1 + ru.ru 1 + ru.ru
Définition 8.1 (Gradient). Le gradient rJ(u) d’une fonction J de H espace de Hilbert dans R
est défini par
(rJ(u), v) = DJ(u)v, 8v 2 H (45)
en utilisant le Théorème de représentation de Riez.
100
Remarque 9. Si H est une espace de dimension fini, alors généralement il est représenté par
une base B = {ei , i = 1, N } et des vecteurs de RN et dans ce cas il peut y avoir plusieurs
produit scalaire, et donc plusieurs gradients,
P
1. le produit scalaire canonique associer à la base B est défini par (u, v) = Ni=1 ui vi où
PN i
PN i
u = i=1 ui e et v = i=1 vi e , et donc le gradient rJ(u) est le vecteur defini par
N
X
rJ(u) = (@i J(u)) ei , où @i J(u) = DJ(u) ei (46)
i=1
101
8.3.1 Methode de Gradient
Définition 8.4 (Méthode de Gradient à pas simple). Soit n = 0 et u0 donné ainsi de une suite
de ⇢k
un+1 = un ⇢n rJ(un ), n=n+1 (52)
Remarque 13. Si la fonctionnelle est quadratique et coercive alors le ⇢n optimal dans la direction
hk peut être calculer avec la formule suivante :
(rJ(hk ) rJ(0), hk )
⇢k = (54)
(rJ(hk ), hk )
Le problème de recherche du ⇢ optimal correspondant à arg min⇢ J(un ⇢hn ) n’est pas simple,
il y a beaucoup heuristique en anglais cette recherche s’appel ⌧line search ,
102
9 Algorithmique
9.1 Introduction
Dans ce chapitre, nous allons décrire les notions d’algorithmique élémentaire, La notions de
complexité.
Puis nous présenterons les chaı̂nes et de chaı̂nages ou liste d’abord d’un point de vue mathématique,
puis nous montrerons par des exemples comment utiliser cette technique pour écrire des pro-
grammes très efficaces et simple.
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.
103
constant, quels que soient les entiers considérés (ils seront en e↵et codés sur 32 bits). En re-
vanche, lorsque l’on étudie des problèmes de calcul formel où la taille des nombres manipulés
n’est pas bornée, le temps de calcul du produit de deux nombre dépendra de la taille de ces
deux nombres.
On définit alors la taille de la donnée sur laquelle s’applique chaque problème par un entier lié
au nombre d’éléments de la donnée. Par exemple, le nombre d’éléments dans un algorithme de
tri, le nombre de sommets et d’arcs dans un graphe.
On évalue le nombre d’opérations élémentaires en fonction de la taille de la donnée : si ’n’ est
la taille, on calcule une fonction t(n).
Les critères d’analyse : le nombre d’opérations élémentaires peut varier substantiellement pour
deux données de même taille. On retiendra deux critères :
— analyse au sens du plus mauvais cas : t(n) est le temps d’exécution du plus mauvais cas
et le maximum sur toutes les données de taille n. Par exemple, le tri par insertion simple
avec des entiers présents en ordre décroissants.
— analyse au sens de la moyenne : comme le ⌧ plus mauvais cas peut en pratique
n’apparaı̂tre que très rarement, on étudie tm(n), l’espérance sur l’ensemble des temps
d’exécution, où chaque entrée a une certaine probabilité de se présenter. L’analyse
mathématique de la complexité moyenne est souvent délicate. De plus, la signification
de la distribution des probabilités par rapport à l’exécution réelle (sur un problème réel)
est à considérer.
On étudie systématiquemnt la complexité asymptotique, noté grâce aux notations de Landau.
idée 1 : évaluer l’algorithme sur des données de grande taille. Par exemple, lorsque n est
’grand’, 3n3 + 2n2 est essentiellement 3n3 .
idée 2 : on élimine les constantes multiplicatrices, car deux ordinateurs de puissances di↵érentes
di↵èrent en temps d’exécution par une constante multiplicatrice. De 3 ⇤ n3 , on ne retient
que n3
L’algorithme est dit en 0(n3 ).
L’idée de base est donc qu’un algorithme en 0(na ) est ⌧ meilleur qu’un algorithme en 0(nb )
si a < b.
Les limites de cette théorie :
le coefficient multiplicateur est oublié : est-ce qu’en pratique 100 ⇤ n2 est ⌧ meilleur que
5⇤n3 ? l’occupation mémoire, les problèmes d’entrées/sorties sont occultés, dans les algorithmes
numériques, la précision et la stabilité sont primordiaux. Point fort : c’est une notion indispen-
sable pour le développement d’algorithmes efficaces.
Les principales classes de complexité :
— logarithmique : logb n
— linéaire : an +P b
— polynomiale : ni=0 ai ni
— exponentielle : an
— factorielle : n!
Pour finir, il est aussi possible de parlé de la complexité mémoire, d’un algorithme.
104
double umax=u[0];
for (int i=1;i<N;++i)
umax=max(umax,u[i]);
int imax=0;
for (int i=1;i<N;++i)
if(u[imax] < u[i])
imax=i;
Ecrire un programme trouve les 5 plus grande valeurs du tableau u avec une
Exercice 8.
complexité de O(N ) en temps calcul et O(1) en mémoire additionnelle.
Remarque, tout ce passe bien car la valeur de u[i 1] n’est plus utilisé dans la boucle, pour les
i suivants mais dans le cas contraire unew [i + 1] = uold [i], 8i 2 {0, .., n 2}, il suffit de faire la
boucle à l’envers pour éviter le problème d’écrasement.
Ou si l’on ne veut pas changer le sens de parcours, alors il suffit de stocker les dépendances
dans une deux mémoires auxiliaires en u[i 1] et u[i], ce qui donne
105
// remumérotation
for (int i=0; i<n;++i )
v[i]=u[sigma[i]];
// remumérotation inverse
for (int i=0; i<n;++i )
v[sigma[i]]=u[i]; // remarquons v[sigma[i]]=u[i];
Et donc pour construction la permutation inverse stocker dans le tableau sigma1 il suffit
d’ecrire :
// permutation inverse
for (int i=0; i<n;++i )
sigma1[sigma[i]]=i;
106
Construction de l’image réciproque d’un tableau
1. Construction :
template<class T>
void HeapSort(T *c,int n) {
c--; // because fortran version array begin at 1 in the routine
int m,j,r,i;
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;}
}
}
}
107
9.6 Construction des arêtes d’un maillage
Rappelons qu’un maillage est défini par la donnée d’une liste de points et d’une liste d’éléments
(des triangles par exemple). Dans notre cas, le maillage triangulaire est implémenté dans la
classe Mesh qui a deux membres nv et nt respectivement le nombre de sommets et le nombre
de triangle, et qui a l’opérateur fonction (int j,int i) qui retourne le numero de du sommet
i du triangle j. Cette classe Mesh pourrait être par exemple :
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 :
108
Construction lente des arêtes d’un maillage Td,h
Cet algorithme trivial est bien trop cher (en O(9 n2 )) dès que le maillage a plus de 103 sommets
(plus de 9 106 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 9 ⇥ 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.
109
Construction rapide des arêtes d’un maillage Td,h
for(int t=0;t<Th.nt;t++)
Algorithme 5. for(int et=0;et<3;et++) {
int i= Th(t,SommetDesAretes[et][0]); // premier sommet ;
int j= Th(t,SommetDesAretes[et][1]); // second sommet ;
if (j < i) Exchange(i,j) // on oriente l’arête
bool existe =false; // l’arête n’existe pas a priori
for (int e=head_minv[i];e!=end_list;e = next_edge[e] )
// on parcourt les arêtes déjà construites
if ( arete[e][1] == j) // l’arête est déjà construite
{existe=true;break;} // stop
if (!existe) { // nouvelle arête
assert(nbe < nbex);
arete[nbe][0]=i,arete[nbe][1]=j;
// génération des chaı̂nages
next_edge[nbe]=head_minv[i],head_minv[i]=nbe++;}
}
delete [] head_minv;
delete [] next_edge;
return nbe;
}
110
— sinon adj[i+3k]=-1.
Voici donc l’algorithme pour construire l’ensemble des triangles ayant un sommet en commun :
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.
111
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 6. p = 3*k+j;
i = Th(k,j);
next_p[p]=head_s[i];
head_s[i]= p;}
Exercice 12. Optimiser le code en initialisant p =-1 et en remplaçant p = 3*j+k par p++.
Utilisation pour parcours optimal de tous les triangles ayant le sommet numéro i
112
9.8 Construction de la structure d’une matrice morse
Il est bien connu que la méthode des éléments finis conduit à des systèmes linéaires associés à
des matrices très creuses, c’est-à-dire contenant un grand nombre de termes nuls. Dès que le
maillage est donné, on peut construire le graphe des coefficients a priori non nuls de la matrice.
En ne stockant que ces termes, on pourra réduire au maximum l’occupation en mémoire et
optimiser les produits matrices/vecteurs.
class MatriceMorseSymetrique {
int n,nbcoef; // dimension de la matrice et nombre de coefficients non nuls
int *ligne,* colonne;
double *a;
MatriceMorseSymetrique(Maillage & Th); // constructeur
double* pij(int i,int j) const ; // retourne le pointeur sur le coef i, j
// de la matrice si il existe
}
113
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
114
Construction de la structure d’une matrice morse
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. 9.5 page 107).
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
115
creuse, en utilisant une recherche dichotomique comme suit :
Remarque : Si vous avez tout compris dans ces algorithmes, vous pouvez vous attaquer à la
plupart des problèmes de programmation.
template<class R>
template<class Mesh>
SparseMatrix<R>::SparseMatrix(Mesh & Th)
:
VirtualMatrice<R>(Th.nv),n(Th.nv),m(Th.nv), nbcoef(0),
i(0),j(0),a(0)
{
const int nve = Mesh::Element::nv;
int end=-1;
int nn= Th.nt*nve;
int mm= Th.nv;
KN<int> head(mm);
KN<int> next(nn);
KN<int> mark(mm);
int color=0;
mark=color;
head = end;
for (int p=0;p<nn;++p)
{
int s= Th(p/nve,p%nve);
next[p]=head[s];
head[s]=p;
}
nbcoef =0;
n=mm;
m=mm;
int kk=0;
116
for (int step=0;step<2;++step) // 2 etapes
// une pour le calcul du nombre de coef
// l’autre pour la construction
{
for (int ii=0;ii<mm;++ii)
{
color++;
int ki=nbcoef;
for (int p=head[ii];p!=end;p=next[p])
{
int k=p/nve;
for (int l=0;l<nve;++l)
{
int jj= Th(k,l);
if( mark[jj] != color) // un nouveau sommet de l’ensemble
if (step==1)
{
i[nbcoef]=ii;
j[nbcoef]=jj;
a[nbcoef++]=R();
}
else
nbcoef++;
mark[jj]=color; // on colorie le sommet j;
}
}
int ki1=nbcoef;
if(step==1)
HeapSort(j+ki,ki1-ki);
}
if(step==0)
{
cout << " Allocation des tableaux " << nbcoef << endl;
i= new int[nbcoef];
j= new int[nbcoef];
a= new R[nbcoef];
kk=nbcoef;
nbcoef=0;
}
}
}
117
10 Utilisation de la STL
10.1 Introduction
La STL un moyen pour unifier utilisation des di↵érents type de stockage informatiques que sont
les, tableau, liste, arbre binaire, etc....
Ces moyens de stockage sont appelés des conteneurs. A chaque type de conteneur est associé
un iterator et const iterator , qui une formalisation des pointeurs de parcours associés
au conteneur.
Si T est un type conteneur de la STL , alors pour parcourir sans modification les éléments du
conteneur l de type T , il suffit d’écrire
Pour parcourir avec modification possible les éléments du conteneur l de type T , il suffit d’écrire
<algorithm> copy(),find(),sort()
<array> array
<chrono> duration,time_point
<cmath> sqrt(),pow() complex,sqrt(),pow()
<complex>
<fstream> fstream,ifstream,ofstream
118
<iostream> istream,ostream,cin,cout
<map>
<memory> unique_ptr,shared_ptr,allocator
<random>
<regex> regex, smatch
<string>
<set>
<sstream>
<thread>
<unordered_map> unordered_map,unordered_multimap
<utility> move(),swap(),pair
<vector>
10.2 exemple
Le répertoire des sources : http://www.ann.jussieu.fr/˜hecht/ftp/cpp/stl4
// voila un exemple assez complet des possibilites de la STL
// --------------------------------------------------------
#include <list>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
#include <algorithm>
#include <iterator>
#include <complex>
// --------------------------------------//
// pour afficher un container de la stl //
// --------------------------------------//
template<typename T>
void show(const char * s,const T & l,const char * separateur="\\n")
{
cout << s << separateur;
for (typename T::const_iterator i=l.begin(); i != l.end(); ++i)
119
cout << ’\\t’ << * i << separateur;
cout << endl;
}
// -------------------------------------------------------- //
// pour afficher un container de la stl dans l’autre sens //
// -------------------------------------------------------- //
template<typename T>
void showr(const char * s,const T & l,const char * separateur="\\n")
{
cout << s << separateur;
for (typename T::const_reverse_iterator i=l.rbegin(); i != l.rend(); ++i)
// for ( auto i=l.rbegin(); i != l.rend(); ++i)
cout << ’\\t’ << * i << * separateur;
cout << endl;
}
// ----------------------------------------
/* ordre lexicographie sur les pair est deja dans la stl */
// -------------------------------------
/* donc inutile
template<typename A,typename B>
bool operator<(const pair<A,B> &a,const pair<A,B> & b) {
return a.first == b.first ? ( a.second < b.second ): a.first < b.first;
}
*/
int main() {
// ------------------------------------
// les vecteurs sont des tableaux ----
// -------------------------------------
{
vector<double> v; // un vector vide
v.push_back(1.);
v.push_back(-1.);
v.push_back(-5.);
v.push_back(4.);
// v.push front(-9); not implemented in vector
120
}
// ---------------------------------------------------
// ----- Test des listes doublement chainees----------
// ----------------------------------------------------
{
list<double> l;
l.push_back(1.);
l.push_back(5.);
l.push_back(3.);
l.push_back(6.);
l.push_back(3.);
list<double>::iterator l5=find(l.begin(),l.end(),5.);
cout << " taille de la liste : o(n) operation " << l.size() << endl;
cout << " liste vide ? " << l.empty() << endl;
// remarque:
// list< double>::iterator l3=l.begin()+3; interdit
// car un iterator de list n’est pas aleatoire (random)
assert( l5 != l.end());
l.insert(l5,1000.); // insert 1000. avant l5
show("list :",l," ");
list<double> ll(l); // copie la liste
show("list ll : ",l," ");
l.splice(l5,ll); // deplace la list ll dans l avant l5 (sans copie =>vide
ll)
cout << " ll.size = " << ll.size() << endl;
assert( ll.empty()) ; // la liste est vide
// remarque utile:
// empty pour savoir si un liste de vide O(1) operations
// car size() est en O(size()) operations.
ll.push_front(-3.); // ajoute un element a la liste ll
list<double> vv;
vv.push_back(1.);
vv.push_back(5.);
121
l.clear(); // vide la liste
}
// ----------------------------------- //
// exemple d’utilisation des map //
// ----------------------------------- //
{
map<string,string> m;
m["hecht"]="+33 1 44 27 44 11";
m["toto"]="inconnue";
m["ddd"]="a supprime";
m["aaaa"]="inconnueaaa";
m.erase("ddd");
map<string,string>::iterator ff=m.find("toto");
if (ff != m.end())
cout << " find " << *ff<< endl;
else
cout << " pas trouver " << endl;
{
pair<map<string,string>::iterator,bool> pbi
=m.insert(make_pair<string,string>("toto","inconnuetoto"));
cout << "insert map : " << *pbi.first << " " << pbi.second << endl;
}
{
pair<map<string,string>::iterator,bool> pbi
=m.insert(make_pair<string,string>("toto1","inconnuetoto"));
cout << "insert map : " << *pbi.first << " " << pbi.second << endl;
}
show("map",m);
cout << " -------------------------- \\n";
}
// -----------------
// un ensemble .
// ------------------
{
// -----------------------------------------------
// un petit exemple d’ensemble d’entier
// les elements doivent etre comparable ( operateur "<" existe)
set<int> S;
S.insert(1);
S.insert(2);
S.insert(2);
// le test apartenence est i\\inS \\texttt(S.find(i) != S.end())
show("set S :", S, " ");
for (int i=0;i<5;i++)
if ( S.find(i) != S.end())
cout << i << " est dans l’ensemble S\\n";
else
cout << i << " n’est pas dans l’ensemble S\\n";
cout << " -------------------------- \\n";
}
{
map<complex<double>,int > cc; // pas de bug ici
122
complex<double> cca=1;
// cc[cca]=2; // bug car pas de comparasion < sur des complex
}
/*
Attention dans une map tout depend de l’ordre.
Voilà une exemple qui ne fait pas ce qui est attendue generalement
*/
{
map<const char *,const char *> dicofaux; // ICI L’ORDRE UTILISE EST
l’adresse memoire.
const char * b="b";
const char * c="c";
char a[2];
a[0]=’a’; a[1]=0;
const char * d="d";
dicofaux[a]="1";
dicofaux[c]="4";
dicofaux[d]="3";
dicofaux[b]="2";
dicofaux["a"]="22";
show(" dico faux: ",dicofaux,"\\n");
}
// -------------------------------------------------
// ici modelisation d’un ensemble de arete defini
// par les numeros d’extermités (first et second)
// de plus a chaque arete on associe un entier
// ----------------------------------------------
// ------------------------------------------------
// une exemple d’ensemble d’arete (pair d’entier)
// numerote.
// -----------------------------------------------
{
map<pair<int,int>,int > edges;
int nbe =0, nbedup=0;
for (int i=0;i<10;i++)
{
pair<int,int> e(i%5,(i+1)%5);
// insertion de l’arete e avec la valeur nbe;
123
if( edges.find(e) == edges.end() )
{
nbe++; // ici nouvelle item
edges[e]=i;
}
else
nbedup++; // ici item existe deja
// remarque edges[e] peut etre un nouvel élément dans
// dans ce cas, il est initialisé avec T() qui est la valeur par defaut
// de toute classe / type T, ici on a T == int et int() == 0
#endif
}
cout << " nbe = " << nbe << " nbedup = " << nbedup << endl;
// du code pour voir les items qui sont ou non dans la map
for (int i=0;i<10;i++)
{
pair<int,int> e(i,(i+1));
if (edges.find(e)!=edges.end() )
cout << " trouver arete (" << e << ") \\n";
else
cout << " non trouver arete (" << e << ") \\n";
}
show(" les aretes ", edges);
}
// exemple d’utilisation d’une multi map ( oct 2010)
// pour invertion d’une function a valeur entiere non injective ..
// peut etre utile car ici l’ensemble d’arrive dans un intervalle tres
grand 23 1 1
{
int n= 10;
multimap<int,int> F1;
typedef multimap< int,int>::iterator MI;
// creation de la multi map
for(int i=0;i<n;++i)
{
int Fi = i*i%25;
F1.insert(make_pair(Fi,i));
}
show(" F1 ",F1);
for (int j=0;j<25;++j)
{
pair<MI,MI> cj =F1.equal_range(j);
cout <<" Fˆ-1("<< j << " ) = :" ;
for ( MI k=cj.first; k != cj.second; ++k)
cout << k->second << " ";
cout << endl;
}
}
// ----------------------------- //
// exemple de queue prioritaire //
// ----------------------------- //
{
cout << " queue prioritaire " <<endl;
priority_queue<int> pq;
// pour ajoute des valeurs
pq.push(10);
124
pq.push(1);
pq.push(5);
// pour voir la haut de la queue
while(!pq.empty()) // la queue est telle vide
{
int t=pq.top();
// pour supprimer le haut de la queue
cout << "top t = " << t << " and pop " <<endl;
pq.pop();
if (t== 5) { pq.push(6); cout << " push 6 n"; } // je mets 6 dans la queue
}
// il est impossible de parcourir les elements d’une queue
// ils sont caches
}
return 0;
}
#include <sstream>
#include <iostream>
using namespace std;
int main(int argc,char ** argv)
{
string str;
char * chaine = "1.2+ dqsdqsd q ";
// pour construire la chaine str
for (char * c=chaine; *c; ++c)
str+= *c; // ajoute
125
return 0;
}
126
11 Di↵é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 di↵é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 di↵é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
di↵érentielle (dx). La di↵é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 di↵érentielles à
zéro en début de programme sauf la di↵é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.
127
Les langages C,C++ , FORTRAN... ont la notion de constante. Donc si l’on
sait que, par exemple, a = 2 dans tous le programme et que a ne changera
pas, on n’est pas obligé de lui associer une di↵érentielle. Par exemple, la
fonction C
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 e↵et, 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
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;
128
x=0;
for( int i=1; i<= 3; i++) x=x+i/u;
cout << x << endl;
qui, en fait, calcule
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;
ce qui est réalisé simplement par l’instruction :
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 di↵érentiable par rapport à la variable u, mais est di↵érentiable
à droite et à gauche et les dérivées calculées comme ci-dessus sont justes.
Exercice 13. Ecrire le programme qui dérive une boucle while.
• Il existepdes fonctions non-di↵érentiables pour des valeurs particulières de la variable (par
exemple, u pour u = 0). Dans ce cas, toute tentative de di↵érentiation automatique pour ces
valeurs conduit à des erreurs d’exécution du type overflow ou NaN (not a number).
129
Pour obtenir dJ pour (u1 , u2 ) donnés, il faut exécuter le programme deux fois : une première
fois avec dx1 = 1, dx2 = 0, ensuite, une deuxième fois, avec dx1 = 0, dx2 = 1.
Une meilleure solution est de dupliquer les lignes dyi = · · · et les évaluer successivement pour
dxi = ij . Le programme correspondant :
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 di↵é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)
130
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) {}
};
131
{ return ddouble(sin(a.val),a.dval*cos(a.val));}
inline ddouble cos(const ddouble & a)
{ return ddouble(cos(a.val),-a.dval*sin(a.val));}
inline ddouble exp(const ddouble & a)
{ return ddouble(exp(a.val),a.dval*exp(a.val));}
inline ddouble fabs(const ddouble & a)
{ return (a.val > 0) ? a : -a;}
inline bool operator<(const ddouble & a ,const ddouble & b)
{ return a.val < b.val ;}
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;
132
template<class R> Diff<R> operator-(const Diff<R> & a,const Diff<R> & b)
{ return Diff<R>(a.val-b.val,a.dval-b.dval);}
int main()
{
typedef double R;
cout << " -- newtow (2) = " << Newtow(2.0,1.) << endl;
Diff<R> y(2.,1) , x0(1.,0.); // donne
Diff<R> xe(sqrt(2.), 0.5/sqrt(2.)); // solution exact
cout << "\n -- x = Newton " << y << " ," << x0 << ")"<< endl;
Diff<R> x= Newtow(y,x0) ;
cout << " x = " << x << " == " << xe << " = xe " << endl;
return 0;
}
133
Les resultats sont
134
12 Automates finis
Un automate (déterministe) fini est un quintuplet (S, A, S, ⌧, f ), où :
• S est un ensemble fini dont les éléments sont appelés des états,
• A est un alphabet,
• S est un état particulier, appelé état initial,
• ⌧ est une fonction de S ⇥ A dans S, appelée fonction de transition,
• f est une fonction de S à valeurs dans l’ensemble à deux éléments {succès, échec} (on dira
qu’un état X est acceptant si f (X) est succès).
Il faut comprendre l’automate comme une machine qui permet d’attribuer une valeur, succès
ou échec, à chaque mot ↵ de A⇤ . Au départ, la machine est, comme on peut s’en douter, dans
l’état initial. On lit de gauche à droite les caractères de ↵, et chaque caractère fait passer la
machine d’un état à un autre selon la fonction de transition. Quand le mot a été entièrement
lu, la machine se trouve dans un état X, et on dira que la machine accepte ou reconnaı̂t ↵ si
X est acceptant.
Le langage associé à ou reconnu par un automate est l’ensemble des mots acceptés par l’auto-
mate.
On appelle état d’erreur un état non acceptant E tel que, quel que soit a dans A, ⌧ (a, E)
soit égal à E. En d’autres termes, si au cours de la lecture d’un mot ↵, on entre dans un état
d’erreur, il est inutile de poursuivre la lecture car ↵ ne sera pas accepté.
Pour représenter les automates finis, on utilise généralement un graphe plan orienté dont les
sommets correspondent aux états et les flèches sont étiquetées par les caractères correspondant
aux transitions. Les états acceptants sont entourés deux fois, l’état de départ est signalé par
une flèche entrante. Finalement, pour alléger le dessin, il est entendu de ne pas représenter les
états d’erreur ainsi que les transitions qui y mènent.
Exemple 1:
Le langage reconnu par cet automate est le même que celui engendré par la grammaire de
l’Exemple 3.1.2, c’est-à-dire les nombres entiers positifs en écriture décimale.
Exemple 2:
Le langage reconnu par cet automate est l’ensemble des chaı̂nes de 0 et 1 dont les trois derniers
caractères sont des 1.
135
On obtient la notion d’automate (non déterministe) fini en autorisant l’existence de plusieurs
flèches partant du même état et portant la même étiquette, ainsi que de flèches sans étiquettes
ou plutôt étiquetées ", que l’on désignera sous le nom d’"-transitions.
Un chemin dans un tel automate est une suite de flèches consécutives, et le mot associé à ce
chemin est formé par les étiquettes des flèches. Le langage reconnu par l’automate est l’ensemble
des mots associés aux chemins menant de l’état initial à un état acceptant.
On observe que la terminologie est légèrement incorrecte, bien que traditionnelle, puisqu’un
automate déterministe n’est qu’un cas particulier d’automate non déterministe. Contraire-
ment à ce que l’on pourrait penser, l’ensemble des langages reconnus par des automates non
déterministes n’est pas plus grand que celui des langages reconnus par des automates déterministes.
En e↵et, la procédure suivante associe à tout automate fini (S, A, S, ⌧, f ) un autre automate
(S 0 , A0 , S 0 , ⌧ 0 , f 0 ), déterministe, qui reconnaı̂t le même langage :
• si S est l’ensemble des états du premier automate, on prendra S 0 égal à l’ensemble P(S) des
parties de S ; on choisit aussi A0 égal à l’alphabet A ;
• si P est un élément de S 0 et a appartient à l’alphabet A, ⌧ 0 (a, P ) est l’ensemble des extrémités
des chemins du premier automate qui sont associés au mot a et dont l’origine est dans P (c’est-
à-dire l’ensemble des états où l’on peut arriver en partant d’un état de P et en parcourant une
suite finie de flèches dont une et une seule est étiquetée a et les autres sont étiquetées ") ;
• l’état initial S 0 est l’ensemble des états où l’on peut aboutir à partir de l’état initial S du
premier automate par une suite finie d’"-transitions, en particulier il contient S ;
• enfin, un élément P de S 0 est acceptant s’il contient un état acceptant du premier automate.
On constate en e↵et que, dans le nouvel automate (S 0 , A0 , S 0 , ⌧ 0 , f 0 ), l’état dans lequel on se
trouve après lecture du mot ↵ est bien l’ensemble des états possibles après lecture de ↵ dans
le premier automate ; ↵ est donc reconnu par un des deux automates si et seulement si il est
reconnu par l’autre.
Exemple 3:
Cet automate est non déterministe puisque deux flèches étiquetées 1 partent de S. Le langage
qu’il reconnaı̂ t est formé des chaı̂nes de A = {0, 1} comportant au moins trois caractères et
dont le troisième caractère à partir de la fin est un 1. En appliquant l’algorithme précédent,
on constate que seules les 8 parties de {S, T, U, V } contenant S interviennent. Dans le schéma
ci-après, elles sont numérotées de la façon suivante :
136
On peut interpréter ainsi les états des deux automates : pour savoir si une chaı̂ne ↵ est dans le
langage ou non, il suffit de la lire caractère par caractère en retenant les trois derniers caractères
lus ; ceux-ci forment l’écriture binaire d’un entier compris entre 0 et 7 ; les états acceptants étant
ceux entre 4 et 7, la fonction de transition peut s’écrire
⌧ (k, N ) = (k + 2N ) mod 8.
Proposition 12.1. Si A est un automate fini, le langage reconnu par A est régulier.
137
qu’un état, l’état initial S, le langage reconnu est L⇤ ou l’ensemble vide, selon que S est
acceptant ou non. Dans les deux cas, le langage reconnu est régulier. Supposons maintenant la
proposition démontrée pour un automate à n états et que l’automate A a n+1 états. Notons Si ,
i 2 {1, · · · , m}, les extrémités des flèches d’origine S distinctes de S et ai , i 2 {1, · · · , m}, les
étiquettes de ces flèches. De même, notons Tj , j 2 {1, · · · , p}, les origines des flèches d’extrémité
S distinctes de S et bj , j 2 {1, · · · , p}, leurs étiquettes. Notons encore Ai l’automate obtenu à
partir de A en enlevant l’état S ainsi que toutes les flèches qui en viennent ou y aboutissent et
en choisissant Si comme nouvel état initial. Finalement, notons Aij l’automate obtenu à partir
de Ai en désignant Tj comme unique état acceptant. D’après l’hypothèse de récurrence, les
langages Li et Lij reconnus respectivement par Ai et Aij sont réguliers. Un chemin accepté par
A part de S et y revient un certain nombre de fois, puis ne passe plus par S. On voit que le
mot associé à un tel chemin appartient à
⇣[ ⌘⇤ ⇣[ ⌘
ai Lij bj [ L ai Li [ {"}
i,j i
ou à ⇣[ ⌘⇤ ⇣[ ⌘
ai Lij bj [ L ai L i ,
i,j i
selon que S est acceptant ou non. Dans les deux cas, le langage reconnu par A est encore
régulier, ce qui achève la démonstration.
Proposition 12.2. Pour tout langage régulier non vide, il existe un automate régulier
(i) qui le reconnaı̂t,
(ii) tel qu’aucune flèche n’a pour extrémité l’état initial,
(iii) tel qu’aucune flèche n’a pour origine l’unique état acceptant.
Nous représenterons un automate satisfaisant (ii) et (iii) par une boı̂ te avec une flèche rentrant
à gauche et une flèche sortant à droite. Par exemple, le schéma suivant
décrit l’automate dont le langage associé est formé de l’unique mot a de longueur 1. Les
opérations de concaténation, de réunion et de fermeture de Kleene sont représentées respecti-
vement par les schémas suivants :
138
Comme les langages finis peuvent s’obtenir par les opérations de concaténation et de réunion à
partir de langages formés d’un seul mot de longueur 1, on en déduit que la classe des langages
reconnus par des automates finis contient tous les langages réguliers.
Une autre façon de représenter les automates non déterministes finis utilise les grammaires. à
chaque état de l’automate, on associe un symbole non terminal, l’état initial devenant l’axiome.
à chaque transition de X vers Y étiquetée a (respectivement "), on associe la production
X ! aY (respectivement X ! Y ). Enfin, pour chaque état acceptant X, on écrit la production
X ! ". Une grammaire qui ne contient que des productions des types précédents est appellé
une grammaire régulière, et on peut donc résumer cette chapter par le
Théorème 12.3. Un langage est régulier si et seulement si il peut être décrit par une grammaire
régulière.
S ! (S)|"
Démonstration : Il est clair que les mots du langage sont formés d’un certain nombre de
parenthèses ouvrantes suivi du même nombre de parenthèses fermantes. Supposons qu’il existe
139
un automate déterministe fini A reconnaissant ce langage, et notons Sn l’état de A atteint à
partir de l’état initial après lecture de n parenthèses ouvrantes. Pour m 6= n, en partant de Sn
et en lisant m parenthèses fermantes, on doit atteindre un état non acceptant alors que la même
opération à partir de Sm mène à un état acceptant. On en déduit Sm 6= Sm et l’application :
n 7! Sn est une injection de l’ensemble des entiers naturels dans l’ensemble des états de A. La
contradiction vient du fait que ce dernier ensemble est fini.
Nous allons donc décrire une méthode d’analyse qui s’applique à une classe de grammaires
un peu plus générale. L’analyse syntaxique est l’opération qui, pour toute chaı̂ne terminale ↵,
permet de préciser si elle fait partie du langage et surtout, quand c’est le cas, de donner une
dérivation explicite menant de l’axiome à la chaı̂ne ↵. Cette dérivation est rarement unique,
mais nous dirons que la grammaire étudiée est non ambiguë si toutes les dérivations possibles
donnent le même diagramme syntaxique : il s’agit d’un arbre dont la racine est l’axiome et où
chaque nœ ud est indexé par un symbole, chaque dérivation élémentaire étant représentée par
des branches situées au-dessous 2 Tradionnellement, en informatique, les arbres sont représentés
racines en l’air et feuilles en bas. . . d’un nœ ud associé à une symbole non terminal, les feuilles
de l’arbre étant associées à des symboles non terminaux ou à ". Un exemple donnera une idée
de ce dont il s’agit. Nous considérons la grammaire
E ! F MP
P ! +E|"
M ! ⇤F M |"
F ! 0|1|2|3|4|5|6|7|8|9|(E)
et la chaı̂ne
(4 + 8) ⇤ 6 + 3 ⇤ 5.
Le diagramme suivant représente de façon évidente toutes les dérivations possibles de cette
chaı̂ne à partir de l’axiome E :
Par exemple,
ou
E ! F M P ! F ⇤ F M P ! F ⇤ F M + E ! F ⇤ F M + F M P ! (E) ⇤ F M + F M P ! · · ·
ou encore
La première de ces dérivations, où l’on a choisi à chaque fois de dériver le non terminal le plus
à gauche de la chaı̂ne, est appelé dérivation gauche. Il y a donc une correspondance biunivoque
entre dérivation gauche et diagramme syntaxique.
Le rôle de l’analyseur syntaxique est, à partir d’un flot de symboles fourni par l’analyseur
lexical, de déterminer la dérivation gauche menant de l’axiome à ce flot quand elle existe. Dans
une analyse par descente récursive, il y a un objectif courant et un symbole terminal courant.
L’objectif courant, qui est toujours un symbole, est au départ l’axiome. Pour remplir l’objectif
courant, deux cas se présentent :
2. *
140
• si c’est un symbole terminal a, il s’agit de vérifier que le symbole terminal courant, fourni par
l’analyseur syntaxique, est bien égal à a. Si c’est le cas, on passe à l’objectif suivant et au symbole
suivant dans le flot ; dans le cas contraire, on conclut que la chaı̂ne originale n’appartient pas
au langage ;
• si c’est un symbole non terminal X, on détermine, à l’aide de la valeur du symbole terminal
courant laquelle des productions dont le membre de gauche est X doit être appliquée. Les
symboles formant le membre de droite de la production choisie deviennent alors des objectifs
secondaires dont la résolution successive représentera celle de l’objectif initial.
Dans l’exemple précédent, les symboles fournis par l’analyseur lexical sont dans cet ordre (,
4, +, 8, ), ⇤, 6, +, 3, ⇤ et 5. L’objectif est d’abord de reconnaı̂tre un E. La seule production
possible étant : E ! F M P , le nouvel objectif devient donc de reconnaı̂tre successivement un F ,
un M et un P . Pour reconnaı̂tre un F , le symbole terminal courant étant une (, la production
choisie est : F ! (E). Il faut donc d’abord reconnaı̂tre une parenthèse ouvrante, ce qui se fait
simplement en passant au symbole terminal suivant qui est un 4. Le E se décompose de nouveau
en F M P , la reconnaissance du F consomme le symbole 4 et il faut maintenant reconnaı̂tre un
M alors que le symbole terminal courant est un +. Ceci impose le choix de la production :
M ! ". Ensuite, il faut reconnaı̂tre le P qui devient +E, et ainsi de suite.
à ce point, L contient la liste cherchée. En outre, il est clair que, pour toute chaı̂ne ↵ appartenant
à (N [ T )⇤ , la chaı̂ne " dérive de ↵ — ce que nous noterons V (↵) — si et seulement si ↵
appartient à L⇤ .
13 Interpréteur de formules
Le but de ce chapitre est de donner les outils informatiques pour manipuler des formules.
141
Dans un premier temps, nous allons prendre comme exemple la grammaire des expressions
E = T | T 0 +0 E
T = F | F 0 ⇤0 T
F = c | 0 (0 E 0 )0
Où les symbole non-terminaux de la grammaire sont E qui est une expression, T qui est un
terme, F qui est un facteur. Ces trois non terminaux qui sont définis par les trois règles. Et où
les trois symboles terminaux de la grammaire sont c qui représente un nombre et le symbole 0 (0
, resp. 0 )0 qui représente le caractère ( , resp. ) .
L’idée est très simple : nous allons supposer que nous disposons d’un analyseur lexical qui
découpe l’entrée standard (le texte) en symboles terminaux, qui lit le symbole terminal suivant
si le terminal est reconnu. Les symboles terminaux sont numérotés via un entier, les caractères
ont leur code ASCII et les nombres sont définis par le numéro c=257 et la fin de fichier par le
numéro EOF = - 1.
Cet analyseur lexical est modélisé par une classe Lexical composée de :
— sym le symbole courant ;
— void Nextsym() fonction qui lit le symbole terminal suivant qui peut être ici ’+’ ’*’
’(’ ’)’ c EOF ;
— void Match(int c) : si le symbole courant est c, la fonction lit le symbole suivant, sinon
elle génére une erreur ;
— bool IFSym(int c) retourne faux si le symbole courant n’est pas c, sinon elle lit le
symbole suivant et retourne vrai.
class Lexical { //
typedef double R;
int sym;
static const int c=257; // numéro du terminal nombre
static const int EOF=-1; // numéro du terminal EOF
istream & cin; // flot de lecture
R valeur; // Valeur du nombre
public:
Lexical(istream & s) :cin(s) // le constructeur définit le istream
{NextSym();} // et lit un symbole
142
{ cerr << " caract{è}re invalide " << char(s) << " " << s << endl;
exit(1);}
};
Une fois le problème de l’analyse lexicale résolu, nous allons nous occuper de la grammaire.
À chaque non-terminal nous allons associer une fonction booléenne qui retourne vrai si l’on
a trouvé le non-terminal, faux sinon. Nous utiliserons la fonction IFSym(c) pour tester les
terminaux. De plus, nous ne voulons pas faire de retour en arrière (un symbole d’avance, le 1
de LL(1) ) donc dans une concaténation AB si A est vrai et B est faux, il faut remettre l’état
du système avant l’appel de A, ce que l’on ne sait pas faire généralement. Donc, dans ce cas,
nous générons une erreur.
Il suffit de programmer chaque fonction non-terminale, en factorisant à gauche par non-terminaux,
de façon à factoriser tous les termes en commun dans les opération de concaténation.
Les concaténations E = AB, F = Ac , G = cA sont programmées comme suit :
bool E(){
if ( A() )
return Match(B(),’B()’);} // Erreur si B() est faux
bool F(){
if ( A() )
return Match(c); } // Erreur si c’est faux
bool G(){
if (IFSym(c))
return Match(A(),"A()");} // Erreur si A() est faux
0 0
L’opérateur ou dans P = C | c | ( donne
bool P() {
if (C()) return true;
else if(IFsym(c)) return true;
143
else if(IFsym(’(’)) return true;
else return false;}
bool Q() {
if (C()) return true;
else if(IFsym(c)) return true;
else if(IFsym(’(’)) return true;
else return true; } // la chaı̂ne vide rend vrai
E = T |T 0 +0 E F = c| 0 (0 E 0 )0
T = F |F 0 ⇤0 T en E = T ( 0 +0 E|")
F = c| 0 (0 E 0 )0 F = c| 0 (0 E 0 )0
Après cette modification élémentaire, il est trivial d’écrire le programme C++ suivant :
Malheureusement, cette technique ne marche pas dans tous les cas, il faut donc encore travailler.
144
La remarque fondamentale, qui donne le principe de fonctionnement et qui
définit les grammaires LL(1) est : si une règle de production commence par
Remarque 15.
un symbole s, alors la fonction associée retourne vrai ou génère une erreur
si le symbole courant est s.
Nous en déduisons la règle suivante :
Si deux expressions grammaticales commençent par un terminal commun
Règle 6. dans une opération logique du type | (ou), alors la grammaire n’est pas
LL(1).
E↵ectivement, la première expression retourne vrai, ou génère une erreur, donc la seconde
expression n’est jamais utilisée.
L’autre règle est simplement sur l’utilisation de la règle vide (").
La règle vide (") doit être mise en fin de chaı̂ne de l’opérateur logique | (ou),
Règle 7. car l’expression associée à la règle vide est toujours vraie. Pratiquement, si
l’on teste les autres branches du |, il faut que cela soit la dernière régle.
Maintenant nous pouvons définir une grammaire LL(1).
Une grammaire est dite LL(1) si le langage reconnu par le programme est le même que celui
défini par la grammaire.
E = T |T 0 +0 E
T = F |F 0 ⇤0 T
F = P | 0 (0 E 0 )0
P = c| 0 (0 c 0 ,0 c 0 )0
Exercice 14. Écrire une autre grammaire équivalente et qui est LL(1).
Mais une grammaire sans sémantique n’est pas vraiment utile.
Voici deux versions de la même grammaire avec l’évaluation des calculs. Le principe est d’ef-
fectuer les calculs seulement dans le cas où la fonction retourne vrai. Mais attention, deux
grammaires qui définissent le même langage peuvent avoir deux sémantiques di↵érentes :
E = T |T + E E = T |T + E
T = F |F ⇤ T et T = F |F ⇤ E
F = c|(E) F = c|(E)
145
13.1.1 Une calculette complete
Afin d’écrire une calculette complète qui calcule la valeur associé, Nous remarquons le problème
suivant lié à la sémentique et non à la syntaxe.
La grammaire de la calculette devrait etre :
E = T |T 0 +0 E|T 0 0 E
T = F |F 0 ⇤0 T |F 0 /0 T
F = c|(E)
Mais cette grammaire faite une associativité à gauche, c’est dire que l’évaluation de 1 1 + 2
est donné pas 1 (1 + 2). donc pour corrigé ce problème il suffit d’ajouter deux règles E et
T / qui correspondent aux expressions commençant par ou aux termes commençant par /.
E = T |T 0 +0 E|T 0 0 E
T = F |F 0 ⇤0 T |F 0 /0 T /
E = T |T 0 +0 E |T 0 0 E
T/ = F |F 0 ⇤0 T / |F 0 /0 T
F = c|(E)
Cette écriture est lourde et toutes boucles se font par récurrence, mais si l’on introduit un
nouvelle opérateur ⇤ dans la description des grammaires qui dit expression suivante doit être là
n fois (n 2 N) comme dans les expressions régulières, alors nous pouvons réécrit la grammaire
comme :
E = T ⇤ ( 0 +0 T | 0 0 T )
T = F ⇤ ( 0 ⇤0 F | 0 /0 F )
F = c|(E)
Voila un exemple complète de calculette, avec la fonction cos, la constante ⇡ et trois variables
x, y, r qui sont utiliser via les pointeurs xxxx,yyyy,rrrrr.
Le source de l’exemple suivante est disponible à : http://www.ann.jussieu.fr/˜hecht/
ftp/Calculette.cpp
#include <fstream>
#include <iostream>
#include <cstdio>
#include <sstream>
#include <cmath>
#include <cstdlib>
#include <cassert>
#include <cstring>
146
// F = c | ’(’ E ’)’ | FUNC1 ’(’ E ’)’ | GVAR | ’-’ F | ’+’ F ;
// Les fonctions FUNC1 implementées: cos,
double *xxxx,*yyyy,*rrrr;
const double PI=4*atan(1.0);
class Exp {
public:
typedef double R;
private:
int sym; // la valeur de symbole courant
static const int Number=257;
static const int FUNC1=258;
static const int GVAR=259;
istringstream cin; // contient la chaine de l’expression
public:
int c=cin.peek();
if (isdigit(c) || c == ’.’) { sym=Number; cin >> valeur;}
else if (c==’+’ || c==’*’ || c==’(’ || c==’)’ || c==EOF) {sym=c;cin.get();}
else if (c==’-’ || c==’/’ || c==’ˆ’ ) {sym=c;cin.get();}
else if (isspace(c)) {sym=’ ’; cin.get();}
else if (c==EOF ) sym=EOF;
else if (isalpha(c)) {
string buf;
buf += cin.get();
while (isalnum(cin.peek()))
buf += cin.get();
if(buf=="cos") { sym=FUNC1; f1=cos;}
else if (buf=="x") { sym=GVAR; pv=xxxx;}
else if (buf=="y") { sym=GVAR; pv=yyyy;}
else if (buf=="r") { sym=GVAR; pv=rrrr;}
else if (buf=="pi") { sym=Number; valeur=PI;}
else { cerr << " mot inconnu ’" << buf <<"’" << endl; exit(1);}
cout << "Id " << buf << " " << sym << endl;}
else { cerr << "caractere invalide " << char(c) << " " << c << endl; exit(1);}
}
// -----------------------------------------------------------------------
void Match(int c) { // le symbole courant doit etre c
if(c!=sym) { cerr << " On attendait le symbole " ;
if ( c == Number) cerr << " un nombre " ;
147
else cerr << "’"<<char(c)<<"’" ; exit(1);}
else NextSym();
}
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
148
cout << " T " << v <<endl;
return true;
}
// -----------------------------------------------------------------------
};
// -----------------------------------------------------------------------
assert(argc==2);
Exp exp(argv[1]);
Exp::R v; // pour declare une variable de type real de la calculette
exp.E(v);
cout << argv[1]<< "= "<< v << endl;
assert(exp.IFSym(EOF));
return 0;
}
149
Pour chaque type de fonction, on construira une classe qui dérivera de la classe Cvirt. Comme
tous les opérateurs doivent être définis dans une classe, une fonction sera définie par une classe
Cfonc qui contient un pointeur sur une fonction virtuelle Cvirt. Cette classe contiendra
l’opérateur ⌧ fonction d’évaluation, ainsi qu’une classe pour construire les opérateurs binaires
classiques. Cette dernière classe contiendra deux pointeurs sur des Cvirt qui correspondent
aux membres de droite et gauche de l’opérateur et la fonction de IR2 à valeur de R pour définir
l’opérateur.
Les programmes sources sont accessibles à l’adresse :
http://www.ann.jussieu.fr/˜hecht/ftp/cpp/lg/fonctionsimple.cpp.
#include <iostream>
// pour la fonction pow
#include <math.h>
typedef double R;
class Cvirt { public: virtual R operator()(R ) const =0;};
150
Fonction operator*(const Fonction & dd) const
{return new Coper(Mul,f,dd.f);}
Fonction operator/(const Fonction & dd) const
{return new Coper(Div,f,dd.f);}
Fonction operatorˆ(const Fonction & dd) const
{return new Coper(Pow,f,dd.f);}
};
Dans cet exemple, la fonction f = cos2 + (sin ⇤ sin + x4 ) sera définie par un arbre de classes
qui peut être représenté par :
151
La première est la fonction d’évaluation et la seconde calcule la fonction dérivée et retourne la
fonction nulle par défaut. Bien entendu, nous stockons la fonction dérivée (CVirt) qui ne sera
construite que si l’on utilise la dérivée de la fonction.
Nous introduisons aussi les fonctions de IR2 à valeurs dans R, qui seront modélisées dans la
classe Fonction2.
Les programmes sources sont dans les 2 URL
— http://www.ann.jussieu.fr/˜hecht/ftp/cpp/lg/fonction.cpp
— http://www.ann.jussieu.fr/˜hecht/ftp/cpp/lg/fonction.hpp
Toutes ces classe sont définies dans le fichier d’en-tête suivant :
#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;
};
152
class Fonction2 { // Fonction de deux variables
CVirt2 *p;
public:
operator CVirt2 *() const {return p;}
Fonction2 (CVirt2 *pp) : p(pp) {}
Fonction2 (R (*) (R, R)); // Création à partir d’une fonction C
Fonction2 (R x); // Fonction constante
Fonction2 (const Fonction2& f) : p(f.p) {} // Constructeur par copie
Fonction2 d1 () {return p->d1 ();}
Fonction2 d2 () {return p->d2 ();}
void setd (Fonction2 f1, Fonction2 f2) {p->md1 = f1; p->md2 = f2;}
R operator() (R x, R y) {return (*p)(x, y);}
friend class Fonction;
Fonction operator() (Fonction, Fonction); // Composition de fonctions
Fonction2 operator() (Fonction2, Fonction2);
static Fonction2 monome (R, int, int);
};
#endif
Maintenant, prenons l’exemple d’une fonction Monome qui sera utilisée pour construire les
fonctions polynômes. Pour construire e↵ectivement 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 .
153
public:
CMonome2 (R cc = 0, int k1 = 0, int k2 = 0) : c (cc), n1 (k1), n2 (k2) {}
R operator () (R x, R y) ; // return cxn1 y n2
CVirt2 *dex () {return n1 ? new CMonome2 (c * n1, n1 - 1, n2) : zero;}
CVirt2 *dey () {return n2 ? new CMonome2 (c * n2, n1, n2 - 1) : zero;}
};
Function2 Function2::monome (R x, int k, int l)
{return new CMonome2 (x, k, l);}
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 :
154
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
#include <cstdlib>
#include <iostream>
#include <string>
#include <map>
using namespace std;
typedef double R;
155
R operator()() const {cout << *p ; return 0;}
};
const ExpBase * f;
Exp() : f(0) {}
Exp(const ExpBase *ff) : f(ff) {}
Exp(R a) : f( new ExpConst(a)) {}
Exp(R* a) :f( new ExpPtr(a)) {}
R operator()() const {return (*f)();}
operator const ExpBase * () const {return f;}
Exp operator=(const Exp & a) const { return f->set(a.f);}
Exp operator,(const Exp & a) const { return new ExpOp2(Comma,*f,*a.f);}
Exp operator+(const Exp & a) const { return new ExpOp2(Add,*f,*a.f);}
Exp operator-(const Exp & a) const { return new ExpOp2(Sub,*f,*a.f);}
Exp operator-() const { return new ExpOp(Neg,*f);}
Exp operator!() const { return new ExpOp(Not,*f);}
Exp operator+() const { return *this;}
Exp operator/(const Exp & a) const { return new ExpOp2(Div,*f,*a.f);}
Exp operator*(const Exp & a) const { return new ExpOp2(Mul,*f,*a.f);}
156
Exp operator<=(const Exp & a) const { return new ExpOp2(Le,*f,*a.f);}
Exp operator>=(const Exp & a) const { return new ExpOp2(Ge,*f,*a.f);}
Exp operator==(const Exp & a) const { return new ExpOp2(Eq,*f,*a.f);}
Exp operator!=(const Exp & a) const { return new ExpOp2(Ne,*f,*a.f);}
};
Exp comp(R (*ff)(R ),const Exp & a){ return new Exp::ExpOp(ff,*a.f);}
Exp comp(R (*ff)(R,R ),const Exp & a,const Exp & b)
{ return new Exp::ExpOp2(ff,*a.f,*b.f);}
Exp comp(Exp::ExpOp2_::func ff,const Exp & a,const Exp & b)
{ return new Exp::ExpOp2_(ff,*a.f,*b.f);}
Exp print(const Exp & a) { return new Exp::ExpOp(Print,*a.f);}
Exp print(const string * p) { return new Exp::ExpPString(p);}
R While(const Exp::ExpBase & a,const Exp::ExpBase & b) {
R r=0;
while( a() ) { r=b();}
return r;
}
} tableId;
Remarque dans le code précédent, la table des symboles est juste une map de la STL, ou les
valeurs sont des paires de int le type du symblos et void * pointeur sur la valeur du symbole,
ici les types des symboles peuvent être de
—
157
%{
#include <iostream>
#include <complex>
#include <string>
#include <cassert>
using namespace std;
#ifdef __MWERKS__
#include "alloca.h"
#endif
#include "Expr.hpp"
int yylex();
%}
%union{
double dnum;
string * str;
const Exp::ExpBase * exp;
R (*f1)(R);
void * p;
}
%token WHILE
%token PRINT
%token GE
%token LE
%token EQ
%token NE
158
%%
start: code ENDOFFILE { leprogram=$1; return 0; }
;
code : instr
| code instr { $$=(Exp($1),$2);}
;
expr: expr1
| expr ’<’ expr {$$ = Exp($1)<Exp($3);}
| expr ’>’ expr {$$ = Exp($1)>Exp($3);}
| expr LE expr {$$ = Exp($1)<=Exp($3);}
| expr GE expr {$$ = Exp($1)>=Exp($3);}
| expr EQ expr {$$ = Exp($1)==Exp($3);}
| expr NE expr {$$ = Exp($1)!=Exp($3);}
;
expr1: unary_expr
| expr1 ’+’ expr1 {$$ = Exp($1)+$3;}
| expr1 ’-’ expr1 {$$ = Exp($1)-Exp($3);}
| expr1 ’*’ expr1 {$$ = Exp($1)*$3;}
| expr1 ’/’ expr1 {$$ = Exp($1)/$3;}
;
unary_expr: pow_expr
| ’-’ pow_expr {$$=-Exp($2);}
| ’!’ pow_expr {$$= !Exp($2);}
| ’+’ pow_expr {$$=$2;}
;
pow_expr: primary
| primary ’ˆ’ unary_expr {$$=comp(pow,$1,$3);}
;
%%
159
// mettre ici les fonctions utilisateurs
// et généralement le programme principal.
160
else { // erreur
cerr << " caractere invalide " << char(c) << " " << c << endl;
exit(1);} }
}
le programme principale :
x=0;
y=0;
tableId.Add("x",Exp(&x));
tableId.Add("y",Exp(&y));
tableId.Add("cos",cos);
tableId.Add("end",ENDOFFILE);
tableId.Add("while",WHILE);
tableId.Add("print",PRINT);
tableId.Add("endl","\n");
yyparse();
cout << " Compile OK code: " << leprogram <<endl;
if(leprogram)
{
R r= ( * leprogram)(); // execution du code
cout << " return " << r <<endl;
}
return 0;
}
Les fonctions utiles pour la table des symboles : recherche un symbole Find qui retour les deux
valeur associer, type et pointeur. Et trois méthodes pour ajouter des symboles de type di↵érent,
une chaı̂ne de caractères, une fonction C++ : double (*)(double) , ou une expression
du langage.
161
Voilà un petit programme example-exp.txt testé :
i=1;
while(i<10)
{
i = i+1;
a= 2ˆi+cos(i*3+2);
print " i = ", i,a, endl;
}
i;
end
et la sortie :3
162
Références
[S. Bourne] S. Bourne le système unix, InterEdition, Paris.
[Kernighhan,Pike] B.W. Kernighan, R. Pike L’environnement de programmation UNIX,
InterEdition, Paris.
[1] [Kernighhan, Richie]B.W. Kernighhan et D.M. Richie Le Langage C, Masson, Paris.
[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’optimisa-
tion, 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.
[2] 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++ : Manual
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 Information 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 : Fundamental algo-
rithms, Addison-Wesley, Reading, Mass, 1998.
[Knuth-1998b] D.E. Knuth The Art of Computer Programming, Vol III : Sorting and Sear-
ching, Addison-Wesley, Reading, Mass, 1998.
[Lachand-Robert] T. Lachand-Robert, A. Perronnet
(http://www.ann.jussieu.fr/courscpp/)
[Lascaux et Théodor] P. lascaux et R. Théodor Analyse numérique matricielle appliquée
a l’art de l’ingénieur, Tome 2 Masson, 1987
[Löhner-2001] R. Löhner Applied CFD Techniques, Wiley, Chichester, England, 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.
163
[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 : Di↵erence 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 Edition, 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 Programs. 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
[3] C. Berge, Théorie des graphes, Dunod, 1970.
164