[jrgp -- Documentation ]

How to write a function set.

Basics
External objects
Terminals
Random values
Types
fsd

* Basics *

Writing a function set for 'jrgp' is usually a quick and easy operation. The basic concept is that you write a java class containing methods that do with their arguments exactly what your GPFunctions should do with their children (these methods should be public and not static). For example:

public float add(float x, float y) { return x+y; }
creates a gap.GPFunction subclass that evaluates its children and returns their sum [equivalent handwritten version].

Then to extract the method to a GPFunction, one can open the enclosing class (subtree) in gool and use the 'to gp-function' popup option (see gool how-to). The class must be present in the project directory. Alternatively one can use the command line fsd tool , gool simply uses this behind the scene.

If you want to explicitly control the evaluation flow of your program node, your method can declare arguments of type jrgp.fsd.Child and then evaluate them with arg.eval(). For example:

public float if_(float cond, Child then_instr, Child else_instr) {
 if (cond == 1.0f)
  then_instr.eval();
 else
  else_instr.eval();
 return 0.0f;
}

The created GPFunction will evaluate its first branch; if the result is equal to 1.0f it evaluates the second branch, else the third one. Note that one of the branches does not get evaluated. Here's another possible application:

public float while_(Child cond, Child loop, Child leave) {
 for (int i=0; i<10 && cond.eval()!=1.0f; i++)
  loop.eval();
 return leave.eval();
}

Note that the cond and the loop branches could be evaluated more than once.

External objects *

'jrgp' is so designed that you can easily control external objects. If the first argument of your methods is not a float but an object, it can be used by your function nodes. Note that you can specify one object type per function set, as this object is passed to all the program nodes. (If you want to use more than one, just define a container object.)

For example in the function set of the automatic ant problem we defined an external AntRobot object. The evaluator of this problem creates an AntRobot object and then calls

progr.exec(antRobotInstance, null);
for each program of the population. All the nodes in a program tree can drive the robot in this way: e.g.
public float left(AntRobot ant) {
 ant.left();
 return 0.0f;
}

The corresponding GPTerminal calls the external AntRobot method left()and then returns 0.

Using the '--ref' mode of the command line fsd tool one can construct GPFunctions that invoke specific methods on the external object. Conversion from and to float for the arguments and the return value are suitably handled. (See details).

Terminals *

To specify a terminal simply write a function without arguments or with just a non-float argument (e.g. the left() method above).

Random values *

One often needs nodes that represent constants or parameters. For such a node, you need to obtain a random value at creation time. You can easily obtain this by having your class implementing the jrgp.fsd.Randomize interface, then your methods can freely refer to the magic (static) member randInt or randFloat, for getting an integer or float random value respectively.

Further specifying static fields beginning with the name of your method and followed by one of this postfixes:

You can omit both of them or just the min value (which defaults to 0); the max float value defaults to 1.0f.

Here's an example:

public class FunctionSet extends jrgp.fsd.Randomize {
 //...
 public float fConst() { return randFloat; }
 static public float fConstRandFloatMax = 12.0f;
}

When a FConst node is created in a program tree, it receives a float number in the range 0.0f..12.0f, to which it refers through randFloat and simply returns during execution.

Types *

One of the main features of 'gap' is the possibility to define strong typed nodes. As for the random values, you can specify the type and the compatibility table of each function using static fields as in this example:

public class Abc2 {
 public final static int A_TYPE = 101;
 public final static int B_TYPE = 102;
 public final static int C_TYPE = 103;    
 
 public float a(float arg) { return arg; }
 public static int aType = A_TYPE;
 public static int[][] aCompatibility = { {B_TYPE} }; 
 
 public float b(float arg) { return arg; }
 public static int bType = B_TYPE;
 public static int[][] bCompatibility = { {C_TYPE} };   
 
 public float c(float arg) { return arg; }
 public static int cType = C_TYPE;
 public static int[][] cCompatibility = { {A_TYPE} };  

 public float ta() { return A_TYPE; }
 public static int taType = A_TYPE;

 public float tb() { return B_TYPE; }
 public static int tbType = B_TYPE;

 public float tc() { return C_TYPE; }
 public static int tcType = C_TYPE;
}
where fooCompatibility and fooType are respectively the compatibility table and type (an int) for the method foo.

This function set can produce programs such as: A( B( C( A( Tb ) ) ) ) . For reference: [An equivalent handwritten version of the GPFunction produced for a].

Remember that a compatibility table (int[][]) contains for each argument an array (int[]) of the allowed types. If you don't specify these values, the default is always the constant gap.GPFunction.NO_TYPE, and the children of the function node can be of any type.

It is recommended to specify the type numbers as public static constants, so that gool can find the corresponding names to display.

fs-d: function set distiller ~

The fsd script for launching the tool should be located in the main 'jrgp' directory. fsd can be invoked this  way (from a command line):
fsd Myfunctionset.class [methodName ...] [--classes-pkg foo.java.pkg] [--classes-dir dir ]

Method names are just something like add ;-) , if they are omitted all the methods in the class will be extracted to GPFunctions.

Optionally one can specify both the java package (with --classes-pkg) and a destination directory (with --classes-dir) for the created classes.

--ref mode ~

fsd --ref mode is invoked like: fsd ActedOn.class methodName ... [--ref-info-class-file OptInfo.class] [--classes-pkgfoo.java.pkg] [--classes-dir dir ]

Here method names are mandatory. --ref-info-class-file can specify a class from which supplementary information (e.g. type, compatibility table etc) is extracted, if this is absent in the target methods class. Anyway the format for supplying it is unchanged. A void method produces a 0.0f returning function. [An example].


/* add */

public class Add extends gap.GPFunction {

  public int getNArgs() { return 2; }

  public float exec(Object o,float[] args) {
    return arg[0].exec(o,args)+arg[1].exec(o,args);
  }

}
/* [Types] a */

public class A extends gap.GPFunction {
  private final static int A_TYPE = 101; // ! the fsd produced code will not contain such
  private final static int B_TYPE = 102; // definitions

  public int getNArgs() { return 1; }

  public float exec(Object o,float[] args) {
    return arg[0].exec(o,args);
  }

  public int getType() { return A_TYPE; }

  private final static int[][] compatibility = { {B_TYPE} };

  public int[] getCompatibility(int arg_no) { return compatibility[arg_no]; }

}

--ref mode example

Given

/* Toy */

public class Toy {
  public void foo(int bar) { ... };

  public static int fooType = 3;

  static public int bar(int foo) { ... };
}

/* ToyInfo */

public class ToyInfo {
  public static int barType = 4;
}

with fsd --ref Toy.class foo bar --ref-info-class-file ToyInfo.class one obtains something equivalent to:

/* foo */
public class Foo extends gap.GPFunction {

  public int getNArgs() { return 1; }

  public float exec(Object o,float[] args) {
    ((Toy)o).foo((int)arg[0].exec(o,args));
    return 0;
  }
 
  public getType() { return 3; }

}

/* bar */
public class Bar extends gap.GPFunction {

  public int getNArgs() { return 1; }

  public float exec(Object o,float[] args) {
    return (int)Toy.bar((int)arg[0].exec(o,args));
  }
 
  public getType() { return 4; }

}