Projet : étape 4.2
Scorpion avec système neuronal

Buts: Simuler les mécanismes internes de prédation chez le scorpion

L'objectif de cette étape est de programmer la classe NeuronalScorpion capable de simuler les mécanismes de prédation du scorpion.

Nous considérerons qu'un NeuronalScorpion est équipé de 8 senseurs situés à l'extrémité de ses pattes. Pour simplifier, ces senseurs seront à équidistance du centre du scorpion, sur une rayon getAppConfig().scorpion_sensor_radius depuis le Vec2d servant de position au scorpion :

Modele
← modélisation simplifiée des senseurs (cercles verts)
Le CircularCollider a été dessiné en noir pour une meilleur visibilité.

Commencez par créer une classe Sensor vide.

Codez ensuite une amorce de la classe NeuronalScorpion dérivant de Scorpion. Un NeuronalScorpion et caractérisé par:

Nous verrons un peu plus loin que le NeuronalScorpion va être dans différents états (de façon analogue au Scorpion standard). Lorsque l'un de ses senseurs est activé, le scorpion passe dans un état indiquant qu'il perçoit les signaux d'une onde et va rester dans cet état pendant un temps limité. Lorsque ce temps est écoulé il va désactiver l'ensemble de ses senseurs.

[Question Q4.5] Comment proposez-vous de représenter, l'ensemble des senseurs et leurs angles dans NeuronalScorpion ? Répondez à cette question dans votre fichier fichier REPONSES en justifiant vos choix.

Le calcul fait par NeuronalScorpion pour estimer la direction de sa cible va dépendre de la position de ses senseurs. Il est donc judicieux de doter cette classe d'une méthode getPositionOfSensor retournant la position d'un senseur donné dans le repère global.

[Question Q4.6] Quel prototype proposez-vous pour la méthode getPositionOfSensor? Répondez à cette question dans votre fichier fichier REPONSES en justifiant vos choix.

Vous pouvez coder les classes NeuronalScorpion et Sensor dans le répertoire Animal/NeuronalScorpion.

Il s'agit maintenant de modéliser plus précisément les senseurs.

Les senseurs

Chaque senseur est caractérisé essentiellement par :

Activation et inhibition des senseurs

Un senseur est activé lorsque les ondes qui le touchent (ou plus précisément qui touchent sa position) cumulent une intensité dépassant un certain seuil (getAppConfig().sensor_intensity_threshold).

L'intensité des ondes de l'environnement en un point donné est calculée comme la somme des intensités de chaque onde en ce point.

L'intensité d'une onde en un point location se calcule comme suit : si location est sur le rayon de l'onde (à getAppConfig().wave_on_wave_marging près), et si location appartient à un des arcs de cette dernière, alors l'intensité se calculera tel que décrit ici, sinon ce sera la valeur zéro.

