How to write your own simulations

Alchemist Simulations

The common language between you and the simulator

Since version 2.0.0, Alchemist uses YAML as primary mean for writing simulations. Historically, it relied on XML instead, but the system has since been deprecated, due to its verbosity and human-unfriendliness. Some resources on how to write a proper Alchemist XML are still available here. Keep in mind, however, that such method is now deprecated and unmaintained, and it is likely to get dropped if any major breakage occurs. If you are new to Alchemist, we recommend to follow the remainder of this page, and learn about the wonders of YAML.

Learn YAML

As a first step, we recommend learning the YAML basics. The language is so simple and human readable that there is probably no better way to learn it than to read it directly. My suggestion is to use the tutorial “Learn X in Y minutes where X = YAML”, it should provide a good YAML guide (surely sufficient to follow the tutorial).

The Alchemist YAML

Alchemist expects a YAML map as input. In the following section, we’ll discuss which keys it expects. Of course, users are free to use all the YAML features (e.g. anchors) to organize their code and reduce duplication.

Class loading with the Alchemist YAML

One important aspect of the Alchemist YAML is the ability to let the user control which actual Java classes should be loaded inside a simulation, and which constructor should be used to do so. Almost every entity of an Alchemist simulation can be instanced using arbitrary Java classes that implement the required interfaces. When the alchemist YAML parser encounters a YAML Map providing the keys type and parameters, it tries to resolve the value of the value associated to type to a class name, then tries to create the object by calling the constructor with parameters most suited to the value of parameters.

Class name resolution

The value associated with type must be a string representing a valid Java identifier. If the value contains one or more . characters, then it will be interpreted as a fully qualified name. If no such character is included, then the default package for the desired alchemist entity will be prefixed. Alchemist won’t ever attempt to load a class situated in the default package (but I am sure good people like you don’t put stuff there, do you?).

Object instancing

If the class gets loaded correctly (meaning if a class is present in the classpath with the fully qualified name, whether it was passed or guessed by Alchemist), then its constructors get sorted based on the number and type of parameters. The system tries to build an object with all each constructor until one of them provides an instanced object, in an order that considers both the current context (namely, the entities that have already been instanced) and the value of parameters.

For instance, imagine that you are trying to build an instance of a Reaction, whose only constructor requires an Environment, a Node, an int and a String. In this case, an Environment and a Node must have already been created (or the YAML loader won’t be at this point). As a consequence, the first two parameters are automatically inferred by the current context and passed to the constructor. The other two parameters can not be inferred this way; instead, the value associated to parameters is used to extract the proper values (if possible). In this case, this would have been a valid parameters entry:

parameters: [4, foo]

As you can easily infer, the value of parameters must be a YAML list.

Don’t despair if the class loading system is still unclear: it is used pervasively and it will become clearer with the examples in the next sections.

The incarnation key

The incarnation key is mandatory. The YAML parser expects a string value, and does not support the class loading mechanism. Such string will be used to get the most similarly named incarnation (the algorithm may vary), namely the subclass of Incarnation whose simple name is closest to the string. The (obvious) suggestion is to use an existing incarnation name, such as sapere or protelis. New incarnations may (and will) be available in future.

Examples

incarnation: sapere
incarnation: protelis

Note: this is also the most minimal valid alchemist specification

The variables key

The variables section lists variable simulation values. A custom variable is defined in a Java class which implements the Variable interface. If no fully qualified variable name is provided for class loading, Alchemist uses the package variables to search for the class.

Examples

variables:
  # Defining variable `random` with its properties
  random: &random
    min: 0
    max: 9
    step: 1
    default: 0
  mape: &mape
    type: Flag
    # Actual parameters of the variable constructor
    parameters: [true]

The seeds key

The seeds section may contains two optional values: scenario and simulation. The former is the seed of the pseudo-random generator used during the creation of the simulation. For instance, perturbating grid nodes in the displacement section. The latter is the seed of the pseudo-random generator used during the simulation. For instance, handling events concurrently (which event occurs before another).

Examples

Setting seeds with integer values.

incarnation: protelis
seeds:
  scenario: 0
  simulation: 1

Setting seeds with variables.

variables:
  random: &random
    min: 0
    max: 9
    step: 1
    default: 0
seeds:
  # reference to the `random` variable
  scenario: *random
  simulation: *random

The environment key

The environment key is used to load the Environment implementation. It is optional and it defaults to a continuous bidimensional space. If no fully qualified environment name is provided for class loading, Alchemist uses the package environments to search for the class.

Examples

