AFAS::Acting and Sensing

From Agent Factory

Jump to: navigation, search

This is lesson 5 of the AF-AgentSpeak language guide.

Contents

Introduction

The core API described above in lesson 1 introduced a number of default actions and sensors that are available to all AF-AgentSpeak agents. For many applications, this API is insufficient for practical purposes. As a result, AF-AgentSpeak includes mechanisms for associating additional sensors and actions with an agent. We will describe each of these mechanisms in the next sections.

Implementing and Using Actions

To implement a custom action, you must first implement that action as a Java class, and then you link the action to the agent via an action construct in AF-AgentSpeak. First, lets have a look at a simple action, and then later, we will look at how to link that action to an agent.

Actions are implemented by extending the com.agentfactory.clf.interpreter.Action class and implementing the execute(...) method. To see what this means in practice, lets create a simple random number generator action:

import com.agentfactory.clf.interpreter.Action;
import com.agentfactory.clf.lang.Predicate;

public class RandomNumber extends Action {
    @Override
    public boolean execute(Predicate activity) {
        int lower = termAsInt(activity, 0);
        int upper = termAsInt(activity, 1);

        int range = upper - lower;
        int number = (int) (Math.random() * range) + lower;
	
        addBelief("mathsResult(random, " + lower + ", " + upper + "," + number +")");
        return true;
    }
}

Whenever an action is invoked, the parameters specified are passed to the execute(...) method in the form of a Predicate object. This object represents the action and all the parameters. To extract the parameters from this object, you should use one of the following helper methods:

  • termAsInt(activity, x): return the x'th parameter cast as an integer.
  • termAsLong(activity, x): return the x'th parameter cast as a long.
  • termAsFloat(activity, x): return the x'th parameter cast as a float.
  • termAsDouble(activity, x): return the x'th parameter cast as a double.
  • termAsString(activity, x): return the x'th parameter cast as a string.

In the above snippet of code is an implementation of an action that takes 2 parameters and generates a random number. The first two lines of code extract the parameters from the action identifier. The next two lines of code generate the random number, and the fifth line creates a belief that contains the result of the action (i.e. the random number that was generated).

We illustrate how actions are used via the simple example below:

#agent Actor

action randomInt(?l, ?h) -> afas.peract.RandomNumber;

+initialized : true <-
    !randomTest(1,10),
    !randomTest(20, 25),
    !randomTest(21, 21);

+!randomTest(?l, ?h) : true <-
    randomInt(?l, ?h),
    query(mathsResult(random, ?l, ?h, ?v)),
    .println("random number: " + ?v);

This agent, known as an "Actor", performs three random number tests for the ranges (1, 10), (20, 25), and (21, 21). The output of this program is something like:

random number: 8
random number: 24
random number: 21

This approach allows you to write individual actions that are entirely self contained. In cases where the action must be related to other actions and/or shared information, then you should implement your actions as part of a module or API. This is described in a later lesson.

Implementing and Using Sensors

Implementing and Using Active Actions

NOTE: This section refers to functionality that has been released as part of version 1.8.0 of AFSE

Active actions are a special type of action that support the introduction of additional variable bindings to the intention stack of the agent. Active actions are implemented by extending the com.agentfactory.clf.interpreter.ActiveAction class as is illustrated in the example below:

import java.util.List;

import com.agentfactory.clf.interpreter.ActiveAction;
import com.agentfactory.clf.interpreter.CoreUtilities;
import com.agentfactory.clf.lang.ITerm;
import com.agentfactory.clf.lang.Predicate;
import com.agentfactory.clf.lang.Variable;
import com.agentfactory.clf.reasoner.Bindings;

public class MyAction extends ActiveAction {
    @Override
    public boolean execute(Predicate activity, List<Variable> variables, Bindings bindings) {
        Variable variable = variables.get(0);
        ITerm value = CoreUtilities.factory.createTerm("rem");
        bindings.addBinding(variable, value);		
        return true;
    }
}