Indication : location appartient à un arc donné, si l'angle (location - origin).angle(), où origin est l'origine de l'onde, est contenu dans l'arc (supérieur ou égal au début de l'arc et inférieur ou égal à sa fin). Pensez à modulariser vos traitements compte tenu de ce que vous avez déjà eu à coder précédemment.

[Question Q4.7] Quelle(s) méthode(s) ajoutez-vous et à quelle(s) classe(s) pour qu'un senseur puisse connaître l'intensité cumulée des ondes qui le touchent; sans pour autant coder de getters trop intrusifs donnant accès à l'ensemble des ondes de l'environnement ? Répondez à cette question dans votre fichier fichier REPONSES et adaptez votre code en conséquence.

Dès qu'il est activé, un senseur va augmenter son score à chaque pas de simulation. Il va également commencer à inhiber un certain nombre de senseurs situés en face de lui.

Modele
← le senseur 5 inhibe les senseurs 0, 1 et 2
Supposons que les senseurs soient numérotés de 0 à 7, chaque senseur i va inhiber les 3 senseurs (index + i) % 8index varie dans l'ensemble {3,4,5}

Soit si un senseur connecté au senseur this. Sur un pas de temps dt, this va agir sur si en augmentant sont degré d'inhibition proportionellement à son propre score (plus le score de this est élevé et plus il inhibe si). Concrètement, le degré d'inhbition de si va être augmenté du score de this multiplié par getAppConfig().sensor_inhibition_factor.

Pour résumer, à chaque pas de simulation et tant qu'il est actif, un senseur augmente le degré d'inhibition des senseurs qui lui sont connectés et met à jour son propre score en l'augmentant de 2.0 * (1.0 - inhibitor).

Lorsqu'il est inactif, le senseur va être à l'écoute d'ondes potentielles. L'algorithme est simplement le suivant: si l'intensité d'une onde perçue au niveau de la position du senseur est suffisamment forte (supérieur à getAppConfig().sensor_intensity_threshold), le senseur devient actif.

La position du senseur dépend de celle du scorpion (il faut la recalculer en fonction de la position de ce dernier). La méthode getPositionOfSensor du scorpion permet justement de faire ce calcul.

[Question Q4.8] Le senseur, pour connaître sa position à donc besoin de connaître le scorpion auquel il appartient. Qu'est-ce que cela implique au niveau des dépendances entre les classes NeuronalScorpion et Sensor ? comment le gérez-vous? Répondez à ces questions dans votre fichier fichier REPONSES.

[Question Q4.9] Cela a t-il une incidence sur la façon de construire un senseur? Répondez à ces questions dans votre fichier fichier REPONSES en justifiant votre réponse.

Au vu de ce qui précède, proposez le codage complet de la classe Sensor. Cette dernière doit dériver de Updatable et mettre à jour son score et son degré d'inhibition selon le descriptif précédente. Vous ne vous préoccuperez pas de dessiner les senseurs pour le moment.

Comme évoqué précédemment, le NeuronalScorpion n'est sensible à l'activation des senseurs que pendant un temps limité. Une fois ce temps écoulé, il va désactiver l'ensemble de ses senseurs.

La classe Sensor devra donc fournir une méthode capable de le ré-initialiser (son score et son degré d'inhibition passent à zéro et il devient inactif).

Simulation du NeuronalScorpion

Maintenant que les senseurs sont codés, nous pouvons compléter les aspects comportementaux du NeuronalScorpion. Le fonctionnement de ce type de scorpion (méthode update et updateState ) ne tient compte que de la prédation.

Vous trouverez ci-dessous des indications sur :

Estimation de la direction vers une cible

La direction vers la cible estimée par le scorpion est simplement la résultante :

Modele

si est le score du senseur i et v⃗i son vecteur position.

Score d'une estimation

A une estimation d⃗ de la direction de la cible sera associé un score donné simplement par la norme de d⃗.

Etats du scorpion

Le comportement d'un NeuronalScorpion est conditionné par des états différents de ceux du Scorpion (pour ne tenir compte que de la prédation notamment):

Un temps est associé à différents états : il ne peut pas rester dans l'état IDLE plus de 5 secondes (configurable avec getAppConfig().neuronal_scorpion_idle_time), et dans l'état MOVING plus de 3 secondes (configurable avec getAppConfig().neuronal_scorpion_moving_time).

[Question Q4.10] Comment et où proposez-vous de modéliser les états possibles? Répondez à cette question dans votre fichier fichier REPONSES en justifiant vos choix.

[Question Q4.11] Comment proposez-vous de modéliser les horloges associées aux états? Répondez à cette question dans votre fichier fichier REPONSES en justifiant vos choix.

Méthode update

L'algorithme mis en oeuvre est le suivant :

  1. mise à jour des senseurs;
  2. si l'un de ses senseurs est actif, gérer le temps durant lequel le scorpion en tient compte (est réceptif);
  3. si ce temps dépasse le temps maximal getAppConfig().sensor_activation_duration , calculer d⃗ (l'estimation de la direction vers la cible) et réinitialiser les senseurs;
  4. mise à jour de son état (méthode updateState spécialisée par rapport aux Scorpion): voir plus bas;
  5. calcul de la force f qui gère le mouvement  :
    • dans l'état WANDERING : même calcul que pour le scorpion standard;
    • dans l'état TARGET_IN_SIGHT : même calcul que pour le scorpion standard dans l'état FOOD_IN_SIGHT;
    • dans l'état MOVING :
      • gérer l'horloge liée cet état;
      • si l'estimation de la direction vers la cible implique de lui faire faire une rotation de plus de scorpion_rotation_angle_precision, lui faire faire cette rotation et calculer f comme une force de ralentissement vers la cible (à l'image de ce qui est suggéré dans ici); si vous n'avez pas programmé la partie ralentissement, vous pouvez faire le calcul comme vous l'avez fait dans les autres cas analogues, par exemple FEEDING);
      • sinon (l'angle est insuffisant) faire avancer le scorpion tout droit d'un pas donné p (10 par exemple, mais il est mieux de lier la valeur de ce pas aux dimensions du monde). La force f est alors une force d'attraction vers une cible virtuelle située à la position {p,0} du repère local du scorpion;
    • dans l'état IDLE : aucune force de s'exerce (l'horloge liée à cet état doit cependant être gérée);
  6. mettre à jour le déplacement (position, vitesse et direction) compte tenu de f.

Méthode updateState

  1. trouver les entités de l'environnement vues par l'animal
  2. parmi cet ensemble d'entités choisir l'entité mangeable la plus proche;
  3. s'il y en a une, la mémoriser et basculer vers l'état TARGET_IN_SIGHT;
  4. sinon :
    • si l'on est déjà dans l'état IDLE : évaluer le score lié à la direction d'une cible. S'il est suffisant (supérieur à getAppConfig().scorpion_minimal_score_for_action) passer à l'état MOVING. Sinon, si l'on est déjà dans l'état IDLE depuis plus de 5 secondes on passe à l'état WANDERING;
    • dans l'état MOVING: si l'on est dans cet état depuis plus de 3 secondes on repasse à l'état IDLE. Le scorpion devient ainsi réceptif à de nouvelles ondes et oublie celles déjà perçues auparavant (pensez à mettre à jour la direction de la cible en conséquence);
    • dans l'état WANDERING: si l'un des senseurs est devenu actif on passe à l'état IDLE;
    • dans l'état TARGET_IN_SIGHT : on passe à l'état IDLE (on est dans le sinon du pas 4 de l'algorithme).

[Question Q4.12] Quelles actions liées à la gestion des différentes horloges impliquées devez-vous entreprendre et dans lesquels des états du scorpion? Répondez à cette question dans votre fichier fichier REPONSES en justifiant votre réponse.

Attention à la bonne modularisation de vos traitements

Constructeurs

Pour être compatible avec les fichiers de tests fourni, le NeuronalScorpion aura des constructeurs aux signatures analogues à ceux codés pour Scorpion. Il aura WANDERING comme état par défaut. La direction estimée de la cible est bien sûr nulle au départ.

Vous veillerez à faire le reste des initialisations qui s'imposent.

Test 26 : perception des ondes

Le test fourni NeuronalTest.cpp que vous pouvez lancer au moyen de la cible neuronalTest permet de tester la perception des ondes par le scorpion.

Il est programmé de sorte à ce qu'un clic gauche avec la souris crée un onde. Vous devriez voir le scorpion d'orienter en fonction de la perception de cette onde tel que montré par la vidéo suivante :

[Video : Modèle neuronal du scorpion]

Testez également le fait que les obstacles bloquent la perception des ondes par le scorpion.

Si le scorpion ne se comporte pas tel que souhaité, quelques compléments graphiques peuvent vous aider à «debugger» votre code.

Test 27 : Affichages en mode "Debugging"

Pour vérifier vos développements, complétez l'affichage en mode «debugging» de sorte à  :

Le rayon graphique d'un senseur sera calculé comme une portion de celui du scorpion (par exemple 1/4).

Vous devriez pouvoir observer un fonctionnement analogue à celui montré par la petite vidéo ci-dessous.

N'oubliez pas que si vous voulez observez les choses de plus près, vous pouvez aussi changer la taille du monde simulé dans le fichier de configuration (en la faisant passer à 700 par exemple).
[Video : Affichage du modéle neuronal en mode «debugging»]

L'affichage en mode «debugging» vous permettra notamment de vérifier que les obstacles font bien barrage à l'activation des neurones.

[Video : Obstacles empêchant l'activation des neurones]

Test 28 : ondes et neurones

Le test fourni FinalApplication.cpp permet de tester la cohabitation d'un NeuronalScorpion et d'une WaveLizard dans un environnement.

Les modalités de lancement du test seront les suivantes :

Le scorpion devrait alors pouvoir repérer la lézard au moyen des ondes qu'il émet, comme dans la vidéo ci-dessous :

[Video : Scorpion repérant un lézard hors de son champs de vision
grâce à la perception des ondes qu'elle émet en marchant.]

La simulation du modèle neuronal est maintenant terminée. Bravo d'avoir atteint le résultat voulu! Lors de l'ultime étape du projet vous allez paufiner un peu l'outil de simulation et aurez le loisir de coder quelques extensions librement choisies.


Retour à l'énoncé du projet (partie 4) Module précédent (part 4.1)