5.2.1 Un élément fondamental du langage NetLogo : les listes
Les listes occupent une place centrale dans les programmes NetLogo et on les utilisent constamment.
On les délimite avec des crochets et on sépare leurs éléments avec un espace :
Une liste de trois nombres : [0.5 1 2]
Une liste contenant deux listes : [[0.5 1 2] [2.5 1.33 25]]
Une liste hétérogène : [0.5 [2.5 1.33 25] ["Paul" "Jacques" "Aliye"]]
Une liste vide : []
Attention piège! Le caractère espace est le séparateur des valeurs et des instructions dans le langage NetLogo. Si vous êtes habitué au langage mathématique ou à d’autres langages informatiques, vous pouvez être tentés d’utiliser des virgules pour séparer des valeurs et des points-virgules pour séparer les instructions. Cela causera des erreurs dans NetLogo.
On peut aussi construire des listes en utilisant l’instruction list : (list 0.5 1 2) créera dans la mémoire la liste . Les parenthèses sont nécessaires dans ce cas car elles délimitent les éléments à partir desquels la liste sera constituée.
On fait référence aux éléments d’une liste en utilisant l’instruction item : item 0 donnera la valeur . L’indice de la première valeur d’une liste est . Les indices qu’on doit utiliser pour accéder aux différents éléments de cette listes sont donc :
D’autres instructions [Q] [Q] Toutes ces instructions sont parfaitement documentées dans le dictionnaire de NetLogo. utiles pour la manipulation des listes sont : replace-item, fput, lput, sentence, map, reduce, sort, sort-by.
5.2.2 Déclaration des types de tortues et de leurs variables spécifiques
Des agents de type tortue (turtle) existent par défaut dès le démarrage de NetLogo. Vous n’avez pas besoin de déclarer ce type. Pour tout autre type que vous voulez inclure dans votre modèle, vous devez utiliser l’instruction breed qui attend une liste contenant le nom pluriel de ces agents (pour s’adresser à l’ensemble des agents de ce type) et son nom singulier (pour s’adresser à un individu de ce type). Par exemple, l’instruction suivante indiquera à NetLogo que le programme contiendra des agents de type firme :
breed [firmes firme].
NetLogo ne connaît pas les firmes et les caractéristiques spécifiques de ce type d’agents bien sûr. Vous devez déclarer variables spécifiques qui doivent caractériser chaque firme. Pour cela on adapte l’instruction générique turtles-own [R] [R] Les tortues existant par défaut, si vous voulez les utiliser, vous pouvez directement utiliser turtles-own telle quelle. pour chaque type d’agent : Pour nos firmes, il nous faudra déclarer par conséquent des variables spécifiques de la manière suivante :
firmes-own [
prix ; le prix de chaque firme
profit ; le profit de chaque firme
]
indiquant à NetLogo que chaque firme possède un prix et un profit individuels.
Remarquez que nous sommes en train de déclarer une liste des variables (d’où les crochets droits) séparées par des espaces (pour NetLogo, le saut à la ligne est un espace comme un autre). Cette manière d’écrire permet de mettre un commentaire à coté de chaque variable comme nous le voyons dans cet exemple. Evitez d’utiliser des caractères accentués dans les commentaires car ils peuvent mal vivre le passage d’un système d’exploitation à un autre.
Le programme va aussi contenir des variables qui ne concernent pas un type d’agents en particulier. Ces variables se déclarent de différentes manières selon les besoins.
Dans l’interface graphique, attachées aux contrôles (champ de texte, sélecteur de liste, curseur, etc.). Il s’agit alors des variables nécessairement globales, visibles par tous les agents.
Au début du programme, comme variable globale , dans une liste qui s’appelle globals : globals [ prix nbAgents ]
Comme variable locale dans une procédure ou un bloc de code, grâce à l’instruction let qui la déclare et lui attribue une valeur:
let prix-initial 10
On modifie la valeur d’une variable existante (et donc qui doit être déclarée au préalable) en utilisant l’instruction set :
set prix-initial 0.5 qui donne la valeur à la variable prix-initial;
set mes-prix qui affecte la liste donnée à la variable mes-prix;
set mon-nom qui stocke cette chaîne de caractère dans la variable mon-nom;
set prix ( item 0 mes-prix ) qui lit le premier élément de la liste contenue dans la variable mes-prix et l’attribue à la variable prix;
set mes-prix ( replace-item 1 mes-prix 15 ) qui modifie la liste contenue dans la variable mes-prix et attribue la liste modifiée à cette même variable qui contient maintenant la liste modifiée.
Décortiquons un peu plus cette dernière commande pour voir la structure générale d’une instruction de NetLogo :
Nous avons utilisé des parenthèses pour grouper les instructions, même si cela n’est pas nécessaire et l’ordre de priorité à l’exécution de NetLogo est suffisant pour exécuter ses instructions dans l’ordre. En utilisant les espaces, comme séparateurs, Netlogo repère facilement les opérateurs et leurs arguments. Il utilise pour cela e nombre arguments attendus par chaque opérateur, nombre qu’il connait parfaitement.
Dans notre exemple, NetLogo commence par construire dans la mémoire la liste modifiée par l’instruction replace-item (qui attend trois arguments : le numéro de la place dans la liste de l’élément à changer, la liste dans laquelle on doit faire ce changement et la nouvelle valeur à mettre à cette place) et utilise ensuite cette liste construite comme le second argument de l’instruction d’affectation set (le premier argument étant la variable à laquelle il faut affecter ce second argument).
On peut afficher dans l’interface graphique la valeur d’une variable en utilisant l’instruction show : show mes-prix affichera dans la partie Command center de l’interface graphique, en base de cet écran.
Il faut déclarer leur type dans le début du programme breed [firmes firme]
et déclarer, le cas échéant, les variables qui vont leur être propres, comme nous l’avons déjà vu
Ensuite, créer autant d’agents de ce type que nécessaire au début du programme (en général dans setup) create-firmes 25
pour créer firmes, par exemple.
Comme nous l’avons vu, les tortues (turtles) constituent un type qui est présent par défaut. L’instruction suivante create-turtles 25
créera 25 agents de type tortues sans qu’on ait besoin de déclarer ce type d’agent.
Les modèles NetLogo se déroulent dans un temps discret qui avance pas à pas. Pour nous faciliter la gestion de ce temps, NetLogo contient un compteur qui mémorise le nombre de périodes déjà exécutées : ticks.
Pour lire la valeur de ce compteur, on utilise directement cette variable : letcette-periodeticks lira la valeur actuelle de la variable ticks et l’attribuera à une variable locale appelée cette-période.
Il est nécessaire d’initialiser la valeur de ce compteur au début de chaque simulation et on exécute l’instruction reset-ticks dans la procédure setup pour cela.
Pour augmenter sa valeur d’une unité (en général à la fin des instructions correspondant au déroulement complet d’une période, déroulement décrit dans la procédure go), on exécute l’instruction tick. L’exécution du modèle passera alors à la période suivante.
NetLogo nous permet d’étendre son langage en ajoutant des procédures spécialisées à notre programme. Les procédures sont donc des fonctions spécialisées qui regroupent certaines instructions qu’on est amené à exécuter plusieurs fois et auxquelles on peut directement faire appel grâce à la procédure qui les contient.
NetLogo possède deux types de procédures selon le résultat final qu’on obtient à leur exécution :
On utilise les commandes (commands) pour modifier directement les valeurs des variables globales et/ou des variables qu’on leur passe en argument. Une fois exécuté, les commandes ne retournent aucun résultat.
Les rapporteurs (reporter) sont des méthodes qui retournent (en fait «rapportent») une valeur (qui peut aussi être une liste bien sûr) à la fin de leurs opérations. Cette valeur n’existe que dans la mémoire et on doit soit la stocker dans une variable ou l’utiliser dans un calcul (exemple : replace-item ci-dessus qui retourne la liste modifiée et qu’on doit stocker dans la variable initiale).
Les commandes (commands)
Leur déclaration commence avec l’instruction to [S] [S] Ce sont donc des verbes correspondant à l’exécution d’une suite d’opérations. et se termine avec l’instruction end. On a la possibilité de leur fournir une liste d’arguments (délimitée par les crochets, comme pour toute liste) avec lesquels elles pourrons travailler (notamment pour modifier la valeur des variables contenues dans cette liste).
to maprocedure [ argument1 argument2 ]
set argument2 (argument1 * argument2)
; argument2 contient maintenant la multiplication
; deux arguments
set argument2 (2 * argument2)
; sa valeur a double
; Attention aux espaces qui entourent l’opérateur multiplication
; (on aurait pu exécuter les deux instructions en une ligne)
end
Remarque : Dans cet exemple quelque peu artificiel, j’ai mis les parenthèses uniquement pour vous aider à décortiquer plus facilement le code, étant donné que le langage est encore nouveau pour vous. L’ordre de priorité interne de NetLogo rend ces parenthèses tout-à-fait inutiles en général.
Plus tard dans le code, on fait appel à cette commande comme avec toute procédure interne de NetLogo, par son nom suivi des valeurs pour les arguments indiqués pendant sa définition :
maprodecure x y
et si , cette commande change d’abord la valeur de x à et ensuite à
Par exemple, la procédure suivante pourrait être utilisée pour fixer la valeur du prix de marché à sa valeur qui solderait le marché (comme dans une approche en termes d’équilibre) :
to fixePrix [prix0 quantite0]
set prix0 (1000 - 2 * quantite0)
end
Attention à nouveau aux espaces autour des opérateurs. 2*2 est juste une chaîne de caractères pour NetLogo qui ne peut reconnaître l’opérateur de multiplication, si vous ne le séparez pas de ses arguments avec espaces : 2 * 2 serait la bonne instruction et donnerait bien le résultat .
Et on pourrait fixer le prix de marché au prix d’équilibre en faisant appel à cette commande :
fixePrix prixMarche quantiteTotale
La commande utiliserait alors quantiteTotale de la période pour fixer prixMarche à sa valeur d’équilibre en utilisant la fonction de demande inverse programmée dans cette commande.
Les rapporteurs (reporters)
Les rapporteurs retournent un résultat à la fin des opérations qu’ils effectuent. En cela il ressemblent beaucoup à des fonctions mathématiques. On les déclare avec l’instruction to-report et la déclaration se termine avec l’instruction end comme pour les commandes :
to-report monrapporteur [ argument1 argument2 ]
let resultat 0
; On declare une variable locale qui va stocker
; le resultat qui sera retourne
instruction1
instruction2
set resultat
; Sa valeur est fixee en fonction des instructions effectuees
report resultat ; On rapporte le résulta obtenu
end
Plus tard dans le code on lui fait appel pour affecter une valeur à une variable ou faire des calculs à partir du résultat rapporté :
set ma-variable monrapporteur 1 5
ma-variable contient alors la dernière valeur calculée pour resultat.
Plutôt qu’une commande comme ci-dessus, nous pourrions utiliser un rapporteur pour fixer le prix qui solde le marché.
to-report calculePrix [quantite0]
let prix 1000 - 2 * quantite0
report prix
end
Nous pouvons alors utiliser ce rapporteur dans notre programme pour fixer le prix au prix d’équilibre qui résulte de cette fonction de demande inverse :
set prixMarche calculePrix 300
ce qui fixerait le prix de marché à .
NetLogo dispose aussi d’un jeu d’instructions complet pour contrôler le flot d’exécution des instructions, comme dans tout langage de programmation moderne. On peut répéter des instructions avec ou sans conditions, implémenter un branchement conditionnel dans les instructions à exécuter et c’est une spécificité de NetLogo, travailler facilement des ensembles d’agents.
Répéter des instructions fois : on utilise l’instruction repeat qui attend deux arguments : le nombre de fois les instructions seront répétées et la liste d’instructions à répéter (encore une liste!) :
repeat n [ instructions ]
Par exemple, les instructions
let x 0
repeat 10 [set x x + 1]
ajouterait dix fois la valeur à qui a une valeur initiale et donc la valeur de x serait de à la fin de ces répétitions.
Parfois, nous ne connaissons pas à l’avance le nombre de fois que nous devons répéter la liste d’instruction. C’est une condition qui dicte la fin des répétitions : nous répéterons les instructions tant que la condition est remplie (elle donne la valeur vraie – true) et nous nous arrêterons quand la condition n’est plus remplie et donne la valeur false
while [ condition ] [ instructions ]
répètera la liste d’instructions tant que la condition est vraie.
Par exemple, en reprenant notre exemple pour repeat, nous pourrions désirer d’incrémenter x tant qu’une condition dépendant de x reste vraie (le prix résultant des quantités est positif par exemple, ou la firme qui augmente ses quantités tant que son profit marginal est positif)
let q 0
while [ prof-margin >= 0] ; Condition
[ ; Liste d’instructions
let q q + 1
; On recalcule le prof-margin
;avec la nouvelle production
calcule-prof-margin q
]
dès que la firme arrive à un niveau de production qui ne prédit plus un niveau positif du profit marginal, elle arrêterait d’augmenter ses quantités et utiliserait la dernière valeur pour laquelle ce profit marginal était positif.
Un autre exemple qui traduirait plus la «magie» de NetLogo pourrait être :
while [any? other turtles-here] [ forward 1 ]
La tortue avance (forward) dans le Monde tant qu’elle trouve d’autres tortues sur les patches qu’elle visite et elle s’arrêtera donc une fois arrivée sur un patch où elle est seule.
Parfois, on a besoin d’exécuter des instructions différentes selon l’état de l’environnement de l’agent ou de l’agent lui-même. Pour exécuter certaines instructions seulement si certaines conditions sont remplies, nous utilisons les commandes if ou ifelse qui attendent comme argument une condition qui évalue true ou false et une liste d’instructions pour if et deux listes pour ifelse.
ifcondition [instructions] : la liste d’instructions n’est exécutée que si la condition est remplie (condition = true).
ifelse condition [instructions1] [instructions2] : si la condition est vraie,la liste instructions1 est exécutée, sinon la liste instructions2 est exécutée.
Nous pouvons maintenant améliorer notre fonction de fixation de prix, de sorte qu’elle ne retourne plus un prix négatif si jamais les quantités deviennent supérieures à .
to-report calculePrix [quantite0]
let prix 1000 - 2 * quantite0
ifelse prix >= 0 [report prix][report 0]
end
Maintenant le prix retourné pour les quantités trop élevées sera .
5.2.9 Un point fort de NetLogo : faire des calculs avec les listes d’agents
On a souvent besoin, dans un MMA, de demander à tous les agents d’un même type d’effectuer certaines opérations ou besoin de travailler avec les valeurs d’une variable donnée de tous les agents d’un même type.
Dans ces cas, on a besoin de parler à tous les agents de même type et pour cela on utilise le nom pluriel que nous leur avons donné au moment de leur déclaration avec l’instruction breed. Ainsi, le nom pluriel d’un type d’agents (par exemple, firmes) permet de faire référence à une liste de tous les agents de ce type.
On peut alors leur demander d’exécuter une liste d’instructions, chacun à son tour, dans un ordre aléatoire, grâce à l’instruction ask:
ask firmes [
fixe-production ; appel procedure determinant la production
calcule-profit ; appel procedure qui calcule le profit
]
demandera à chaque firme, choisie dans un ordre aléatoire chaque fois qu’on leur parle avec l’instruction ask, de calculer son niveau de production et le niveau de profit correspondant.
On peut aussi demander aux agents de nous fournir une liste contenant les valeurs d’une variable spécifique, une valeur par agent, grâce à l’instruction of. Cette liste sera composée dans un ordre aléatoire (différent chaque fois qu’on interroge les agents) et nous pouvons utiliser sur cette liste n’importe quel opérateur qui sait faire des calculs sur des listes. calculer des statistiques sur une propriété des agents d’un certain type en utilisant une structure de type :
opérateur [variable-type] of nom-pluriel-agents
Exemples :
Le profit moyen des firmes : mean [ profit ] of firmes
Le profit maximal parmi les firmes : max [ profit ] of firmes
On peut aussi calculer le minimum (min), la médiane (median), l’écart-type (standard-deviation), la variance (variance) d’une variable des agents avec cette méthode très pratique.
En fait, ce que nous obtenons avec l’instruction of est une vraie liste, qui peut aussi contenir le résultat d’opérations effectuées par chaque agent, en utilisant les variables qui sont à sa disposition (ses variables individuelles, les variables globales et les variables locales qui lui sont visibles au moment où on l’interroge). Ainsi tout autre calcul utilisant une variable commune des agents du même type peut-il être demandé. Par exemple, nous pouvons utiliser la variable who que possède automatiquement chaque tortue et qui indique son ordre de création dans l’ensemble des tortues (et donc indépendamment de leur type) pour interroger les agents :
show [ who ] of firmes => [0 3 2 1] (ordre aléatoire)
show sort [who] of firmes => [0 1 2 3]
show sort [who * who] of firmes => [0 1 4 9]
Ces exemples ne sont pas très parlants, mais il nous permettent d’avoir un aperçu de cette variable importante who. Bien sûr, il est en général plus utile de faire des calculs qui ont un sens économique.
show sort [recettes - couts ] of firmes => [100 110 115 150]
imprimera dans le Centre de commande, une liste ordonnée (grâce à l’instruction sort) des profits des firmes, sans que cela ordonne les firmes elles-mêmes. En effet, si on décompose ces instructions, la première partie
[recettes - couts ] of firmes
donnera une liste aléatoire de profit, [115 110 150 100], qui sera ordonnée ensuite par l’instruction sort. L’instruction show imprimera la liste ordonnée dans le Centre de commandes.
Remarque : Cet exemple nous donne aussi un aperçu de l’ordre de priorité utilisé par défaut par NetLogo : les opérations sont effectuées de la droite vers la gauche. Ainsi d’abord la liste des profits est composée, ensuite on fait appel à leur classement, et en dernier lieu, à l’opération la plus à gauche qui les imprime.
Une autre fonctionnalité très puissante de NetLogo est le filtrage d’un ensemble d’agents selon un critère donné, grâce à l’instruction with. On peut alors créer une liste des agents vérifiant une condition recherchée. Par exemple,
let champions firmes with [profit > 1000]
crée une liste champions contenant uniquement les firmes qui ont un profit supérieur à la valeur à partir de l’ensemble d’agents firmes contenant toutes les firmes.
Nous avons vu ci-dessus qu’une instruction de type sort [prix] of firmes n’ordonnait pas les firmes elles-mêmes, mais juste la liste de leur prix. Or, il est aussi possible de créer une liste ordonnée d’agents quand cela est nécessaire (quand les consommateur devront rencontrer les firmes selon un ordre croissant de prix par exemple). Pour des critères simples, nous pouvons utiliser l’instruction sort-on pour cela
let firmes-prix-croissants sort-on [prix] of firmes
créera une liste firmes-prix-croissants contenant toutes les firmes ordonnées par des prix croissants.
Nous pouvons alors demander à chaque firme dans cette liste (et selon la hauteur de leur prix) d’exécuter des opérations, grâce à l’instruction foreach :
foreach firmes-prix-croissants [
la-firme -> ask la-firme [show prix]
]
L’opérateur -> que nous rencontrons pour la première fois permet de créer des procéduresanonymes (donc sans nom, et sans déclaration explicite avec to ou to-report) qui permet de demander (ask) à chaque élément de cette liste ordonnée (que nous appelons grâce au nom générique la-firme (comme un joker) que nous utilisons pendant cette instruction – on aurait pu l’appeler toto) d’exécuter la liste d’opérations que nous donnons, ici composée uniquement de l’instruction show prix qui leur demande d’afficher leur prix dans le Centre de commandes. Nous devrions alors voir une succession de prix croissants dans le Centre de commandes. Il s’agit d’une construction relativement complexe introduite dans la version 6 de NetLogo, mais elle est très puissante.
D’autres instructions très puissantes de NetLogo font appel à ces procédures anonymes : map, reduce, filter, n-values, et sort-by.
Je vous invite à consulter la section Anonymous procedures du Programming guide de la documentation de NetLogo pour approfondir ce point.
5.2.10 Modélisation des liens entre les agents et des réseaux
NetLogo contient des instructions spécifiques pour représenter les liens entre les agents et la structure d’un réseau que peuvent constituer ces liens (les agents sont alors les noeuds de ce réseau).
Les liens dans NetLogo sont un type d’agent à part entière (appelés link par défaut - équivalent de turtle pour les agents qui constituent les noeuds), avec notamment la possibilité de créer différentes espèces de liens, pour représenter différents types de liens entre les agents, comme nous l’avons évoqué ci-dessus (voir section 5.1.1↑). Ces liens peuvent être orientés ou non-orientés. Il ne peut y avoir qu’un seul lien de même type entre deux agents (exclusion de plus d’un lien non-orienté de même espèce ou de lien orienté de même espèce pointant dans la même direction). Chaque lien contient d’office deux variables : end1 et end2 qui correspondent aux deux agents qui sont liés par ce lien qui va, alors, de l’agent end1 à l’agent end2. Si le lien est non-orienté, l’agent le plus «vieux» (celui avec le who le plus faible) est end1. La variable globale links est un ensemble d’agents qui contient tous les liens existant dans le monde à un moment donné.
L’ordre create-link-with donné à un agent, lui demande de créer un lien non-orienté avec un autre agent. De manière similaire, l’instruction create-link-to (resp. create-link-from) demande à cet agent de créer un lien orienté vers un agent (resp. à partir d’un agent) donné en argument. Les versions pluriels de ces instructions ont besoin d’un ensemble d’agents comme argument et lient l’agent d’origine à tous les agents contenus dans cet ensemble.
clear-all
create-turtles 10
ask turtle 0 [create-links-with other turtles]
show count links
Dans cet exemple, nous créons 10 agents et nous demandons à l’agent le plus vieux de créer des liens non-orientésavec l’ensemble d’agents contenant les autres agents. La dernière ligne compte tous les liens existant dans le Monde à ce moment et donne comme résultat .
Nous pouvons déclarer différentes espèces de liens qui peuvent exister entre les agents et qui correspondent, dans ce cas, aux différents types de réseaux possibles :
undirected-link-breed [ collegues collegue ]
directed-link-breed [ clients client ]
Une fois créé, le réseau sera représenté graphiquement dans le Monde, comme des liens reliant les agents. Nous pouvons demander à NetLogo d’utiliser un schéma de représentation qui nous semble adapté parmi ceux qu’il propose. Les deux plus communs sont layout-circle qui place les agents sur un cercle autour du centre de Monde et layout-radial qui met un agent au centre et essaie de représenter le réseau comme une arborescence à partir de lui.
layout-circle turtles 10
arrange le réseau entre tous les noeuds sur un cercle centré sur le centre du Monde et avec un rayon de 10 en termes de distance. L’exemple précédent donnerait par exemple la représentation donnée dans Figure 5.2↓–(a). L’instruction
layout-radial turtles links (turtle 0)
donnerait la représentation 5.2↓–(b), qui semble mieux adapté pour ce cas. Il existe d’autres schémas que vous pouvez tester (layout-tutte et layout-spring) selon la complexité de la structure de votre réseau.
(a) layout-circle
(b) layout-radial
Figure 5.2 Deux représentations pour un petit exemple de réseau
L’extension Networks qui est incluse dans les versions récentes de NetLogo étend considérablement les possibilités pour travailler avec les réseaux et faire des calculs dans les réseaux pour en caractériser la structure (en termes de chemins, distances, centralité, clusters).
Prev Section 5.1: Qu’est-ce NetLogo?Up Chapitre 5: Développer un MMA avec NetLogoSection 5.3: Structure–type d’un modèle NetLogo Next