Solving Problems using Genetic Programming with JGAP

Identifying Candidate Problems

Genetic Programming has been applied to a wide variety of problems. In particular, Koza has demonstrated that many problems can be reformulated so that they share a similar software architecture and so that they can be solved with Genetic Programming techniques. This architecture consists of presenting the genetic programming component with a suite of inputs, running the inputs through a series of genetically generated programs, and then measuring the success of each program against the expected answer for the corresponding input. The process is repeated again and again until the success measure meets the exit criteria you’ve provided.

The first challenge is identifying an interesting problem and determining that the selected problem has a reasonable chance for success. The key characteristic about finding a genetic programming solution to a problem is that you are trading computer processing effort against human development effort. There are problems that can be developed with traditional programming that can be also solved with genetic programming. However, you may find in some cases that the effort to create the genetic programming software architecture above is equal to or greater than the effort to create the solution with traditional programming.

However, you might receive by creating a genetic solution to a previously solved problem or a simple problem. I definitely learned about using JGAP by trying to solve a simple problem and to repeat a research result. This tutorial will start by identifying how to use JGAP to find programs using a previously solved genetic problem.

Problem Statement

For this tutorial, we’ll repeat the solution that Koza found in his paper, Evolution of Emergent Cooperative Behavior using Genetic Programming. We’ll first solve this problem by carefully repeating the elements that Koza used in the JGAP system. This means that we’ll program Java classes that emulate the Lisp functions that Koza used in his paper. The intent of starting with this approach is that we can learn how to use JGAP with a problem that has a known solution. Once we’ve learned lessons about using JGAP by repeating this solution, then we can proceed to solving the problem with a more modern approach. At the current time, I’ve only implemented the lisp-like solution and it conversion of this solution to a more modern approach may be a exercise for the reader.

Painted Desert / Ant Problem

The problem consists of 10 ants in a 10x10 toroidal grid with 10 black sand grains, 10 gray sand grains, and 10 striped sand grains. The goal is to find a single program that when run on each ant, moves the grains of sands into the first three columns.

Starting map

Final map with sand aligned into columns

Selecting the Function Set

For a problem with an unknown solution, we need to identify a set of functions that are sufficient to solve the problem. For our problem, we are using the function set identified by Koza.

Terminals = {X, Y, Carrying, SandColor, GO_N, GO_E, GO_S, GO_W, MoveRandom, Pickup}

Functions = {IfLessThanOrEqual, IfLessThanZero, IfDrop}

For the first solution, we’ll be mimicking the Lisp programming language by having each function return an integer value according to the rules in the paper.

Creating the Domain classes

I chose to implement the business logic in two classes. The AntMap contains the knowledge of the locations and the set of ants. It is responsible for loading the problem from a file location that contains the sand and ant locations. It is also responsible for presenting the current state of the map on System.out. The ant class is responsible for any ant specific behavior. The approach of using the domain classes allowed unit testing the behavior outside of the JGAP framework and subsequently creating JGAP based unit tests to compare against the domain behavior.

The domain classes can easily be modified to a more modern approach by changing the return values from int’s to void’s. These procedures can then be mapped to appropriate genetic classes.

Creating the Function Set

Now that we know the functions that we want to try for our program, we need to convert the functions into appropriate JGAP compatible classes. For each terminal and each function listed above there is a corresponding Java class. You can browse the classes in the paintedDesert package. For this tutorial, we’ll focus on the key concepts to integrate with the JGAP framework.

Key JGAP concepts

Class constructor – Each function class needs a constructor that extends from CommandGene. Just like the JGAP anttrail example, we’ve created an AntCommand class to provide the common behavior related to retrieving the AntMap.Somewhere in the construction of the object, the instance must identify the number of arguments and the return type. The number of arguments is the second argument in the CommandGene constructor and passed in as the a_arity of the argument. The third argument in the constructor is the return type. You should use the CommandGene static definitions of types; BooleanClass, IntegerClass, LongClass, FloatClass, and VoidClass. The additional parameters int a_subReturnType and final int[] a_childSubTypes have the following meaning:

Consider a function that returns an integer number, thus it’s return type is declared as IntegerClass (or: Integer.class). Consider further, that you have a function that expects as parameter an Integer typed input. If for any reason you want to avoid that the first mentioned function returning IntegerClass is potentially used as input for the latter mentioned function, you can use different sub types. That means: The function returning IntegerClass could have a subReturnType of, say, 1, whereas the function receiving the input type IntegerClass could have a childSubType that is different from the subReturnType 1. For childSubtype you could use 2, e.g.

In which cases does it make sense to avoid a function with fitting return type being the input of a function expecting this return type as input type? For example, if one function returns an integer number that represents a multiplier which could be in the range 2..5, and the other function expecting an integer number representing the index of an array, being in the range of 0..99.

Function Operations

Arguments The allowable set of argument types are defined in the operation getChildType(IGPProgram a_ind, int a_chromNum). The a_chrom_Num is the key argument. It identifies the 0-based index of the arguments. If you want to support combinations of argument types, then you’ll need to return the appropriate types. For example, if the first argument is a VoidClass and the second argument needs to be an IntegerClass, you would want a method like:

      if (a_chromNum == 0) {

         return CommandGene.VoidClass;

      } else if (a_chromNum == 1) {

         return CommandGene.IntegerClass;

      }

String Representations for the Function Class

There are two mechanisms where the system creates a string representation of the class. The first is the getName() function that returns the name of the function. This version is utilized in the various Exceptions thrown by the CommandGene class. The second mechanism provides feedback on the genetically derived function. The IGPProgram instance function of toStringNorm(int a_startNode) is used to return a string representing the program the JGAP system has created. It hierarchically calls the toString() operation to return the program representation.

