Tutorial: Environment

The LightJason system architecture does not require any environment, but you can easily write your own.

Based on the asynchronous and parallel execution model in LightJason you have to create thread-safe data structures for your environment to avoid any race condition. Keep in mind that all calls of the environment are done in parallel and asynchronously, so your environment must handle these accesses correctly.

Contents [Hide]

Previous Knowledge

The tutorial consists of three steps:

  1. create the basic agent structure based on the AgentSpeak 15min, Triggering and Actions tutorials
  2. we use the object-actions (internal actions) to pass the calls from the agent to the environment
  3. we create a thread-safe environment which can execute the object-actions from the agent.

Environment

For this example we use a small structure within which the agents should change their positions. The agents can move from one cell to the left or right, but the agents can only move if the cell is free. The number of cells is $1.5 \cdot \text{number of agents}$, so there are guaranteed free cells. The agent position will be set at random on initialisation.

In the environment, we need two structures for storing, both structures must be thread-safe:

  • a storage for the agents (the cell definition) line 14
  • a map to store agent positions for read access line 16

For execution we need two methods:

  • a method for initial set of the agent line 36-50
  • the method for moving, which is used for the agent action line 52-84

With a trigger on line 66-73 we notify all other agents if an agent changes the position in line 79-80. If the agent cannot move because the cell is not empty, we throw an exception on line 83 and this will fail the agent action.


package myagentproject;
import org.lightjason.agentspeak.language.CLiteral;
import org.lightjason.agentspeak.language.CRawTerm;
import org.lightjason.agentspeak.language.instantiable.plan.trigger.CTrigger;
import org.lightjason.agentspeak.language.instantiable.plan.trigger.ITrigger;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReferenceArray;
final class CEnvironment
{
    
    private final AtomicReferenceArray<MyAgent> m_position;
    
    private final Map<MyAgent, Integer> m_agentposition = new ConcurrentHashMap<>();
    
    private final int m_size;
    
    CEnvironment( final int p_size )
    {
        m_size = p_size;
        m_position = new AtomicReferenceArray<>( new MyAgent[(int) ( m_size * 1.5 )] );
    }
    
    final int length()
    {
        return m_size;
    }
    
    final int size()
    {
        return m_position.length();
    }
    
    final boolean initialset( @Nonnull final MyAgent p_agent, final int p_position )
    {
        
        if ( m_agentposition.size() >= m_size )
            return true;
        
        if ( ( p_position < 0 ) || ( p_position >= m_position.length() ) )
            return false;
        if ( m_position.compareAndSet( p_position, null, p_agent ) )
        {
            m_agentposition.put( p_agent, p_position );
            return true;
        }
        return false;
    }
    
    final void move( @Nonnull final MyAgent p_agent, @Nonnull final Number p_value )
    {
        
        
        if ( ( p_value.intValue() < 0 ) || ( p_value.intValue() >= m_position.length() ) )
            throw new RuntimeException( "position index is incorrect" );
        
        if ( m_position.compareAndSet( p_value.intValue(), null, p_agent ) )
        {
            final int l_oldposition = m_agentposition.get( p_agent );
            
            m_position.set( l_oldposition, null );
            m_agentposition.put( p_agent, p_value.intValue() );
            
            final ITrigger l_trigger = CTrigger.from(
                ITrigger.EType.ADDGOAL,
                CLiteral.from(
                    "other/agent-position/changed",
                    CLiteral.from( "from", CRawTerm.from( l_oldposition ) ),
                    CLiteral.from( "to", CRawTerm.from( p_value.intValue() ) )
                )
            );
            
            m_agentposition
                .keySet()
                .parallelStream()
                
                .filter( i -> !i.equals( p_agent ) )
                .forEach( i -> i.trigger( l_trigger ) );
        }
        else
            throw new RuntimeException( "position is not free" );
    }
    
    final int position( @Nonnull final MyAgent p_agent )
    {
        return m_agentposition.get( p_agent );
    }
}

Agent with environment actions

The agent class must define the actions which passed the call to the environment. Based on this definition the action can be called inside the agent script.

Agent class

