Creating a model with Assasim

This article can be seen as a tutorial to develop an agent-based simulation using assasim.

Prerequisite: Having installed assasim

Start by editing the simulation-dev.sh script to provide the editor you want to use in the rest of the tutorial in the $EDITOR variable.

Overview of the developement steps:

  1. Definition of the agents and interactions (in terms of attributes)
  2. First step of precompilation which generates the tools to implement behaviors
  3. Implementation of behaviors by the user
  4. Second step of precompilation which generates the compilable code
  5. Compilation of the model

To begin the development, execute the following command: ./simulation-dev.sh my_model.hpp where my_model.hpp is the name of the model you want to create.

Authorized types

In the following, an authorized type is defined by:

  • int, float, char, bool and variants (e.g. uint64_t)
  • A c struct (i.e. a struct with no methods, no inheritance and no access specifier) with attributes of authorized type.

Warning: you should never use anonymous struct for an authorized type attribute. Always declare your structs using the C++ syntax:

struct MyStruct {
   T1 attr1;
   ...
};

Definition of the agents and interactions

A model is composed of two main objects: agents and interactions.

Defining interactions

Interactions are messages that agents can use to exchange information, give orders etc. You can define an interaction by defining a class which inherites from class Interaction. There are a few restrictions for an interaction to be valid:

  • all the attributes should be public, and of authorized type
  • it should contain no method (except custom constructors).
class Request : public Interaction {
   public:
      int money;
      char order;
      bool overridable;
};

Defining agents

Agents are the players in the simulation. An agent type is defined by a set of attributes which can be public, private or critical (explained below) and a Behavior method which is called for each instance of an agent type at each step of the simulation. For this step, you should only define the attributes of the agents.

An agent type is just a class which inherits from class Agent. There are a few restriction for an agent type to be valid:

  • all the public attributes should be of authorized type
  • private attributes can be of non-authorized type, but in that case you have to bear in mind that the initial value of the attribute is determined by the default constructor of the non-authorized type.
  • you can also add a tag to a public variable (see example below) to make it critical. A critical value is replicated on all the machines and is updated only when it it changes. It can be useful if the attribute will be accessed a lot in one step, and if it does not change very often.

Example:

struct Coordinates {
   int x;
   int y;
};

class Citizen : public Agent {
   public:
      Coordinates pos;
      $critical bool male;
   private:
      int money;
};

First step of precompilation

When you are done with defining the attributes of the interactions and agents, you can close your editor or press enter depending on the mode option you put in the script. It will trigger the first step of precompilation, generating the environment to implement behaviors of agents.

Implementation of behaviors

After the first step of precompilation, the script should have opened two files: your file defining the model that you created and a file “behaviors.cpp”. It is in the latter that you should implement the behavior function. Several syntax shortcuts are provided to handle messages and public attribute access.

Constants

You can use the syntaxe id_ to get the id of the agent and there is a constant T_type which gives the unique id of agent type T.

Public attribute request

You can get the value of public attribute A of agent of type T with id I with the following syntax:

Ts[I].A

Example: to get attribute money of agent of type Citizen with id 42

Citizens[42].pos

You can use this syntax anywhere in the methods of the agent type you consider, in conditions, expressions etc. Note that the id given in the [] can be any valid expression, including variables.

Iterate over ids of existing agents

You can browse all ids of valid agents of type T with the following syntax:

for (auto i : GetAgentsOfType(T_type)) {
    /* your code here treating i as an id of a agent of type T */
}

Example: Make Citizen 0 display all the values of pos.x of the existing Citizens

if (id_ == 0)
   for (auto i : GetAgentsOfType(Citizen_type)) {
      std::cerr << "[" << id_ << "] Citizen " << i << ": " << Citoyens[i].pos.x << std::endl;
   }

Handling messages

If you want to send interactions to an other agent, you first should look at the generated complete constructor in the interaction. Then if you wish to send the interaction defined with the constructor M(a1,…,ak) to agent of type T with id I, you can use the following syntax:

Send(Ts[I],M(a1,...,ak))

Example: to send message Request(42,’a’,true) to agent of type Citizen with id 42

Send(Citizens[42],Request(42,'a',true))

Now if you want to process the received messages of type M, you can use the following C++-11 syntax:

for (const auto &m : received_M)  {
   /* your code here treating m as a message of type M */
}

Example: iterate over the received Request and print the orders received

for (const auto &r : received_Request)  {
   std::cerr << "[" << id_ << "] " << "order " <<  r.order << " received from Citizen " << r.GetSenderId() << std::endl;
}

Compilation

When you have finished implementing behaviors, you can close your editor or press enter depending on the mode option you put in the script. Then the compilable code is generated and the final code is compiled. If no error occurs, the executable can be found in directory path/to/filename.hpp_build.

Running the program using the CLI

Instanciation

First you need to define an instanciation, i.e the initial state of the model. You need to specify the number of agent for each agent types and then you can give the default values of the attributes for each agent type, and even specify for each agent the value of its attributes. A prototype of instanciation file have been generated and is located in the directory path/to/filename.hpp_step2/empty_instance.json. It contains some fields which need to be filled (marked by a “#”). You can copy it and fill it with the values you want. Once it is done, the simulation is ready be launched.

Using the CLI

You can find the sources for the CLI in the folder cli/ in the root directory of Assasim. See the installation guide to compile the program. Then you can launch the program with the following syntax:

./assasim-cli path/to/filename.hpp_build/assasim-simulation <number_of_masters>

Once you are in the CLI, you can set some general variables like the number of threads used or the number of steps executed at each generation run by the user. You can see the list of available commands using autocompletion and the command “help”.

Then you have to specify the instanciation file using the command init path/to/instanciation.json

You can launch the simulation for a certain number of steps with run <number> or you can launch it in background and then pause it at any time. When the simulation is paused, you can export the snapshot of the simulation into json or binary json. You can also convert a snapshot into an instanciation in order to restart your simulation later where you left it.

Finally, when you are done just use the command exit and the simulation is properly killed.

Written on April 12, 2016