The Synchronous Engine

Table of Contents

When building a multi-agent systems, it might be necessary to launch hundred of agents. Until now, that implied in MadKit launching hundred of threads, which is extremely resource-consuming. Thus it was not possible to define artificial-like simulations, hybrid systems, etc with the standard Agent class.

In the previous versions we offered a small simulation toolkit, the so-called ReactiveLib, which was a set of classes in the madkit.lib.reactive package. In this library, a standard MadKit agent was encapsulating a small simulation engine. There was numerous design flaws with this library: simulated entities were not MadKit agents (thus having no access to the messaging or agent/group/role API), the visualization mechanisms were hard-coded, the agents homogeneous, etc..

Thus, we wrote from scratch a new engine to permit development of agents that are not associated with a thread, and can be scheduled by an external entity and easily monitored from the outside.

Architecture

The synchronous engine provides 5 essential building blocks. The following figure shows an illustration of this architecture:

Figure 6.1. Synchronous Engine Example

The Referenceable Agent

In the synchronous engine, any agent can be scheduled or monitored. However, this requires direct reference access to the corresponding agents (as synchronous execution or monitoring is achieved through method invocation or direct variable access). As a main feature of MadKit is that an agent developer never manipulates directly other agents by reference (only with the AgentAddress), having direct reference access to any agent would be a major security and stability problem. Thus, we require that a developer writing a agent that will be executed in the synchronous engine declares its intention and allows a direct reference. This is made with a empty Java interface, the ReferenceableAgent, which does not require any method implementation: it is just here for typing "yes, I do accept that the class I'm writing can be manipulated through the synchronous engine".

The Scheduler Agent

The Scheduler is a regular MadKit agent. It manages a collection of activators to execute synchronous agents.

The Activator class

This Activator tool class defines a basic scheduling policy. It works in conjunction with the scheduler to get a list of schedulable agent. An activator is configured according to a group and a role. On update() operation, it dynamically discovers the implementation classes of the agents having the given group and roles. These direct references can be used afterwards to directly invoke operations on the agents. Subclasses have to implement the execute() abstract operation, which might be invoked by a Scheduler agent.

The Watcher agent

The principle of the Watcher agent resembles the Scheduler one. A Watcher manages a list of probes, and combines their input in something meaningful. But in contrast with the scheduler, a watcher is not a threaded agent and have to be executed from the outside, with an associated scheduler.

The Probe class

The Probe tool class is the basic code for exploring code of a ReferenceableAgent agent. It is configured with a group, and a role. On update() operation, it dynamically discovers the implementation classes of the agents having the given group and roles. These direct references can be used afterwards to directly invoke operations on the agents. Subclasses have to implement the actual probing mechanism. This mechanism is close to the probe mechanism implemented in other platforms, like Swarm or Cormas

An example

We will take the example of small ants that walk randomly on the ground.

Defining the agent

In this very simple simulation, we will only have one type of agent: the ant. We'll start be defining the agent class

import madkit.kernel.AbstractAgent;
import madkit.kernel.ReferenceableAgent;

public class SmallAnt extends AbstractAgent implements ReferenceableAgent
{
   public double x = 0;
   public double y = 0;

   public void activate()
   {
      joinGroup("mysimu");
      requestRole("mysimu", "ant");
   }

   public void walk()
   {
      x=x+(Math.random()-0.5);
      y=y+(Math.random()-0.5);
   }
}
Minimalist, isn't it ? Things to note in this code is that we inherit from AbstractAgent, and we won't have an associated thread, and the Ant implements (quite easy: there's nothing to do) the ReferenceableAgent interface. In contrast with "regular" MadKit agents, we don't have to implements a "live() method. We just defined our own walk() code.

The scheduler and activator

We now have to decide how our small ant will be run. To this end, we're going to implement our own Scheduler and Activator

The activator

The activator is the basic bloc of synchronous execution in MadKit. In our example, execution is really simple: we just have to execute the walk() method on our ants

import madkit.kernel.Activator;

public class AntActivator extends Activator
{
   public AntActivator(String group, String role)
   {
      super(group, role);
   }  

   public void execute()
   {
      for (int i=0; i < agents.length; i++)
             ((SmallAnt)agents[i]).walk();
   }
}
Our implementation is really simple. We refine the mandatory execute() method to implements our own scheduling mechanism: at each step, we invoke the walk() method. The agents array is something that the Activator class provides us: it holds references to the agents appertaining to the given group and role, only if they implement the ReferenceableAgent interface. That's something the MadKit kernel built for us. Note that we have to cast to SmallAnt as the element type is ReferenceableAgent.

In this example, we suppose that the number of ants remain constant. If not, we would have to call update() on the activator.

The scheduler

We will do two things in one agent to keep the code small: launching the ants, and scheduling them. That is not an obligation, and big simulation system will probably an agent specialized in the configuration of launch of a simulation, and distinct from the scheduler.

import madkit.kernel.Scheduler;

public MySchedulerAgent extends Scheduler
{
   public void activate()
   {
      foundGroup("mysimu");
      requestRole("mysimu","scheduler");
      for (int i = 0; i < 100; i++)
          launchAgent(new SmallAnt(),"An ant", false);
   }

   public void live()
   {
      AntActivator a1 = new AntActivator("mysimu","ant");
      addActivator(a1);

      update();

      while(true)
      {
         pause(100);
         a1.execute();
      }
   }
}
We launched our agent for the simulation in the activate() method. Note that although we used the launchAgent(...), the kernel will not actually "run" the agents by itself as they need to be scheduled. Also, we precised in this call that we don't want MadKit to try to instantiate a hypothetical graphical interface for each of these agents (but that's possible anyway, especially if we want something to control the behavior of these agents).

