Buts: Afficher graphiquement les courbes d'évolution de certains paramètres.
Les mécanismes complexes comme les mutations sont difficiles à visualiser au cours du temps par simple observation du contenu d'une boite de culture. De même pour comparer l'évolution de plusieurs colonies en compétition, il est plus aisé d'avoir recours à des graphes.
Cette étape du projet a pour but de permettre l'affichage des courbes d'évolution relatives à un certain nombre de paramètres, selon le modèle suivant (encadré rouge):
Comprendre et compléter du code existant est une compétence importante en programmation. Vous allez, lors de cette étape, exercer cette facette de façon un peu plus importante que précédemment.
Une classe Graph est fournie dans l'archive relative à cette étape (répertoire src/Stats).
Cette classe permet de dessiner un ensemble de courbes superposées. Chaque courbe est une série de points reliés entre eux et est associée à un titre (une chaîne de caractères).
Vous avez par exemple ci-dessus, un graphe qui superpose 4 séries de points:
Pour résumer, un graphe a un libellé et un ensemble de séries de points. Chaque série de points a un titre.
L'idée est de pouvoir afficher plusieurs de ces Graph et de basculer de l'un à l'autre au moyen de nouveaux contrôles (similaire à ceux que vous avez utilisé pour contrôler la température et l'exposant du gradient).
Par exemple, il devra être possible, lorsque le contrôle courant (celui en rouge) est celui des statistiques, de basculer au moyen de PgUp/X vers l'affichage du graphe relatif à la quantité totale de nutriments disponible dans l'assiette courante (associé au libellé nutrient quantity), ce graphe n'aurait par exemple qu'une série de points à afficher (pas de courbes superposées):
Il vous est demandé à cette étape de compléter la classe Stats (voir ci-dessous) permettant de gérer tous les graphes que l'on souhaite afficher dans le cadre de cette simulation et d'ajouter la fonctionnalité permettant de basculer d'un graphe à l'autre.
La classe Stats permettra donc d'afficher plusieurs graphes, chaque graphe étant associé à un libellé le caractérisant.
Ce sont les programmes graphiques, tels que la classe FinalApplication qui, par des appels à la méthode addGraph héritée de Application, vont définir :
C'est la classe FinalApplication qui, par des appels à la méthode addGraph héritée de Application, va définir le libellé associé à un graphe, l'ensemble des séries de points qui sont associés (désignés par leur titre) ainsi que les bornes inférieures et minimales des plages de valeurs associées à ces séries de points.
Un objet de la classe Stats est essentiellement caractérisé par un ensemble de graphes (ensemble de std::unique_ptr<Graph>) et un ensemble de libellés associés à ces graphes (des string). Il s'agit d'un objet dessinable et évoluant au cours du temps.
Chaque graphe (et donc chaque libellé aussi) sera associé à un identifiant entier. Par exemple l'identifiant zéro sera associé au graphe ayant pour libellé "general", l'identifiant 1, sera associé au graphe ayant pour libellé "nutrient quantity" etc.
Un objet de type Stats sera aussi caractérisé par l'identifiant actif (celui correspondant à l'option sélectionnée dans les contrôles): la méthode de dessin de Stats ne dessinera que le graphe ayant pour identifiant l'identifiant actif.
Vous compléterez dans la classe Stats :
[Question Q5.3] : quelle(s) structure(s) de données choisissez-vous pour l'ensemble des graphes et l'ensemble des titres de la classe Stats. Répondez à cette question dans votre fichier REPONSES et réalisez les ajouts correspondants dans votre code.
Vous compléterez par ailleurs dans la classe Stats :
void addGraph( int id, const std::string &title, const std::vector<std::string> &series, double min, double max, const Vec2d &size );
#include <iostream>
#include <memory>
int main() {
std::unique_ptr<int> p(new int(1));
std::cout << *p << std::endl; // 1
p.reset(new int(2));
std::cout << *p << std::endl; // 2
return 0;
}
getApp().resetStats(); // appel à la méthode Stats::reset
Pour tester cette partie vous pouvez continuer à utiliser l'application finale fournie src/Tests/GraphicalTests/FinalApplication.cpp qui peut être lancée au moyen de la cible application. Vous prendrez soin au préalable de décommenter l'ajout des graphes (lignes 25 et 26 de FinalApplication) et de modifier le paramètre de la ligne 23 de false à true (pour activer l'affichage des statistiques).
Notez que la taille de la zone graphique permettant d'afficher les statistiques est configurable dans le fichier app.json :
"window":{
"antialiasing level":4,
"title":"INFOSV Simulation",
"simulation":{
"width":700,
"height":700
},
"stats":{
"width":200 // ICI par exemple mettre 300 à la place de 200
},
"control":{
"width":300
}
Vous devriez alors simplement voir s'afficher les titres associés aux courbes à venir (sans affichage de courbe à proprement parler pour le moment). Par exemple pour le graphe de libellé "general" :
Pour voir des courbes s'afficher effectivement, il faut alimenter en données les séries de points associées à chaque graphe. C'est ce qu'il vous est demandé de faire dans ce qui suit.
Les données ne sont pas mises à jour à chaque update mais tous les sf::seconds(getAppConfig()["stats"]["refresh rate"].toDouble()); secondes.
L'algorithme pour la mise à jour des données est ensuite le suivant.
Pour chacun des graphes:
Partons d'un exemple. Supposons que nous soyons en train de mettre à jour le Graph "general" (tel que décrit plus haut). Il est constitué de 5 séries de points: une pour le nombre de bactérie simple, une autre pour le nombre de bactéries à tentacule etc. Ces séries peuvent être schématisées comme ci-dessous:
{ "monotrichous", {p11, p12, ... p1j}}
{"pilus mediated", {p21, p22, ... p2j}}
{"group motility", {p31, p32, ... p3j}}
{"nutriment sources", {p41, p42, ... p4j}}
{"temperature", {p51, p52, ... p5j}}
où pXj est le nombre d'entités de la série tel que calculé lors de la jième mise à jour (nombre de bactéries simples pour X valant 1, de bactéries à tentacule pour X valant 2 etc.)
Le pas 1 de l'algorithme ci-dessus, doit donc aller calculer l'ensemble
{new_data = { "simple bacteria", p1j+1}, {"twitching bacteria", p2j+1}, ... {"nutriment sources", p4j+1}...}
où chacun des pXj+1 est le nombre d'éléments de chaque type (bactéries simples, nutriments etc.) tel qu'on peut le calculer à l'instant courant à partir du Lab.
Le pas 2 de l'algorithme «injecte» ces données dans le graphe pour mettre à jour les séries:
{ "monotrichous", {p11, p12, ... p1j+1}}
{"pilus mediated", {p21, p22, ... p2j+1}}
{"group motility", {p31, p32, ... p3j+1}}
{"nutriment sources", {p41, p42, ... p4j+1}}
{"temperature", {p51, p52, ... p5j+1}}
Le pas 2 de l'algorithme peut être implémenté très simplement au moyen de l'appel Graph::updateData(deltaT, new_data) sur le graphe à mettre à jour, où deltaT est le temps écoulé depuis la dernière mise à jour des donnée et new_data est l'ensemble des nouvelles données pour chaque série (telle que décrit dans l'exemple précédent).
Il vous est donc demandé pour coder l'étape 1 de l'algorithme de mettre en oeuvre une méthode std::unordered_map<std::string, double> Lab::fetchData(const std::string &) prenant en paramètre le titre du graphe (une string) et retournant l'ensemble new_data lui correspondant (demandez-vous également si cette méthode doit être marquée comme étant const ou non).
Par exemple :
[Question Q5.4] quelle méthodes prévoyez-vous d'ajouter/modifier et dans quelles classes pour réaliser les décomptes souhaités et construire les ensembles new_data? en d'autres termes, comment pouvez-vous compter le nombre d'instances d'une certaine classe? Répondez à cette question dans votre fichier REPONSES et réalisez les implémentations nécessaires.
Lancez votre simulation comme précédemment et créer différents types ce bactéries. Vous devriez voir les courbes d'évolution des populations et de la quantité de nutriments s'afficher. Créez tout à coup beaucoup de bactéries d'un certain type et vous devriez pouvoir constater que la courbe d'évolution "réagit" en conséquence. Vérifiez que vous parvenez à basculer d'une statistique à l'autre au moyen des touches de contrôles prévues:
Vérifiez que les courbes de statistiques se re-initialisent proprement en passant d'une boite de culture à l'autre.
Complétez enfin l'affichage des courbes en permettant de faire afficher les courbes d'évolution de paramètres mutables numériques. Par exemple, pour les bactéries à grappins, les longueurs et vitesses moyennes des grappins.
Lancez votre simulation comme précédemment. Vous devriez avoir la possibilité de visualiser l'évolution des paramètres mutables numériques de vos bactéries, dont voici l'exemple pour les bactéries à tentacule :
Vous disposez maintenant d'un outil de simulation complet. Essayez de le paramétrer de sorte à trouver les conditions permettant par exemple d'obtenir des fluctuations du nombre des diverses populations mais sans l'extinction de l'une ou l'autre.