Buts: Afficher graphiquement les courbes d'évolution de certains paramètres.
Certaines données importantes de la simulation, comme l'évolution du nombre d'animaux au cours du temps sont difficiles à visualiser par simple observation du monde simulé. Il est plus aisé pour cela 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 :
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 exemple de graphe qui superpose 3 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.
Il vous est demandé à cette étape de créer une 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 :
Un objet de la classe Stats est essentiellement caractérisé par un ensemble de graphes (ensemble de unique_ptr<Graph>) et un ensemble de libellés associés à ces graphes (des string). 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" et l'identifiant 1 sera associé au graphe de libellé "Waves". Ce dernier permettra de visualiser des statistiques de la vue «modèle neuronal».
Un objet de type Stats sera aussi caractérisé par l'identifiant actif (celui correspondant au graphe visible): la méthode de dessin de Stats ne doit dessiner que le graphe ayant pour identifiant l'identifiant actif.
Vous doterez la classe Stats :
[Question Q4.1] : quelle(s) structure(s) de données choisissez-vous pour l'ensemble des graphes et l'ensemble des libellés de la classe Stats. Répondez à cette question dans votre fichier REPONSES et réalisez les ajouts correspondants dans votre code.
Vous doterez par ailleurs 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; }
Pour tester cette partie, utilisez la cible application associée au programme de test FinalApplication.
Vous devriez alors simplement voir s'afficher l'intitulé des graphes, sans courbes à proprement parler:
Pour voir des courbes s'afficher, 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 getAppConfig().stats_refresh_rate secondes.
L'algorithme pour la mise à jour des données est le suivant:
Pour chacun des graphes:
Partons d'un exemple. Supposons que nous soyons en train de mettre à jour le Graph "general" décrit plus haut. Il est constitué de 3 séries de points: une pour le nombre de scorpions, une autre pour le nombre de lézards etc. Ces séries peuvent être schématisées comme ci-dessous:
{"Scorpions", {p11, p12, ... p1j}} {"Lizards", {p21, p22, ... p2j}} {"Cactuses", {p31, p32, ... p3j}}où pXj est le nombre d'entités de la série tel que calculé lors de la jième mise à jour (nombre de scorpions pour X valant 1, de lézards pour X valant 2 etc.)
Le pas 1 de l'algorithme ci-dessus, doit donc aller calculer l'ensemble
{new_data = { "Scorpions", p1j+1}, {"Lizards", p2j+1}, {"Cactuses", p4j+1}}où chacun des pXj+1 est le nombre d'éléments de chaque type (scorpions, lézards etc.) tel qu'on peut le calculer à l'instant courant à partir du Environment.
Le pas 2 de l'algorithme «injecte» ces données dans le graphe pour mettre à jour les séries:
{"Scorpions", {p11, p12, ... p1j+1}} {"Lizards", {p21, p22, ... p2j+1}} {"Cactuses", {p31, p32, ... p3j+1}}
Le pas 2 de l'algorithme peut être implémenté très simplement au moyen de l'appel updateData(deltaEpoch, new_data) sur le graphe à mettre à jour. deltaEpoch 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
Par exemple :
[Question Q5.2] 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? Répondez à cette question dans votre fichier REPONSES et réalisez les implémentations nécessaires.
Lancez votre simulation comme précédemment. Créez tout à coup plusieurs cactus, scorpions et lézards pour constater que les courbes associées "réagissent" en conséquence (pics de croissance). Au final, vous devriez pouvoir observer les courbes d'évolution conjointes, comme dans notre exemple du départ.
Le baculement vers la vue "modèle neuronal» devrait automatiquement permettre le basculement vers les statistiques de décomptes des ondes que vous devriez voir s'afficher de façon appropriées:
"window":{ "antialiasing level":4, "title":"POOSV Simulation", "simulation" : { "size" : 600 }, "stats":{ "height":200 <-ICI }, "control" : { "width" : 200 }
Notez que l'on peut adapter l'échelle d'affichage des graphes au moment où ils sont ajoutés (voir la méthode FinalApplication::resetStats). Par exemple, si vous simulez régulièrement des grandes populations:
addGraph(s::GENERAL, { s::SCORPIONS, s::LIZARDS, s::CACTUSES,}, 0, 500);sera plus adapté que :
addGraph(s::GENERAL, { s::SCORPIONS, s::LIZARDS, s::CACTUSES,}, 0, 200);
Vous pouvez compléter votre programme de sorte à ce que :
[Question Q5.3] Quelles modifications apportez-vous et dans quelles classes pour ajouter les traitements souhaités ? Répondez à cette question dans votre fichier REPONSES et réalisez les implémentations nécessaires.
Lancez votre simulation comme précédemment. Basculez sur la simulation «modèle neuronal» (au moyen de la touche Tab). Créez des NeuronalScorpion et des WaveLizard. Basculez à nouveau sur la simulation à l'échelle des populations et vérifiez que le décompte des scorpions et lézards y reste nul.