/* * This file is part of JGAP. * * JGAP offers a dual license model containing the LGPL as well as the MPL. * * For licensing information please see the file license.txt included with JGAP * or have a look at the top of class org.jgap.Chromosome which representatively * includes the JGAP license policy applicable for any file delivered with JGAP. */ package org.jgap.gp.impl; import java.io.*; import java.util.*; import org.apache.log4j.*; import org.jgap.*; import org.jgap.gp.*; import org.jgap.util.*; /** * Population for GP programs. * * @author Klaus Meffert * @since 3.0 */ public class GPPopulation implements Serializable, Comparable { /** String containing the CVS revision. Read out via reflection!*/ private final static String CVS_REVISION = "$Revision: 1.41 $"; final static String GPPROGRAM_DELIMITER_HEADING = "<"; final static String GPPROGRAM_DELIMITER_CLOSING = ">"; final static String GPPROGRAM_DELIMITER = "#"; public final static double DELTA = 0.0000001d; private transient Logger LOGGER = Logger.getLogger(GPPopulation.class); /** * The array of GPProgram's that make-up the Genotype's population. */ private IGPProgram[] m_programs; private /*transient*/ float[] m_fitnessRank; private int m_popSize; private transient IGPProgram m_fittestProgram; private GPConfiguration m_config; /** * Indicates whether at least one of the programs has been changed * (deleted, added, modified). */ private boolean m_changed; /** * Indicates that the list of GPPrograms has been sorted. */ private boolean m_sorted; private IGPProgram m_fittestToAdd; /** * Default constructor, only for dynamic instantiation. * * @throws Exception * * @author Klaus Meffert * @since 3.3.4 */ public GPPopulation() throws Exception { } /* * @param a_config the configuration to use. * @param a_size the maximum size of the population in GPProgram unit * * @author Klaus Meffert * @since 3.0 */ public GPPopulation(GPConfiguration a_config, int a_size) throws InvalidConfigurationException { if (a_config == null) { throw new InvalidConfigurationException("Configuration must not be null!"); } if (a_size < 1) { throw new InvalidConfigurationException( "Population size must be greater zero!"); } m_config = a_config; m_programs = new GPProgram[a_size]; m_popSize = a_size; m_fitnessRank = new float[a_size]; for (int i = 0; i < a_size; i++) { m_fitnessRank[i] = 0.5f; } } /* * Instantiate new population with parameters from other population. * Don't copy the GPProgram instances from the given population! * * @param a_pop the population to retrieve the parameters from * * @author Klaus Meffert * @since 3.0 */ public GPPopulation(GPPopulation a_pop) throws InvalidConfigurationException { this(a_pop, false); } /** * * @param a_pop the population to retrieve the parameters from * @param a_keepPrograms true copy programs of given population to this one * @throws InvalidConfigurationException * * @author Klaus Meffert */ public GPPopulation(GPPopulation a_pop, boolean a_keepPrograms) throws InvalidConfigurationException { m_config = a_pop.getGPConfiguration(); m_popSize = a_pop.getPopSize(); m_programs = new GPProgram[m_popSize]; m_fitnessRank = new float[m_popSize]; if (a_keepPrograms) { synchronized (m_programs) { for (int i = 0; i < m_popSize; i++) { m_programs[i] = a_pop.getGPProgram(i); m_fitnessRank[i] = a_pop.getFitnessRank(i); } } m_fittestProgram = a_pop.determineFittestProgramComputed(); if (m_fittestProgram != null) { m_fittestProgram = (IGPProgram) m_fittestProgram.clone(); } setChanged(a_pop.isChanged()); if (!m_changed) { m_sorted = true; } } else { for (int i = 0; i < m_popSize; i++) { m_fitnessRank[i] = 0.5f; } } } /** * Sorts the population into "ascending" order using some criterion for * "ascending". A Comparator is given which will compare two individuals, * and if one individual compares lower than another individual, the first * individual will appear in the population before the second individual. * * @param c the Comparator to use * * @author Klaus Meffert * @since 3.0 */ public void sort(Comparator c) { Arrays.sort(m_programs, c); float f = 0; for (int i = 0; i < m_programs.length; i++) { if (m_fitnessRank.length > i) { m_fitnessRank[i] = f; } if (m_programs[i] != null) { f += m_programs[i].getFitnessValue(); } } } /** * Creates a population. * * @param a_types the type for each chromosome, the length of the array * represents the number of chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each chromosome, * must be an array of arrays, the first dimension of which is the number of * chromosomes and the second dimension of which is the number of nodes * @param a_minDepths contains the minimum depth allowed for each chromosome * @param a_maxDepths contains the maximum depth allowed for each chromosome * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosome * generations during evolution is allowed (true) or not (false) * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public void create(Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean[] a_fullModeAllowed) throws InvalidConfigurationException { create(a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, a_fullModeAllowed, new DefaultProgramCreator()); } /** * Creates a population. * * @param a_types the type for each chromosome, the length of the array * represents the number of chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each chromosome, * must be an array of arrays, the first dimension of which is the number of * chromosomes and the second dimension of which is the number of nodes * @param a_minDepths contains the minimum depth allowed for each chromosome * @param a_maxDepths contains the maximum depth allowed for each chromosome * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosome * generations during evolution is allowed (true) or not (false) * @param a_programCreator service to create new programs with * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.2.2 */ public void create(Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean[] a_fullModeAllowed, IProgramCreator a_programCreator) throws InvalidConfigurationException { create(a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, a_fullModeAllowed, a_programCreator, 0); } /** * Creates a population. * * @param a_types the type for each chromosome, the length of the array * represents the number of chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each chromosome, * must be an array of arrays, the first dimension of which is the number of * chromosomes and the second dimension of which is the number of nodes * @param a_minDepths contains the minimum depth allowed for each chromosome * @param a_maxDepths contains the maximum depth allowed for each chromosome * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosome * generations during evolution is allowed (true) or not (false) * @param a_programCreator service to create new programs with * @param a_offset start index for new programs to put into the configuration * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.3.3 */ public void create(Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_maxNodes, boolean[] a_fullModeAllowed, IProgramCreator a_programCreator, int a_offset) throws InvalidConfigurationException { int divisor; if (m_popSize < 2) { divisor = 1; } else { divisor = m_popSize - 1; } int genNr = getGPConfiguration().getGenerationNr(); int genI = new Random().nextInt(m_popSize); RandomGenerator generator = getGPConfiguration().getRandomGenerator(); int minDepth = getGPConfiguration().getMinInitDepth(); int maxDepth = getGPConfiguration().getMaxInitDepth(); for (int i = a_offset; i < m_popSize; i++) { IGPProgram program = null; // Vary depth dependent on run index. // ---------------------------------- /**@todo add element of randomness*/ int depth = minDepth + (maxDepth - minDepth) * i / divisor; // Create new GP program. // ---------------------- int tries = 0; int maxTries = getGPConfiguration().getProgramCreationMaxtries(); do { try { // Randomize grow option as growing produces a valid program // more likely than the full mode. // --------------------------------------------------------- boolean grow; if (i % 2 == 0 || generator.nextInt(8) > 6) { grow = true; } else { grow = false; } program = create(i, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, depth, grow, a_maxNodes, a_fullModeAllowed, tries, a_programCreator); if (program != null && getGPConfiguration().getPrototypeProgram() == null) { // Remember a prototype of a valid program in case generation // cannot find a valid program within some few tries // --> then clone the prototype. // Necessary if the maxNodes parameter is chosen small or a // validator is used which is quite restrictive. // ---------------------------------------------------------- getGPConfiguration().setPrototypeProgram(program); LOGGER.info("Prototype program set"); } else if (genNr % 5 == 0 && genNr > 0 && i == genI) { /**@todo 5: make configurable*/ // Set prototype to new value after every some evolutions. // ------------------------------------------------------- double protoFitness = getGPConfiguration().getPrototypeProgram(). getFitnessValue(); if (getGPConfiguration().getGPFitnessEvaluator().isFitter(program. getFitnessValue(), protoFitness)) { getGPConfiguration().setPrototypeProgram(program); } } break; } catch (IllegalStateException iex) { if (depth < maxDepth) { depth = depth + generator.nextInt(2); } else { depth = depth - generator.nextInt(4); if (depth < minDepth) { depth = minDepth; } } tries++; if (maxTries >=0 && tries > maxTries || (i > a_offset && tries > 40)) { IGPProgram prototype = getGPConfiguration().getPrototypeProgram(); if (prototype != null) { ICloneHandler cloner = getGPConfiguration().getJGAPFactory(). getCloneHandlerFor(prototype, null); if (cloner != null) { try { program = (IGPProgram) cloner.perform(prototype, null, null); /**@todo only output once*/ LOGGER.warn("Prototype program reused because random" + " program did not satisfy constraints"); break; } catch (Exception ex) { // Rethrow original error. // ----------------------- throw iex; } } else { LOGGER.warn("Warning: no clone handler found for" + " prototype program type " + prototype); } } // Rethrow original error. // ----------------------- throw iex; } } } while (true); setGPProgram(i, program); } setChanged(true); } /** * Creates a population. * * @param a_types Class[] * @param a_argTypes Class[][] * @param a_nodeSets CommandGene[][] * @param a_minDepths int[] * @param a_maxDepths int[] * @param a_depth int * @param a_grow boolean * @param a_maxNodes int * @param a_fullModeAllowed boolean[] * @param a_tries int * @return IGPProgram * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.2.2 */ public IGPProgram create(Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_depth, boolean a_grow, int a_maxNodes, boolean[] a_fullModeAllowed, int a_tries) throws InvalidConfigurationException { return create(0, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_depth, a_grow, a_maxNodes, a_fullModeAllowed, a_tries); } /** * * @param a_programIndex int * @param a_types Class[] * @param a_argTypes Class[][] * @param a_nodeSets CommandGene[][] * @param a_minDepths int[] * @param a_maxDepths int[] * @param a_depth int * @param a_grow boolean * @param a_maxNodes int * @param a_fullModeAllowed boolean[] * @param a_tries int * @return IGPProgram * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.3 */ public IGPProgram create(int a_programIndex, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_depth, boolean a_grow, int a_maxNodes, boolean[] a_fullModeAllowed, int a_tries) throws InvalidConfigurationException { return create(a_programIndex, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_depth, a_grow, a_maxNodes, a_fullModeAllowed, a_tries, new DefaultProgramCreator()); } /** * Creates a valid IGPProgram. No fitness computation is initiated here! * * @param a_programIndex index of the program in the population * @param a_types the type of each chromosome, the length is the number of * chromosomes * @param a_argTypes the types of the arguments to each chromosome, must be an * array of arrays, the first dimension of which is the number of chromosomes * and the second dimension of which is the number of arguments to the * chromosome * @param a_nodeSets the nodes which are allowed to be used by each chromosome, * must be an array of arrays, the first dimension of which is the number of * chromosomes and the second dimension of which is the number of nodes * @param a_minDepths contains the minimum depth allowed for each chromosome * @param a_maxDepths contains the maximum depth allowed for each chromosome * @param a_depth the maximum depth of the program to create * @param a_grow true: grow mode, false: full mode * @param a_maxNodes reserve space for a_maxNodes number of nodes * @param a_fullModeAllowed array of boolean values. For each chromosome there * is one value indicating whether the full mode for creating chromosomes * during evolution is allowed (true) or not (false) * @param a_tries maximum number of tries to get a valid program * @param a_programCreator strategy class to create programs for the * population * * @return valid program * * @throws InvalidConfigurationException * * @author Klaus Meffert * @since 3.0 */ public IGPProgram create(int a_programIndex, Class[] a_types, Class[][] a_argTypes, CommandGene[][] a_nodeSets, int[] a_minDepths, int[] a_maxDepths, int a_depth, boolean a_grow, int a_maxNodes, boolean[] a_fullModeAllowed, int a_tries, IProgramCreator a_programCreator) throws InvalidConfigurationException { // Is there a fittest program to be injected? // ------------------------------------------ if (m_fittestToAdd != null) { IGPProgram program; ICloneHandler cloner = getGPConfiguration().getJGAPFactory(). getCloneHandlerFor(m_fittestToAdd, null); if (cloner == null) { program = (IGPProgram) m_fittestToAdd; } else { try { program = (IGPProgram) cloner.perform(m_fittestToAdd, null, null); } catch (Exception ex) { ex.printStackTrace(); program = (IGPProgram) m_fittestToAdd; } } // Clear out the fittest program to add as it just has been added. // --------------------------------------------------------------- m_fittestToAdd = null; return program; } else { // Create new GP program. // ---------------------- IGPProgram program; program = a_programCreator.create(getGPConfiguration(), a_programIndex, a_types, a_argTypes, a_nodeSets, a_minDepths, a_maxDepths, a_maxNodes, a_depth, a_grow, a_tries, a_fullModeAllowed); return program; } } /** * @return fixed size of the population * * @author Klaus Meffert * @since 3.0 */ public int getPopSize() { return m_popSize; } /** * @return the GPConfiguration set * * @author Klaus Meffert * @since 3.0 */ public GPConfiguration getGPConfiguration() { return m_config; } /** * Sets the given GPProgram at the given index in the list of GPProgram's. * If the given index is exceeding the list by one, the chromosome is * appended. * * @param a_index the index to set the GPProgram in * @param a_program the GPProgram to be set * * @author Klaus Meffert * @since 3.0 */ public void setGPProgram(final int a_index, final IGPProgram a_program) { synchronized (m_programs) { m_programs[a_index] = a_program; } setChanged(true); } public IGPProgram getGPProgram(int a_index) { return m_programs[a_index]; } /** * Sets the GPPrograms of the given population to this population. * * @param a_pop the population to use as template * * @author Klaus Meffert * @since 3.3.3 */ public void setGPPrograms(final GPPopulation a_pop) { synchronized (m_programs) { m_programs = a_pop.m_programs; m_popSize = m_programs.length; } setChanged(true); } /** * Sets the GPPrograms of the given array to this population. * * @param a_progs the programs to set * * @author Klaus Meffert * @since 3.3.3 */ public void setGPPrograms(final IGPProgram[] a_progs) { synchronized (m_programs) { m_programs = a_progs; m_popSize = m_programs.length; } setChanged(true); } /** * Sets the GPPrograms of the given population to this population. * * @param a_pop the population to use as template * * @author Klaus Meffert * @since 3.3.3 */ public void copyGPPrograms(final GPPopulation a_pop) { int size = a_pop.size(); synchronized (m_programs) { for (int i = 0; i < size; i++) { m_programs[i] = a_pop.getGPProgram(i); } } setChanged(true); } /** * Sets the GPPrograms of the given population to this population. * * @param a_pop the population to use as template * * @author Klaus Meffert * @since 3.3.3 */ public void setGPPrograms(final List a_pop) { synchronized (m_programs) { int size = a_pop.size(); m_programs = new GPProgram[size]; for (int i = 0; i < size; i++) { m_programs[i] = (IGPProgram) a_pop.get(i); } } setChanged(true); } public IGPProgram[] getGPPrograms() { return m_programs; } public int size() { return m_programs.length; } /** * Determines the fittest GPProgram in the population (the one with the * highest fitness value) and memorizes it. This is an optimized version * compared to calling determineFittesPrograms(1). * * @return the fittest GPProgram of the population * * @author Klaus Meffert * @since 3.0 */ public IGPProgram determineFittestProgram() { if (!m_changed && m_fittestProgram != null) { return m_fittestProgram; } double bestFitness = -1.0d; IGPFitnessEvaluator evaluator = getGPConfiguration().getGPFitnessEvaluator(); double fitness; for (int i = 0; i < m_programs.length && m_programs[i] != null; i++) { IGPProgram program = m_programs[i]; try { fitness = program.getFitnessValue(); } catch (IllegalStateException iex) { fitness = Double.NaN; } if (!Double.isNaN(fitness) && Math.abs(GPFitnessFunction.NO_FITNESS_VALUE - fitness) > DELTA) { if (m_fittestProgram == null || evaluator.isFitter(fitness, bestFitness)) { bestFitness = fitness; m_fittestProgram = program; } } } setChanged(false); if (m_fittestProgram != null) { IJGAPFactory factory = getGPConfiguration().getJGAPFactory(); if (factory == null) { throw new IllegalStateException("JGAPFactory must not be null!"); } ICloneHandler cloner = factory.getCloneHandlerFor(m_fittestProgram, null); if (cloner != null) { try { m_fittestProgram = (IGPProgram) cloner.perform(m_fittestProgram, null, null); } catch (Exception ex) { ; // ignore } } } return m_fittestProgram; } /** * Determines the fittest GPProgram in the population, but only considers * programs with already computed fitness value. * * @return the fittest GPProgram of the population * * @author Klaus Meffert * @since 3.2 */ public IGPProgram determineFittestProgramComputed() { double bestFitness = -1.0d; IGPFitnessEvaluator evaluator = getGPConfiguration().getGPFitnessEvaluator(); double fitness; IGPProgram fittest = null; for (int i = 0; i < m_programs.length && m_programs[i] != null; i++) { IGPProgram program = m_programs[i]; if (program instanceof GPProgramBase) { GPProgramBase program1 = (GPProgramBase) program; fitness = program1.getFitnessValueDirectly(); } else { fitness = program.getFitnessValue(); } if (Math.abs(fitness - FitnessFunction.NO_FITNESS_VALUE) > FitnessFunction.DELTA) { if (fittest == null || evaluator.isFitter(fitness, bestFitness)) { fittest = program; bestFitness = fitness; } } } return fittest; } /** * Sorts the GPPrograms list and returns the fittest n GPPrograms in * the population. * * @param a_numberOfPrograms number of top performer GPPrograms to be * returned * @return list of the fittest n GPPrograms of the population, or the fittest * x GPPrograms with x = number of GPPrograms in case n > x. * * @author Klaus Meffert * @since 3.0 */ public List determineFittestChromosomes(final int a_numberOfPrograms) { int numberOfChromosomes = Math.min(a_numberOfPrograms, m_programs.length); if (numberOfChromosomes <= 0) { return null; } if (!m_changed && m_sorted) { return Arrays.asList(m_programs).subList(0, numberOfChromosomes); } // Sort the list of chromosomes using the fitness comparator sortByFitness(); // Return the top n chromosomes return Arrays.asList(m_programs).subList(0, numberOfChromosomes); } /** * Sorts the programs within the population according to their fitness * value using GPProgramFitnessComparator. * * @author Klaus Meffert * @since 3.0 */ public void sortByFitness() { // The following construction could be cached but wrt that the // evaluator registered with the configuration could change // --> Don't cache it! sort(new GPProgramFitnessComparator(getGPConfiguration(). getGPFitnessEvaluator())); setChanged(false); setSorted(true); m_fittestProgram = m_programs[0]; } public float[] getFitnessRanks() { return m_fitnessRank; } public float getFitnessRank(int a_index) { return m_fitnessRank[a_index]; } /** * Mark that for the population the fittest program may have changed. * * @param a_changed true: population's fittest program may have changed, * false: fittest program evaluated earlier is still valid * * @author Klaus Meffert * @since 3.0 */ protected void setChanged(final boolean a_changed) { m_changed = a_changed; setSorted(false); } /** * @return true: population's programs (maybe) were changed, * false: not changed for sure * * @since 3.0 */ public boolean isChanged() { return m_changed; } /** * Mark the population as sorted. * @param a_sorted true: mark population as sorted * * @author Klaus Meffert * @since 3.0 */ protected void setSorted(final boolean a_sorted) { m_sorted = a_sorted; } /** * This method is not producing symmetric results as -1 is more often returned * than 1 (see description of return value). * * @param a_pop the other population to compare * @return 1: a_pop is null or having fewer programs or equal number * of programs but at least one not contained. 0: both populations * containing exactly the same programs. -1: this population contains fewer * programs than a_pop * * @author Klaus Meffert * @since 2.6 */ public int compareTo(Object a_pop) { GPPopulation other = (GPPopulation) a_pop; if (a_pop == null) { return 1; } int size1 = size(); int size2 = other.size(); if (size1 != size2) { if (size1 < size2) { return -1; } else { return 1; } } IGPProgram[] progs2 = other.getGPPrograms(); for (int i = 0; i < size1; i++) { if (!containedInArray(progs2, m_programs[i])) { return 1; } } return 0; } /** * Checks if a program is contained within an array of programs. Assumes that * in the array no element will follow after the first null element. * * @param a_progs the array to search thru * @param a_prog the program to find * @return true: program found in array via equals-method * * @author Klaus Meffert * @since 3.0 */ protected boolean containedInArray(IGPProgram[] a_progs, IGPProgram a_prog) { for (int i = 0; i < a_progs.length; i++) { if (a_progs[i] == null) { return false; } if (a_progs[i].equals(a_prog)) { return true; } } return false; } /** * The equals-method. * * @param a_pop the population instance to compare with * @return true: given object equal to comparing one * * @author Klaus Meffert * @since 3.0 */ public boolean equals(Object a_pop) { try { return compareTo(a_pop) == 0; } catch (ClassCastException e) { // If the other object isn't an Population instance // then we're not equal. // ------------------------------------------------ return false; } } /** * Adds a GP program to this Population. Does nothing when given null. * The injection is actually executed in method create(..) * * @param a_toAdd the program to add * * @author Klaus Meffert * @since 3.2 */ public void addFittestProgram(final IGPProgram a_toAdd) { if (a_toAdd != null) { m_fittestToAdd = a_toAdd; } } /** * Clears the list of programs. Normally, this should not be necessary. * But especially in distributed computing, a fresh population has to be * provided sometimes. * * @author Klaus Meffert * @since 3.2 */ public void clear() { for (int i = 0; i < m_programs.length; i++) { m_programs[i] = null; } m_changed = true; m_sorted = true; m_fittestProgram = null; } public boolean isFirstEmpty() { if (size() < 1) { return true; } if (m_programs[0] == null) { return true; } return false; } /** * @return the persistent representation of the population, including all * GP programs * * @author Klaus Meffert * @since 3.2.3 */ public String getPersistentRepresentation() { StringBuffer b = new StringBuffer(); for (IGPProgram program : m_programs) { b.append(GPPROGRAM_DELIMITER_HEADING); b.append(encode( program.getClass().getName() + GPPROGRAM_DELIMITER + program.getPersistentRepresentation())); b.append(GPPROGRAM_DELIMITER_CLOSING); } return b.toString(); } protected String encode(String a_string) { return StringKit.encode(a_string); } protected String decode(String a_string) { return StringKit.decode(a_string); } }