Skip to main content

Simulate a MAS with Alchemist

The alchemist-jakta-incarnation module runs JaKtA nodes as devices of an Alchemist simulation: each Alchemist node hosts a JaKtA node, agents run in simulated time, and messages travel over Alchemist's network model. It is the way to evaluate a MAS at scale, in a reproducible way. It requires Java 17+.

Limitations
  • The entry point is found by reflection: it must be public, and return the result of device(...).
  • Agents cannot be removed from a node during a simulation yet.
  • Behaviour must be defined in JaKtA: Alchemist actions and conditions cannot be set from the YAML file, where only the time distribution (a fixed rate) can be configured.
Complete example

The alchemist-ping-pong example has Alice and Bob play ping-pong from two devices: run it with ./gradlew :examples:alchemist-ping-pong:run to watch it in Alchemist's viewer.

Dependencies​

dependencies {
implementation("it.unibo.jakta:alchemist-jakta-incarnation:<VERSION>")
implementation("it.unibo.jakta:jakta-core:<VERSION>")
}

Define the device​

An entry point is an extension function on JaktaForAlchemistRuntime that builds a device with device(NodeBuilders.alchemistNode()) { node { ... } }; agents are defined inside as usual. device and alchemistNode come from it.unibo.jakta.dsl, JaktaForAlchemistRuntime from it.unibo.alchemist.jakta.properties.

@file:JvmName("PingPong")

package my.simulation

fun <P : Position<P>> JaktaForAlchemistRuntime<P>.entrypoint() = device(NodeBuilders.alchemistNode()) {
node {
context(MessagingSkill(node)) {
agent(BaseAgentID("Alice")) {
// as in any other node
}
}
}
}

Use simulated time​

delay advances in simulated time, where a time unit is a second, and so do timeouts: those of agent.wait, of the KQML asks, and withTimeout. alchemistEnvironment gives access to the Alchemist environment and simulation:

} triggers {
agent.print("Time: ${alchemistEnvironment.simulation.time}")
delay(1500.milliseconds)
agent.print("Time after delay: ${alchemistEnvironment.simulation.time}")
}

The program's time-distribution (see below) is how often the agent thinks: with a rate of 10, it takes a reasoning step every 0.1 time units. An agent only notices time passing when it thinks, so a delay or timeout ends at its first step at or after the deadline: with a rate of 1, a 1.5 s delay ends after 2 s.

Configure the simulation​

Select the jakta incarnation in the Alchemist YAML file, and point each program to the entry point (the JVM class name set by @file:JvmName, followed by the function name):

incarnation: jakta

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

deployments:
- type: Point
parameters: [ 2, 2 ]
programs:
- program: my.simulation.PingPong.entrypoint
time-distribution: 1

Then run it as any Alchemist simulation, e.g. programmatically:

val simulation = LoadAlchemist.from(yamlUrl).getDefault<Any, Euclidean2DPosition>()
simulation.play()
simulation.run()

To watch it, add Alchemist's viewer (it.unibo.alchemist:alchemist-swingui, at Alchemist's version) and attach it to the simulation before running it:

val viewer = SwingGUI<Any, Euclidean2DPosition>(simulation.environment)
viewer.isRealTime = true
simulation.addOutputMonitor(viewer)