The following simulations are equivalent, and load the default environment (which is incarnation independent, here protelis is picked, but it works for any other incarnation as well):

incarnation: protelis
incarnation: protelis
environment:
  type: Continuous2DEnvironment
incarnation: protelis
environment:
  type: it.unibo.alchemist.model.implementations.environments.Continuous2DEnvironment
incarnation: protelis
environment:
  type: Continuous2DEnvironment
  parameters: []

The following simulation loads data from an Openstreetmap file (OSM, XML and PBF formats are supported) located in the classpath in the folder maps:

incarnation: protelis
environment:
  type: OSMEnvironment
  parameters: [/maps/foo.pbf]

The following simulation loads data from a black and white raster image file located in the classpath in the folder images , interpreting the black pixels as obstacles (areas that cannot be accessed by nodes):

incarnation: protelis
environment:
  type: ImageEnvironment
  parameters: [/images/foo.png]

The following simulation loads a personalized class named my.package.FooEnv implementing Environment, whose constructor requires a String and a double:

incarnation: protelis
environment:
  type: my.package.FooEnv
  parameters: [bar, 2.2]

More about the environments shipped with the distribution here.

The positions key

The positions section lists the coordinate types of the simulation. Actually, only one value is taken into account. Each type implements the interface Position. If no fully qualified position name is provided for class loading, Alchemist uses the package positions to search for the class.

The position type should reflect the simulation’s physical features. For instance, when a city map is considered, Continuous2DEuclidean distance might be no longer suitable. Given two points A and B, distance(A, B) may differ from distance(B, A).

Example

positions:
  type: LatLongPosition

The network-model key

The network-model key is used to load the implementation of linking rule to be used in the simulation. It relies on the class loading mechanism, it is optional and defaults to NoLinks (nodes in the environment don’t get connected). Omitting such key is equivalent to writing any of the following:

network-model:
  type: NoLinks
network-model:
  type: it.unibo.alchemist.model.implementations.linkingrules.NoLinks
  parameters: []
network-model:
  type: NoLinks
  parameters: []

If no fully qualified linking rule name is provided for class loading, Alchemist uses the package linkingrules to search for the class.

Example

network-model:
  type: EuclideanDistance
  # Link together all the nodes closer than 100 according to the euclidean
  # distance function
  parameters: [100]

The displacements key

The displacements section lists the node locations at the beginning of the simulation. Each displacement type extends the interface Displacement. If no fully qualified displacement name is provided for class loading, Alchemist uses the package displacements to search for the class.

Examples

A single point located in (0, 0).

displacements:
  # "in" entries, where each entry defines a group of nodes
  - in:
      type: Point
      # Using a constructor taking (x,y) coordinates
      parameters: [0, 0]

10000 nodes, placed in a circle with center in (0, 0) and radius 10.

displacements:
  - in:
      type: Circle
      parameters: [10000, 0, 0, 10]

Nodes are randomly located in a square with a 0.1 distance units long side, centered in the point where the node was previously placed.

displacements:
  - in:
      type: Grid
      parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]

It is possible to set the content of the nodes inside a given region. Only the nodes inside the Rectangle area contain the source and randomSensor molecules (global variables).

displacements:
  - in:
      type: Grid
      parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
    contents:
      - in:
          type: Rectangle
          parameters: [-6, -6, 2, 2]
        molecule: source
        # Concentration = molecule value, any valid stateless protelis program is allowed
        concentration: true
        molecule: value
        # Java imports and method calls are allowed. Pay attention to randomness as
        # it breaks the reproducibility invariant of the simulation
        molecule: randomSensor
        concentration: >
          import java.lang.Math.random
          random() * pi

Nodes can execute a list of protelis programs.

# Variable representing the program to be executed
gradient: &gradient
  - time-distribution: 1
    # Make sure that the program folder is part of the project classpath
    program: program:package:distanceTo
  - program: send
displacements:
  - in:
      type: Grid
      parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
    programs:
      # Reference to the "gradient" list of programs. This program is executed in all
      # the grid nodes
      - *gradient

The export key

The export section lists which simulation values are exported into the folder specified with the -e path/to/folder argument. Data aggregators are statistically univariate. Valid aggregating functions extend AbstractStorelessUnivariateStatistic.

Examples

export:
  # Time step of the simulation
  - time
  # Number of nodes involved in the simulation
  - number-of-nodes
  # Molecule representing an aggregated value
  - molecule: danger
    aggregators: [sum]

Extending the simulation

It is possible to enrich the simulation with custom classes (variable types, displacements, etc). The latter have to be included in the simulation classpath and have to implement/extend the respective interfaces/abstract classes.