In this example we implement the action as object-action (internal action) on line 23-28. The method name can be arbitrarily chosen, so the annotation defines the action name. Arguments can be passed with the native Java type inside the method. The method passes the data to the method of the environment object in line 27. The environment object is set by the constructor and stored inside the agent object line 15, 17, 20. All agents references the same environment, because in Java all inheritance of objects are passed as references.


package myagentproject;
import org.lightjason.agentspeak.action.binding.IAgentAction;
import org.lightjason.agentspeak.action.binding.IAgentActionFilter;
import org.lightjason.agentspeak.action.binding.IAgentActionName;
import org.lightjason.agentspeak.agent.IBaseAgent;
import org.lightjason.agentspeak.configuration.IAgentConfiguration;
import javax.annotation.Nonnull;
@IAgentAction
final class MyAgent extends IBaseAgent<MyAgent>
{
    
    private static final long serialVersionUID = -5105593330267677627L;
    
    private final CEnvironment m_environment;
    
    MyAgent( @Nonnull final IAgentConfiguration<MyAgent> p_configuration, @Nonnull final CEnvironment p_environment )
    {
        super( p_configuration );
        m_environment = p_environment;
    }
    
    @IAgentActionFilter
    @IAgentActionName( name = "env/move" )
    private void envmove( @Nonnull final Number p_value )
    {
        m_environment.move( this, p_value );
    }
    
    final int position()
    {
        return m_environment.position( this );
    }
}

Agent AgentSpeak(L++) Script

The agent script can use the action env/move at line 10. The parameter is the new position, but this can fail, so the whole can fail also. Here we encapsulate the action with a plan to handle failure execution. The failure execution starts with the plan line 15-20. Each of the movement plans calls itself to create an infinity loop of moving. In parallel the plan line 21-23 will be executed if another agents have been moved.


!main.
+!main <-
    NewPosition = MyPosition + 1;
    NewPosition = NewPosition % EnvSize;
    !!move( NewPosition )
.
+!move(X) <-
    generic/print( "agent", MyName, "is on position", MyPosition, "move to ", X );
    env/move( X );
    X++;
    X = X % EnvSize;
    !move( X )
.
-!move(X) <-
    Y = MyPosition - 1;
    Y = Y < 0 ? EnvSize + Y : Y;
    generic/print( "agent", MyName, "cannot move from", MyPosition, "to ", X, "try to move to", Y );
    !move(Y)
.
+!other/agent-position/changed( from(X), to(Y) ) <-
    generic/print( "agent", MyName, "get information that other agent has moved from", X, "to", Y )
.

Agent generator with environment

The agent generator is structured for a flexible environment, so with the constructor the environment object is set into line 13, 15, 29


package myagentproject;
import org.lightjason.agentspeak.common.CCommon;
import org.lightjason.agentspeak.generator.IBaseAgentGenerator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
final class MyAgentGenerator extends IBaseAgentGenerator<MyAgent>
{
    
    private final CEnvironment m_environment;
    
    MyAgentGenerator( @Nonnull final InputStream p_stream, @Nonnull final CEnvironment p_environment ) throws Exception
    {
        super(
            
            p_stream,
            Stream.concat(
                
                CCommon.actionsFromPackage(),
                
                CCommon.actionsFromAgentClass( MyAgent.class )
            ).collect( Collectors.toSet() ),
            
            new CVariableBuilder( p_environment )
        );
        m_environment = p_environment;
    }
    
    @Nullable
    @Override
    public final MyAgent generatesingle( @Nullable final Object... p_data )
    {
        
        final MyAgent l_agent = new MyAgent( m_configuration, m_environment );
        
        int l_position = (int) ( Math.random() * m_environment.size() );
        while ( !m_environment.initialset( l_agent, l_position ) )
            l_position = (int) ( Math.random() * m_environment.size() );
        return l_agent;
    }
}

Variable Builder

For creating fast access to data of the environment (size) and agent name (hashcode) we use a variable builder that gets the environment also as reference in line 14, 16, 18. The generate method creates constants for each plan line 23-30. Regarding the environment data, line 28 creates a variable with the size of the environment


