Direct links to other parts of the IODA NetLogo Manual: General Documentation FAQ Dictionary
In what follows, we assume that you are already familiar with the NetLogo platform and with the NetLogo programming language. Installation issues for the IODA NetLogo extension are discussed in the General Documentation.
The IODA methodology is aimed at facilitating the design of agent-based simulations, and also at enhancing reusability, model revision and bias elicitation. The examples provided in this tutorial will help you to get into IODA step by step, starting with very simple behaviors, and going up to subtle refinements in the interaction selection process. Thus, this tutorial is also a IODA programming guide. We suggest that you follow this tutorial in sequence, since important IODA concepts and programming techniques are presented throughout this page.
RANDOM
RANDOM-INT
BEST
, PRORATA
and ALL-BEST
ALL
, NUMBER
and FILTER
tutorials
directoryLet's start with a simple problem: we want to model particles moving randomly. The corresponding NetLogo code is quite simple:
to setup clear-all set-default-shape turtles "circle" crt 1000 [ setxy random-xcor random-ycor set color red ] reset-ticks end to go ask turtles [ wiggle ] tick end to wiggle set heading random 360 fd 1 end
Since IODA is a generic methodology, it is not really convenient to such a simple simulation, because it requires a bit more code in that case. Yet, we are going to IODA-ize this model to explain the differences between the IODA approach and ad-hoc modelling.
The first step in IODA consists
in identifying agents: here, we have
only turtles
.
Then, we must identify interactions, i.e. abstract conditions/actions rules that describe the behaviors of the agents. An interaction involves a source agent (which performs the interaction) and one (or more) target agents (which undergo the interaction). It is composed of:
In the current case, the turtles just have to move randomly, without any restriction. Thus we have to define but one interaction, for instance
MoveRandomly
, which contains only a sequence of 1 action:wiggle
. Put the following lines in a text file named "interactions.txt":interaction MoveRandomly actions wiggle end
Now, you have to specify what interactions can be performed by agents. Thus, you must define an interaction matrix, which assigns interactions to source and target agents.
Here, the agents (turtles) can only perform the
MoveRandomly
interaction. Additionnally, each turtle moves by itself: the target agent of the interaction is actually the same as the source. This is called a reflexive interaction.Put the following lines in a text file named "matrix.txt":
turtles MoveRandomly 0
This means any turtle can perform
interaction MoveRandomly
with itself as a target. The "0"
is the priority level of the interaction, from the point of
view of the source agent (here it does not matter since there is only
one interaction).
Now there is some code to write. First, you must use the IODA simulation engine provided in the IODA extension folder, and the ioda extension.
Put a copy of
IODA_2_2.nls
in your own folder and rewrite your procedures as follows:__includes ["IODA_2_2.nls"] extensions [ioda] to setup clear-all set-default-shape turtles "circle" crt 1000 [ setxy random-xcor random-ycor set color red ] ioda:setup reset-ticks end to go ioda:go tick end to wiggle set heading random 360 fd 1 end
The ioda:setup
command initializes the IODA parameters of
the simulation, while the ioda:go
runs one simulation
step in IODA. Though, if you press the "go" button, nothing happens.
Indeed, at this time you did not specify the IODA model to use,
i.e. you did not define any interactions nor interaction matrix in
your NetLogo program.
Rewrite your
setup
procedure as follows:to setup clear-all set-default-shape turtles "circle" crt 1000 [ setxy random-xcor random-ycor set color red ] ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " " ioda:setup reset-ticks end
Now, your interactions and your interaction matrix are automatically loaded into the NetLogo program. Unfortunately, if you press "go" at this time, you get an error message:
Indeed, in IODA, the interactions make use
of abstract primitives
(here, wiggle
), which must be
instantiated into concrete primitives. Concrete
primitives depend on the breed of the agent: thus, in that example,
the wiggle
abstract primitive is run by turtles,
hence it must be renamed
turtles::wiggle
.
Rename the
wiggle
procedure:turtles::wiggle
.Congratulations! Your program should now run correctly and produce the same result as the ad-hoc NetLogo program.
Now, we are going to modify the behavior of or turtles. If a turtle perceives another turtle "in radius" 1, it will kill it. A turtle may kill at most one other turtle per tick. If it kills no turtle, it just keeps moving randomly.
First, let's examine the classical go
procedure that
answers those specifications:
to go ask turtles [ let n other turtles in-radius 1 ifelse (any? n) [ ask one-of n [ die ]] [ wiggle ] ] tick end
The behavior is still quite simple; nevertheless, this modification
requires to re-write the go
procedure and to introduce
ifelse
cascades, which would not be very convenient to
more sophisticated behaviors.
At the opposite, in the IODA version, the go
procedure
does not change. Instead, you have first to define a new interaction,
which consists in making the target of the interaction die.
Add the following lines to file "interactions.txt":
interaction Delete actions target:die endNote: in the definition of an interaction, abstract primitives (reporters or commands) are always evaluated and run by the source agent (which owns a reference to its target), unless they begin with the keyword
target:
Then, you have to change your interaction matrix to take into
account the Delete
interaction: it can be performed by
any turtle on any other turtle, within a distance less or equal to
1.
Add the following line to file "matrix.txt":
turtles Delete 1 turtles 1This line can be read as follows: "Turtles can perform interaction Delete with priority 1 on other turtles with distance ≤ 1"
Finally, you certainly have to write additional primitives for your turtles. Fortunately, the extension can tell you what remains to write.
Push the "setup" button, to ensure your model is loaded. Then, type in the command center:
observer>print ioda:primitives-to-write to turtles::filter-neighbors end to turtles::die end
The procedure turtles::die
is required because of
the target:die
item in the Delete
interaction, which means that the target (namely here, a turtle) must
implement a concrete primitive for die
.
Add the following procedure to your program:
to turtles::die ioda:die end
Note: You MUST NOT use the NetLogo die
primitive in a IODA primitive, because the IODA simulation engine has
its own scheduler. By using ioda:die
instead, you ensure
that the agent is considered dead (through a convenient variable), and
that it will be actually removed from the NetLogo simulation at the
end of the ioda:go
procedure.
The other procedure (turtles::filter-neighbors
) is
required for perception. Indeed, turtles now need to have a
look on other agents: thus you need to specify how neighboring agents
are perceived.
We assume here that turtles perceive their neighbors in a circle of radius 1. Then we can use a predefined command:
to turtles::filter-neighbors ioda:filter-neighbors-in-radius 1 end
If you are already familiar with the IODA NetLogo extension, this section can be useful right now for you. Otherwise, just skip it for now and come back
This second step in getting familiar with IODA consists in re-implementing the classic "Termites" NetLogo model. In that model, a wandering termite can pick a wood chip up, wander again, and when it encounters another wood chip, it searches for an empty place and puts its load down. In the example that comes with the NetLogo Models Library, the wood chips are patches. Thus we have only one kind of agents (the turtles which play the role of termites).
First, you have to sketch your program as usual.
__includes ["IODA_2_2.nls"] extensions [ioda] to setup clear-all set-default-shape turtles "bug" create-turtles 50 [ setxy random-xcor random-ycor set color white ] ask n-of 500 patches [set pcolor yellow] ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " " ioda:setup reset-ticks end to go ioda:go tick end
Now we have to define possible interactions, by decomposing the activity of the agents into coarse-grain, abstract and coherent sequences:
MoveRandomly
: the same than in previous tutorialPickUp
: if a wood chip is present and the source
agent does not carry anything, it can pick a chip upFindEmptyPlace
: it has the same effect
than MoveRandomly
, but is triggered by the presence of a
wood chipPutDown
: the source agent puts a wood chip down, if
nothing is already at this placePut the following lines in a text file named "interactions.txt"
interaction MoveRandomly actions wiggle end interaction PickUp trigger not-empty-here? condition not-carrying? actions take-load get-away end interaction FindEmptyPlace trigger not-empty-here? condition carrying? actions wiggle end interaction PutDown trigger something-nearby? condition carrying? empty-here? actions drop-load random-turn get-away end
Please note that some interactions contain 2 items in addition to
the actions: the trigger represents the motivation or implicit
goals that lead to choose the interaction, while the condition
represents physical or logical prerequisistes that must be fulfilled
to execute the actions. From an operational point of view, there is
not much difference between trigger and condition, since ALL
primitives found in both must return true
to allow the
interaction to be selected. The distinction makes sense from a
knowledge representation viewpoint. For instance,
the PickUp
interaction is triggered by the
perception of a wood chip (not-empty-here?
), which means
that the termite tries to perform PickUp
because
its perceives a wood chip (in order to carry it somewhere); yet, the
actions of the interaction cannot take place if its condition
is not fulfilled, namely the termite must not already carry any wood
chip (not-carrying?
). Anyway, if you hesitate, put your
tests where you want...
You also have to define an interaction matrix. In that case, you only have one kind of agents, and their interactions are reflexive all the time (you could do exactly the same simulation with only one turtle !). Thus, you have litle to do but think about the priorities of interactions, i.e. if all interactions have their trigger and condition fulfilled, what will the source agent perform ?
It appears that the turtle should first try to pick a wood chip up; if not possible, to find an empty place if it is already carrying a wood chip; if not possible, try to put the wood chip down; finally, the random move is the default behavior.
Put the following lines in a text file named "matrix.txt".
turtles MoveRandomly 0 turtles PutDown 10 turtles FindEmptyPlace 20 turtles PickUp 30Note: the exact values of priorities have no importance. What is taken into account is the resulting order relation.
Finally, you have to write several primitives:
observer>setup observer>print ioda:primitives-to-write to-report turtles::not-empty-here? end to-report turtles::not-carrying? end to-report turtles::something-nearby? end to-report turtles::carrying? end to-report turtles::empty-here? end to turtles::take-load end to turtles::wiggle end to turtles::drop-load end to turtles::random-turn end to turtles::get-away end
Copy-paste those primitives to you program. You can try to find what they should do in this model; we suggest the following solution:
Add
turtles-own [carrying?]
after theextension
instruction.Add
set carrying? false
in the initialization of the turtlesto-report turtles::not-empty-here? report pcolor = yellow end to-report turtles::not-carrying? report not carrying? end to-report turtles::something-nearby? report any? neighbors with [pcolor = yellow] end to-report turtles::carrying? report carrying? end to-report turtles::empty-here? report pcolor = black end to turtles::take-load set carrying? true set color red set pcolor black end to turtles::wiggle left random 50 right random 50 fd 1 end to turtles::drop-load set pcolor yellow set carrying? false set color white end to turtles::get-away fd 20 end to turtles::random-turn right random 360 end
The IODA methodology states that all relevant entities of the model must be agents: thus the latter case is not completely IODA-compliant, since patches are considered a mere discretization of the environment. But, why not use true wood chips instead? Then, we have two kinds of agents: termites of course (a NetLogo breed), buts also chips (another breed).
Here is the squeletton for this new simulation:
__includes ["IODA_2_2.nls"] extensions [ioda] breed [ termites termite ] breed [ chips chip ] termites-own [carrying?] to setup clear-all set-default-shape termites "bug" set-default-shape chips "line" create-termites nb-termites [ init-termite ] create-chips nb-chips [ init-chip ] ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " \t" ioda:setup reset-ticks end to init-termite setxy random-xcor random-ycor set color white set carrying? false end to init-chip setxy random-xcor random-ycor set color yellow end to go tick ioda:go end
Now, we can examine the model of the previous tutorial to find out
what the consequences of the 'agentification' of the wood chips are.
Especially, it appears that several abstract primitives, such
as empty-here?
or something-nearby?
, rely
implicitly upon the perception of an wood chip. Thus, handling wood
chips as true agents removes this implicit perception: the
interactions can be simplified; especially,
the FindEmptyPlace
interaction no more exists. In
counterpart, the interaction matrix now involves explicitely both
termites and wood chips. We can summarize possible interactions as
follows:
Sources / Targets | ∅ | chips | termites |
---|---|---|---|
chips | |||
termites | (MoveRandomly, 0) | (MoveRandomly, 10, 1) (PickUp, 30, 1) (PutDown, 20, 0.3) |
The matrix above should be read as follows: the first column is
the list of source agents; the first row contains the list of target
agents. An interaction at row S and column T can be
performed by an agent of the kind S (source) on another agent
of the kind T (target). The column marked with ∅
represents reflexive interactions (the target is the source
itself). In this category, we find for instance MoveRandomly
:
a termite can perform such an interaction alone. In the
column chips, we find interactions that chips can undergo: for
instance, a termite can perform PickUp
with a chip.
The association between an interaction, a source agent and a target
agent is called an assignation. It comes with a priority and,
in regular cases, a limit distance between source and target (this
distance has no meaning in reflexive interactions). For instance, in
the above matrix, the assignation (PickUp
, 30, 1) means
that termites try to perform this interaction first (30 is the highest
priority level on the termites row), with chips in radius 1.
Please also note that the interaction MoveRandomly
is
used twice. With the lowest priority level, it is present as a
reflexive interaction, to allow termites to wander in search for wood
chips. But, it also plays the role of the FindEmptyPlace
interaction in the previous tutorial, as an interaction between
termites and chips. Indeed, a termite which cannot
perform PickUp
on a chip (thus is already carrying a
chip), is likely to perform MoveRandomly
instead, i.e.
move around, and it will do that until being far enough from a chip
(distance > 1).
To use the matrix above in your model, put the following lines in a text file named "matrix.txt"
termites MoveRandomly 0 termites MoveRandomly 10 chips 1 termites PutDown 20 chips 0.3 termites PickUp 30 chips 1
Those considerations lead us to propose a simplified definition of the interactions needed in that model.
Put the following lines in a text file named "interactions.txt"
interaction MoveRandomly actions wiggle end interaction PickUp condition not-carrying? actions take-load get-away end interaction PutDown condition carrying? actions drop-load random-turn get-away end
You also have to write the primitives for termites and chips. Take
some time to try by yourself. Useful indication: when running a
concrete primitive (reporter or command) either as a source or as a
target, you can refer to the other agent through
the ioda:my-target
reporter.
Here is the set of primitives we propose:
to-report termites::not-carrying? report not carrying? end to-report termites::carrying? report carrying? end to termites::filter-neighbors ioda:filter-neighbors-in-radius 1 end ; note that this primitive has an action ; both on source and target agents to termites::take-load set carrying? true set color red ask ioda:my-target [ioda:die] end to termites::wiggle left random 50 right random 50 fd 1 end to termites::drop-load set carrying? false set color white hatch-chips 1 [init-chip] end to termites::get-away fd 20 end to termites::random-turn right random 360 end
The simulation of the above model should lead to the formation of
compact piles as shown below. The sparseness of the piles depends on
the limit distance assigned to the PutDown
interaction.
In this section, we try to build a small ecosystem with three species: grass, sheep and wolves. At the beginning, we allow sheep to eat grass, wolves to eat sheep, and only sheep to mate (no death by starvation). In addition, we decide to use patches to represent grass; but, remember, in IODA any relevant entity should be an agent: hence, in the IODA NetLogo extension, patches can be explicitly be used as regular agents in the model. Thus, we can summarize the relations between agents through the interaction matrix below.
Sources / Targets | ∅ | patches | sheep | wolves |
---|---|---|---|---|
patches | ||||
sheep | (MoveRandomly, 0) | (Eat, 20, 0) | (Mate, 10, 1) | |
wolves | (MoveRandomly, 0) | (Eat, 20, 1) (Hunt, 10, 5) |
Moreover, the grass resource must be at the same time limited (to
avoid exponential growth of sheep) and regularly restored (to allow
sheep to reproduce). Thus, the state of the patches has to change
regularly, without considering interactions that the patches could
perform or undergo.
In IODA such update operations can be specified
in the update matrix below:
Sources | UPDATE |
---|---|
patches | (GrowFood, 0) |
sheep | |
wolves |
Interaction matrix and update matrix can be specified in the same file. To do so, put the following lines in a text file named "matrix.txt":
; interactions performed by wolves wolves Eat, 20 sheep 1 wolves Hunt, 10 sheep 5 wolves MoveRandomly, 0 ; interaction performed by sheep sheep Eat, 20 patches 0 sheep Mate, 10 sheep 1 sheep MoveRandomly, 0 ; interaction performed by patches patches Grow, 0 UPDATENotes:
1. As you can see above, to put an interaction in the update matrix instead of the interaction matrix, you just have to write the "UPDATE" keyword after the priority (an interaction used for update is obviously reflexive).
2. The matrix file is written in CSV format with any field separator. When you load a file (with theioda:load-matrix
command), you have to specify a string containing all characters used as field separators (most of the time, spaces ' ' and tabs '\t'). In the file above, we used at the same time ' ', '\t' and ','. Thus the string containing the field separators is " \t,".
3. If you want to insert comments in the files, you only need to start the line with a non-letter, non-digit character (here, ';').
ioda:go
)This small parenthesis explains how the simulation engine works, to help you understand the difference between interactions put in the update matrix and those put in the interaction matrix.
First, when matrices are loaded, the IODA NetLogo extension builds three lists of agents:
wolves
and sheep
are active agents; patches
are
not, because they cannot really perform interactions (only
updates).sheep
and patches
are passive agents; wolves
are
not, because the "wolves" column is empty: wolves cannot undergo but
reflexive interactions.patches
are the only labile agents.From the above definitions, it appears that agents can be at the same time active, passive and labiles: this only depends on the contents of the interaction and update matrices.
The ioda:go
command makes use of this
characterization, to execute one step in the simulation through the
following operations:
ioda:die
removes the "alive"
flag) and as operative
nobody
)As a consequence of this scheduling algorithm, an agent may act as a source (i.e. perform an interaction) at most once during a time step.
Due to the scheduling of agents, actions that happen during a same time step are actually run in sequence. Thus, it can be sometimes very important to prevent some agents to perform and undergo interactions simultaneously, i.e. during the same time step.
For instance, it is clear that several wolves can perform
the Hunt
interaction with the same sheep as
target. Conversely, if we allow a sheep to be the target
of Mate
twice (or more) during the same time step, the
birth rate will be higher than expected. It would be more likely that
a sheep cannot perform nor undergo Mate
but once at a
time.
In order to cope with such situations, IODA provides to classes of interactions:
Hunt
or MoveRandomly
are parallel interactions.
Mate
or Eat
are exclusive interactions.
To identify the class of an interaction
I
, you have to answer the following questions:
- can a target of
I
be the source of other interactions, during the same time step?- can an agent be twice the target in interaction
I
with different sources, during the same time step?If you answer "YES" to one or both questions, then interaction
I
is parallel. Otherwise, it is exclusive.
We are now able to define the interactions required to build our
little ecosystem. In IODA NetLogo, interactions
are considered parallel by default. You can put two keywords
before interaction
to modify the class of the
interaction: parallel
or exclusive
. The
general algorithm of interaction selection is designed to take
automatically into account the class of each
interaction. You are strongly recommended to
explicitly specify the class of your interactions, in order to make
their meaning unambiguous.
Put the following lines in a text file called "interactions.txt":
parallel interaction Hunt trigger hungry? condition adult? target:alone? actions chase end exclusive interaction Eat trigger hungry? condition target:healthy? actions digest target:die end parallel interaction MoveRandomly actions wiggle end parallel interaction Grow condition not-grown? actions increas-size end exclusive interaction Mate trigger adult? target:adult? condition not-hungry? target:not-hungry? actions reproduce decrease-health target:decrease-health end
We propose the following rules regarding the nature of the agents:
food
attribute (between 0
and max-food
), which represents the quantity of
food available on the patch; food growth rate is 0.1 per tick; a
patch is considered "healthy"
if food
≥ 0.2*max-food
stomach
attribute to store the food;
sheep are hungry when stomach
≤ 20;
any move costs 1 food unit; in addition, for now sheep are
always considered adult, and healthy means "not hungry";
breeding offspring costs 5 food units to both source and target
(sheep::decrease-health
): as a counterpart the
offspring starts with 10 food units in
its stomach
last-dinner
attribute to memorize
the time (the ticks
) they performed
the digest
action: thus they get hungry
when last-dinner
≥50; wolves are also
considered adultYou can use the following lines as the beginning of your program:
__includes ["IODA_2_2.nls"] extensions [ioda] breed [wolves wolf] breed [sheep a-sheep] wolves-own [last-dinner] sheep-own [stomach] patches-own [food] to setup clear-all set-default-shape wolves "wolf" set-default-shape sheep "sheep" init-agents ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " \t," ioda:setup reset-ticks end to go ioda:go tick end to init-agents ask patches [ set food random max-food recolor-patch ] create-sheep 50 [ setxy random-xcor random-ycor set color white set stomach random 20 ] create-wolves 5 [ setxy random-xcor random-ycor set last-dinner (- random 50) set color brown ] end to recolor-patch set pcolor scale-color green food 0 (max-food * 1.2) end to wolves::filter-neighbors ioda:filter-neighbors-in-radius 5 end to sheep::filter-neighbors ioda:filter-neighbors-in-radius 2 end
The end of the exercise for you is now to program the appropriate primitives according to the above indications and test your simulation. The program named "model1.nlogo" in the "4-simple-ecosystem" folder of the tutorial directory provides a solution, which leads to an equilibrium between grass and sheep:
You should now be familiar enough with IODA to try adding the following extensions to your model (an implementation is proposed in the file named "model2.nlogo"):
stomach =
0
Mate
interaction more realisticOf course, a challenge here (but not related to IODA) is to find parameters which allow an equilibrium in the ecosystem! After that, you can do the same for wolves (but this becomes very unstable).
When writing a trigger (or a condition), it is fulfilled if all
primitives report true
: the TRIGGER
and CONDITION
items of an interaction express
a logical conjunction of primitives. Yet, sometimes you need to
express alternatives (or logical disjunctions). To do so, you
just need to put as many TRIGGER
(or CONDITION
) lines as necessary.
For example, the model provided in folder
4b-leap years
of thetutorials
directory involves agents playing the role or years. In order to keep only leap years, agents can perform an interaction calledSurvive
, which needs to express that a year is a leap year if, and only if, its value is divisible either by 400, or by 4 but not by 100. This is done using twoCONDITION
lines:INTERACTION Survive CONDITION multiple-of-400 CONDITION multiple-of-4-but-not-100 ACTIONS print-value end
Thus, the interaction can occur if one (at least) of
the CONDITION
statements is
fulfilled. If you have both triggers
and conditions, at least one trigger AND at least one condition must
be fulfilled. Other examples for using this feature are shown in the
model 15-rescue the
princess
.
In order to avoid simulation biases, the IODA scheduler shuffles
the list of labile agents before the update step, and the list of
active agents before the interaction selection step (those steps are
explained above). Yet, in some (rare)
circumstances this is not adequate because the agents must act in a
particular order. Thus we allow the user to define its own ordering
policy for the update step and the interaction selection step,
which consists in providing a two-parameter reporter task which
decides whether agent ?1
must be scheduled before
agent ?2
or not.
For example, if you want 'year' agents to print their value in the chronological order, they have to perform the
Survive
interaction according to the ascending order of their values. This can be performed by specifying an appropriate ordering policy for interaction selection after the execution ofioda:setup
:to setup clear-all init-world ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " \t" ioda:setup if chronological-order? [ ioda:set-ordering-policy-for-interaction-selection task chronological-ordering ] reset-ticks end to-report chronological-ordering [ag1 ag2] report [value] of ag1 < [value] of ag2 end
The ioda:set-ordering-policy-for-interaction-selection
primitive specifies the task to use for scheduling active agents
before the interaction selection step ; similarly,
the ioda:set-ordering-policy-for-update
primitive
specifies the task to use for scheduling labile agents before the
update step. In both cases you can use the
string "random"
as parameter value so as to restore the
default policy (shuffling the lists of
agents). In most situations you need no special
ordering policy. Use them with caution in order to avoid simulation
biases !
In the interaction selection process, all
realizable interaction/target pairs with a same priority level
(eventually with target=nobody
), i.e. pairs for which
trigger and condition are fulfilled, can occur
with equal probability. But, is some cases, this might lead to
an unconvincing behavior, or even to biases actions. For instance, if
you consider implementing pheromones following, you expect your agents
to go where pheromones have the highest level. In this tutorial, we
are going to deal with this very problem.
We are starting with a simple path following problem, with two kinds
of agents: patches
, which can a certain amount of
pheromones (according to a constant distribution),
and ants
which have to follow the pheromone gradient
uphill. For convenience, ants die when they reach the highest level of
pheromones: what is of interest here is the path followed. We can
summarize the problem with the interaction matrix below.
Sources / Targets | ∅ | patches | ants |
---|---|---|---|
patches | |||
ants | (Die, 20, 0) (FollowGradient, 10, 1.5) (MoveRandomly, 0, 0) |
Put the following lines in text a file called "interactions.txt":
; FollowGradient is a parallel interaction because ; a same patch can be the target of several ants parallel interaction FollowGradient trigger low-pheromone? condition feel-gradient? actions move-towards end parallel interaction MoveRandomly trigger target:no-pheromone? actions wiggle end parallel interaction Die trigger target:high-pheromone? actions die end
Put the following lines in a text file called "matrix.txt":
ants Die 20 patches 0 ants FollowGradient 10 patches 1.5 ants MoveRandomly 0 patches 0
Finally, here is the code of the program with appropriate primitives:
__includes ["IODA_2_2.nls"] extensions [ioda] breed [ants ant] patches-own [pheromone] to setup clear-all set-default-shape ants "ant" init-patches create-ants 200 [ setxy random-xcor random-ycor set color brown pen-down ] ioda:load-interactions "interactions.txt" ioda:load-matrices "matrix.txt" " \t" ioda:setup reset-ticks end to init-patches ask patch -5 5 [ set pheromone 1000 ] repeat 10 [ diffuse pheromone 0.5 ] let m max [pheromone] of patches ask patches [ set pcolor scale-color yellow pheromone 0 m ] end to go tick ioda:go if (not any? ants) [stop] end to ants::filter-neighbors ioda:filter-neighbors-in-radius 2 end to ants::die ioda:die end to-report patches::high-pheromone? report pheromone > 40 end to-report ants::low-pheromone? report pheromone < 40 end to-report ants::feel-gradient? report [pheromone] of ioda:my-target > [pheromone] of patch-here end to ants::move-towards face ioda:my-target fd 1 end to-report patches::no-pheromone? report pheromone < 1 end to ants::wiggle left random 45 right random 45 fd 1 end
Try this model. You should see your ants wiggle and go more or less
directly to the center of the yellow hill. However, you also can
observe that their trails are not quite straight
(see figure a below). They
can reach the center thanks to the feel-gradient?
primitive, which ensures that an ant moves to a neighboring patch only
if the value of the pheromone on the new patch is higher than the
value on the current patch. But, this does not
mean at all that the ant chooses the patch with the highest pheromone
level.
Obviously, we would expect ants to
perform FollowGradient
on "best" patches, i.e. on patches
where the amount of pheromone is higher that others, or where it is
the highest.
IODA provides generic tools for refining the way interaction/target
pairs are chosen. These tools are called Target
Selection Policies (TSP). TSP are integrated in the IODA
simulation engine, with a default policy: RANDOM
, which
ensures an equiprobable choice between all interaction/target pairs
with the same priority level.
The IODA NetLogo extension provides several other Target Selection
Policies, among those which are most often needed. For now, we
introduce two frequent policies: BEST
and PRORATA
.
In the case of pheromones following, we can compare those policies, using a reporter that gives the amount of pheromones on a patch.
Add the following reporter to your procedures:
to-report patches::pheromone report pheromone endPut the following lines in a text file called "matrix-PRORATA.txt":
ants Die 20 patches 0 ants FollowGradient 10 patches 1.5 PRORATA:pheromone ants MoveRandomly 0 patches 0Put the following lines in a text file called "matrix-BEST.txt":
ants Die 20 patches 0 ants FollowGradient 10 patches 1.5 BEST:pheromone ants MoveRandomly 0 patches 0
Test your model with each of the new matrices. You should obtain different trails, as shown on figures b and c below.
The figures below come from simulations with respectively
a RANDOM
(a), a PRORATA
(b)
and a BEST
(c) Target Selection Policy. You can
clearly see that, in the area where pheromones are felt, the trails
left by ants are more and more deterministic. Especially, in
the BEST
TSP, the ants go straight to the patch with the
highest pheromone level.
(a) | (b) | (c) |
Target Selection Policies are designed to help you to specify preferences in the way target are chosen in an assignation. Thus you can indicate one at the end of a line in the interaction matrix. Of course it does not make sense in a reflexive interaction.
To use a Target Selection Policy, just add, at the end of the
appropriate line, the keyword refering to the TSP
(e.g. BEST
or PRORATA
) followed with ":" and
the name of the abstract primitive which must be used to compare
targets. That means that you have to write an additional concrete
primitive, depending on the nature of the target agents. In the
example above, the BEST:pheromone
clause is used in the
context where the target is a patch: hence you have to write a
concrete reporter named patches::pheromone
.
Other Target Selection Policies are presented in Tutorial #8.
Try to reproduce the classical "Ants" NetLogo model (Models Library → Biology → Ants) with IODA, using only agents. We suggest you start with the the following interaction and update matrices:
Sources / Targets | ∅ | ants | food | pheromones | nests |
---|---|---|---|---|---|
ants | (ReturnToNest, 10) (MoveRandomly, 0) |
(Take, 30, 1) (Follow, 20, 5) |
(Follow, 10, 5) | (DropFood, 30, 1) | |
food | |||||
pheromones | (Die, 20) (MoveRandomly, 0) |
(Take, 10, 0.8) | |||
nests |
Sources | UPDATE |
---|---|
ants | |
food | |
pheromones | (Evaporate, 0) |
nests |
ReturnToNest
).MoveRandomly
and Evaporate
respectively); pheromones can also merge (Take
)
since it does not make sense to have several pheromones agents
at the same place. Finally, they Die
when their
value is too low.MoveRandomly
, then add Take
and ReturnToNest
, and so on.Take
food
vs. Take
pheromones).We propose the following interactions to reproduce the Ants simulation:
parallel interaction Follow trigger foraging? actions follow-target target:decrease-strength end exclusive interaction Take condition can-take-load? actions take-target target:die end interaction MoveRandomly actions wiggle end interaction ReturnToNest trigger carrying-food? actions drop-pheromone move-to-home end interaction DropFood condition carrying-food? actions drop-load turn-back end exclusive interaction Die trigger too-weak? actions die end interaction Evaporate actions decrease-strength end
We propose the following matrix file to reproduce the Ants simulation:
; FILE FORMAT: ; Source Interaction (priority) [UPDATE] ; or ; Source Interaction (priority) Target (distance) [<TARGET-SEL-METHOD>] ; interactions performed by ants Ants Take (30) Food (1) Ants DropFood (30) Nests (1) Ants Follow (20) Food (5) Ants Follow (10) Pheromones (5) BEST:strength Ants ReturnToNest (10) Ants MoveRandomly (0) ; interactions performed by pheromones Pheromones Die (20) Pheromones Take (10) Pheromones (0.8) Pheromones MoveRandomly (0) Pheromones Evaporate (10) UPDATE
Your simulation will probably look like this little movie, which demonstrates several additional features:
quality
attribute, and ants
choose the best quality (among perceived food agents).Please have a look at the "6-ants-and-food"
folder in
the tutorials
directory.
The IODA method is designed for simulations involving large numbers of agents, agent families, and interactions between agents. Yet, it can also be used for more simple computations, such as cellular automata. In addition, some agent-based simulations may rely upon a cellular automaton e.g. for representing resources. We show here how to program a cellular automaton with the IODA NetLogo extension.
The Game of Life, invented by John Horton Conway, is a 2D cellular automaton with two states (usually called "alive" and "dead"). A NetLogo implementation is provided in the model library (Models Library → Computer Science → Cellular Automata → Life). The rules are the following:
State changes must occur simultaneously, thus at time t all cells have to compute their state at time t+1, then all cells must actually change their state if needed. Therefore, it is convenient in the IODA methode to make use of update interactions so as to compute state changes, and of regular interactions to perform real state changes, according to the following matrices:
Sources | UPDATE |
---|---|
patches | (ComputeNextState, 0) |
Sources / Targets | ∅ | patches |
---|---|---|
patches | (ChangeState, 0) |
Exercise: Write the interactions and the corresponding
NetLogo program.
A solution is provided in the "7-life"
folder in the
tutorials
directory.
The files provided in the folder 8-explosion
of
the tutorials
directory aim at demonstrating several
target selection policies available in IODA NetLogo. You are
recommended to launch the corresponding model and to experiment while
reading this section of the tutorial.
The general context is the following: a mine, surrounded by
soldiers, is going to explode. It is likely to blast several
agents. Soldiers are characterized by their strength, and some
wear shields (in yellow) which protect them against mines. What
we show below is 1) how many agents can be the target of
the
|
A view of the initial configuration of the "explosion"
experiments. The mine is surrounded by soldiers, some of them
wearing a protection shield (yellow). |
Below is the
Blast
interaction:EXCLUSIVE INTERACTION Blast TRIGGER activated? countdown-finished? CONDITION target:no-shield? ACTIONS target:die die END
RANDOM
As it has been explained above, the default process used for
interaction and target selection retrieves all interaction/target
pairs for which the trigger and condition are fulfilled, then selects
one pair randomly (the target can be nobody
). This
process is suitable for most simulations.
RANDOM-INT
Yet, suppose we have now the following interaction matrix:
Sources / Targets | ∅ | soldiers | mines |
---|---|---|---|
soldiers | |||
mines | (Blast, 0) (Defuse, 10) |
(Blast, 10, 4) |
When a mine performs the Defuse
interaction, it does
not explode at all. The priority for that interaction is the same than
for Blast
other soldiers, but since it is a reflexive
interaction, the target is nobody
. Thus, the probability
that Defuse
occurs is: 1/(N + 1), where N is the number of
unprotected soldiers within the Blast
radius (here,
4).
But, if the specifications of the simulation require that
the Defuse
and Blast
interactions occur with
the same probability, the default target selection policy is not
suitable. Then, you can use the interaction-first
selection policy: when several interactions have the same
priority, a first random selection occurs (with equal probability)
among those interactions (if they have potential targets), then for
the interaction that was selected, the target is chosen randomly. In
our case, the probability for each interaction is 1/2,
though Blast
has a large number of potential targets
while Defuse
has only 1.
To use the interaction-first policy, you just have to use the
keyword RANDOM-INT
at the end of the matrix line.
In the tutorial model, compare the two experiments named "RANDOM" and "RANDOM-INT": in the latter, there is a probability 1/2 that the mine does not explode. Examine the corresponding matrix files to see the difference.
BEST
, PRORATA
and ALL-BEST
We have already introduced property-driven
selection policies in the pheromones
section of this tutorial. By using the keyword BEST:
(resp. PRORATA:
), followed by an abstract reporter, you
ensure that the target is chosen with the highest value of the
reporter (resp. with a probability which is proportional to the
reporter). By using the keyword ALL-BEST:
with a
reporter, the target of the interaction is the
list of all targets with the highest value for the reporter.
In the tutorial model, compare the three experiments named "BEST", "PRORATA" and "ALL-BEST". The reporter that is used there is
weakness
(the opposite of the strength of the soldier): thus, with "BEST" one of the weakest soldier is killed; with "ALL-BEST", all soldiers with the smallest strength are killed; and with "PRORATA", one soldier is killed, with a probability proportional to its weakness.
ALL
, NUMBER
and FILTER
The three policies we present here allow a source to perform an interaction on a list of agents.
ALL
In the tutorial model, if you experiment "ALL", the mine will blast all soldiers which are not protected by a shield.
NUMBER:x-y
, NUMBER:x-
,
NUMBER:-y
, NUMBER:z
In the tutorial model, you can compare experiments "NUMBER-10" (blast up to 10 soldiers), "NUMBER10-" (blast at least 10) and "NUMBER45-" (blast at least 45), which is not possible when too many soldiers wear a shield). In the latter, the
Blast
interaction cannot be performed on soldiers when too many of them wear a shield: then, only the reflexive occurrence ofBlast
(with a lower priority, see the interaction matrix above) is performed (the mine explodes without killing anyone).
FILTER:reporter
ioda:my-target
reporter) according to global
considerations.
In the tutorial model, you can compare experiments "FILTER1" and "FILTER2". In the first one, a global damage budget is used to determine which soldiers are killed, depending on their proximity to the mine and their own strength (see procedure
mines::distance-strength-tradeoff
). In the second one, a random number of soldiers is killed (seemines::random-number
).
ioda:my-target
reports a
list. Trigger and condition are evaluated for each
potential target individually, and actions performed by the target
too (e.g. in the Blast
interaction, target:die
).Blast
and a Hurt
interactions realizable
at the same time on soldiers, if one uses "RANDOM-INT" and the other
nothing (or "RANDOM"), then "RANDOM-INT" overpowers "RANDOM" and is
used for both. As you can see in the interface tab of the provided model, you can
change the metric used to compute distances between agents. This can
be useful in several situations, especially when working with
discretized space. By default, the ioda:setup
procedure
sets a variable (ioda:metric
)
to "Euclidean"
. You can change this
value after the setup through
the ioda:set-metric
primitive. Valid parameters are the following:
"Euclidean"
(default value): the usual distance, or
2-norm distance, defined by:
"Moore"
: also called ∞-norm distance
or Chebyshev distance, defined by:
"Von Neumann"
: also called the taxicab norm,
or Manhattan distance, or 1-norm distance, defined by:
![]() |
![]() |
The initial configuration with the Moore metric | The initial configuration with the Von Neumann metric |
tutorials
directory9-emergent planning
An example of simple behaviors found in arcade-type games. The character is endowed to simple interactions which are hierarchized so as to make it search and eat the apple. It behaves thus as if following a plan, with a combination of forced steps and opportunistic actions. Two experiments are presented: in the first one, the character has to unlock and open the door by himself, while in the second one, the door automatically locks/unlocks and opens/close, if the character owns the appropriate remote. This illustrates how the IODA approach facilitates the transformation of "inanimate artifacts" (the passive door) into "true agents" (the automatic door), since all entities are agents from the beginning.
10-water
An example of chemical reaction, here the synthesis of water
from dioxygen and dihydrogen, according to the equation:
O2 + 2 H2 → 2
H2O. This example makes use of
the NUMBER
target selection policy and demonstrates
that total mass is conserved.
11-primes
An example of distributed and spatialized computation of prime numbers, based on Eratosthenes' algorithm. Numbers, created by a generator, become primes if they are not killed by other primes.
12-colors
A multi-purpose simulation with 4 agent breeds (named after
heraldic tinctures: azure, gules, gold and silver). All breeds
have exactly the same capabilities (in terms of perception and
actions), so they all can perform or undergo generic and
self-evident interactions (such
as: CloneSource
, KillTarget
,
etc). Several matrices demonstrate how to use such interactions
to produce interesting phenomena, such
as emergent spirals
or segregation models.
13-warbot
Basic agents and behaviors designed for "Warbot" simulations, aimed at testing strategies in robot teams competitions. This IODA Netlogo version is freely inspired from the original Warbot game, introduced by Jacques Ferber and distributed as a part of the MadKit platform. This basic implementation can be used as a first sketch to design, test and compare various AI techniques.
14-age-of-crisis
A little simulation of villages managing their environmental resources to build their civilization. Any similarity with well-known games would be mere coincidence!
15-rescue-the-princess
This model demonstrates the use of the interaction-oriented approach for designing behaviors driven by dynamic goals. It involves a knight in a labyrinth with several locked doors. The knight must meet the princess and therefore has to find a convenient path. Depending on the personality traits that are given to the knight, and on its knowledge regarding the environment, the goals and actions that are carried out may differ significantly. This model comes from an original applet based on planification. It also provides example of interactions using alternatives in triggers or condition (as allowed in the extension since v. 2.2).
Authors: Sébastien Picault and Philippe Mathieu
Email: ioda
(at) univ-lille1.fr
Web Site: http://www.lifl.fr/SMAC/projects/ioda
All contents © 2008-2013 Sébastien PICAULT and Philippe MATHIEU –
SMAC Research Team
Multi-Agent Research Team, Laboratoire d'Informatique Fondamentale
de Lille (LIFL), UMR CNRS 8021
University Lille 1 –
Cité Scientifique, F-59655 Villeneuve d'Ascq Cedex, FRANCE.
The IODA NetLogo extension is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
The IODA NetLogo extension is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU
General Public License along with IODA NetLogo extension. If not,
see http://www.gnu.org/licenses.