The live() method is the real thing: we build an instance of our tool class AntActivator and add it to the collection of activators managed by our scheduler. Note that the activators are configured with the group/role mechanism. Then, we call the scheduler update() method which makes the activator gets their agent list according to their configured group and role.

The final part is executing our ants again and again. That's the final while loop were we execute the activator.

Observing the system

Our example is now complete and runs well, but there is a small inconvenience: we can't see anything !

To solve this problem, we could have added some instruction in the ant to trace its behavior, or access it from the scheduler. However, that is not really extensible and hard-coding an observation mechanism could be annoying (what if we want a graphical representation ? and then dump everything in a DBMS ? and then .. ? and everything at the same time ? etc, etc)

The synchronous engine in MadKit introduces a way to observe the system from the outside. It is very similar to the scheduler/activator mechanism.

Building an observer

We would like to have a very simple observer: something that gives us the most extreme points that the ants have reached for each turn.

In this example, we will not build our own probe, although that is something quite easy to do, but just use a generic probe that ship with MadKit in the madkit.lib.simulation package, the NumericProbe, which already implements some basic operations on numeric attributes.

import madkit.lib.simulation.NumericProbe;
import madkit.kernel.Watcher;

public class AntObserver extends Watcher 
{
   NumericProbe p1;
   NumericProbe p2;

   public AntObserver()
   {
       p1=new NumericProbe("x", "mysimu", "ant");
       p2=new NumericProbe("y", "mysimu", "ant");

       addProbe(p1);
       addProbe(p2);
   }

   public void activate()
   {
      joinGroup("mysimu");
      requestRole("mysimu","observer");
      update();
   }    

   public void observeAnts()
   {
      println("Max x and y reached this turn:");
      println(p1.getMax()+" , "+p2.getMax());
   }
}
We just define two numeric probes on the x and y properties of our ants. The NumericProbe extends a special probe of the simulation library package that has the ability to explore through reflection the class of the probed object, so we don't have anything to do to our own code. Also note that we design the ants only by their group and role, thus we could even have different implementation classes, as long as they have a x or y property.

Important

In this version of the synchronous engine, the properties that can be discovered by the reflexive probes are only public fields on the agents. A future version will support accessors functions (with the getXXX() pattern).

Executing the observer

The only thing left is to run the observer. To this end, we will modify the code in our scheduler to execute the observer in addition to the ants. We won't build another Activator, but just reuse an activator which come with MadKit, the SingleMethodActivator. This activator takes as argument a method name, a group and a role and execute the method that has the given name through reflection. We could have used this one for the ants but we wanted to show how to build an activator.

Our scheduler agent become:
import madkit.kernel.Scheduler;
import madkit.lib.simulation.SingleMethodActivator;

public class MySchedulerAgent extends Scheduler
{
    public void activate()
    {
	foundGroup("mysimu");
	requestRole("mysimu","scheduler");
	for (int i = 0; i < 100; i++)
	    launchAgent(new SmallAnt(),"An ant", false);
	
	launchAgent(new AntObserver(),"My observer", true); // a GUI for this one
    }
    
    public void live()
    {
	AntActivator a1 = new AntActivator("mysimu","ant");
	SingleMethodActivator a2 = new SingleMethodActivator("observeAnts","mysimu","observer");
	
	addActivator(a1);
	addActivator(a2);

	update();
	
	while(true)
	    {
		pause(100);
		a1.execute();
		a2.execute();
	    }
    }
}
	    

Launching the simulation

You just have to start the MySchedulerAgent, it will launch the ants and the observer. You can do this in G-Box mode or with the console booter, as we don't use graphical interfaces for our agents.

For instance, here is a short simulation run in console mode
$ java madkit.platform.console.Booter --config test_tutorial.scm
	    Booting MadKit Kernel ...
	    <Daemon> : MadKit/Aalaadin - by O. Gutknecht and J. Ferber (c) 1997-1999
	    <Daemon> :     version: 1.4 
	    <Daemon> : --------------------
	    <Daemon> : Please file bug reports on http://www.lirmm.fr/madkit/
	    <Daemon> : MadKit Agent microKernel is up and running
	    <Daemon> :  (My observer) Max x and y reached this turn:
	    <Daemon> :  (My observer) 0.4915111386577862 , 0.49222698315252467
	    <Daemon> :  (My observer) Max x and y reached this turn:
	    <Daemon> :  (My observer) 0.873452806885152 , 0.9354369551234195
	    <Daemon> :  (My observer) Max x and y reached this turn:
	    <Daemon> :  (My observer) 1.2531099361392506 , 1.1749691286640953
	    <Daemon> :  (My observer) Max x and y reached this turn:
	    ...
	  

Final remarks

This simplistic example does not really show the genericity of the Synchronous Engine. Things are getting really interesting when you have multiple observers show differing point of views on a system, or different groups being scheduled independently, with many agents executed according to their roles, etc..

The bees example in the demo source code shows something more elaborated, while being close to the ant example. For something more complex, see the "TurtleKit" (a StarLogo-like) simulation packs and the corresponding examples.

A final word: the synchronous engine is definitively not restricted to simulations: actually, most of the "classic" agents could be scheduled in synchronous mode by just inheriting from AbstractAgent instead of Agent and having an activator configured on their live() method.