Projet : étape 3.2
Paramètres mutables
Buts:
Mettre en oeuvre de classes utilitaires permettant la modélisation de paramètres mutables.
Un certain nombre de caractéristiques des bactéries pourront muter au cours du temps. Dans certain cas, ceci influencera leur aptitude à la survie.
Par exemple, pour les bactéries à flagelle unique, nous avons vu dans le descriptif du projet qu'elles se déplaceront à vitesse constante. Nous allons cependant introduire le fait que cette vitesse puisse muter au cours du temps (rendant les bactéries plus lentes ou plus rapides au bout d'un certain nombre de cycles de simulation).
Les bactéries à flagelle unique auront d'autres paramètres mutables comme :
- leur probabilité à changer aléatoirement de direction (basculement) pour augmenter leur chance de trouver des nutriments,
- ou leur couleur.
Des modifications dans la vitesse de déplacement ou dans la probabilité de basculement peuvent clairement avoir une influence sur la capacité de survie. Vous pourrez d'ailleurs jouer avec votre outil de simulation pour essayer de dégager "manuellement" les valeurs offrant une capacité optimale à la survie.
L'idée est de permettre de voir si par le biais de mutations, la sélection naturelle des caractéristiques optimales peut se faire.
La présence de la couleur comme caractéristique mutable permettra de simuler le fait que certains attributs peuvent muter tout en étant absolument neutres par rapport à l'aptitude à la survie (apparition de caractéristiques dans une populations sans que celles-ci ne soient liées à une mécanisme de sélection positive).
Ceci permettra potentiellement à l'outil de simulation de montrer que non seulement des caractéristiques favorisant la survie peuvent se fixer dans une population mais également des caractéristiques neutres. Ces dernières pourront être favorisées par des conditions externes comme l'introduction d'un obstacle dans la boite de culture (modules d'extension du projet).
Intéressons-nous maintenant à la modélisation des caractéristiques mutables.
Ces dernières :
- peuvent être de nature très différentes : un double pour la norme de la vitesse, une probabilité pour les caractéristiques liées au basculement ou une valeur de type "couleur" (nous en reparlerons un peu plus bas) pour la couleur. Certains paramètres mutables seront des double devant rester positif (longueur de tentacules chez certaines bactéries introduites plus tard).
- ont toutes en commun de pouvoir changer de valeur par une méthode de mutation (mutate).
Il vous est demandé dans ce qui suit de programmer deux classes permettant de représenter des valeurs mutables.
Programmez ces classes utilitaires dans le répertoire src/Utility
La cible permettant de compiler et lancer cette partie est mutableTest. Il faut la décommenter le moment venu dans le CmakeLists.txt. Notez que Ctrl-F permet de faire une recherche dans l'éditeur de QtCreator (pour chercher où se trouve mutableTest par exemple).
Classe MutableNumber
Un MutableNumber n'est autre qu'un double qui peut changer aléatoirement de valeur. Les valeurs aléatoires seront tirées selon une distribution dite Gaussienne (normale), de moyenne nulle et d'écart type donné.
Un MutableNumber sera doté des attributs suivants :
- sa valeur double.
- sa probabilité de mutation un double aussi.
- deux booléens indiquant respectivement si le nombre a une borne inférieure et une borne supérieure.
- ses bornes inférieure et supérieure (dont les valeurs ne sont significatives que si les booléens précédents valent true).
- l'écart type permettant la modification aléatoire de la valeur du nombre (double).
Vous doterez cette classe des méthodes suivantes :
- un getter get() retournant la valeur du nombre.
- un set(double value) permettant d'attribuer une valeur au nombre.
- une méthode mutate() permettant de modifier la valeur du nombre de façon aléatoire.
Voici une façon possible de la coder :
- invoquez la fonction bernoulli (fournie dans Random/Random.[hpp/cpp]) en lui passant en paramètre la probabilité de mutation du nombre. La fonction bernoulli(p) retourne en fait la valeur 1 avec la probabilité et 0 avec la probabilité 1-p.
- si l'appel de la fonction bernoulli retourne 1, tirez un nombre aléatoire selon une distribution normale en utilisant l'appel normal(0, sigma*sigma) où sigma est la valeur de l'attribut écart type (pour rappel la fonction normal est aussi définie dans Random/Random.[hpp/cpp]).
- ajoutez enfin à la valeur du nombre, la valeur du nombre aléatoire ainsi tiré.
La classe MutableNumber sera également dotée :
- d'un constructeur permettant d'initialiser l'ensemble de ses attributs avec des valeurs passées en paramètres. Les attributs booléens auront false comme valeur par défaut. Les bornes inférieure et supérieure auront zéro comme valeur par défaut.
- d'un constructeur permettant d'initialiser l'ensemble de ses attributs à partir d'une entrée d'un fichier de configuration (de type j::Value const&). Supposons que le paramètre du constructeur soit j::Value const& config, alors
- la valeur à attribuer au nombre sera config["initial"].toDouble().
- la valeur à attribuer à la probabilité de mutation sera config["rate"].toDouble().
- la valeur à attribuer à l'écart type sera config["sigma"].toDouble().
- etc. (procédez de façon analogue en utilisant les "étiquettes" "clamp min" , "clamp max", "min" et "max" du fichier de configuration app.json).
Les valeurs assignées au nombre mutable doivent respecter le fait que le ce dernier peut avoir des bornes inférieure et supérieure. Il faudra veiller dans ce cas à faire plafonner, au moyen de ces bornes, la valeur affectée. Ainsi, tenter d'affecter une valeur plus basse que la borne inférieure se traduira par affecter la borne inférieure, et l'on procédera de façon analogue pour la borne supérieure.
[Question Q3.7] Quelles méthodes parmi celles suggérées pour un MutableNumber devront procéder au plafonnage de la valeur entre la borne inférieure et la borne supérieure ? Comment éviter les duplications de code si ce traitement est amené à se répéter à plusieurs endroits ? Répondez à ces questions dans votre fichier REPONSES et procédez aux codages suggérés.
Pour simplifier la génération de nombres mutables de différentes natures (probabilités, nombres toujours positifs), codez enfin les méthodes suivantes :
- probability(double initialValue, double mutationProbability, double sigma) créant et retournant un MutableNumber de type probabilité (valeurs plafonnées entre 0 et 1).
- probability(j::Value const& config) faisant pareil que la précédente mais puisant la valeur initiale, la probabilité de mutation et l'écart type dans une entrée config du fichier de configuration.
- positive(double initialValue, double mutationProbability, double sigma, bool hasMax=false, double max=0.) créant et retournant un MutableNumber de type positif (valeurs minimale plafonnée à zéro).
- positive(j::Value const& config, bool hasMax=false, double max=0.) faisant pareil que la précédente mais puisant la valeur initiale, la probabilité de mutation et l'écart type dans une entrée config du fichier de configuration.
[Question Q3.8] Comment faut-il procéder pour que ces méthodes puissent être invoquées sans passer par la création d'une instance de MutableNumber ? Répondez à cette question dans votre fichier REPONSES et procédez aux codages suggérés.
Classe MutableColor
Comme évoqué précédemment, la couleur des bactéries sera un paramètre mutable.
Il vous est demandé maintenant d'implémenter la classe MutableColor permettant de modéliser la notion de couleur mutable.
Une couleur sera représentée au moyen de ses quatres composants, selon le modèle RGBA. Chaque composant sera codifié comme un MutableNumber.
Vous doterez donc la classe MutableColor :
- d'un attribut représentant les composants RGBA de la couleur (un array de 4 MutableNumber).
- d'un constructeur MutableColor(j::Value const& config) :
- affectant à la composante R de la couleur un MutableNumber créé à partir de l'entrée config["r"] du fichier de configuration (pour bien comprendre ce que vous faites, jetez un oeil à comment est codifiée la couleur d'une bactérie simple dans le fichier app.json, voir cela sous l'étiquette "monotrichous").
- affectant à la composante G de la couleur un MutableNumber créé à partir de l'entrée config["g"]du fichier de configuration.
- et faisant pareil pour les deux autres composants restants.
- une méthode mutate() faisant muter chacun des composants de la couleur (rappelez-vous que ce sont des MutableNumber dotés d'une méthode mutate!);
- un getter sf::Color get() retournant la couleur sous une forme reconnue par SFML :
Le code pour la conversion de vos 4
MutableNumber en une
sf::Color est relativement technique (et de dépendant de la vesrion de SFML) est vous est donné tel quel ici :
#if SFML_VERSION_MAJOR >= 3
return { static_cast<std::uint8_t>(mComponents[0].get() * 255),
static_cast<std::uint8_t>(mComponents[1].get() * 255),
static_cast<std::uint8_t>(mComponents[2].get() * 255),
static_cast<std::uint8_t>(mComponents[3].get() * 255) };
#else
return { static_cast<sf::Uint8>(mComponents[0].get() * 255),
static_cast<sf::Uint8>(mComponents[1].get() * 255),
static_cast<sf::Uint8>(mComponents[2].get() * 255),
static_cast<sf::Uint8>(mComponents[3].get() * 255) };
#endif
où
comp représente l'attribut de
MutableColor (le tableau de 4
MutableNumber).
Pour des raisons techniques, vous veillerez à réactiver le constructeur par défaut de MutableNumber (que se passe t'il si on le fait pas?).
Test 7 : Paramètres mutables
Le programme de test pour cette partie est fourni dans le fichier src/Tests/UnitTests/MutablePropertyTest.cpp. Il s'agit d'un test non-graphique, analogue à celui que vous avez utilisé pour la première partie du projet.
Lancez ce test au moyen de la cible mutableTest. Vous aurez pris soin de la décommenter dans le fichier CMakelists.txt et d'avoir intégré ce nouveau matériel au moyen de Build > Run CMake.
Lorsque vos classes auront été correctement codée, vous devriez voir s'afficher :
===============================================================================
All tests passed (6000 assertions in 1 test case)
retour à l'énoncé du projet (partie 3)
Module suivant (partie 3.3)