Projet : étape 3 (semaine 4 du semestre)

Buts

Nous allons cette semaine continuer la mise en place des premiers éléments de base de notre projet (qui pour le moment est encore assez général) et réviser (déjà !) la classe des vecteurs tridimensionnels à la lumière de nos nouvelles connaissances (constructeurs et surcharge d'opérateurs).

Une fois ces vecteurs révisés, vous allez coder les premières bases pour les objets à simuler: les premières particules.

Préliminaires :

N'oubliez pas tous les conseils donnés la semaine passée :


[1*] Exercice P4 : la classe Vecteur3D revisitée

Le but de cet exercice est de modifier la classe Vecteur3D commencée il y a deux semaines en ajoutant les constructeurs et les principaux opérateurs.
Reprenez donc vos fichiers Vecteur3D.cc, Vecteur3D.h et testVecteur3D.cc.

[ Remarque : les modifications apportées à un code existant sont typiques de l'évolution d'un logiciel. On appelle cela le « refactoring » (et il y a des livres entiers dédiés à ce sujet).
Ce n'est donc pas par erreur de conception du projet que je vous fais modifier votre code, mais bien pour
(1) voir l'introduction des nouveaux concepts vus en cours et leur importance, leurs implications ; et
(2) pratiquer ce type de révisions/modifications de code (refactoring).
Nous le ferons encore quelques fois au fur et à mesure des nouveaux concepts. Si votre conception du projet est claire et cohérente, cela ne devrait pas poser plus de problèmes que de déplacer/remplacer quelques lignes de code.
]

P4.1 Constructeurs

Définissez (au moins) les constructeurs suivants :

  1. le constructeur par défaut qui crée le vecteur nul  ;
  2. un constructeur par coordonnées cartésiennes, prenant trois double comme arguments et construisant un vecteur 3D à partir de ces coordonnées ;
  3. le constructeur de copie, si vous pensez que cela est nécessaire.

[Question P4.1] Avez-vous ajouté un constructeur de copie ? Pourquoi (justifiez votre choix) ?

[Question P4.2] Si l'on souhaitait ajouter un constructeur par coordonnées sphériques (deux angles et une longueur),

Réfléchissez à ces questions et répondez-y dans votre fichier REPONSES.
Argumentez vos réponses.

P4.2 Opérateurs

Transformez les méthodes affiche() et compare() en des opérateurs.

[Question P4.3] Quels opérateurs avez vous introduits ?

Répondez à cette question dans votre fichier REPONSES.

Modifiez votre programme de test dans le même esprit que ci-dessous (cela peut signifier que le code ci-dessous n'est pas forcément 100% juste) :

// un vecteur en 3D :
Vecteur3D vect1(1.0, 2.0, -0.1);

// un autre vecteur en 3D
Vecteur3D vect2(2.6, 3.5,  4.1);

Vecteur3D vect3(vect1);  // copie de V1
Vecteur3D vect4;         // le vecteur nul

cout << "Vecteur 1 : " << vect1 << endl;
cout << "Vecteur 2 : " << vect2 << endl;
cout << "Vecteur 3 : " << vect3 << endl;
cout << "Vecteur 4 : " << vect4 << endl;

cout << "Le vecteur 1 est ";
if (vect1 == vect2) {
    cout << "égal au";
} else {
    cout << "différent du";
}
cout << " vecteur 2," << endl << "et est ";
if (vect1 != vect3) {
    cout << "différent du";
} else {
    cout << "égal au";
}
cout << " vecteur 3." << endl;

P4.3 Opérateurs algébriques

Continuez votre amélioration du code en utilisant la surcharge d'opérateurs pour les opérations algébriques de base définies il y a deux semaines :
transformez en opérateurs toutes les méthodes de la classe Vecteur3D qui s'y prettent (p.ex. exceptées la norme euclidienne et son carré).

Pensez aux deux formes des opérateurs : la forme Op et la forme Op= ; par exemple, + et +=.

Note : pour le produit vectoriel, utiliser l'opérateur operator^.
Notez cependant que cet opérateur a une priorité faible, notamment inférieure à celle de l'opérateur d'affichage <<. Il faut donc écrire :

cout << (a ^ b);

et non pas

cout << a ^ b;

qui serait compris comme

(cout << a) ^ b;

ce qui n'a aucun sens.

Pour le vecteur unitaire, vous pouvez utiliser l'opérateur (unaire) operator~. Exemple :

 Vecteur3D a({1., 2.5, -3.4});
 Vecteur3D direction( ~a ); // utilisation de l'opérateur unaire ~

Modifiez votre programme de test de sorte à prendre en compte vos modifications (et testez votre programme). Effectuez les mêmes tests que la semaine passée, plus leur version symétrique pour tous les opérateurs commutatifs (c.-à-d. si vous aviez par exemple testé a.addition(b) la semaine passée, cela devient cette semaine a + b ; mais il faut aussi tester b + a).


[1*] Exercice P5 : Particules

On vous demande ici de coder une classe Particule qui servira à représenter les particules du système granulaire simulé. Cette classe devra posséder au moins les membres suivants :

Vous doterez votre classe des constructeurs et des méthodes qui vous semblent adéquat (et du destructeur si nécessaire).

[Question P5.1] Comment avez-vous implémenté la masse des particules : comme attribut ou comme méthode ?

Explicitez et justifiez votre choix dans votre fichier REPONSES.

Vous associerez de plus à votre classe une surcharge de l'opérateur externe << permettant d'afficher des informations de base sur la particule (utile pour « déboguer » le programme). Vous êtes libres de faire cet affichage selon le format que vous souhaitez, par exemple : (ρ1= 1 mg/mm3, ρ2= ρ3= 12.5 mg/mm3 ; m est ci-dessous affichée en mg et r en mm)

Voici trois particules :
[ pos = (0 0 0), v = (0 0 0), m = 0.268083, r = 0.4 ]
[ pos = (1 0 0), v = (0 0.2 0), m = 0.176715, r = 0.15 ]
[ pos = (0 0 1), v = (0 0 -0.05), m = 0.05236, r = 0.1 ]

Produisez également un programme de test testParticule.cc dans lequel sont créés différentes particules, dont au moins une par copie, et d'en afficher les caractéristiques.

Modifiez le Makefile (ou le(s) CMakeLists.txt) pour établir le lien entre les différents fichiers de votre projet.

NOTE IMPORTANTE : Je rappelle que les programmes de test font pleinement partie du projet et devront être rendus avec le projet.

Pour finir, complétons la classe « Particule » avec ce qui sera nécessaire pour simuler son comportement physique. Pour chacun des ajouts ci-dessous, c'est à vous de choisir s'ils doivent être public ou private.
Vous pouvez faire cette partie progressivement à partir de maintenant et jusqu'à la semaine 6 (dans deux semaines) qui sera vraiment le moment où nous utiliserons tout ça.

Ajoutez à la classe « Particule » :

[ Vous pouvez bien sûr changer ces noms comme bon vous semble et les adapter à votre guise. ]

Pour toutes les constantes physiques dont vous avez besoin (g, ρmilieu, ηmilieu, ...), vous pouvez soit en faire des attributs (constants) de classe (c.-à-d. static) pour une classe bien choisie (mais réfléchissez à ce que cela signifie), soit des constantes, p.ex. dans un namespace (optionnel, hors programme du cours). Voir à ce sujet l'annexe suivante.

Si vous souhaitez déjà tester certaines de ces implémentations, voici quelques exemples, que nous retrouverons en semaine 6 :

Voici trois particules :
[ pos = (0 0 0), v = (0 0 0), m = 0.268083, r = 0.4 ]
[ pos = (1 0 0), v = (0 0.2 0), m = 0.176715, r = 0.15 ]
[ pos = (0 0 1), v = (0 0 -0.05), m = 0.05236, r = 0.1 ]

Particule 1 :
Force "perso" :
	f= (0 0 0) --(l=0.135717)--> f= (0 0 -2629.89)
Forces des autres grains :
    Particule 2 :
	distance entre grains = 1
	forceLJ: 24 * 25 / 0.885^2 * f(1 / 0.885)
	       = 766.063 * 0.0166149
	       = 12.7281
	df= (12.7281 0 0), f= (12.7281 0 -2629.89)
    Particule 3 :
	distance entre grains = 1
	forceLJ: 24 * 25 / 0.885^2 * f(1 / 0.885)
	       = 766.063 * 0.0166149
	       = 12.7281
	df= (0 0 12.7281), f= (12.7281 0 -2617.16)
Bouger :
	f= (12.7281 0 -2617.16)
	dv= (0.0474783 0 -9.76252)
	dx= (4.74783e-05 0 -0.00976252)
	x= (4.74783e-05 0 -0.00976252)
	v= (0.0474783 0 -9.76252)

***SANS*** QUE LA PARTICULE 1 AIT BOUGÉ :
Particule 2 :
Force "perso" :
	f= (0 0 0) --(l=0.0508938)--> f= (0 -0.0101788 -1733.57)
Forces des autres grains :
    Particule 1 :
	distance entre grains = 1
	forceLJ: 24 * 25 / 0.885^2 * f(1 / 0.885)
	       = 766.063 * 0.0166149
	       = 12.7281
	df= (-12.7281 0 0), f= (-12.7281 -0.0101788 -1733.57)
    Particule 3 :
	distance entre grains = 1.41421
	forceLJ: 24 * 25 / 0.885^2 * f(1.41421 / 0.885)
	       = 766.063 * 0.0330692
	       = 25.3331
	df= (-17.9132 0 17.9132), f= (-30.6413 -0.0101788 -1715.66)
Bouger :
	f= (-30.6413 -0.0101788 -1715.66)
	dv= (-0.173394 -5.76e-05 -9.70863)
	dx= (-0.000173394 0.000199942 -0.00970863)
	x= (0.999827 0.000199942 -0.00970863)
	v= (-0.173394 0.199942 -9.70863)

***SANS*** QUE LES PARTICULES 1 ET 2 AIENT BOUGÉ :
Particule 3 :
Force "perso" :
	f= (0 0 0) --(l=0.0339292)--> f= (0 0 -513.649)
Forces des autres grains :
    Particule 1 :
	distance entre grains = 1
	forceLJ: 24 * 25 / 0.885^2 * f(1 / 0.885)
	       = 766.063 * 0.0166149
	       = 12.7281
	df= (0 0 -12.7281), f= (0 0 -526.377)
    Particule 2 :
	distance entre grains = 1.41421
	forceLJ: 24 * 25 / 0.885^2 * f(1.41421 / 0.885)
	       = 766.063 * 0.0330692
	       = 25.3331
	df= (17.9132 0 -17.9132), f= (17.9132 0 -544.29)
Bouger :
	f= (17.9132 0 -544.29)
	dv= (0.342117 0 -10.3952)
	dx= (0.000342117 0 -0.0104452)
	x= (0.000342117 0 0.989555)
	v= (0.342117 0 -10.4452)

Les valeurs utilisées pour les constantes physiques sont les suivantes (attention aux unités !) :

g=(0,0,−9.81) ms-2=(0,0,−9.81e3) mm s-2, ρparticule= 1 mg/mm3, dt=0.001 s, σ=0.885 mm, ε=25 mg mm3 s-2, ηmilieu=ηair=1.8e-2 mg mm-1 s-1 et ρmilieu=ρair=1.3e-3 mg mm-3.


Annexe : comment partager des constantes entre plusieurs fichiers ?

Comment correctement faire, sans duplication de code (c.-à-d. sans copie), le partage de constantes, comme par exemple la valeur de g, entre plusieurs fichiers C++ ? En clair : comment partager des variables globales (ouille !!) constantes ?

Tout d'abord un rappel : JAMAIS de variables globales ! C'est vraiment la règle...
...mais comme toute bonne règle, il y a des exceptions (qu'il faut bien comprendre).
Partager la constante g dans un programme de simulation physique est une telle exception car g est à la fois universelle (bien connue bien avant de faire ce programme) et constante.

Depuis C++ 17

La meilleure et plus simple façons de faire, mais qui nécessite de compiler au moins avec la norme C++17 (ou au dessus, -std=c++17), c'est simplement de faire un fichier constantes.h, qui contiendra cette variable et sa valeur :

#pragma once
#include "Vecteur3D.h"

namespace votre_choix_de_nom {
  inline constexpr double cte(2.1967);
  inline const Vecteur3D g(0.0, 0.0, -9.81); // ou autre initialisation similaire avec vos constructeurs
}

Note : si vous voulez faire des constantes constexpr de vos propres classes (comme p.e.x Vecteur3D ci-dessus), alors cela supposera d'avoir au moins un constructeur constexpr (mettre constexpr devant le nom du contructeur). Il faut pour cela que ce soit possible (p.ex. avec des vecteurs de taille fixe connue au départ implémentés p.ex. à base de array c'est possible; avec des vecteurs de taille variable implémentés p.ex. à base de vector cela est beaucoup plus compliqué...).

À noter, qu'en effet, il n'y a pas de fichier constantes.cc ici ; le seul .h suffit.

Avant C++ 17

Une autre façon de faire, est de définir un fichier, p.ex. constantes.cc, qui contiendra effectivement cette variable et sa valeur :

#include "Vecteur3D.h"

extern const Vecteur3D g(0.0, 0.0, -9.81); // ou autre initialisation similaire avec vos constructeurs

(et c'est tout, rien de plus ici ; si, par contre, vous souhaitez définir d'autres constantes, c'est bien sûr ici qu'il faudrait les mettre, on ne définirait pas un fichier différent par constante !).
Notez bien, cependant, la présence du mot clé extern, nécessaire en C++ (mais pas en C, pour celles ou ceux qui en auraient fait avant).

Ensuite, bien sûr, il faut le fichier « .h » correspondant :

#pragma once
#include "Vecteur3D.h"

extern const Vecteur3D g;

Attention ! SANS initialisation ici !!

Et voilà !

Pour celles et ceux qui savent ce que c'est et veulent améliorer leur code : il serait bien sûr préférable de mettre cela dans un namespace.


Dernière mise à jour le 13 février 2026
Last modified: Fri Feb 13, 2026