package myagentproject;
import org.lightjason.agentspeak.agent.IAgent;
import org.lightjason.agentspeak.language.execution.IVariableBuilder;
import org.lightjason.agentspeak.language.instantiable.IInstantiable;
import org.lightjason.agentspeak.language.variable.CConstant;
import org.lightjason.agentspeak.language.variable.IVariable;
import javax.annotation.Nonnull;
import java.text.MessageFormat;
import java.util.stream.Stream;
final class CVariableBuilder implements IVariableBuilder
{
    
    private final CEnvironment m_environment;
    
    CVariableBuilder( final CEnvironment p_environment )
    {
        m_environment = p_environment;
    }
    
    @Nonnull
    @Override
    public final Stream<IVariable<?>> apply( @Nonnull final IAgent<?> p_agent, @Nonnull final IInstantiable p_runningcontext )
    {
        return Stream.of(
            new CConstant<>( "MyPosition", p_agent.<MyAgent>raw().position() ),
            new CConstant<>( "MyName", MessageFormat.format( "{0}", p_agent.hashCode() ) ),
            new CConstant<>( "EnvSize", m_environment.length() )
        );
    }
}

Reference Solution

If you struggled at some point or wish to obtain our exemplary solution with code documentation of this tutorial, you can download the archive containing the source code and an executable jar file:

We run the example with 5 agents and 5 cycles (different runs returns different outputs)

agent   2.003.087.617   is on position   6   move to    2
agent   544.479.674   is on position   2   move to    3
agent   793.503.139   is on position   5   move to    1
agent   2.135.449.562   is on position   3   move to    4
agent   1.713.875.358   get information that other agent has moved from   3   to   4
agent   1.713.875.358   get information that other agent has moved from   5   to   1
agent   1.713.875.358   is on position   0   move to    1
agent   2.135.449.562   get information that other agent has moved from   5   to   1
agent   2.135.449.562   is on position   4   move to    0
agent   544.479.674   get information that other agent has moved from   3   to   4
agent   544.479.674   get information that other agent has moved from   5   to   1
agent   544.479.674   cannot move from   2   to    3   try to move to   1.0
agent   2.003.087.617   get information that other agent has moved from   3   to   4
agent   2.003.087.617   get information that other agent has moved from   5   to   1
agent   2.003.087.617   cannot move from   6   to    2   try to move to   5.0
agent   793.503.139   is on position   1   move to    2
agent   793.503.139   get information that other agent has moved from   3   to   4
agent   1.713.875.358   cannot move from   0   to    1   try to move to   4.0
agent   2.003.087.617   is on position   6   move to    5.0
agent   544.479.674   is on position   2   move to    1.0
agent   2.135.449.562   cannot move from   4   to    0   try to move to   3.0
agent   793.503.139   cannot move from   1   to    2   try to move to   0.0
agent   1.713.875.358   is on position   0   move to    4.0
agent   1.713.875.358   get information that other agent has moved from   6   to   5
agent   2.003.087.617   is on position   5   move to    1
agent   2.135.449.562   get information that other agent has moved from   6   to   5
agent   2.135.449.562   is on position   4   move to    3.0
agent   544.479.674   get information that other agent has moved from   6   to   5
agent   544.479.674   cannot move from   2   to    1.0   try to move to   1.0
agent   793.503.139   is on position   1   move to    0.0
agent   793.503.139   get information that other agent has moved from   6   to   5
agent   1.713.875.358   cannot move from   0   to    4.0   try to move to   4.0
agent   1.713.875.358   get information that other agent has moved from   4   to   3
agent   2.003.087.617   get information that other agent has moved from   4   to   3
agent   2.135.449.562   is on position   3   move to    4
agent   2.003.087.617   cannot move from   5   to    1   try to move to   4.0
agent   544.479.674   get information that other agent has moved from   4   to   3
agent   544.479.674   is on position   2   move to    1.0
agent   793.503.139   get information that other agent has moved from   4   to   3
agent   793.503.139   cannot move from   1   to    0.0   try to move to   0.0
agent   1.713.875.358   get information that other agent has moved from   3   to   4
agent   1.713.875.358   is on position   0   move to    4.0
Communication previous page
next page About