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 be 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, 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 an entity that is 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 with the constructor better suiting the provided 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. In no case Alchemist will attempt loading 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 correctly loaded (namely if a class is present in the classpath with the fully qualified name as passed or as 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 the constructors, in order, until one of them provides an instanced object, considering 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; as such 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 get clearer and clearer with the examples in the next sections.

The incarnation key

The key incarnation is mandatory.

It 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 variable 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 seed 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 concurrency (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, 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 where the nodes should can not get):

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 position section list 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 should reflect the simulation physical features. For instance, when a city map is considered Continuous2DEuclidean distance might be no longer suitable. Given two points A e 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 use in the simulation. It relies on the class loading mechanism, it is optional, and if not specified defaults to NoLinks (nodes in the environment don’t get connected). Omitting such key is equivalent to writing any of the following (they are equivalent):

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 displacement sections 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, displaced 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 univariated. 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).