Vous êtes sur la page 1sur 50

Université de Caen Basse-Normandie

Licence de Physique  niveau L3


Méthodes Numériques pour la Physique 2
2016-2017

Projet  Résolution de l'équation de Schrödinger


indépendante du temps par la méthode de Numerov

Résumé
L'objectif de ce projet est de rechercher et d'étudier les solutions de l'équation
de Schrödinger indépendante du temps à une dimension pour les états liés d'une
particule en interaction avec un potentiel quelconque.

Le projet est divisé en trois séquences de diculté graduelle. La première sé-


quence permet de découvrir et implémenter les principes de base de l'algorithme
de Numerov et de comprendre la procédure de résolution du problème sur la base
d'un cas physique élémentaire. On y utilise des programmes simples ne justiant
pas l'utilisation de techniques de programmation avancées.

La deuxième séquence introduit des techniques plus élaborées (programmation


orientée objet, généricité) de manière à utiliser le langage C++ pour permettre
l'écriture de modules et de programmes capables de traiter de manière versatile de
nombreux cas physiques au moyen d'un même outil : votre projet numérique.

Le troisième et dernière séquence consiste à étudier un cas concret et en trouver


les solutions de manière automatique, en mettant en ÷uvre les techniques introduites
précédemment. Ce sera l'occasion d'illustrer des comportements fondamentaux de
quelques systèmes quantiques d'intérêt physique.
Table des matières
1 Introduction 3
1.1 Formulation mathématique . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Récurrence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3

2 Problème 5
2.1 Principe de la méthode de résolution . . . . . . . . . . . . . . . . . . . . 5
2.2 Algorithme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.3 Etudes de cas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

3 Séquence 1 : approche basique du problème 10


3.1 Problème simple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3.2 La question des unités physiques . . . . . . . . . . . . . . . . . . . . . . . 10
3.3 Exercice [1.1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
3.4 Calcul numérique des dérivées à gauche et à droite . . . . . . . . . . . . . 13
3.5 Exercice [1.2] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.6 Exercice [1.3] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
3.7 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

4 Séquence 2 : approche générique du problème 18


4.1 Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.2 Modélisation du problème . . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.3 Modélisation du système . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.4 Exercice [2.1] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
4.5 Interface de la fonction  potentiel  . . . . . . . . . . . . . . . . . . . . 23
4.6 Exercice [2.2] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
4.7 Vers une interface uniée de la fonction  potentiel  . . . . . . . . . . . 24
4.8 L'interface de la fonction potentiel selon C . . . . . . . . . . . . . . . . . 25
4.9 Exercice [2.3] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
4.10 Utilisation des pointeurs de fonctions . . . . . . . . . . . . . . . . . . . . 28
4.11 Exercice [2.4] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4.12 L'interface de la fonction potentiel selon C++ : objets fonction . . . . . . 32
4.13 Exercice [2.5] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.14 L'interface de la fonction potentiel selon C++ : héritage . . . . . . . . . 35
4.15 Exercice [2.6] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
4.16 L'interface de la fonction potentiel selon C++ : classe abstraite . . . . . 40
4.17 Exercice [2.7] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
4.18 Retour sur les unités . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
4.19 Exercice [2.8] : modélisation C++ du système physique . . . . . . . . . . 45
4.20 Modélisation de la solution . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.21 Exercice [2.9] : implémentation de la classe solution . . . . . . . . . . . . 46
4.22 Modélisation de l'algorithme . . . . . . . . . . . . . . . . . . . . . . . . . 47
4.23 Exercice [2.10] : implémentation de la classe numerov_algo . . . . . . . . 47
4.24 Conclusion . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48

5 Séquence 3 : étude spécique 50

Page 2
1 Introduction
1.1 Formulation mathématique
L'équation de Schrödinger indépendante du temps à une dimension s'écrit ainsi :
~2 d2 φpxq
´ ` V pxqφpxq “ Eφpxq (1)
2m dx2
où :
‚ ~ est la constante de Planck réduite,
‚ m est la masse de la particule,
‚ x la variable de position,
‚ E son énergie totale
‚ V pxq le potentiel auquel la particule est soumise en x,
‚ φpxq la fonction d'onde solution de l'équation dépendant de x.
Elle peut également s'écrire sous la forme de Sturm-Liouville :
1
rppxqu1 pxqs ` qpxqupxq “ spxq
on obtient alors :

: ` 2m rE ´ V pxqs φpxq “ 0
φpxq
~2
où on reconnaît la forme de Sturm-Liouville avec :
ppxq “ 1
qpxq “ 2m
~2
rE ´ V pxqs
spxq “ 0
upxq “ φpxq (2)
u1 pxq “ dφpxq
dx
9
“ φpxq
d2 φpxq :
u2 pxq “ dx2 “ φpxq

1.2 Récurrence
La méthode est basée sur la discrétisation de l'espace continu représenté selon l'axe
(x Ox). On choisit ainsi un pas de discrétisation de la variable de position sur un do-
1

maine/intervalle d'intérêt de l'axe (x1 Ox) et on construit un réseau mono-dimensionnel


dont chaque noeud pourra être indicé avec un nombre entier k d'abscisse xk . On peut
alors résoudre l'équation diérentielle en φpxq en utilisant la formule de récurrence sui-
vante (précise localement à l'ordre 6) :

cn`1 un`1 » cn un ´ cn´1 un´1 ` dn (3)


où : 2
δ
cn`1 “ 1 ` 12 qn`1
5δ 2
cn “ 2 ´ 6 qn
δ2 (4)
cn´1 “ 1 ` 12 qn´1
δ2
dn “ 12 psn`1 ` 10sn ` sn´1 q

Page 3
où δ est le pas de discrétisation sur l'axe x1 Ox et qn “ qpxn q. Il est donc à priori possible de
calculer φn`1 ” φpxn`1 q à partir des valeurs φn ” φpxn q et φn´1 ” φpxn´1 q et de construire
par itérations successives la solution φ sur un domaine rxa ; xb s choisi convenablement (voir
la gure 1).
Cet algorithme est appelé  méthode de Numerov . Il conduit grosso-modo à une
résolution de l'équation diérentielle à l'ordre 4.

φpxq

φn`1

φn e
rr enc
φn´1 récu

x1 x
O xn´1 xn xn`1
δ

Figure 1  Détermination de φn`1 par récurrence.

V pxq

x1 x
O

´V0

Figure 2  Puits de potentiel arbitraire respectivement de profondeur et de largeur


typiques ´V0 et a.

Page 4
2 Problème
2.1 Principe de la méthode de résolution
On propose ici de résoudre l'équation de Schrödinger à une dimension et de déterminer
les fonctions d'onde φn pxq et les valeurs propres associées de l'énergie En d'une particule
de masse m dans le cas d'un puits de potentiel V pxq de forme arbitraire (gure 2).
On choisira un intervalle de travail rxa ; xb s encadrant susament largement la région
 active  du puits de potentiel (x „ Opaq). Le principe de l'algorithme consiste  pour
une énergie test Et donnée  à construire par récurrence la fonction φpxq dans deux
régions adjacentes :
‚ φl pxq sur l'intervalle de gauche rxa ; xm s
‚ φr pxq sur l'intervalle de droite rxm ; xb s
où xm est le point de contrôle, compris entre xa et xb , où les deux fonctions d'onde
respectivement de la branche  gauche  et de la branche  droite  doivent coïncider
ainsi que leurs dérivées (on note φ9 ” dφ dx
):
"
φl pxm q “ φr pxm q
(5)
φ9 l pxm q “ φ9 r pxm q
Si ces conditions ne sont pas réalisées à la précision souhaitée, c'est que l'énergie Et testée
n'est pas une valeur propre de l'équation et ne conduit donc pas à une solution physique.
On teste alors une autre valeur Et1 “ Et ` ∆E , où ∆E est un pas d'énergie qui mesure la
précision avec laquelle on scanne l'intervalle des énergies E possibles ; pour un système
lié, il s'agit de l'intervalle r´V0 ; 0s (gure 3).
V pxq E

x1 x
O
Ek`1
∆E
Ek

´V0

Figure 3  L'intervalle d'énergie E à scanner s'étend typiquement de la valeur minimale


du potentiel E » ´V0 jusqu'à la valeur E “ 0 (système non lié). Il est discrétisé par pas
∆E .

On prendra pour initier l'algorithme pour la branche à gauche :

φl,0 “ φpxa q “ 0 et φl,1 “ φpxa ` δq “ εl


De même on prendra pour la branche à droite :

Page 5
φr,0 “ φpxb q “ 0 et φr,1 “ φpxb ´ δq “ εr
Les valeurs εl et εr sont des valeurs initiales choisies arbitrairement. Elles sont supposées
de petites amplitudes par rapport à l'amplitude maximale de la fonction d'onde dans la
région d'interaction (|x| À a).
Cette manière d'initialiser les valeurs de la fonction d'onde aux extrémités du domaine
(pour x=xa , xa ` δ , xb ´ δ , xb ) est justiée par le fait qu'on s'attend à ce que la particule
soit piégée dans la région du puits de potentiel (|φ|2 ‰ 0 pour |x| À a). En conséquence,
la fonction d'onde devra être essentiellement non nulle dans la région |x| À a mais elle
sera évanescente pour |x| ąą a (|φ|2 ÝÝÝÝÑ 0, gure 4). On approxime ce comportement
|x|Ñ8

en forçant les valeurs extrêmes à des valeurs proches de zéro. La récurrence construira
ensuite de proche en proche les valeurs de φpxq en partant de l'extrêmité du domaine
vers la région centrale, pour chacune des deux branches. De ce fait, la fonction d'onde
est supposée évanescente sur presque tout le domaine  sauf la région du potentiel  et
on doit ainsi pouvoir trouver une solution de carré sommable.

V pxq φpxq

φpxq ‰ 0
φpxq » 0 φpxq » 0
O
x1 x

´V0

Figure 4  Forme attendue de la fonction d'onde φpxq dans la région d'interaction


eective du potentiel (|x| À a) et loin de la région d'interaction (|x| ąą a).

