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.
N'oubliez pas tous les conseils donnés la semaine passée :
Il est prévu une répartition des tâches du projet au sein du binôme : chacun doit faire environ la moitié du travail lié au projet ; essayez donc de rapidement vous organiser et concevoir correctement la répartition entre vous ;
par ailleurs, avant de vous lancer directement dans la réalisation de la nouvelle partie du projet, assurez-vous de bien avoir compris les concepts présentés en cours, par exemple en faisant au préalable quelques exercices si nécessaire ;
cette première partie du projet (de la semaine 1 à cette semaine) est assez progressive de sorte à vous permettre de vous
organiser et prendre vos marques dans ce projet ; profitez-en donc pour
faire les choses correctement (ce qui permet de gagner du
temps par la suite) et évitez absolument de prendre du retard...
Je
vous conseille d'avancer le plus possible en synchronisation avec le
planning prévu, ou alors avec au maximum une semaine de décalage pour bien
assimiler le cours.
Pour terminer avec les conseils et remarques préliminaires, n'oubliez pas de revenir de temps en temps consulter la page de description générale du projet et la page d'administration afin de situer votre travail courant par rapport au projet dans son ensemble.
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.
]
Définissez (au moins) les constructeurs suivants :
[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.
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;
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).
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 » :
un attribut force permettant de représenter la force s'appliquant sur la particule ;
un attribut de classe (static) constant epsilon ;
un attribut de classe (static) constant sigma ;
une méthode forceLJ() (elle pourrait aussi être static pour celles et ceux qui veulent et qui comprennent [hors cours]) ;
cette méthode calculera 24 ε / σ2 f(x/σ), avec f donnée en page 3 des compléments mathématiques du projet ; elle retourne un double ;
une méthode lambda() calculant la force due au frottement fluide (elle dépend de la vitesse et du rayon de la particule, cf les compléments mathématiques du projet) ; elle retourne un vecteur ;
une méthode ajouteForce() prenant un vecteur en argument et l'ajoutant simplement à l'attribut force ;
une autre méthode ajouteForce() sans argument (peut-on faire ça en C++ ?) qui ajoute à force la valeur m g - λ v, partie des forces s'appliquant sur la particule qui ne dépend ni des autres particules ni des obstacles (et qui est donc totalement indépendante de tout modèle d'interaction) ;
une troisième méthode ajouteForce() prenant comme argument une particule (peut-on faire ça en C++ ?) et qui ajoute à l'attribut force de la particule courante, la force qu'exerce la particule passée en argument sur la particule courante ;
pour l'implémentation de cette méthode, voir la section 4.1 page 3 des compléments mathématiques du projet ;
une méthode bouger(), prenant un pas de temps (nombre réel) comme argument, et qui aura pour but de faire évoluer la particule (mise à jour de sa vitesse et de sa position) ;
en supposant que l'attribut force est à jour (ce qui sera fait par ailleurs), écrivez la définition de cette méthode bouger() en utilisant la section 2 des compléments mathématiques du projet ; cette définition est extrêmement simple et devrait tenir en 2 lignes de code ;
cette méthode devra de plus, pour finir, remettre à 0 la force (puisqu'elle a été traitée).
[ 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.
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.
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.
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.