Projet : étape 4.2
Strates
Buts:
Modéliser des différentes «couches» de cellules d'un fragment d'organe.
Préambule
Comme évoqué précédemment, un Organ va être discretisé et vu comme une grille carrée. Nous allons donc, à partir de maintenant, devoir travailler avec deux types de coordonnées :
- Les Vec2d, utilisés jusqu'ici, et qui permettent de représenter un point précis, ce que nous appelerons une position physique, sur la grille. Un Vec2d est une paire de double représentant les coordonnées en x et en y d'une position physique.
- Les CellCoord, définies dans le fichier fourni Utility.hpp, qui permettent de représenter une position logique dans la grille. Un CellCoord est donc une paire de coordonnées (ligne, colonne) permettant de repérer une case dans la grille.
Pour commencer, dotez la classe Organ :
- d'une méthode isOut prenant en paramètre une CellCoord et retournant true si cette dernière représente une case en dehors de la grille et false sinon.
- d'une méthode CellCoord toCellCoord(const Vec2d& pos) permettant de trouver la case de la grille contenant le point pos. Pour le codage de cette méthode vous pourrez utiliser la fonction vec2dToCellCoord fournie dans Utility/Utility.[hpp][cpp].
Empilement
Comme nous l'avons vu dans le decriptif général de cette partie, chaque case de la grille doit pouvoir superposer jusqu'à trois niveaux
de cellules différentes : une cellule de type «ECM», une de type «organe» et une de type «réseau sanguin». L'idée générale est qu'une cellule sanguine peut reposer sur une cellule du organe qui elle même repose sur une cellule de l'ECM.
Une case aura donc :
- un empilement à un seul niveau si elle ne contient qu'une cellule de type ECM;
- un empilement à deux niveaux si elle contient une cellule du organe et une cellule ECM mais pas de cellule sanguine ou si elle contient une cellule sanguine et une cellule ECM mais pas de cellule du organe;
- et un empilement à trois niveaux s'il y a une cellule sanguine qui irrigue une cellule du organe qui repose sur une cellule ECM.
Dans ce qui suit, nous vous suggérons d'introduire :
- la classe CellsLayer qui représente la superposition d'au plus trois types de cellules;
- et d'une hiérarchie de cellules permettant de représenter les différents types de cellules impliquées.
Chaque case de la grille discretisant la vue interne correspondra donc à une instance de CellsLayer (nous y reviendrons un peu plus loin).
Hiérarchie de cellules
Nous allons commencer par matérialiser la notion de cellule en créant une classe Cell dont dériveront trois sous-classes : ECMCell, OrganCell et BloodCell (dont chacune correspondra à un des niveaux mentionnés plus haut).
Chacun de ces niveaux va véhiculer des substances (et donc être associé à un objet de type Substance). Ceci permet de modéliser les flèches rouges du schéma déjà donné dans le descriptif général:
Comme une cellule peut aussi ne véhiculer aucune substance, il est judicieux de modéliser la substance associée à un Cell comme un pointeur (un Substance* qui vaudra nullptr lorsqu'aucune substance n'est véhiculée par la cellule).
Pour faire en sorte qu'il y ait un échange entre un niveau et un autre (flèches bleues), il faudra gérer le passage d'une fraction de produit d'un niveau à l'autre.
Exemple concret:
supposons qu'une case [i][j] superpose une ECMCell véhiculant la Substance s1 et une OrganCell véhiculant la Substance s2 . On peut imaginer par exemple que s1 contient une quantité q1 de glucose et que s2 contient une quantité q2 du même produit. Pour matérialiser le fait que la OrganCell consomme une quantité q de glucose depuis la ECMCell on va simplement débiter s1 d'une quantité q de glucose (q1 = q1 - q) et ajouter à s2 cette même quantité de glucose (q2 = q2 + q).
Ceci devrait rappeler à votre bon souvenir les méthodes codées dans la classe Substance mais nous y reviendrons un plus tard.
A ce stade la hiérarchie des cellules va être très simple car nous ne nous préoccuperons pas encore de l'implémentation de l'échange de substances ou de d'autres mécanismes d'évolution (comme la division des cellules). ECMCell, OrganCell et BloodCell auront donc au terme de cette étape un contenu très proche et très basique.
Les BloodCell pourront représenter soit des cellules sanguines de l'artère, soit des cellules sanguines des capillaires (seules ces dernières pourront diffuser des substances).
La catégorie de la BloodCell (artère ou capillaire) sera modélisée au moyen d'un attribut de type TypeBloodCell fourni dans le fichier Types.hpp.
"Strates" : la classe CellsLayer
Dans la grille utilisée pour modéliser la vue interne, une position logique d'indices [i][j] peut donc voir s'"empiler" jusqu'à trois niveaux de Cell différents. Comme suggéré plus haut, la classe CellsLayer a pour but de "fédérer" et gérer ces différents niveaux.
Un CellsLayer sera ainsi caractérisé par :
- une position logique sur la grille (un CellCoord);
- la cellule de l'ECM occupant cette position logique (un attribut de type ECMCell* fera bien l'affaire pour que la valeur nullptr puisse indiquer, le cas échéant, qu'il n'y a pas de cellule d'un tel type à cette position);
- la cellule du organe occupant cette position (un OrganCell*);
- et la cellule du réseau sanguin occupant cette position (un BloodCell*).
Les trois derniers attributs représentent donc les 3 niveaux («ECM», «organe» et «réseau sanguin») présents à une position donnée et que le CellsLayer va gérer.
Par ailleurs, ces trois niveaux pourront diffuser des substances sur les cases avoisinantes. Pour connaitre ces cases, il sera nécessaire de savoir sur quelle vue interne (Organ) elles prennent place. Ceci permettra de savoir par exemple dans quelles limites il existe des cases voisines.
- Vous pourrez donc supposer qu'un CellsLayer a «connaissance» de l'organe auquel il appartient (attribut de type Organ*).
- Vous supposerez également qu'un Cell a connaissance du CellsLayer en charge de sa gestion sur l'organe. C'est par ce biais qu'il va savoir quels sont les autres Cell qui occupent la même position logique que lui et donc avec lesquels il pourra échanger des substances au cours du temps.
- Attention alors aux situations de dépendances circulaires. Tous les attributs de type pointeurs peuvent voir leur type uniquement pre-déclarés dans CellsLayer.hpp et Cell.hpp, pour éviter tout souci au niveau des inclusions.
Pour ce qui est des constructeurs, nous vous suggérons (pour être conforme aux tests fournis):
- De doter les Cell d'un constructeur prenant en paramètre un CellsLayer* (ce qui permettra au Cell de savoir quel CellsLayer est en charge de sa gestion sur l'organe). Ce constructeur initialisera la substance présente au niveau de la cellule en allouant dynamiquement une substance (avec zéro comme quantité de glucose, VGEF et inhibiteur). Vous coderez les constructeurs dans les sous-classes en supposant qu'il prennent la même liste d'arguments, sauf pour BloodCell qui prendra en plus en paramètre la nature de la cellule sanguine (de type TypeBloodCell) avec ARTERY comme valeur par défaut.
- De doter la classe CellsLayer d'un constructeur prenant en paramètre une position logique (de type CellCoord) et un Organ*. Le constructeur initialisera le niveau ECM au moyen d'une ECMCell dynamiquement allouée. Il n'y aura par défaut ni cellule du organe ni de cellule sanguine.
[Question Q4.4] Avec quelle valeur le constructeur de CellsLayer doit il invoquer celui du ECMCell qu'il alloue dynamiquement ?
[Question Q4.5] Que doit faire le destructeur d'un Cell pour éviter les fuites de mémoire ?
Répondez à ces questions dans votre fichier REPONSES et apportez les modifications impliquées à votre code.
Nous vous suggérons également de doter la classe CellsLayer :
- Des méthodes hasECMCell, hasOrganCell et hasBloodCell retournant true s'il existe bien une cellule du type ECMCell, OrganCell ou BloodCell respectivement au niveau de la position gérée par le CellsLayer (valeur différente de nullptr), et false dans le cas contraire.
- Des «setters» void setECM(), void setOrganCell() et setBlood(TypeBloodCell) permettant de donner une valeur aux attributs de type ECMCell*, OrganCell* et BloodCell*. La valeur sera allouée dynamiquement et ne sera attribuée que s'il n'existait pas de cellule au préalable (cellule valant nullptr).
- De la méthode updateSubstance qui servira à mettre des substances sur le niveau "ECM". Cette méthode prendra comme paramètre un objet de type Substance et l'ajoutera à l'objet Substance liée au niveau ECM du CellsLayer. Souvenez vous que la classe Substance dispose d'un opérateur +=. Vous ajouterez aux classes existantes toute méthode utile vous permettant de réaliser ce traitement.
- Des «getters» getECMQuantity(SubstanceId id), getOrganCellQuantity(SubstanceId) et getBloodQuantity(SubstanceId) retournant la quantité de la substance identifiée par id respectivement véhculée par les niveaux «ECM», «organe» et «réseau sanguin» du CellsLayer. Par exemple, l'appel getECMQuantity(GLUCOSE) retournera la quantité de glucose dans la substance associée au «niveau ECM» du CellsLayer.
- De la méthode organCellTakeFromECM(SubstanceId id, double fraction) permettant au niveau «ECM» du CellsLayer de céder au niveau «organe» une fraction de la substance identifié par id. Par exemple, l'appel organCellTakeFromECM(GLUCOSE, 0.5), permettra au niveau «ECM» de céder 50% de son glucose au niveau «organe». Souvenez-vous de Substance::uptakeOnGradient.
- De la méthode bool isOut(const CellCoord& coord) retournant true si la coordonnées coord est à l'extérieur de l'organe auquel appartient le CellsLayer et false sinon.
Pour le codage des méthodes suggérées ci-dessus, si vous pensez devoir définir une méthode Cell::getSubstance(), ce qui n'est pas indispensable, elle devrait alors être en accès protégé (seules les sous-classes de Cell devraient pouvoir accéder à la substance qu'elles véhiculent).
[Question Q4.6] Que doit faire le destructeur d'un CellsLayer pour éviter les fuites de mémoire ?
[Question Q4.7] Quelle(s) méthode(s) ajoutez vous et à quelle(s) classe(s) pour permettre le codage de CellsLayer::updateSubstance ?
[Question Q4.8] Quelle(s) méthode(s) ajoutez vous et à quelle(s) classe(s) pour permettre le codage des «getters» sur les quantités de substances véhiculées par chaque niveau d'un CellsLayer ?
[Question Q4.9] Quelle(s) méthode(s) ajoutez vous et à quelle(s) classe(s) pour permettre le codage de CellsLayers::organCellTakeFromEcm ?
Répondez à ces questions dans votre fichier REPONSES en justifiant vos choix et codez les traitements suggérés.
Lien entre Organ et CellsLayer
Dotez donc maintenant la classe Organ d'un attribut de type tableau de CellsLayer (à vous de voir si vous voulez recourir à des pointeurs ou pas).
Ce tableau sera carré de taille nbCells x nbCells. Nous le désignerons sous le nom cellsLayers_ dans le reste de l'énoncé (mais vous êtes libre de le nommer autrement).
La méthode Organ::reloadConfig qui est en charge d'initialiser les éléments paramétrables relatifs à l'organe peut initialiser le tableau cellsLayers_ dès qu'elle a connaissance du nombre de lignes dictées par le fichier de configuration.
Vous ferez donc en sorte que cette méthode initialise le tableau cellsLayers_ en le remplissant d'instances CellsLayer n'ayant que le niveau «ECM». On considèrera donc que le niveau ECM est toujours présent a minima. Attention à faire en sorte qu'il s'agisse bien d'instances distinctes!!
Test 21 : Strates
Un test non graphique, Tests/UnitTests/CellsLayerTest.cpp, est mis à votre disposition pour tester l'essentiel des développements de cette étape. Examinez le contenu de ce fichier pour comprendre quels tests sont réalisés.
Lancez le test au moyen de la cible cellsLayerTest après avoir pris soin de la décommenter dans le fichier CMakeLists.txt.
Le résultat du test devrait être:
===============================================================================
All tests passed (87 assertions in 4 test cases)
Ici encore, un peu de recul et un petit schéma de synthèse sur «papier» pour avoir une vue d'ensemble seront certainement bénéfiques.
Retour à l'énoncé du projet (partie 4)
Module suivant (partie 4.3)