Gene expression programming [Java]: How to view the members of the population

262 views Asked by At

I'm using a gene expression programming library demo to obtain alternative mathematical expressions. I downloaded all the class files for uncommons.watchmaker framework and created a new project that runs without jar files. Java Project (full source code) is attached here.

I have modified the demo a little bit to produce alternative mathematical expressions for a given number. For example let's say I want to get all the combinations of numbers between 2 - 11 which would multiply to give 12. I would get 6 * 2, 3 * 4, 3 * 2 * 2, 2 * 6, 4 * 3, 2 * 2 * 3. The main program is TestMainProg.java

I am interested in knowing how to print the population of the final generation.

Findings:

In the watchmaker API it says evolvePopulation() in EvolutionEngine interface can be used to get the final population data. However I am not sure how to invoke the method and print the data. Looking at the EvolutionEngine.java, EvaluatedCandidate.java and AbstractEvolutionEngine.java will be useful.

Below is the code I use:

import java.util.ArrayList;
import java.util.List;
import org.gep4j.GeneFactory;
import org.gep4j.INode;
import org.gep4j.INodeFactory;
import org.gep4j.IntegerConstantFactory;
import org.gep4j.KarvaEvaluator;
import org.gep4j.MutationOperator;
import org.gep4j.RecombinationOperator;
import org.gep4j.SimpleNodeFactory;
import org.gep4j.math.Multiply;
import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.Probability;
import org.uncommons.watchmaker.framework.EvolutionEngine;
import org.uncommons.watchmaker.framework.EvolutionObserver;
import org.uncommons.watchmaker.framework.EvolutionaryOperator;
import org.uncommons.watchmaker.framework.FitnessEvaluator;
import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
import org.uncommons.watchmaker.framework.PopulationData;
import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
import org.uncommons.watchmaker.framework.termination.TargetFitness;

public class TestMainProg {
    final KarvaEvaluator karvaEvaluator = new KarvaEvaluator();
    public INode[] bestIndividual=null;

    public void go() {
        List<INodeFactory> factories = new ArrayList<INodeFactory>();

        // init the GeneFactory that will create the individuals

        //factories.add(new SimpleNodeFactory(new Add()));
        factories.add(new SimpleNodeFactory(new Multiply()));
        factories.add(new IntegerConstantFactory(2, 35)); //12,60,1 and the target number
        double num = 36.0;

        GeneFactory factory = new GeneFactory(factories, 20); //20 is the gene size

        List<EvolutionaryOperator<INode[]>> operators = new ArrayList<EvolutionaryOperator<INode[]>>();
        operators.add(new MutationOperator<INode[]>(factory, new Probability(0.01d)));
        operators.add(new RecombinationOperator<INode[]>(factory, new Probability(0.5d)));
        EvolutionaryOperator<INode[]> pipeline = new EvolutionPipeline<INode[]>(operators);

        FitnessEvaluator<INode[]> evaluator = new FitnessEvaluator<INode[]>() {
            @Override
            public double getFitness(INode[] candidate, List<? extends INode[]> population) {
                double result = (Double) karvaEvaluator.evaluate(candidate);
                double error = Math.abs(num - result);
                return error;
            }

            @Override
            public boolean isNatural() {
                return false;
            }
        };

        EvolutionEngine<INode[]> engine = new GenerationalEvolutionEngine<INode[]>(factory, pipeline, evaluator,
                new RouletteWheelSelection(), new MersenneTwisterRNG());


        // add an EvolutionObserver so we can print out the status.         
        EvolutionObserver<INode[]> observer = new EvolutionObserver<INode[]>() {
            @Override
            public void populationUpdate(PopulationData<? extends INode[]> data) {
                bestIndividual = data.getBestCandidate();
                System.out.printf("Generation %d, PopulationSize = %d, error = %.1f, value = %.1f, %s\n", 
                                  data.getGenerationNumber(), data.getPopulationSize(),
                                  Math.abs(/*Math.PI*/ num - (Double)karvaEvaluator.evaluate(bestIndividual)), 
                                  (Double)karvaEvaluator.evaluate(bestIndividual), 
                                  karvaEvaluator.print(bestIndividual));    

            }
        };
        engine.addEvolutionObserver(observer);

        //to get the total population
        engine.evolvePopulation(100,10,new TargetFitness(0.0001, false));

    }

    public static final void main(String args[]) {
        new TestMainProg().go();        
    }
}
1

There are 1 answers

0
Sheepy On BEST ANSWER

Printing all the correct candidates in the final population is simple:

engine.evolvePopulation(100,10,new TargetFitness(0, false)).stream()
   .filter( e -> e.getFitness() == 0 ) // Find all survivors
   .map( e -> karvaEvaluator.print( e.getCandidate() ) ) // Convert to String
   .forEach( System.out::println ); // Print

Getting multiple two number combinations, however, is more tricky:

  1. GeneFactory with a gene length of 5 or above may produce A x B x C, e.g. 2 x 2 x 9 = 36
  2. Only one correct result is guaranteed in each evolution.

First point should be easy to fix. For the second, we can run the evolution a few times and consolidate the results. There is no guarantee you'll get all combinations, but the more you run the higher chance it will be.

Optimisation tips:
1. Number range should be as small as possible, i.e. 2 to (target/2).
2. Recombination is unnecessary since there is only multiplication.
3. That leaves only (numeric) mutation, which can have a higher chance to occur.

My solution:

import java.util.*;
import java.util.stream.Collectors;
import org.gep4j.*;
import org.gep4j.math.Multiply;
import org.uncommons.maths.random.MersenneTwisterRNG;
import org.uncommons.maths.random.Probability;
import org.uncommons.watchmaker.framework.*;
import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
import org.uncommons.watchmaker.framework.termination.TargetFitness;

public class TestMainProg {
   private static final double NUM = 36.0;
   private static final int RUN = 50;

   public void go() {
      KarvaEvaluator karvaEvaluator = new KarvaEvaluator();

      GeneFactory factory = new GeneFactory( Arrays.asList(
         new SimpleNodeFactory(new Multiply()),
         new IntegerConstantFactory( 2, (int)(NUM/2) )
      ), 3 );

      EvolutionaryOperator<INode[]> pipeline = new EvolutionPipeline<>( Arrays.asList(
         new MutationOperator<>(factory, new Probability(0.5d))
      ) );

      FitnessEvaluator<INode[]> evaluator = new FitnessEvaluator<INode[]>() {
         @Override public double getFitness(INode[] candidate, List<? extends INode[]> population) {
            return Math.abs( NUM - (Double) karvaEvaluator.evaluate(candidate) );
         }
         @Override public boolean isNatural() {
            return false;
         }
      };

      EvolutionEngine<INode[]> engine = new GenerationalEvolutionEngine<>(factory, pipeline, evaluator,
            new RouletteWheelSelection(), new MersenneTwisterRNG());

      Set<String> results = new HashSet<>();
      for ( int i = 0 ; i < RUN ; i ++ ) {
         List<EvaluatedCandidate<INode[]>> finalPopulation =
            engine.evolvePopulation(100,10, new TargetFitness(0, false));
         // Add all survivors to result
         finalPopulation.stream().filter( e -> e.getFitness() == 0 )
            .map( e -> karvaEvaluator.print( e.getCandidate() ) )
            .forEach( results::add );
      }
      new TreeSet( results ).stream().forEach( System.out::println );
   }

   public static final void main(String args[]) {
      new TestMainProg().go();
   }
}