Putting it all Together

We’ve analyzed our problem and created our functions that are hopefully sufficient to solve the problem, now we need to add the logic that I called the “Process Control” in the first diagram.

We’ll extend from the GPProblem class to create our PaintedDesertProblem class. One of the key methods is the GPGenotype create() method. The various options for controlling the learning process will be documented as soon as possible.

Running our Test

The next step is to create a batch command or other mechanism to invoke the PaintedDesertProblem’s main subprogram. However, if your experience is like mine, then the program runs and it doesn’t solve the problem as you had hoped.

Creating Unit Tests

Like all programming exercises, it’s important to create unit tests. You’re hoping to find the reason why JGAP didn’t solve the problem as you expected. You could have made a mistake in the sufficiency test. Can the available functions be combined to solve the problem? Is your fitness function returning the proper results? What actually is happening in that magic JGAP black box? Is JGAP putting the program together in a way that you expected? Did you find a bug in JGAP?

Or you could have made a traditionally programming mistake? In the Painted Desert problem, the domain object approach allows for traditional junit testing on the Ant and AntMap classes. Certainly, I had my mistakes in the domain classes. The advantage of the existing solution approach is that in addition to the unit tests, I could write a program that demonstrated that the solution works.

I’d like to add a note about the kinds of mistakes you can make. I’ve identified that mistakes were made in the domain classes. I put together some elemental unit test examples to work through the issues. Certainly, the unit test coverage is not complete, but it provides an idea that may help you with your challenge. You might notice that one test case, OneElementGenElementsTest, compares the result of the hand coded solution against a solution using the JGAP function classes. One other confession to note, as simple as this problem is, I made a key mistake in programming the IfDrop class. It did not drop the sand. Therefore, the genetic program could never solve the problem. It was a simple oversight that cost too many hours of frustration and exploration, but helped me understand more about JGAP.

Writing Unit Tests in JGAP

JGAP is a Java program just like your traditional programming approaches. It is possible to create a unit test using the function classes that we created.

      public void testGO_W() throws Exception {
           
GPProgram prog =
new GPProgram(m_gpconf, 3);
           
m_antMap.resetMap();
           
prog.setApplicationData(
m_antMap);
           
ProgramChromosome pc1 =
new ProgramChromosome(m_gpconf, 50, prog);
           
pc1.getFunctions()[0] =
new GO_W(m_gpconf);
           
pc1.redepth();
           
prog.setChromosome(0, pc1);

           
Object[] noargs =
new Object[0];
                       
int answer = prog.execute_int(0, noargs);
           
assertEquals(1,
m_antMap.getAnt().getXpos());
           
assertEquals(1,
m_antMap.getAnt().getYpos());
           
answer = prog.execute_int(0, noargs);
           
assertEquals(0,
m_antMap.getAnt().getXpos());
           
assertEquals(1,
m_antMap.getAnt().getYpos());
           
answer = prog.execute_int(0, noargs);
           
assertEquals(2,
m_antMap.getAnt().getXpos());
           
assertEquals(1,
m_antMap.getAnt().getYpos()); 
     
}

The second argument in new GPProgram(m_gpconf, 3) identifies the number of programs that you are going to add to the GPProgram instance.  One trick is that you must run the redepth() subprogram before you add the program to the GPProgram instance using setChromosome(…). 

Another minor trick is how you handle nesting subfunctions with arguments.  The testSolution subprogram of the OneAntGenElementsTest class is an example of how nesting works.

//          IFLTE (GO-W) (IF-DROP COLOR (IFLTE (GO-W) X (GO-S) (PICK-UP))) (IFLTE X
//          COLOR COLOR (PICK-UP)) (GO-S)

           
ProgramChromosome pc1 =
new ProgramChromosome(m_gpconf, 50, prog);
           
pc1.getFunctions()[0] =
new IfLessThanOrEqual(m_gpconf, CommandGene.IntegerClass);
           
pc1.getFunctions()[1] =
new GO_W(m_gpconf);
           
pc1.getFunctions()[2] =
new IfDrop(m_gpconf, CommandGene.IntegerClass);
           
pc1.getFunctions()[3] =
new SandColor(m_gpconf);
           
pc1.getFunctions()[4] =
new IfLessThanOrEqual(m_gpconf, CommandGene.IntegerClass);
           
pc1.getFunctions()[5] =
new GO_W(m_gpconf);
           
pc1.getFunctions()[6] =
new X(m_gpconf);
           
pc1.getFunctions()[7] =
new GO_S(m_gpconf);
           
pc1.getFunctions()[8] =
new Pickup(m_gpconf, "First Pickup");
           
pc1.getFunctions()[9] =
new IfLessThanOrEqual(m_gpconf, CommandGene.IntegerClass);
           
pc1.getFunctions()[10] =
new X(m_gpconf);
           
pc1.getFunctions()[11] =
new SandColor(m_gpconf);
           
pc1.getFunctions()[12] =
new SandColor(m_gpconf);
           
pc1.getFunctions()[13] =
new Pickup(m_gpconf, "Second Pickup");
           
pc1.getFunctions()[14] =
new GO_S(m_gpconf);
           
pc1.redepth();
           
prog.setChromosome(0, pc1);

If the function class requires one or more arguments, then the classes are added in the following array elements and are used by the nested function class before continuing to add arguments to the parent function class.

Summary

Genetic programming with JGAP can work if you’ve set your problem up correctly. Debugging a program that the system automatically created offers a significant challenge. To combat the delegation of control to JGAP, consider writing unit tests that check your domain logic and seriously consider checking your genetic functions with JGAP unit tests.

[JGAP Home]
SourceForge Logo