summaryrefslogtreecommitdiffstats
path: root/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
diff options
context:
space:
mode:
Diffstat (limited to 'watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java')
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java341
1 files changed, 341 insertions, 0 deletions
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
new file mode 100644
index 0000000..486f10d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
@@ -0,0 +1,341 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// 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.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Base class for {@link EvolutionEngine} implementations.
+ * @param <T> The type of entity evolved by the evolution engine.
+ * @author Daniel Dyer
+ * @see CandidateFactory
+ * @see FitnessEvaluator
+ */
+public abstract class AbstractEvolutionEngine<T> implements EvolutionEngine<T>
+{
+ // A single multi-threaded worker is shared among multiple evolution engine instances.
+ private static FitnessEvaluationWorker concurrentWorker = null;
+
+ private final Set<EvolutionObserver<? super T>> observers = new CopyOnWriteArraySet<EvolutionObserver<? super T>>();
+
+ private final Random rng;
+ private final CandidateFactory<T> candidateFactory;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+
+ private volatile boolean singleThreaded = false;
+
+ private List<TerminationCondition> satisfiedTerminationConditions;
+
+
+ /**
+ * Creates a new evolution engine by specifying the various components required by
+ * an evolutionary algorithm.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ protected AbstractEvolutionEngine(CandidateFactory<T> candidateFactory,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ Random rng)
+ {
+ this.candidateFactory = candidateFactory;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.rng = rng;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolve(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ seedCandidates,
+ conditions).get(0).getCandidate();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ if (eliteCount < 0 || eliteCount >= populationSize)
+ {
+ throw new IllegalArgumentException("Elite count must be non-negative and less than population size.");
+ }
+ if (conditions.length == 0)
+ {
+ throw new IllegalArgumentException("At least one TerminationCondition must be specified.");
+ }
+
+ satisfiedTerminationConditions = null;
+ int currentGenerationIndex = 0;
+ long startTime = System.currentTimeMillis();
+
+ List<T> population = candidateFactory.generateInitialPopulation(populationSize,
+ seedCandidates,
+ rng);
+
+ // Calculate the fitness scores for each member of the initial population.
+ List<EvaluatedCandidate<T>> evaluatedPopulation = evaluatePopulation(population);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ PopulationData<T> data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+
+ List<TerminationCondition> satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ while (satisfiedConditions == null)
+ {
+ ++currentGenerationIndex;
+ evaluatedPopulation = nextEvolutionStep(evaluatedPopulation, eliteCount, rng);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+ satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ }
+ this.satisfiedTerminationConditions = satisfiedConditions;
+ return evaluatedPopulation;
+ }
+
+
+ /**
+ * This method performs a single step/iteration of the evolutionary process.
+ * @param evaluatedPopulation The population at the beginning of the process.
+ * @param eliteCount The number of the fittest individuals that must be preserved.
+ * @param rng A source of randomness.
+ * @return The updated population after the evolutionary process has proceeded
+ * by one step/iteration.
+ */
+ protected abstract List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng);
+
+
+ /**
+ * Takes a population, assigns a fitness score to each member and returns
+ * the members with their scores attached, sorted in descending order of
+ * fitness (descending order of fitness score for natural scores, ascending
+ * order of scores for non-natural scores).
+ * @param population The population to evaluate (each candidate is assigned
+ * a fitness score).
+ * @return The evaluated population (a list of candidates with attached fitness
+ * scores).
+ */
+ protected List<EvaluatedCandidate<T>> evaluatePopulation(List<T> population)
+ {
+ List<EvaluatedCandidate<T>> evaluatedPopulation = new ArrayList<EvaluatedCandidate<T>>(population.size());
+
+ if (singleThreaded) // Do fitness evaluations on the request thread.
+ {
+ for (T candidate : population)
+ {
+ evaluatedPopulation.add(new EvaluatedCandidate<T>(candidate,
+ fitnessEvaluator.getFitness(candidate, population)));
+ }
+ }
+ else
+ {
+ // Divide the required number of fitness evaluations equally among the
+ // available processors and coordinate the threads so that we do not
+ // proceed until all threads have finished processing.
+ try
+ {
+ List<T> unmodifiablePopulation = Collections.unmodifiableList(population);
+ List<Future<EvaluatedCandidate<T>>> results = new ArrayList<Future<EvaluatedCandidate<T>>>(population.size());
+ // Submit tasks for execution and wait until all threads have finished fitness evaluations.
+ for (T candidate : population)
+ {
+ results.add(getSharedWorker().submit(new FitnessEvalutationTask<T>(fitnessEvaluator,
+ candidate,
+ unmodifiablePopulation)));
+ }
+ for (Future<EvaluatedCandidate<T>> result : results)
+ {
+ evaluatedPopulation.add(result.get());
+ }
+ }
+ catch (ExecutionException ex)
+ {
+ throw new IllegalStateException("Fitness evaluation task execution failed.", ex);
+ }
+ catch (InterruptedException ex)
+ {
+ // Restore the interrupted status, allows methods further up the call-stack
+ // to abort processing if appropriate.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return evaluatedPopulation;
+ }
+
+
+
+ /**
+ * <p>Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the evolution engine. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.</p>
+ *
+ * <p>If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.</p>
+ *
+ * <p>If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.</p>
+ *
+ * @throws IllegalStateException If this method is invoked on an evolution engine before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ public List<TerminationCondition> getSatisfiedTerminationConditions()
+ {
+ if (satisfiedTerminationConditions == null)
+ {
+ throw new IllegalStateException("EvolutionEngine has not terminated.");
+ }
+ else
+ {
+ return Collections.unmodifiableList(satisfiedTerminationConditions);
+ }
+ }
+
+
+ /**
+ * Adds a listener to receive status updates on the evolution progress.
+ * Updates are dispatched synchronously on the request thread. Observers should
+ * complete their processing and return in a timely manner to avoid holding up
+ * the evolution.
+ * @param observer An evolution observer call-back.
+ * @see #removeEvolutionObserver(EvolutionObserver)
+ */
+ public void addEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.add(observer);
+ }
+
+
+ /**
+ * Removes an evolution progress listener.
+ * @param observer An evolution observer call-back.
+ * @see #addEvolutionObserver(EvolutionObserver)
+ */
+ public void removeEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.remove(observer);
+ }
+
+
+ /**
+ * Send the population data to all registered observers.
+ * @param data Information about the current state of the population.
+ */
+ private void notifyPopulationChange(PopulationData<T> data)
+ {
+ for (EvolutionObserver<? super T> observer : observers)
+ {
+ observer.populationUpdate(data);
+ }
+ }
+
+
+ /**
+ * By default, fitness evaluations are performed on separate threads (as many as there are
+ * available cores/processors). Use this method to force evaluation to occur synchronously
+ * on the request thread. This is useful in restricted environments where programs are not
+ * permitted to start or control threads. It might also lead to better performance for
+ * programs that have extremely lightweight/trivial fitness evaluations.
+ * @param singleThreaded If true, fitness evaluations will be performed synchronously on the
+ * request thread. If false, fitness evaluations will be performed by worker threads.
+ */
+ public void setSingleThreaded(boolean singleThreaded)
+ {
+ this.singleThreaded = singleThreaded;
+ }
+
+
+ /**
+ * Lazily create the multi-threaded worker for fitness evaluations.
+ */
+ private static synchronized FitnessEvaluationWorker getSharedWorker()
+ {
+ if (concurrentWorker == null)
+ {
+ concurrentWorker = new FitnessEvaluationWorker();
+ }
+ return concurrentWorker;
+ }
+}