-
Notifications
You must be signed in to change notification settings - Fork 0
Quick tutorial
In this tutorial, we'll cover the basics of writing a MASON simulation using BioSim, including:
- Writing the code for a simple obstacle avoiding ant
- Setting up an environment with obstacles and points-of-interest
- Running the simulation using MASONs built in console
We'll write a simple simulation of obstacle avoiding ants in a small enclosed arena. You can follow along with the tutorial to create the code step-by-step, or you can take a look in the biosim/app/tutorial/ directory and dive right in to the code.
BioSim is designed for quickly writing simulations that explore the behavior of biological entities in specific environments, and it's intended to (broadly) mirror laboratory experiments with animals. To do this we need to specify three components:
- The environment that the animals will be inhabiting.
- The physical characteristics of the animals.
- The behavior which governs how the animals act.
For this tutorial, we'll cover the first and third parts in depth, but we'll leave most of the details of implementing the second part until later. At a high level, we can describe the three parts of the simulation like so:
- environment - A small, flat, rectangular environment with walls at the edges that prevent the ants from climbing out of the arena.
- animals - Our ants will be modeled after Aphaenogaster cockerelli, a common desert ant.
- behavior - Our ants will simply be moving at a constant forward speed while avoiding walls and other ants.
BioSim follows the MASON conventions for naming simulations: Each new simulation should be its own package (under the biosim.app package), and should have the same name as the java class which executes the simulation, so our simulation will be in the biosim.app.tutorial package, and Tutorial.java will set up and run the simulation. The three files in this package are:
- Tutorial.java
- AvoidAnt.java
- AvoidAntLogger.java
AvoidAntLogger.java defines a simple logging interface to record what the ants do at each step of the simulation, and is covered in depth in this section.
Next, we'll introduce the code which implements the obstacle avoiding behavior.
This file will define the obstacle avoiding behavior.
package biosim.app.tutorial;
import biosim.core.agent.Agent;
import biosim.core.body.AbstractAnt;
import sim.util.MutableDouble2D;
public class AvoidAnt implements Agent {We start by creating a subclass of Agent which lets us plug in to any of the Body subclasses, including the AphaenogasterCockerelli class.
public class AvoidAnt implements Agent {
AbstractAnt antBody;
public AvoidAnt(AbstractAnt b){
antBody = b;
}
public void init(){
}
public void finish(){
}Here we define a simple constructor that takes an AbstractAnt as the body which serves as the agents interface to the simulation environment. The init() and finish() methods provide hooks for the agent if there was any clean up necessary at the start or end of a simulation run. For this tutorial it's fine to leave them empty.
public void act(double time){
//our default is to move forward in a straight line
double forwardSpeed = 0.024; //24mm per second straight ahead
double lateralSpeed = 0.0; //Ants *can* move laterally, but ours won't for now
double turningSpeed = 0.0; //no rotational velocity by defaultThe act(...) method is called at each simulated time step to determine what the agent does. The time argument contains the current simulated time (in seconds) since the start of the run. The next three lines of code set up some temporary variables to hold our default action, which in this case is to move straight forward. Note that the convention is that all units should be SI, which is especially important when defining new body classes.
double turningSpeed = 0.0; //no rotational velocity by default
//get a vector towards the nearest thing so we can avoid it
MutableDouble2D ant = new MutableDouble2D();
boolean sawAnt = antBody.getNearestSameTypeVec(ant);
MutableDouble2D wall = new MutableDouble2D();
boolean sawWall = antBody.getNearestObstacleVec(wall);Here we query our sensors using the antBody object. All sensors are pass-by-reference, and return boolean values indicating whether the sensor query worked.
boolean sawWall = antBody.getNearestObstacleVec(wall);
MutableDouble2D avoidPoint = null;
if(!sawWall && !sawAnt){
avoidPoint = null;
} else if (!sawAnt){
avoidPoint = wall;
} else if (!sawWall){
avoidPoint = ant;
} else {
avoidPoint = (ant.lengthSq() < wall.lengthSq())? ant:wall;
}If an ant detects both a wall and another ant, it prioritizes avoiding the wall.
avoidPoint = ant;
}
if(avoidPoint != null){
if(avoidPoint.y > 0) turningSpeed = -40.0*(Math.PI/180.0);
else turningSpeed = 40.0*(Math.PI/180.0);
}
antBody.setDesiredVelocity(forwardSpeed,lateralSpeed,turningSpeed);
}
}The simple rule here is to turn the opposite direction from the detected obstacle/ant. Note that the sensors return an egocentric vector to the detected wall or ant, where the vector <1,0> points in the direction that the subject is facing. Therefore, if the y value of the sensor vector is positive, the obstacle is on the left, and on the right if negative. The ant turns at a constant speed (40 degrees per second, converted to radians per second (SI units)) in the opposite direction. The final call to setDesiredVelocity passes on the desired actions to the antBody, which determines what the actual actions are in the next simulation tick based on potential hard limits and noise.
In this file we'll set up the environmental parameters, instantiate some ants, and start the simulation with MASON's standard GUI console.
package biosim.app.tutorial;
import biosim.core.sim.Simulation;
import biosim.core.sim.Environment;
import biosim.core.sim.RectObstacle;
import biosim.core.body.AphaenogasterCockerelli;
import biosim.core.gui.GUISimulation;
public class Tutorial {
public static final double WIDTH=0.2;
public static final double HEIGHT=0.2;
public static void main(String[] args){
//set up the environment
int numAnts = 10;
Environment env = new Environment(WIDTH,HEIGHT,1.0/30.0);
env.addStaticPOI("nest",WIDTH/2,0.02);
env.addObstacle(new RectObstacle(0.01,0.2), 0.19, 0.0);//east wall
env.addObstacle(new RectObstacle(0.01,0.2), 0.0, 0.0);//west
env.addObstacle(new RectObstacle(0.2,0.01), 0.0, 0.0);//north
env.addObstacle(new RectObstacle(0.2,0.01), 0.0, 0.19);//southThe Environment class is the main point of entry for setting up a simulation in BioSim. It defines all the characteristics of the simulated environment that the agents are placed in to. In the constructor we define the physical dimensions of the environment, as well as the simulation resolution (how much time each simulation step represents). In this example, we have a 20cm by 20cm arena, and we'll be running at about video framerate where each step is 1/30th of a second. Next, we add some static elements to the arena: 4 obstacles that serve as walls, and a point-of-interest that's detectable by the ants representing the entrance to their nest.
env.addObstacle(new RectObstacle(0.2,0.01), 0.0, 0.19);//south
//Create bodies
AphaenogasterCockerelli[] bodies = new AphaenogasterCockerelli[numAnts];
for(int i=0;i<bodies.length;i++){
bodies[i] = new AphaenogasterCockerelli();
env.addBody(bodies[i]);
}Next, we create the physical bodies of the ants, and add them to the environment. By default, env.addBody(...) will iteratively attempt to place the agents at random starting positions within the environment until it finds a location that does not collide with any inserted obstacles. The Environment class has been designed to be subclassed in a fairly intuitive way if you need more control over how elements are added to the environment (see the classdoc).
The AphaenogasterCockerelli class is an example Body subclass that defines the physical characteristics of a common desert ant, and since it's a subclass of AbstractAnt, we can use it with our AvoidAnt agent:
env.addBody(bodies[i]);
}
//Add link agents to bodies
AvoidAnt[] agents = new AvoidAnt[numAnts];
for(int i=0;i<agents.length;i++){
agents[i] = new AvoidAnt(bodies[i]);
bodies[i].setAgent(agents[i]);
}Remember to tell each body about it's agent using setAgent(...).
And finally, lets start the simulation and launch the MASON console:
bodies[i].setAgent(agents[i]);
}
//Create new simulation and turn on logging
Simulation sim = env.newSimulation();
//sim.addLogger(new AvoidAntLogger());
GUISimulation gui = new GUISimulation(sim);
//display the MASON console
gui.createController();
}
}After compiling (and fixing any typos) you should be able to run the simulation from the command line like so: java biosim.app.tutorial.Tutorial, and you should see the familiar MASON console