SAPERE Incarnation Tutorial

An explanation of the basics of the SAPERE Incarnation is available here. A tutorial similar to the base tutorial, with increasingly rich examples, focused on the SAPERE incarnation. Reference descriptions of the SAPERE LSA language inside the simulator are available here

The tutorial can be found on GitHub. The README.md file of the project explains the use and the steps to follow.

Show README.md

CI CI

Hands-on tutorial with the Alchemist SAPERE incarnation

This tutorial demonstrates how to use Alchemist-SAPERE through a sequence of working examples and increasingly challenging exercises.

Introductory material on simulation concepts and the rationale behind the development of Alchemist and its SAPERE incarnation is available here (PDF).

Further information on writing simulations in Alchemist can be found on the official website.

Prerequisites

Alchemist requires a working installation of Java Runtime Environment 17 or newer. We recommend using the latest LTS OpenJDK from Adoptium.

Prerequisites with Docker

If running via Docker, you do not need to install the JDK.

Instead, ensure that both Docker and Docker Compose are installed.
On macOS and Windows, an X Window System is required to display the simulation interface:

To run the project with Docker:

docker-compose up

You can specify the simulation to be run via the docker-compose.yml file.

Launching simulations

This project uses the Gradle Build Tool.
You don’t need to install Gradle — the provided launch script will download the correct version automatically.

To launch a simulation named SIMNAME, a file named SIMNAME.yml must be located in the src/main/yaml directory.
Gradle will automatically generate a task for each simulation file, which you can run as follows:

./gradlew SIMNAME

If an effect file named SIMNAME.json is present in the effects/ folder, it will be automatically loaded.

> Note
> The environment variable CI is used to determine whether the task is running in a headless continuous integration environment.
> If CI=true is set, the graphical interface will not be launched.

Exercises

Use the provided simulation files and the documentation on the official Alchemist website to complete the following exercises:

  1. Add two nodes to an empty, continuous environment and ensure they are connected.
  2. Create 10,000 nodes randomly placed inside a circle centered at (0,0) with radius 10.
  3. Create a regular grid of nodes from (-5,-5) to (5,5) spaced by (0.25, 0.25), without perturbation.
  4. Create a perturbed version of the grid from Exercise 3.
  5. Insert {token} LSAs in some nodes.
  6. Implement a "dodgeball" program.
  7. Use YAML to write custom sections and refer to them.
  8. Modify the dodgeball program to count passes within the LSA.
  9. Implement an LSA diffusion program so that all nodes eventually contain the {token} LSA using the * operator.
  10. Open 10-math.yml, experiment with it, and manually move nodes around.
  11. Write a gradient propagation program that:
    1. Converts {source} into {gradient, 0} (keep the source).
    2. Diffuses the gradient to neighbors, incrementing the value by #D.
    3. Keeps only the lowest gradient value if multiple copies exist.
    4. Ages the gradient value periodically.
    5. Deletes gradients with values exceeding a threshold (e.g., 20).
  12. Review 12-sets.yml and observe the behavior of set arithmetic.
  13. Modify Exercise 12 to use a custom time distribution (e.g., a DiracComb with parameter 0.5).
  14. Review 14-yaml.vars.yml and understand the variable system.
  15. Open 15-move.yml, run it, and experiment with its parameters.
  16. Run 16-maps.yml (note: first run may take time). Observe complex scenario capabilities, modify as desired, and reflect on results.
Note

Something went wrong along the line? Drop us an issue report and we’ll get back to you.

LSAs

Syntax details are available in the reference. The following code creates an irregular grid of devices, of which those located around the center of such grid contain the tuple { token }:

incarnation: sapere

network-model:
  type: ConnectWithinDistance
  parameters: [0.5]
  
deployments:
  type: Grid
  parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
  contents: # A description of what will be included in the node
    - molecule: hello # Everywhere
    - in: # Restrict the area to...
        type: Rectangle # ...a class implementing Shape with this name...
        parameters: [-1, -1, 2, 2] # ...which can get built with these parameters
      molecule: token # Molecule to inject

The relevant part here is molecule: token. If we wanted to inject the tuple { foo, 1, bar, 2 }, we could have written molecule: foo, 1, bar, 2.

Eco-Laws

Nodes can be programmed with Eco-Laws as follows:

incarnation: sapere

network-model:
  type: ConnectWithinDistance
  parameters: [0.5]

deployments:
  type: Grid
  parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
  contents:
    - in:
        type: Rectangle
        parameters: [-0.5, -0.5, 1, 1]
      molecule: token
  programs: # A list of the sets of reactions programming the node
    - 
      - time-distribution: 1 # Frequency. If the class is not specified, the implementation to use is chosen by the incarnation. The SAPERE incarnation automatically loads ExponentialTime, which takes a number representing the Markovian rate.
      # program lets the incarnation choose the class implementing Reaction, and passes down a string that, when parsed, produces the program
        program: > # ">" begins a multiline string (quote mode)
          {token} --> {firing}
      # If the time distribution is unspecified, the SAPERE incarnation assumes a "ASAP" behavior (rate = Infinity)
      - program: "{firing} --> +{token}"

Eco-Laws can be programmed to send LSAs to neighbors, as well as to look into neighboring nodes for getting LSAs. In order to do so, the LSA template in the Eco-Law must be preceded by a neighbor operator, either + or *.

+ means in a neighbor: if used on the left hand side, it considers the condition satisfied if at least one neighbor has at least one LSA matching the provided template; if used on the right hand side, sends the LSA to one random neighbor.

* means in all neighbors: if used on the left hand side, it considers the condition satisfied if all neighbors have at least one LSA matching the provided template; if used on the right hand side, sends a copy of the LSA to all neighbors.

The following code exemplifies a diffusion program: when { token } is present locally, it is copied into neighboring nodes once per second; and as soon as two copies of { token } are present, one gets removed.

incarnation: sapere
network-model:
  type: ConnectWithinDistance
  parameters: [0.5]
_send: &send
  - time-distribution: 1
    program: >
      {token} --> {token} *{token}
  - program: >
      {token}{token} --> {token}
deployments:
  type: Grid
  parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
  contents:
    in:
      type: Rectangle
      parameters: [-0.5, -0.5, 1, 1]
    molecule: token
  programs: *send

Rates

The time distribution with which reactions should get scheduled can be controlled by thinkering with the yaml specification as per every reaction in Alchemist. If no TimeDistribution is specified, the Eco-Law is assumed to run “as soon as possible” (ASAP).

This may lead to unwanted behaviour. For instance, programming a single node with: --> { foo } will cause the simulation to schedule a reaction producing { foo } at time zero, and at each execution the time will remain zero: the simulator will be producing copies over copies of the tuple, never advancing in time (Alchemist is a discrete event simulator), and possibly going on until the JVM memory limit is reached.

If a number is specified as time distribution, using the time-distribution key, then it will be interpreted as the Markovian rate of an exponentially distributed time.

Other distributions found at it.unibo.alchemist.model.timedistributions can be used leveraging the arbitrary class loading system.

In the following example, two Eco-Laws are configured, and one of them is bound to an ExponentialTime with rate 1, namely, when the reaction can be executed (the left hand LSAs have local matches), it will execute at an average of once per second (with a variance of 1 s²).

incarnation: sapere

network-model:
  type: ConnectWithinDistance
  parameters: [0.5]

deployments:
  type: Grid
  parameters: [-5, -5, 5, 5, 0.25, 0.25, 0.1, 0.1]
  contents:
    - in:
        type: Rectangle
        parameters: [-0.5, -0.5, 1, 1]
      molecule: token
  programs: # A list of the sets of reactions programming the node
    - 
      - time-distribution: 1 # Frequency. If the class is not specified, the implementation to use is chosen by the incarnation. The SAPERE incarnation automatically loads ExponentialTime, which takes a number representing the Markovian rate.
      # program lets the incarnation choose the class implementing Reaction, and passes down a string that, when parsed, produces the program
        program: > # ">" begins a multiline string (quote mode)
          {token} --> {firing}
      # If the time distribution is unspecified, the SAPERE incarnation assumes a "ASAP" behavior (rate = Infinity)
      - program: "{firing} --> +{token}"

Exercise

To better grasp details of the incarnation, we recommend looking at the examples available on the Alchemist SAPERE Incarnation tutorial on GitHub.

Besides examples with growing complexity, there are a number of proposed exercises that should help you get acquainted with the SAPERE way of writing self-organizing behaviors.