design intelligence
Table des matières

Tutoriel : les principes de génie logiciel

Cette partie du tutoriel illustre, au regard des principes de génie logiciel, les points particuliers du langage nomo dans le cadre d'un projet.

L'exemple consiste à réaliser dans un premier temps un agent devant trouver une bille, la prendre puis de tenter de la déposer sur une dalle bleue. L'agent explorera le monde de manière aléatoire.

Puis, dans un second temps, l'objet de l'exemple consistera à dupliquer et à gérer quatre agents devant réaliser cette tâche comme l'illustre par exemple la figure ci-dessous.

Définition d'un agent

La définition d'un agent se traduit par un programme agent s'appuyant sur un modèle agent qui se base sur le modèle prédéfini du monde de dalles worldsquare.

Dans cet exemple, la conception de l'agent s’effectuera en deux étapes :

  1. la définition de la logique des états internes,
  2. la définition des actions en fonction de ces états.

Formalisation

La première étape consiste à formaliser le comportement de l'agent sous la forme d'un automate :

L'automate se traduit par la création d'un programme controller contenant sa description comme suit :

<formalism xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="automaton">
  <automaton xmlns="http://www.nomoseed.org/automaton">
    <state name="search_sphere">
      <transition target="search_sphere" event="no_found"/>
      <transition target="search_location" event="found"/>
    </state>
    <state name="search_location">
      <transition target="search_location" event="no_found"/>
      <transition target="terminate" event="found"/>
    </state>
    <state name="terminate"/>
  </automaton>
</formalism>

Le programme controller sera un sous-programme du programme agent. Chaque agent devant contenir son propre système de contrôle, l'automate correspond à une nouvelle instance.

Pour utiliser les types state et event générés par le sous programme, il faut importer le modèle de controller contenu par le programme controller dans le programme agent. Ces différentes opérations s'écrivent comme suit :

