Multi-Agents Simulation

Applied to MATh.en.JEANS research workshop themes

May 16, 2025 - Yves Papegay - yap@informathiques.fr

This notebook is the final version of the support of the seventh session of the course “Online teacher training and community of practices - Digital tools for STEAM education and Makers pedagogy, November 2024 - June 2025” , an activity of the “MEJ MAKERS ERASMUS+ project (2023-2-FR01-KA210-SCH-000176068)” - visit the course dedicated web page for details.

Introduction

We are interested in simulating the evolution of a group of agents interacting (moving) in a closed (planar) space.

We will construct step by step a simulation environment and study the result of applying individual behaviours to the agents.

1st step : Simulation environment

Each agent is represented by a small disk and has four properties, its horizontal and vertical position and speed.

Configuration parameters give respectively the width and the height of the space, the diameter of the disk, the maximum speed, the maximum acceleration and the number of agents.

width=600;
height=400;
radius=5;
maxSpeed=3;
maxAcc=0.05;
nb =3;

Simulation maintains, at each time step the properties of the agents.

Initialisation

agents=Dataset[Table[<|"position"->{RandomInteger[{radius,width-radius}],RandomInteger[{radius,height-radius}]},"speed"->{RandomInteger[{-maxSpeed,maxSpeed}],RandomInteger[{-maxSpeed,maxSpeed}]}|>,nb]]

Evolution - position