Page 6
2.2 Algorithme
D'un point de vue algorithmique, la résolution du problème se décompose en plusieurs
étapes (gure 5) :
1. choix de l'intervalle rxa ; xb s et de la valeur de test xm ,
2. choix des nombres de pas Nl et Nr respectivement de longueurs δl =rxm ´ xa s{Nl
à gauche et δr =rxb ´ xm s{Nr à droite,
3. choix raisonnable de l'énergie minimum Emin à partir de laquelle chercher les
valeurs propres et de l'incrément d'énergie ∆E entre les valeurs testées,
4. choix d'un critère de tolérance pour la comparaison des dérivées,
5. choix d'une énergie Et candidate (Et ě Emin ),
6. aectations des valeurs φl,0 “ 0, φl,1 “ εl et φr,0 “ 0 et φr,1 “ εr ,
7. calcul des φl,i (i=2,. . .,Nl ) et φr,j (j =2,. . .,Nr ) grâce à la formule de récurrence,
8. mise à l'échelle de φr par rapport à φl pour garantir φl pxm q “ φr pxm q,
9. test de la correspondance au point xm des fonctions dérivées φ9 l pxm q et φ9 r pxm q,
10. acceptation ou rejet de la valeur Et comme valeur propre,
11. nouvelle tentative avec l'énergie Et1 “ Et ` ∆E (retour à l'étape no 5) et ainsi de
suite. . .

2.3 Etudes de cas


Disposant d'un tel algorithme, on pourra étudier les solutions de l'équation de Schrö-
dinger dans divers cas d'intérêt physique. Cela reviendra à xer un potentiel V pxq modé-
lisant l'interaction de la particule pour un système donné. La gure 6 présente des formes
de quelques potentiels d'interaction qu'on pourra aborder grâce à ce calcul.

Page 7
φpxq
(a)

φl pxq φr pxq

δr
δl
x1 O x
´a{2 `a{2
xl,0 xl,1 xm xr,1 xr,0
xa xb
Région  active 
du potentiel
φ9 l pxm q φ9 r pxm q
φpxq
(b)
Point de contrôle
φr pxq
scaling φr pxq
φl pxq φl pxq

x1 O x
´a{2 `a{2
xa xb
xm

Figure 5  (a) Algorithme de construction des branches gauche et droite de φpxq ; (b)
test de la continuité de la dérivée de la fonction d'onde au point de contrôle xm après
mise à l'échelle des deux branches.

Page 8
V pxq V pxq

a a

x1 x x1 x
O O

(a) ´V0 ´V0 (b)

V pxq V pxq

a a

x1 x x1 x
O O
c

b ´V1

(c) ´V0 ´V0 (d)

V pxq V pxq
a
V2 a
c
1 1
x x x x
O O

´V0

(e) ´V0
(f)

Figure 6  Exemples de potentiels V pxq pouvant être étudiés. (a) Puits carré (cas ana-
lytique) ; (b) Puits harmonique (oscillateur) ; (c) Potentiel type Wood-Saxon ; (d) Double
puits (avec barrière partielle centrale) ; (e) Puits (harmonique) avec  barrières coulom-
biennes  extérieures ; (f) Réseau de puits (cristal).

Page 9
3 Séquence 1 : approche basique du problème
3.1 Problème simple
L'objectif de cette première séquence de travail est d'écrire et utiliser quelques pro-
grammes simples, relativement rustiques, qui vont permettre d'appliquer l'algorithme de
Numerov dans le cas du potentiel carré de largeur et de profondeur xées (gure 6 (a)).
Un programme type mettra en ÷uvre des fonctionnalités élémentaires du langage
C++. Il n'utilisera d'ailleurs pas de techniques propres au langage C++ (programmation
objet, généricité) ; cependant, il constituera une première implémentation fonctionnelle
de l'algorithme de Numerov et nous permettra dans les séances futures d'aborder une
approche plus générique de ce problème de la résolution de l'équation de Schrödinger en
utilisant des techniques plus évoluées de programmation en C++.

3.2 La question des unités physiques


On considère ici le cas d'un électron de masse me en interaction avec un puits carré
d'énergie potentielle Vs . Ce système physique permet de modéliser de manière simpliste
l'électron d'un atome d'hydrogène par exemple.
An de s'aranchir de toute considération compliquée à propos des unités physiques,
on remarque que le terme dimensionnel de l'équation (1) peut s'écrire :
~2 p~cq2
K ´1 “ “
2me 2me c2

or on sait que ~c » 197,326 MeV.fm et me c2 » 0,511 MeV 1 , d'où :


p197, 326 106 eV 10´5 Åq2 2
K ´1 “ 6
» 3, 81 Å .eV
2 ˆ 0, 511 10 eV

Ainsi, on peut réécrire l'équation (1) sous la forme :


d2 φpxq
´3, 81 ` V pxqφpxq “ Eφpxq (6)
dx2
où les énergies V et E sont données en eV et les positions x en Å, unités adaptées à un
problème type en physique atomique.
Cette écriture nous permet de nous aranchir de la gestion des unités physiques dans
l'implémentation du problème en C++. On y perd évidemment en généralité, mais on
y gagne en simplicité. C'est une approche courante de choisir ainsi un système d'unités
adapté au problème considéré ( Å et eV  pour un système atomique,  fm et MeV 
pour un système nucléaire. . .). On peut alors se concentrer sur les aspects purement
mathématiques et numériques du problème.

1. http://pdg.lbl.gov/2012/reviews/rpp2012-rev-phys-constants.pdf

Page 10
3.3 Exercice [1.1]
Nous allons procéder à l'écriture d'un programme simple en C++, prgnumerov1.cxx,
qui traite le cas des états liés de l'électron dans un puits carré centré sur l'origine de l'axe
x1 Ox avec :
‚ a=2 Å, la largeur du puits,
‚ V0 =20 eV, la profondeur du puits.

Les fonctionnalités du programme prgnumerov1.cxx sont les suivantes :


‚ Le programme ne comporte que la fonction principale main ; on n'utilisera donc
pas (ou peu) de fonctions.
‚ Les valeurs de a et de V0 sont xées en dur dans le programme, par exemple sous
la forme de données constantes.
‚ Le domaine spatial sur lequel on résoud l'équation (6) est rxmin : xmax s avec
xmin “ ´20 a et xmax “ `20 a. Le choix du facteur 20 est arbitraire. On garantit
ainsi de démarrer le calcul par la récurrence (3) dans les deux régions (x » xmin
et x » xmax ) où la fonction d'onde est considérée comme évanescente, c'est-à-dire
assez loin de la région  active  du potentiel dans laquelle on s'attend à piéger
préférentiellement l'électron.
‚ Le point de contrôle xm est choisi de manière assymétrique par rapport à la symé-
trie évidente du potentiel (x “ 0), par exemple :
pxmax ` xmin q
xm “ ` f ˆ pxmax ´ xmin q
2
avec f ą 0, ou bien simplement :
a
xm “ `
2
‚ Le domaine spatial rxmin ; xm s (branche gauche) est divisé en Nl ´ 1 intervalles de
longueur hl . De même, le domaine spatial rxm ; xmax s (branche droite) est divisé en
Nr ´ 1 intervalles de longueur hr . Les nombres Nl et Nr sont des constantes xées
de manière à être du même ordre de grandeur l'une de l'autre. Ils sont susamment
grands pour garantir un échantillonage spatial relativement n, particulièrement
dans la zone  active  du potentiel x P r´a{2; `a{2s (exemple : Nl =Nr =2000) :

Point de contrôle

δl δr
x
xmin xm xmax

‚ La valeur de l'énergie  test  Et est choisie par l'utilisateur et le calcul des coef-
cients ql,i pour i P r0; Nl r et qr,j pour j P r0; Nr r est eectué une fois pour toute
car il ne dépend alors que de Et (formule 2).
‚ Les valeurs de ql,i et qr,j (formule (2)) respectivement pour les branches gauche et
droite de la fonction potentiel Vs pxq sont stockées dans deux tableaux statiques de
nombres réels (en double précision).

Page 11
‚ Les valeurs de φl,i et φr,j respectivement pour les branches gauche et droite de la
fonction d'onde sont stockées dans deux tableaux statiques de nombres réels (en
double précision) en utilisant la récurrence de la formule (3).
‚ Les valeurs de φl,i et φr,i sont chacune mises à l'échelle indépendamment de manière
à ce que la valeur absolue maximale d'une branche soit égale conventionnellement
à 1.
Remarque : Cette étape n'est pas strictement nécessaire mais elle permet de
faciliter l'achage par le choix d'une échelle conventionnelle raisonnable. En eet le
choix des valeurs initiales φl,1 “ εl et φr,1 “ εr , aussi petites soient elles, n'interdit
nullement que la fonction d'onde prenne des valeurs très grandes et délicates à
acher (diérence d'échelle entre les deux branches calculées indépendamment).
‚ Les valeurs de φr,i sont mises arbitrairement à l'échelle de manière à ce que
φl,Nl ´1 “ φr,Nr ´1 soit φl pxm q “ φr pxm q.
‚ Les valeurs de la fonction potentiel V pxq sont enregistrées dans un chier "vs.data"
utilisant un format texte (jeu de caractères ASCII) sur deux colonnes en utilisant
un pas ∆x susamment petit pour pouvoir visualiser convenablement la fonction
potentiel sous Gnuplot. Exemple :

Colonne 1 Colonne 2
xmin V pxmin q
.. ..
. .
xk V pxk q
.. ..
. .
xmax V pxmax q

‚ Les valeurs de la fonction d'onde calculée à gauche (φl ) et à droite (φr ) sont enre-

Page 12
gistrées dans un chier "phi.data" utilisant un format texte sur deux colonnes :
Colonne 1 Colonne 2
xmin φl,0
xmin ` hl φl,1 “ εl
xmin ` 2 hl φl,2
.. ..
. .
xi φl,i
.. ..
. .
xm ´ hl φl,Nl ´2
xm φl,Nl ´1
ê
ê
xm φr,Nr ´1
xm ` hr φr,Nr ´2
.. ..
. .
xj φr,j
.. ..
. .
xmax ´ 2 hr φr,2
xmax ´ hr φr,1 “ εr
xmax φr,0
ê
ê

On prendra soin de faire suivre chaque lot de données des deux branches par
deux lignes blanches ê de manière à pouvoir acher individuellement les deux
branches dans Gnuplot (datasets d'indices respectifs 0 et 1).
Le programme Gnuplot permettra de visualiser le résultat du calcul de la fonction
d'onde eectué grâce à la récurrence (3).
Pour une énergie test Et quelconque (pas une valeur propre), on observera visuellement
une discontinuité en xm pour la dérivée φ9 “ dφ dx
de la fonction d'onde candidate (gure
7 à gauche). En revanche, lorsque que Et coïncide avec une valeur propre du système,
on observe que la dérivée φ9 de la fonction d'onde est continue au point de contrôle xm
(gure 7 à droite).
En changeant la valeur de l'énergie test Et , on recherchera graphiquement avec Gnu-
plot et par une méthode d'encadrement les valeurs propres En (n “ 1, . . . ) de l'énergie
ainsi que la forme des fonctions d'onde associées φn pxq.
On pourra également étudier la stabilité du calcul en fonction de la taille du pas
d'intégration hl ou hr .

3.4 Calcul numérique des dérivées à gauche et à droite


L'exercice [1.1] ne permet pas de vérier automatiquement si l'énergie test E constitue
une valeur propre. An de vérier la continuité de la dérivée de la fonction d'onde au
point xm , il est d'abord nécessaire d'évaluer la valeur de cette dérivée à partir des branches
gauche et droite de la fonction d'onde candidate.

Page 13
Figure 7  Comparaison des dérivées de φ à gauche (rouge) et à droite (vert) du point
de contrôle xm (centre du cercle) : à gauche, cas φ9 l pxm q ‰ φ9 r pxm q ; à droite, cas φ9 l pxm q »
φ9 r pxm q dans le cas où E est une solution valeur propre de l'énergie.

Si l'on considère la branche de gauche, il faut évaluer la valeur φ9 “ dφ


dx
de la dérivée
de la fonction d'onde en xm à partir des valeurs de φ calculée en amont de xm (gure 8).
φpxq
f3
f2
f1

f0 “ φpxm q f90 “ φl pxm q


δl
x
x3 x2 x1 x0 “ x m

Figure 8  Echantillonage de la fonction d'onde à gauche autour du point de contrôle


xm et estimation de la dérivée.

On note :
x0 “ xm , x1 “ xm ´ hl , x2 “ xm ´ 2 hl , x3 “ xm ´ 3 hl
et
f0 “ φpx0 q, f1 “ φpx1 q, f2 “ φpx2 q et f3 “ φpx3 q
et
9 0 q, f:0 “ φpx
f90 “ φpx : 0 q et f p3q “ φp3q px0 q.
0

On établit les relations suivantes grâce aux développements de Taylor en x1 , x2 et x3


autour de x0 avec h “ ´hl :
2 3 p3q
f 1 “ f 0 ` hf90 ` h2 f:0 ` h3! f0 ` Oph4 q
(7)
2 3 p3q
f 2 “ f 0 ` 2hf90 ` p2hq
2
f:0 ` p2hq
3!
f0 ` Oph4 q
2 3 p3q
f 3 “ f 0 ` 3hf90 ` p3hq
2
f:0 ` p3hq
3!
f0 ` Oph4 q
La résolution de ce système d'équations implique l'établissement d'une expression
analytique de la valeur approchée de la dérivée f90 “ φ9 l pxm q. Par symétrie, cette approche
vaut également pour la branche de droite.

Page 14
3.5 Exercice [1.2]
An d'établir l'expression de f90 , on propose ici d'utiliser le logiciel Maxima 2 , un
programme capable d'eectuer des calculs symboliques et numériques.
Questions :
1. Dans un terminal, lancer la commande maxima :
Terminal
1 dupont@scssvr:~$ maxima
2
3 Maxima 5.24.0 http://maxima.sourceforge.net
4 using Lisp GNU Common Lisp (GCL) GCL 2.6.7 (a.k.a. GCL)
5 Distributed under the GNU Public License. See the file COPYING.
6 Dedicated to the memory of William Schelter.
7 The function bug_report() provides bug reporting information.
8 (%i1)

Maxima est un programme interactif en ligne de commande, comme le shell Bash


ou le programme Gnuplot. Evidemment, Maxima ne reconnaît que son propre jeu
de commandes, orientées notamment vers le calcul symbolique mathématique. A son
démarrage, l'invite de commande de Maxima est (%i1) qui signie  input n1 .
On peut quitter la session Maxima avec la commande  quit();  (attention au
point-virgule nal) ou bien la séquence de touches Ctrl-d , signiant la n du ot
d'entrée (EOF ” end of le ).
2. On doit dénir en premier lieu les trois équations (7). On choisit d'utiliser les variables
df0, d2f0 et d3f0 pour représenter respectivement les dérivées première, deuxième
et troisième de la fonction d'onde en x0 “ xm . Dans le cas de la branche gauche de
la fonction d'onde φl , le pas hl est représenté par la variable h tel que h ” ´hl . Les
valeurs de la fonction d'onde aux points x0 “ xm , x1 “ x0 ´ hl , x2 “ x0 ´ 2 hl et
x3 “ x0 ´ 3 hl sont représentées par les variables f0, f1, f2 et f3 respectivement.
On dénit ainsi la première équation dans le langage de Maxima :
Maxima
(%i1) eq_1:f1=f0+h*df0+(h^2)*d2f0/2+(h^3)*d3f0/6;

Vérier la réponse de Maxima ( (%o1) pour  output n1 ).


De même on dénit les deux autres équations eq_2 et eq_3 en utilisant les coecients
adéquats pour le développement de Taylor.
3. Dénir la variable fvalues comme un tableau contenant les trois équations :
Maxima
(%i4) fvalues:[eq_1, eq_2, eq_3];

4. Dénir la variable unknowns comme un tableau contenant les trois inconnues :


Maxima
(%i5) unknowns:[df0, d2f0, d3f0];

2. http://maxima.sourceforge.net/

Page 15
5. Résoudre, au moyen de la fonction solve, le système constitué par le tableau des
trois équations fvalues avec pour solutions les valeurs constituant le tableau des
trois inconnues unknowns :
Maxima
(%i6) solve(fvalues,unknowns);

La solution symbolique achée par Maxima donne l'expression analytique de f90 (l'in-
connue df0) en fonction des données connues du problème.
6. Quitter Maxima et créer un programme C++ prgderiv0.cxx qui utilise ˇ la formule
approchée obtenue précédemment pour évaluer numériquement dx ˇ
d sinpxq ˇ
, dérivée
ˇ approx
de la fonction sinpxq et la comparer avec la valeur exacte connue d sinpxq ˇ
dx ˇ
“ cospxq.
exact
Les deux valeurs de la dérivée de sinpxq ainsi calculées seront achées sur un intervalle
r´5; `5s en utilisant un pas h=0,05. Vérier ainsi que la formule approchée donne
un résultat correct.
7. Utiliser plusieurs valeurs du pas : h “ 0, 5 , h “ 5 10´2 , h “ 5 10´3 , h “ ˇ 5 10
´4

et évaluer la précision relative εr sur le résultat de l'approximation d sinpxq en


ˇ
dx ˇ
approx
x “ π{4 :
ˇ ˇ ˇ
d sinp π4 q ˇ
´ cosp π4 q ˇ
ˇ ˇ
ˇ dx ˇ
approx
εr “ ˇ
ˇ ˇ
π ˇ
ˇ cosp 4 q ˇ
ˇ ˇ

En déduire l'ordre d'approximation de cette méthode de calcul de la dérivée. Indica-


tion : on tracera sous Gnuplot le graphe εr “ f phq en mode logarithmique.
A l'issue de l'exercice, la solution obtenue sous Maxima sera validée et pourra donc
être utilisée ultérieurement pour calculer automatiquement les valeurs approchées de la
dérivée de la fonction d'onde φ9 l pxm q et φ9 r pxm q en fonction des valeurs de φ échantillonnées
par la méthode de Numerov.

3.6 Exercice [1.3]


Questions :
1. Créer un module en C++ nommé deriv4points et implémentant la fonction de
calcul de la dérivée mise en ÷uvre dans l'exercice [1.2]. La fonction sera nommée
derivative_4points_x0. On la localisera dans l'espace de nom numerov.
On rappelle ici qu'un module est composé de deux chiers source écrit en C++ :
‚ le chier d'entête deriv4points.hpp contenant la déclaration de la fonction,
‚ le chier source deriv4points.cpp contenant la dénition de la fonction.
Le module deriv4points sera compilé indépendamment an de pouvoir le lier avec
un programme exécutable.
2. Vérier le bon fonctionnement du module avec un programme de test nommé
test_deriv4points.cxx.

Page 16
3. Ecrire le programme prgnumerov2.cxx, inspiré de prgnumerov1.cxx et utilisant le
module deriv4points, qui ache dans la sortie standard la valeur de l'énergie test
Et ainsi que les estimations de φ9 l pxm q et φ9 r pxm q. Format sur une ligne :

Et φ9 l pxm q φ9 r pxm q

4. Exécuter le programme prgnumerov2 pour des valeurs de Et allant de ´V0 jusqu'à


0 par pas ∆E =0,5 eV. Tracer le graphe pφ9 l pxm q ´ φ9 r pxm qq “ f pEt q. Identier les
solutions des valeurs propres En .

3.7 Conclusion
Cette séquence doit avoir permis de réaliser une première implémentation du problème
de physique. Elle est qualiée de  manuelle  car le programme nal écrit en C++ ne
permet a priori de ne traiter qu'un cas très particulier, celui du potentiel carré.
Ce premier travail permet néanmoins d'identier les diérents composants mise en
÷uvre dans la méthode de Numerov : structures de données (tableaux. . .), éléments al-
gorithmiques (boucles, branchements conditionnels. . .).
Il est néanmoins temps de passer à la vitesse supérieure en exploitant des concepts
du C++ plus élaborés et plus puissants, de manière à concevoir un outil logiciel pour
aborder la question de la résolution de l'équation 1 dans un cadre général.

Page 17
4 Séquence 2 : approche générique du problème
4.1 Introduction
Avant de commencer à concevoir des programmes utilisant des notions plus sophisti-
quées du C++, nous allons reconsidérer le projet en identiant les composants fondamen-
taux de la résolution du problème. On va donc se poser des questions un peu  théoriques 
avant de passer derrière le clavier et de se mettre à taper du code C++ bille en tête ! On
utilisera modestement le formalisme du langage de modélisation objet UML (Unied Mo-
deling Language 3 ) qui permet de représenter graphiquement l'architecture des éléments
logiciels participant à la conception d'un projet et leurs relations.
Nous partirons de la formalisation mathématique du problème physique (équation 1).
Nous nous poserons la question de  traduire  le problème en terme logiciel, avant de
proposer une implémentation pratique dans le langage de programmation C++.
La démarche que nous adopterons n'est pas la seule possible. En matière de pro-
grammation, il existe très souvent de nombreuses manières d'implémenter la solution
d'un problème. Déterminer la meilleure façon de procéder n'est pas une tâche toujours
évidente. Après tout, nous sommes intéressés ici par la solution numérique d'un pro-
blème de physique et on pourrait s'interroger sur l'intérêt d'utiliser tel ou tel langage
de programmation (C, C++, Fortran, Ada, Python. . .), tel ou tel algorithme (Numerov,
diérences nies. . .), pourvu qu'au nal on produise des résultats corrects.
Cependant, parmi l'ensemble des possibilités pour aborder un problème :
 Il en existe toujours plus de mauvaises que de bonnes ! 
Nous essayerons de dénir et d'implémenter une approche du problème qui fasse partie
des meilleures possibles à notre portée, c'est-à-dire dans le cadre des compétences que
nous sommes capables de déployer sur la base de nos connaissances.
La notion même de  meilleure approche  dépend du contexte dans lequel on tra-
vaille. Une technique éprouvée et ecace pour un certain type de problème pourra s'avé-
rer moins performante, voire complètement inadaptée dans un autre cas. L'objectif de
ce travail est non seulement d'éclairer un problème physique particulier au moyen des
méthodes numériques et de leur mise en ÷uvre au moyen de la programmation informa-
tique, mais également de transmettre des outils d'analyse et une maîtrise de techniques
dont l'acquisition permettra à l'avenir d'aborder d'autres problèmes scientiques.

4.2 Modélisation du problème


Dans un premier temps, pour appuyer le raisonnement, on va considérer le cas d'un
électron (m “ me ) en interaction avec un potentiel simple. On choisit ici un puits carré
Vs de profondeur V0 “ 20 eV et de largeur a=1 Å(voir la séquence no 1). Nous ignorerons
encore une fois la problématique des unités physiques an de ne pas compliquer cette
phase de la conception.
Quels sont les composants de ce projet de calcul ? Pour répondre à cette question,
il faut identier des éléments logiciels (structures de données, algorithmes) qui peuvent
être envisagés de manière autonome ou au moins de manière susamment individuelle
pour qu'on puisse conner conceptuellement leur structure et leur interaction avec leur
3. http://www.uml.org/

Page 18
environnement. On peut ici raisonnablement considérer trois éléments de base dans ce
problème :
1. la description du système physique (ici : l'électron dans le potentiel électrostatique
du noyau de l'atome d'hydrogène dans une version simpliste à une dimension) ;
elle constituera une donnée d'entrée fondamentale du problème,
2. l'algorithme (ici : la méthode de Numerov) ; il est le c÷ur du programme, c'est le
siège de tous les calculs numériques et donc de la consommation du CPU de l'or-
dinateur ; il travaille à partir de la donnée d'entrée décrivant le système physique,
3. la description de la solution du problème (ici : les énérgies propres du système et
les fonctions d'onde associées) ; c'est la donnée attendue en sortie de l'algorithme
au terme du traitement du problème. Cette donnée pourra ensuite être analysée,
visualisée graphiquement. . .
On peut aisément justier cette approche. On peut en eet imaginer de pouvoir ré-
soudre l'équation de Schrödinger pour l'atome d'hydrogène avec une autre méthode que
celle de Numerov, par exemple avec une méthode des diérences nies. De même, la mé-
thode de Numerov peut être utilisée pour calculer la solution de l'équation de Schrödinger
pour un nucléon dans le potentiel nucléaire d'un noyau atomique. Dans les deux cas, on
attend une solution qui puisse s'exprimer sous la même forme, c'est-à-dire une liste de
valeurs propres En (n P N ˚) et les fonctions d'onde φn pxq qui leur sont associées.
Le diagramme ci-dessous illustre que les deux concepts de  système physique  et
d' algorithme  sont considérés indépendamment et qu'on devrait pouvoir en principe
associer un système physique arbitraire à un algorithme arbitraire.

Systèmes physiques Algorithmes

Electron de l'hydrogène Méthode de Numerov

Nucléon dans un noyau Méthode des diérences nies

Cette approche est particulièrement intéressante dans le cadre de ce projet où l'on


envisage d'étudier divers cas physiques au moyen de la méthode de Numerov. Si l'on
conçoit correctement le logiciel, on pourra bénécier de la versatilité de cette approche :
changer facilement le cas physique étudié en conservant le même algorithme. En allant
encore plus loin, on pourrait concevoir cette versalité de manière à pouvoir traiter le
même cas physique au moyen de diérents algorithmes, par exemple pour en comparer
les ecacités respectives. Cette dernière possibilité ne sera toutefois pas abordée lors de ce
projet. On se contentera donc d'une  demi-versalité , illustrée par le schéma ci-dessous :

Page 19
Systèmes physiques Algorithmes

Electron de l'hydrogène Méthode de Numerov

Nucléon dans un noyau Méthode des diérences nies

Electron dans un cristal

Cette propriété de versalité de la modélisation logicielle est souvent qualiée de gé-


néricité. Elle implique que l'association du modèle logiciel du système physique et du
modèle logiciel d'algorithme doit se faire de manière conventionnelle et indépendamment
des systèmes physiques et des algorithmes étudiés en particulier. Cette association entre
les deux éléments fondamentaux du projet  ou couplage des composants logiciels  sera
donc élaborée de manière à être générique. On nomme ce couplage l'interface (gure 9).

interface
Couplage
Système physique X Algorithme A
générique

Figure 9  Relation entre deux des éléments fondamentaux de la modélisation de la


résolution de l'équation de Schrödinger.

Quelle est la nature du couplage entre la description du système physique et l'algo-


rithme ? Lorsque l'on envisage le scénario de la résolution du problème, on est amené à
déterminer une direction naturelle du ux des informations. Cela est illustré sur la gure
10 :

interface interface
Système physique Consomme Algorithme Produit Solution

Figure 10  Relation entre les trois éléments fondamentaux de la modélisation de la


résolution de l'équation de Schrödinger.

On voit ici qu'il faut d'abord fournir la description du système physique avant toute
chose, ensuite l'algorithme utilise cette donnée pour eectuer des calculs, et enn on peut

Page 20
extraire de l'algorithme une expression de la solution du problème (En , φn pxq et n P N ˚).
On pourrait tout à fait représenter ce protocole de la même manière qu'on représente les
fonctions en mathématique (gure 11).
y = f pxq
Solution = Algorithme(Système physique)

Figure 11  Un algorithme de résolution de l'équation 1 peut être considéré comme une


fonction qui à un système physique associe une solution.

4.3 Modélisation du système


Au regard de l'équation 1, le système physique constitué d'une particule en interaction
avec un potentiel est complètement caractérisé par deux éléments :
‚ la masse du système, notée m, est un nombre réel strictement positif (qu'on ex-
primera dans une unité adéquate),
‚ le  potentiel d'interaction  est représenté par une fonction V , de R dans R qui
à chaque position x (dimension d'une longueur) associe la valeur V pxq (dimension
d'une énergie) : "
R Ñ R
V (8)
x Ý
Þ Ñ V pxq
Il sera aisé de modéliser le paramètre de la masse m au moyen d'un réel en double
précision. Comment en revanche représenter la fonction  potentiel d'interaction  ?
Evidemment, comme on l'a déjà vu, le langage C++  comme le C  permet de dénir
des fonctions. Ainsi la fonction f telle que :
"
R Ñ R
f (9)
Þ Ñ f pxq “ 4px ´ 1q2
x Ý

peut être implémentée en C++ par :


C++
12 double f(double x_)
13 {
14 return 4.0 * (x_ - 1) * (x_ - 1);
15 }

4.4 Exercice [2.1]


Questions :
1. Ecrire un programme prgfunc0.cxx qui ache, au moyen d'une fonction nommée
vs les valeurs du potentiel carré Vs de largeur a “ 1 Å et profondeur V0 “ 10 eV
pour toutes les positions x P r´2Å; `2Ås par pas de ∆x=0,01 Å. La signature de la
fonction sera :
C++
double vs(double x_);

Page 21
Le format d'achage en mode texte utilisera au moins 10 chires signicatifs et la
disposition suivante :

Colonne 1 Colonne 2
x0 Vs px0 “ ´2Åq
x1 Vs px1 “ x0 ` ∆xq
x2 Vs px2 “ x1 ` ∆xq
.. ..
. .
xmax Vs pxmax “ `2Åq

2. Exécuter le programme et tracer sous Gnuplot le graphique de ce potentiel.


3. Compléter le programme de manière à acher les valeurs des potentiels triangulaire
Vt pxq (fonction nommée vt ) et harmonique Vh pxq (fonction nommée vh ) en co-
lonnes 3 et 4 et superposer les trois potentiels sur le même graphique (gure 12). On
pourra produire le graphique au format jpeg dans un chier prgfunc0.jpeg.

V pxq V pxq

a a

x1 x x1 x
O O

´V0 ´V0

Figure 12  Potentiels triangulaire et harmonique de largeur a et de profondeur V0 .

Page 22
4.5 Interface de la fonction  potentiel 
On voit dans l'exercice [2.1] comment programmer diérents types simples de fonctions
 potentiel . Cela signie que le système physique traité par l'algorithme de Numerov
sera totalement décrit par :
‚ la masse m représentée en C++ par un réel (en double précision) :
double m;

‚ le potentiel V pxq sera représenté en C++ par une fonction ayant comme signature :
double v(double);

Un des objectifs de ce projet est de concevoir un programme dont les composants


seront aussi génériques que possible. Cela signie en particulier que l'algorithme devrait
être couplé au système physique sans qu'il ne connaisse les détails de ce système autre que
ceux, strictement nécessaires, dont il a besoin pour travailler : la masse m et la fonction
potentiel qui à tout x associe une valeur V pxq.
Cela pose toutefois une diculté lorsque l'on veut exprimer cela en C ou C++. Com-
ment faire pour écrire un programme qui pourra utiliser un système physique avec, au
choix :
 un puits carré de potentiel de profondeur V0 “ ´10 eV et de largeur a=1 Å,
 ou bien un puits carré de potentiel de profondeur V0 “ ´1eV et de largeur a=10
Å
 ou encore un puits triangulaire de profondeur V0 “ ´50eV et de largeur a=0,25
Å?
Rien qu'avec une seule forme du potentiel, par exemple le puits carré, il existe une innité
de combinaisons des valeurs numériques de V0 et a qui décrivent autant de systèmes
physiques diérents. Un utilisateur  ici le physicien qui cherche les solutions du problème
 devrait pouvoir changer comme il le souhaite les paramètres du système physique avant
de lancer la résolution de l'équation par la méthode de son choix (choix limité ici à la
méthode de Numerov).
Or, l'approche adoptée dans l'exercice [2.1] dénit les trois types de fonctions potentiel
de manière à ce que les paramètres V0 et a soient xés une fois pour toute. Une fois
les fonctions dénies, il n'est plus possible de modier ces paramètres. Pour tracer de
nouveau les graphes des potentiels Vs pxq, Vt pxq et Vh pxq avec la valeur a=2,5 Å au lieu de
1 Å, il est nécessaire de modier le code source du programme et de le recompiler pour
générer un nouvel exécutable qui fera le calcul dans ces nouvelles conditions. Cela rend les
choses pratiquement pénibles voire inutilisables car pour étudier 100 systèmes physiques
diérents, il faut 100 programmes diérents, même si ces programmes partagent l'essentiel
du même code source, à l'exception de valeurs numériques choisies pour a et V0 .
Une solution envisageable à ce problème est de compléter la signature de la fonction
potentiel en ajoutant, en plus de l'argument pour le paramètre de position x, des argu-
ments pour les paramètres représentant les caractéristiques V0 et a du potentiel. Ainsi on
déclarerait les trois fonctions de l'exercice [2.1] ainsi :

double vs(double x_, double v0_, double a_);


double vt(double x_, double v0_, double a_);
double vh(double x_, double v0_, double a_);

Page 23
4.6 Exercice [2.2]
Questions :
1. Sur la base du programme prgfunc0.cxx de l'exercice [2.1], écrire un programme
prgfunc1.cxx qui ache, dans le même format que prgfunc0.cxx, les valeurs des
potentiels Vs , Vt et Vh pour x P r´10Å; `10Ås. Le programme permettra d'abord la
saisie des paramètres V0 et a qui seront transmis aux trois fonctions potentiel.
2. Produire le graphique.
3. Ajouter une fonction potentiel Va pxq telle que représentée sur la gure 13.

V pxq

x1 x
O

´V1

´V0

Figure 13  Potentiel puits assymétrique de largeur a et de paramètres de profondeur


V0 (gauche) et V1 (droite).

4.7 Vers une interface uniée de la fonction  potentiel 


La technique mise en ÷uvre dans l'exercice [2.2] fonctionne mais n'est pas satisfaisante.
En eet, si l'on se place du point de vue de l'élément  Algorithme , l'accès à la valeur
du potentiel V px0 q à une position donnée x0 dépend de la connaissance d'un nombre
arbitraire de paramètres supplémentaires comme V0 , V1 , a. De plus, toutes les formes
de potentiel n'utilisent a priori pas les mêmes paramètres. Si on se reporte à la gure
6, on constate bien que les diérents cas physiques auront besoin de plus ou moins de
paramètres pour être complétement dénis et par conséquent de permettre le calcul de
V px0 q à la demande de l'algorithme.
C'est ainsi que l'exercice [2.2] brise l'interface commune que nous avions mise en place
dans l'exercice [2.1]. En eet, pour les fonctions Vs , Vt et Vh , on a opté pour l'interface
commune suivante :
"
R3 Ñ R
VX avec X “ s, t, h (10)
x, V0 , a Ý
Þ Ñ VX px, V0 , aq
alors que pour Va (potentiel asymétrique), on a :
"
R4 Ñ R
Va (11)
x, V0 , V1 , a Ý
Þ Ñ Va px, V0 , V1 , aq

Page 24
De manière générale, il n'y a aucune raison qu'une autre fonction potentiel utilise
cette même signature. On peut envisager tous les cas de gure, y compris qu'une fonction
potentiel particulière dépendant de l'âge du capitaine ! En bref, on n'a pas encore trouvé
la solution pour décrire de manière générique l'interface de la fonction potentiel.
Notre objectif va donc être de restaurer une interface typique de l'équation 8, souhai-
table parce que simple, en permettant de nouveau à l'algorithme de manipuler n'importe
quelle fonction potentiel d'une manière uniée. Un indice important nous est livré en
considérant, par exemple pour le potentiel Va , la nature des diérents arguments de la
fonction Va px, V0 , V1 , aq lors de son invocation dans le programme prgfunc1.cxx. On
constate qu'à chaque fois que la fonction est invoquée dans la boucle d'achage, la va-
leur de x est modiée, alors que les valeurs des arguments V0 , V1 et a sont inchangées,
ces dernières ayant été dénies par l'utilisateur au tout début du programme en tant que
constantes fondamentales du système. Ceci nous montre clairement que la nature même
des paramètres V0 , V1 et a dière fondamentalement de celle de x. Les valeurs V0 , V1 et
a sont des paramètres statiques, qui tous ensemble donnent son identité à la fonction
Va , alors que x est une valeur dynamique qui dénit une grandeur physique (la position
sur l'axe x1 Ox) extérieure à la qualication de la fonction.
On aimerait donc pouvoir réécrire l'interface de la fonction Va sous cette forme :
"
R Ñ R
Va pV ,V ,aq (12)
Þ Ñ Va 0 1 pxq
x Ý

où pV0 , V1 , aq représente l'ensemble des paramètres statiques dénissant la fonction Va .


De même, pour les fonctions Vs , Vt et Vh , on écrirait :
"
R Ñ R
VX pV ,aq avec X “ s, t, h (13)
Þ Ñ VX 0 pxq
x Ý

où pV0 , aq représente l'ensemble des paramètres statiques dénissant ces fonctions.


On voit ici que par un jeu d'écriture, on a pu restaurer l'interface générique de la fonc-
tion potentiel qui est considérée de nouveau comme une fonction de R dans R, associant
V pxq à un argument x, indépendamment du jeu de paramètres nécessaires pour qualier
le potentiel utilisé : "
R Ñ R
V (14)
Þ Ñ V pparamètresq pxq
x Ý
Cette manipulation reste conceptuelle et il va falloir trouver un moyen pratique d'ex-
primer cela dans le langage de programmation. Il existe typiquement deux approches,
l'une traditionnellement utilisée en C, l'autre en C++. Il est utile de connaître les deux.
De plus ces deux méthodes peuvent collaborer au sein d'un même logiciel qui  mélange-
rait  du code C et du code C++.

4.8 L'interface de la fonction potentiel selon C


La première étape consiste à reconnaître que les paramètres statiques d'une fonction
potentiel donnée constituent une structure de données cohérente. Le langage C propose
les structures pour manipuler une composition de plusieurs valeurs potentiellement
hétérogènes. La technique consistera donc à créer un type de données représentant expli-
citement le jeu de paramètres d'un potentiel donné. Par exemple, on dénira les structures

Page 25
vsth_params et va_params qui représentent respectivement les jeux de paramètres des
potentiels Vs , Vt et Vh d'une part (ils utilisent tous en eet a et V0 ), et du potentiel Va
d'autre part (utilisant V1 en plus de a et V0 ). Dans ce contexte, une structure vws_params
représenterait utilement un potentiel VW S de type Wood-Saxon bien connu en physique
nucléaire.
D'autre part si l'on souhaite manipuler des fonctions respectant l'interface (14), il
nous faudra bien trouver une façon de le faire en C. La proposition est ici d'utiliser, pour
toutes les fonctions potentiel imaginables, la signature C suivante :
double v(double x_, void * params_);

où le premier argument double x_ se rapporte à la variable dynamique de position x et


où le pointeur anonyme void * params_ permettra d'adresser la structure qui contient
les paramètres statiques du potentiel utilisé. Ainsi, tout ce que l'algorithme de Numerov
aura à connaître de la fonction potentiel pour pouvoir l'utiliser est l'adresse de la va-
riable structure la qualiant, sans rentrer dans les détails des paramètres statiques qui la
dénissent.
Le programme prgfunc2.cxx montre comment on met en ÷uvre cette technique pour
acher les valeurs du potentiel Vx comme dans l'exercice [2.2].
prgfunc2.cxx
1 #include<iostream>
2 #include<cmath>
3
4 // Structure pour stocker les paramètres statiques
5 // des fonctions potentiel carré, triangulaire et harmonique :
6 struct vsth_params
7 {
8 double v0;
9 double a;
10 };
11
12 // Signature de la fonction potentiel carré :
13 double vs(double x_, void * params_);
14
15 int main(void)
16 {
17 // Déclaration d'une variable structure contenant les
18 // paramètres statiques :
19 vsth_params vs_pars;
20 std::clog << "Saisir V0 : ";
21 std::cin >> vs_pars.v0;
22 std::clog << "Saisir a : ";
23 std::cin >> vs_pars.a;
24 std::clog << "V0 = " << vs_pars.v0 << '\n';
25 std::clog << "a = " << vs_pars.a << '\n';
26
27 // Réinterprétation de l'adresse de la structure 'vs_pars'
28 // comme une adresse anonyme (non typée) :
29 void * vs_params_ptr = static_cast<void*>(&vs_pars);

Page 26
30
31 double dx = 0.01;
32 std::cout.precision(10);
33 for (double x = -10.0; x <= 10.0 + 0.5 * dx; x += dx)
34 {
35 // Invocation de la fonction potentiel avec l'argument
36 // de position 'x' et l'adresse anonyme 'vs_params_ptr'
37 // qui pointe en mémoire vers la structure contenant les
38 // paramètres qualifiant la fonction :
39 std::cout << x << ' ' << vs(x, vs_params_ptr) << std::endl;
40 }
41 return 0;
42 }
43
44 double vs(double x_, void * params_)
45 {
46 // Reinterprétation de l'adresse anonyme 'params_' comme
47 // l'adresse d'une structure 'pars' de type 'vsth_params' :
48 vsth_params * pars = static_cast<vsth_params *>(params_);
49
50 // Extraction des paramètres statiques du potentiel stockés
51 // dans la structure. La fonction se charge ainsi elle-même
52 // d'accéder aux paramètres qui la qualifient, avant de les utiliser :
53 const double & a = pars->a;
54 const double & v0 = pars->v0;
55
56 // Calcul de la valeur du potentiel carré :
57 if (std::abs(x_) > +0.5 * a) return 0.0;
58 return -v0;
59 }

Le diagramme UML représentant la structure vsth_params est :

vsth_params

+ v0: double Profondeur du puits


+ a: double
Largeur du puits

4.9 Exercice [2.3]


Questions :
1. Sur la base du programme prgfunc2.cxx et en réutilisant la même technique d'inter-
façage de la fonction potentiel vs , écrire un programme prgfunc3.cxx qui ache
également les valeurs des potentiels Vt , Vh et Va dans les mêmes conditions que pour
Vs .
2. Générer le graphique superposant les quatre courbes.

Page 27
3. Compléter le programme de manière à traiter d'un potentiel VW S de type Wood-Saxon
dont la forme mathématique est donnée par :
V0
VW S pxq “ ´ (15)
1 ` expp 0,5ˆx´a
b
q
On prendra par exemple : V0 =7,2 , a=5,6 et b=0,5 (sans aucune considération d'unités
physiques d'énergie et de longueur).

4.10 Utilisation des pointeurs de fonctions


On a vu que la technique utilisée ci-dessus consiste à conner le jeu de paramètres
statiques du potentiel (V0 , a. . .) dans une structure, et à passer cette structure  par
l'intermédiaire d'un pointeur anonyme  à la fonction réalisant eectivement le calcul. La
fonction prend donc deux arguments, respectivement de type double et void * . Toutes
les fonctions qui respecteront cette interface pourront représenter un potentiel physique
et être manipulée de la même manière, notamment par l'algorithme de Numerov. En
ce sens, elles constituent une même famille de fonctions, librement interchangeables par
l'utilisateur désireux d'étudier les diérents systèmes physiques qu'elles caractérisent.
Le langage C/C++ permet de dénir un type spécique pour de telles fonctions en
introduisant la notion de pointeur de fonction. De la même manière qu'une variable entière
possède une adresse en mémoire :
C++
int i; // On déclare une variable 'i' de type entier.
int * pi = &i; // On déclare une variable 'pi' de type pointeur
// sur un entier et on l'initialise avec l'adresse
// de la variable 'i' au moyen de l'opérateur
// unaire '&'.
*pi = 2.0; // On initialise 'i' à travers le pointeur 'pi'
// en déréférençant le pointeur 'pi' au moyen
// de l'opérateur unaire '*'.
i = 2.0; // Equivalent à l'instruction précédente
// par initialisation directe de 'i'.

Il est possible de manipuler une fonction au moyen de l'addresse qui la localise en


mémoire. On rappelle à cet eet que dans l'architecture de l'ordinateur selon le modèle
de Von Neuman (cf. le cours), les données comme les instructions (et donc les fonctions
qui sont des ensembles d'instructions) sont stockées en mémoire de l'ordinateur. Il est
donc possible de récupérer l'adresse d'une fonction chargée en mémoire et d'invoquer
cette dernière à travers son pointeur. L'exemple ci-dessous illustre la syntaxe dénissant
une variable de type pointeur de fonction de R dans R :
C/C++
1 double mult3(double x_) // une fonction de R dans R
2 {
3 return 3.0 * x_;
4 }
5
6 int main(void)

Page 28
7 {
8 double (*op)(double) = &mult3; // On déclare la variable 'op' de type
9 // pointeur de fonction de R dans R et
10 // on l'initialise avec l'adresse de
11 // la fonction 'mult3'.
12 double x = 2.0;
13 double y = (*op)(x); // On invoque 'mult3' à travers le pointeur 'op'
14 // en déréférençant ce dernier.
15 return 0;
16 }

Le programme prgfunc4.cxx ci-dessous montre comment on met en ÷uvre cette


technique pour acher les valeurs du potentiel Vs comme dans l'exercice [2.2]. Il utilise
notamment un alias de type grâce à l'instruction typedef (ligne 15) qui permet d' in-
venter  un nom pratique pour remplacer utilement (ligne 36) la syntaxe déclarative un
peu compliquée de la ligne 8 de l'exemple précédent.
prgfunc4.cxx
1 #include<iostream>
2 #include<cmath>
3
4 struct vsth_params
5 {
6 double v0;
7 double a;
8 };
9
10 double vs(double x_, void * params_);
11
12 // Déclaration d'un alias pour le type de pointeur de fonction
13 // nommé 'potential_func_type' qui adresse toute fonction
14 // prenant un double et un void * comme arguments et retournant un double :
15 typedef double (*potential_func_type)(double x_, void * params_);
16
17 int main(void)
18 {
19 vsth_params vs_pars;
20
21 std::clog << "Saisir V0 : ";
22 std::cin >> vs_pars.v0;
23 std::clog << "Saisir a : ";
24 std::cin >> vs_pars.a;
25 std::clog << "V0 = " << vs_pars.v0 << '\n';
26 std::clog << "a = " << vs_pars.a << '\n';
27
28 double dx = 0.01;
29 std::cout.precision(10);
30
31 // Pointeur anonyme vers les paramètres statiques du potentiel :
32 void * v_params_address = static_cast<void*>(&vs_pars);

Page 29
33
34 // Déclaration d'une variable pointeur de fonction initialisée
35 // avec l'adresse de la fonction potentiel carré :
36 potential_func_type v_pot = &vs;
37
38 // Vérification :
39 std::clog << "Adresse de la fonction vs : " << ((void*) &vs) << '\n';
40 std::clog << "Adresse stockée dans v_pot : " << ((void*) v_pot) << '\n';
41
42 // Algorithme principal :
43 for (double x = -10.0; x <= 10.0 + 0.5 * dx; x += dx)
44 {
45 std::cout << x << ' ' << (*v_pot)(x, v_params_address) << std::endl;
46 }
47
48 return 0;
49 }
50
51 double vs(double x_, void * params_)
52 {
53 vsth_params * pars = static_cast<vsth_params *>(params_);
54 const double & v0 = pars->v0;
55 const double & a = pars->a;
56 if (std::abs(x_) > +0.5 * a) return 0.0;
57 return -v0;
58 }

On constate désormais qu'à l'intérieur de la boucle d'achage, il n'est plus fait au-
cune référence explicite ni à la nature de la structure contenant les paramètres statiques
du potentiel, ni à la fonction potentiel utilisée eectivement. La boucle, qui constitue ici
l'algorithme principal du programme, ignore tout des détails internes de la fonction po-
tentiel Vs dont elle est chargée d'acher les valeurs sur un intervalle donné. Nous sommes
parvenus à concevoir une interface générique au problème.
Cette technique est utilisée dans de nombreuses situations. Citons en particulier celui
de la GNU Scientic Library (Bibliothèque Scientique GNU 4 ) qui utilise ce mécanisme
(type gsl_function) pour manipuler de manière transparente des fonctions arbitraires
dans les algorithmes numériques (recherche de racine, calcul de dérivée, intégration. . .).
4. http://www.gnu.org/software/gsl/

Page 30
Remarque : Un tel mécanisme peut être illustré par l'analogie suivante : une personne
habitant à l'adresse A souhaite faire parvenir en recommandé un courrier (une lettre par
exemple) à une autre personne habitant à l'adresse B. Pour ce faire, elle utilise un service
postal qui lui propose le protocole suivant pour pouvoir s'acquitter de cette tâche :
 fournir l'adresse A de l'expéditeur,
 fournir l'adresse B du destinataire,
 payer un timbre en rapport avec le poids, l'encombrement du courrier à transporter
et la distance AB,
 déposer le courrier dans la boîte à lettres à l'adresse A,
 attendre un temps proportionnel à la distance AB,
 réceptionner le reçu à l'adresse A.
La mission de transport peut alors être complètement réalisée. Notons ici que ce protocole,
ou interface fonctionnelle, peut être réalisé indépendamment du fait que le service postal
transporte une lettre ou un colis. Le facteur n'a même pas à savoir ce qu'il transporte. Une
forme d'anonymat et de condentialité est ainsi garantie et les détails internes au sujet
du contenu de l'objet transporté (enveloppe/colis) sont totalement ignorés par le service
 l'algorithme  alors qu'ils sont parfaitement connus par l'expéditeur et le destinataire
qui ont la possibilité d'accéder au contenu de l'objet à un moment particulier (fermeture
de l'enveloppe ou du colis avant l'envoi, ouverture à la réception).

4.11 Exercice [2.4]


Ecrire un programme prgfunc5.cxx ayant les fonctionnalités suivantes :
1. L'utilisateur pourra choisir la forme du potentiel parmi une liste achée dans un
menu simple :
‚ puits carré Vs pxq
‚ puits triangulaire Vt pxq
‚ puits harmonique Vh pxq
‚ puits assymétrique Va pxq
‚ puits de Wood-Saxon VW S pxq
2. La forme du potentiel étant choisie, l'utilisateur pourra saisir les paramètres statiques
de ce potentiel. On implémentera une fonction scan_real_param qui permet à l'utili-
sateur la saisie d'un nombre réel passé en argument à partir du ot d'entrée standard,
validera la saisie et garantira que la valeur saisie est comprise dans un intervalle de
valeurs raisonnable passé en argument.
3. Enn le programme, basé sur la technique des programmes prgfunc3.cxx (exercice
[2.3]) et prgfunc4.cxx, achera les valeurs du potentiel choisi sur un intervalle au
choix de l'utilisateur.
4. Générer le graphique.

Page 31
4.12 L'interface de la fonction potentiel selon C++ : objets fonc-
tion
La technique vue précédemment permet de faire connaître les paramètres statiques de
la fonction potentiel à cette dernière en les  dissimulant  derrière un argument pointeur
anonyme. Cette opération est réalisée au moment de l'invocation de la fonction :

paramètres statiques
+
V0 P R
structure
anonymat
a P R

x0 P R Vs pxq Vs px0 q P R
position fonction potentiel valeur calculée du potentiel
On va maintenant montrer comment exploiter des possibilités du langage C++ pour
obtenir une interface générique au travers de la mise en ÷uvre de la programmation
orientée objet.
Nous allons nous appuyer sur l'observation suivante : les structures que nous avons
créées dans les exercices précédents (structure vsth_params pour les fonctions vs, va. . .)
sont des compositions de données très simples. Jusqu'à maintenant, il s'agit au maximum
de trois nombres réels placés dans un même  sac  (un multiplet contenant : V0 , a. . .).
Un tel assemblage (composition) de variables pourrait fort bien être considéré comme un
objet, c'est-à-dire comme l'instance d'une classe (voir le cours de L2 Physique  Langage
C++). Ceci est en particulier justié si l'on considère les faits suivants, par exemple dans
le cas du puits carré de potentiel Vs tel que nous l'avons déni jusqu'à maintenant :
‚ la valeur de V0 est nécessairement positive (éventuellement nulle si l'interaction
est évanescente),
‚ la valeur de a (une longueur) est nécessairement positive (non nulle).
Cela signie qu'en toute rigueur V0 et a sont plus que de simples nombres réels ; ce sont des
nombres réels dont les valeurs sont contraintes par le fait qu'à eux deux ils représentent
un système physique avec des contraintes fortes.
L'usage d'une structure de type :
C++
4 struct vsth_params
5 {
6 double v0;
7 double a;
8 };

ne permet pas d'exprimer de telles contraintes. Rien ne nous empêche dans ce cas d'initia-
liser malencontreusement les paramètres V0 et a avec des valeurs négatives parfaitement
absurdes, comme dans l'exemple suivant :
C++
1 vsth_params vs_pars;
2 vs_pars.v0 = -666.0; // unité arbitraire
3 vs_pars.a = -1.e+38; // unité arbitraire

Page 32
Ceci peut être considéré comme une fragilité de la conception en C. La structure de
données vsth_params n'est pas sécurisée.
Les classes sont l'outil parfait pour représenter une composition de données pri-
vées  comme les grandeurs V0 et a  tout en fournissant des mécanismes pour garantir
la cohérence de ces données : constructeur(s), destructeur, méthodes publiques acces-
seur /mutateur (getter /setter ), vérication de la validité de l'état interne de l'objet. . .
Ceci devrait orir une technique sûre de gestion des paramètres statiques qualiant n'im-
porte quelle fonction  potentiel .
De plus, le C++ autorise la dénition (ou la redénition) des opérateurs reconnus par
le langage (+ , * ,. . .). En particulier, il est possible d'équiper une classe avec l'opérateur
... operator()(...) (invocation avec liste d'arguments et type de retour) qui sera
considéré tout simplement comme une des méthodes de la classe. C'est ce qu'on appelle
un objet fonction.
Le programme prgobjfunc0.cxx présente un exemple élémentaire de classe d'objet
fonction. La classe scaled_sum possède un unique attribut privé (ligne 9). On y déclare
(ligne 7) l'opérateur () de telle sorte que celui-ci accepte trois arguments de type double
et retourne une valeur également de type double. On peut donc considérer qu'il s'agit
d'une fonction de R3 dans R. Un objet fonction f de cette classe est instancié dans la
fonction principale (ligne 14) et on invoque son opérateur () en lui passant trois arguments
réels conformément à sa signature (ligne 18).
prgobjfunc0.cxx
1 class scaled_sum
2 {
3 public:
4 scaled_sum(double factor_);
5 // Déclaration de l'opérateur '()' comme une méthode
6 // publique de la classe 'scaled_sum' :
7 double operator()(double x_, double y_, double z_);
8 private:
9 double _factor_;
10 };
11
12 int main(void)
13 {
14 scaled_sum f(4.0); // Déclaration de 'f', objet-fonction de type 'scaled_sum'
15 double a = 2.0;
16 double b = 3.0;
17 double c = -6.0;
18 double y = f(a,b,c); /* Invocation de la méthode 'operator()'
19 * de la classe 'scaled_sum' avec 3 arguments double.
20 */
21 y = f.operator()(a,b,c); // Autre manière d'invocation de l'opérateur
22 return 0;
23 }
24
25 scaled_sum::scaled_sum(double factor_)
26 {
27 _factor_ = factor_;
28 }

Page 33
29
30 // Définition de l'opérateur '()' de R3 dans R :
31 double scaled_sum::operator()(double x_, double y_, double z_)
32 {
33 return _factor_ * (x_ + y_ + z_);
34 }

La ligne 18 montre la syntaxe d'appel de l'opérateur qui rejoint la notation mathématique


usuelle :
y “ f pa, b, cq
Cette technique permet donc de représenter un fonction f de type :
"
R3 Ñ R
f (16)
px, y, zq Ý
Þ Ñ u “ f px, y, zq “ F ˆ px ` y ` zq
avec F un paramètre (le facteur).
On voit ainsi qu'on donne l'illusion que l'objet f est une fonction car le C++ permet
de le faire se comporter comme une fonction en dénissant l'operateur () pour ce type.
De plus, pour construire l'objet-fonction f à la ligne 14, on a fourni un paramètre (un
double de valeur 4.0). Ce paramètre est utilisé pour xer la valeur de l'attribut réel
privé de la classe : _factor_. C'est un paramètre xé une fois pour toute pour l'objet
f. Il servira lors du calcul (ligne 33) pour toutes les combinaisons de 3 arguments réels
transmis à la fonction. On peut alors représenter la signature de la fonction ainsi :
"
R3 Ñ R
f (17)
Þ Ñ u “ f pparamètresq px, y, zq
px, y, zq Ý
Cette écriture rejoint celle de l'équation 14. Il semble bien qu'on ait là un autre moyen
de construire une interface générique pour les fonctions potentiel.
Le diagramme UML représentant la structure (objet fonction) scaled_sum est :

scaled_sum

− _factor_ : double Attribut privé


+ operator()(...)
Méthode publique

Remarque : On ne doit pas perdre de vue que l'opérateur () est en fait une méthode
normale de la classe comme il est montré à la ligne 21 dans laquelle il est invoqué de
manière traditionnelle. Cette notation est toutefois moins naturelle que celle de la ligne
18.

4.13 Exercice [2.5]


Ayant considéré précédemment comment on peut promouvoir le jeu de paramètres
statiques (V0 , a. . .) au rang d'objet d'une certaine classe, et comment on peut équiper
cette classe d'un opérateur adéquat pour qu'il respecte l'interface souhaitée pour une
fonction  potentiel  de R dans R, il est maintenant possible de créer une classe vs
représentant le potentiel carré VspV0 ,aq pxq.

Page 34
objet-fonction potentiel Vs
vs
V0 P R`˚
a P R`˚ u paramètres statiques (attributs)
x0 P R operator px P Rq Vs px0 q P R
position valeur calculée du potentiel

Questions :
1. Ecrire un programme prgobjfunc1.cxx qui reproduit le comportement du pro-
gramme prgfunc3.cxx de l'exercice [2.3]. On utilisera le formalisme C++ des objets
fonction pour implémenter la classe vs dont le diagramme UML est :
vs

− _v0_ : double
− _a_ : double

+ operator()(double)

2. Le potentiel carré Vs sera représenté au moyen d'un objet fonction de la classe vs. La
classe vs possèdera un constructeur initialisant convenablement les paramètres V0 et
a et un opérateur () adapté à la situation.
3. On procèdera de même pour les quatre autres potentiels Vt , Vh , Va et VW S :
vt vh

− _v0_ : double − _v0_ : double


− _a_ : double − _a_ : double

+ operator()(double) + operator()(double)

va vws

− _v0_ : double − _v0_ : double


− _v1_ : double − _a_ : double
− _a_ : double − _b_ : double

+ operator()(double) + operator()(double)

4.14 L'interface de la fonction potentiel selon C++ : héritage


Le recours aux objets fonction ne permet pas encore de garantir la généricité de notre
technique de programmation. On se souvient que la technique du C utilise d'une part
les structures et d'autre part l'utilisation du pointeur anonyme void * pour permettre

Page 35
à l'algorithme de calculer la valeur du potentiel sans connaître la nature réelle de la
fonction.
Avec les objets fonction du C++, on n'a pas encore gagné la partie. Chacune des
classes du programme prgobjfunc1.cxx de l'exercice précédent (vs, vt, vh, va et vws)
possède l'interface d'une fonction de R dans R. Dans ce sens, elles remplissent toutes le
contrat et peuvent être considérées comme de bons modèles de potentiel pour l'équation
de Schrödinger.
Il ne nous reste plus qu'à trouver le moyen de les faire manipuler par l'algorithme de
Numerov. Le C++ propose deux techniques pour atteindre la généricité tant désirée :
‚ Une approche statique mettant en ÷uvre le concept de patron (en anglais : tem-
plate ). Cette approche implique de demander au compilateur C++ de générer,
au moment de la compilation, l'interface (et donc le code correspondant) entre le
modèle de l'algorithme A et le modèle du système physique Σ (voir la gure 9).
Ceci demande d'utiliser des éléments syntaxiques spéciques du langage C++.
‚ Une approche dynamique s'appuyant sur le concept d'héritage de classe, plus par-
ticulièrement sur celui de classe mère abstraite. Dans ce cas, le compilateur
C++ délègue l'établissement de l'interface entre l'algorithme A et le modèle Σ
au moment de l'exécution du programme grâce à des mécanismes propres au lan-
gage.
Nous utiliserons ici la seconde approche. Ce choix n'enlève en rien aux mérites de la
première technique qui s'avère très ecace et très puissante dans de très nombreux cas.
Elle est d'ailleurs à la base de la conception de la bibliothèque standard de patrons du
C++ (STL 5 ) et de la bibliothèque Boost.
On a déjà rencontré le concept de composition à travers la mise en ÷uvre des
structures (en C) et des classes (en C++). Dans la forme simple qu'on a manipulée avec
la structure vsth_params dans le programme d'exemple prgfunc4.cxx, la composition
exprime l'idée d'appartenance. Ainsi la variable (objet) vs_pars déclarée en ligne 19
contient les variables (attributs) v0 et a, toutes deux de type double.
Dès lors que la relation entre deux objets A et B dans un programme peut être
naturellement conçue comme une relation d'appartenance exprimée par :
A possède B

on peut sérieusement envisager de créer une structure (ou une classe) pour le type de
l'objet A, avec un attribut représentant B .
Exemple :
C++
struct a_type
{
int b; // Par construction, une variable 'b' est contenue dans tout
// objet de type 'a_type'.
};

int main(void)
{
a_type a;
a.b = 1; // On manipule la variable 'b' contenue dans la variable 'a'.
5. http://www.cplusplus.com/reference/stl/

Page 36
return 0;
}

Ainsi on dira que toute automobile possède (contient) des roues :

struct wheel
{
bool puncture; // crevaison
double pressure; // unité: bar
};

struct car
{
wheel front_right;
wheel front_left;
wheel back_right;
wheel back_left;
wheel spare;
};

Ici la relation entre le type wheel et le type car est clairement exprimée. Il ne viendrait
(sans doute) pas à l'idée d'un programmeur de créer un modèle de roue qui contient une
voiture ! On voit ainsi que la composition permet d'exprimer un certain type de relation
qui apparait naturellement lorsque l'on souhaite modéliser un système. En UML cela est
représenté par le diagramme :
0..1 4..5
car wheel

Ainsi, ce schéma dénit qu'une voiture possède 4 ou 5 roues (4..5) et qu'une roue peut
appartenir à aucune ou une seule voiture (0..1).
Il existe cependant une autre forme de relation qu'on peut établir entre diérents
éléments d'un système. Reprenons ainsi l'exemple de la voiture ! Il est établi qu'une
voiture possède des roues. Mais c'est aussi le cas d'une bicyclette, quoiqu'en nombre et
position diérents. Ainsi on exprimerait aisément :
C++
struct bike
{
wheel front;
wheel back;
};

traduisant ainsi par la composition notre manière  très approximative  de concevoir un


vélo, au même titre qu'on l'a fait pour une voiture.

Page 37
wheel car bike
+ puncture: bool + front_right: wheel + front: wheel
+ pressure: double + front_left: wheel + back: wheel
+ back_right: wheel
+ back_left: wheel
+ spare: wheel

Imaginons maintenant que notre objectif soit de concevoir un programme simulant les
conditions de circulation de divers types de véhicule sur un réseau routier. On s'interesse
à l'inuence du comportement des conducteurs sur le trac en fonction de leur état :
expérience de conduite, état de fatigue, alcoolémie. . . et de la densité de la circulation d'un
réseau donné. On dénit ainsi utilement un type de données représentant un conducteur :
C++
struct driver
UML :
{
driver unsigned int experience_points;
+ experience_points: unsigned int double tiredness;
+ tiredness: double
+ alcoholization: double double alcoholization; // g/l
};

Si l'on considère maintenant qu'une voiture, aussi bien qu'une bicyclette, possède
un conducteur pour pouvoir évoluer sur le réseau, on est amené assez naturellement à
compléter la conception des classes car et bike :
C++
struct car
{
wheel front_right;
wheel front_left;
wheel back_right;
wheel back_left;
wheel spare;
driver pilot; // Composition additionnelle
};

struct bike
{
wheel front;
wheel back;
driver pilot; // Composition additionnelle
};

On voit ici qu'une auto et un vélo ont quelque chose en commun : un pilote (chauf-
feur/cycliste) ! En fait on peut traduire cela par le fait suivant :
Une auto et un vélo sont des véhicules pilotés.
Le C++ permet d'exprimer cette notion au travers de l'héritage de classe. Ainsi on peut
dénir une nouvelle structure représentant un véhicule piloté qui possède un pilote :

Page 38
C++
struct driven_vehicle
{
driver pilot;
};

On spécie ensuite le fait qu'une voiture et un vélo sont des véhicules pilotés grâce à
l'héritage de classe. La classe driven_vehicle devient ici la classe mère des deux
classe lles car et bike. On dit que les classes car et bike dérivent ou héritent de la
classe driven_vehicle :
C++
1 struct car : public driven_vehicle
2 {
3 wheel front_right;
4 wheel front_left;
5 wheel back_right;
6 wheel back_left;
7 wheel spare;
8 };
9
10 struct bike : public driven_vehicle
11 {
12 wheel front;
13 wheel back;
14 };

La représentation UML des relations entres les classes ci-dessus est représentée sur le
diagramme suivant :

Classe mère driven_vehicle


+ pilot: driver Attribut mis en commun

car bike
+ front_right: wheel + front: wheel
+ front_left: wheel + back: wheel
+ back_right: wheel
+ back_left: wheel
+ spare: wheel

Classes lles spécialisées

Ainsi, on factorise la fonctionnalité/caractéristique des classes lles (la possession


d'un pilote) au niveau même de la classe mère qui transmet par héritage cet attribut
pilote. On peut ici utiliser ces attributs (publics) comme s'ils étaient propres aux classes
lles :

Page 39
C++
1 int main(void)
2 {
3 car ma_charette;
4 ma_charette.front_right.puncture = false; // ^
5 ma_charette.front_right.pressure = 2.7; // | Accès aux
6 ma_charette.front_left.puncture = false; // | attributs
7 ma_charette.front_left.pressure = 2.7; // | propres
8 ma_charette.back_right.puncture = false; // | de la
9 ma_charette.back_right.pressure = 2.7; // | classe fille.
10 ma_charette.back_left.puncture = false; // |
11 ma_charette.back_left.pressure = 2.7; // v
12 ma_charette.driver.experience_points = 120; // ^ Accès à l'attribut
13 ma_charette.driver.tiredness = 0.23; // | de la classe mère.
14 ma_charette.driver.alcoholization = 0.0; // v
15
16 bike ton_clou:
17 ton_clou.front.puncture = false; // ^
18 ton_clou.front.pressure = 1.3; // | Accès aux attributs propres
19 ton_clou.back.puncture = false; // | de la classe fille.
20 ton_clou.back.pressure = 1.3; // v
21 ton_clou.driver.experience_points = 64; // ^ Accès à l'attribut
22 ton_clou.driver.tiredness = 0.11; // | de la classe mère.
23 ton_clou.driver.alcoholization = 0.0; // v
24
25 return 0;
26 }

Ce mécanisme est également valide pour les méthodes publiques (et protégées) de la classe
mère qui peuvent être réutilisées par les classes lles.
A noter toutefois que seuls les attributs ou méthodes publics de la classe mère peuvent
être utilisés librement à partir des classes lles. Les classes lles peuvent de plus utiliser
en interne les attributs ou méthodes protégés de la classe mère (c'est-à-dire de l'intérieur
de leurs propres méthodes).

4.15 Exercice [2.6]


Ecrire un programme prgobjfunc2.cxx adapté de prgobjfunc1.cxx (exercice [2.5])
de manière à ce que les classes d'objets-fonctions potentiel vs, vt, vh, va et vws héritent
d'une même classe mère vwell factorisant les attributs V0 (profondeur du puits) et a
(largeur du puits).

4.16 L'interface de la fonction potentiel selon C++ : classe abs-


traite
Le mécanisme d'héritage utilisé ci-dessus est très pratique car il permet de mettre en
commun des ressources dans plusieurs classes. On économise ainsi l'écriture de nombreuses

Page 40
lignes de code qui n'ont pas besoin d'être dupliquées dans chacune des classes spécialisées.
La relation entre une classe lle A et sa classe mère B peut être exprimée par :
A est un B
On dit également que A est une spécialisation de B . Ainsi, la classe vwell est la dépositrice
des paramètres statiques de tous les potentiels qui sont de type puits de profondeur et de
largeur xés. La classe vs (le puits carré) fait partie d'une telle famille de potentiels et
héritent naturellement des caractéristiques internes de vwell : V0 et a.
Toutefois, l'objectif à atteindre est celui de la généricité. En eet nous devons trouver
un moyen pour que l'algorithme de Numerov utilise n'importe quel objet fonction po-
tentiel de manière transparente. Ce dont a besoin l'algorithme est de pouvoir calculer la
valeur du potentiel V px0 q associée à une position x0 . Autrement dit, c'est l'interface de
l'opérateur d'invocation operator() qui doit être mis en commun à tous les potentiels.
Le C++ propose un mécanisme d'héritage particulier pour représenter une telle fonc-
tionnalité imposée aux objets fonction potentiel : elles doivent hériter d'une même classe
abstraite possédant au moins une méthode virtuelle pure.
L'exemple suivant montre la syntaxe de déclaration d'une classe abstraite base_vehicle
qui dénit une interface générique pour tous les types de véhicules à travers un ensemble
de méthodes publiques :
C++
1 struct base_vehicule
2 {
3 public:
4 // Méthodes virtuelles pures de l'interface de la classe abstraite,
5 // c'est le contrat que doivent remplir les classes filles concrètes :
6 virtual void start() = 0;
7 virtual void stop() = 0;
8 virtual void turn_left() = 0;
9 virtual void turn_right() = 0;
10 }

On s'attend alors à ce que la classe car, considérée comme un certain type de véhicule,
dérive de la classe base_vehicule et fournisse une implémentation de ces méthodes, an
de remplir son  contrat .
C++
1 struct drone : public base_vehicule
2 {
3 wheel front_right;
4 wheel front_left;
5 wheel back;
6 oper remote_control;
7 // Implémentation concrète de l'interface imposée
8 // par la classe mère 'base_vehicule' :
9 virtual void start();
10 virtual void stop();
11 virtual void turn_left();
12 virtual void turn_right();

Page 41
13 };
14
15 struct driven_vehicle : public base_vehicule
16 {
17 driver pilot;
18 // Pas d'implémentation de l'interface de la classe mère;
19 // cette classe est également abstraite.
20 // Les classes filles devront implémenter les 4 méthodes
21 // pour représenter des objets concrets.
22 };
23
24 struct car : public driven_vehicle
25 {
26 public:
27 wheel front_right;
28 wheel front_left;
29 wheel back_right;
30 wheel back_left;
31 wheel spare;
32
33 // Implémentation concrête de l'interface imposée
34 // par la classe grand-mère 'base_vehicule' :
35 virtual void start();
36 virtual void stop();
37 virtual void turn_left();
38 virtual void turn_right();
39 }

La représentation UML des relations entres les classes ci-dessus est représenté sur le
diagramme de la gure 14.

4.17 Exercice [2.7]


Questions :
1. Ecrire un programme prgobjfunc3.cxx adapté de prgobjfunc2.cxx (exercice [2.6])
de manière à ce que la classe vwell, dont dérivent les classes d'objets-fonctions po-
tentiel vs, vt, vh, va et vws, hérite elle-même d'une classe abstraite vbase qui impose
une interface constituée de l'opérateur d'invocation :
1 double operator()(double) const;

2. Dessiner le diagramme UML de la hiérachie des classes ainsi constituée.


3. Vérier qu'on ne peut pas instancier de variables de type vwell ou vbase.

Page 42
Classe mère abstraite base_vehicle

+ start() +
+ stop() Méthodes virtuelles pures
+ turn_right() (interface contractuelle)
+ turn_left()
Classe intermédiaire abstraite
(ne réalise pas l'interface)
Généralisation
Spécialisation

driven_vehicle drone
+ front_right: wheel
+ pilot: driver + front_left: wheel
+ back: wheel
+ remote_control: oper
+ start()
+ stop()
+ turn_right()
car bike + turn_left()
+ front_right: wheel + front: wheel
+ front_left: wheel + back: wheel
+ back_right: wheel + spare: wheel
+ back_left: wheel
+ start()
+ spare: wheel
+ stop()
+ start()
+ turn_right()
+ stop()
+ turn_left()
+ turn_right()
+ turn_left()
Classes spécialisées concrêtes
(réalisent l'interface)

Figure 14  Diagramme UML avec une classe abstraite et ses spécialisations.

Page 43
4.18 Retour sur les unités
An de nous préparer à pouvoir modéliser n'importe quel système physique, il nous
faut trouver un moyen de travailler quelque soit l'échelle de distance et d'énergie carac-
téristique du système. La solution proposée dans la section 3.2 ne fonctionne que si l'on
utilise une combinaison particulière d'unités et de constantes fondamentales exprimées
dans un certain système d'unités. Il est possible de mettre en oeuvre une technique qui
va nous permettre de gérer n'importe quelle situation. Il s'agit pratiquement de dénir
des constantes C++ qui prennent des valeurs exprimant les rapports relatifs entre les
unités fondamentales d'un système conventionnel d'unités comme le système SI. Dans un
second temps, on dénira des constantes C++ représentant les constantes fondamentales
usuelles à partir du jeu des unités de base. Enn des unités dérivées pourront être dénies
à partir des unités de base et de ces constantes fondamentales. Cette approche est inspirée
de la bibliothèque CLHEP 6 .
On trouvera sur le Moodle :
http://foad2.unicaen.fr/moodle/course/view.php?id=11655
le chier units_constants.hpp qui dénit un ensemble d'unités et constantes fondamen-
tales usuelles. Le programme d'exemple suivant illustre l'utilisation de ce composant :
C++
1 // Standard library:
2 #include<iostream>
3
4 // The Numerov project:
5 #include<units_constants.hpp>
6
7 int main(void)
8 {
9 double m = 0.13 * numerov::kilogram;
10 double v = 0.2 * numerov::meter / numerov::second;
11 double k = 0.6 * numerov::newton / numerov::meter;
12 double x = 0.12 * numerov::meter;
13 double ec = 0.5 * m * v * v;
14 double ep = 0.5 * k * x * x;
15 std::cout << "x = " << x / numerov::mm << " mm" << std::endl;
16 std::cout << "m = " << m / numerov::g << " g" << std::endl;
17 std::cout << "Ec = " << ec / numerov::joule << " J" << std::endl;
18 std::cout << "Ec = " << ec / numerov::keV << " keV" << std::endl;
19 std::cout << "Ep = " << ep / numerov::joule << " J" << std::endl;
20 std::cout << "Ep = " << ep / numerov::keV << " keV" << std::endl;
21 std::cout << "e = " << numerov::electron_charge / numerov::coulomb
22 << " C" << std::endl;
23 std::cout << "c = " << numerov::c_light / (numerov::cm / numerov::ns)
24 << " cm/ns" << std::endl;
25 std::cout << "hbarc = " << numerov::hbarc / (numerov::MeV * numerov::fm)
26 << " MeV.fm" << std::endl;
27 return 0;
28 }

6. http://proj-clhep.web.cern.ch/proj-clhep/

Page 44
4.19 Exercice [2.8] : modélisation C++ du système physique
Nous sommes maintenant n prêts pour créer une classe représentant le système phy-
sique. D'après ce qu'on a discuté dans la section 4.3, le système est totalement décrit
par :
‚ la masse du système,
‚ le potentiel d'interaction.
L'utilisation d'une classe abstraite comme classe mère de toutes les classes d'objets
fonction potentiel va nous permettre de manipuler toute forme de potentiel au travers
d'un pointeur attribut de la classe. Le diagramme UML de la classe physical_system à
créer est présenté sur la gure 15.

physical_system vbase

− _mass_ : double + operator()(double) const


− _potential_ : const vbase *
+ get_mass() const:double
+ set_mass(double)
+ compute_potential(double) const: double
+ set_potential(const vbase &)

physical_system vbase
0..* 1

Figure 15  Diagramme UML de la classe physical_system. Le contenu minimal des


classes est montré. En bas, le diagramme relationnel entre les deux classes exprime que
le système physique utilise l'agrégation d'un objet fonction héritant de vbase, c'est-à-
dire qu'il utilise un attribut pointeur pour faire référence à une instance concrète ex-
terne d'objet fonction potentiel. L'agrégation est un forme spéciale de la composition
où l'objet système physique ne possède pas l'attribut objet fonction mais simplement
une référence vers celui-ci ; l'objet fonction ainsi référencé peut avoir un cycle de vie
(création/utilisation/destruction) indépendant de celui de l'objet système physique qui
l'agrège.

Questions :
1. Créer le module C++ nommé numerov_utils. Dans l'espace de nom numerov, dénir
la classe abstraite vbase interface de tous les objets fonction potentiel ainsi que la
classe physical_system.
2. Créer le module C++ nommé potentials qui implémente, dans l'espace de nom
numerov, tous les objets fonction potentiel utilisés dans le programme prgobjfunc3.cxx.
3. Créer un programme prgobjfunc4.cxx qui reprend les fonctionnalités de prgobjfunc3.cxx
en s'appuyant sur les modules numerov_utils et potentials.

Page 45
4.20 Modélisation de la solution
Si l'on considère les trois éléments du projet exposés sur la gure 9, nous n'avons
implémenté que le premier. En attendant d'implémenter l'algorithme sous la forme d'une
classe, on peut désormais s'attaquer au problème de la modélisation de la solution du
problème.
On s'attend à ce que la résolution de l'équation de Schrödinger soit constituée d'un
ensemble de valeurs propres de l'énergie Ek associées chacune à une fonction d'onde φk pxq.
On représentera une telle relation en UML de la façon suivante :

Ensemble des solutions Solution


0..*
E1 , φ1 pxq ; E2 , φ2 pxq ; . . . Ek , φk pxq

où l'objet ensemble des solutions possède un certain nombre d'objets solutions indé-
pendantes (couple Ek , φk pxq). Ce nombre peut éventuellement être nul en l'absence de
solution.
Ainsi, on pourra créer une classe numerov_solution dont le diagramme UML est :

représente Ek

numerov_solution

− _energy_ : double
− _x_ : std::vector<double> représente les xj P ra, bs
− _phi_ : std::vector<double>

+ get_energy() const : double


+ set_energy(double)
+ append_phi_sample(double, double)
+ get_xmin() const : double
représente les φk,j “ φk pxj q
+ get_xmax() const : double
+ get_phi(double) const : double
+ reset()
+ print(std::ostream &, double) const

L'ensemble des solutions pourra être représenté ultérieurement par un tableau d'objets de
type numerov_solution, par exemple au moyen d'un std::vector<numerov_solution>,
classe générique patron disponible dans la bibliothèque standard STL.

4.21 Exercice [2.9] : implémentation de la classe solution


Questions :
1. Interpréter le diagramme UML ci-dessus pour comprendre la fonctionnalité de chaque
attribut et de chaque méthode.
2. Compléter le module numerov_utils avec la classe numerov_solution.
3. Tester cette classe au moyen d'un programme test_numerov_solution.cxx.

Page 46
4.22 Modélisation de l'algorithme
Enn il est temps de proposer une modélisation orientée objet de l'algorithme de
Numerov. Nous en avons déjà proposé une implémentation élémentaire et non générique
dans l'exercice [1.1]. Il y a donc lieu désormais de concevoir un module numerov_algo
qui va implémenter la méthode de recherche de solution de l'équation 1 dans tous les cas
possibles, grâce à l'approche générique que nous avons implémentée dans cette deuxième
séquence de travail.
Il est ici utile de reprendre le programme prgnumerov2.cxx et d'en identier les élé-
ments dénis dans la fonction principale. Cette analyse du programme original permettra
de dégager les caractéristiques principales d'un objet algorithme.
Quels sont les structures de données utilisées au sein de l'algorithme ? On doit ici consi-
dérer que tout ce qui concerne le système physique (masse de la particule m et fonction
potentiel V pxq) relève de la modélisation de ce système par l'intermédiaire de la classe
physical_system. Il s'agit donc d'une ressource extérieure, référencée par l'utilisateur
par exemple par agrégation.
On utilisera ici l'adresse (pointeur) d'un objet de type physical_system, extérieur à
la classe, à partir duquel l'algorithme réalise les calculs (déni par l'utilisateur).
Ainsi tout ce qui reste est attribuable à l'algorithme lui-même. On établit alors l'in-
ventaire suivant :
 les bornes de l'intervalle de travail rxmin ; xmax s (xées par l'utilisateur),
 les nombres de pas Nl et Nr pour discrétiser l'espace (xés par l'utilisateur),
 la valeur de la coordonnées du point de contrôle xm (xée par l'utilisateur),
 les valeurs l et r utilisées pour amorcer la récurrence (3) (xées par l'utilisateur),
 la taille des pas hl et hr (calculées automatiquement),
 l'énergie de test Et (xée par l'utilisateur),
 les tableaux des valeurs ql et qr des équations (4) (calculées automatiquement),
 les tableaux des valeurs φl et φr (calculées automatiquement),
 les valeurs des dérivées φ9 l pxm q et φ9 r pxm q (calculées automatiquement).

4.23 Exercice [2.10] : implémentation de la classe numerov_algo


Questions :
1. Dénir le diagramme UML de la classe numerov_algo en commençant par les attri-
buts (privés).
2. Compléter le diagramme par un ensemble de méthodes protégées réalisant chacune
une tâche particulière parmi celles implémentées dans le programme prgnumerov2.cxx
(voir aussi la séquence de l'algorithme à la section 2.2).
3. Compléter le diagramme par un ensemble de méthodes protégées réalisant chacune
une tâche particulière parmi celles implémentées dans le programme prgnumerov2.cxx
(voir aussi la séquence de l'algorithme à la section 2.2).
4. Enn ajouter une ou plusieurs méthodes publiques qui permettront à l'utilisa-
teur d'invoquer l'algorithme pour un système physique (classe physical_system)
et une énergie test Et donnés et de calculer la solution candidate associée (classe
numerov_solution). On pourra décomposer en plusieurs méthodes aux tâches bien
dénies :

Page 47
 une méthode configure(...) qui positionne les paramètres de  réglages  de
l'objet algorithme (choix de xa , xb , xm , Nl , Nr , l , r ),
 une méthode set_physical_system(...) qui dénit une référence interne (par
agrégation) à un objet de type physical_system au sein de l'objet algorithme.
 une méthode compute(...) qui réalise le calcul complet de la fonction d'onde
candidate pour l'énergie test Et demandée.
 une méthode build_solution(...) qui permet d'extraire un objet de type
numerov_solution de la solution calculée par l'algorithme.
5. Implémenter la classe numerov_algo en prenant soin que le constructeur initialise
correctement les attributs de la classe avec des valeurs  raisonnables .
6. Ecrire un ou plusieurs programmes de test qui permettront de retrouver les résultats
déjà établis avec les versions précédentes du programme (n de la séquence 1) et
d'étudier d'autres systèmes physiques que ceux correspondant au potentiel rectangu-
laire.

Potentiel carré a=1.00 (Ångström), V0=20.00 (eV), E=-14.00 (eV)

Potentiel V(x)
0 Fonction d'onde φ candidate

φ(x) (sans dimension)


-5
Energie (eV)

-10

-15

-20

-6 -4 -2 0 2 4 6
x (Ångström)

4.24 Conclusion
A l'issue de cette séquence, on dispose d'un outil générique complet permettant l'étude
de nombreux systèmes physiques. Cet outil est implémenté grâce à un ensemble de classes
et d'interfaces regroupées au sein d'un module écrit en C++. C'est une petite bibliothèque
numérique à part entière sur la base de laquelle il est possible de concevoir des programmes
spécialisés pour l'étude de cas physiques variés tombant dans la portée de la résolution
de l'équation 1.
L'intérêt de cette approche est de mettre à disposition un outil unique qui pourra être
réutilisé de nombreuses fois en se concentrant seulement sur les aspects et questions de
physique et non plus sur les techniques de programmation. L'apprentissage et la maîtrise
de ces techniques de programmation en C++ est donc essentiel pour nous permettre
d'aborder la famille de problèmes physiques qui nous intéressent aujourd'hui.
D'autres avantages viennent naturellement avec cette approche. La séparation claire
entre la mise en ÷uvre de la méthode de Numerov (notre bibliothèque/module) et son

Page 48
utilisation pour un cas particulier (l'étude d'un système physique spécique faite au
moyen d'un programme principal) permet :
 de forcer les utilisateurs à analyser un problème de physique particulier de manière
à discriminer clairement les éléments qui relèvent de la physique de ceux qui ne
concernent que la technique numérique.
 de maintenir et corriger plus facilement les diérents éléments logiciels mis en jeu
dans le cadre d'une étude.
 de travailler de manière collaborative en répartissant les tâches de programmation
entre plusieurs partenaires respectant ensemble les interfaces et concepts proposés
par la bibliothèque.
 d'étendre les fonctionnalités des éléments logiciels.
Il faut noter qu'il n'est pas strictement nécessaire d'utiliser un langage orienté objet
comme le C++ pour parvenir à une telle approche générique. Cependant un tel langage
permet de le faire de manière très naturelle au moyen de ses constructions et concepts
qui rendent possible une implémentation claire, ecace et puissante : classes et objets,
héritage, interfaces, foncteurs. . .

On peut encore envisager des améliorations à notre projet. En particulier, on pourra


envisager de créer un algorithme qui permet de trouver, si elles existent, toutes les solu-
tions du problème, épargnant ainsi à l'utilisateur de les rechercher de rechercher manuel-
lement et graphiquement.
Un tel algorithme devra donc scanner automatiquement les  énergies test  dans l'in-
tervalle adéquat et rechercher à une précision donnée les valeurs propres de l'énergie. En
principe, la technique utilisée est similaire à une celle mise en ÷uvre lors d'une recherche
de racine. En eet, il s'agit de détecter les énergies pour lesquelles la diérence des déri-
vées à droite et à gauche au point de contrôle s'annule. On est donc amenée à résoudre
l'équation suivante :
´ ¯
9 9
φl pxm q ´ φr pxm q pEt q “ 0 (18)
On peut donc envisager de créer une nouvelle classe algorithme numerov_solver, uti-
lisant les outils développés précédemment, pour implémenter la collecte automatique des
solutions, grâce par exemple à une méthode dichotomique ou une méthode de Lagrange
(voir le cours).

Page 49
5 Séquence 3 : étude spécique
On propose ici d'utiliser le module de résolution de l'équation de Schrödinger 1 dans
un cas particulier tiré par exemple de la gure 6.
Ecrire un ou plusieurs programmes permettant une telle étude et en présenter les
résultats marquants : existence et évolution des solutions en fonction des paramètres du
potentiel, analogie avec des systèmes physiques réels.

Page 50

Vous aimerez peut-être aussi