[jrgp | -- Documentation ] |
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.
'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).
left()
method above).
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:
RandIntMin
, RandIntMax
: the integer random
value (randInt
) is picked within the range
RandIntMin..RandIntMax;RandFloatMin
, RandFloatMax
: the float random
variable (randFloat
) is picked within the range
RandFloatMin..RandFloatMaxHere'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.
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.
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.
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]; } }
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; } }