Clear[Move]
Move[st_Dataset]:=st[All,Move[#position,#speed]&]
Move[pos_,spd_]:=<|"position"->pos+spd,"speed"->spd|>

Move[agents]

Visualisation

Static

Clear[View]
View[st_Dataset]:=Graphics[{AbsolutePointSize[2radius],Blue,Point[Normal[st[All,"position"]]]},ImageSize->{width,height},Frame->True,FrameTicks->False,PlotRange->{{0,width},{0,height}}]

View[agents]

Animated

Clear[Anim]
Anim[st_Dataset]:=Manipulate[Refresh[(If[moving,agt=Move[agt]];View[agt]),UpdateInterval->If[moving,0,Infinity]],{{moving,False,"run simulation"},{True, False}},{{agt,st},ControlType->None},SaveDefinitions->True,AutorunSequencing->{3},TrackedSymbols:>{moving,agt},SynchronousUpdating->True]

Anim[agents]

2st step : Staying in the space

To stay in the space, agents have to react to walls and change of speed accordingly.

Evolution - position and speed

Clear[Move]
Move[st_Dataset]:=st[All,Move[#position,#speed]&]
Move[{xp_,yp_},{xs_,ys_}]:=With[{xn=xp+xs,yn=yp+ys},With[{new=
Which[radius<=xn<=width-radius&&radius<=yn<=height-radius,{{xn,yn},{xs,ys}},
radius<=xn<=width-radius&&yn<radius,{{xn,2 radius-yn},{xs,-ys}},
radius<=xn<=width-radius&&height-radius<yn,{{xn,2(height-radius)-yn},{xs,-ys}},
xn<radius&&radius<=yn<=height-radius,{{2 radius-xn,yn},{-xs,ys}},
width-radius<xn&&radius<=yn<=height-radius,{{2(width-radius)-xn,yn},{-xs,ys}},
xn<radius&&yn<radius,{{2 radius-xn,2 radius-yn},{-xs,-ys}},
xn<radius&&height-radius<yn,{{2 radius-xn,2(height-radius)-yn},{-xs,-ys}},
width-radius<xn&&yn<radius,{{2(width-radius)-xn,2 radius-yn},{-xs,-ys}},
width-radius<xn&&height-radius<yn,{{2(width-radius)-xn,2 radius-yn},{-xs,-ys}},
True,{{xp,yp},{0,0}} ]},<|"position"->new[[1]],"speed"->new[[2]]|>]]

Anim[agents]

Clear[checkWall,Move]
checkWall[{xn_,yn_},{xs_,ys_}]:=Which[radius<=xn<=width-radius&&radius<=yn<=height-radius,{{xn,yn},{xs,ys}},
radius<=xn<=width-radius&&yn<radius,{{xn,2 radius-yn},{xs,-ys}},
radius<=xn<=width-radius&&height-radius<yn,{{xn,2(height-radius)-yn},{xs,-ys}},
xn<radius&&radius<=yn<=height-radius,{{2 radius-xn,yn},{-xs,ys}},
width-radius<xn&&radius<=yn<=height-radius,{{2(width-radius)-xn,yn},{-xs,ys}},
xn<radius&&yn<radius,{{2 radius-xn,2 radius-yn},{-xs,-ys}},
xn<radius&&height-radius<yn,{{2 radius-xn,2(height-radius)-yn},{-xs,-ys}},
width-radius<xn&&yn<radius,{{2(width-radius)-xn,2 radius-yn},{-xs,-ys}},
width-radius<xn&&height-radius<yn,{{2(width-radius)-xn,2 radius-yn},{-xs,-ys}} ]
Move[st_Dataset]:=st[All,Move[#position,#speed]&]
Move[pos_,spd_]:=With[{new=checkWall[pos+spd,spd]},<|"position"->new[[1]],"speed"->new[[2]]|>]

3st step : Reaching a target

Understanding

Let’s consider an agent at a given position (in black) at time t, with a speed v represented by the vector. At time t+1, the agent will be at the gray point.

p1={2,2};p2={4,10};
Graphics[{Line[{{0,0},{20,0},{20,20},{0,20},{0,0}}],Disk[p1],{LightGray,Disk[p2]},Arrow[{p1,p2}]}]

If its goal is to reach the pink point as a target, its desired speed would be the one represented by the red arrow.

t={18,10};
Graphics[{Line[{{0,0},{20,0},{20,20},{0,20},{0,0}}],{LightGray,Disk[p2]},{Pink,Disk[t]},Arrow[{p1,p2}],{Red,Arrow[{p1,t}]},Disk[p1]}]

If its desired speed is greater then the maximal speed, we can define the desired speed as the max speed in the same direction (red arrow). The brown arrow is the difference between the current speed and the desired speed : it is the desired acceleration.

v1=p2-p1;
vd1=(t-p1)/Norm[t-p1]*1.2Norm[p2-p1];
Graphics[{Line[{{0,0},{20,0},{20,20},{0,20},{0,0}}],{LightGray,Disk[p2]},{Pink,Disk[t]},Arrow[{p1,p2}],{Red,Dashed,Line[{p1,t}]},{Dashed,Circle[{2,2},1.2Norm[p2-p1],{Pi/10,Pi/5}]},{Red,Arrow[{p1,p1+vd1}]},{Brown,Arrow[{p2,p1+vd1}]},Disk[{2,2}]}]

Once again, the desired acceleration can be bigger than the maximum acceleration, and we can define the desired acceleration as the maximum acceleration in the same direction (brown arrow).

v1=p2-p1;
vd1=(t-p1)/Norm[t-p1]1.2Norm[p2-p1];
ad1 = 3(p1+vd1-p2)/Norm[p1+vd1-p2];
Graphics[{Line[{{0,0},{20,0},{20,20},{0,20},{0,0}}],{LightGray,Disk[p2]},{Pink,Disk[t]},Arrow[{p1,p2}],{Red,Dashed,Line[{p1,t}]},{Dashed,Brown,Circle[p2,3,{-Pi/3,0}]},{Red,Arrow[{p1,p1+vd1}]},{Brown,Dashed,Line[{p2,p1+vd1}]},{Brown,Arrow[{p2,p2+ad1}]},Disk[{2,2}]}]

Then the new speed is the total of the current speed and the desired acceleration, (green arrow), and the new position is the one corresponding to that speed (green disk).

np2=p2+ad1;
Graphics[{Line[{{0,0},{20,0},{20,20},{0,20},{0,0}}],{LightGray,Disk[p2]},{Pink,Disk[t]},Arrow[{p1,p2}],{Red,Dashed,Line[{p1,t}]},{Red,Arrow[{p1,p1+vd1}]},{LightGreen,Disk[np2]},{Green,Arrow[{p1,np2}]},{Brown,Dashed,Line[{p2,p1+vd1}]},{Brown,Arrow[{p2,p2+ad1}]},Disk[{2,2}]}]

Evolution - position and speed

Clear[Follow,Move]
Follow[pos_,spd_,tgt_]:= With[{ds=If[Norm[tgt-pos]>maxSpeed,maxSpeed(tgt-pos)/Norm[tgt-pos],tgt-pos]},With[{da=If[Norm[ds-spd]>maxAcc,maxAcc(ds-spd)/Norm[ds-spd],ds-spd]},With[{ns=spd+da},{pos+ns,ns}]]]
Move[st_Dataset]:=st[All,Move[#position,#speed]&]
Move[pos_,spd_]:=With[{new=checkWall[pos+spd,spd]},<|"position"->new[[1]],"speed"->new[[2]]|>]
Move[tgt_List]:=Move[#1,#2,tgt]&
Move[st_Dataset,tgt_]:=st[All,Move[tgt][#position,#speed]&]
Move[pos_,spd_,tgt_]:=With[{dum=Follow[pos,spd,tgt]},With[{new=Apply[checkWall,dum]},<|"position"->new[[1]],"speed"->new[[2]]|>]]

View[st_Dataset,tgt_]:=Graphics[{AbsolutePointSize[2radius],Blue,Point[Normal[st[All,"position"]]],AbsolutePointSize[1.5radius],Red,Point[tgt]},ImageSize->{width,height},Frame->True,FrameTicks->False,PlotRange->{{0,width},{0,height}}]

Anim[st_Dataset,tgt_]:=Manipulate[Refresh[(If[moving,agt=Move[agt,tgt]];View[agt,tgt]),UpdateInterval->If[moving,0,Infinity]],{{moving,False,"run simulation"},{True, False}},{{agt,st},ControlType->None},SaveDefinitions->True,AutorunSequencing->{3},TrackedSymbols:>{moving,agt},SynchronousUpdating->True]

Anim[agents,{150,200}]

Moving target

AnimT[st_Dataset]:=Manipulate[Refresh[(If[moving,agt=Move[agt,tgt]];View[agt,tgt]),UpdateInterval->If[moving,0,Infinity]],{{moving,False,"run simulation"},{True, False}},{{agt,st},ControlType->None},{{tgt,{50,200}},ControlType->Locator,Appearance->None},SaveDefinitions->True,AutorunSequencing->{3},TrackedSymbols:>{moving,agt},SynchronousUpdating->True]

AnimT[agents]