This action implementation works in exactly the same way as a standard action except that you also have the ability to update the variable bindings. This is done by modifying the Bindings object that is passed as an additional parameter. The other new parameter, variables, contains a list of variables (passed from the agent program) of the variables that are to be bound. You do not implement the standard execute(Predicate) method for active actions - the method above replaces this method.

In the implementation the ordering is used rather than the variable name as the variable name can change depending on the AF-AS code given.

An example agent program is the test agent below:

#agent test

action myact->MyAction;

+initialized : true <-
    ?x = myact,
    .println("?x = " + ?x);

As can be seen, the action is declared in the normal way, but the way it is invoked has changed: rather than simply using the action identifier (myact) you must use the assignment (=) plan operator. The variables to be bound are given on the left hand side and the action to be performed is on the right hand side (this may have bound parameters). Once bound, the variable can be used in the normal way. For example, this code would result in the following output:

?x = rem

In cases where the action is either not an active action, or is not an action as all, the behaviour of the assignment operator reverts to the standard form (i.e. the right hand side is treated as a term and is bound to the variable).

A limitation of the above syntax is that only one variable can be bound to a value. As a result, we extend the assignment syntax to allow the left hand side to be either a single variable or an ordered set of variables, for example, the following is equivalent to the program above:

#agent test

action myact->MyAction;

+initialized : true <-
    <?x> = myact,
    .println("?x = " + ?x);

To mirror the modification to assignment, if the right-hand side is not a valid active action, then the right-hand side is treated as a term and all variables are initialized to that term. For example:

#agent test2

+initialized : true <-
    <?x, ?y> = 1,
    ?z = ?x + ?y,
    .println("?x = " + ?x),
    .println("?y = " + ?y),
    .println("?z = " + ?z);

Would result in the following output:

?x = 1
?y = 1
?z = 2

The action itself is executed as a blocking action; the action is executed within the main control thread of the agent. To execute the action as a durative action, you must wrap the action in the durative(...) plan operator as is illustrated below:

#agent dtest

action myact->MyAction;

+initialized : true <-
    <?x> = durative(myact),
    .println("?x = " + ?x);

This causes the active action to be executed in a separate thread and not to block the agent interpreter cycle. However, remember that the intention itself does not progress until the action is completed.

NOTE: the activate plan operator will not accept actions that do not extend the ActiveAction class. If you do attempt to use this plan operator with a standard action, an runtime error will be raised

As a second example, we rewrite the random number agent shown above using active actions. In the revised action code below, the code that generated the mathsResult(...) belief has been replaced by code that adds a variable binding to the bindings.

import java.util.List;
import com.agentfactory.clf.interpreter.ActiveAction;
import com.agentfactory.clf.interpreter.CoreUtilities;
import com.agentfactory.clf.lang.ITerm;
import com.agentfactory.clf.lang.Predicate;
import com.agentfactory.clf.lang.Variable;
import com.agentfactory.clf.reasoner.Bindings;

public class RandomNumber extends ActiveAction {
    public boolean execute(Predicate activity, List<Variable> variables, Bindings bindings) {
        int lower = termAsInt(activity, 0);
        int upper = termAsInt(activity, 1);
        int range = upper - lower;
        int number = (int) (Math.random() * range) + lower;
        Variable variable = variables.get(0);
        ITerm value = CoreUtilities.factory.createTerm(""+number);
        bindings.addBinding(variable, value);
        return true;
    }
}

A corresponding change is made to the agent code to employ the activate plan operator (actually reducing the code by one line):

#agent Actor

action randomInt(?l, ?h) -> RandomNumber;

+initialized : true <-
    !randomTest(1,10),
    !randomTest(20, 25),
    !randomTest(21, 21);

+!randomTest(?l, ?h) : true <-
    ?v = randomInt(?l, ?h),
    .println("random number: " + ?v);

The resultant code is simpler and does not require the artificial belief to be generated to return the results of the action.