<program xmlns="http://www.nomoseed.org/program" name="agent">
  ...
  <subprograms xmlns:xi="http://www.w3.org/2001/XInclude">
    <new instance="controller">
      <xi:include href="controller.prg"/>
    </new>
  </subprograms>
  <body>
    <models xmlns:xi="http://www.w3.org/2001/XInclude">
      <import instance="controller" subprogram="controller">
        <xi:include href="controller.prg#xmlns(model=http://www.nomoseed.org/model)
                          xpointer((//model:model[@name='controller'])[1])"/>
      </import>
      ...
    </models>
  ...
  </body>
</program>

Le schéma ci-dessous illustre les relations de dépendance :

Dont voici la légende :

Programme (new)
Programme hérité (inherit)
Modèle (new)
Modèle importé (import)
Modèle hérité (inherit)
Représentation de l'arborescence
Spécification du numéro d'instance
L'original vers la copie

Définition des actions et des évènements

La seconde étape consiste à définir les actions au cours de ces états et le déclenchement des évènements.

Pour les états search_sphere et search_location, les actions peuvent se décomposer en deux phases :

  1. une phase d'exploration,
  2. une phase d'évaluation.

Il restera enfin la gestion de l'état terminate.

L'exploration

Pour les états search_sphere et search_location, la première phase d'action est identique de manière aléatoire, soit de tourner à gauche, soit de tourner à droite, soit

La première phase d'action repose sur le déclenchement de deux règles successives : une règle de perception de type random suivie d'une règle de commande de type motor.

Cette première phase d'action se déclenche à la transition des états sauf pour l'état terminate. Les règles de perceptions auront une prémisse state permissive sur le contenu mais stricte sur l'étiquette temporelle et une prémisse inhibitrice sur l'état terminate.

Le choix entre le trois commandes repose sur la perception de la variable aléatoire de l'agent dont la valeur sera considérée soit petite, soit moyenne, soit grande. Au modèle s'ajoute alors au type random trois items : small, middle, large.

La macro de discrétisation permet de définir les règles perceptives de type random de la manière suivante :

<macro xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" active="true" name="random" scheme="perceptions" xsi:type="discretization">
  <discretization xmlns="http://www.nomoseed.org/discretization" center="true">
    <rule name="random">
      <premise model="controller" category="conception" type="state">
        <information tolerance="INF"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="controller" category="conception" type="state" inhibitor="true">
        <information value="terminate" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="input" type="random">
        <information interval="small 0.25 middle 0.75 large"/>
      </premise>
      <conclusion model="agent" category="perception" type="random"/>
    </rule>
  </discretization>
</macro>

Au déclenchement d'une règle de perception, random doit impliquer le déclenchement de la règle de commande motrice associée, soit la macro de patron suivante :

<macro xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" active="true" name="explore" scheme="actions" xsi:type="template">
  <csv xsi:type="embedded">
        command, output, random
        advance, 2, middle
        turn_left, 0, small
        turn_right, 1, large
  </csv>
  <template xmlns="http://www.nomoseed.org/template">
    <rule name="motor">
      <premise model="agent" category="perception" type="random">
        <information value="#random" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <conclusion model="agent" category="command" type="motor">
        <information value="#command"/>
        <output value="#output"/>
      </conclusion>
     </rule>
  </template>
</macro>

L'évaluation

Selon l'état search_sphere et l'état search_location, l'évaluation du résultat diffère un peu :

  • Pour l'état search_sphere, l'agent a-t-il été déplacé et si oui l'agent a-il pris une sphère ?
  • Pour l'état search_location, l'agent a-t-il été déplacé et si oui l'agent est-il sur une dalle bleue ?

L'échec du déplacement ou de la prise d'une sphère se traduit par la perception d'une résistance correspondant au type d'entrée apériodique strength soit la règle perceptive idoine :

<rule name="strength">
  <premise model="agent" category="input" type="strength">
    <information tolerance="INF"/>
  </premise>
  <conclusion model="agent" category="perception" type="strength">
    <information value="failed"/>
  </conclusion>
</rule>

L'entrée hue est périodique, à chaque pas, des messages entrés sont reçus par l'agent. Dans le modèle agent, il est prévu que l'agent puisse percevoir une teinte : red, blue et green. Mais la perception de ces messages n'est pertinente uniquement lorsque l'agent se trouve dans l'état search_location et qu'il vient d'avancer, soit la macro de discrétisation suivante :

<macro xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" active="true" name="hue" scheme="perceptions" xsi:type="discretization">
  <discretization xmlns="http://www.nomoseed.org/discretization" center="false">
    <rule name="hue">
      <premise model="controller" category="conception" type="state">
        <information value="search_location" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="-1" tolerance="INF"/>
      </premise>
      <premise model="agent" category="command" type="motor">
        <information value="advance" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="1" tolerance="0"/>
      </premise>
      <premise model="agent" category="perception" type="strength" inhibitor="true">
        <information tolerance="INF"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="input" type="hue">
        <information interval="red 60 green 180 blue 300 red"/>
      </premise>
      <conclusion model="agent" category="perception" type="hue"/>
    </rule>
  </discretization>
</macro>

La tentative de capturer une sphère est également conditionnée à l'état de l'automate controller et à la réussite de l'avance soit :

<rule name="search_sphere">
  <premise model="controller" category="conception" type="state">
    <information value="search_sphere" tolerance="0"/>
    <credibility tolerance="INF"/>
    <timespan value="-1" tolerance="INF"/>
  </premise>
  <premise model="agent" category="command" type="motor">
    <information value="advance" tolerance="0"/>
    <credibility tolerance="INF"/>
    <timespan value="1" tolerance="0"/>
  </premise>
  <premise model="agent" category="perception" type="strength" inhibitor="true">
    <information tolerance="INF"/>
    <credibility tolerance="INF"/>
    <timespan value="0" tolerance="0"/>
  </premise>
  <conclusion model="agent" category="command" type="motor">
    <information value="capture"/>
    <output value="3"/>
  </conclusion>
</rule>

À partir de ces différentes règles, la définition des évènements de l'automate controller peuvent s'exprimer de la manière suivante avec deux macros de formulation :

<macro xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" active="true" name="found" scheme="events" xsi:type="formulation">
  <formulation xmlns="http://www.nomoseed.org/formulation">
    <declarations>
      <premise model="agent" category="perception" type="strength" name="failed">
        <information tolerance="INF"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="command" type="motor" name="capture">
        <information value="capture" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="1" tolerance="0"/>
      </premise>
      <premise model="agent" category="perception" type="hue" name="blue_hue">
        <information value="blue" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <conclusion model="controller" category="conception" type="event" name="found">
        <information value="found"/>
      </conclusion>
    </declarations>
    <formulas>
      blue_hue : found
      failed ! capture : found
    </formulas>
  </formulation>
</macro>
<macro xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" active="true" name="no_found" scheme="events" xsi:type="formulation">
  <formulation xmlns="http://www.nomoseed.org/formulation">
    <declarations>
      <premise model="agent" category="perception" type="strength" name="failed">
        <information tolerance="INF"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="perception" type="hue" name="other_hue">
        <information tolerance="INF"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="command" type="motor" name="turn_left">
        <information value="turn_left" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <premise model="agent" category="command" type="motor" name="turn_right">
        <information value="turn_right" tolerance="0"/>
        <credibility tolerance="INF"/>
        <timespan value="0" tolerance="0"/>
      </premise>
      <conclusion model="controller" category="conception" type="event" name="no_found">
        <information value="no_found"/>
      </conclusion>
    </declarations>
    <formulas>
      failed + other_hue + turn_right +  turn_left : no_found
    </formulas>
  </formulation>
</macro>

Finalisation

La finalisation correspond à la transition vers l'état terminate de l'automate controller. L'action associée est celle de déposer la sphère contenue par l'agent soit :

<rule name="terminate">
  <premise model="controller" category="conception" type="state">
    <information value="terminate" tolerance="0"/>
    <credibility tolerance="INF"/>
    <timespan value="0" tolerance="0"/>
  </premise>
  <conclusion model="agent" category="command" type="motor">
    <information value="depose"/>
    <output value="4"/>
  </conclusion>
</rule>

A noter qu'aucune vérification de la réussite de l'action n'est effectuée. En effet, la présence d'une sphère empêcherait le dépôt mais cela sort du cadre de l'exemple.

Ajout d'un interrupteur

Dès le début, l'état Search_Sphere de l'automate controller l'état est déclenché. Maitriser ce déclenchement revient à introduire un nouvel état avec deux événements représentant les transitions d'un interrupteur.

L'expression de l'automate devient :

<formalism xmlns="http://www.nomoseed.org/sdk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="automaton">
  <automaton xmlns="http://www.nomoseed.org/automaton">
    <state name="wait">
      <transition target="search_sphere" event="switched_to_on"/>
      <transition target="terminate" event="switched_to_off"/>
    </state>
    <state name="search_sphere">
      <transition target="search_sphere" event="no_found"/>
      <transition target="search_location" event="found"/>
      <transition target="terminate" event="switched_to_off"/>
    </state>
    <state name="search_location">
      <transition target="search_location" event="no_found"/>
      <transition target="terminate" event="found"/>
      <transition target="terminate" event="switched_to_off"/>
    </state>
    <state name="terminate"/>
  </automaton>
</formalism>

L'interrupteur est modélisé par un type de conception appelé switch possédant deux items on et off défini dans le modèle switch.

Le déclenchement de l'interrupteur pouvant avoir sa propre logique de fonctionnement, il constituera un autre programme, ici la logique de fonctionnement est minimale :

<program xmlns="http://www.nomoseed.org/program" name="switch_on">
  <body>
    <models xmlns:xi="http://www.w3.org/2001/XInclude">
      <inherite instance="switch">
        <xi:include href="switch.mod"/>
      </inherite>
    </models>
    <scheme>
      <rule name="switch_on" relevance="0.0">
        <conclusion model="switch" category="conception" type="switch">
          <information value="on"/>
        </conclusion>
      </rule>
    </scheme>
  </body>
</program>

Le modèle switch est hérité puisque le message doit être accessible par ailleurs.

Bien que l'item off ne soit pas exploité ici par souci d’exhaustivité, la définition des deux évènements de l'automate controller est rajoutée :

<rule name="switched_to_on">
  <premise model="switch" category="conception" type="switch">
    <information value="on" tolerance="0"/>
    <credibility tolerance="INF"/>
    <timespan value="0" tolerance="0"/>
  </premise>
  <conclusion model="controller" category="conception" type="event">
    <information value="switched_to_on"/>
  </conclusion>
</rule>
<rule name="switched_to_off">
  <premise model="switch" category="conception" type="switch">
    <information value="off" tolerance="0"/>
    <credibility tolerance="INF"/>
    <timespan value="0" tolerance="0"/>
  </premise>
  <conclusion model="controller" category="conception" type="event">
    <information value="switched_to_off"/>
  </conclusion>
</rule>

En anticipant, le fait que l'interrupteur puisse être commun à plusieurs agent, le programme switch_on est hérité ainsi que le modèle switch :

<program xmlns="http://www.nomoseed.org/program" name="agent">
  ...
  <subprograms xmlns:xi="http://www.w3.org/2001/XInclude">
    <inherite instance="switch">
      <xi:include href="switch_on.prg"/>
    </inherite>
    <new instance="controller">
      <xi:include href="controller.prg"/>
    </new>
  </subprograms>
  <body>
    <models xmlns:xi="http://www.w3.org/2001/XInclude">
      <inherite instance="switch">
        <xi:include href="switch.mod"/>
      </inherite>
      <new instance="agent">
        <xi:include href="agent.mod"/>
      </new>
      <import instance="controller" subprogram="controller">
        <xi:include href="controller.prg#xmlns(model=http://www.nomoseed.org/model)
                          xpointer((//model:model[@name='controller'])[1])"/>
      </import>
    </models>
    ...
  <body>
</program>

Soit le schéma de dépendance suivant :

Définition d'un groupe d'agent

Le programme agent défini, il est possible de l'utiliser comme programme principal. Toutefois, dans le cadre de l'exemple, quatre agents doivent parcourir un monde de dalles, il faut alors définir un autre programme regroupant quatre instances du programme agent :

<program xmlns="http://www.nomoseed.org/program" name="agent_group">
  <subprograms xmlns:xi="http://www.w3.org/2001/XInclude">
    <new instance="agent_1">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_2">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_3">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_4">
      <xi:include href="agent.prg"/>
    </new>
  </subprograms>
</program>

Le schéma de dépendance ci-dessous montre bien que chaque agent possède son propre interrupteur dont la valeur est bien distincte :

Pour qu'un seul interrupteur soit commun à tous les agents, il suffit d'en instancier un avec le modèle associé afin que la communication entre les agents et leur interrupteur puisse se faire.

<program xmlns="http://www.nomoseed.org/program" name="agent_group">
  <subprograms xmlns:xi="http://www.w3.org/2001/XInclude">
    <new instance="agent_1">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_2">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_3">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="agent_4">
      <xi:include href="agent.prg"/>
    </new>
    <new instance="switch">
      <xi:include href="switch_on.prg"/>
    </new>
  </subprograms>
  <body>
    <models xmlns:xi="http://www.w3.org/2001/XInclude">
      <new instance="switch">
        <xi:include href="switch.mod"/>
      </new>
    </models>
  </body>
</program>

Soit le schéma de dépendance ci-dessous :

Dans le cas où le programme switch est instancié mais pas le modèle, alors les agents n'auraient pu se servir des messages envoyés par l'interrupteur, comme le montre le schéma de dépendance ci-dessous, car les instances du modèles switch sont différentes :

Dans le cas où le modèle switch est instancié mais pas le programme, alors il y aurait eu une duplication du programme switch donc duplication de règles avec un contenu identique, comme le montre le schéma de dépendance ci-dessous :