/* * Encog(tm) Core v3.4 - Java Version * http://www.heatonresearch.com/encog/ * https://github.com/encog/encog-java-core * Copyright 2008-2016 Heaton Research, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * For more information on Heaton Research copyrights, licenses * and trademarks visit: * http://www.heatonresearch.com/copyright */ package org.encog.ml.prg.generator; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import org.encog.EncogError; import org.encog.mathutil.randomize.factory.BasicRandomFactory; import org.encog.mathutil.randomize.factory.RandomFactory; import org.encog.ml.CalculateScore; import org.encog.ml.ea.exception.EACompileError; import org.encog.ml.ea.exception.EARuntimeError; import org.encog.ml.ea.population.Population; import org.encog.ml.ea.species.Species; import org.encog.ml.genetic.GeneticError; import org.encog.ml.prg.EncogProgram; import org.encog.ml.prg.EncogProgramContext; import org.encog.ml.prg.ProgramNode; import org.encog.ml.prg.expvalue.ValueType; import org.encog.ml.prg.extension.ProgramExtensionTemplate; import org.encog.ml.prg.train.PrgPopulation; import org.encog.ml.prg.train.ZeroEvalScoreFunction; import org.encog.util.concurrency.MultiThreadable; /** * The abstract base for Full and Grow program generation. */ public abstract class AbstractPrgGenerator implements PrgGenerator, MultiThreadable { /** * An optional scoring function. */ private CalculateScore score = new ZeroEvalScoreFunction(); /** * The program context to use. */ private final EncogProgramContext context; /** * The maximum depth to generate to. */ private final int maxDepth; /** * The minimum const to generate. */ private double minConst = -10; /** * The maximum const to generate. */ private double maxConst = 10; /** * True, if the program has enums. */ private final boolean hasEnum; /** * The actual number of threads to use. */ private int actualThreads; /** * The number of threads to use. */ private int threads; /** * The contents of this population, stored in rendered form. This prevents * duplicates. */ private final Set<String> contents = new HashSet<String>(); /** * A random number generator factory. */ private RandomFactory randomFactory = new BasicRandomFactory(); /** * The maximum number of allowed generation errors. */ private int maxGenerationErrors = 500; /** * Construct the generator. * * @param theContext * The context that is to be used for generation. * @param theMaxDepth * The maximum depth to generate to. */ public AbstractPrgGenerator(final EncogProgramContext theContext, final int theMaxDepth) { if (theContext.getFunctions().size() == 0) { throw new EncogError("There are no opcodes defined"); } this.context = theContext; this.maxDepth = theMaxDepth; this.hasEnum = this.context.hasEnum(); } /** * Add a population member from one of the threads. * * @param population * The population to add to. * @param prg * The program to add. */ public void addPopulationMember(final PrgPopulation population, final EncogProgram prg) { synchronized (this) { final Species defaultSpecies = population.getSpecies().get(0); prg.setSpecies(defaultSpecies); defaultSpecies.add(prg); this.contents.add(prg.dumpAsCommonExpression()); } } /** * Attempt to create a genome. Cycle the specified number of times if an * error occurs. * * @param rnd The random number generator. * @param pop The population. * @return The generated genome. */ public EncogProgram attemptCreateGenome(final Random rnd, final Population pop) { boolean done = false; EncogProgram result = null; int tries = this.maxGenerationErrors; while (!done) { result = generate(rnd); result.setPopulation(pop); double s; try { tries--; s = this.score.calculateScore(result); } catch (final EARuntimeError e) { s = Double.NaN; } if( !pop.getRules().isValid(result)) { s = Double.NaN; } if (tries < 0) { throw new EncogError("Could not generate a valid genome after " + this.maxGenerationErrors + " tries."); } else if (!Double.isNaN(s) && !Double.isInfinite(s) && !this.contents.contains(result.dumpAsCommonExpression())) { done = true; } } return result; } /** * Create a random note according to the specified paramaters. * @param rnd A random number generator. * @param program The program to generate for. * @param depthRemaining The depth remaining to generate. * @param types The types to generate. * @param includeTerminal Should we include terminal nodes. * @param includeFunction Should we include function nodes. * @return The generated program node. */ public ProgramNode createRandomNode(final Random rnd, final EncogProgram program, final int depthRemaining, final List<ValueType> types, final boolean includeTerminal, final boolean includeFunction) { // if we've hit the max depth, then create a terminal nodes, so it stops // here if (depthRemaining == 0) { return createTerminalNode(rnd, program, types); } // choose which opcode set we might create the node from final List<ProgramExtensionTemplate> opcodeSet = getContext() .getFunctions().findOpcodes(types, getContext(), includeTerminal, includeFunction); // choose a random opcode final ProgramExtensionTemplate temp = generateRandomOpcode(rnd, opcodeSet); if (temp == null) { throw new EACompileError( "Trying to generate a random opcode when no opcodes exist."); } // create the child nodes final int childNodeCount = temp.getChildNodeCount(); final ProgramNode[] children = new ProgramNode[childNodeCount]; if (temp.getNodeType().isOperator() && children.length >= 2) { // for an operator of size 2 or greater make sure all children are // the same time final List<ValueType> childTypes = temp.getParams().get(0) .determineArgumentTypes(types); // now create the children of a common type for (int i = 0; i < children.length; i++) { children[i] = createNode(rnd, program, depthRemaining - 1, childTypes); } } else { // otherwise, let the children have their own types for (int i = 0; i < children.length; i++) { final List<ValueType> childTypes = temp.getParams().get(i) .determineArgumentTypes(types); children[i] = createNode(rnd, program, depthRemaining - 1, childTypes); } } // now actually create the node final ProgramNode result = new ProgramNode(program, temp, children); temp.randomize(rnd, types, result, getMinConst(), getMaxConst()); return result; } /** * Create a terminal node. * @param rnd A random number generator. * @param program The program to generate for. * @param types The types that we might generate. * @return The terminal program node. */ public ProgramNode createTerminalNode(final Random rnd, final EncogProgram program, final List<ValueType> types) { final ProgramExtensionTemplate temp = generateRandomOpcode( rnd, getContext().getFunctions().findOpcodes(types, this.context, true, false)); if (temp == null) { throw new EACompileError("No opcodes exist for the type: " + types.toString()); } final ProgramNode result = new ProgramNode(program, temp, new ProgramNode[] {}); temp.randomize(rnd, types, result, this.minConst, this.maxConst); return result; } public int determineMaxDepth(final Random rnd) { return this.maxDepth; } /** * {@inheritDoc} */ @Override public EncogProgram generate(final Random rnd) { final EncogProgram program = new EncogProgram(this.context); final List<ValueType> types = new ArrayList<ValueType>(); types.add(this.context.getResult().getVariableType()); program.setRootNode(createNode(rnd, program, determineMaxDepth(rnd), types)); return program; } /** * {@inheritDoc} */ @Override public void generate(final Random rnd, final Population pop) { // prepare population this.contents.clear(); pop.getSpecies().clear(); final Species defaultSpecies = pop.createSpecies(); // determine thread usage if (this.score.requireSingleThreaded()) { this.actualThreads = 1; } else if (this.threads == 0) { this.actualThreads = Runtime.getRuntime().availableProcessors(); } else { this.actualThreads = this.threads; } // start up ExecutorService taskExecutor = null; if (this.threads == 1) { taskExecutor = Executors.newSingleThreadScheduledExecutor(); } else { taskExecutor = Executors.newFixedThreadPool(this.actualThreads); } for (int i = 0; i < pop.getPopulationSize(); i++) { taskExecutor.execute(new GenerateWorker(this, (PrgPopulation) pop)); } taskExecutor.shutdown(); try { taskExecutor.awaitTermination(Long.MAX_VALUE, TimeUnit.MINUTES); } catch (final InterruptedException e) { throw new GeneticError(e); } // just pick a leader, for the default species. defaultSpecies.setLeader(defaultSpecies.getMembers().get(0)); } /** * Generate a random opcode. * @param rnd Random number generator. * @param opcodes The opcodes to choose from. * @return The selected opcode. */ public ProgramExtensionTemplate generateRandomOpcode(final Random rnd, final List<ProgramExtensionTemplate> opcodes) { final int maxOpCode = opcodes.size(); if (maxOpCode == 0) { return null; } int tries = 10000; ProgramExtensionTemplate result = null; while (result == null) { final int opcode = rnd.nextInt(maxOpCode); result = opcodes.get(opcode); tries--; if (tries < 0) { throw new EACompileError( "Could not generate an opcode. Make sure you have valid opcodes defined."); } } return result; } /** * @return the context */ public EncogProgramContext getContext() { return this.context; } /** * @return the maxConst */ public double getMaxConst() { return this.maxConst; } /** * @return the maxDepth */ public int getMaxDepth() { return this.maxDepth; } /** * @return the maxGenerationErrors */ @Override public int getMaxGenerationErrors() { return this.maxGenerationErrors; } /** * @return the minConst */ public double getMinConst() { return this.minConst; } /** * @return the randomFactory */ public RandomFactory getRandomFactory() { return this.randomFactory; } /** * @return the score */ public CalculateScore getScore() { return this.score; } /** * @return The desired number of threads. */ @Override public int getThreadCount() { return this.threads; } /** * @return the hasEnum */ public boolean isHasEnum() { return this.hasEnum; } /** * @param maxConst * the maxConst to set */ public void setMaxConst(final double maxConst) { this.maxConst = maxConst; } /** * @param maxGenerationErrors * the maxGenerationErrors to set */ @Override public void setMaxGenerationErrors(final int maxGenerationErrors) { this.maxGenerationErrors = maxGenerationErrors; } /** * @param minConst * the minConst to set */ public void setMinConst(final double minConst) { this.minConst = minConst; } /** * @param randomFactory * the randomFactory to set */ public void setRandomFactory(final RandomFactory randomFactory) { this.randomFactory = randomFactory; } /** * @param score * the score to set */ public void setScore(final CalculateScore score) { this.score = score; } /** * @param numThreads * The desired thread count. */ @Override public void setThreadCount(final int numThreads) { this.threads = numThreads; } }