Le texte qui suit explique quelques importantes caractéristiques de la programmation en NetLogo.
Les modèles d'exemples de code cités au cours de ce chapitre se trouvent dans la section "Code Examples" de la Bibliothèques des modèles.
Remarque : tous les exemples de code cités dans ce chapitre ont été traduits et se trouvent dans le fichier Code Examples_fr.zip à télécharger, décompresser et installer dans le dossier "models" de votre installation NetLogo.
Le monde de NetLogo est constitué d'agents. Les agents sont des êtres capables d'exécuter des instructions. Chaque agent peut avoir sa propre activité, mais tous agissent simultanément.
NetLogo connaît quatre types d'agents : des tortues, des patches, des liens et l'observateur (observer). Les tortues ("turtles") sont les agents qui peuvent se déplacer dans le monde. Le monde ("world") est à deux dimensions et est divisé en une grille de patches. Chaque patch est un morceau de « sol » carré sur lequel les tortues peuvent se déplacer. Les liens ("links") sont des agents qui relient deux tortues. L'observateur ("observer") n'a pas de position déterminée – vous pouvez l'imaginer regardant d'en haut (comme un dieu) le monde des tortues et des patches.
Quand NetLogo démarre, il n'y a pas encore de tortues. L'observateur peut créer de nouvelles tortues. Les patches peuvent aussi créer de nouvelles tortues. (Les patches ne peuvent se déplacer, mais sinon, ils sont « vivants » tout comme l'est aussi l'observateur.)
Les patches sont désignés par leurs coordonnées dans le plan du monde. Le patch de coordonnées (0,0) est appelé origine, et les coordonnées des autres patches correspondent aux distances horizontale et verticale par rapport à ce patch. Les coordonnées d'un patch sont appelées pxcor et pycor. Tout comme pour les coordonnées du plan mathématique standard, la valeur de pxcor augmente quand on se déplace vers la droite et celle de pycor augmente quand on se déplace vers le haut.
Le nombre total de patches est déterminé par les paramètres min-pxcor, max-pxcor, min-pycor et max-pycor. Quand NetLogo démarre, min-pxcor, max-pxcor, min-pycor et max-pycor valent respectivement -16, 16, -16 et 16. Ce qui signifie que les valeurs de pxcor et de pycor peuvent aller de -16 à 16, et donc qu'il y a 33 fois 33, soit 1089 patches au total. (Vous pouvez modifier ces valeurs au moyen du bouton "Settings…" du panneau "Interface".)
Les tortues ont aussi des coordonnées : xcor et ycor. Les coordonnées d'un patch sont toujours des nombres entiers, alors que les coordonnées des tortues peuvent être des nombres décimaux. Ce qui signifie qu'une tortue peut être placée en n'importe quel point d'un patch, elle n'a pas besoin d'être toujours au milieu du patch.
Les liens n'ont pas de coordonnées, ils ont par contre deux points terminaux (chacun correspondant à une tortue). Les liens sont dessinés entre ces deux points terminaux en suivant le plus court chemin possible, même si cela signifie qu'il faut « tourner autour du monde ».
La manière dont le monde des patches se referme sur lui-même peut être modifiée. Par défaut, le monde est un tore, ce qui signifie qu'il n'a pas de limites, mais s'enroule (wraps) sur lui-même. Ainsi, lorsqu'une tortue traverse le bord du monde, elle disparaît puis réapparaît immédiatement au bord opposé. Sur ce tore, chaque patch possède aussi le même nombre de voisins : si vous êtes sur un patch situé sur un bord du monde, certains de vos « voisins » sont sur le bord opposé. Toutefois, vous pouvez modifier les paramètres de l'enroulement du monde au moyen des réglages de la boîte de dialogues "Model Settings" appelée par le bouton "Settings" du panneau "Interface". Si l'enroulement n'est pas autorisé dans une direction donnée, le monde est limité dans cette direction (X ou Y). Les patches placés le long de ce bord ont donc moins de huit voisins et les tortues ne peuvent pas franchir cette limite. Voir la section Topologie pour plus d'informations.
En NetLogo, les commandes et les reporters disent aux agents ce qu'ils doivent faire. Une commande est une action que l'agent doit exécuter. Un reporter calcule un résultat et en fait part à celui qui l'a appelé (on dit qu'il retourne un résultat). Les autres langages de programmation l'appelleraient une fonction.
La plupart des commandes commencent par un verbe, par exemple create (créer), die (mourir), jump (sauter), inspect (inspecter), clear (nettoyer), alors que les plupart des reporters sont des substantifs ou des expressions formées de substantifs.
Les commandes et les reporters prédéfinis sont appelés des primitives. Le Dictionnaire NetLogo contient et définit toutes les commandes et les reporters prédéfinis du langage NetLogo.
Les commandes et les reporters définis par l'utilisateur du langage (le programmeur de modèle) sont appelés des procédures. Chaque procédure commence par son nom qui doit être précédé du mot-clé to. Le mot-clé end doit marquer la fin des commandes placées dans la procédure, donc la fin de la procédure. Une fois définie, la procédure peut être utilisée n'importe où dans le programme.
Nombre de commandes et de reporters doivent recevoir des entrées (inputs), appelées arguments, qui contiennent des valeurs que la commande ou le reporter utilise pour accomplir ses actions (et/ou ses calculs).
Exemples : Voici deux procédures de type commande :
to setup clear-all ;; effacer le monde crt 10 ;; créer 10 nouvelles tortues end
to go ask turtles [ fd 1 ;; toutes les tortues avancent d'un pas rt random 10 ;; ... et pivotent d'un angle aléatoire lt random 10 ] end
Notez l'utilisation des points-virgules " ; " pour ajouter des commentaires au programme. Les commentaires facilitent la lecture et, surtout, la compréhension du programme.
Dans ce programme,
Les procédures setup et go peuvent être appelées par d'autres procédures ou par des boutons. Bien des modèles NetLogo possèdent un bouton « une fois » (once button) qui appelle une procédure nommée setup et un bouton « pout toujours » (forever) qui appelle une procédure nommée go.
En NetLogo, vous devez spécifier quel type d'agents – turtles, patches, links ou observer – doit exécuter chaque commande. (Si vous ne spécifiez rien, le code est exécuté par l'observateur.) Dans le code ci-dessus, l'observateur utilise ask pour s'adresser à l'ensemble de toutes les tortues qui devront alors exécuter les commandes placées entre crochets.
clear-all et crt ne peuvent être exécutées que par l'observateur. De la même façon, fd ne peut être exécutée que par les tortues. Par contre, certaines autres commandes (et reporters), telles que set, peuvent être exécutées par différents types d'agents.
Voici quelques autres fonctionnalités avancées dont vous pouvez tirer parti quand vous définissez vos propres procédures.
Les procédures avec arguments (ou entrées)
Les procédures que vous créez peuvent recevoir des informations au cours du déroulement du programme, exactement comme peuvent le faire les primitives. Pour créer une procédure qui puisse accepter des valeurs en entrée (ces valeurs sont appelées des paramètres), il faut inclure dans la définition de la procédure une liste d'arguments (qui sont les noms des variables qui contiendront les valeurs que l'on veut passer à la procédure). Cette liste est à placer entre crochets à la suite du nom de la procédure. Par exemple :
to dessiner-polygone [nombre-cotes longueur] pen-down repeat nombre-cotes [ fd longueur rt 360 / nombre-cotes ] end
Quelque part ailleurs dans le programme, vous pouvez demander aux tortues de dessiner chacune un octogone dont la longueur d'un côté est égale à sa propriété who (numéro reçu par la tortue à sa création) :
ask turtles [ dessiner-polygone 8 who ]
Les procédures reporter (ou fonctions)
Tout comme vous pouvez définir vos propres commandes, vous pouvez aussi définir vos propres reporters. Mais vous avez deux choses particulières à faire. D'abord, utiliser le mot-clé to-report à la place de to pour marquer le début de la procédure. Puis, dans le corps de la procédure, utiliser le mot-clé report pour retourner la valeur que vous voulez exporter.
to-report valeur-absolue [ nombre ] ifelse nombre >= 0 [ report nombre ] [ report (- nombre) ] end
Les variables sont des endroits où ranger des valeurs (telles que des nombres). Une variable peut être une variable globale, une variable tortue, une variable patch ou une variable lien.
Si une variable est une variable globale, il n'existe qu'une seule valeur pour cette variable et chaque agent peut y avoir accès. Mais chaque tortue a sa propre valeur pour chaque variable tortue et chaque patch sa propre valeur pour chaque variable patch.
Un certain nombre de variables sont prédéfinies dans NetLogo. Par exemple, toutes les tortues ont une variable color et tous les patches ont une variable pcolor. (La variable patch commence par un "p" de manière à ce qu'on ne la confonde pas avec la variable tortue.) Si vous initialisez la variable (donc si vous lui donnez une valeur), la tortue ou le patch change de couleur. (Voir ci dessous pour plus de détails.)
Les variables tortue prédéfinies comprennent entre autres xcor, ycor et heading. Les variables patch comprennent par exemple pxcor et pycor. (La liste complète des variables préprogrammées se trouve dans le Dictionnaire.)
Vous pouvez aussi définir vos propres variables.
Vous pouvez créer une variable globale en ajoutant un commutateur ou un curseur au modèle, ou en utilisant le mot-clé globals au début du code du modèle, comme ceci :
globals [ score ]
Vous pouvez également définir de nouvelles variable tortue, variable patch et variable lien avec les mots-clés turtles-own , patches-own et links-own de la manière suivante :
turtles-own [energie vitesse] patches-own [frottement] links-own [force]
Ces variables peuvent ensuite être utilisées librement dans le modèle. Il faut utiliser la commande set pour les initialiser (leur transmettre une valeur de départ). (Si vous ne le faites pas, ces variables reçoivent la valeur zéro par défaut.)
Les variables globales peuvent être lues et modifiées en tout temps par n'importe quel agent. De même, une tortue peut lire et modifier les variables patch du patch sur lequel elle se trouve. Par exemple, le code suivant
ask turtles [ set pcolor red ]
fait que chaque tortue peint en rouge le patch sur lequel elle se trouve. (C'est parce que les variables patch sont accessibles aux tortues de cette manière que vous ne pouvez pas avoir une variable patch et une variable tortue portant le même nom).
Dans d'autres situations, où vous voudriez qu'un agent lise la variable d'un autre agent, vous pouvez utiliser le mot-clé of. Exemple :
show [color] of turtle 5 ;; affiche la couleur de la tortue dont le numéro who est 5
Le mot-clé of peut aussi être utilisé dans une expression plus complexe qu'un simple nom de variable, par exemple :
show [xcor + ycor] of turtle 5 ;; affiche la somme des coordonnées X et Y ;; de la tortue dont le numéro who est 5
Une variable locale est définie et utilisée uniquement dans le contexte d'une procédure particulière ou même dans celui d'une partie seulement d'une procédure particulière. Pour créer une variable locale, il faut utiliser la commande let. Vous pouvez utiliser cette commande n'importe où. Si vous la mettez au début d'une procédure, elle existera dans tout le corps de cette procédure. Si vous la déclarez à l'intérieur d'une paire de crochets, par exemple dans une commande ask, elle n'existera alors qu'à l'intérieur de cette paire de crochets.
to permuter-couleurs [tortue1 tortue2] let temporaire [color] of tortue1 ask tortue1 [ set color [color] of tortue2 ] ask tortue2 [ set color temporaire ] end
Dans le langage NetLogo, les couleurs sont représentées de deux manières différentes. Premièrement en tant que nombres de 0 à 140, à l'exception de 140 lui-même. Les couleurs, avec leur numéro (que vous pouvez utiliser dans le code) sont présentées dans la figure ci-dessous :
Ce tableau montre que :
Exemple de code : La palette de couleurs montrée ci-dessus a été faite en NetLogo à l'aide du modèle "Color Chart Example".
Si vous utilisez un nombre n'appartenant pas à l'intervalle 0 à 140, NetLogo ajoute ou soustrait 140 au nombre fourni jusqu'à ce que le résultat tombe dans la fourchette désirée. Par exemple, 25 est orange, de même que 165, 305, 445, etc. sont aussi orange, il en va de même pour -115, -255, -395, etc. Ces calculs se font automatiquement chaque fois que vous donnez une valeur à la variable tortue color ou à la variable patch pcolor. Si vous deviez faire ce type de calcul dans un autre contexte, utilisez la primitive wrap-color.
Si vous voulez une couleur ne figurant pas dans le tableau, vos pouvez la trouver entre les nombres entiers. Par exemple, 26.5 est une teinte orange qui se trouve à mi-chemin entre l'orange 26 et l'orange 27. Mais tout ceci ne veut pas dire que vous pouvez créer n'importe quelle couleur en NetLogo. L'espace de couleurs de NetLogo n'est qu'un sous-ensemble de toutes les couleurs possibles. Il ne comporte qu'un ensemble déterminé de teintes discrètes (une teinte par ligne du tableau). En commençant par l'une de ces teintes, vous pouvez soit diminuer sa luminance (l'assombrir) soit diminuer sa saturation (l'éclaircir), mais vous ne pouvez pas diminuer à la fois la luminance et la saturation. Il s'ensuit que les valeurs des couleurs sont arrondies vers le bas à 0.1 près. Il n'y a, par exemple, pas de différences visibles entre 26.5 et 26.52 ou 26.58.
Les primitives pour les couleurs
NetLogo possède quelques primitives utiles pour travailler avec les couleurs.
Nous avons déjà mentionné la primitive wrap-color.
La primitive scale-color est utile pour convertir des données numériques en couleurs.
La primitive shade-of? nous indique si deux couleurs sont des nuances de la même teinte de base. Par exemple, shade-of? orange 27 est vrai, car 27 est une nuance plus claire de la teinte orange.
Exemple de code : "Scale-color Example" montre l'utilisation du reporter scale-color.
Les couleurs RGB
La seconde représentation des couleurs en NetLogo est une liste RGB (red/green/blue). L'utilisation du système des couleurs RGB donne accès à toute les couleurs. Les listes RGB sont formées de trois nombres entiers allant de 0 à 255. Si un nombre sort de cet intervalle, 255 en est soustrait jusqu'à ce qu'il puisse y entrer. Vous pouvez passer une liste RGB à n'importe quelle variable couleur de NetLogo (color pour les tortues et les liens et pcolor pour les patches) et l'agent sera affiché avec la couleur désirée. Ainsi, pour obtenir un patch d'un beau rouge pur, vous pouvez utiliser le code suivant :
set pcolor [255 0 0]
Vous pouvez convertir les couleurs des systèmes de couleurs RGB, HSB (hue/saturation/brightness) et NetLogo en utilisant approximate-hsb et approximate-rgb pour passer des couleurs HSB et RGB aux couleurs NetLogo, et extract-hsb et extract-rgb pour les convertir dans l'autre sens. Vous pouvez utiliser rgb pour générer des listes RGB et hsb pour convertir une liste HSB en liste RGB.
Puisque de nombreuses couleurs manquent dans l'espace de couleurs NetLogo, approximate-hsb et approximate-rgb ne peuvent pas toujours donner la couleur exacte demandée, mais ils tentent d'en arriver le plus près possible.
Exemple de code : Le modèle "HSB and RGB Example" permet de se familiariser avec les systèmes de couleurs HSB et RGB.
La boîte de dialogue "Color Swatches"
La boîte de dialogue "Color Swatches" permet detester et de choisir les couleurs. Vous pouvez l'ouvrir en sélectionnant "Color Swatches" dans le menu "Tools".
Quand vous cliquez sur un échantillon de couleur (ou sur un bouton de couleur), cette couleur est montrée (sous forme de pointe de flèche) au-dessus des différentes couleurs de base dans la colonne "Preview", sur un fond noir dans le coin supérieur gauche et sur un fond blanc dans le coin supérieur droit. Le nom (code) de la couleur sélectionnée (par exemple red + 2) est affiché dans le bas de la fenêtre, juste à droite du bouton "Copy selected color" qui vous permet de copier ce code pour le coller ensuite dans votre code. En bas à droite se trouvent trois options d'incrémentation : 1, 0.5 et 0.1. Ces nombres indiquent l'incrément utilisé pour l'affichage de la palette entre deux échantillons de couleurs successifs. Quand l'incrément vaut 1, il y a 10 nuances différentes dans chaque rangée, quand l'incrément vaut 0.1, il y a 100 nuances différentes dans chaque rangée. 0.5 est un réglage intermédiaire. La case à cocher "Numbers" active/désactive l'affichage des numéros de couleur NetLogo dans chaque échantillon de couleur. Les deux boutons placés dans le haut et les quatorze boutons placés à gauche de la fenêtre permettent de sélectionner directement l'une des seize couleurs de base. Enfin, si les échantillons vous paraissent trop petits (surtout avec des incrément de 0.5 ou 0.1) vous pouvez agrandir cette fenêtre à l'aide des boutons prévu à cet effet par le système sur lequel tourne NetLogo.
NetLogo utilise la commande ask pour transmettre les ordres aux tortues, aux patches et aux liens. Tout le code devant être exécuté par des tortues doit être placé dans un « contexte » tortue. Vous pouvez établir un contexte tortue de l'une des trois manières suivantes :
Il en va de même pour les patches, les liens et l'observateur, sauf que vous ne pouvez pas utiliser ask avec l'observateur. Tout code qui ne se trouve pas dans des crochets ask est par défaut du code pour l'observateur.
Voici un exemple d'utilisation de ask dans une procédure NetLogo :
to setup clear-all crt 100 ;; créer 100 tortues ask turtles [ set color red ;; les colorer en rouge rt random-float 360 ;; leur donner une orientation aléatoire fd 50 ] ;; les disperser ask patches [ if pxcor > 0 ;; colorer les patches de la moitié [ set pcolor green ] ] ;; droite de la Vue en vert end
Les modèles de la Bibliothèque des modèles sont pleins d'autres exemples. La section "Code Examples" est un bon endroit pour commencer.
En général, l'observateur utilise la commande ask pour demander à toutes les tortues, tous les patches ou tous les liens d'exécuter des commandes. Mais vous pouvez aussi utiliser ask pour demander à une seule tortue, à un seul patch ou à un seul lien d'exécuter des commandes. Les reporters turtle, patch, link et patch-at sont utiles pour cette technique. Par exemple :
to setup clear-all crt 3 ;; créer 3 tortues ask turtle 0 ;; demander à la première... [ fd 1 ] ;; ...d'avancer d'un pas ask turtle 1 ;; demander à la deuxième... [ set color green ] ;; ...de devenir verte ask turtle 2 ;; demander à la troisième... [ rt 90 ] ;; ...de tourner à droite de 90 degrés ask patch 2 -2 ;; demander au patch placé en (2,-2) [ set pcolor blue ] ;; ...de devenir bleu ask turtle 0 ;; demander à la première tortue [ ask patch-at 1 0 ;; ...de demander au patch placé à l'est [ set pcolor red ] ] ;; ...de devenir rouge ask turtle 0 ;; demander à la première tortue... [ create-link-with turtle 1 ] ;; ...de créer un lien avec la seconde ask link 0 1 ;; demander au lien entre les tortues 0 et 1 [ set color blue ] ;; ...de se colorer en bleu end
Chaque tortue créée possède un numéro d'identification (appelé "who number" en NetLogo). La première tortue créée a le numéro 0, la deuxième le numéro 1, et ainsi de suite. La primitive reporter turtle demande un numéro d'identification en entrée et retourne la tortue qui possède ce numéro. La primitive reporter patch demande en entrée les valeurs pour pxcor et pycor et retourne le patch qui possède ces coordonnées. La primitive reporter link demande deux entrées qui sont les numéros d'identification des deux tortues reliées par un lien. Et la primitive reporter patch-at demande des décalages (offsets) : les distances, dans les directions X et Y, à partir du premier agent. Dans l'exemple ci-dessus, on demande à la tortue dont le numéro d'identification est 0 de s'adresser au patch placé à l'est (et non aux patches placés au nord) d'elle-même.
Vous pouvez aussi sélectionner un sous-ensemble de tortues, un sous-ensemble de patches ou un sous-ensemble de liens puis leur demander de faire quelque chose. Mais cette manière de faire implique un nouveau concept appelé ensembles d'agents (agentsets). La section suivante expliquera ce concept en détails.
Quand vous demandez à un ensemble d'agents d'exécuter plus d'une seule commande, chaque agent doit terminer sa tâche avant que l'agent suivant puisse exécuter la sienne. Un agent exécute toutes les commandes, puis l'agent suivant les exécute toutes à son tour, et ainsi de suite. Par exemple, si vous écrivez :
ask turtles [ fd 1 set color red ]
d'abord une tortue se déplace et devient rouge, puis une autre tortue se déplace et devient rouge, etc.
Mais si vous écrivez ce qui suit :
ask turtles [ fd 1 ] ask turtles [ set color red ]
d'abord toutes les tortues se déplacent. Et une fois qu'elles se sont toutes déplacées, elles deviennent rouges.
(La commande ask possède une autre forme obéissant à une autre règle de priorité. Voir Ask-Concurrent ci-dessous.)
Un ensemble d'agents (agentset) peut contenir soit des tortues, soit des patches, soit des liens, donc jamais plus d'un type d'agents à la fois.
Les éléments d'un ensemble d'agents ne sont pas dans un ordre particulier. En réalité, ils sont toujours dans un ordre aléatoire. Et chaque fois que vous utilisez cet ensemble, ses agents sont dans un ordre aléatoire différent. Cette manière de traiter les ensembles d'agents vous aide à faire en sorte que le modèle ne traite pas une tortue, un patch ou un lien particulier différemment des tous les autres (à moins que vous ne le vouliez). Puisque l'ordre est à chaque fois aléatoire, aucun agent ne passe toujours en premier.
Nous avons déjà rencontré la primitive turtles qui retourne l'ensemble d'agents contenant toutes les tortues, la primitive patches qui retourne l'ensemble d'agents contenant tous les patches et la primitive links qui retourne l'ensemble d'agents contenant tous les liens.
Mais ce qui est particulièrement puissant dans le concept d'ensemble d'agents est que vous pouvez construire des ensembles d'agents qui ne contiennent que certaines tortues, que certains patches ou que certains liens. Par exemple, toutes les tortues rouges, tous les patches dont la coordonnée pxcor est divisible par cinq ou toutes les tortues situées dans le premier quadrant et qui sont placées sur un patch vert ou qui ont des liens les reliant à la tortue 0. Ces ensembles d'agents peuvent ensuite être utilisés par ask ou par divers reporters qui acceptent des ensembles d'agents en entrée.
Une manière de créer un ensemble d'agents ne contenant que les tortues placées sur le patch courant est d'utiliser le reporter turtles-here, ou d'utiliser turtles-at pour créer un ensemble d'agents contenant les tortues placées sur le patch situé à x patches horizontalement et à y patches verticalement du patch courant. Il existe également la primitive turtles-on pour obtenir l'ensemble des tortues placées sur un patch particulier ou sur un ensemble de patches, ou l'ensemble des tortues placées sur le même patch qu'une tortue donnée ou qu'un ensemble de tortues.
Voici encore quelques exemples supplémentaires montrant comment créer des ensembles d'agents :
;; toutes les autres tortues: other turtles ;; toutes les autres tortues sur ce patch: other turtles-here ;; toutes les tortues rouges: turtles with [color = red] ;; toutes les tortues rouges sur mon patch: turtles-here with [color = red] ;; les patches de la moitié droite de la vue: patches with [pxcor > 0] ;; toutes les tortues situées à moins de 3 patches: turtles in-radius 3 ;; les quatre patches placés à l'est, au nord, à l'ouest et au sud: patches at-points [[1 0] [0 1] [-1 0] [0 -1]] ;; abréviation pour ces quatre patches: neighbors4 ;; les tortues du premier quadrant qui sont sur des patches verts: turtles with [(xcor > 0) and (ycor > 0) and (pcolor = green)] ;; les tortues situées sur mes quatre patches voisins: turtles-on neighbors4 ;; tous les liens connectés à la tortue 0 [my-links] of turtle 0
Notez l'utilisation de other pour exclure de l'ensemble l'agent appelant. C'est une pratique courante.
Une fois que vous avez créé un ensemble d'agents, voici quelques tâches que vous pouvez accomplir :
Et voici encore quelques actions plus complexes que vous pouvez entreprendre :
ask one-of turtles [ set color green ]
ask one-of patches [ sprout 1 ]
ask max-one-of turtles [sum assets] [ die ]
show mean [sum assets] of turtles
Ces exemples ne font qu'égratigner la surface. Fouillez dans la Bibliothèque des modèles pour y trouver de nombreux autres exemples et consultez le Dictionnaire NetLogo pour davantage d'informations concernant toutes les primitives d'ensembles d'agents.
D'autres exemples d'utilisation d'ensembles d'agents sont donnés dans les diverses entrées de ces primitives dans le Dictionnaire NetLogo. Au cours de votre apprentissage de la programmation en NetLogo, il est important de commencer de penser aux combinaisons de commandes dans le sens de : comment chaque élément passe des informations à l'élément suivant. Les ensembles d'agents sont une part importante de ce schéma conceptuel et offrent au développeur NetLogo puissance et souplesse, tout en étant plus proche du langage naturel.
Exemple de code : "Ask Ordering Example"
Plus haut, nous avions dit que les agents des ensembles d'agents sont toujours dans un ordre aléatoire, ordre aléatoire qui change chaque fois que l'ensemble est utilisé. Si vous avez besoin que vos agents fassent quelque chose dans un ordre bien déterminé, il faut utiliser une liste d'agents à la place d'un ensemble d'agents. Voir la section Les listes ci-dessous.
NetLogo permet de définir différentes races (breeds) de tortues et races de liens. Une fois ces races définies, vous pouvez aller plus loin et donner à ces différentes races des comportements différents. Par exemple, vous pouvez avoir une race appelée moutons et une race appelée loups et faire que les loups essaient de manger les moutons ou avoir des races appelées routes et trottoirs puis faire en sorte que les piétons marchent sur les trottoirs et les véhicules circulent sur les routes.
Vous définissez les races de tortues au moyen du mot-clé breed. Les définitions des races doivent se trouver au début du code du modèle, avant la définition des procédures.
breed [loups loup] breed [moutons mouton]
Notez que, dans les deux définitions ci-dessus, les noms des races sont d'abord écrites au pluriel puis au singulier. Cette syntaxe est à respecter et si le nom au pluriel ne diffère pas du nom au singulier, il faut marquer la différence, par exemple :
breed [ souris une-souris ]Vous pouvez faire référence à un membre d'une race en utilisant le nom de la race au singulier, tout comme on le fait avec le reporter turtle. De plus, lorsque le nom des membres de la race est affiché, ce nom est aussi au singulier.
Certaines commandes et reporters ont dans leur nom le nom de la race au pluriel, comme create-<breeds>. D'autres ont dans leur nom le nom de la race au singulier, comme dans <breed>
L'ordre dans lequel les races sont déclarées est aussi l'ordre dans lequel elles sont « empilées » dans la vue. Ainsi, les races définies plus bas apparaissent au-dessus des races définies avant elles. Dans l'exemple ci-dessus, les moutons sont dessinés par-dessus les loups.
Quand vous définissez une race telle que moutons, un ensemble d'agents de cette race est automatiquement créé, ce qui fait que toutes les fonctionnalités liées aux ensembles d'agents sont immédiatement disponibles pour l'ensemble d'agents moutons.
Les primitives suivantes sont aussi automatiquement créées et disponibles une fois qu'un ensemble d'agents (ici moutons) a été défini : create-moutons, hatch-moutons, sprout-moutons, moutons-here, moutons-at, moutons-on et is-a-moutons?.
De plus, vous pouvez utiliser moutons-own pour définir de nouvelles variables tortue que seules les tortues appartenant à cet ensemble moutons peuvent avoir.
Un ensemble d'agents de la race de tortues est stocké dans la variable tortue breed. Vous pouvez ainsi tester cette race de tortues comme suit :
if breed = moutons [ ... ]
Notez aussi que les tortues peuvent changer de race. Un loup n'est pas obligé de rester un loup tout au long de sa vie. Transformons un loup choisi au hasard en un mouton :
ask one-of loups [ set breed moutons ]
La primitive set-default-shape est utile pour associer certaines formes de tortues à certaines races. Voir la section concernant les formes plus loin.
Voici un petit exemple d'utilisation des races :
breed [souris une-souris] breed [grenouilles grenouille] souris-own [fromage] to setup clear-all create-souris 50 [ set color white set fromage random 10 ] create-grenouilles 50 [ set color green ] end
Exemple de code : "Breeds and Shapes Example"
Les races de liens ressemblent beaucoup aux races de tortues, mais il y a tout de même quelques différences :
Quand vous déclarez une race de liens, vous devez précisez s'il s'agit d'une race de liens orientés ou non-orientés en utilisant les mots-clés directed-link-breed et undirected-link-breed.
directed-link-breed [ routes route] undirected-link-breed [amis ami ]
Une fois que vous avez créé un lien de race, vous ne pouvez pas créer des liens sans race, et vice-versa. (Toutefois, vous pouvez avoir dans le même monde des liens orientés et des liens non-orientés, mais il ne doivent pas être de la même race.)
Contrairement à ce qui se passe avec les races de tortues, les races de liens doivent avoir un nom au singulier, car nombre de commandes et de reporters de liens demandent un nom au singulier, comme <link-breed>-neighbor?.
Les primitives suivantes sont aussi automatiquement disponibles une fois qu'une race de liens orientés (ici routes) a été définie : create-route-from, create-routes-from, create-route-to, create-routes-to, in-route-neighbor?, in-route-neighbors, in-route-from, my-in-routes, my-out-routes, out-route-neighbor?, out-route-neighbors et out-route-to.
Et les primitives suivantes sont aussi automatiquement disponibles une fois qu'une race de liens non-orientés (ici amis) a été définie : create-ami-with, create-amis-with, ami-neighbor?, ami-neighbors, ami-with, my-amis.
Tout comme avec les races de tortues, l'ordre dans lequel les races de liens ont été déclarées définit l'ordre dans lequel ces liens sont dessinés. Ainsi, les amis seront toujours au-dessus des routes (si pour une raison quelconque ces deux races se trouvent dans le même modèle). Vous pouvez aussi utiliser <link-breeds>-own pour déclarer séparément des variables pour chaque race de liens.
Vous pouvez changer la race des liens comme pour les tortues, mais vous ne pouvez pas transformer des liens de race en liens sans race, ceci dans le but d'empêcher d'avoir des liens de race et des liens sans races dans le même monde (ce qui est impossible).
ask one-of amis [ set breed routes ] ask one-of amis [ set breed links ] ;; génère une erreur d'exécution
La primitive set-default-shape peut aussi être utilisée avec des races de liens pour les associer à une forme de lien particulière.
Exemple de code : "Link Breeds Example"
Les boutons placés dans le panneau "Interface" offrent un moyen commode de contrôler le modèle. En général, un modèle aura au moins un bouton "setup" (initialiser), pour mettre en place l'état initial du monde, et un bouton "go" pour faire démarrer la simulation. Certains modèles auront d'autres boutons pour accomplir d'autres actions.
Chaque bouton contient au moins un peu de code NetLogo (sinon il ne sert à rien, si ce n'est de faire joli). Ce code est exécuté quand le bouton est pressé.
Un bouton peut être soit un bouton « une fois » (once button), soit un bouton « pour-toujours » (forever button). Vous spécifiez le type désiré en éditant le bouton et en cochant ou décochant la case "Forever". Les boutons une-fois exécutent leur code une seule fois, puis stoppent et reviennent en position déclenché. Les boutons pour-toujours exécutent leur code encore et encore, jusqu'à ce que, soit le code rencontre la commande stop, soit l'utilisateur presse encore une fois ce bouton pour stopper la simulation. Si vous déclenchez ce bouton pour stopper la simulation, l'exécution du code n'est pas interrompue immédiatement. Le bouton attend jusqu'à ce que le code du cycle en cours ait été exécuté, puis il revient en position déclenché.
Normalement, un bouton porte le nom du code qu'il exécute. Par exemple, un bouton qui affiche "go" contient généralement le code go, ce qui signifie « exécute la procédure go ». (Les procédures sont définies dans le panneau "Procedures", voir ci-dessous.) Mais vous pouvez aussi éditer un bouton et spécifier un nom d'affichage (dans le champ "Display name" de la fenêtre d'édition "Button"), nom qui apparaîtra sur le bouton à la place de son code. Vous pouvez utiliser cette fonctionnalité si vous pensez que le nom du code n'est pas assez explicite pour l'utilisateur (surtout si vous traduisez l'interface d'un modèle).
Quand vous écrivez du code dans un bouton, vous devez aussi spécifier quels agents devront exécuter ce code. Comme exécuteur du code, vous pouvez choisir, dans le menu "Agent(s)", l'observateur ("Observer"), toutes les tortues ("Turtles"), tous patches ("Patches") ou tous les liens ("Links"). Si vous voulez que le code ne soit exécuté que par certaines tortues ou par certains liens, vous pouvez créer un bouton observateur et faire que l'observateur utilise la commande ask pour demander à ces tortues ou à ces patches particuliers de faire quelque chose.
Quand vous éditez un bouton, vous avez la possibilité de lui assigner une « touche action ». Cette assignation a pour résultat qu'une pression sur cette touche du clavier produit le même effet qu'un clic sur le bouton — attention, il faut que le focus soit dans la zone de l'interface contenant la Vue, sinon les frappes au clavier vont dans la Ligne de commandes (il suffit de cliquez dans cette zone avec la souris pour y placer le focus). Si le bouton est un bouton pour-toujours, il restera enfoncé jusqu'à ce que l'on presse encore une fois sur la touche (ou que l'on clique le bouton). Les touches actions sont particulièrement utiles pour les jeux ou les modèles dans lesquels les actions sur les boutons doivent être rapides.
Les boutons attendent leur tour
Il est possible d'avoir plus d'un bouton pressé à la fois. Si cette situation se présente, les boutons « attendent leur tour », ce qui signifie que le code d'un seul bouton est exécuté à la fois. Chaque bouton exécute son code une fois jusqu'au bout pendant que les autres attendent, puis le bouton suivant prend son tour.
Dans les exemples suivants, "setup" est un bouton une-fois et "go" est un bouton pour-toujours.
Exemple 1 : L'utilisateur presse "setup" puis "go" tout de suite après, sans attendre que le bouton "setup" revienne à sa position déclenché. Résultat : "setup" termine son travail avant que "go" ne prenne la relève.
Exemple 2 : Alors que "go" est encore enclenché, l'utilisateur presse "setup" pour réinitialiser la simulation. Résultat : "go" termine le cycle en cours, le bouton "setup" exécute alors son code et enfin le bouton "go" reprend son travail en démarrant une nouvelle simulation avec les paramètres spécifiés par "setup".
Exemple 3 : L'utilisateur a deux boutons pour-toujours enfoncés en même temps. Résultat : un bouton exécute tout son code une fois puis l'autre bouton exécute tout son code une fois, et ainsi de suite, alternativement.
Notez que si un bouton est « planté » dans une boucle infinie, aucun des autres boutons ne pourra exécuter son code.
Boutons tortue, patch et lien « pour-toujours »
Il y a une différence subtile entre placer des commandes dans un bouton « pour-toujours » de type tortue, patch ou lien et placer les mêmes commandes dans un bouton observateur qui exécute ask turtles, ask patches ou ask links. Une commande ask ne se termine pas tant que tous ses agents n'ont pas fini d'exécuter toutes les commandes données par ask. Il en résulte que les agents, puisqu'ils exécutent leurs commandes indépendamment et en concurrence les uns avec les autres, peuvent ne plus être synchronisés en cours de cycle, mais ils se re-synchronisent à la fin du cycle, une fois toutes leurs tâches accomplies. Il n'en va pas de même pour les boutons « pour-toujours» de type tortue, patch ou lien, car dans ce cas, puisque ask n'est pas utilisé, chaque tortue, patch ou lien exécute son code encore et encore sans s'occuper des autres. Il en résulte qu'ils ne se synchronisent plus et restent désynchronisés jusqu'à la fin de la simulation.
Pour le moment, cette fonctionnalité n'est que très rarement utilisée dans les modèle de la Bibliothèque des modèles. Un des rares qui l'utilise est le modèle "Termites" de la section "Biology" des "Sample Models". Dans ce modèle, le bouton "go" est un bouton tortue pour-toujours, ce qui fait que chaque termite exécute son code indépendamment des autres termites, et l'observateur n'est pas du tout impliqué dans le déroulement de la simulation. Ce qui signifie que si, par exemple, vous vouliez ajouter une traceur au modèle, vous devriez ajouter un second bouton pour-toujours (un bouton observateur pour-toujours), et démarrer les deux boutons pour-toujours en même temps.
Dans sa version actuelle, NetLogo n'offre pas la possibilité, pour un bouton pour-toujours de démarrer un autre bouton. Les boutons ne sont activés que lorsque vous les cliquez (ou que vous pressez les touches qui leur sont attribuées.)
Dans les modèles les plus simples, chaque variable ne contient qu'un « morceau » d'information, généralement un nombre ou une chaîne de caractères. La fonctionnalité liste permet de stocker plusieurs morceaux d'information dans une seule variable en rassemblant ces morceaux d'information dans une liste. Chaque valeur de la liste peut être de n'importe quel type : un nombre, une chaîne, un agent, un ensemble d'agents, ou même une autre liste.
En NetLogo, les listes sont un moyen efficace de rassembler de l'information. Si vos agents doivent exécuter des calculs répétitifs sur de multiples variables, il est souvent plus simple d'avoir une seule variable de type liste qu'un grand nombres de variables numériques distinctes. Plusieurs primitives simplifient le déroulement des mêmes opérations sur chaque valeur d'une liste.
Le Dictionnaire NetLogo comporte toute une section consacrée aux primitives traitant des listes.
Listes constantes
Vous pouvez construire une liste simplement en mettant entre crochets les valeurs devant faire partie de la liste, comme ceci : set maliste [ 2 4 6 8 ]. Notez que les éléments de la liste sont séparés par des espaces. Vous pouvez construire des listes de nombres ou des listes de chaînes de la même manière, et même des listes de listes, comme ici : [ [ 2 4 ] [ 3 5 ] ].
Une liste vide est construite en ne mettant rien entre les crochets, comme ceci : [].
Construire des listes « à la volée »
Si vous voulez construire une liste dont les valeurs sont déterminées par des reporters plutôt que d'être une simple collection de constantes, utilisez le reporter list. Ce reporter accepte deux autres reporters, les active et retourne leurs résultats sous forme de liste.
Ainsi, si je désire une liste contenant deux valeurs aléatoires, je peux utiliser le code suivant :
set liste-aleatoire list (random 10) (random 20)
Ce morceau de code met dans la variable liste-aleatoire une nouvelle liste constituée de deux nombres aléatoires entiers chaque fois qu'il est exécuté.
Pour construire des liste plus longues ou plus courtes, vous pouvez utiliser le reporter list avec plus de deux ou moins de deux entrées, mais dans les deux cas, il faut mettre tout l'appel entre parenthèses, par exemple :
(list random 10) (list random 10 random 20 random 30)
Pour plus d'informations, voir la section Nombre d'arguments variable.
Certains types de listes sont construits plus facilement en utilisant le reporter n-values qui permet de construire une liste d'une longueur spécifique en appelant un reporter donné de manière répétée. Vous pouvez construire une liste contenant plusieurs fois la même valeur, tous les nombres appartenant à un un certain intervalle, un ensemble de nombres aléatoires, ou encore bien d'autres choses. Voir les différentes entrées y-relatives du dictionnaire pour plus de détails et d'exemples.
La primitive of permet de construire une liste à partir d'un ensemble d'agents. Elle retourne une liste contenant la valeur de chaque agent pour un reporter donné. (Le reporter peut être un simple nom de variable ou une expression plus complexe – voire même un appel de procédure définie en utilisant la primitive to-report.) Une construction fréquente est :
max [...] of turtles sum [...] of turtles
et ainsi de suite.
Vous pouvez combiner deux ou plusieurs listes avec le reporter sentence , qui concatène des listes en combinant leur contenu en une liste unique, plus grande. Tout comme list, sentence demande normalement deux arguments, mais peut accepter n'importe quel nombre d'arguments si l'appel est entouré de parenthèses.
Modifier les membres d'une liste
Du point de vue technique, les listes ne peuvent pas être modifiées, mais vous pouvez construire de nouvelles listes à partir de listes plus anciennes. Si vous voulez que la nouvelle liste remplace l'ancienne, utilisez le mot-clé set. Par exemple :
set maliste [2 7 5 Bob [3 0 -2]] ; maliste est maintenant [2 7 5 Bob [3 0 -2]] set maliste replace-item 2 maliste 10 ; maliste est maintenant [2 7 10 Bob [3 0 -2]]
On peut remplacer un membre d'une liste avec le reporter replace-item qui demande trois arguments, le premier spécifiant quel membre de la liste doit être modifié. Le premier membre d'une liste porte le numéro 0, le deuxième le numéro 1, et ainsi de suite.
Pour ajouter un membre, disons 42, à la fin de la liste, utilisez le reporter lput. (fput ajoute un membre au début de la liste.)
set maliste lput 42 maliste ; maliste est maintenant [2 7 10 Bob [3 0 -2] 42]
Mais que faire si vous changez d'avis? Le reporter but-last (bl en abrégé) retourne tous les membres de la liste sauf le dernier.
set maliste but-last maliste ; maliste est maintenant [2 7 10 Bob [3 0 -2]]
Supposons que vous vouliez vous passer du membre 0, le 2 du début de la liste.
set maliste but-first maliste ; maliste est maintenant [7 10 Bob [3 0 -2]]
Supposons que vous vouliez remplacer par 9 le -2 qui est le troisième membre de la liste emboîtée en troisième position dans la liste « de base ». La solution est de se rendre compte que le nom qui peut être utilisé pour appeler la liste emboîtée [ 3 0 -2 ] est item 3 maliste. Ensuite, le reporter replace-item peut être emboîté pour changer la « liste-dans-une-liste ». Les parenthèses sont ajoutées pour plus de clarté.
set maliste (replace-item 3 maliste (replace-item 2 (item 3 maliste) 9)) ; maliste est maintenant [7 10 Bob [3 0 9]]
Itération sur des listes
Si vous voulez effectuer une opération successivement sur chaque membre d'une liste, la primitive commande foreach et le reporter map peuvent être utiles.
foreach est utilisée pour exécuter une commande ou un ensemble de commandes sur chaque membre d'une liste. Il demande en entrée une liste et un bloc de commandes, comme ici :
foreach [2 4 6] [ crt ? show (word "créé " ? " tortues") ] => créé 2 tortues => créé 4 tortues => créé 6 tortues
Dans le bloc de commandes, la variable ? contient la valeur courante de la liste passée en entrée.
Voici encore quelques exemples d'utilisation de foreach :
foreach [1 2 3] [ ask turtles [ fd ? ] ] ;; les tortues avancent de 6 patches (1 + 2 + 3) foreach [true false true true] [ ask turtles [ if ? [ fd 1 ] ] ] ;; les tortues avancent de 3 patches (1 + 0 + 1 + 1)
map est semblable à foreach, mais c'est un reporter. Il demande en entrée une liste et un autre reporter. Notez toutefois que, contrairement à foreach, le reporter doit être à la première place, comme ici :
show map [round ?] [1.2 2.2 2.7] ;; affiche [1 2 3]
map retourne une liste contenant les résultats de l'action du reporter sur chaque membre de la liste reçue en entrée. A nouveau, utilisez ? pour faire référence au membre courant de la liste.
Voici un autre exemple de l'utilisation de map :
show map [? < 0] [1 -1 3 4 -2 -10] ;; affiche [false true false false true true]
foreach et map ne sont pas nécessairement utiles dans toutes les situations dans lesquelles vous voulez opérer sur toute une liste. Dans certains cas, vous devrez avoir recours d'autres techniques, telles que des boucles créées par repeat ou while ou une procédure récursive.
La primitive sort-by utilise une syntaxe ressemblant à celles de map et de foreach, sauf que, puisque le reporter doit comparer deux objets, il faut utiliser les deux variables spéciales ?1 et ?2 à la place de ?.
Voici un exemple avec sort-by :
show sort-by [?1 < ?2] [4 1 3 2] ;; affiche [1 2 3 4]
Certaines commandes et certains reporters utilisant des listes et des chaînes peuvent recevoir un nombre d'arguments variable. Dans ces cas, pour pouvoir leur passer un nombre d'arguments autre que celui par défaut, la primitive et ses arguments doivent être entourés de parenthèses. En voici quelques exemples :
show list 1 2 => [1 2] show (list 1 2 3 4) => [1 2 3 4] show (list) => []
Notez que chacune de ces commandes spéciales est définie avec un nombre d'arguments par défaut pour lequel les parenthèses ne sont pas nécessaires. Les primitives offrant cette fonctionnalité sont: list, word, sentence, map et foreach.
Les listes d'agents
Plus haut, nous avons dit que les éléments des ensembles d'agents sont toujours dans un ordre aléatoire, ordre qui change chaque fois que l'on fait appel à l'ensemble. Si vos agents doivent faire quelque chose dans un ordre déterminé, vous devez utiliser des listes d'agents à la place des ensembles d'agents.
Les deux primitives sort et sort-by vous aident dans cette tâche.
Les primitives sort et sort-by peuvent recevoir comme argument un ensemble d'agents. Le résultat est toujours une nouvelle liste contenant le même nombre d'agents que l'ensemble d'agents dont elle est issue, mais dans un ordre particulier qui ne changera pas.
Si vous utilisez sort sur un ensemble d'agents de type tortue, le résultat est une liste de tortues triée par numéro d'identification ("who number") croissant .
Si vous utilisez sort sur un ensemble d'agents de type patches, le résultat est une liste de patches triée de gauche à droite et de haut en bas.
Si vous utilisez sort sur un ensemble d'agents de type lien, le résultat est une liste de liens triés en ordre croissant, d'abord en fonction de end1 puis en fonction de end2, tous les liens restants étant triés par race en fonction de l'ordre dans lequel ces races ont été déclarées dans le panneau "Procedures".
Si vous avez besoin d'un ordre décroissant, vous pouvez combiner la primitive reverse avec le reporter sort, par exemple reverse sort turtles.
Si les agents doivent être triés en fonction d'un autre critère que celui utilisé par défaut par la primitive sort, vous devez la remplacer par la primitive sort-by
Voici un exemple :
sort-by [[size] of ?1 < [size] of ?2] turtles
Cette commande retourne une liste de tortues triée par taille (valeur de la variable tortue size ) croissante.
Commander une liste d'agents
Une fois la liste d'agents obtenue, vous allez demander à chacun d'eux de faire quelque chose. À cet effet, il faut utiliser une combinaison des commandes foreach et ask de la manière suivante :
foreach sort turtles [ ask ? [ ... ] ]
Ce morceau de code fait appel à chaque tortue successivement en fonction de son numéro d'identification en ordre croissant. Mettez patches à la place de turtles pour faire appel aux patches dans l'ordre, de gauche à droite et de haut en bas.
Si vous utilisez la commande foreach de cette manière, les agents de la liste exécutent les commandes transmise par ask séquentiellement, et non concurremment. Chaque agent termine ses commandes avant que l'agent suivant ne commence avec les siennes.
Notez qu'on ne peut pas utiliser ask directement sur une liste de tortues. En effet, cette primitive ne fonctionne qu'avec des ensembles d'agents ou des agents isolés..
Si votre modèle fait un usage imtensif des listes, et spécialement de longues listes, vous avez tout intérêt à connaître la vitesse d'exécution des différentes opérations sur les listes afin de pouvoir écrire un code qui ne soit pas trop lent.
Les listes de NetLogo sont des listes « unidirectionnelles » (singly linked). Ce terme technique, issu des sciences informatiques, signifie que quand NetLogo doit trouver un membre d'une liste, il commence sa recherche au début de la liste et la parcourt, membre après membre, jusqu'à ce qu'il trouve celui qu'il recherche. Par exemple, pour trouver le centième membre, il doit d'abord passer par les 99 précédents.
Mais cela signifie aussi que certaines opérations sont particulièrement efficientes, notamment les opérations sur les têtes de listes. Les reporters first, but-first et fput sont très rapides : leur durée d'exécution est la même quelle que soit la longueur de la liste. Donc, si vous construisez une liste en ajoutant un membre après l'autre, l'utilisation de fput (mets-premier) produit un code bien plus rapide que l'utilisation de lput (mets-dernier). (Si cette manière de faire produit une liste en sens inverse de celle que vous vouliez, vous pouvez utiliser reverse pour la « retourner » une fois qu'elle aura été construite.)
Le reporter length est aussi très rapide car NetLogo garde toujours une trace de la longueur de chaque liste, il n'a donc jamais besoin de les mesurer véritablement.
Parmi les reporters les plus lents sur les longues listes, on trouve item, lput, but-last, last et one-of.
En NetLogo, tous les nombres sont stockés en interne sous la forme de nombres à virgule flottante en double précision, tels que définis dans le standard IEEE 754. Ce sont des nombres à 64 bits composés d'un bit de signe, d'un exposant à 11 bits et d'une mantisse à 52 bits. Voir le standard IEEE 754 plus plus de détails.
Pour NetLogo, un entier est simplement un nombre qui semble ne pas avoir de partie décimale. Aucune distinction n'est faite entre 3 et 3.0 : ces deux notations représentent le même nombre. (C'est de cette manière que la plupart des gens utilisent les nombres au quotidien, mais ce n'est pas celle utilisée par certains langages de programmation. En effet, certains langage de programmation traitent les nombres entiers et les nombres en virgule flottante comme des types distincts.)
NetLogo affiche toujours les nombres entiers sans le point décimal et le zéro qui suit, donc sans " .0 " :
show 1.5 + 1.5 observer: 3
Si un nombre avec une partie décimale est fourni dans un contexte où un entier est demandé, la partie fractionnaire (décimale) est simplement ignorée. Ainsi, par exemple, la commande crt 3.5 crée trois tortues, le 0.5 étant ignoré.
Le domaine couvert par les nombres entiers va de -9007199254740992 à +9007199254740992 (±2^53, soit environ 9 quadrillions). Les opérations qui conduisent au dépassement de ces limites ne provoquent pas d'erreur d'exécution, mais la précision diminue quand les bits de poids faible sont ignorés (le nombre est arrondi) afin de pouvoir faire entrer le nombre obtenu dans les 64 bits disponibles. Avec de très grands nombres, l'opération d'arrondi peut conduire à des résultats surprenants :
show 2 ^ 60 + 1 = 2 ^ 60 => true
Des opérations avec des nombres plus petits peuvent aussi produire des résultats surprenants lorsqu'elles comprennent certaines fractions puisque toutes les fractions ne peuvent pas être représentées exactement en notation décimale. Il y a donc automatiquement un calcul d'arrondi. Par exemple :
show 1 / 6 + 1 / 6 + 1 / 6 + 1 / 6 + 1 / 6 + 1 / 6 => 0.9999999999999999 show 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 + 1 / 9 => 1.0000000000000002
Toute opération produisant les valeurs spéciales « infini » ou « pas un nombre » génère une erreur d'exécution.
La notation scientifique
NetLogo utilise la « notation scientifique » pour afficher les très grands et très petits nombres en virgule flottante. Exemples :
show 0.000000000001 => 1.0E-12 show 50000000000000000000 => 5.0E19
On reconnaît les nombres affichés en notation scientifique par la présence de la lettre E (E pour « exposant »). Ce E signifie « fois 10 puissance », donc, par exemple, 1.0E-12 signifie « 1.0 fois 10 puissance -12 » :
show 1.0 * 10 ^ -12 => 1.0E-12
Vous pouvez aussi utiliser la notation scientifique dans le code NetLogo que vous écrivez :
show 3.0E6 => 3000000 show 8.123456789E6 => 8123456.789 show 8.123456789E7 => 8.123456789E7 show 3.0E16 => 3.0E16 show 8.0E-3 => 0.0080 show 8.0E-4 => 8.0E-4
Ces exemples montrent que les nombres ayant une partie décimale sont affichés en notation scientifique si l'exposant est inférieur à -3 ou supérieur à 6. Les nombres sortant du domaine des nombres entiers (n'appartenant pas à l'intervalle -9007199254740992 +9007199254740992 ou +/-2^53) sont toujours affichés en notation scientifique :
show 2 ^ 60 => 1.15292150460684698E18
Quand vous écrivez un nombre, la lettre E peut être en majuscule ou en minuscule. Quand il affiche un nombre, NetLogo utilise toujours la majuscule E :
show 4.5e20 => 4.5E20
La précision en virgule flottante
Puisqu'en NetLogo les nombres sont sujets aux limitations dues à la manière dont les nombres en virgule flottante sont représentés en binaire, vous pouvez obtenir des réponses qui sont légèrement imprécises. Par exemple :
show 0.1 + 0.1 + 0.1 => 0.30000000000000004 show cos 90 => 6.123233995736766E-17
C'est une conséquence inhérente à l'arithmétique en virgule flottante. On la trouve dans tous les langages de programmation qui utilisent les nombres en virgule flottante.
Si vous avez à faire à des quantités demandant une précision absolue, par exemple des francs et des centimes, une technique courante consiste à n'utiliser que des entiers (les centimes) en interne, puis à diviser le résultat par 100 pour obtenir un affichage en francs.
Si vous devez utiliser des nombres en virgule flottante, alors, dans certaines situations, vous devrez remplacer des tests d'égalité stricte tels que if x = 1 [ ... ] par des tests qui tolèrent une légère imprécision, par exemple if abs (x - 1) < 0.0001 [ ... ].
Enfin, la primitive precision est utile pour arrondir les nombres en vue d'un affichage. Les moniteurs NetLogo arrondissent aussi les nombres qu'ils affichent avec une précision (un nombre de décimales) qui est configurable.
Les nombres aléatoires utilisés par NetLogo sont ce qu'il est convenu d'appeler des nombres « pseudo-aléatoires ». (Ce qui est typique en programmation.) Cela signifie qu'ils paraissent aléatoires, alors qu'ils sont en réalité générés par un processus déterministe. « Déterministe » veut dire que vous obtenez à chaque fois les mêmes résultats si vous commencez avec la même « valeur de départ » (appelée aussi parfois « germe » ou « amorce ). Nous allons expliquer dans quelques instants ce que nous entendons par «valeur de départ».
Dans le domaine de la modélisation scientifique, les nombres pseudo-aléatoires sont absolument nécessaires. Car il est important que les résultats d'une expérience scientifique soient reproductibles – afin que chacun puisse essayer par lui-même et obtienne les mêmes résultats que vous. Puisque NetLogo utilise les nombres pseudo-aléatoires, les « expériences » que vous faites avec ses modèles sont reproductibles par d'autres.
Voici comment cela fonctionne. Le générateur de nombres aléatoires de NetLogo peut être initialisé avec une certaine « valeur de départ », qui peut être n'importe quel nombre entier. Une fois le générateur « initialisé » avec la commande random-seed, il produit toujours la même séquence de nombres aléatoires à partir de la même « valeur de départ » donnée. Par exemple, si vous exécutez ces commandes :
random-seed 137 show random 100 show random 100 show random 100
Vous obtiendrez toujours les nombres 95, 7, et 54, dans cet ordre.
Notez toutefois que vous n'avez la garantie d'obtenir les mêmes suites de nombres que si vous utilisez la même version de NetLogo. Il peut arriver que, quand nous développons une nouvelle version de NetLogo, le générateur de nombres aléatoires change. (Pour le moment nous utilisons un générateur appelé « Tornade de Mersenne » (Mersenne Twister).)
Pour créer un nombre convenant à l'amorçage aléatoire du générateur de nombres aléatoires, autrement dit une « valeur de départ », il faut utiliser le reporter new-seed. Ce reporter crée et retourne une « amorce » impartialement choisie dans tout l'ensemble des amorces possibles, en se basant sur la date et l'heure courante du système. Il ne retourne jamais deux fois la même amorce.
Exemple de code : "Random Seed Example"
Si vous ne fournissez pas l'amorce aléatoire vous-même, NetLogo lui donne une valeur basée sur la date et l'heure courantes du système. Mais il n'y a aucun moyen de savoir quelle amorce aléatoire est choisie. C'est pourquoi, si les simulations faites avec votre modèle doivent être reproductibles, vous devez fournir vous-même cette amorce aléatoire avec random-seed.
Les primitives NetLogo qui ont random dans leurs noms (random, random-float, etc.) ne sont pas les seules à utiliser des nombres pseudo-aléatoires. Bien d'autres opérations font aussi des choix aléatoires. Par exemples, les agents des ensembles d'agents sont toujours rangés dans un ordre aléatoire, les primitives one-of and n-of choisissent les agents au hasard, la commande sprout crée des tortues dont les couleurs et les orientations sont choisies au hasard, et le reporter downhill choisit un patch au hasard quand il y a deux ou plusieurs patches avec la même valeur. Tous ces choix aléatoires sont également gouvernés par l'amorce aléatoire, c'est ce qui fait que les simulations sont reproductibles.
En plus des nombres entiers et des nombres en virgule flottante uniformément distribués générés par les primitives random et random-float, NetLogo offre encore d'autres distributions aléatoires. Voyez les entrées random-normal, random-poisson, random-exponential, et random-gamma du dictionnaire NetLogo.
Le code exécuté par les boutons ou par le Centre de commande utilise le générateur de nombres aléatoires principal.
Le code placé dans les moniteurs utilise un générateur aléatoire auxiliaire. De cette manière, même si un moniteur exécute une opération qui utilise les nombres aléatoires, le résultat de la simulation n'en est pas affecté. Il en va de même pour le code des curseurs.
Il est parfois nécessaire de spécifier explicitement qu'une section de code ne doive pas influencer l'état du générateur aléatoire principal de manière à ce que le résultat de la simulation n'en soit pas affecté. La commande with-local-randomness est justement là pour traiter ces cas. Voir cette entrée dans le Dictionnaire NetLogo pour plus d'informations.
Les images des tortues NetLogo sont des images vectorielles. Elles sont construites à partir des formes géométriques de base que sont les carrés, les cercles et les courbes plutôt qu'à partir d'une grille de pixels. On peut agrandir, rétrécir et/ou faire pivoter ces formes vectorielles. NetLogo place les formes vectorielles d'échelle 1, 1.5 et 2 dans des caches bitmap de manière à accélérer la vitesse d'exécution du programme.
L'image d'une tortue, stockée dans sa variable shape, peut être spécifiée au moyen de la commande set.
À leur création, les nouvelles tortues ont une forme par défaut appelée default. La primitive set-default-shape permet de remplacer la forme de tortue par défaut par une autre, ou de spécifier une forme de tortue par défaut différente pour chaque race de tortue.
La primitive shapes retourne une liste des différentes formes de tortues actuellement disponibles dans le modèle. Cette liste est utile si, par exemple, vous voulez affecter à une tortue une forme choisie au hasard :
ask turtles [ set shape one-of shapes ]
Utilisez l'Éditeur de formes de tortue "Turtle Shapes Editor" pour créer vos propres formes de tortue, pour ajouter à votre modèle des formes provenant de notre bibliothèque de formes ou pour transférer des formes d'un modèle à l'autre. Consultez le chapitre Editeur de formes de ce manuel pour plus d'informations.
L'épaisseur des lignes utilisées pour dessiner les formes vectorielles
peut être contrôlée au moyen de la primitive __set-line-thickness
.
Exemples de code : "Breeds and Shapes Example", "Shape Animation Example".
Les formes des liens sont semblables aux formes des tortues, vous devez simplement utiliser l'Éditeur de formes de liens "Link Shape Editor" pour les créer et les éditer. Les formes des liens sont constituées de 0, 1, 2 ou 3 lignes qui peuvent avoir différents motifs et un indicateur d'orientation composé des mêmes éléments que les formes des tortues. Les liens possèdent aussi une variable shape qui peut recevoir n'importe quelle forme de lien se trouvant dans le modèle. Par défaut, les liens ont la forme default, toutefois, vous pouvez en changer en utilisant la primitive set-default-shape. Le reporter link-shapes retourne toutes les formes de liens incluses dans le modèle courant.
L'épaisseur des lignes d'une forme de lien est contrôlée par la variable de type lien thickness .
Dans de nombreux modèles NetLogo, le temps de la simulation est décomposé en pas discrets appelés cycles ("ticks"). NetLogo possède un compteur de cycles intégré, ce qui permet toujours de savoir combien de pas la simulation a déjà effectués.
La valeur actuelle du compteur de cycles est affichée dans bord supérieur de la vue. (Vous pouvez utiliser le bouton "Settings" pour cacher cet affichage ou pour remplacer son étiqiuette "ticks" par une autre dénomination.)
Dans le code, il faut utiliser le reporter ticks pour obtenir la valeur courante du compteur de cycles. La commande tick incrémente le compteur de cycles d'une unité. La commande clear-all remet le compteur de cycles à zéro. Si vous voulez remettre ce compteur à zéro sans tout effacer, utilisez la commande reset-ticks.
Si votre modèle est conçu pour une mise à jour basée sur les cycles, alors la commande tick est généralement aussi utilisée pour mettre à jour l'affichage de la vue. Voir ci-dessous Les mises à jour de la Vue pour plus d'informations.
Nous vous conseillons d'utiliser la commande tick une fois que tous les agents ont terminé leurs mouvements et leurs actions, mais avant que vous ne mettiez à jour les graphiques ou ne fassiez des statistiques. De cette manière, si le code pour l'affichage des valeurs ou pour le calcul se sert du compteur de cycles, il utilisera la nouvelle valeur de ce compteur, reflétant le fait que le cycle est maintenant terminé. Exemple :
to go ask turtles [ move ] ;; les tortues se déplacent ask patches [ grow ] ;; les patches font pousser quelque chose tick ;; on actualise le compteur de cycles dessine-graphes ;; on affiche les résultats end
to dessine-graphes plotxy ticks count turtles end
En mettant tick avant dessine-graphes, le code pilotant le traceur de courbes reçoit la bonne valeur du compteur de cycles lorsqu'il fait appel au reporter ticks.
Dans la plupart des modèles, le compteur de cycle commence à 0 et est incrémenté par pas de 1, passant d'un nombre entier à un nombre entier. Mais le compteur de cycles peut aussi prendre des valeurs intermédiaires en virgule flottante .
Pour faire avancer le compteur de cycles d'un nombre fractionnaire, il faut utiliser la commande tick-advance qui demande une entrée numérique spécifiant de combien faire avancer le compteur de cycles.
Une utilisation typique des cycles fractionnaires est l'approximation d'un mouvement continu ou courbe. Voyez par exemple les modèles "GasLab" de la Bibliothèques des modèles (dans la section "Chemistry & Physics"). Ces modèles calculent le moment exact où un événement particulier devra avoir lieu, puis avancent le compteur de cycle exactement sur ce moment.
En NetLogo, la Vue ("View") permet de voir sur l'écran de l'ordinateur les agents de votre modèle. Quand les agents se déplacent ou se transforment, vous les voyez se déplacer et se transformer dans la Vue.
Bien entendu, vous ne pouvez pas réellement voir vos agents. La Vue n'est qu'une image dessinée par NetLogo, montrant à quoi ressemblent vos agents à un instant donné. Une fois cet instant passé et vos agents quelque peu déplacés et/ou transformés, cette image doit être redessinée pour refléter le nouvel état du monde. Redessiner la vue est appelé mise à jour (updating) de la vue.
Mais quand la Vue doit-elle être mise à jour? Cette section explique comment NetLogo décide quand mettre à jour la Vue, et comment vous pouvez influencer le moment de cette mise à jour.
NetLogo offre deux modes de mise à jour, les mises à jour « continues » ("continuous") et les mises à jour « par cycle » ("on ticks"). Vous pouvez passer d'un type de mise à jour à l'autre au moyen du menu placé dans le haut du panneau "Interface".
Les mises à jours continues sont le réglage par défaut quand vous démarrez NetLogo ou quand vous commencez un nouveau modèle. Cependant, presque tous les modèles de notre Bibliothèque des modèles utilisent la mise à jour par cycle.
Les mises à jour continues sont les plus simples à mettre en oeuvre, mais les mises à jours par cycles offrent plus de contrôle sur le moment et la fréquence des mises à jours.
Le moment exacte de la mise à jour est important car il détermine ce que vous voyez à l'écran. Si une mise à jour se fait à un moment inapproprié, vous observerez certainement quelque chose d'inattendu – peut-être quelque chose de confus ou de trompeur.
La fréquence des mises à jour est aussi importante, car les mises à jour prennent du temps. Plus NetLogo passe de temps à mettre la Vue à jour, plus la simulation ralentit. Avec moins de mises à jour, la simulation tourne plus rapidement.
Les mises à jour continues sont très simples. Avec les mises à jour continues, NetLogo met la Vue à jour un certain nombre de fois par seconde – par défaut, 50 fois par seconde quand le curseur vitesse se trouve à sa position par défaut "normal speed", au milieu de son échelle.
Si vous déplacez le curseur sur une vitesse inférieure (vers la gauche), NetLogo fait une mise à jour de la Vue plus de 50 fois par seconde, ralentissant effectivement la déroulement de la simulation. À une vitesse supérieure à la moyenne (curseur vers la droite), NetLogo met à jour moins de 50 fois par seconde, ce qui permet à la simulation de « tourner» plus rapidement. Pour les vitesses les plus grandes, les mises à jour peuvent être séparées par plusieurs secondes.
Aux vitesses les plus lentes, NetLogo fait des mises à jour si fréquentes que vous voyez les agents se déplacer (ou changer de couleur, etc.) les uns après les autres.
Si vous avez besoin de désactiver temporairement les mises à jour continues, utilisez la commande no-display. La commande display réactive les mises à jour et du même coup force une mise à jour immédiate (à moins que l'utilisateur n'ait fortement accéléré la simulation à l'aide du curseur vitesse).
Comme nous l'avons déjà signalé ci-dessus dans la section Le compteur de cycles, dans bien des modèles NetLogo, le temps qui passe est divisé en pas discrets appelés cycles (ticks). Et il y a typiquement une mise à jour de la Vue par cycle, entre deux cycles successifs. C'est le comportement par défaut pour les mises à jour basées sur les cycles.
Si vous voulez des mises à jour supplémentaires, vous devez les forcer à l'aide de la commande display. (La mise à jour peut être sautée si l'utilisateur accélère fortement la simulation avec le curseur vitesse.)
Il n'est pas nécessaire d'utiliser le compteur de cycles pour avoir une mise à jour basée sur les cycles. Si le compteur de cycles n'avance jamais, la vue ne sera mise à jour que lorsqu'on utilise la commande display.
Si vous déplacez le curseur vitesse vers la droite (vitesse plus élevée), NetLogo sautera un certain nombre de mises jour qui auraient normalement dû se faire. Le déplacement du curseur vitesse vers la gauche (vitesse plus lente) n'augmente pas le nombre de mises à jour, il oblige plutôt NetLogo à faire une pause après chaque mise à jour. Plus basse est la vitesse, plus longues sont les pauses.
Même en régime de mises à jour basées sur les cycles, la vue est aussi mise à jour chaque fois qu'un bouton de l'interface remonte en position déclenché (aussi bien les boutons une-fois que les boutons pour-toujours) et aussi chaque fois qu'une commande passée dans le Centre de commande termine son travail. C'est pourquoi il n'est pas nécessaire d'ajouter une commande display aux boutons une-fois qui ne font pas avancer le compteur de cycles. Par contre, de nombreux boutons pour-toujours qui ne font pas avancer le compteur de cycles ont besoin d'utiliser la commande display. Un exemple de la Bibliothèque des modèles qui le démontre est le modèle "Life" (dans la section "Computer Science >> Cellular Automata"). Les boutons pour-toujours qui permettent à l'utilisateur de dessiner dans la vue utilisent la commande display pour permettre à cet utilisateur de voir ce qui se passe, même si le compteur de cycle n'est pas incrémenté.
Les avantages des mises à jour basées sur les cycles sur les mises à jour continues sont :
Comme indiqué ci-dessus, la plus grande partie des modèles de la Bibliothèque des modèles utilisent maintenant les mises à jour basées sur les cycles.
Les mises à jour continues sont utiles pour les modèles dont la simulation n'est pas divisée en courtes phases discrètes. "Termites" en est un exemple dans la Bibliothèques des modèles. (Mais voyez aussi le modèle "State Machine Example" qui montre comment re-coder le modèle "Termites" en utilisant les cycles).
Même avec les modèles qui sont normalement conçus pour une mise à jour basée sur les cycles, il peut être utile de basculer temporairement sur une mise à jour continue au cours de la mise au point du modèle. Observer ce qui se passe dans le cycle plutôt que de d'observer le résultat du cycle complet peut aider à résoudre des problèmes de fonctionnement. Après avoir basculé en mode mises à jour continues, vous pouvez utiliser le curseur vitesse pour ralentir le modèle jusqu'à ce que vous voyez les agents se déplacer les uns après les autres. N'oubliez pas de revenir ensuite en mode de mises à jour basées sur les cycles lorsque vous avez terminé car le mode de mise à jour est enregistré avec le modèle.
Les fonctionnalités de traçage de courbes de NetLogo permettent de créer et d'afficher des graphiques qui sont sensés aider à comprendre ce qui se passe au cours de la simulation.
Avant de pouvoir dessiner des graphiques, vous devez créer un ou plusieurs traceurs de courbes (plots), ou plus simplement traceurs, dans le panneau "Interface". Chaque traceur doit avoir son propre nom, nom qui sera utilisé dans le code développé dans le panneau "Procedures" pour faire référence au traceur correspondant.
Voir le Guide de l'Interface pour des informations supplémentaires concernant l'utilisation et le paramétrage des traceurs dans le panneau "Interface".
Sélectionner un traceur
Si le modèle ne comporte qu'un traceur, vous pouvez directement commencer de dessiner les courbes. Mais s'il en comporte plusieurs, vous devez spécifier quel traceur vous voulez utiliser. À cet effet, utilisez la commande set-current-plot avec le nom du traceur entre guillemets doubles (chaîne de caractères) comme ceci :
set-current-plot "Distance en fonction du temps"
Vous devez fournir le nom du traceur exactement comme vous l'aviez orthographié quand vous avez créé le traceur. Notez que si, par la suite, vous modifiez le nom du traceur, vous devez aussi modifier tous les appels de la primitive set-current-plot de votre modèle afin qu'elle reçoive le nouveau nom. (Copie-coller peut se révéler utile ici.)
Sélectionner un crayon
Quand un nouveau traceur est créé, il ne possède qu'un seul crayon. Et vous pouvez immédiatement commencer de dessiner une courbe si le traceur actif ne possède qu'un seul crayon.
La comparaison de l'évolution de plusieurs paramètres au cours de la simulation présentant souvent un grand intérêt, les traceurs de NetLogo peuvent avoir plusieurs crayons afin de pouvoir dessiner plusieurs courbes en même temps sur le même graphique. Vous pouvez donc ajouter des crayons au crayon par défaut dans l'éditeur du traceur en utilisant les contrôles de la section "Plot Pens" située au bas de la fenêtre d'édition. Chaque crayon doit alors avoir un nom différent, nom qui sera utilisé pour faire référence à ce crayon dans le code développé dans le panneau "Procedures".
Pour un traceur à plusieurs crayons, il faut spécifier quel crayon doit être utilisé pour dessiner la courbe. Si vous ne spécifiez pas de crayon, le traçage de la courbe se fera avec le premier crayon du traceur. Pour dessiner avec un autre crayon, il faut appeler la procédure set-current-plot-pen avec le nom du crayon placé entre guillemets doubles, comme ici :
set-current-plot-pen "distance"
Dessiner des points
Les deux commandes de base pour dessiner effectivement (faire apparaître quelque chose dans le traceur) sont plot et plotxy.
Avec plot, vous ne devez spécifier que la valeur y (valeur du paramètre fourni par la simulation) que vous voulez représenter. La valeur x (qui représente généralement le cours du temps) est automatiquement 0 pour le premier point, 1 pour le deuxième, et ainsi de suite. (C'est le cas si l'« intervalle » du crayon vaut 1, sa valeur par défaut. Mais cet intervalle est modifiable.)
La commande plot est particulièrement utile quand vous voulez que le modèle dessine un nouveau point à chaque cycle. Exemple :
to setup ... plot count turtles end
to go ... plot count turtles end
Notez que dans cet exemple, si nous faisons dessiner le graphique par les deux procédures setup et go, c'est parce que nous voulons que notre graphe comprenne aussi l'état initial de la simulation. Nous dessinons à la fin de la procédure go et non au début, car nous voulons que le graphique soit à jour après que le code associé au bouton go ait terminé son travail.
Si vous avez besoin de spécifier les deux valeurs x et y pour le point que vous voulez dessiner, vous devez utiliser la procédure plotxy.
Exemple de code : "Plotting Example"
D'autres types de graphes
Par défaut, les crayons des traceurs NetLogo dessinent en mode ligne de manière à ce que les points du graphique soient reliés par des lignes.
Si vous voulez déplacer le crayon sans laisser une trace, vous pouvez utiliser la commande plot-pen-up. Après exécution de cette commande, les commandes plot et plotxy déplacent le crayon mais ne dessinent rien. Une fois le crayon à l'endroit voulu, utilisez plot-pen-down pour l'abaisser, prêt à dessiner.
Si vous voulez ne dessiner que des points à la place des lignes, ou si vous voulez dessiner des barres à la place des lignes ou des points, vous devez changer le mode du crayon du traceur. Trois modes sont disponibles : "line" (ligne), "bar" (barre) et "point", "line" étant le mode par défaut.
Normalement, le mode d'un crayon est spécifié ou modifié dans l'éditeur du traceur. Cette action change son mode par défaut. Mais il est aussi possible de changer le mode du crayon temporairement en utilisant la commande set-plot-pen-mode. Cette commande demande un nombre en entrée: 0 pour le mode ligne, 1 pour le mode barre et 2 pour le mode point.
Les histogrammes
Un histogramme est un type de graphique spécial qui montre combien de fois certaines valeurs, ou des valeurs dans certains intervalles, apparaissent dans une ensemble de nombres générés par votre modèle.
Supposons par exemple que les tortues de votre modèle ont une variable age. Vous pouvez créer un histogramme montrant la distribution des âges des tortues avec la commande histogram, comme ceci :
histogram [age] of turtles
Les nombres que vous voulez représenter au moyen de l'histogramme ne doivent pas obligatoirement provenir d'un ensemble d'agents; ils peuvent provenir de n'importe quelle liste de nombres.
Notez que l'utilisation de la commande histogram ne met pas automatiquement le crayon courant du traceur en mode barre. Si vous voulez des barres, vous devez mettre ce crayon en mode barre vous-mêmes. (Comme cela a déjà été signalé, vous pouvez changer le mode par défaut du crayon du traceur en éditant le traceur dans le panneau "Interface".)
La largeur des barres d'un histogramme est contrôlée par l'intervalle du crayon du traceur. L'intervalle par défaut peut être spécifié en éditant le traceur dans le panneau "Interface", mais vous pouvez aussi le modifier temporairement avec les commandes set-plot-pen-interval ou set-histogram-num-bars. Si vous utilisez cette dernière commande, NetLogo ajuste la valeur de l'intervalle de manière à ce que le nombre de barres spécifié couvre l'ensemble courant des valeurs de x.
Exemple de code : "Histogram Example"
Effacer et réinitialiser
Vous pouvez effacer le contenu du traceur courant avec la commande clear-plot, ou effacer le contenu de tous les traceurs avec la commande clear-all-plots. La commande clear-all efface aussi tous les traceurs en plus d'effacer tout ce qu'il a d'autre dans le modèle.
Si vous ne voulez effacer que le tracé fait par le crayon courant, utilisez la commande plot-pen-reset.
L'effacement du contenu d'un traceur ou la réinitialisation d'un crayon ne se limite pas à la suppression des données affichées. Ces actions réinitialisent aussi le traceur ou le crayon à leurs réglages par défaut, tels qu'ils ont été spécifiés dans le panneau "Interface" lors de la création du traceur ou lors de sa dernière édition. C'est pourquoi les effets des commandes telles que set-plot-x-range et set-plot-pen-color ne sont que temporaires.
Le dessin automatique
Par défaut, tous les traceurs de NetLogo ont la fonctionnalité de dessin automatique "autoplotting" activée à leur création. Cela signifie que si le modèle essaie de dessiner un point qui sort du domaine d'affichage courant, l'échelle du graphique diminue le long d'un ou de deux axes afin que le nouveau point soit visible.
Dans l'idée que les échelles ne doivent pas changer chaque fois qu'un point est ajouté, elles laissent un espace supplémentaire chaque fois qu'elle croissent. Cet espace s'accroît de 25% horizontalement et de 10% verticalement si nécessaire.
Si vous voulez désactiver cette fonctionnalité, éditez le traceur et désélectionnez le bouton "Autoplot". Pour le moment, il n'est pas possible de désactiver cette fonctionnalité pour un seul axe; elle s'applique toujours aux deux axes.
Les crayons de traceur temporaires
La plupart des graphiques peuvent être dessinés avec un nombre constant de crayons. Mais certains ont des besoins plus complexes et il serait intéressant que le nombre de crayons disponibles pour les dessiner puisse changer en fonction de certaines conditions. C'est pour des situations de ce genre que NetLogo offre la possibilité de créer des crayons « temporaires » à partir du code, puis de les utiliser pour dessiner. Ces crayons sont appelés temporaires parce qu'ils disparaissent quand le contenu de traceur est effacé (avec les commandes clear-plot, clear-all-plots ou clear-all).
Pour créer un crayon temporaire, utilisez la commande create-temporary-plot-pen. Une fois le crayon créé, vous pouvez l'utiliser comme n'importe quel crayon ordinaire. Par défaut, le nouveau crayon est abaissé, sa couleur est noire, son intervalle est de 1 et son mode de tracé est ligne. Tous ces paramètres sont modifiables par des commandes (voir la section Les Traceurs du Dictionnaire NetLogo).
Afficher la légende
Vous pouvez afficher la légende des courbes d'un traceur en cochant le bouton "Show legend" dans la fenêtre d'édition du traceur. Si vous ne voulez pas voir apparaître un certain crayon dans la légende, il faut désactiver le bouton "Show in Legend" de ce crayon.
Conclusion
Tous les aspects du système des traceurs de courbes de NetLogo n'ont pas été abordés ici. Consultez la section Les Traceurs du Dictionnaire NetLogo pour en savoir plus sur d'autres commandes et reporters s'occupant des graphiques et des traceurs.
De nombreux exemples de modèles de la Bibliothèque des modèles montrent diverses techniques avancées de traçage de graphiques. Consultez aussi les exemples de code suivants :
Exemples de code : "Plot Axis Example", "Plot Smoothing Example" et "Rolling Plot Example"
Pour écrire une chaîne de caractères en NetLogo, il faut l'entourer de guillemets doubles anglais, comme ceci : "Bonjour le monde" .
La chaîne vide est obtenue en ne mettant rien (pas même un espace) entre ces guillemets, comme ceci : "".
La plupart des primitives dédiées aux listes fonctionnent aussi avec les chaînes :
but-first "string" => "tring" but-last "string" => "strin" empty? "" => true empty? "string" => false first "string" => "s" item 2 "string" => "r" last "string" => "g" length "string" => 6 member? "s" "string" => true member? "rin" "string" => true member? "ron" "string" => false position "s" "string" => 0 position "rin" "string" => 2 position "ron" "string" => false remove "r" "string" => "sting" remove "s" "strings" => "tring" replace-item 3 "string" "o" => "strong" reverse "string" => "gnirts"
Quelques primitives sont spécifiques aux chaînes, telles que : is-string?, substring et word :
is-string? "string" => true is-string? 37 => false substring "string" 2 5 => "rin" word "tur" "tle" => "turtle"
Les chaînes peuvent être comparées au moyen des opérateurs = , != , < , > , <= et >= .
Si vous avez besoin de mettre un caractère spécial dans une chaîne, utilisez les séquences d'échappement suivantes :
Cette section est consacrée l'affichage des données sur l'écran. Ce qui est affiché à l'écran peut aussi être enregistré par la suite dans un fichier au moyen de la commande export-output. Si vous avez besoin d'une méthode plus souple pour écrire des données dans un fichier externe, voyez la section suivante, E/S de fichiers.
Les commandes NetLogo de base pour générer une sortie à l'écran sont print, show, type et write. Ces commandes permettent d'écrire dans la zone d'affichage du Centre de commande.
Pour une description détaillée de ces commandes, consultez leurs entrées dans le Dictionnaire NetLogo. En voici un résumé :
Un modèle NetLogo peut aussi avoir en option une « zone de sortie » "Output" dans son panneau "Interface", zone distincte et indépendante du Centre de Commande. Pour y envoyer une sortie (plutôt que dans le Centre de commande), utilisez les commandes output-print, output-show, output-type et output-write .
Le contenu de la zone de sortie peut être effacé avec la commande clear-output et enregistré dans un fichier avec la commande export-output. Le contenu de la zone de sortie est aussi sauvegardé par la commande export-world. La commande import-world efface la zone de sortie et remplace son contenu par les données du fichier importé. Il faut toutefois noter que de grandes quantités de données envoyées et affichées dans la zone de sortie peuvent accroître considérablement la taille des mondes exportés.
Si vous utilisez les commandes output-print, output-show, output-type, output-write, clear-output ou export-output dans un modèle qui n'a pas de zone de sortie séparée, ces primitives agissent alors sur la zone d'affichage du Centre de commande.
NetLogo possède un ensemble de primitives qui donnent la possibilité d'interagir avec des fichiers externes. Elles commencent toutes par le préfixe file-.
Lorsqu'on travaille avec des fichiers , les deux principaux modes sont la lecture (reading) et l'écriture (writing). La différence réside dans la direction du flux de données. Quand vous lisez une information dans un fichier, les données qui sont stockées dans le fichier « coulent » dans votre modèle. D'un autre côté, l'écriture permet aux données de « couler » de votre modèle vers et dans un fichier.
Quand un modèle NetLogo tourne en tant qu'applet dans un navigateur internet, il ne peut que lire les données des fichiers qui se trouvent sur le serveur et dans le même répertoire que le fichier du modèle. Les applets ne peuvent pas écrire dans un fichier.
Quand on travaille avec des fichiers, il faut toujours commencer par utiliser la primitive file-open. Cette dernière spécifie le fichier avec lequel vous voulez travailler. Aucune des autres primitives dédiées aux fichiers ne fonctionne si le fichier n'a pas d'abord été ouvert.
Immédiatement après cette primitive, vous devez utiliser l'une des primitives file- suivantes qui permettent de spécifier dans quel mode, lecture ou écriture, se trouvera le fichier jusqu'à ce qu'il soit refermé. Pour passer d'un mode à l'autre, il faut fermer puis ré-ouvrir le fichier.
Les primitives de lecture comprennent file-read, file-read-line, file-read-characters et file-at-end?. Notez qu'il faut que le fichier existe déjà avant que vous puissiez l'ouvrir en mode lecture.
Exemple de code : "File Input Example"
Les primitives pour écrire dans un fichier ressemblent à celles utilisées pour écrire dans le Centre de commande, sauf qu'ici, la sortie est enregistrée dans un fichier. Elles comprennent file-print, file-show, file-type et file-write. Notez que vous ne pouvez jamais « écraser » (overwrite) des données. En d'autres mots, si vous voulez écrire des données dans un fichier qui en contient déjà, ces nouvelles données seront ajoutées à la fin du fichier. (Si vous voulez écraser le contenu d'un fichier, utilisez d'abord file-delete pour l'effacer puis ouvrez-le pour y écrire.)
Exemple de code : "File Output Example"
Quand vous avez fini de travaller avec un fichier, vous devez utiliser la commande file-close pour terminer votre session avec ce fichier et le refermer. Si par la suite vous voulez supprimer ce fichier, utilisez la primitive file-delete pour l'effacer. Si plusieurs fichiers sont ouverts en même temps et qu'il faut en fermer quelques-uns, il faut d'abord en sélectionner un avec file-open avant de le fermer avec file-close, manoeuvre à répéter pour tous les fichiers à fermer, comme dans l'exemple ci-dessous :
;; Ouverture de 3 fichiers file-open "myfile1.txt" file-open "myfile2.txt" file-open "myfile3.txt"
;; et maintenant fermeture ciblée des 3 fichiers file-close ;; ferme le fichier myfile3.txt qui est le fichier courant file-open "myfile2.txt" file-close file-open "myfile1.txt" file-close
Ou, si vous savez que vous voulez fermer tous les fichiers ouverts, il suffit d'utiliser file-close-all.
Deux primitives valent encore la peine d'être signalées, ce sont file-write et file-read. Ces primitives ont été tout spécialement développées pour faciliter l'enregistrement et la lecture des constantes NetLogo telles que les nombres, les listes, les booléens et les chaînes. file-write écrit toujours les variables de manière telle que file-read puisse les lire et les interpréter correctement.
file-open "myfile.txt" ;; Ouvrir le fichier en écriture ask turtles [ file-write xcor file-write ycor ] file-close
file-open "myfile.txt" ;; Ouvrir le fichier en lecture ask turtles [ setxy file-read file-read ] file-close
Code Examples : "File Input Example" et "File Output Example"
À l'utilisateur de choisir
Les primitives user-directory, user-file et user-new-file sont utiles quand vous voulez que l'utilisateur puisse choisir un répertoire ou un fichier dans lequel ou avec lequel travailler.
Cette section décrit comment enregistrer une animation représentant une simulation NetLogo sous la forme d'une séquence vidéo QuickTime.
Il faut commencer par l'utilisation de la commande movie-start pour créer une nouvelle vidéo. Le nom de fichier que vous fournissez doit se terminer par .mov , extension servant à identifier les séquences vidéos QuickTime.
Pour ajouter une image à la séquence vidéo, utilisez movie-grab-view si vous voulez enregistrer uniquement le contenu de la Vue courante (l'aspect du monde des tortues) ou movie-grab-interface si vous voulez enregistrer la totalité du panneau "Interface". À l'intérieur d'une séquence vidéo, vous ne pouvez utiliser qu'une seule de ces deux primitives : vous ne pouvez pas mélanger les deux types de prises de vues.
Quand vous avez ajouté toutes les images voulues, refermez la séquence vidéo avec movie-close.
;; exporter une vidéo de 30 images de la vue setup movie-start "out.mov" movie-grab-view ;; montrer l'état initial repeat 30 [ go movie-grab-view ] movie-close
Par défaut, une vidéo est projetée à 15 images par seconde. Pour faire une vidéo avec une fréquence d'images différente, appelez la primitive movie-set-frame-rate avec un autre nombre d'images par seconde. Il faut spécifier le nombre d'images par seconde après l'appel de movie-start mais avant d'enregistrer la première image.
Pour connaître la fréquence d'images de la séquence vidéo ou le nombre d'images déjà enregistrées, appelez la primitive movie-status qui retourne une chaîne décrivant le statut de la vidéo courante.
Pour supprimer une séquence vidéo et effacer le fichier vidéo, appelez la primitive movie-cancel.
Exemple de code : "Movie Example"
Les séquences vidéos faites avec NetLogo sont des fichiers QuickTime non compressés. Pour projeter une vidéo QuickTime, vous pouvez utiliser le QuickTime Player téléchargeable gratuitement sur le site d'Apple.
N'étant pas compressées, les séquences vidéos peuvent occuper beaucoup de place sur le disque. Vous voudrez certainement les compresser avec des programmes adéquats. Ces logiciels offrent généralement un grand choix de modes de compression. Certains modes de compression travaillent sans pertes de données alors que d'autres compriment avec pertes. Comprimer avec pertes signifie que, dans le but de réduire la taille des fichiers, certains détails des images de la vidéo sont ignorés, donc perdus. En fonction de la nature de la simulation enregistrée, vous aurez certainement tout intérêt à éviter toute perte de données (donc à utiliser un mode de compression sans pertes), par exemple dans le cas où la vue contient des détails au niveau des pixels.
QuickTime Pro est un logiciel capable de compresser des vidéos QuickTime aussi bien sur les plates-formes Mac que Windows. Sur Mac, iMovie peut aussi très bien remplir ce rôle.
Les Vues 2D et 3D montrent le monde du point de vue de l'observateur. Par défaut, l'observateur regarde le monde d'en haut, situé quelque part le long de la partie positive de l'axe Z passant par l'origine (le centre du patch 0 0). Vous pouvez modifier le point de vue de l'observateur en utilisant les commandes observateur follow, ride et watch et les commandes tortue follow-me, ride-me et watch-me. Quand il est en mode poursuite ("follow") ou chevauchée ("ride"), l'observateur se déplace avec l'agent sujet autour du monde. La différence entre ces deux modes n'est perceptible que dans la Vue 3D. Dans la Vue 3D, l'utilisateur peut modifier sa distance par rapport à l'agent au moyen de la souris. Quand l'observateur suit l'agent à une distance nulle, c'est comme s'il le chevauchait. Quand l'observateur est en mode observation ("watch"), il suit les mouvements d'une tortue des yeux sans se déplacer lui-même. Dans les deux types de Vue (2D et 3D), un spot illumine le sujet (la tortue observée) et, dans la vue 3D, l'observateur fait toujours face au sujet. Pour savoir quel agent se trouve dans la « ligne de mire » de l'observateur, vous pouvez utiliser le reporter subject.
Exemple de code : "Perspective Example"
Dans la Vue 2D, le monde de NetLogo est constitué de trois couches superposées. À la base se trouve la couche des patches, au milieu la couche de dessin et au sommet la couche des tortues. Ces deux dernières couches sont transparentes pour permettre de voir ce qu'il y a en dessous.
Le dessin est la couche (le calque) sur laquelle les tortues peuvent laisser des marques visibles. Cette couche est initialement vide et totalement transparente.
Seul l'utilisateur peut voir ce qui se trouve sur la couche de dessin, les tortues (et les patches) ne le peuvent pas. Elles ne peuvent ni « sentir » ce qui se trouve sur cette couche, ni y réagir. Le dessin n'est là que pour les utilisateurs qui le regardent.
Les tortues peuvent y dessiner et y effacer des lignes au moyen des commandes pen-down et pen-erase. Quand le crayon d'une tortue est abaissé (pen-down) ou quand ce crayon est transformé en gomme (erase), la tortue dessine (ou efface) une ligne derrière elle chaque fois qu'elle se déplace. Les lignes sont de la même couleur que la tortue. Pour ne plus dessiner (ou effacer), il suffit de lever le crayon avec pen-up.
Les lignes dessinées par la tortue ont normalement une épaisseur de 1 pixel. Si vous voulez des lignes d'une autre épaisseur, donnez à la variable pen-size une autre valeur avant de dessiner (ou d'effacer). Cette variable a toujours la valeur 1 pour les nouvelles tortues.
Pour les lignes dessinées par une tortue se déplaçant dans un mode qui ne précise pas son orientation, par exemple avec des commandes telles que setxy ou move-to, c'est le chemin le plus court obéissant à la topologie qui est dessiné.
Ci-dessous, le dessin fait par quelques tortues au-dessus d'un pavage de patches colorés de manière aléatoire. Notez comment les tortues passent au-dessus des lignes et comment les lignes couvrent les patches. Les variables pen-size des tortues ont reçu la valeur 2.
La commande stamp demande à la tortue de laisser son image sur le dessin, image qui apparaîtra lorsqu'elle se déplacera. stamp-erase efface tous les pixels du dessin placés au-dessous de la forme de la tortue.
Pour effacer tout le dessin, utilisez la commande observateur clear-drawing. (Vous pouvez aussi utiliser clear-all, qui efface en même temps aussi tout le reste.)
Importer une image
La commande observateur import-drawing permet d'importer un fichier image d'une mémoire de masse pour mettre l'image sur le dessin.
import-drawing n'est utile que pour avoir une image d'arrière-plan pour les gens qui regardent la Vue. Si vous voulez que les tortues et les patches puissent réagir par rapport à l'image, vous devez utiliser à la place import-pcolors ou import-pcolors-rgb.
Comparaison à d'autres Logo
En NetLogo, le dessin fonctionne quelque peu différemment que dans les autres Logo.
Les principales différences sont :
Les fonctionnalités de dessins non supportées par NetLogo sont :
La topologie du monde NetLogo peut avoir quatre formes possibles : un tore (torus), une boîte (box), un cylindre vertical (vertical cylinder) et un cylindre horizontal (horizontal cylinder). La topologie est contrôlée en activant ou en désactivant l'« enroulement » du monde dans les directions X et Y. Le monde par défaut est un tore, comme le furent tous les mondes NetLogo avant la version 3.1.
Un tore enroule le plan du monde dans les deux directions X et Y, autrement dit, les bords supérieur et inférieur du monde se rejoignent tout comme le font les bords gauche et droit. Donc, si une tortue se déplace au-delà du bord droit du monde, elle réapparaît au bord gauche, à la même hauteur. Il en va de même pour les bords supérieur et inférieur.
Une boîte n'a aucun enroulement. Le monde est entouré d'une bordure infranchissable et les tortues qui essaient de se déplacer au-delà des bords de ce monde ne le peuvent pas. Notez que les patches placés le long de ses bords ont moins de huit voisins, ceux des coins en ont trois, les autres cinq.
Les cylindres horizontaux et verticaux n'enroulent le plan du monde que dans une direction. Un cylindre horizontal (son axe est horizontal) s'enroule verticalement, ce qui fait que le haut du monde est connecté à la base, les bords gauche et droit sont barrés. Un cylindre vertical est l'opposé, il s'enroule horizontalement (son axe est vertical) de manière à ce que ses bords gauche et droit soient connectés alors que ses bords supérieur et inférieur sont barrés.
Exemple de code : "Neighbors Example"
Dès sa version 3.0, NetLogo offre des réglages permettant d'activer la visualisation de l'enroulement de manière à ce que, si la forme d'une tortue dépasse un bord, la partie de la forme qui dépasse le bord apparaisse sur le bord opposé. (Les tortues elles-mêmes ne sont que des points qui n'occupent aucun espace, elles ne peuvent donc pas se trouver sur deux bords du monde en même temps. Dans la vue, elles paraissent occuper un certain espace puisqu'elles y sont représentées graphiquement par leurs formes.)
L'enroulement affecte aussi l'apparence de la vue quand vous suivez une tortue. Sur un tore, où que la tortue aille, vous verrez toujours le monde entier autour d'elle.
Alors qu'avec une boîte ou un cylindre, le monde a des barrières, ce qui fait que les zones hors des frontières sont représentées en gris :
Exemples de code : "Termites Perspective Demo" (tore), "Ants Perspective Demo" (boîte)
Alors que les réglages offerts par la version 3.0 ne contrôlaient que l'apparence de l'enroulement dans la vue, NetLogo 3.1 possède des réglages qui spécifient si le monde s'enroule « pour de vrai », c'est-à-dire si les bords opposés sont réellement connectés. Ces nouveaux réglages d'enroulement déterminent la topologie du monde, autrement dit si le monde est un tore, une boîte ou un cylindre. Ils influencent le comportement et non plus seulement l'apparence visuelle du monde du modèle.
Par le passé, les auteurs de modèles devaient écrire du code spécial pour simuler un monde en boîte à l'aide de primitives spéciales « non-enroulantes ». Des versions non-enroulantes étaient disponibles pour les primitives distance(xy), in-radius, in-cone, face(xy) et towards(xy). À partir de NetLogo 3.1, ces versions ne sont plus nécessaires. C'est maintenant la topologie qui contrôle si les primitives « enroulent » ou non. Ces primitives utilisent toujours le chemin le plus court autorisé par la topologie. Par exemple, la distance du centre du patch occupant le coin inférieur gauche (min-pxcor, min-pycor) et le centre du patch occupant le coin supérieur droit (max-pxcor, max-pycor) sera la suivante, en fonction de la topologie et étant donné que les pxcor et pycor min et max sont +/-2 :
Toutes les autres primitives agissent de même pour la distance. Si vous aviez utilisé auparavant des primitives non-enroulantes, nous vous recommandons de les enlever et de modifier à la place la topologie du monde.
Plusieurs raisons devraient vous inciter à modifier vos anciens modèles pour qu'ils utilisent les différentes topologies plutôt que des primitives non-enroulantes.
Premièrement, nous pensons que puisque vous utilisez des primitives non-enroulantes, vous modélisez en fait un monde qui n'est pas un tore. Si vous utilisiez une topologie correspondant au monde que vous modélisez, NetLogo effectuerait automatiquement pour vous les tests de franchissement des frontières, ce qui devrait vous faciliter la tâche, rendre le code plus simple à comprendre et ajouter des effets visuels permettant à l'utilisateur du modèle de mieux comprendre ce que vous avez modélisé. Notez qu'avec les primitives non-enroulantes, il était même très difficile de modéliser des mondes cylindriques puisque ces primitives retournent la distance ou l'orientation alors que l'enroulement n'est pas autorisé dans certaines directions.
Vos modèles peuvent contenir des erreurs. Si vous utilisez une combinaison des deux types de primitives (non-enroulantes et enroulantes), il y aura toujours un bogue dans votre modèle, que ce soit pour une raison ou une autre (nous avons trouvés quelques bogues dans nos modèles). Ainsi, le modèle "Conductor" comparait une distance non-enroulante à une distance pour tester si la position suivante était « enroulée » autour du monde et, dans ce cas, l'électron sortait du système. C'est une manière astucieuse de résoudre le problème, mais elle présente malheureusement un défaut. Les électrons qui s'enroulent dans la direction Y quittaient aussi le système, ce qui n'était pas correct dans notre cas. La seule manière correcte de quitter le système étant d'atteindre la cathode à l'extrémité gauche du fil de fer.
Si vous supprimez les commandes non-enroulantes, la topologie n'est plus codée dans le modèle. Il est donc plus facile de tester différentes formes du monde sans un long travail de re-codage (vous pourriez avoir besoin d'ajouter quelques tests pour passer du tore à la boîte, ce qui est expliqué plus en détails dans la section "Comment convertir votre modèle").
Notez que, bien que nous ayons retiré les entrées des primitives non-enroulantes du Dictionnaire, elles sont toujours disponibles dans le langage NetLogo lui-même. Ceci dans le but de vous permettre de faire tourner les anciens modèles sans avoir besoin de les modifier.
Comment convertir votre modèle
Quand vous ouvrez pour la première fois un ancien modèle avec une version 3.1 ou supérieure, NetLogo remplace automatiquement toutes les occurrences de ( screen-edge-x ) par min-pxcor et toutes les occurrences de ( screen-edge-x ) par max-pxcor (il fait la même chose pour les primitives en Y). Bien ce que ne soit pas directement en relation avec les modifications de la topologie, vos devriez aussi réfléchir si le déplacement de l'origine du monde loin du centre de la Vue garde encore tout son sens pour votre modèle. Avant NetLogo 3.1, le monde devait être symétrique par rapport à l'origine, c'est-à-dire que le monde devait avoir une largeur et une hauteur semblables. Ce n'est maintenant plus le cas puisque vous pouvez utiliser n'importe quelle combinaison de minima et de maxima, pourvu que le point (0,0) existe et soit toujours dans le monde. Si vous ne modélisez logiquement que dans un ou deux quadrants, ou si l'utilisation exclusive des nombres positifs peut simplifier votre code, vous pouvez envisager de modifier votre modèle. Si vous aviez modélisé quelque chose qui nécessitait une grille uniforme, vous serez certainement content de vous débarrasser des astuces de programmation qui la rendaient possible dans le passé.
Exemples de code : "Lattice Gas Automaton", "Binomial Rabbits" et "Rugby"
Nous avons ajouté de nouvelles primitives à la version 3.1 de NetLogo, primitives qui sont essentielles si vous modifiez la topologie et qui restent intéressantes même si vous ne le faites pas. random-pxcor, random-pycor, random-xcor et random-ycor retournent des valeurs aléatoires entrant dans les limites minimales et maximales (x et y). Dans les anciennes versions de NetLogo, nous faisions souvent appel à l'enroulement pour placer aléatoirement les tortues à travers le monde au moyen du code setxy random-float screen-size-x random-float screen-size-y. Toutefois, si l'enroulement n'est pas autorisé dans une direction ou une autre, ce morceau de code ne marche pas (vous obtenez une erreur d'exécution si vous essayez de placer des tortues hors du monde). Quelle que soit la topologie, il est plus simple et plus direct d'utiliser setxy random-xcor random-ycor à la place.
Pour convertir un ancien modèle dans le but d'utiliser une topologie, vous devez d'abord décider quels réglages décrivent le mieux le monde modélisé. Si la réponse ne vous paraît pas immédiatement évidente en vous basant sur le mode réel (une chambre est une boîte, un fil de fer est un cylindre), voici quelques indices qui peuvent vous aider. Si quelque part dans le code vous testez les limites du monde ou si certains patches ne sont pas considérés comme des voisins des patches de l'autre bord de la Vue, il est à peu près sûr que vous n'utilisez pas un tore. Si vous testez les limites dans les deux directions X et Y, c'est une boîte, si le test est en X seulement, c'est un cylindre horizontal, s'il est en Y seulement, c'est un cylindre vertical.
Si vous aviez utilisé des primitives non-enroulantes, vous ne modélisiez certainement pas un tore. Soyez toutefois prudent avec ce critère si vous aviez utilisé un mélange de primitives enroulantes et non-enroulantes. Il se pourrait en effet que vous ayez utilisé une primitive non-enroulante pour un élément visuel alors que le reste du monde NetLogo est bien un tore.
Après avoir déterminé la topologie utilisée et l'avoir modifiée en éditant la Vue dans le panneau "Interface", vous aurez peut-être à apporter quelques petites modifications au code. Si vous avez décidé que le monde est un tore, il n'y aura probablement aucun changement à faire. Si votre modèle n'utilise que les voisins des patches et la diffusion, vous n'aurez probablement pas à faire beaucoup de modifications.
Si votre modèle contient des tortues qui se déplacent, votre prochaine étape consistera à déterminer ce qui se passe lorsqu'elles atteignent le bord du monde. Il y a quelques options usuelles : soit la tortue rebondit (systématiquement ou aléatoirement) dans le monde, soit elle quitte le système (elle meurt), soit elle est cachée. Il n'est plus nécessaire de tester le franchissement les limites du monde en utilisant les coordonnées des tortues, il suffit simplement demander à NetLogo si la tortue est sur le bord du monde. Il y a deux moyens d'y parvenir. Le plus simple est d'utiliser la primitive can-move? :
if not can-move? distance [ rt 180 ]
can-move? retourne simplement true (vrai) si la position située à distance de la tortue est encore dans le monde NetLogo, sinon elle retourne false (faux). Dans ce cas, si la tortue est sur le bord du monde, elle revient simplement par le chemin qu'elle a pris pour y aller. Vous pouvez aussi utiliser patch-ahead 1 != nobody à la place de can-move?. Si vous devez faire quelque chose de plus subtil que de simplement revenir en arrière, il peut être intéressant d'utiliser les primitives patch-at avec dx et dy.
if patch-at dx 0 = nobody [ set heading (- heading) ] if patch-at 0 dy = nobody [ set heading (180 - heading) ]
Ce morceau de code teste si la tortue heurte un mur horizontal ou vertical et la fait rebondir contre ce mur.
Dans certains modèles, la tortue meurt si elle ne peut plus avancer (elle quitte le système, comme dans "Conductor"ou "Mousetraps").
if not can-move? distance[ die ]
Si vous déplacez la tortue en utilisant setxy plutôt que forward, vous devez faire un test pour vous assurer que le patch sur lequel vous voulez mettre la tortue existe bien car setxy génère une erreur d'exécution si les coordonnées reçues sortent du monde. C'est une situation courante quand le modèle simule un plan infini et que les tortues hors de la vue doivent être simplement cachées.
let new-x new-value-of-xcor let new-y new-value-of-ycor
ifelse patch-at (new-x - xcor) (new-y - ycor) = nobody [ hide-turtle ] [ setxy new-x new-y show-turtle ]
Plusieurs modèles de la Bibliothèque des modèles utilisent cette technique, par exemple "Gravitation", "N-Bodies" et "Electrostatics" en sont de bons exemples.
En utilisant une topologie différente, vous obtenez la diffusion « pour rien » (ce qui était très difficile par le passé). Chaque patch diffuse et répartit également entre ses différents voisins la quantité spécifiée dans la variable diffuse. Si le patch possède moins de huit voisins, (ou 4 si vous utilisez diffuse4), ce qui n'a pas pu être diffusé reste dans le patch diffuseur. Cela signifie que la somme totale des valeur de cette variable patch dans le monde reste constante. Si vous avez du code spécial chargé de gérer la diffusion, vous pouvez le supprimer. Toutefois, si vous voulez que la matière diffusée déborde des limites du monde comme ce serait le cas sur un plan infini, vous devez toujours nettoyer (vider) les bords à chaque pas comme dans l'exemple "Diffuse Off Edges".
Un lien (link) est un agent qui relie deux tortues. Le deux tortues placées aux extrémités du lien sont appelées des noeuds (nodes). Le lien est toujours dessiné sous la forme d'une ligne entre les deux tortues. Les liens n'ont pas de position (des coordonnées) comme les tortues. Ils ne sont pas considérés comme se trouvant sur un patch particulier et vous ne pouvez pas déterminer la distance d'un lien à un autre point.
Il y a deux types de liens, les liens non-orientés (undirected) et les liens orientés (directed). Un lien orienté « sort » d'un noeud pour « entrer » dans un autre noeud ou « vient » d'un noeud pour aller « vers » un autre noeud. La relation parent-enfant peut être modélisée à l'aide d'un lien orienté. Un lien non-orienté est vu de la même manière par les deux noeuds, chaque noeud a un lien « avec » un autre noeud. La relation entre conjoints ou entre frères (ou soeurs) peut être modélisée par un lien non-orienté.
Dans un modèle NetLogo, il y a un ensemble d'agents global réunissant tous les liens, comme il en existe un pour toutes les tortues et un autre pour tous les patches. Vous pouvez créer des liens non-orientés avec les commandes create-link-with et create-links-with, et des liens orientés en utilisant les commandes create-link-to, create-links-to, create-link-from et create-links-from. Une fois le premier lien (orienté ou non-orienté) créé, tous les liens sans race doivent être de ce même type (les liens pouvant aussi appartenir à des races différentes, comme les tortues). Il n'est en effet pas possible d'avoir deux liens sans race dont l'un est orienté et l'autre est non-orienté. Une erreur d'exécution est générée si vous essayez de le faire. (Si tous les liens sans race meurent, alors vous pouvez créer des liens sans race d'un type autre que celui des liens précédents.)
En général, les primitives qui fonctionnent avec des liens orientés ont les suffixes -in, -out, -to et -from dans leur nom. Les primitives des liens non-orientés les omettent ou utilisent «-with».
Les races de liens, comme les races de tortues, permettent de définir différents types de liens dans votre modèle. Les races de liens doivent être soit orientées, soit non-orientées, contrairement aux liens sans race, car ils sont définis à la compilation plutôt qu'à l'exécution. Les races de liens sont déclarées avec les mots-clés undirected-link-breed et directed-link-breed. Les liens d'une race peuvent être créés avec les commandes create-<breed>-with et create-<breeds>-with pour les races non-orientées et les commandes create-<breed>-to, create-<breeds>-to, create-<breed>-from et create-<breeds>-from pour les races de liens orientés.
Il ne peut pas y avoir plus d'un lien non-orienté de la même race (ou plus de deux liens orientés) entre deux agents, ni plus d'un lien de la même race orienté dans la même direction entre une paire d'agents. Vous pouvez avoir deux liens orientés de la même race (ou deux liens sans race) entre une paire d'agents si ces liens sont de sens (d'orientation) opposés.
Les réseaux de liens
En tant que support des réseaux, nous avons aussi ajouté plusieurs primitives qui vous aiderons à visualiser les réseaux. La plus simple est layout-circle qui distribue les agents régulièrement autour du centre du monde sur un cercle de rayon donné.
La distribution layout-radial est une bonne disposition si vous avez quelque chose qui ressemble à une structure en arbre, et elle fonctionnera même s'il y a quelques cycles dans l'arbre; toutefois plus il y aura de cycles, plus l'aspect en sera dégradé. layout-radial demande un agent « racine » (root) qui doit être le noeud central placé en (0,0), puis elle arrange les noeuds qui lui sont connectés (à l'agent racine) en un motif concentrique. Les noeuds situés à un niveau du noeud racine sont arrangés selon un motif circulaire, le niveau suivant autour de ces noeuds est également arrangé selon un motif circulaire, et ainsi de suite. La primitive layout-radial tentera de tenir compte des graphes asymétriques et donnera plus de place aux branches qui sont les plus grandes. layout-radial peut aussi recevoir une race en entrée, ce qui permet d'utiliser une race de liens pour afficher un réseau plutôt qu'un autre.
Quand on lui donne un ensemble de noeuds d'ancrage, layout-tutte place tous les autres noeuds au centre de masse (centre de gravité) des noeuds qui leur sont liés. L'ensemble des ancrages est automatiquement disposé en cercle de rayon défini par l'utilisateur et les autres noeuds convergeront à leur place (cela signifie bien entendu que vous devrez exécuter plusieurs fois cette primitive avant que l'arrangement des noeuds obtenu soit stable.
layout-spring et __layout-magspring sont presque semblables et sont très utiles pour de nombreux types de réseaux. L'inconvénient est qu'il sont relativement lents puisqu'ils demandent un grand nombre d'itérations pour converger. Dans les deux réseaux, les liens jouent le rôle de ressorts qui tirent les noeuds qu'ils relient les uns vers les autres alors que les noeuds se repoussent les uns les autres. Dans le ressort magnétique, il y a aussi un champ magnétique qui « tire » les noeuds dans une direction magnétique que vous choisissez. Les intensités des toutes ces forces sont contrôlées par les paramètres passés en entrée à ces primitives. Ces entrées auront toujours une valeur comprise entre 0 et 1. Gardez à l'esprit que même de très petites modifications peuvent déjà affecter l'apparence du réseau. Les ressorts ont aussi une longueur (en unités patch), toutefois, à cause de toutes les forces mises en jeu, les noeuds ne finissent pas exactement à cette distance les uns des autres. Le réseau de type ressort magnétique demande aussi une entrée booléenne, bidirectional?, qui indique si le ressort doit pousser dans les deux directions parallèlement au champ magnétique. Si bidirectional? est vrai (true) les éléments du réseau seront espacés plus uniformément.
Exemples de code : "Network Example", "Network Import Example", "Giant Component", "Small Worlds", "referential Attachment"
Dans les versions précédentes de NetLogo, la primitive ask activait les agents en parallèle (en concurrence) par défaut, c'est-à-dire que les différents agents obéissant aux ordres transmis par ask tentaient d'exécuter leurs tâches « en même temps ». Depuis NetLogo 4.0, ask est sériel, c'est-à-dire que les agents exécutent les commandes transmises par ask les uns après les autres.
Les informations suivantes décrivent le comportement de la commande ask-concurrent qui agit de la même manière que le faisait l'ancien ask.
ask-concurrent produit une concurrence simultanée par un mécanisme de « turn-taking » que l'on pourrait traduire par « chacun son tour ». Le premier agent prend un tour, puis l'agent suivant prend un tour, et ainsi de suite jusqu'à ce que tous les agents de l'ensemble d'agents concernés par la commande aient effectué leur tour. Puis l'on revient au premier agent. Ce mécanisme se poursuit jusqu'à ce que tous les agents aient exécuté toutes les commandes reçues.
Un « tour » d'agent se termine lorsque l'agent a effectué une action qui modifie l'état du monde, tel qu'un déplacement, la création d'une tortue ou encore la modification de la valeur d'une variable globale, de tortue, de patch ou de lien. (L'action sur une variable locale ne compte pas.)
Les commandes forward (fd) et back ( bk) sont traitées différemment. Quand elles sont utilisées dans une commande ask-concurrent, elles peuvent nécessiter plusieurs tours pour faire leur travail. Pendant son tour, une tortue ne peut se déplacer que d'un pas. Ainsi, avec la commande fd 20 (avancer 20 pas), qui est équivalente à repeat 20 [ fd 1 ], le tour de la tortue se termine après l'exécution de fd 1 (elle a donc besoin de 20 tours pour faire tout son travail). Si la distance spécifiée n'est pas un nombre entier, la partie fractionnaire compte comme un pas et prend un tour complet. Par exemple, fd 20.3 est équivalent à repeat 20 [ fd 1 ] fd 0.3.
La commande jump se fait toujours en un seul tour, quelle que soit la distance.
Pour comprendre la différence entre ask et ask-concurrent, considérez les deux commandes suivantes :
ask turtles [ fd 5 ] ask-concurrent turtles [ fd 5 ]
Avec ask, la première tortue avance de dix pas, puis la deuxième tortue avance de dix pas, et ainsi de suite.
Avec ask-concurrent, toutes les tortues avancent d'un pas. Puis toutes les tortues avancent d'un deuxième pas, et ainsi de suite. Ce qui fait que cette dernière commande est équivalente à :
repeat 5 [ ask turtles [ fd 1 ] ]
Exemple de code : "Ask-Concurrent Example" montre la différence entre ask et ask-concurrent.
Le comportement de ask-concurrent ne peut pas toujours être imité aussi simplement en utilisant ask comme dans l'exemple ci-dessus. Considérez la commande :
ask-concurrent turtles [ fd random 10 ]
Afin d'obtenir le même comportement en utilisant ask, il faudrait écrire :
turtles-own [ pas ] ask turtles [ set pas random 10 ] while [any? turtles with [pas > 0] [ ask turtles with [pas > 0] [ fd 1 set pas pas - 1 ] ]
Pour prolonger le « tour » d'un agent, utilisez la commande without-interruption. (Les blocs de commandes dans certaines commandes, telles que create-turtles et hatch sont « encadrées » par une commande without-interruption implicite.)
Notez que le comportement de ask-concurrent est complètement déterministe. Étant donnés le même code et les mêmes conditions initiales, il se passera toujours la même chose (si vous utilisez la même version de NetLogo et que vous lancez l'exécution du modèle avec la même valeur d'amorçage du générateur de nombres aléatoires. (Voir la section Les nombres aléatoires.)
De manière générale, nous vous recommandons d'écrire le code de votre modèle de manière à ce qu'il ne dépende pas des détails de fonctionnement de ask-concurrent. Nous ne donnons aucune garantie que la sémantique de cette commande restera la même dans les futures versions de NetLogo.
L'attache (tie) connecte deux tortues de manière à ce que le mouvement de l'une des tortues influence l'emplacement et l'orientation de l'autre. L'attache étant une propriété des liens, il doit y avoir un lien entre deux tortues pour pouvoir créer une relation de type attache.
Quand la propriété tie-mode d'un lien a reçu la valeur fixed ou free, end1 et end2 sont attachés l'un à l'autre. Si le lien est orienté, end1 est l'agent « racine » et end2 est l'agent « feuille ». Cela signifie que quand end1 se déplace (en utilisant fd, jump, setxy, etc.), end2 se déplace aussi de la même distance et dans la même direction. Mais quand end2 se déplace, end1 n'en est pas affecté.
Si le lien est non-orienté, la relation d'attache est réciproque, ce qui signifie si l'une des deux tortues se déplace, l'autre tortue suit. Ainsi, chaque tortue peut être considérée comme la racine ou la feuille en fonction de quelle tortue se déplace. La tortue racine est toujours la tortue qui a commencé le mouvement.
Quand la tortue racine pivote à droite ou à gauche, le tortue feuille tourne autour de la tortue racine du même angle, comme si une barre rigide reliait les deux tortues. Quand tie-mode est réglé à fixed, l'orientation de la tortue feuille change du même angle. Si tie-mode est réglé à free, l'orientation de la tortue feuille n'est pas modifiée.
Le mode d'attache tie-mode d'un lien peut être réglé à fixed au moyen de la commande tie et désactivé (c'est-à-dire que les tortues ne sont plus attachées) avec la commande untie. Pour mettre le mode d'attache à free, vous devez utiliser set tie-mode "free".
Exemple de code : "Tie System Example"
Le mot-clé __includes permet d'utiliser plusieurs fichiers source pour un seul modèle NetLogo.
Ce mot-clé commence par deux caractères soulignement pour indiquer que cette fonctionnalité est expérimentale et peut encore être modifiée dans de futures versions de NetLogo.
Quand vous ouvrez un modèle qui utilise le mot-clé __includes, ou si vous ajoutez ce mot-clé au début du code du modèle et pressez le bouton "Check", le menu "Includes" apparaît dans la barre d'outils. Ce menu vous permet alors de sélectionner les fichiers inclus dans le modèle.
Quand vous ouvrez des fichiers inclus, leur code apparaît dans des panneaux "Procedures" supplémentaires. Consultez le Guide de l'interface pour plus de détails.
Les fichiers source externes (.nls) peuvent contenir tout ce que vous pouvez mettre normalement dans un panneau "Procedures", c'est-à-dire des définitions de variables globales (globals), des variables de races (breed), des variables tortue particulières ( turtles-own), des variables patch particulières (patches-own) ou race particulière ( breeds-own), des définitions de procédures, etc. Notez toutefois que ces déclarations partagent toutes le même espace de noms. Ce qui signifie que si vous déclarez une variable globale ma-globale dans le panneau "Procedures", vous ne pouvez pas aussi déclarer une variable (ou quoi que ce soit d'autre) portant le nom de ma-globale dans l'un des fichiers qui sont inclus dans le modèle. La variable ma-globale sera accessible par tous les fichiers inclus. Il en irait de même si ma-globale était déclarée dans l'un quelconque des fichiers inclus.
Cette section contient une terminologie technique qui pourra paraître « ésotérique » à bien des lecteurs.
Les seuls et véritables mots-clés du langage sont globals, breed, turtles-own, patches-own, to, to-report et end, plus extensions et le mot-clé expérimental __includes (Les noms des primitives prédéfinies, qui ne peuvent pas être remplacés ou redéfinis, sont aussi en définitive des sortes de mots-clés.)
Toutes les primitives, les noms des variables globales et des variables d'agents et les noms des procédures se partagent un espace de noms global où les minuscules ne sont pas distinctes des majuscules. Les noms locaux (ceux des variables spécifiées par let et les noms des arguments des procédures) ne doivent pas « faire de l'ombre » (être les mêmes) que ceux des noms globaux ou que ceux d'autres variables locales. Les identificateurs peuvent contenir des lettres, des chiffres et les caractères ASCII suivants :
[ . ? = * ! < > : # + / % $ _ ^ ' & - ]
Les caractères non-ASCII (surtout les lettres accentuées) ne sont pour le moment pas autorisés dans les identificateurs. (Nous sommes conscients qu'il s'agit d'une gêne pour les utilisateurs non-anglophones et avons planifié la résolution de ce problème pour une mise à jour future.)
Certains noms de primitives commencent par deux caractères soulignement pour indiquer que ces primitives sont expérimentales et donc fortement sujettes à modifications, voire susceptibles de disparaître dans une des versions futures de NetLogo.
Les identificateurs qui commencent par un point d'interrogation sont réservés.
Les domaines de validité (appelés aussi portées) dans NetLogo sont de type lexical. Les variables locales (y compris les arguments des procédures) sont accessibles dans le bloc de commandes dans lequel elles ont été déclarées. Elles ne sont par contre pas accessibles par les procédures appelées par ces commandes.
Le caractère point-virgule marque le début d'un commentaire, commentaire qui se termine automatiquement à la fin de la ligne. Il n'y a pas de syntaxe pour un commentaire multi-lignes.
Un programme est formé de déclarations optionnelles (globals, breed, turtles-own, patches-own, <BREED>-own) placées dans n'importe quel ordre, suivies de zéro à plusieurs définitions de procédures. Les diverses races peuvent être déclarées dans des déclarations breed séparées, les autres déclarations ne pouvant par contre n'apparaître qu'une seule fois.
Chaque définition de procédure doit commencer par to ou par to-report suivi du nom de la procédure et, en option, de la liste des arguments placée entre crochets. Chaque définition de procédure doit se terminer par end placé seul sur la dernière ligne de la procédure. Entre les deux (to et end) prennent place zéro ou plusieurs commandes.
Les commandes reçoivent zéro ou plusieurs entrées par l'intermédiaire de leurs arguments; les arguments sont des reporters qui à leur tour peuvent recevoir zéro ou plusieurs entrées. Aucune ponctuation ne sépare ni ne termine les commandes; aucune ponctuation ne sépare les arguments. Les identificateurs (tous les noms utilisés par le langage) doivent être séparés par des espaces, par des parenthèses ou encore par des crochets. (Ainsi, par exemple, a+b est un identificateur unique, alors que l'expression a(b[c]d)e contient cinq identificateurs.)
Toutes les commandes sont préfixées. Tous les reporters définis par l'utilisateur sont préfixés. La plupart des primitives reporters sont aussi préfixées, mais certaines (les opérateurs arithmétiques, les opérateurs booléens et quelques opérateurs d'ensembles d'agents (tels que with et in-points) sont infixés.
Toutes les commandes et les reporters, aussi bien primitives que définis par l'utilisateur, demandent par défaut un nombre d'entrées (arguments) prédéfini. (C'est pour cette raison que le langage peut être analysé syntaxiquement même s'il n'y a pas de ponctuation pour séparer ou terminer les commandes et/ou les arguments.) Certaines primitives sont « variadic », c'est-à-dire qu'elles peuvent optionnellement recevoir un nombre d'arguments différent de celui par défaut. Dans ce cas, il faut utiliser des parenthèses pour marquer cette particularité, comme dans (list 1 2 3) par exemple (puisque la primitive list ne demande par défaut que deux arguments). Les parenthèses sont aussi utilisées pour modifier l'ordre de priorité des opérateurs, par exemple (1 + 2) * 3, comme c'est le cas dans les autres langages de programmation.
Parfois, l'argument d'une primitive est un bloc de commandes (zéro ou plusieurs commandes placées entre crochets) ou un bloc de reporters (une seule expression reporter entre crochets). Les procédures définies par l'utilisateur ne peuvent pas recevoir de bloc de commandes ou de reporter en entrée.
Les priorités des opérateurs sont les suivantes, des plus hautes au plus basses :
Il n'existe pas de définition standard du langage Logo. Ce terme recouvre plutôt une famille de langages assez disparate ayant une origine commune, le langage Lisp. Nous croyons toutefois que NetLogo possède suffisamment de caractéristiques communes avec les autres dialectes Logo pour mériter aussi le nom de Logo. Il n'en reste pas moins que NetLogo diffère par certains aspects de la plupart des autres Logo. Les différences les plus importantes sont énumérées ci-dessous.
Les trois dernières différences sont illustrées dans les définitions des procédures suivantes :
La plupart des Logo | NetLogo |
---|---|
to square :x output :x * :x end |
to-report square [x] report x * x end |
Bien entendu, le langage NetLogo offre encore beaucoup d'autres fonctionnalités que l'on ne trouve pas dans les autres Logo, les plus importantes étant les agents et les ensembles d'agents.