diff --git a/examples/nb-configuration.xml b/examples/nb-configuration.xml index ae35717d..2db68b70 100644 --- a/examples/nb-configuration.xml +++ b/examples/nb-configuration.xml @@ -14,10 +14,10 @@ That way multiple projects can share the same settings (useful for formatting ru Any value defined here will override the pom.xml file value but is only applicable to the current project. --> project - 2 - 2 - 2 - false + 4 + 4 + 4 + true 100 words 0 @@ -29,33 +29,28 @@ Any value defined here will override the pom.xml file value but is only applicab WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - WRAP_IF_LONG true + WRAP_IF_LONG WRAP_IF_LONG - true true + true NEW_LINE - WRAP_IF_LONG WRAP_IF_LONG + WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - true - 100 WRAP_IF_LONG true LEAVE_ALONE - 2 - 2 - false + 4 NEW_LINE NEW_LINE LEAVE_ALONE WRAP_IF_LONG + 2 diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java b/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java index 8bf600ae..33c761b6 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java @@ -17,21 +17,15 @@ import java.awt.Container; import java.lang.reflect.InvocationTargetException; -import javax.swing.JApplet; -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; +import javax.swing.*; /** * Base class for examples that run as applets. + *

* @author Daniel Dyer */ public abstract class AbstractExampleApplet extends JApplet { - /** - * {@inheritDoc} - */ @Override public void init() { @@ -74,7 +68,8 @@ public void run() catch (InvocationTargetException ex) { ex.getCause().printStackTrace(); - JOptionPane.showMessageDialog(container, ex.getCause(), "Error Occurred", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(container, ex.getCause(), "Error Occurred", + JOptionPane.ERROR_MESSAGE); } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java b/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java index 5c47146b..255312b7 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java @@ -19,20 +19,22 @@ import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver; /** - * Trivial evolution observer for displaying information at the end - * of each generation. + * Trivial evolution observer for displaying information at the end of each generation. + *

* @param The type of entity being evolved. * @author Daniel Dyer */ public class EvolutionLogger implements IslandEvolutionObserver { - public void populationUpdate(PopulationData data) + public void populationUpdate(PopulationData data) { - System.out.println("Generation " + data.getGenerationNumber() + ": " + data.getBestCandidateFitness()); + System.out.println("Generation " + data.getGenerationNumber() + ": " + data. + getBestCandidateFitness()); } - public void islandPopulationUpdate(int islandIndex, PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + PopulationData populationData) { // Do nothing. } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java index 2be66309..1673d2c2 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java @@ -24,28 +24,13 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.concurrent.TimeUnit; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.SpringLayout; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.uncommons.maths.random.MersenneTwisterRNG; import org.uncommons.maths.random.Probability; import org.uncommons.swing.SpringUtilities; import org.uncommons.swing.SwingBackgroundTask; import org.uncommons.watchmaker.examples.AbstractExampleApplet; -import org.uncommons.watchmaker.framework.EvolutionEngine; -import org.uncommons.watchmaker.framework.EvolutionObserver; -import org.uncommons.watchmaker.framework.EvolutionaryOperator; -import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine; -import org.uncommons.watchmaker.framework.PopulationData; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.interactive.InteractiveSelection; import org.uncommons.watchmaker.framework.interactive.Renderer; import org.uncommons.watchmaker.framework.termination.GenerationCount; @@ -54,6 +39,7 @@ /** * Watchmaker Framework implementation of Dawkin's biomorph program. + *

* @author Daniel Dyer */ public class BiomorphApplet extends AbstractExampleApplet @@ -98,31 +84,35 @@ protected void prepareGUI(Container container) * the GUI when it is done. */ private SwingBackgroundTask createTask(final int populationSize, - final int generationCount, - final boolean random) + final int generationCount, + final boolean random) { return new SwingBackgroundTask() { @Override protected Biomorph performTask() { - EvolutionaryOperator mutation = random ? new RandomBiomorphMutation(new Probability(0.12d)) - : new DawkinsBiomorphMutation(); - InteractiveSelection selection = new InteractiveSelection(console, - renderer, - populationSize, - 1); - EvolutionEngine engine = new GenerationalEvolutionEngine(new BiomorphFactory(), - mutation, - selection, - new MersenneTwisterRNG()); + EvolutionaryOperator mutation = random + ? new RandomBiomorphMutation(new Probability(0.12d)) + : new DawkinsBiomorphMutation(); + InteractiveSelection selection = new InteractiveSelection( + console, + renderer, + populationSize, + 1); + EvolutionEngine engine = + new GenerationalEvolutionEngine(new BiomorphFactory(), + mutation, + selection, + new MersenneTwisterRNG()); engine.addEvolutionObserver(new SwingEvolutionObserver( - new GenerationTracker(), 300, TimeUnit.MILLISECONDS)); + new GenerationTracker(), 300, TimeUnit.MILLISECONDS)); return engine.evolve(populationSize, - 0, - new GenerationCount(generationCount)); + 0, + new GenerationCount(generationCount)); } + @Override protected void postProcessing(Biomorph result) { @@ -144,27 +134,25 @@ public static void main(String[] args) new BiomorphApplet().displayInFrame("Watchmaker Framework - Biomporphs Example"); } - /** * Simple observer to update the dialog title every time the evolution advances * to a new generation. */ private final class GenerationTracker implements EvolutionObserver { - public void populationUpdate(final PopulationData populationData) + public void populationUpdate(final PopulationData populationData) { SwingUtilities.invokeLater(new Runnable() { public void run() { selectionDialog.setTitle("Biomorph Selection - Generation " - + (populationData.getGenerationNumber() + 1)); + + (populationData.getGenerationNumber() + 1)); } }); } } - /** * Panel for controlling the evolutionary algorithm parameters. */ @@ -174,6 +162,7 @@ private final class ControlPanel extends JPanel private JSpinner generationsSpinner; private JComboBox mutationCombo; + ControlPanel() { super(new BorderLayout()); @@ -198,7 +187,10 @@ private JComponent createInputPanel() inputPanel.add(generationsLabel); inputPanel.add(generationsSpinner); JLabel mutationLabel = new JLabel("Mutation Type: "); - mutationCombo = new JComboBox(new String[]{"Dawkins (Non-random)", "Random"}); + mutationCombo = new JComboBox(new String[] + { + "Dawkins (Non-random)", "Random" + }); mutationCombo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent itemEvent) @@ -232,8 +224,8 @@ private JComponent createButtonPanel() public void actionPerformed(ActionEvent actionEvent) { createTask((Integer) populationSpinner.getValue(), - (Integer) generationsSpinner.getValue(), - mutationCombo.getSelectedIndex() == 1).execute(); + (Integer) generationsSpinner.getValue(), + mutationCombo.getSelectedIndex() == 1).execute(); selectionDialog.setVisible(true); } }); diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java index f718a76d..28102d9e 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java @@ -21,15 +21,17 @@ import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** - * Non-random mutation of a population of biomorphs. This ensures that each selected candidate - * is mutated differently. This is the mutation used by Dawkins in his original experiment. + * Non-random mutation of a population of biomorphs. This ensures that each selected candidate is + * mutated differently. This is the mutation used by Dawkins in his original experiment. + *

* @author Daniel Dyer */ public class DawkinsBiomorphMutation implements EvolutionaryOperator { /** - * Mutate a population of biomorphs non-randomly, ensuring that each selected - * candidate is mutated differently. + * Mutate a population of biomorphs non-randomly, ensuring that each selected candidate is + * mutated differently. + *

* @param selectedCandidates {@inheritDoc} * @param rng A source of randomness (not used since this mutation is non-random). * @return {@inheritDoc} @@ -39,7 +41,7 @@ public List apply(List selectedCandidates, Random rng) List mutatedPopulation = new ArrayList(selectedCandidates.size()); int mutatedGene = 0; int mutation = 1; - for (Biomorph b : selectedCandidates) + for (Biomorph b: selectedCandidates) { int[] genes = b.getGenotype(); @@ -49,8 +51,10 @@ public List apply(List selectedCandidates, Random rng) mutatedGene = (mutatedGene + 1) % Biomorph.GENE_COUNT; } genes[mutatedGene] += mutation; - int min = mutatedGene == Biomorph.LENGTH_GENE_INDEX ? Biomorph.LENGTH_GENE_MIN : Biomorph.GENE_MIN; - int max = mutatedGene == Biomorph.LENGTH_GENE_INDEX ? Biomorph.LENGTH_GENE_MAX : Biomorph.GENE_MAX; + int min = mutatedGene == Biomorph.LENGTH_GENE_INDEX ? Biomorph.LENGTH_GENE_MIN + : Biomorph.GENE_MIN; + int max = mutatedGene == Biomorph.LENGTH_GENE_INDEX ? Biomorph.LENGTH_GENE_MAX + : Biomorph.GENE_MAX; if (genes[mutatedGene] > max) { genes[mutatedGene] = min; diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java index a98cb55f..a057ca1f 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java @@ -22,14 +22,16 @@ import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** - * Mutation operator for biomorphs. Mutates each individual gene - * according to some mutation probability. + * Mutation operator for biomorphs. Mutates each individual gene according to some mutation + * probability. + *

* @author Daniel Dyer */ public class RandomBiomorphMutation implements EvolutionaryOperator { private final Probability mutationProbability; + /** * @param mutationProbability The probability that a given gene * is changed. @@ -49,7 +51,7 @@ public RandomBiomorphMutation(Probability mutationProbability) public List apply(List selectedCandidates, Random rng) { List mutatedPopulation = new ArrayList(selectedCandidates.size()); - for (Biomorph biomorph : selectedCandidates) + for (Biomorph biomorph: selectedCandidates) { mutatedPopulation.add(mutateBiomorph(biomorph, rng)); } @@ -66,7 +68,8 @@ public List apply(List selectedCandidates, Random rng) private Biomorph mutateBiomorph(Biomorph biomorph, Random rng) { int[] genes = biomorph.getGenotype(); - assert genes.length == Biomorph.GENE_COUNT : "Biomorphs must have " + Biomorph.GENE_COUNT + " genes."; + assert genes.length == Biomorph.GENE_COUNT: "Biomorphs must have " + Biomorph.GENE_COUNT + + " genes."; for (int i = 0; i < Biomorph.GENE_COUNT - 1; i++) { if (mutationProbability.nextEvent(rng)) diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java b/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java index 3a6421c4..916b65d8 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java @@ -20,8 +20,8 @@ import org.uncommons.watchmaker.framework.FitnessEvaluator; /** - * A fitness evaluator that simply counts the number of ones in a bit - * string. + * A fitness evaluator that simply counts the number of ones in a bit string. + *

* @see BitString * @author Daniel Dyer */ @@ -29,6 +29,7 @@ public class BitStringEvaluator implements FitnessEvaluator { /** * Calculates a fitness score for the candidate bit string. + *

* @param candidate The evolved bit string to evaluate. * @param population {@inheritDoc} * @return How many bits in the string are set to 1. diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java index 641510cb..08895179 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java @@ -17,13 +17,15 @@ /** * Simple addition operator {@link Node}. + *

* @author Daniel Dyer */ public class Addition extends BinaryNode { /** - * Creates a node that evaluates to the sum of the values of its two - * child nodes ({@literal left} and {@literal right}). + * Creates a node that evaluates to the sum of the values of its two child nodes ({@literal left} + * and {@literal right}). + *

* @param left The first operand. * @param right The second operand. */ @@ -45,9 +47,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public Node simplify() { Node simplifiedLeft = left.simplify(); diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java index 2273f742..efb23142 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java @@ -22,17 +22,20 @@ /** * Convenient base class for {@link Node}s that have two sub-trees. + *

* @author Daniel Dyer */ abstract class BinaryNode implements Node { protected static final double[] NO_ARGS = new double[0]; - - /** The first argument to the binary function. */ + /** + * The first argument to the binary function. + */ protected final Node left; - /** The second argument to the binary function. */ + /** + * The second argument to the binary function. + */ protected final Node right; - private final char symbol; @@ -49,9 +52,6 @@ protected BinaryNode(Node left, Node right, char symbol) } - /** - * {@inheritDoc} - */ public String getLabel() { return String.valueOf(symbol); @@ -88,18 +88,12 @@ public int getWidth() } - /** - * {@inheritDoc} - */ public int countNodes() { return 1 + left.countNodes() + right.countNodes(); } - /** - * {@inheritDoc} - */ public Node getNode(int index) { if (index == 0) @@ -118,23 +112,20 @@ public Node getNode(int index) } - /** - * {@inheritDoc} - */ public Node getChild(int index) { switch (index) { - case 0: return left; - case 1: return right; - default: throw new IndexOutOfBoundsException("Invalid child index: " + index); + case 0: + return left; + case 1: + return right; + default: + throw new IndexOutOfBoundsException("Invalid child index: " + index); } } - /** - * {@inheritDoc} - */ public Node replaceNode(int index, Node newNode) { if (index == 0) @@ -154,10 +145,6 @@ public Node replaceNode(int index, Node newNode) } - - /** - * {@inheritDoc} - */ public String print() { StringBuilder buffer = new StringBuilder("("); @@ -171,9 +158,6 @@ public String print() } - /** - * {@inheritDoc} - */ public Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory) { if (mutationProbability.nextEvent(rng)) @@ -199,9 +183,8 @@ public Node mutate(Random rng, Probability mutationProbability, TreeFactory tree private Node newInstance(Node newLeft, Node newRight) { - Constructor constructor = ReflectionUtils.findKnownConstructor(this.getClass(), - Node.class, - Node.class); + Constructor constructor = ReflectionUtils.findKnownConstructor(this. + getClass(), Node.class, Node.class); return ReflectionUtils.invokeUnchecked(constructor, newLeft, newRight); } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java index 2a075381..5866bce2 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java @@ -18,13 +18,13 @@ import java.text.DecimalFormat; /** - * A program node that evaluates to a constant value. + * A program node that evaluates to a constant value. + *

* @author Daniel Dyer */ public class Constant extends LeafNode { private static final DecimalFormat NUMBER_FORMAT = new DecimalFormat("######0.##"); - private final double constant; private final String label; @@ -50,9 +50,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public String getLabel() { return label; diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java index 54abc4c2..0023d3f0 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java @@ -20,6 +20,7 @@ /** * Simple conditional program {@link Node}. + *

* @author Daniel Dyer */ public class IfThenElse implements Node @@ -28,6 +29,7 @@ public class IfThenElse implements Node private final Node then; private final Node otherwise; + /** * @param condition If this node evaluates to a value greater than zero then * the value of the {@literal then} node is returned. Otherwise the value of @@ -45,9 +47,6 @@ public IfThenElse(Node condition, Node then, Node otherwise) } - /** - * {@inheritDoc} - */ public String getLabel() { return "if"; @@ -64,36 +63,24 @@ public int getArity() } - /** - * {@inheritDoc} - */ public int getDepth() { return 1 + Math.max(condition.getDepth(), Math.max(then.getDepth(), otherwise.getDepth())); } - /** - * {@inheritDoc} - */ public int getWidth() { return condition.getWidth() + then.getWidth() + otherwise.getWidth(); } - /** - * {@inheritDoc} - */ public int countNodes() { return 1 + condition.countNodes() + then.countNodes() + otherwise.countNodes(); } - /** - * {@inheritDoc} - */ public Node getNode(int index) { if (index == 0) @@ -120,25 +107,22 @@ public Node getNode(int index) } - /** - * {@inheritDoc} - */ public Node getChild(int index) { switch (index) { - case 0: return condition; - case 1: return then; - case 2 : return otherwise; - default: throw new IndexOutOfBoundsException("Invalid child index: " + index); + case 0: + return condition; + case 1: + return then; + case 2: + return otherwise; + default: + throw new IndexOutOfBoundsException("Invalid child index: " + index); } } - - /** - * {@inheritDoc} - */ public Node replaceNode(int index, Node newNode) { if (index == 0) @@ -156,19 +140,20 @@ public Node replaceNode(int index, Node newNode) int thenNodes = then.countNodes(); if (index <= conditionNodes + thenNodes) { - return new IfThenElse(condition, then.replaceNode(index - conditionNodes - 1, newNode), otherwise); + return new IfThenElse(condition, then.replaceNode(index - conditionNodes - 1, + newNode), otherwise); } else { return new IfThenElse(condition, then, - otherwise.replaceNode(index - conditionNodes - thenNodes - 1, newNode)); + otherwise.replaceNode(index - conditionNodes - thenNodes - 1, + newNode)); } } } - /** * {@inheritDoc} * Operates on three other nodes. The first is an expression to evaluate. @@ -180,23 +165,17 @@ public Node replaceNode(int index, Node newNode) public double evaluate(double[] programParameters) { return condition.evaluate(programParameters) > 0 // If... - ? then.evaluate(programParameters) // Then... - : otherwise.evaluate(programParameters); // Else... + ? then.evaluate(programParameters) // Then... + : otherwise.evaluate(programParameters); // Else... } - - /** - * {@inheritDoc} - */ + public String print() { return "(" + condition.print() + " ? " + then.print() + " : " + otherwise.print() + ")"; } - /** - * {@inheritDoc} - */ public Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory) { if (mutationProbability.nextEvent(rng)) @@ -222,9 +201,6 @@ public Node mutate(Random rng, Probability mutationProbability, TreeFactory tree } - /** - * {@inheritDoc} - */ @Override public String toString() { @@ -232,9 +208,6 @@ public String toString() } - /** - * {@inheritDoc} - */ public Node simplify() { Node simplifiedCondition = condition.simplify(); @@ -255,7 +228,8 @@ public Node simplify() return simplifiedThen; } // Only create a new node if something has actually changed, otherwise return the existing node. - if (simplifiedCondition != condition || simplifiedThen != then || simplifiedOtherwise != otherwise) + if (simplifiedCondition != condition || simplifiedThen != then || simplifiedOtherwise + != otherwise) { return new IfThenElse(simplifiedCondition, simplifiedThen, simplifiedOtherwise); } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java index bd23472a..ded3644a 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java @@ -16,16 +16,17 @@ package org.uncommons.watchmaker.examples.geneticprogramming; /** - * A program {@link Node} that evaluates to a one if the value of its first - * argument is greater than the value of its second, or evaluates to zero otherwise. + * A program {@link Node} that evaluates to a one if the value of its first argument is greater than + * the value of its second, or evaluates to zero otherwise. + *

* @author Daniel Dyer */ public class IsGreater extends BinaryNode { /** - * Creates a node that evaluates to one if the value of the first child node - * is greater than the value of the second child node. Otherwise it evaluates - * to zero. + * Creates a node that evaluates to one if the value of the first child node is greater than the + * value of the second child node. Otherwise it evaluates to zero. + *

* @param left The first operand. * @param right The second operand. */ @@ -48,9 +49,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public Node simplify() { Node simplifiedLeft = left.simplify(); @@ -64,7 +62,8 @@ public Node simplify() // ever change. else if (simplifiedLeft instanceof Constant && simplifiedRight instanceof Constant) { - return new Constant(simplifiedLeft.evaluate(NO_ARGS) > simplifiedRight.evaluate(NO_ARGS) ? 1 : 0); + return new Constant(simplifiedLeft.evaluate(NO_ARGS) > simplifiedRight.evaluate(NO_ARGS) + ? 1 : 0); } else if (simplifiedLeft != left || simplifiedRight != right) { diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java index cf8718a7..9527c9db 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java @@ -20,12 +20,14 @@ /** * Convenient base class for {@link Node}s that have no sub-trees. + *

* @author Daniel Dyer */ abstract class LeafNode implements Node { /** * The arity of a non-function node is always zero. + *

* @return 0 */ public int getArity() @@ -33,7 +35,7 @@ public int getArity() return 0; } - + /** * Leaf nodes always have a depth of 1 since they have no child nodes. * @return 1 @@ -53,10 +55,7 @@ public int getWidth() return 1; } - - /** - * {@inheritDoc} - */ + public int countNodes() { return 1; @@ -73,9 +72,6 @@ public Node getNode(int index) } - /** - * {@inheritDoc} - */ public Node getChild(int index) { throw new IndexOutOfBoundsException("Leaf nodes have no children."); @@ -88,13 +84,10 @@ public Node replaceNode(int index, Node newNode) { throw new IndexOutOfBoundsException("Invalid node index: " + index); } - return newNode; + return newNode; } - - /** - * {@inheritDoc} - */ + public Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory) { if (mutationProbability.nextEvent(rng)) diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java index bd5dd600..42bd8204 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java @@ -17,13 +17,15 @@ /** * Simple multiplication operator {@link Node}. + *

* @author Daniel Dyer */ public class Multiplication extends BinaryNode { /** - * Creates a node that evaluates to the product of the values of its two - * child nodes ({@literal left} and {@literal right}). + * Creates a node that evaluates to the product of the values of its two child nodes ({@literal left} + * and {@literal right}). + *

* @param left The first operand. * @param right The second operand. */ @@ -45,9 +47,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public Node simplify() { Node simplifiedLeft = left.simplify(); @@ -84,7 +83,7 @@ else if (constant == 0) } } return simplifiedLeft != left || simplifiedRight != right - ? new Multiplication(simplifiedLeft, simplifiedRight) - : this; + ? new Multiplication(simplifiedLeft, simplifiedRight) + : this; } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java index 575bd1d0..9d88a565 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java @@ -16,14 +16,15 @@ package org.uncommons.watchmaker.examples.geneticprogramming; /** - * A program {@link Node} that simply returns the value of one of the - * program's parameters. + * A program {@link Node} that simply returns the value of one of the program's parameters. + *

* @author Daniel Dyer */ public class Parameter extends LeafNode { private final int parameterIndex; + /** * @param parameterIndex Which of the program's (zero-indexed) parameter * values should be returned upon evaluation of this node. @@ -49,9 +50,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public String getLabel() { return "P" + parameterIndex; diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java index 97083af9..3a5b2d16 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java @@ -17,13 +17,14 @@ /** * Simple subtraction operator {@link Node}. + *

* @author Daniel Dyer */ public class Subtraction extends BinaryNode { /** - * Creates a node that evaluates the the value of {@literal left} - * minus the value of {@literal right}. + * Creates a node that evaluates the the value of {@literal left} minus the value of {@literal right}. + *

* @param left The first operand. * @param right The second operand. */ @@ -45,9 +46,6 @@ public double evaluate(double[] programParameters) } - /** - * {@inheritDoc} - */ public Node simplify() { Node simplifiedLeft = left.simplify(); diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java index abb935de..3a5b470c 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java @@ -20,23 +20,21 @@ import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory; /** - * {@link org.uncommons.watchmaker.framework.CandidateFactory} for generating - * trees of {@link Node}s for the genetic programming example application. + * {@link org.uncommons.watchmaker.framework.CandidateFactory} for generating trees of {@link Node}s + * for the genetic programming example application. + *

* @author Daniel Dyer */ public class TreeFactory extends AbstractCandidateFactory { // The number of program parameters that each program tree will be provided. private final int parameterCount; - // The maximum depth of a program tree. No function nodes will be created below // this depth (branches will be terminated with parameters or constants). private final int maximumDepth; - // Probability that a created node is a function node rather // than a value node. private final Probability functionProbability; - // Probability that a value (non-function) node is a parameter // node rather than a constant node. private final Probability parameterProbability; @@ -53,9 +51,9 @@ public class TreeFactory extends AbstractCandidateFactory * non-function node will be a parameter node rather than a constant node. */ public TreeFactory(int parameterCount, - int maxDepth, - Probability functionProbability, - Probability parameterProbability) + int maxDepth, + Probability functionProbability, + Probability parameterProbability) { if (parameterCount < 0) { @@ -73,9 +71,6 @@ public TreeFactory(int parameterCount, } - /** - * {@inheritDoc} - */ public Node generateRandomCandidate(Random rng) { return makeNode(rng, maximumDepth); @@ -96,11 +91,17 @@ private Node makeNode(Random rng, int maxDepth) int depth = maxDepth - 1; switch (rng.nextInt(5)) { - case 0: return new Addition(makeNode(rng, depth), makeNode(rng, depth)); - case 1: return new Subtraction(makeNode(rng, depth), makeNode(rng, depth)); - case 2: return new Multiplication(makeNode(rng, depth), makeNode(rng, depth)); - case 3: return new IfThenElse(makeNode(rng, depth), makeNode(rng, depth), makeNode(rng, depth)); - default: return new IsGreater(makeNode(rng, depth), makeNode(rng, depth)); + case 0: + return new Addition(makeNode(rng, depth), makeNode(rng, depth)); + case 1: + return new Subtraction(makeNode(rng, depth), makeNode(rng, depth)); + case 2: + return new Multiplication(makeNode(rng, depth), makeNode(rng, depth)); + case 3: + return new IfThenElse(makeNode(rng, depth), makeNode(rng, depth), + makeNode(rng, depth)); + default: + return new IsGreater(makeNode(rng, depth), makeNode(rng, depth)); } } else if (parameterProbability.nextEvent(rng)) diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java index 490ed928..cb3d85c0 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java @@ -25,8 +25,8 @@ import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** - * Base class for mutation operators that modify the points of polygons in an - * image. + * Base class for mutation operators that modify the points of polygons in an image. + *

* @author Daniel Dyer */ abstract class AbstractVertexMutation implements EvolutionaryOperator @@ -36,12 +36,12 @@ abstract class AbstractVertexMutation implements EvolutionaryOperator mutationProbability, - Dimension canvasSize) + Dimension canvasSize) { this.mutationProbability = mutationProbability; this.canvasSize = canvasSize; @@ -67,32 +67,32 @@ protected NumberGenerator getMutationProbability() /** - * Applies the mutation to each polygon in the list provided according to the - * pre-configured mutation probability. If the probability is 0.1, approximately - * 10% of the individuals will be mutated. The actual mutation operation is - * defined in the sub-class implementation of the + * Applies the mutation to each polygon in the list provided according to the pre-configured + * mutation probability. If the probability is 0.1, approximately 10% of the individuals will be + * mutated. The actual mutation operation is defined in the sub-class implementation of the * {@link #mutateVertices(java.util.List, java.util.Random)} method. + *

* @param polygons The list of polygons to be mutated. * @param rng A source of randomness. - * @return The polygons after mutation. None, some or all will have been - * modified. + * @return The polygons after mutation. None, some or all will have been modified. */ public List apply(List polygons, Random rng) { - List newPolygons = new ArrayList(polygons.size()); - for (ColouredPolygon polygon : polygons) - { - List newVertices = mutateVertices(polygon.getVertices(), rng); - newPolygons.add(newVertices == polygon.getVertices() - ? polygon - : new ColouredPolygon(polygon.getColour(), newVertices)); - } + if (!getMutationProbability().nextValue().nextEvent(rng)) + return polygons; + List newPolygons = new ArrayList(polygons); + int index = rng.nextInt(polygons.size()); + ColouredPolygon polygon = polygons.get(index); + List newVertices = mutateVertices(polygon.getVertices(), rng); + if (newVertices != polygon.getVertices()) + newPolygons.set(index, new ColouredPolygon(polygon.getColour(), newVertices)); return newPolygons; } /** * Implemented in sub-classes to perform the mutation of the vertices. + *

* @param vertices A list of the points that make up the polygon. * @param rng A source of randomness. * @return A mutated list of points. diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java index decebdd4..d3b7f98c 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java @@ -25,21 +25,23 @@ import org.uncommons.maths.random.Probability; /** - * Evolutionary operator for mutating individual polygons. Polygons are mutated - * by adding a point, according to some probability. + * Evolutionary operator for mutating individual polygons. Polygons are mutated by adding a point, + * according to some probability. + *

* @author Daniel Dyer */ public class AddVertexMutation extends AbstractVertexMutation { static final int MAX_VERTEX_COUNT = 10; + /** - * @param mutationProbability A {@link NumberGenerator} that controls the probability - * that a point will be added. - * @param canvasSize The size of the canvas. Used to constrain the positions of the points. + * @param mutationProbability A {@link NumberGenerator} that controls the probability that a + * point will be added. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. */ public AddVertexMutation(Dimension canvasSize, - NumberGenerator mutationProbability) + NumberGenerator mutationProbability) { super(mutationProbability, canvasSize); } @@ -47,18 +49,19 @@ public AddVertexMutation(Dimension canvasSize, /** * @param mutationProbability The probability that a point will be added. - * @param canvasSize The size of the canvas. Used to constrain the positions of the points. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. */ public AddVertexMutation(Dimension canvasSize, - Probability mutationProbability) + Probability mutationProbability) { this(canvasSize, new ConstantGenerator(mutationProbability)); } /** - * Mutates the list of vertices for a given polygon by adding a new random point. - * Whether or not a point is actually added is determined by the configured mutation probability. + * Mutates the list of vertices for a given polygon by adding a new random point. Whether or not + * a point is actually added is determined by the configured mutation probability. + *

* @param vertices A list of the points that make up the polygon. * @param rng A source of randomness. * @return A mutated list of points. @@ -68,17 +71,14 @@ protected List mutateVertices(List vertices, Random rng) { // A single point is added with the configured probability, unless // we already have the maximum permitted number of points. - if (vertices.size() < MAX_VERTEX_COUNT && getMutationProbability().nextValue().nextEvent(rng)) - { - List newVertices = new ArrayList(vertices); - newVertices.add(rng.nextInt(newVertices.size()), - new Point(rng.nextInt(getCanvasSize().width), - rng.nextInt(getCanvasSize().height))); - return newVertices; - } - else // Nothing changed. - { + if (vertices.size() >= MAX_VERTEX_COUNT) return vertices; - } + List newVertices = new ArrayList(vertices); + int index = rng.nextInt(newVertices.size()); + + // Add a vetex without modifying the visible properties of the polygon. Other mutations will + // do that. + newVertices.add(index, new Point(newVertices.get(index))); + return newVertices; } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java index 0549cc69..725819ea 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java @@ -17,6 +17,7 @@ import java.awt.Dimension; import java.awt.Point; +import java.awt.Polygon; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -26,26 +27,26 @@ import org.uncommons.maths.random.Probability; /** - * Evolutionary operator for mutating individual polygons. Polygons are mutated - * by moving a point, according to some probability. + * Evolutionary operator for mutating individual polygons. Polygons are mutated by moving a point, + * according to some probability. + *

* @author Daniel Dyer */ public class AdjustVertexMutation extends AbstractVertexMutation { private final NumberGenerator changeAmount; + /** - * @param mutationProbability A {@link NumberGenerator} that controls the - * probability that a point will be moved. - * @param canvasSize The size of the canvas. Used to constrain the positions - * of the points. - * @param changeAmount A {@link NumberGenerator} that controls the distance - * that points are moved (in pixels). Should generate both positive and - * negative values. + * @param mutationProbability A {@link NumberGenerator} that controls the probability that a + * point will be moved. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. + * @param changeAmount A {@link NumberGenerator} that controls the distance that points are + * moved (in pixels). Should generate both positive and negative values. */ public AdjustVertexMutation(Dimension canvasSize, - NumberGenerator mutationProbability, - NumberGenerator changeAmount) + NumberGenerator mutationProbability, + NumberGenerator changeAmount) { super(mutationProbability, canvasSize); this.changeAmount = changeAmount; @@ -54,15 +55,13 @@ public AdjustVertexMutation(Dimension canvasSize, /** * @param mutationProbability The probability that a point will be moved. - * @param canvasSize The size of the canvas. Used to constrain the positions - * of the points. - * @param changeAmount A {@link NumberGenerator} that controls the distance - * that points are moved (in pixels). Should generate both positive and - * negative values. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. + * @param changeAmount A {@link NumberGenerator} that controls the distance that points are + * moved (in pixels). Should generate both positive and negative values. */ public AdjustVertexMutation(Dimension canvasSize, - Probability mutationProbability, - NumberGenerator changeAmount) + Probability mutationProbability, + NumberGenerator changeAmount) { this(canvasSize, new ConstantGenerator(mutationProbability), changeAmount); } @@ -72,23 +71,29 @@ public AdjustVertexMutation(Dimension canvasSize, protected List mutateVertices(List vertices, Random rng) { // A single point is modified with the configured probability. - if (getMutationProbability().nextValue().nextEvent(rng)) + int index = rng.nextInt(vertices.size()); + Point oldPoint = vertices.get(index); + List newVertices; + Polygon polygon; + do { - List newVertices = new ArrayList(vertices); int xDelta = (int) Math.round(changeAmount.nextValue().doubleValue()); int yDelta = (int) Math.round(changeAmount.nextValue().doubleValue()); - int index = rng.nextInt(newVertices.size()); - Point oldPoint = newVertices.get(index); + if (xDelta == 0 && yDelta == 0) + { + // The vertex has nowhere to move. + return vertices; + } + newVertices = new ArrayList(vertices); int newX = oldPoint.x + xDelta; int newY = oldPoint.y + yDelta; - newX = Maths.restrictRange(newX, 0, getCanvasSize().width - 1); - newY = Maths.restrictRange(newY, 0, getCanvasSize().height - 1); + newX = Maths.restrictRange(newX, 0, getCanvasSize().width); + newY = Maths.restrictRange(newY, 0, getCanvasSize().height); newVertices.set(index, new Point(newX, newY)); - return newVertices; - } - else // Nothing changed. - { - return vertices; - } + polygon = new Polygon(); + for (Point point: newVertices) + polygon.addPoint(point.x, point.y); + } while (Path2Ds.isSelfIntersecting(polygon.getPathIterator(null))); + return newVertices; } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java index eb8c4819..d969f666 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java @@ -75,4 +75,23 @@ public Polygon getPolygon() { return polygon; } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof ColouredPolygon)) + return false; + final ColouredPolygon other = (ColouredPolygon) o; + return this.colour.equals(other.colour) + && this.vertices.equals(other.vertices); + } + + @Override + public int hashCode() + { + int hash = 7; + hash = 89 * hash + this.colour.hashCode(); + hash = 89 * hash + this.vertices.hashCode(); + return hash; + } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java index 4a0db998..a5a3a800 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java @@ -18,52 +18,44 @@ import java.awt.BorderLayout; import java.awt.Container; import java.awt.Dimension; +import java.awt.Graphics; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.IOException; import java.net.URL; import java.util.List; import java.util.Random; import javax.imageio.ImageIO; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; +import javax.swing.*; +import org.uncommons.maths.number.AdjustableNumberGenerator; import org.uncommons.maths.random.Probability; import org.uncommons.maths.random.XORShiftRNG; import org.uncommons.swing.SwingBackgroundTask; import org.uncommons.watchmaker.examples.AbstractExampleApplet; -import org.uncommons.watchmaker.framework.CachingFitnessEvaluator; -import org.uncommons.watchmaker.framework.EvolutionEngine; -import org.uncommons.watchmaker.framework.EvolutionaryOperator; -import org.uncommons.watchmaker.framework.FitnessEvaluator; -import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine; -import org.uncommons.watchmaker.framework.SelectionStrategy; -import org.uncommons.watchmaker.framework.TerminationCondition; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.interactive.Renderer; import org.uncommons.watchmaker.framework.selection.TournamentSelection; import org.uncommons.watchmaker.framework.termination.Stagnation; +import org.uncommons.watchmaker.framework.termination.TargetFitness; import org.uncommons.watchmaker.swing.AbortControl; import org.uncommons.watchmaker.swing.ProbabilityParameterControl; import org.uncommons.watchmaker.swing.evolutionmonitor.EvolutionMonitor; /** * This program is inspired by Roger Alsing's evolution of the Mona Lisa - * (http://rogeralsing.com/2008/12/07/genetic-programming-evolution-of-mona-lisa/). - * It attempts to find the combination of 50 translucent polygons that most closely - * resembles Leonardo da Vinci's Mona Lisa. + * (http://rogeralsing.com/2008/12/07/genetic-programming-evolution-of-mona-lisa/). It attempts to + * find the combination of 50 translucent polygons that most closely resembles Leonardo da Vinci's + * Mona Lisa. + *

* @author Daniel Dyer */ public class MonaLisaApplet extends AbstractExampleApplet { - private static final String IMAGE_PATH = "org/uncommons/watchmaker/examples/monalisa/monalisa.jpg"; - + private static final String IMAGE_PATH = + "org/uncommons/watchmaker/examples/monalisa/monalisa.jpg"; private ProbabilitiesPanel probabilitiesPanel; private EvolutionMonitor> monitor; private JButton startButton; @@ -72,6 +64,10 @@ public class MonaLisaApplet extends AbstractExampleApplet private JSpinner elitismSpinner; private ProbabilityParameterControl selectionPressureControl; private BufferedImage targetImage; + private double zoomFactor = 1.0; + private final AdjustableNumberGenerator damping = + new AdjustableNumberGenerator(1.0); + private final boolean antialias = false; @Override @@ -81,33 +77,72 @@ public void init() { URL imageURL = MonaLisaApplet.class.getClassLoader().getResource(IMAGE_PATH); targetImage = ImageIO.read(imageURL); + optimizeTargetImage(); super.init(); } catch (IOException ex) { ex.printStackTrace(); - JOptionPane.showMessageDialog(this, ex, "Failed to Load Image", JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog(this, ex, "Failed to Load Image", + JOptionPane.ERROR_MESSAGE); + + } + } + + /** + * Optimize the size and image type of targetImage to speed up rendering and fitness evaluation. + */ + public void optimizeTargetImage() + { + zoomFactor = Math.max(targetImage.getWidth() / 100.0, targetImage.getHeight() / 100.0); + double shrinkFactor = 1.0 / zoomFactor; + AffineTransformOp shrinkOp = new AffineTransformOp(AffineTransform.getScaleInstance( + shrinkFactor, shrinkFactor), AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + targetImage = shrinkOp.filter(targetImage, null); + if (targetImage.getType() == BufferedImage.TYPE_INT_RGB) + { + // Convert the target image into the most efficient format for rendering. + BufferedImage temp = new BufferedImage(targetImage.getWidth(), + targetImage.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics tempGraphics = temp.getGraphics(); + tempGraphics.drawImage(targetImage, 0, 0, null); + tempGraphics.dispose(); + this.targetImage = temp; } + this.targetImage.setAccelerationPriority(1); } /** * Initialise and layout the GUI. + *

* @param container The Swing component that will contain the GUI controls. */ @Override protected void prepareGUI(Container container) { probabilitiesPanel = new ProbabilitiesPanel(); - probabilitiesPanel.setBorder(BorderFactory.createTitledBorder("Evolution Probabilities")); + probabilitiesPanel.setBorder(BorderFactory.createTitledBorder("Evolution Probabilities")); JPanel controls = new JPanel(new BorderLayout()); controls.add(createParametersPanel(), BorderLayout.NORTH); controls.add(probabilitiesPanel, BorderLayout.SOUTH); container.add(controls, BorderLayout.NORTH); - Renderer, JComponent> renderer = new PolygonImageSwingRenderer(targetImage); - monitor = new EvolutionMonitor>(renderer, false); + Renderer, JComponent> renderer = + new PolygonImageSwingRenderer(targetImage.getWidth(), targetImage.getHeight(), antialias, + zoomFactor); + AffineTransformOp zoomOp = new AffineTransformOp(AffineTransform.getScaleInstance(zoomFactor, + zoomFactor), AffineTransformOp.TYPE_NEAREST_NEIGHBOR); + final BufferedImage zoomedSolution = zoomOp.filter(targetImage, null); + Renderer solutionRenderer = new Renderer() + { + public JComponent render(Object entity) + { + return new JLabel(new ImageIcon(zoomedSolution)); + } + }; + monitor = new EvolutionMonitor>(renderer, solutionRenderer, false); container.add(monitor.getGUIComponent(), BorderLayout.CENTER); } @@ -134,14 +169,14 @@ private JComponent createParametersPanel() parameters.add(new JLabel("Selection Pressure: ")); parameters.add(Box.createHorizontalStrut(10)); selectionPressureControl = new ProbabilityParameterControl(Probability.EVENS, - Probability.ONE, - 2, - new Probability(0.7)); + Probability.ONE, + 2, + new Probability(0.7)); parameters.add(selectionPressureControl.getControl()); parameters.add(Box.createHorizontalStrut(10)); startButton = new JButton("Start"); - abort = new AbortControl(); + abort = new AbortControl(); startButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) @@ -153,9 +188,10 @@ public void actionPerformed(ActionEvent ev) elitismSpinner.setEnabled(false); startButton.setEnabled(false); new EvolutionTask((Integer) populationSpinner.getValue(), - (Integer) elitismSpinner.getValue(), - abort.getTerminationCondition(), - new Stagnation(1000, false)).execute(); + (Integer) elitismSpinner.getValue(), + abort.getTerminationCondition(), + new TargetFitness(0, false), + new Stagnation(100000, false)).execute(); } }); abort.getControl().setEnabled(false); @@ -170,23 +206,24 @@ public void actionPerformed(ActionEvent ev) /** * Entry point for running this example as an application rather than an applet. + *

* @param args Program arguments (ignored). - * @throws IOException If there is a problem loading the target image. + * @throws IOException If there is a problem loading the target image. */ public static void main(String[] args) throws IOException { MonaLisaApplet gui = new MonaLisaApplet(); // If a URL is specified as an argument, use that image. Otherwise use the default Mona Lisa picture. URL imageURL = args.length > 0 - ? new URL(args[0]) - : MonaLisaApplet.class.getClassLoader().getResource(IMAGE_PATH); + ? new URL(args[0]) + : MonaLisaApplet.class.getClassLoader().getResource(IMAGE_PATH); gui.targetImage = ImageIO.read(imageURL); + gui.optimizeTargetImage(); gui.displayInFrame("Watchmaker Framework - Mona Lisa Example"); } - /** - * The task that acutally performs the evolution. + * The task that actually performs the evolution. */ private class EvolutionTask extends SwingBackgroundTask> { @@ -195,7 +232,8 @@ private class EvolutionTask extends SwingBackgroundTask> private final TerminationCondition[] terminationConditions; - EvolutionTask(int populationSize, int eliteCount, TerminationCondition... terminationConditions) + EvolutionTask(int populationSize, int eliteCount, + TerminationCondition... terminationConditions) { this.populationSize = populationSize; this.eliteCount = eliteCount; @@ -209,20 +247,46 @@ protected List performTask() throws Exception Dimension canvasSize = new Dimension(targetImage.getWidth(), targetImage.getHeight()); Random rng = new XORShiftRNG(); - FitnessEvaluator> evaluator - = new CachingFitnessEvaluator>(new PolygonImageEvaluator(targetImage)); + FitnessEvaluator> evaluator = + new CachingFitnessEvaluator>(new PolygonImageEvaluator( + targetImage, antialias)); PolygonImageFactory factory = new PolygonImageFactory(canvasSize); - EvolutionaryOperator> pipeline - = probabilitiesPanel.createEvolutionPipeline(factory, canvasSize, rng); - - SelectionStrategy selection = new TournamentSelection(selectionPressureControl.getNumberGenerator()); - EvolutionEngine> engine - = new GenerationalEvolutionEngine>(factory, - pipeline, - evaluator, - selection, - rng); + EvolutionaryOperator> pipeline = probabilitiesPanel. + createEvolutionPipeline(factory, canvasSize, rng, damping); + + SelectionStrategy selection = new TournamentSelection(selectionPressureControl. + getNumberGenerator()); + EvolutionEngine> engine = + new GenerationalEvolutionEngine>(factory, + pipeline, + evaluator, + selection, + rng); engine.addEvolutionObserver(monitor); + engine.addEvolutionObserver(new EvolutionObserver>() + { + private double lastFitness = Double.MAX_VALUE; + /** + * Incremented every generation the fitness remains unchanged, decremented every + * generation the fitness improves. + */ + private int stagnatedGenerations; + + + @Override + public > void populationUpdate( + PopulationData data) + { + int generation = data.getGenerationNumber(); + double newFitness = data.getBestCandidateFitness(); + if (Double.compare(newFitness, lastFitness) < 0) + --stagnatedGenerations; + else + ++stagnatedGenerations; + lastFitness = newFitness; + damping.setValue(1111111.0 / (stagnatedGenerations + 1111111)); + } + }); return engine.evolve(populationSize, eliteCount, terminationConditions); } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/NumberGenerators.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/NumberGenerators.java new file mode 100644 index 00000000..8b470400 --- /dev/null +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/NumberGenerators.java @@ -0,0 +1,46 @@ +//============================================================================= +// 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.examples.monalisa; + +import org.uncommons.maths.number.NumberGenerator; + +/** + * NumberGenerator helper functions. + *

+ * @author Gili Tzabari + */ +public class NumberGenerators +{ + /** + * Returns the multiplication of two NumberGenerators. + *

+ * @param first the first value generator + * @param second the second value generator + * @return the multiplication of first and second NumberGenerators + */ + public static NumberGenerator multiplyDouble(final NumberGenerator first, + final NumberGenerator second) + { + return new NumberGenerator() + { + @Override + public Double nextValue() + { + return first.nextValue() * second.nextValue(); + } + }; + } +} diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/Path2Ds.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/Path2Ds.java new file mode 100644 index 00000000..9ae570bd --- /dev/null +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/Path2Ds.java @@ -0,0 +1,178 @@ +//============================================================================= +// 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.examples.monalisa; + +import java.awt.Point; +import java.awt.geom.Line2D; +import java.awt.geom.PathIterator; +import java.util.*; + +/** + * Path2D helper functions. + *

+ * @see http://stackoverflow.com/questions/4479010/finding-if-path2d-self-intersects + * @author Gili Tzabari + */ +public class Path2Ds +{ + /** + * Indicates if a Path2D intersects itself. + *

+ * @return true if a Path2D intersects itself + */ + public static boolean isSelfIntersecting(PathIterator path) + { + SortedSet lines = getLines(path); + if (lines.size() <= 1) + return false; + + Set candidates = new HashSet(); + for (Line2D line: lines) + { + if (Double.compare(line.getP1().distance(line.getP2()), 0) <= 0) + { + // Lines of length 0 do not cause self-intersection + continue; + } + for (Iterator i = candidates.iterator(); i.hasNext();) + { + Line2D candidate = i.next(); + + // Logic borrowed from Line2D.intersectsLine() + int lineRelativeToCandidate1 = Line2D.relativeCCW(line.getX1(), line.getY1(), line. + getX2(), + line.getY2(), candidate.getX1(), candidate.getY1()); + int lineRelativeToCandidate2 = Line2D.relativeCCW(line.getX1(), line.getY1(), line. + getX2(), + line.getY2(), candidate.getX2(), candidate.getY2()); + int candidateRelativeToLine1 = Line2D.relativeCCW(candidate.getX1(), + candidate.getY1(), + candidate.getX2(), candidate.getY2(), line.getX1(), line.getY1()); + int candidateRelativeToLine2 = Line2D.relativeCCW(candidate.getX1(), + candidate.getY1(), + candidate.getX2(), candidate.getY2(), line.getX2(), line.getY2()); + boolean intersection = (lineRelativeToCandidate1 * lineRelativeToCandidate2 <= 0) + && (candidateRelativeToLine1 * candidateRelativeToLine2 <= 0); + if (intersection) + { + // Lines may share a point, so long as they extend in different directions + if (lineRelativeToCandidate1 == 0 && lineRelativeToCandidate2 != 0) + { + // candidate.P1 shares a point with line + if (candidateRelativeToLine1 == 0 && candidateRelativeToLine2 != 0) + { + // line.P1 == candidate.P1 + continue; + } + if (candidateRelativeToLine1 != 0 && candidateRelativeToLine2 == 0) + { + // line.P2 == candidate.P1 + continue; + } + // else candidate.P1 intersects line + } + else if (lineRelativeToCandidate1 != 0 && lineRelativeToCandidate2 == 0) + { + // candidate.P2 shares a point with line + if (candidateRelativeToLine1 == 0 && candidateRelativeToLine2 != 0) + { + // line.P1 == candidate.P2 + continue; + } + if (candidateRelativeToLine1 != 0 && candidateRelativeToLine2 == 0) + { + // line.P2 == candidate.P2 + continue; + } + // else candidate.P2 intersects line + } + else + { + // line and candidate overlap + } + return true; + } + if (candidate.getX2() < line.getX1()) + i.remove(); + } + candidates.add(line); + } + return false; + } + + + /** + * Returns all lines in a path. The lines are constructed such that the starting point is found + * on the left (or same x-coordinate) of the ending point. + *

+ * @param path the path + * @return the lines, sorted in ascending order of the x-coordinate of the starting point and + * ending point, respectively + */ + private static SortedSet getLines(PathIterator path) + { + double[] coords = new double[6]; + SortedSet result = new TreeSet(new Comparator() + { + @Override + public int compare(Line2D o1, Line2D o2) + { + int result = Double.compare(o1.getX1(), o2.getX1()); + if (result == 0) + { + // Ensure we are consistent with equals() + return Double.compare(o1.getX2(), o2.getX2()); + } + return result; + } + }); + if (path.isDone()) + return result; + int type = path.currentSegment(coords); + assert (type == PathIterator.SEG_MOVETO): type; + Point.Double startPoint = new Point.Double(coords[0], coords[1]); + Point.Double openPoint = startPoint; + path.next(); + + while (!path.isDone()) + { + type = path.currentSegment(coords); + assert (type != PathIterator.SEG_CUBICTO && type != PathIterator.SEG_QUADTO): type; + switch (type) + { + case PathIterator.SEG_MOVETO: + { + openPoint = startPoint; + break; + } + case PathIterator.SEG_CLOSE: + { + coords[0] = openPoint.x; + coords[1] = openPoint.y; + break; + } + } + Point.Double endPoint = new Point.Double(coords[0], coords[1]); + if (Double.compare(startPoint.getX(), endPoint.getX()) < 0) + result.add(new Line2D.Double(startPoint, endPoint)); + else + result.add(new Line2D.Double(endPoint, startPoint)); + path.next(); + startPoint = endPoint; + } + return result; + } +} diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java index 04346465..c1923d97 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java @@ -26,9 +26,9 @@ import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** - * Evolutionary operator for mutating individual polygons. Polygons are mutated - * by changing their colour and/or either adding a point, removing a point or - * changing the position of a point. + * Evolutionary operator for mutating individual polygons. Polygons are mutated by changing their + * colour and/or either adding a point, removing a point or changing the position of a point. + *

* @author Daniel Dyer */ public class PolygonColourMutation implements EvolutionaryOperator @@ -38,13 +38,13 @@ public class PolygonColourMutation implements EvolutionaryOperator mutationProbability, - NumberGenerator mutationAmount) + NumberGenerator mutationAmount) { this.mutationProbability = mutationProbability; this.mutationAmount = mutationAmount; @@ -53,11 +53,11 @@ public PolygonColourMutation(NumberGenerator mutationProbability, /** * @param mutationProbability The probability that the colour will be modified. - * @param mutationAmount A {@link NumberGenerator} that controls the amount - * that the colour's components are adjusted by. + * @param mutationAmount A {@link NumberGenerator} that controls the amount that the colour's + * components are adjusted by. */ public PolygonColourMutation(Probability mutationProbability, - NumberGenerator mutationAmount) + NumberGenerator mutationAmount) { this(new ConstantGenerator(mutationProbability), mutationAmount); } @@ -65,48 +65,71 @@ public PolygonColourMutation(Probability mutationProbability, public List apply(List polygons, Random rng) { - List newPolygons = new ArrayList(polygons.size()); - for (ColouredPolygon polygon : polygons) - { - Color newColour = mutateColour(polygon.getColour(), rng); - newPolygons.add(newColour == polygon.getColour() - ? polygon - : new ColouredPolygon(newColour, polygon.getVertices())); - } + if (!mutationProbability.nextValue().nextEvent(rng)) + return polygons; + int index = rng.nextInt(polygons.size()); + List newPolygons = new ArrayList(polygons); + ColouredPolygon oldPolygon = newPolygons.get(index); + newPolygons.set(index, new ColouredPolygon(mutateColour(oldPolygon.getColour(), rng), + oldPolygon.getVertices())); return newPolygons; } /** * Mutate the specified colour. + *

* @param colour The colour to mutate. * @param rng A source of randomness. * @return The (possibly) mutated colour. */ private Color mutateColour(Color colour, Random rng) { - if (mutationProbability.nextValue().nextEvent(rng)) - { - return new Color(mutateColourComponent(colour.getRed()), - mutateColourComponent(colour.getGreen()), - mutateColourComponent(colour.getBlue()), - mutateColourComponent(colour.getAlpha())); - } - else + int red = colour.getRed(); + int green = colour.getGreen(); + int blue = colour.getBlue(); + int alpha = colour.getAlpha(); + switch (rng.nextInt(3)) { - return colour; + case 0: + { + red = mutateColourComponent(red); + break; + } + case 1: + { + green = mutateColourComponent(green); + break; + } + case 2: + { + blue = mutateColourComponent(blue); + break; + } + case 3: + { + // Alpha mutation is disabled by default, but you can enable it by changing the + // above RNG limit from 3 to 4. + alpha = mutateColourComponent(alpha); + break; + } } + return new Color(red, green, blue, alpha); } /** * Adjust a single component (red, green, blue or alpha) of a colour. + *

* @param component The value to mutate. * @return The mutated component value. */ private int mutateColourComponent(int component) { - int mutatedComponent = (int) Math.round(component + mutationAmount.nextValue()); + double amount = mutationAmount.nextValue(); + if (Double.compare(Math.abs(amount), 1) < 0) + amount = Math.signum(amount); + int mutatedComponent = (int) Math.round(component + amount); mutatedComponent = Maths.restrictRange(mutatedComponent, 0, 255); return mutatedComponent; } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java index acc3e1b9..11048dc9 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java @@ -16,8 +16,8 @@ package org.uncommons.watchmaker.examples.monalisa; import java.awt.Dimension; -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; +import java.awt.Graphics; +import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.awt.image.Raster; import java.util.List; @@ -25,62 +25,55 @@ import org.uncommons.watchmaker.framework.interactive.Renderer; /** - * Compares the generated polygon-based images to the target bitmap. The polygon images - * are rendered the same size as the target image and then each pixel is compared. The - * fitness value is a combination of the differences for each pixel. Lower fitness is better. + * Compares the generated polygon-based images to the target bitmap. The polygon images are rendered + * the same size as the target image and then each pixel is compared. The fitness value is a + * combination of the differences for each pixel. Lower fitness is better. + *

* @author Daniel Dyer */ public class PolygonImageEvaluator implements FitnessEvaluator> { // This field is marked as transient, even though the class is not Serializable, because // Terracotta will respect the fact it is transient and not try to share it. - private final transient ThreadLocal, BufferedImage>> threadLocalRenderer - = new ThreadLocal, BufferedImage>>(); - + private final transient ThreadLocal, BufferedImage>> threadLocalRenderer = + new ThreadLocal, BufferedImage>>(); private final int width; private final int height; - private final AffineTransform transform; private final int[] targetPixels; + private final boolean antialias; /** - * Creates an evaluator that assigns fitness scores to images based on how - * close they are to the specified target image. + * Creates an evaluator that assigns fitness scores to images based on how close they are to the + * specified target image. + *

* @param targetImage The image that all others are compared to. + * @param antialias Whether or not to enable anti-aliasing for the rendered image. */ - public PolygonImageEvaluator(BufferedImage targetImage) + public PolygonImageEvaluator(BufferedImage targetImage, boolean antialias) { - // Scale the image down so that its smallest dimension is 100 pixels. For large images this drastically + this.antialias = antialias; // reduces the number of pixels that we need to check for fitness evaluation. Raster targetImageData; - if (targetImage.getWidth() > 100 && targetImage.getHeight() > 100) - { - double ratio = 100.0d / (targetImage.getWidth() > targetImage.getHeight() ? targetImage.getHeight() : targetImage.getWidth()); - transform = AffineTransform.getScaleInstance(ratio, ratio); - AffineTransformOp transformOp = new AffineTransformOp(transform, - AffineTransformOp.TYPE_NEAREST_NEIGHBOR); - targetImageData = convertImage(transformOp.filter(targetImage, null)).getData(); - } - else - { - targetImageData = convertImage(targetImage).getData(); - transform = null; - } + Rectangle targetRect = new Rectangle(targetImage.getMinX(), targetImage.getMinY(), + targetImage.getWidth(), targetImage.getHeight()); + targetImageData = convertImage(targetImage).getData(targetRect); this.width = targetImageData.getWidth(); this.height = targetImageData.getHeight(); int[] pixelArray = new int[targetImageData.getWidth() * targetImageData.getHeight()]; - targetPixels = (int[]) targetImageData.getDataElements(0, - 0, - targetImageData.getWidth(), - targetImageData.getHeight(), - pixelArray); + targetPixels = (int[]) targetImageData.getDataElements(targetImageData.getMinX(), + targetImageData.getMinY(), + targetImageData.getWidth(), + targetImageData.getHeight(), + pixelArray); } /** - * Make sure that the image is in the most efficient format for reading from. - * This avoids having to convert pixels every time we access them. + * Make sure that the image is in the most efficient format for reading from. This avoids having + * to convert pixels every time we access them. + *

* @param image The image to convert. * @return The image converted to INT_RGB format. */ @@ -93,51 +86,53 @@ private BufferedImage convertImage(BufferedImage image) else { BufferedImage newImage = new BufferedImage(image.getWidth(), - image.getHeight(), - BufferedImage.TYPE_INT_RGB); - newImage.getGraphics().drawImage(image, 0, 0, null); + image.getHeight(), BufferedImage.TYPE_INT_RGB); + Graphics g = newImage.getGraphics(); + g.drawImage(image, 0, 0, null); + g.dispose(); return newImage; } } /** - * Render the polygons as an image and then do a pixel-by-pixel comparison - * against the target image. The fitness score is the total error. A lower - * score means a closer match. + * Render the polygons as an image and then do a pixel-by-pixel comparison against the target + * image. The fitness score is the total error. A lower score means a closer match. + *

* @param candidate The image to evaluate. * @param population Not used. - * @return A number indicating how close the candidate image is to the target image - * (lower is better). + * @return A number indicating how close the candidate image is to the target image (lower is + * better). */ public double getFitness(List candidate, - List> population) + List> population) { // Use one renderer per thread because they are not thread safe. Renderer, BufferedImage> renderer = threadLocalRenderer.get(); if (renderer == null) { - renderer = new PolygonImageRenderer(new Dimension(width, height), - false, - transform); + renderer = new PolygonImageRenderer(new Dimension(width, height), antialias, null); threadLocalRenderer.set(renderer); } - + BufferedImage candidateImage = renderer.render(candidate); Raster candidateImageData = candidateImage.getData(); int[] candidatePixelValues = new int[targetPixels.length]; - candidatePixelValues = (int[]) candidateImageData.getDataElements(0, - 0, - candidateImageData.getWidth(), - candidateImageData.getHeight(), - candidatePixelValues); + candidatePixelValues = + (int[]) candidateImageData.getDataElements(candidateImageData.getMinX(), + candidateImageData.getMinY(), + candidateImageData.getWidth(), + candidateImageData.getHeight(), + candidatePixelValues); double fitness = 0; for (int i = 0; i < targetPixels.length; i++) { fitness += comparePixels(targetPixels[i], candidatePixelValues[i]); } + // Hidden polygons should be removed + fitness += candidate.size(); return fitness; } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java index 96131727..717903e2 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java @@ -25,6 +25,7 @@ /** * Creates random polygon-based images. + *

* @author Daniel Dyer */ public class PolygonImageFactory extends AbstractCandidateFactory> @@ -33,24 +34,27 @@ public class PolygonImageFactory extends AbstractCandidateFactory generateRandomCandidate(Random rng) { List polygons = new ArrayList(MINIMUM_POLYGON_COUNT); @@ -65,11 +69,12 @@ public List generateRandomCandidate(Random rng) ColouredPolygon createRandomPolygon(Random rng) { List vertices = new ArrayList(MINIMUM_VERTEX_COUNT); - for (int j = 0; j < MINIMUM_VERTEX_COUNT; j++) - { - vertices.add(new Point(rng.nextInt(canvasSize.width), rng.nextInt(canvasSize.height))); - } - Color colour = new Color(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256), rng.nextInt(256)); + Point topLeft = new Point(rng.nextInt(canvasSize.width - 1), + rng.nextInt(canvasSize.height - 1)); + vertices.add(topLeft); + vertices.add(new Point(topLeft.x + 1, topLeft.y)); + vertices.add(new Point(topLeft.x + 1, topLeft.y + 1)); + Color colour = new Color(rng.nextInt(256), rng.nextInt(256), rng.nextInt(256), 255); return new ColouredPolygon(colour, vertices); } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java index 876c9f0e..a030f29d 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java @@ -25,18 +25,18 @@ import org.uncommons.watchmaker.framework.interactive.Renderer; /** - * Renders a polygon-based image to a {@link BufferedImage}. For efficiency reasons, this - * renderer returns the same image object (with different data) for subsequent invocations. - * This means that invoking code should not expect returned images to be unaltered following - * subsequent invocations. It also means that a renderer is not thread-safe. + * Renders a polygon-based image to a {@link BufferedImage}. For efficiency reasons, this renderer + * returns the same image object (with different data) for subsequent invocations. This means that + * invoking code should not expect returned images to be unaltered following subsequent invocations. + * It also means that a renderer is not thread-safe. + *

* @author Daniel Dyer */ public class PolygonImageRenderer implements Renderer, BufferedImage> { private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform(); - private final Dimension targetSize; - private final AffineTransform transform; + private AffineTransform transform; private final BufferedImage image; private final Graphics2D graphics; @@ -44,30 +44,28 @@ public class PolygonImageRenderer implements Renderer, Buf /** * @param targetSize The size of the canvas on which the polygons will be rendered. * @param antialias Whether or not to enable anti-aliasing for the rendered image. - * @param transform A transformation applied to the vertices of an image's polygons - * before drawing to the destination image. This transformation adjusts the image - * so that it fits on a canvas of the specified {@code targetSize}. + * @param transform A transformation applied to the vertices of an image's polygons before + * drawing to the destination image. This transformation adjusts the image so that it fits on a + * canvas of the specified {@code targetSize}. */ - public PolygonImageRenderer(Dimension targetSize, - boolean antialias, - AffineTransform transform) + public PolygonImageRenderer(Dimension targetSize, boolean antialias, AffineTransform transform) { this.targetSize = targetSize; - this.transform = transform; - this.image = new BufferedImage(targetSize.width, - targetSize.height, - BufferedImage.TYPE_INT_RGB); + this.transform = transform; + this.image = new BufferedImage(targetSize.width, targetSize.height, + BufferedImage.TYPE_INT_RGB); this.graphics = image.createGraphics(); if (antialias) { graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); + RenderingHints.VALUE_ANTIALIAS_ON); } } /** * Renders the specified polygons as an image. + *

* @param entity A collection of coloured polygons. * @return An image object displaying the polygons. */ @@ -81,7 +79,7 @@ public BufferedImage render(List entity) { graphics.setTransform(transform); } - for (ColouredPolygon polygon : entity) + for (ColouredPolygon polygon: entity) { graphics.setColor(polygon.getColour()); graphics.fillPolygon(polygon.getPolygon()); diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java index 1ce7e05c..c534feca 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java @@ -18,45 +18,50 @@ import java.awt.Dimension; import java.awt.FontMetrics; import java.awt.Graphics; +import java.awt.geom.AffineTransform; +import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.util.List; import javax.swing.JComponent; import org.uncommons.watchmaker.framework.interactive.Renderer; /** - * Converts a {@link BufferedImage} to a {@link JComponent} that displays that image - * alongside a pre-specified target image. + * Converts a {@link BufferedImage} to a {@link JComponent} that displays that image alongside a + * pre-specified target image. + *

* @author Daniel Dyer */ public class PolygonImageSwingRenderer implements Renderer, JComponent> { - private final BufferedImage targetImage; private final Renderer, BufferedImage> delegate; + private final int width; + private final int height; + private final double zoomFactor; + private final AffineTransformOp zoomOp; /** - * @param targetImage This image is displayed on the left side of the - * JComponent. + * @param width The width of the candidate. + * @param height The height of the candidate. + * @param antialias Whether or not to enable anti-aliasing for the rendered image. + * @param zoomFactor indicates the scale factor to apply to the image being rendered */ - public PolygonImageSwingRenderer(BufferedImage targetImage) + public PolygonImageSwingRenderer(int width, int height, boolean antialias, double zoomFactor) { - // Convert the target image into the most efficient format for rendering. - this.targetImage = new BufferedImage(targetImage.getWidth(), - targetImage.getHeight(), - BufferedImage.TYPE_INT_RGB); - this.targetImage.getGraphics().drawImage(targetImage, 0, 0, null); - this.targetImage.setAccelerationPriority(1); - - this.delegate = new PolygonImageRenderer(new Dimension(targetImage.getWidth(), - targetImage.getHeight()), - true, // Anti-alias. - null); + this.zoomFactor = zoomFactor; + this.width = (int) (width * zoomFactor); + this.height = (int) (height * zoomFactor); + this.delegate = new PolygonImageRenderer(new Dimension(width, height), + antialias, null); + this.zoomOp = new AffineTransformOp(AffineTransform.getScaleInstance(zoomFactor, zoomFactor), + AffineTransformOp.TYPE_NEAREST_NEIGHBOR); } /** - * Renders the specified image as a JComponent with the image on the - * right and the pre-specified target image on the left. + * Renders the specified image as a JComponent with the image on the right and the pre-specified + * target image on the left. + *

* @param entity The image to render on the right side of the component. * @return A Swing component that displays the rendered image. */ @@ -65,16 +70,12 @@ public JComponent render(List entity) return new ImageComponent(entity); } - /** - * Swing component for rendering a fixed size image. If the image is smaller - * than the component, it is centered. + * Swing component for rendering a fixed size image. If the image is smaller than the component, + * it is centered. */ private final class ImageComponent extends JComponent { - private static final int GAP = 10; - private static final int FOOTER = 20; - private final List candidate; private final Dimension minimumSize; @@ -82,16 +83,17 @@ private final class ImageComponent extends JComponent ImageComponent(List candidate) { this.candidate = candidate; - this.minimumSize = new Dimension(targetImage.getWidth() * 2 + GAP, - targetImage.getHeight() + FOOTER); + this.minimumSize = new Dimension(width, height); } + @Override public Dimension getPreferredSize() { return minimumSize; } + @Override public Dimension getMinimumSize() { @@ -104,14 +106,12 @@ protected void paintComponent(Graphics graphics) { int x = Math.max(0, (getWidth() - minimumSize.width) / 2); int y = Math.max(0, (getHeight() - minimumSize.height) / 2); - graphics.drawImage(targetImage, x, y, this); BufferedImage candidateImage = delegate.render(candidate); candidateImage.setAccelerationPriority(1); - Graphics clip = graphics.create(x + targetImage.getWidth() + GAP, - y, - candidateImage.getWidth(), - candidateImage.getHeight() + FOOTER); - clip.drawImage(candidateImage, 0, 0, this); + Graphics clip = graphics.create(x, y, + (int) (candidateImage.getWidth() * zoomFactor), + (int) (candidateImage.getHeight() * zoomFactor)); + clip.drawImage(zoomOp.filter(candidateImage, null), 0, 0, this); clip.setColor(getForeground()); String info = candidate.size() + " polygons, " + countVertices(candidate) + " vertices"; @@ -119,21 +119,22 @@ protected void paintComponent(Graphics graphics) int width = fontMetrics.stringWidth(info); int height = Math.round(fontMetrics.getLineMetrics(info, clip).getHeight()); clip.drawString(info, - candidateImage.getWidth() - width, - candidateImage.getHeight() + height); + (int) (candidateImage.getWidth() * zoomFactor - width), + (int) (candidateImage.getHeight() * zoomFactor + height)); + clip.dispose(); } /** - * Count the number of vertices in each polygon in the image and return - * the total. + * Count the number of vertices in each polygon in the image and return the total. + *

* @param image The image to inspect. * @return The total number of vertices in all polygons in the image. */ private int countVertices(List image) { int count = 0; - for (ColouredPolygon polygon : image) + for (ColouredPolygon polygon: image) { count += polygon.getVertices().size(); } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java index 5267be3f..0897ada6 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java @@ -23,6 +23,7 @@ import javax.swing.JPanel; import javax.swing.SpringLayout; import org.uncommons.maths.number.ConstantGenerator; +import org.uncommons.maths.number.NumberGenerator; import org.uncommons.maths.random.GaussianGenerator; import org.uncommons.maths.random.Probability; import org.uncommons.swing.SpringUtilities; @@ -33,14 +34,14 @@ import org.uncommons.watchmaker.swing.ProbabilityParameterControl; /** - * Panel that displays controls for the Mona Lisa example program. These - * controls allow the evolution parameters to be tweaked. + * Panel that displays controls for the Mona Lisa example program. These controls allow the + * evolution parameters to be tweaked. + *

* @author Daniel Dyer */ class ProbabilitiesPanel extends JPanel { - private static final Probability ONE_TENTH = new Probability(0.1d); - + private static final Probability THREE_TENTH = new Probability(0.3d); private final ProbabilityParameterControl addPolygonControl; private final ProbabilityParameterControl removePolygonControl; private final ProbabilityParameterControl movePolygonControl; @@ -55,75 +56,76 @@ class ProbabilitiesPanel extends JPanel { super(new SpringLayout()); addPolygonControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.02)); + THREE_TENTH, + 3, + new Probability(0.1)); add(new JLabel("Add Polygon: ")); add(addPolygonControl.getControl()); addPolygonControl.setDescription("For each IMAGE, the probability that a new " - + "randomly-generated polygon will be added."); + + "randomly-generated polygon will be added."); addVertexControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.01)); + THREE_TENTH, + 3, + new Probability(0.15)); add(new JLabel("Add Vertex: ")); add(addVertexControl.getControl()); addVertexControl.setDescription("For each POLYGON, the probability that a new " - + "randomly-generated vertex will be added."); + + "randomly-generated vertex will be added."); removePolygonControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.02)); + THREE_TENTH, + 3, + new Probability(0.1)); add(new JLabel("Remove Polygon: ")); add(removePolygonControl.getControl()); removePolygonControl.setDescription("For each IMAGE, the probability that a " - + "randomly-selected polygon will be discarded."); + + "randomly-selected polygon will be discarded."); removeVertexControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.01)); + THREE_TENTH, + 3, + new Probability(0.15)); add(new JLabel("Remove Vertex: ")); add(removeVertexControl.getControl()); removeVertexControl.setDescription("For each POLYGON, the probability that a " - + "randomly-selected vertex will be discarded."); + + "randomly-selected vertex will be discarded."); movePolygonControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.01)); + THREE_TENTH, + 3, + Probability.ZERO); add(new JLabel("Reorder Polygons: ")); add(movePolygonControl.getControl()); movePolygonControl.setDescription("For each IMAGE, the probability that the z-positions " - + "of two randomly-selected polygons will be swapped."); + + "of two randomly-selected polygons will be swapped."); moveVertexControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.03)); + THREE_TENTH, + 3, + new Probability(0.2)); add(new JLabel("Move Vertex: ")); add(moveVertexControl.getControl()); moveVertexControl.setDescription("For each POLYGON, the probability that a randomly-selected " - + "vertex will be displaced."); + + "vertex will be displaced."); crossOverControl = new ProbabilityParameterControl(Probability.ZERO, - Probability.ONE, - 2, - Probability.ONE); + Probability.ONE, + 2, + Probability.ZERO); add(new JLabel("Cross-over: ")); add(crossOverControl.getControl()); crossOverControl.setDescription("For each PAIR of parent IMAGES, the probability that " - + "2-point cross-over is applied."); + + "2-point cross-over is applied."); changeColourControl = new ProbabilityParameterControl(Probability.ZERO, - ONE_TENTH, - 3, - new Probability(0.01)); + THREE_TENTH, + 3, + new Probability(0.1)); add(new JLabel("Change Colour: ")); add(changeColourControl.getControl()); - changeColourControl.setDescription("For each POLYGON, the probability that its colour will be mutated."); + changeColourControl.setDescription( + "For each POLYGON, the probability that its colour will be mutated."); // Set component names for easy look-up from tests. addPolygonControl.getControl().setName("AddPolygon"); @@ -142,32 +144,37 @@ class ProbabilitiesPanel extends JPanel /** * Construct the combination of evolutionary operators that will be used to evolve the * polygon-based images. + *

* @param factory A source of polygons. * @param canvasSize The size of the target image. * @param rng A source of randomness. + * @param damping a value that decreases from 1.0 to 0.0 over the lifetime of the evolution. * @return A complex evolutionary operator constructed from simpler operators. */ - public EvolutionaryOperator> createEvolutionPipeline(PolygonImageFactory factory, - Dimension canvasSize, - Random rng) + public EvolutionaryOperator> createEvolutionPipeline( + PolygonImageFactory factory, + Dimension canvasSize, + Random rng, + NumberGenerator damping) { - List>> operators - = new LinkedList>>(); + List>> operators = + new LinkedList>>(); operators.add(new ListCrossover(new ConstantGenerator(2), - crossOverControl.getNumberGenerator())); + crossOverControl.getNumberGenerator())); operators.add(new RemovePolygonMutation(removePolygonControl.getNumberGenerator())); operators.add(new MovePolygonMutation(movePolygonControl.getNumberGenerator())); operators.add(new ListOperator(new RemoveVertexMutation(canvasSize, - removeVertexControl.getNumberGenerator()))); + removeVertexControl.getNumberGenerator()))); operators.add(new ListOperator(new AdjustVertexMutation(canvasSize, - moveVertexControl.getNumberGenerator(), - new GaussianGenerator(0, 3, rng)))); + moveVertexControl.getNumberGenerator(), + NumberGenerators.multiplyDouble(new GaussianGenerator(0, 3, rng), damping)))); operators.add(new ListOperator(new AddVertexMutation(canvasSize, - addVertexControl.getNumberGenerator()))); - operators.add(new ListOperator(new PolygonColourMutation(changeColourControl.getNumberGenerator(), - new GaussianGenerator(0, 20, rng)))); - operators.add(new AddPolygonMutation(addPolygonControl.getNumberGenerator(), factory, 50)); + addVertexControl.getNumberGenerator()))); + operators.add(new ListOperator(new PolygonColourMutation(changeColourControl. + getNumberGenerator(), + NumberGenerators.multiplyDouble(new GaussianGenerator(0, 20, rng), damping)))); + operators.add(new AddPolygonMutation(addPolygonControl.getNumberGenerator(), factory, + PolygonImageFactory.MAXIMUM_POLYGON_COUNT)); return new EvolutionPipeline>(operators); } - } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java index 4ec530d3..a58aa695 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java @@ -17,6 +17,7 @@ import java.awt.Dimension; import java.awt.Point; +import java.awt.Polygon; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -25,20 +26,20 @@ import org.uncommons.maths.random.Probability; /** - * Evolutionary operator for mutating individual polygons. Polygons are mutated - * by removing a point, according to some probability. + * Evolutionary operator for mutating individual polygons. Polygons are mutated by removing a point, + * according to some probability. + *

* @author Daniel Dyer */ public class RemoveVertexMutation extends AbstractVertexMutation { /** - * @param mutationProbability A {@link NumberGenerator} that controls the - * probability that a point will be removed. - * @param canvasSize The size of the canvas. Used to constrain the positions - * of the points. + * @param mutationProbability A {@link NumberGenerator} that controls the probability that a + * point will be removed. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. */ public RemoveVertexMutation(Dimension canvasSize, - NumberGenerator mutationProbability) + NumberGenerator mutationProbability) { super(mutationProbability, canvasSize); } @@ -46,11 +47,10 @@ public RemoveVertexMutation(Dimension canvasSize, /** * @param mutationProbability The probability that a point will be removed. - * @param canvasSize The size of the canvas. Used to constrain the positions - * of the points. + * @param canvasSize The size of the canvas. Used to constrain the positions of the points. */ public RemoveVertexMutation(Dimension canvasSize, - Probability mutationProbability) + Probability mutationProbability) { this(canvasSize, new ConstantGenerator(mutationProbability)); } @@ -61,16 +61,39 @@ protected List mutateVertices(List vertices, Random rng) { // A single point is removed with the configured probability, unless // we already have the minimum permitted number of points. - if (vertices.size() > PolygonImageFactory.MINIMUM_VERTEX_COUNT - && getMutationProbability().nextValue().nextEvent(rng)) - { - List newVertices = new ArrayList(vertices); - newVertices.remove(rng.nextInt(newVertices.size())); - return newVertices; - } - else // Nothing changed. + if (vertices.size() <= PolygonImageFactory.MINIMUM_VERTEX_COUNT + || !getMutationProbability().nextValue().nextEvent(rng)) { return vertices; } + int index; + Polygon polygon; + int retry = 0; + do + { + index = rng.nextInt(vertices.size()); + polygon = new Polygon(); + for (int i = 0; i < index; ++i) + { + Point p = vertices.get(i); + polygon.addPoint(p.x, p.y); + } + for (int i = index + 1, size = vertices.size(); i < size; ++i) + { + Point p = vertices.get(i); + polygon.addPoint(p.x, p.y); + } + ++retry; + if (retry > vertices.size() * 10) + { + assert (false): "Cannot remove vertex from: " + vertices; + // Perhaps it's impossible to return the vertex without causing + // self-intersection. + return vertices; + } + } while (Path2Ds.isSelfIntersecting(polygon.getPathIterator(null))); + List newVertices = new ArrayList(vertices); + newVertices.remove(index); + return newVertices; } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java b/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java index ac56c9f2..4e7ed2d0 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java @@ -19,8 +19,9 @@ import org.uncommons.watchmaker.framework.FitnessEvaluator; /** - * Evaluates strings and assigns a fitness score based on how many characters - * differ from the equivalent positions in a given target string. + * Evaluates strings and assigns a fitness score based on how many characters differ from the + * equivalent positions in a given target string. + *

* @author Daniel Dyer */ public class StringEvaluator implements FitnessEvaluator @@ -63,9 +64,6 @@ public double getFitness(String candidate, } - /** - * {@inheritDoc} - */ public boolean isNatural() { return false; diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java b/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java index d8425fa6..deff155c 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java @@ -19,11 +19,7 @@ import java.util.List; 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.GenerationalEvolutionEngine; -import org.uncommons.watchmaker.framework.PopulationData; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.factories.StringFactory; import org.uncommons.watchmaker.framework.operators.EvolutionPipeline; import org.uncommons.watchmaker.framework.operators.StringCrossover; @@ -32,13 +28,16 @@ import org.uncommons.watchmaker.framework.termination.TargetFitness; /** - * Simple evolutionary algorithm that evolves a population of randomly-generated - * strings until at least one matches a specified target string. + * Simple evolutionary algorithm that evolves a population of randomly-generated strings until at + * least one matches a specified target string. + *

* @author Daniel Dyer */ public final class StringsExample { private static final char[] ALPHABET = new char[27]; + + static { for (char c = 'A'; c <= 'Z'; c++) @@ -66,19 +65,21 @@ public static void main(String[] args) public static String evolveString(String target) { StringFactory factory = new StringFactory(ALPHABET, target.length()); - List> operators = new ArrayList>(2); + List> operators = + new ArrayList>(2); operators.add(new StringMutation(ALPHABET, new Probability(0.02d))); operators.add(new StringCrossover()); EvolutionaryOperator pipeline = new EvolutionPipeline(operators); - EvolutionEngine engine = new GenerationalEvolutionEngine(factory, - pipeline, - new StringEvaluator(target), - new RouletteWheelSelection(), - new MersenneTwisterRNG()); + EvolutionEngine engine = + new GenerationalEvolutionEngine(factory, + pipeline, + new StringEvaluator(target), + new RouletteWheelSelection(), + new MersenneTwisterRNG()); engine.addEvolutionObserver(new EvolutionLogger()); return engine.evolve(100, // 100 individuals in the population. - 5, // 5% elitism. - new TargetFitness(0, false)); + 5, // 5% elitism. + new TargetFitness(0, false)); } @@ -102,18 +103,17 @@ private static String convertArgs(String[] args) return result.toString().toUpperCase(); } - /** * Trivial evolution observer for displaying information at the end * of each generation. */ private static class EvolutionLogger implements EvolutionObserver { - public void populationUpdate(PopulationData data) + public void populationUpdate(PopulationData data) { System.out.printf("Generation %d: %s\n", - data.getGenerationNumber(), - data.getBestCandidate()); + data.getGenerationNumber(), + data.getBestCandidate()); } } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java index e1d482af..b45f99da 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java @@ -26,16 +26,7 @@ import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; -import javax.swing.SpringLayout; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.uncommons.maths.random.DiscreteUniformGenerator; import org.uncommons.maths.random.MersenneTwisterRNG; import org.uncommons.maths.random.PoissonGenerator; @@ -43,12 +34,7 @@ import org.uncommons.swing.SpringUtilities; import org.uncommons.swing.SwingBackgroundTask; import org.uncommons.watchmaker.examples.AbstractExampleApplet; -import org.uncommons.watchmaker.framework.EvolutionEngine; -import org.uncommons.watchmaker.framework.EvolutionObserver; -import org.uncommons.watchmaker.framework.EvolutionaryOperator; -import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine; -import org.uncommons.watchmaker.framework.PopulationData; -import org.uncommons.watchmaker.framework.SelectionStrategy; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.operators.EvolutionPipeline; import org.uncommons.watchmaker.framework.selection.TournamentSelection; import org.uncommons.watchmaker.framework.termination.TargetFitness; @@ -59,57 +45,67 @@ /** * An evolutionary Sudoku solver. + *

* @author Daniel Dyer */ public class SudokuApplet extends AbstractExampleApplet { - private static final String[] BLANK_PUZZLE = {".........", - ".........", - ".........", - ".........", - ".........", - ".........", - ".........", - ".........", - "........."}; - - private static final String[] EASY_PUZZLE = {"4.5...9.7", - ".2..9..6.", - "39.6.7.28", - "9..3.2..6", - "7..9.6..3", - "5..4.8..1", - "28.1.5.49", - ".7..3..8.", - "6.4...3.2"}; - - private static final String[] MEDIUM_PUZZLE = {"....3....", - ".....6293", - ".2.9.48..", - ".754...38", - "..46.71..", - "91...547.", - "..38.9.1.", - "1567.....", - "....1...."}; - - private static final String[] HARD_PUZZLE = {"...891...", - "....5.8..", - ".....6.2.", - "5....4..8", - "49....67.", - "8.13....5", - ".6..8..9.", - "..5.4.2.7", - "...1.3.8."}; - - private static final String[][] PUZZLES = {EASY_PUZZLE, - MEDIUM_PUZZLE, - HARD_PUZZLE, - BLANK_PUZZLE}; - + private static final String[] BLANK_PUZZLE = + { + ".........", + ".........", + ".........", + ".........", + ".........", + ".........", + ".........", + ".........", + "........." + }; + private static final String[] EASY_PUZZLE = + { + "4.5...9.7", + ".2..9..6.", + "39.6.7.28", + "9..3.2..6", + "7..9.6..3", + "5..4.8..1", + "28.1.5.49", + ".7..3..8.", + "6.4...3.2" + }; + private static final String[] MEDIUM_PUZZLE = + { + "....3....", + ".....6293", + ".2.9.48..", + ".754...38", + "..46.71..", + "91...547.", + "..38.9.1.", + "1567.....", + "....1...." + }; + private static final String[] HARD_PUZZLE = + { + "...891...", + "....5.8..", + ".....6.2.", + "5....4..8", + "49....67.", + "8.13....5", + ".6..8..9.", + "..5.4.2.7", + "...1.3.8." + }; + private static final String[][] PUZZLES = + { + EASY_PUZZLE, + MEDIUM_PUZZLE, + HARD_PUZZLE, + BLANK_PUZZLE + }; private SelectionStrategy selectionStrategy; - private SudokuView sudokuView; private JButton solveButton; private JComboBox puzzleCombo; @@ -139,10 +135,13 @@ private JComponent createControls() JPanel controls = new JPanel(new BorderLayout()); JPanel innerPanel = new JPanel(new SpringLayout()); innerPanel.add(new JLabel("Puzzle: ")); - puzzleCombo = new JComboBox(new String[]{"Easy Demo (38 givens)", - "Medium Demo (32 givens)", - "Hard Demo (28 givens)", - "Custom"}); + puzzleCombo = new JComboBox(new String[] + { + "Easy Demo (38 givens)", + "Medium Demo (32 givens)", + "Hard Demo (28 givens)", + "Custom" + }); innerPanel.add(puzzleCombo); puzzleCombo.addItemListener(new ItemListener() { @@ -152,10 +151,12 @@ public void itemStateChanged(ItemEvent ev) } }); innerPanel.add(new JLabel("Selection Pressure: ")); - ProbabilityParameterControl selectionPressure = new ProbabilityParameterControl(Probability.EVENS, - Probability.ONE, - 2, - new Probability(0.85d)); + ProbabilityParameterControl selectionPressure = new ProbabilityParameterControl( + Probability.EVENS, + Probability.ONE, + 2, + new Probability( + 0.85d)); selectionStrategy = new TournamentSelection(selectionPressure.getNumberGenerator()); innerPanel.add(selectionPressure.getControl()); @@ -184,8 +185,8 @@ public void actionPerformed(ActionEvent ev) solveButton.setEnabled(false); abortControl.reset(); createTask(sudokuView.getPuzzle(), - populationSize, - (int) Math.round(populationSize * 0.05)).execute(); // Elite count is 5%. + populationSize, + (int) Math.round(populationSize * 0.05)).execute(); // Elite count is 5%. } }); @@ -204,8 +205,8 @@ public void actionPerformed(ActionEvent ev) * the GUI when it is done. */ private SwingBackgroundTask createTask(final String[] puzzle, - final int populationSize, - final int eliteCount) + final int populationSize, + final int eliteCount) { return new SwingBackgroundTask() { @@ -213,28 +214,30 @@ private SwingBackgroundTask createTask(final String[] puzzle, protected Sudoku performTask() { Random rng = new MersenneTwisterRNG(); - List> operators = new ArrayList>(2); + List> operators = + new ArrayList>(2); // Cross-over rows between parents (so offspring is x rows from parent1 and // y rows from parent2). operators.add(new SudokuVerticalCrossover()); // Mutate the order of cells within individual rows. operators.add(new SudokuRowMutation(new PoissonGenerator(2, rng), - new DiscreteUniformGenerator(1, 8, rng))); + new DiscreteUniformGenerator(1, 8, rng))); EvolutionaryOperator pipeline = new EvolutionPipeline(operators); - EvolutionEngine engine = new GenerationalEvolutionEngine(new SudokuFactory(puzzle), - pipeline, - new SudokuEvaluator(), - selectionStrategy, - rng); + EvolutionEngine engine = + new GenerationalEvolutionEngine(new SudokuFactory(puzzle), + pipeline, + new SudokuEvaluator(), + selectionStrategy, + rng); engine.addEvolutionObserver(new SwingEvolutionObserver( new GridViewUpdater(), 300, TimeUnit.MILLISECONDS)); engine.addEvolutionObserver(statusBar); return engine.evolve(populationSize, - eliteCount, - new TargetFitness(0, false), // Continue until a perfect solution is found... - abortControl.getTerminationCondition()); // ...or the user aborts. + eliteCount, + new TargetFitness(0, false), // Continue until a perfect solution is found... + abortControl.getTerminationCondition()); // ...or the user aborts. } @@ -249,15 +252,13 @@ protected void postProcessing(Sudoku result) }; } - - /** * Evolution observer for displaying information at the end of * each generation. */ private class GridViewUpdater implements EvolutionObserver { - public void populationUpdate(final PopulationData data) + public void populationUpdate(final PopulationData data) { SwingUtilities.invokeLater(new Runnable() { @@ -278,5 +279,4 @@ public static void main(String[] args) { new SudokuApplet().displayInFrame("Watchmaker Framework - Sudoku Example"); } - } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java index ca443943..4259daa8 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java @@ -21,19 +21,19 @@ import org.uncommons.watchmaker.framework.FitnessEvaluator; /** - * {@link org.uncommons.watchmaker.framework.FitnessEvaluator} for potential Sudoku - * solutions. Counts the number of duplicate values in rows, columns and sub-grids. - * The fitness score is the total number of duplicate values. Therefore, a fitness - * score of zero indicates a perfect solution. + * {@link org.uncommons.watchmaker.framework.FitnessEvaluator} for potential Sudoku solutions. + * Counts the number of duplicate values in rows, columns and sub-grids. The fitness score is the + * total number of duplicate values. Therefore, a fitness score of zero indicates a perfect solution. + *

* @author Daniel Dyer */ public class SudokuEvaluator implements FitnessEvaluator { /** - * The fitness score for a potential Sudoku solution is the number of - * cells that conflict with other cells in the grid (i.e. if there are - * two 7s in the same column, both of these cells are conflicting). A - * lower score indicates a fitter individual. + * The fitness score for a potential Sudoku solution is the number of cells that conflict with + * other cells in the grid (i.e. if there are two 7s in the same column, both of these cells are + * conflicting). A lower score indicates a fitter individual. + *

* @param candidate The Sudoku grid to evaluate. * @param population {@inheritDoc} * @return The fitness score for the specified individual. diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java index d96ed1a6..9a6d8e2b 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java @@ -15,24 +15,19 @@ //============================================================================= package org.uncommons.watchmaker.examples.sudoku; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory; /** - * Factory that generates potential Sudoku solutions from a list of "givens". - * The rows of the generated solutions will all be valid (i.e. no duplicate values) - * but there are no constraints on the columns or sub-grids (these will be refined - * by the evolutionary algorithm). + * Factory that generates potential Sudoku solutions from a list of "givens". The rows of the + * generated solutions will all be valid (i.e. no duplicate values) but there are no constraints on + * the columns or sub-grids (these will be refined by the evolutionary algorithm). + *

* @author Daniel Dyer */ public class SudokuFactory extends AbstractCandidateFactory { private static final List VALUES = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); - private final Sudoku.Cell[][] template; private final List> nonFixedValues = new ArrayList>(Sudoku.SIZE); @@ -69,7 +64,8 @@ public SudokuFactory(String... pattern) char[] rowPattern = pattern[i].toCharArray(); if (rowPattern.length != Sudoku.SIZE) { - throw new IllegalArgumentException("Sudoku layout must have " + Sudoku.SIZE + " cells in each row."); + throw new IllegalArgumentException("Sudoku layout must have " + Sudoku.SIZE + + " cells in each row."); } for (int j = 0; j < rowPattern.length; j++) { @@ -84,7 +80,8 @@ public SudokuFactory(String... pattern) } else if (c != '.') { - throw new IllegalArgumentException("Unexpected character at (" + i + ", " + j + "): " + c); + throw new IllegalArgumentException("Unexpected character at (" + i + ", " + j + + "): " + c); } } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java index 312e6d46..11f052d7 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java @@ -23,11 +23,11 @@ import org.uncommons.watchmaker.framework.FitnessEvaluator; /** - * Naive brute-force solution to the travelling salesman problem. It would take about - * a day and a half to brute-force the 15-city travelling salesman problem on a home - * computer using this implementation. However, this is a not the best possible - * implementation that is guaranteed to find a the shortest route (for example there - * is no branch-and-bound optimisation). + * Naive brute-force solution to the travelling salesman problem. It would take about a day and a + * half to brute-force the 15-city travelling salesman problem on a home computer using this + * implementation. However, this is a not the best possible implementation that is guaranteed to find + * a the shortest route (for example there is no branch-and-bound optimisation). + *

* @author Daniel Dyer */ public class BruteForceTravellingSalesman implements TravellingSalesmanStrategy @@ -43,16 +43,13 @@ public BruteForceTravellingSalesman(DistanceLookup distances) this.distances = distances; } - - /** - * {@inheritDoc} - */ + public String getDescription() { return "Brute Force"; } - + /** * To reduce the search space we will only consider routes that start * and end at one city (whichever is first in the collection). All other diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java index 547347ff..e680d7b5 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java @@ -15,20 +15,19 @@ //============================================================================= package org.uncommons.watchmaker.examples.travellingsalesman; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; /** - * This class contains data about cities in Europe and the distances - * between them. + * This class contains data about cities in Europe and the distances between them. + *

* @author Daniel Dyer */ public final class EuropeanDistanceLookup implements DistanceLookup { - private static final Map> DISTANCES = new HashMap>(15); + private static final Map> DISTANCES = + new HashMap>(15); + + static { // Distances are in km as the crow flies (from http://www.indo.com/distance/) @@ -305,9 +304,6 @@ public final class EuropeanDistanceLookup implements DistanceLookup } - /** - * {@inheritDoc} - */ public List getKnownCities() { List cities = new ArrayList(DISTANCES.keySet()); @@ -316,9 +312,6 @@ public List getKnownCities() } - /** - * {@inheritDoc} - */ public int getDistance(String startingCity, String destinationCity) { return DISTANCES.get(startingCity).get(destinationCity); diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java index 6814101f..7e34380a 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java @@ -15,20 +15,10 @@ //============================================================================= package org.uncommons.watchmaker.examples.travellingsalesman; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.Random; +import java.util.*; import org.uncommons.maths.random.MersenneTwisterRNG; import org.uncommons.maths.random.PoissonGenerator; -import org.uncommons.watchmaker.framework.CandidateFactory; -import org.uncommons.watchmaker.framework.EvolutionEngine; -import org.uncommons.watchmaker.framework.EvolutionObserver; -import org.uncommons.watchmaker.framework.EvolutionaryOperator; -import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine; -import org.uncommons.watchmaker.framework.PopulationData; -import org.uncommons.watchmaker.framework.SelectionStrategy; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.factories.ListPermutationFactory; import org.uncommons.watchmaker.framework.operators.EvolutionPipeline; import org.uncommons.watchmaker.framework.operators.ListOrderCrossover; @@ -36,8 +26,8 @@ import org.uncommons.watchmaker.framework.termination.GenerationCount; /** - * Evolutionary algorithm for finding (approximate) solutions to the - * travelling salesman problem. + * Evolutionary algorithm for finding (approximate) solutions to the travelling salesman problem. + *

* @author Daniel Dyer */ public class EvolutionaryTravellingSalesman implements TravellingSalesmanStrategy @@ -50,6 +40,7 @@ public class EvolutionaryTravellingSalesman implements TravellingSalesmanStrateg private final boolean crossover; private final boolean mutation; + /** * Creates an evolutionary Travelling Salesman solver with the * specified configuration. @@ -65,16 +56,17 @@ public class EvolutionaryTravellingSalesman implements TravellingSalesmanStrateg * @param mutation Whether or not to use a mutation operator in the evolution. */ public EvolutionaryTravellingSalesman(DistanceLookup distances, - SelectionStrategy> selectionStrategy, - int populationSize, - int eliteCount, - int generationCount, - boolean crossover, - boolean mutation) + SelectionStrategy> selectionStrategy, + int populationSize, + int eliteCount, + int generationCount, + boolean crossover, + boolean mutation) { if (!crossover && !mutation) { - throw new IllegalArgumentException("At least one of cross-over or mutation must be selected."); + throw new IllegalArgumentException( + "At least one of cross-over or mutation must be selected."); } this.distances = distances; this.selectionStrategy = selectionStrategy; @@ -86,14 +78,11 @@ public EvolutionaryTravellingSalesman(DistanceLookup distances, } - /** - * {@inheritDoc} - */ public String getDescription() { String selectionName = selectionStrategy.toString(); return "Evolution (pop: " + populationSize + ", gen: " + generationCount - + ", elite: " + eliteCount + ", " + selectionName + ")"; + + ", elite: " + eliteCount + ", " + selectionName + ")"; } @@ -109,12 +98,13 @@ public String getDescription() * specified cities once. */ public List calculateShortestRoute(Collection cities, - final ProgressListener progressListener) + final ProgressListener progressListener) { Random rng = new MersenneTwisterRNG(); // Set-up evolution pipeline (cross-over followed by mutation). - List>> operators = new ArrayList>>(2); + List>> operators = + new ArrayList>>(2); if (crossover) { operators.add(new ListOrderCrossover()); @@ -122,31 +112,30 @@ public List calculateShortestRoute(Collection cities, if (mutation) { operators.add(new ListOrderMutation(new PoissonGenerator(1.5, rng), - new PoissonGenerator(1.5, rng))); + new PoissonGenerator(1.5, rng))); } EvolutionaryOperator> pipeline = new EvolutionPipeline>(operators); - CandidateFactory> candidateFactory - = new ListPermutationFactory(new LinkedList(cities)); - EvolutionEngine> engine - = new GenerationalEvolutionEngine>(candidateFactory, - pipeline, - new RouteEvaluator(distances), - selectionStrategy, - rng); + CandidateFactory> candidateFactory = + new ListPermutationFactory(new LinkedList(cities)); + EvolutionEngine> engine = new GenerationalEvolutionEngine>( + candidateFactory, + pipeline, + new RouteEvaluator(distances), + selectionStrategy, + rng); if (progressListener != null) { engine.addEvolutionObserver(new EvolutionObserver>() { - public void populationUpdate(PopulationData> data) + public > void populationUpdate(PopulationData data) { - progressListener.updateProgress(((double) data.getGenerationNumber() + 1) / generationCount * 100); + progressListener.updateProgress(((double) data.getGenerationNumber() + 1) + / generationCount * 100); } }); } - return engine.evolve(populationSize, - eliteCount, - new GenerationCount(generationCount)); + return engine.evolve(populationSize, eliteCount, new GenerationCount(generationCount)); } } diff --git a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java index 144d593a..5878a55f 100644 --- a/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java +++ b/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java @@ -19,11 +19,11 @@ import org.uncommons.watchmaker.framework.FitnessEvaluator; /** - * Fitness evalator that measures the total distance of a route in the travelling salesman - * problem. The fitness score of a route is the total distance (in km). A route - * is represented as a list of cities in the order that they will be visited. - * The last leg of the journey is from the last city in the list back to the - * first. + * Fitness evalator that measures the total distance of a route in the travelling salesman problem. + * The fitness score of a route is the total distance (in km). A route is represented as a list of + * cities in the order that they will be visited. The last leg of the journey is from the last city + * in the list back to the first. + *

* @author Daniel Dyer */ public class RouteEvaluator implements FitnessEvaluator> @@ -44,7 +44,7 @@ public RouteEvaluator(DistanceLookup distances) * Calculates the length of an evolved route. * @param candidate The route to evaluate. * @param population {@inheritDoc} - * @return The total distance (in kilometres) of a journey that visits + * @return The total distance (in kilometers) of a journey that visits * each city in order and returns to the starting point. */ public double getFitness(List candidate, diff --git a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java index a4b81426..73903b1d 100644 --- a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java +++ b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java @@ -39,8 +39,8 @@ public class AdjustVertexMutationTest public void testAdjustVertex() { final Point point1 = new Point(1, 1); - final Point point2 = new Point(2, 2); - final Point point3 = new Point(3, 3); + final Point point2 = new Point(2, 3); + final Point point3 = new Point(5, 5); List points = Arrays.asList(point1, point2, point3); ColouredPolygon polygon = new ColouredPolygon(Color.RED, points); List image = new ArrayList(1); diff --git a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java index bbdb1f92..4173ca3c 100644 --- a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java +++ b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java @@ -43,10 +43,11 @@ public void testColourMutation() List image = Arrays.asList(polygon); List mutatedImage = mutation.apply(image, ExamplesTestUtils.getRNG()); Color mutatedColour = mutatedImage.get(0).getColour(); - assert mutatedColour.getRed() == 129 : "Red component should have been incremented, is " + mutatedColour.getRed(); - assert mutatedColour.getGreen() == 129 : "Green component should have been incremented, is " + mutatedColour.getGreen(); - assert mutatedColour.getBlue() == 129 : "Blue component should have been incremented, is " + mutatedColour.getBlue(); - assert mutatedColour.getAlpha() == 129 : "Alpha component should have been incremented, is " + mutatedColour.getAlpha(); + assert mutatedColour.getRed() == 129 + || mutatedColour.getGreen() == 129 + || mutatedColour.getBlue() == 129 + || mutatedColour.getAlpha() == 129 : "Red, Green, Blue or Alpha component should have " + + "been incremented, is " + mutatedColour; } diff --git a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java index fbd94e8a..a5e9e319 100644 --- a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java +++ b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java @@ -17,6 +17,7 @@ import java.awt.Color; import java.awt.Dimension; +import java.awt.Graphics; import java.awt.Point; import java.awt.image.BufferedImage; import java.util.Arrays; @@ -26,13 +27,13 @@ /** * Unit test for {@link PolygonImageEvaluator}. + *

* @author Daniel Dyer */ public class PolygonImageEvaluatorTest { /** - * An image that is identical to the target image should have a fitness - * of zero. + * An image that is identical to the target image should have a fitness of zero. */ @Test(groups = "display-required") public void testPerfectMatch() @@ -40,12 +41,15 @@ public void testPerfectMatch() Dimension canvasSize = new Dimension(100, 100); PolygonImageFactory factory = new PolygonImageFactory(canvasSize); List image = factory.generateRandomCandidate(new MersenneTwisterRNG()); - - BufferedImage targetImage = new PolygonImageRenderer(canvasSize, false, null).render(image); - PolygonImageEvaluator evaluator = new PolygonImageEvaluator(targetImage); - + + final boolean antialias = false; + BufferedImage targetImage = new PolygonImageRenderer(canvasSize, antialias, null).render( + image); + PolygonImageEvaluator evaluator = new PolygonImageEvaluator(targetImage, antialias); + double fitness = evaluator.getFitness(image, null); - assert fitness == 0 : "Fitness should be zero when image is an exact match."; + assert fitness == image.size(): "Fitness should be equal to the number of polygons when " + + "image is an exact match."; } @@ -57,26 +61,28 @@ public void testDifferentImages() { Dimension canvasSize = new Dimension(100, 100); List targetImage = Arrays.asList(new ColouredPolygon(Color.BLACK, - Arrays.asList(new Point(0, 0), - new Point(99, 0), - new Point(99, 99), - new Point(0, 99)))); + Arrays.asList(new Point(0, 0), + new Point(99, 0), + new Point(99, 99), + new Point(0, 99)))); List candidateImage = Arrays.asList(new ColouredPolygon(Color.WHITE, - Arrays.asList(new Point(0, 0), - new Point(99, 0), - new Point(99, 99), - new Point(0, 99)))); - - BufferedImage renderedTarget = new PolygonImageRenderer(canvasSize, false, null).render(targetImage); - PolygonImageEvaluator evaluator = new PolygonImageEvaluator(renderedTarget); - + Arrays.asList(new Point(0, 0), + new Point(99, 0), + new Point(99, 99), + new Point(0, 99)))); + + final boolean antialias = false; + BufferedImage renderedTarget = new PolygonImageRenderer(canvasSize, antialias, null).render( + targetImage); + PolygonImageEvaluator evaluator = new PolygonImageEvaluator(renderedTarget, antialias); + double fitness = evaluator.getFitness(candidateImage, null); - assert fitness > 0 : "Fitness should be non-zero when image does not match target."; + assert fitness > 0: "Fitness should be non-zero when image does not match target."; } /** - * If the image is not INT_RGB, it will be converted. This should not affect the results. + * If the image is not INT_RGB, it will be converted. This should not affect the results. */ @Test(groups = "display-required") public void testImageConversion() @@ -84,18 +90,21 @@ public void testImageConversion() Dimension canvasSize = new Dimension(100, 100); PolygonImageFactory factory = new PolygonImageFactory(canvasSize); List image = factory.generateRandomCandidate(new MersenneTwisterRNG()); - - BufferedImage targetImage = new PolygonImageRenderer(canvasSize, false, null).render(image); + + final boolean antialias = false; + BufferedImage targetImage = new PolygonImageRenderer(canvasSize, antialias, null).render(image); // Convert target image to some format that will have to be converted to INT_RGB. BufferedImage newImage = new BufferedImage(targetImage.getWidth(), - targetImage.getHeight(), - BufferedImage.TYPE_3BYTE_BGR); // Sub-optimal image type. - newImage.getGraphics().drawImage(targetImage, 0, 0, null); - - PolygonImageEvaluator evaluator = new PolygonImageEvaluator(newImage); - + targetImage.getHeight(), + BufferedImage.TYPE_3BYTE_BGR); // Sub-optimal image type. + Graphics g = newImage.getGraphics(); + g.drawImage(targetImage, 0, 0, null); + g.dispose(); + + PolygonImageEvaluator evaluator = new PolygonImageEvaluator(newImage, antialias); + double fitness = evaluator.getFitness(image, null); - assert fitness == 0 : "Fitness should be zero when image is an exact match."; + assert fitness == image.size(): "Fitness should be equal to the number of polygons when " + + "image is an exact match. Fitness: " + fitness + ", polygons: " + image.size(); } - } diff --git a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java index 3e446632..87d59562 100644 --- a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java +++ b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java @@ -29,17 +29,20 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.uncommons.maths.number.ConstantGenerator; import org.uncommons.watchmaker.examples.ExamplesTestUtils; import org.uncommons.watchmaker.framework.EvolutionaryOperator; /** * Unit test for the {@link ProbabilitiesPanel} class. + *

* @author Daniel Dyer */ public class ProbabilitiesPanelTest { private Robot robot; + @BeforeMethod(groups = "display-required") public void prepare() { @@ -53,7 +56,8 @@ public void cleanUp() robot.cleanUp(); robot = null; } - + + @Test(groups = "display-required") public void testSetAllProbabilitiesToZero() { @@ -77,27 +81,26 @@ public void testSetAllProbabilitiesToZero() Dimension canvasSize = new Dimension(100, 100); PolygonImageFactory factory = new PolygonImageFactory(canvasSize); EvolutionaryOperator> operator = panel.createEvolutionPipeline(factory, - canvasSize, - ExamplesTestUtils.getRNG()); + canvasSize, ExamplesTestUtils.getRNG(), new ConstantGenerator(1.0)); List candidate1 = Arrays.asList(new ColouredPolygon(Color.WHITE, - Arrays.asList(new Point(1, 1)))); + Arrays.asList(new Point(1, 1)))); List candidate2 = Arrays.asList(new ColouredPolygon(Color.BLACK, - Arrays.asList(new Point(2, 2)))); + Arrays.asList(new Point(2, 2)))); List> population = new ArrayList>(2); population.add(candidate1); population.add(candidate2); - + List> evolved = operator.apply(population, - ExamplesTestUtils.getRNG()); + ExamplesTestUtils.getRNG()); // Candidate order may have changed, but individual candidates should remain unaltered. assert (checkEquals(evolved.get(0), candidate1) && checkEquals(evolved.get(1), candidate2)) - || (checkEquals(evolved.get(0), candidate2) && checkEquals(evolved.get(1), candidate1)) - : "Candidates should be unaltered when all probabilities are zero."; + || (checkEquals(evolved.get(0), candidate2) && checkEquals(evolved.get(1), candidate1)): + "Candidates should be unaltered when all probabilities are zero."; } private boolean checkEquals(List candidate1, - List candidate2) + List candidate2) { if (candidate1.size() != candidate2.size()) { diff --git a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java index e204d55b..37f1903b 100644 --- a/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java +++ b/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java @@ -37,7 +37,7 @@ public class RemoveVertexMutationTest @Test public void testRemoveVertex() { - List points = Arrays.asList(new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(4, 4)); + List points = Arrays.asList(new Point(1, 1), new Point(2, 3), new Point(3, 4), new Point(4, 5)); ColouredPolygon polygon = new ColouredPolygon(Color.RED, points); List image = new ArrayList(1); image.add(polygon); diff --git a/framework/nb-configuration.xml b/framework/nb-configuration.xml index ae35717d..2db68b70 100644 --- a/framework/nb-configuration.xml +++ b/framework/nb-configuration.xml @@ -14,10 +14,10 @@ That way multiple projects can share the same settings (useful for formatting ru Any value defined here will override the pom.xml file value but is only applicable to the current project. --> project - 2 - 2 - 2 - false + 4 + 4 + 4 + true 100 words 0 @@ -29,33 +29,28 @@ Any value defined here will override the pom.xml file value but is only applicab WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - WRAP_IF_LONG true + WRAP_IF_LONG WRAP_IF_LONG - true true + true NEW_LINE - WRAP_IF_LONG WRAP_IF_LONG + WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - true - 100 WRAP_IF_LONG true LEAVE_ALONE - 2 - 2 - false + 4 NEW_LINE NEW_LINE LEAVE_ALONE WRAP_IF_LONG + 2 diff --git a/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java b/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java index 91635a90..bdad875c 100644 --- a/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java +++ b/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java @@ -19,13 +19,12 @@ import java.util.concurrent.locks.ReentrantLock; /** - *

Thread-safe source for partitioned unique IDs. A single instance of this class - * represents a single 'partition' in the space of possible IDs. By creating - * multiple instances with different discriminators, multiple entities may generate - * globally unique IDs independently.

- *

Any given instance of this class may generate a maximum of 2^31 unique values - * (the most significant 4 bytes are fixed and the least significant 4 bytes vary - * in sequence).

+ *

Thread-safe source for partitioned unique IDs. A single instance of this class represents a + * single 'partition' in the space of possible IDs. By creating multiple instances with different + * discriminators, multiple entities may generate globally unique IDs independently.

Any given + * instance of this class may generate a maximum of 2^31 unique values (the most significant 4 bytes + * are fixed and the least significant 4 bytes vary in sequence).

+ *

* @author Daniel Dyer */ public final class CompositeIDSource implements IDSource @@ -45,9 +44,6 @@ public CompositeIDSource(int topPart) } - /** - * {@inheritDoc} - */ public Long nextID() { lock.lock(); diff --git a/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java b/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java index bf6c3cb0..59836e36 100644 --- a/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java +++ b/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java @@ -19,15 +19,15 @@ import java.util.concurrent.locks.ReentrantLock; /** - * Thread-safe source for unique IDs. This particular implementation restricts - * values to those positive integer values that can be represented by the int data type. - * Provides sequenced 32-bit IDs. + * Thread-safe source for unique IDs. This particular implementation restricts values to those + * positive integer values that can be represented by the int data type. Provides sequenced 32-bit + * IDs. + *

* @author Daniel Dyer */ public final class IntSequenceIDSource implements IDSource { private static final long SECONDS_IN_HOUR = 3600L; - private final Lock lock = new ReentrantLock(); private final long startTime; private int lastID = -1; @@ -57,9 +57,6 @@ public IntSequenceIDSource() } - /** - * {@inheritDoc} - */ public Integer nextID() { lock.lock(); @@ -68,7 +65,8 @@ public Integer nextID() if (lastID == Integer.MAX_VALUE) { long hours = (System.currentTimeMillis() - startTime) / SECONDS_IN_HOUR; - throw new IDSourceExhaustedException("32-bit ID source exhausted after " + hours + " hours."); + throw new IDSourceExhaustedException("32-bit ID source exhausted after " + hours + + " hours."); } ++lastID; return lastID; diff --git a/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java b/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java index 3ea85a99..22be730a 100644 --- a/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java +++ b/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java @@ -19,15 +19,15 @@ import java.util.concurrent.locks.ReentrantLock; /** - * Thread-safe source for unique IDs. This particular implementation restricts - * values to those positive integer values that can be represented by the long data type. - * Provides sequenced 64-bit IDs. + * Thread-safe source for unique IDs. This particular implementation restricts values to those + * positive integer values that can be represented by the long data type. Provides sequenced 64-bit + * IDs. + *

* @author Daniel Dyer */ public final class LongSequenceIDSource implements IDSource { private static final int SECONDS_IN_DAY = 86400; - private final Lock lock = new ReentrantLock(); private final long startTime; private long lastID = -1; @@ -57,9 +57,6 @@ public LongSequenceIDSource() } - /** - * {@inheritDoc} - */ public Long nextID() { lock.lock(); @@ -68,7 +65,8 @@ public Long nextID() if (lastID == Long.MAX_VALUE) { long days = (System.currentTimeMillis() - startTime) / SECONDS_IN_DAY; - throw new IDSourceExhaustedException("64-bit ID source exhausted after " + days + " days."); + throw new IDSourceExhaustedException("64-bit ID source exhausted after " + days + + " days."); } ++lastID; return lastID; diff --git a/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java b/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java index c4d683c8..fd4fa809 100644 --- a/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java +++ b/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java @@ -19,8 +19,9 @@ import java.util.concurrent.locks.ReentrantLock; /** - * Thread-safe ID source that wraps another source of IDs and adds a fixed String - * prefix to each ID generated. + * Thread-safe ID source that wraps another source of IDs and adds a fixed String prefix to each ID + * generated. + *

* @author Daniel Dyer */ public class StringPrefixIDSource implements IDSource @@ -29,6 +30,7 @@ public class StringPrefixIDSource implements IDSource private final String prefix; private final IDSource source; + /** * @param prefix A fixed String that is attached to the front of each ID. * @param source The source of IDs to which the prefix is added. @@ -40,9 +42,6 @@ public StringPrefixIDSource(String prefix, IDSource source) } - /** - * {@inheritDoc} - */ public String nextID() { lock.lock(); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java b/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java index 486f10dd..c4a79d2b 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java @@ -15,18 +15,14 @@ //============================================================================= 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.*; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * Base class for {@link EvolutionEngine} implementations. + *

* @param The type of entity evolved by the evolution engine. * @author Daniel Dyer * @see CandidateFactory @@ -36,15 +32,12 @@ public abstract class AbstractEvolutionEngine implements EvolutionEngine { // A single multi-threaded worker is shared among multiple evolution engine instances. private static FitnessEvaluationWorker concurrentWorker = null; - - private final Set> observers = new CopyOnWriteArraySet>(); - + private final Set> observers = + new CopyOnWriteArraySet>(); private final Random rng; private final CandidateFactory candidateFactory; private final FitnessEvaluator fitnessEvaluator; - private volatile boolean singleThreaded = false; - private List satisfiedTerminationConditions; @@ -59,8 +52,8 @@ public abstract class AbstractEvolutionEngine implements EvolutionEngine * evolutionary operators and selection strategies). */ protected AbstractEvolutionEngine(CandidateFactory candidateFactory, - FitnessEvaluator fitnessEvaluator, - Random rng) + FitnessEvaluator fitnessEvaluator, + Random rng) { this.candidateFactory = candidateFactory; this.fitnessEvaluator = fitnessEvaluator; @@ -68,64 +61,54 @@ protected AbstractEvolutionEngine(CandidateFactory candidateFactory, } - /** - * {@inheritDoc} - */ public T evolve(int populationSize, - int eliteCount, - TerminationCondition... conditions) + int eliteCount, + TerminationCondition... conditions) { return evolve(populationSize, - eliteCount, - Collections.emptySet(), - conditions); + eliteCount, + Collections.emptySet(), + conditions); } - /** - * {@inheritDoc} - */ public T evolve(int populationSize, - int eliteCount, - Collection seedCandidates, - TerminationCondition... conditions) + int eliteCount, + Collection seedCandidates, + TerminationCondition... conditions) { return evolvePopulation(populationSize, - eliteCount, - seedCandidates, - conditions).get(0).getCandidate(); + eliteCount, + seedCandidates, + conditions).get(0).getCandidate(); } - /** - * {@inheritDoc} - */ public List> evolvePopulation(int populationSize, - int eliteCount, - TerminationCondition... conditions) + int eliteCount, + TerminationCondition... conditions) { return evolvePopulation(populationSize, - eliteCount, - Collections.emptySet(), - conditions); + eliteCount, + Collections.emptySet(), + conditions); } - /** - * {@inheritDoc} - */ public List> evolvePopulation(int populationSize, - int eliteCount, - Collection seedCandidates, - TerminationCondition... conditions) + int eliteCount, + Collection seedCandidates, + TerminationCondition... conditions) { if (eliteCount < 0 || eliteCount >= populationSize) { - throw new IllegalArgumentException("Elite count must be non-negative and less than population size."); + 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."); + throw new IllegalArgumentException( + "At least one TerminationCondition must be specified."); } satisfiedTerminationConditions = null; @@ -133,31 +116,32 @@ public List> evolvePopulation(int populationSize, long startTime = System.currentTimeMillis(); List population = candidateFactory.generateInitialPopulation(populationSize, - seedCandidates, - rng); + seedCandidates, + rng); // Calculate the fitness scores for each member of the initial population. List> evaluatedPopulation = evaluatePopulation(population); EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural()); PopulationData data = EvolutionUtils.getPopulationData(evaluatedPopulation, - fitnessEvaluator.isNatural(), - eliteCount, - currentGenerationIndex, - startTime); + fitnessEvaluator.isNatural(), + eliteCount, + currentGenerationIndex, + startTime); // Notify observers of the state of the population. notifyPopulationChange(data); - List satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions); + List 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); + fitnessEvaluator.isNatural(), + eliteCount, + currentGenerationIndex, + startTime); // Notify observers of the state of the population. notifyPopulationChange(data); satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions); @@ -166,7 +150,7 @@ public List> evolvePopulation(int populationSize, return evaluatedPopulation; } - + /** * This method performs a single step/iteration of the evolutionary process. * @param evaluatedPopulation The population at the beginning of the process. @@ -175,9 +159,10 @@ public List> evolvePopulation(int populationSize, * @return The updated population after the evolutionary process has proceeded * by one step/iteration. */ - protected abstract List> nextEvolutionStep(List> evaluatedPopulation, - int eliteCount, - Random rng); + protected abstract List> nextEvolutionStep( + List> evaluatedPopulation, + int eliteCount, + Random rng); /** @@ -192,14 +177,15 @@ protected abstract List> nextEvolutionStep(List> evaluatePopulation(List population) { - List> evaluatedPopulation = new ArrayList>(population.size()); + List> evaluatedPopulation = + new ArrayList>(population.size()); if (singleThreaded) // Do fitness evaluations on the request thread. { - for (T candidate : population) + for (T candidate: population) { - evaluatedPopulation.add(new EvaluatedCandidate(candidate, - fitnessEvaluator.getFitness(candidate, population))); + evaluatedPopulation.add(new EvaluatedCandidate(candidate, fitnessEvaluator. + getFitness(candidate, population))); } } else @@ -210,15 +196,15 @@ protected List> evaluatePopulation(List population) try { List unmodifiablePopulation = Collections.unmodifiableList(population); - List>> results = new ArrayList>>(population.size()); + List>> results = + new ArrayList>>(population.size()); // Submit tasks for execution and wait until all threads have finished fitness evaluations. - for (T candidate : population) + for (T candidate: population) { - results.add(getSharedWorker().submit(new FitnessEvalutationTask(fitnessEvaluator, - candidate, - unmodifiablePopulation))); + results.add(getSharedWorker().submit(new FitnessEvalutationTask( + fitnessEvaluator, candidate, unmodifiablePopulation))); } - for (Future> result : results) + for (Future> result: results) { evaluatedPopulation.add(result.get()); } @@ -239,7 +225,6 @@ protected List> evaluatePopulation(List population) } - /** *

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 @@ -305,7 +290,7 @@ public void removeEvolutionObserver(EvolutionObserver observer) */ private void notifyPopulationChange(PopulationData data) { - for (EvolutionObserver observer : observers) + for (EvolutionObserver observer: observers) { observer.populationUpdate(data); } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java b/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java index ed3d8d2c..5ec72e49 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java @@ -20,39 +20,34 @@ import java.util.concurrent.ConcurrentMap; /** - *

A wrapper that provides caching for {@link FitnessEvaluator} implementations. The - * results of fitness evaluations are stored in a cache so that if the same candidate - * is evaluated twice, the expense of the fitness calculation can be avoided the second - * time. The cache uses weak references in order to avoid memory leakage.

- * - *

Caching of fitness values can be a useful optimisation in situations where the - * fitness evaluation is expensive and there is a possibility that some candidates - * will survive from generation to generation unmodified. Programs that use elitism - * are one example of candidates surviving unmodified. Another scenario is when the - * configured evolutionary operator does not always modify every candidate in the - * population for every generation.

- * - *

Unmodified candidates are identified by reference equality. This is a valid - * assumption since evolutionary operators are required to return distinct objects, - * except when the candidate is unaffected by the evolution, as per the contract of the - * {@link EvolutionaryOperator} interface. In other words, the Watchmaker Framework - * treats candidate representations as immutable even when that is not strictly the case.

- * - *

Caching of fitness scores is provided as an option rather than as the default - * Watchmaker Framework behaviour because caching is only valid when fitness evaluations - * are isolated and repeatable. An isolated fitness evaluation is one where the - * result depends only upon the candidate being evaluated. This is not the case when - * candidates are evaluated against the other members of the population. So unless the - * fitness evaluator ignores the second parameter to the + *

A wrapper that provides caching for {@link FitnessEvaluator} implementations. The results of + * fitness evaluations are stored in a cache so that if the same candidate is evaluated twice, the + * expense of the fitness calculation can be avoided the second time. The cache uses weak references + * in order to avoid memory leakage.

+ *

Caching of fitness values can be a useful optimisation in situations where the fitness + * evaluation is expensive and there is a possibility that some candidates will survive from + * generation to generation unmodified. Programs that use elitism are one example of candidates + * surviving unmodified. Another scenario is when the configured evolutionary operator does not + * always modify every candidate in the population for every generation.

+ *

Unmodified candidates are identified by reference equality. This is a valid assumption + * since evolutionary operators are required to return distinct objects, except when the candidate is + * unaffected by the evolution, as per the contract of the + * {@link EvolutionaryOperator} interface. In other words, the Watchmaker Framework treats candidate + * representations as immutable even when that is not strictly the case.

+ *

Caching of fitness scores is provided as an option rather than as the default Watchmaker + * Framework behaviour because caching is only valid when fitness evaluations are isolated + * and repeatable. An isolated fitness evaluation is one where the result depends only upon the + * candidate being evaluated. This is not the case when candidates are evaluated against the other + * members of the population. So unless the fitness evaluator ignores the second parameter to the * {@link #getFitness(Object, List)} method, caching must not be used.

+ *

* @param The type of evolvable entity that can be evaluated. - * + *

* @author Daniel Dyer */ public class CachingFitnessEvaluator implements FitnessEvaluator { private final FitnessEvaluator delegate; - // This field is marked as transient, even though the class is not Serializable, because // Terracotta will respect the fact it is transient and not try to share it. private final transient ConcurrentMap cache = new MapMaker().weakKeys().makeMap(); @@ -87,9 +82,6 @@ public double getFitness(T candidate, List population) } - /** - * {@inheritDoc} - */ public boolean isNatural() { return delegate.isNatural(); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java index fe3a165d..fe44ba2f 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java @@ -16,42 +16,37 @@ package org.uncommons.watchmaker.framework; /** - *

Call-back interface so that programs can monitor the state of a - * long-running evolutionary algorithm.

- *

Depending on the parameters of the evolutionary program, an observer may - * be invoked dozens or hundreds of times a second, especially when the population - * size is small as this leads to shorter generations. The processing performed by an - * evolution observer should be reasonably short-lived so as to avoid slowing down - * the evolution.

- *

Using an EvolutionObserver to update a Swing GUI: - * Evolution updates are dispatched on the request thread. To adhere to - * Swing threading rules you must use {@link javax.swing.SwingUtilities#invokeLater(Runnable)} + *

Call-back interface so that programs can monitor the state of a long-running evolutionary + * algorithm.

Depending on the parameters of the evolutionary program, an observer may be + * invoked dozens or hundreds of times a second, especially when the population size is small as this + * leads to shorter generations. The processing performed by an evolution observer should be + * reasonably short-lived so as to avoid slowing down the evolution.

Using an + * EvolutionObserver to update a Swing GUI: Evolution updates are dispatched on the request + * thread. To adhere to Swing threading rules you must use {@link javax.swing.SwingUtilities#invokeLater(Runnable)} * or {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} to perform any updates to Swing - * components.

- *

Be aware that if there are too many Swing updates queued for asynchronous - * execution with {@link javax.swing.SwingUtilities#invokeLater(Runnable)}, due to a high - * number of generations per second, then the GUI will become sluggish and - * unresponsive. - * This situation can be mitigated by minimising the amount of work done by - * the evolution observer and/or by not updating the GUI every time the observer is - * notified.

- *

The unresponsive GUI problem does not occur when using - * {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} because updates are - * executed synchronously. The downside is that evolution threads are stalled/idle until - * Swing has finished performing the updates. This won't make much difference on a single - * core machine but will impact throughput on multi-core machines.

- * @param The type of entity that exists in the evolving population - * that is being observed. This type can be bound to a super-type of the - * actual population type so as to allow a non-specific observer that can - * be re-used for different population types. + * components.

Be aware that if there are too many Swing updates queued for asynchronous + * execution with {@link javax.swing.SwingUtilities#invokeLater(Runnable)}, due to a high number of + * generations per second, then the GUI will become sluggish and unresponsive. This situation can be + * mitigated by minimising the amount of work done by the evolution observer and/or by not updating + * the GUI every time the observer is notified.

The unresponsive GUI problem does not occur + * when using + * {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} because updates are executed + * synchronously. The downside is that evolution threads are stalled/idle until Swing has finished + * performing the updates. This won't make much difference on a single core machine but will impact + * throughput on multi-core machines.

+ *

+ * @param The type of entity that exists in the evolving population that is being observed. This + * type can be bound to a super-type of the actual population type so as to allow a non-specific + * observer that can be re-used for different population types. * @author Daniel Dyer */ public interface EvolutionObserver { /** - * Invoked when the state of the population has changed (typically - * at the end of a generation). + * Invoked when the state of the population has changed (typically at the end of a generation). + *

+ * @param The type of evolved entity present in the population that this data describes. * @param data Statistics about the state of the current generation. */ - void populationUpdate(PopulationData data); + void populationUpdate(PopulationData data); } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java index c639b2c1..641072be 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java @@ -20,13 +20,12 @@ import java.util.Random; /** - *

General purpose engine for implementing Evolution Strategies. Both (μ+λ) and (μ,λ) - * strategies are supported (choose which to use by setting the boolean constructor parameter).

- * - *

Though this implementation accepts the {@code eliteCount} argument for each of its evolve + *

General purpose engine for implementing Evolution Strategies. Both (μ+λ) and (μ,λ) strategies + * are supported (choose which to use by setting the boolean constructor parameter).

+ *

Though this implementation accepts the {@code eliteCount} argument for each of its evolve * methods in common with other {@link EvolutionEngine} implementations, it has no effect for - * evolution strategies. Elitism is implicit in a (μ+λ) ES and undesirable for a (μ,λ) ES.

- + * evolution strategies. Elitism is implicit in a (μ+λ) ES and undesirable for a (μ,λ) ES.

+ *

* @param The type of entity that is to be evolved. * @see GenerationalEvolutionEngine * @see SteadyStateEvolutionEngine @@ -83,21 +82,23 @@ public EvolutionStrategyEngine(CandidateFactory candidateFactory, * @param rng A source of randomness. * @return The updated population after the evolution strategy has advanced. */ - @Override - protected List> nextEvolutionStep(List> evaluatedPopulation, - int eliteCount, - Random rng) + protected List> nextEvolutionStep( + List> evaluatedPopulation, + int eliteCount, + Random rng) { // Elite count is ignored. If it's non-zero it doesn't really matter, but if assertions are // enabled we will flag it as wrong. - assert eliteCount == 0 : "Explicit elitism is not supported for an ES, eliteCount should be 0."; - + assert eliteCount == 0: + "Explicit elitism is not supported for an ES, eliteCount should be 0."; + // Select candidates that will be operated on to create the offspring. int offspringCount = offspringMultiplier * evaluatedPopulation.size(); List parents = new ArrayList(offspringCount); for (int i = 0; i < offspringCount; i++) { - parents.add(evaluatedPopulation.get(rng.nextInt(evaluatedPopulation.size())).getCandidate()); + parents.add(evaluatedPopulation.get(rng.nextInt(evaluatedPopulation.size())). + getCandidate()); } // Then evolve the parents. diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java index 2838e318..8ecd517e 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java @@ -21,8 +21,9 @@ import org.uncommons.maths.statistics.DataSet; /** - * Utility methods used by different evolution implementations. This class exists to - * avoid duplication of this logic among multiple evolution implementations. + * Utility methods used by different evolution implementations. This class exists to avoid + * duplication of this logic among multiple evolution implementations. + *

* @author Daniel Dyer */ public final class EvolutionUtils @@ -55,7 +56,7 @@ public static List shouldContinue(PopulationData da } // Otherwise check the termination conditions for the evolution. List satisfiedConditions = new LinkedList(); - for (TerminationCondition condition : conditions) + for (TerminationCondition condition: conditions) { if (condition.shouldTerminate(data)) { @@ -90,7 +91,6 @@ public static void sortEvaluatedPopulation(List> evalu } - /** * Gets data about the current population, including the fittest candidate * and statistics about the population as a whole. @@ -100,24 +100,23 @@ public static void sortEvaluatedPopulation(List> evalu * @param naturalFitness True if higher fitness scores mean fitter individuals, false otherwise. * @param eliteCount The number of candidates preserved via elitism. * @param iterationNumber The zero-based index of the current generation/epoch. - * @param startTime The time at which the evolution began, expressed as a number of milliseconds since - * 00:00 on 1st January 1970. + * @param startTime The time at which the evolution began, expressed as a number of milliseconds since 00:00 on 1st January 1970. * @param The type of entity that is being evolved. * @return Statistics about the current generation of evolved individuals. */ - public static PopulationData getPopulationData(List> evaluatedPopulation, - boolean naturalFitness, - int eliteCount, - int iterationNumber, - long startTime) + public static PopulationData getPopulationData( + List> evaluatedPopulation, + boolean naturalFitness, + int eliteCount, + int iterationNumber, + long startTime) { DataSet stats = new DataSet(evaluatedPopulation.size()); - for (EvaluatedCandidate candidate : evaluatedPopulation) + for (EvaluatedCandidate candidate: evaluatedPopulation) { stats.addValue(candidate.getFitness()); } - return new PopulationData(evaluatedPopulation.get(0).getCandidate(), - evaluatedPopulation.get(0).getFitness(), + return new PopulationData(evaluatedPopulation, stats.getArithmeticMean(), stats.getStandardDeviation(), naturalFitness, diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java index e733f060..7fe84da9 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java @@ -52,7 +52,7 @@ public interface EvolutionaryOperator * deal with multiple candidates in a single operation.

*

The operator must not modify any of the candidates passed * in. Instead it should return a list that contains evolved - * copies of those candidates (umodified candidates can be included in + * copies of those candidates (unmodified candidates can be included in * the results without having to be copied).

* @param selectedCandidates The individuals to evolve. * @param rng A source of randomness for stochastic operators (most diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java b/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java index 3e43915f..63a41f65 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java @@ -22,21 +22,18 @@ import org.uncommons.watchmaker.framework.interactive.InteractiveSelection; /** - *

This class implements a general-purpose generational evolutionary algorithm. - * It supports optional concurrent fitness evaluations to take full advantage of - * multi-processor, multi-core and hyper-threaded machines.

- * - *

If multi-threading is enabled, evolution (mutation, cross-over, etc.) occurs - * on the request thread but fitness evaluations are delegated to a pool of worker - * threads. All of the host's available processing units are used (i.e. on a quad-core - * machine there will be four fitness evaluation worker threads).

- * - *

If multi-threading is disabled, all work is performed synchronously on the - * request thread. This strategy is suitable for restricted/managed environments where - * it is not permitted for applications to manage their own threads. If there are no - * restrictions on concurrency, applications should enable multi-threading for improved - * performance.

- * + *

This class implements a general-purpose generational evolutionary algorithm. It supports + * optional concurrent fitness evaluations to take full advantage of multi-processor, multi-core and + * hyper-threaded machines.

+ *

If multi-threading is enabled, evolution (mutation, cross-over, etc.) occurs on the + * request thread but fitness evaluations are delegated to a pool of worker threads. All of the + * host's available processing units are used (i.e. on a quad-core machine there will be four fitness + * evaluation worker threads).

+ *

If multi-threading is disabled, all work is performed synchronously on the request + * thread. This strategy is suitable for restricted/managed environments where it is not permitted + * for applications to manage their own threads. If there are no restrictions on concurrency, + * applications should enable multi-threading for improved performance.

+ *

* @param The type of entity that is to be evolved. * @see SteadyStateEvolutionEngine * @see EvolutionStrategyEngine @@ -48,6 +45,7 @@ public class GenerationalEvolutionEngine extends AbstractEvolutionEngine private final FitnessEvaluator fitnessEvaluator; private final SelectionStrategy selectionStrategy; + /** * Creates a new evolution engine by specifying the various components required by * a generational evolutionary algorithm. @@ -100,13 +98,10 @@ public GenerationalEvolutionEngine(CandidateFactory candidateFactory, } - /** - * {@inheritDoc} - */ - @Override - protected List> nextEvolutionStep(List> evaluatedPopulation, - int eliteCount, - Random rng) + protected List> nextEvolutionStep( + List> evaluatedPopulation, + int eliteCount, + Random rng) { List population = new ArrayList(evaluatedPopulation.size()); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java b/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java index 0fe0d3ae..23046cc9 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java @@ -18,14 +18,16 @@ import java.util.List; /** - * Fitness evaluation is not required for interactive selection, so this stub - * implementation is used to satisfy the framework requirements. + * Fitness evaluation is not required for interactive selection, so this stub implementation is used + * to satisfy the framework requirements. + *

* @author Daniel Dyer */ class NullFitnessEvaluator implements FitnessEvaluator { /** * Returns a score of zero, regardless of the candidate being evaluated. + *

* @param candidate The individual to evaluate. * @param population {@inheritDoc} * @return Zero. @@ -36,6 +38,7 @@ public double getFitness(Object candidate, return 0; } + /** * Always returns true. However, the return value of this method is * irrelevant since no meaningful fitness scores are produced. diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java b/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java index a04f5bbb..8cb5c168 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java @@ -15,17 +15,21 @@ //============================================================================= package org.uncommons.watchmaker.framework; +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.List; + /** - * Immutable data object containing statistics about the state of - * an evolved population and a reference to the fittest candidate - * solution in the population. - * @param The type of evolved entity present in the population - * that this data describes. + * Immutable data object containing statistics about the state of an evolved population and a + * reference to the fittest candidate solution in the population. + *

+ * @param The type of evolved entity present in the population that this data describes. * @see EvolutionObserver * @author Daniel Dyer */ public final class PopulationData { + private final List> evaluatedPopulation; private final T bestCandidate; private final double bestCandidateFitness; private final double meanFitness; @@ -36,8 +40,9 @@ public final class PopulationData private final int generationNumber; private final long elapsedTime; + /** - * @param bestCandidate The fittest candidate present in the population. + * @param population The evaluated populated sorted in descending order of fitness. * @param bestCandidateFitness The fitness score for the fittest candidate * in the population. * @param meanFitness The arithmetic mean of fitness scores for each member @@ -51,9 +56,10 @@ public final class PopulationData * @param generationNumber The (zero-based) number of the last generation * that was processed. * @param elapsedTime The number of milliseconds since the start of the + * evolutionary algorithm's execution. + * @param attachment The object attached to this population by the user. */ - public PopulationData(T bestCandidate, - double bestCandidateFitness, + public PopulationData(List> population, double meanFitness, double fitnessStandardDeviation, boolean naturalFitness, @@ -62,8 +68,10 @@ public PopulationData(T bestCandidate, int generationNumber, long elapsedTime) { - this.bestCandidate = bestCandidate; - this.bestCandidateFitness = bestCandidateFitness; + this.evaluatedPopulation = ImmutableList.copyOf(population); + EvaluatedCandidate best = population.get(0); + this.bestCandidate = best.getCandidate(); + this.bestCandidateFitness = best.getFitness(); this.meanFitness = meanFitness; this.fitnessStandardDeviation = fitnessStandardDeviation; this.naturalFitness = naturalFitness; @@ -74,6 +82,15 @@ public PopulationData(T bestCandidate, } + /** + * @return The evaluated populated sorted in descending order of fitness. + */ + public List> getPopulation() + { + return Collections.unmodifiableList(evaluatedPopulation); + } + + /** * @return The fittest candidate present in the population. * @see #getBestCandidateFitness() @@ -125,7 +142,7 @@ public boolean isNaturalFitness() return naturalFitness; } - + /** * @return The number of individuals in the current population. */ diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java b/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java index 3321db8c..3098bbc8 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java @@ -19,11 +19,10 @@ import java.util.Random; /** - * An implementation of steady-state evolution, which is a type of evolutionary algorithm - * where a population is changed incrementally, with one individual evolved at a time. This - * differs from {@link GenerationalEvolutionEngine} in which the entire population is evolved in - * parallel. - * + * An implementation of steady-state evolution, which is a type of evolutionary algorithm where a + * population is changed incrementally, with one individual evolved at a time. This differs from {@link GenerationalEvolutionEngine} + * in which the entire population is evolved in parallel. + *

* @param The type of entity that is to be evolved. * @see GenerationalEvolutionEngine * @see EvolutionStrategyEngine @@ -37,6 +36,7 @@ public class SteadyStateEvolutionEngine extends AbstractEvolutionEngine private final int selectionSize; private final boolean forceSingleCandidateUpdate; + /** * Create a steady-state evolution strategy in which one or more (usually just one) evolved * offspring replace randomly-chosen individuals. @@ -86,21 +86,19 @@ public SteadyStateEvolutionEngine(CandidateFactory candidateFactory, this.forceSingleCandidateUpdate = forceSingleCandidateUpdate; } - - /** - * {@inheritDoc} - */ - @Override - protected List> nextEvolutionStep(List> evaluatedPopulation, - int eliteCount, - Random rng) + + protected List> nextEvolutionStep( + List> evaluatedPopulation, + int eliteCount, + Random rng) { EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural()); List selectedCandidates = selectionStrategy.select(evaluatedPopulation, fitnessEvaluator.isNatural(), selectionSize, rng); - List> offspring = evaluatePopulation(evolutionScheme.apply(selectedCandidates, rng)); + List> offspring = evaluatePopulation(evolutionScheme.apply( + selectedCandidates, rng)); doReplacement(evaluatedPopulation, offspring, eliteCount, rng); @@ -124,7 +122,8 @@ protected void doReplacement(List> existingPopulation, int eliteCount, Random rng) { - assert newCandidates.size() < existingPopulation.size() - eliteCount : "Too many new candidates for replacement."; + assert newCandidates.size() < existingPopulation.size() - eliteCount: + "Too many new candidates for replacement."; // If this is strictly steady-state (only one updated individual per iteration), then we can't keep multiple // evolved individuals, so just pick one at random and use that. if (newCandidates.size() > 1 && forceSingleCandidateUpdate) @@ -136,11 +135,12 @@ protected void doReplacement(List> existingPopulation, } else { - for (EvaluatedCandidate candidate : newCandidates) + for (EvaluatedCandidate candidate: newCandidates) { // Replace a randomly selected individual, but not one of the "elite" individuals at the // beginning of the sorted population. - existingPopulation.set(rng.nextInt(existingPopulation.size() - eliteCount) + eliteCount, candidate); + existingPopulation.set(rng.nextInt(existingPopulation.size() - eliteCount) + + eliteCount, candidate); } } } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java b/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java index 5a03985a..ed369e98 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java @@ -15,29 +15,25 @@ //============================================================================= package org.uncommons.watchmaker.framework.factories; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Random; +import java.util.*; import org.uncommons.watchmaker.framework.CandidateFactory; /** * Convenient base class for implementations of * {@link org.uncommons.watchmaker.framework.CandidateFactory}. + *

* @param The type of entity evolved by this engine. * @author Daniel Dyer */ public abstract class AbstractCandidateFactory implements CandidateFactory { /** - * Randomly, create an initial population of candidates. If some - * control is required over the composition of the initial population, - * consider the overloaded {@link #generateInitialPopulation(int,Collection,Random)} + * Randomly, create an initial population of candidates. If some control is required over the + * composition of the initial population, consider the overloaded {@link #generateInitialPopulation(int,Collection,Random)} * method. + *

* @param populationSize The number of candidates to randomly create. - * @param rng The random number generator to use when creating the random - * candidates. + * @param rng The random number generator to use when creating the random candidates. * @return A randomly generated initial population of candidate solutions. */ public List generateInitialPopulation(int populationSize, Random rng) @@ -63,7 +59,8 @@ public List generateInitialPopulation(int populationSize, { if (seedCandidates.size() > populationSize) { - throw new IllegalArgumentException("Too many seed candidates for specified population size."); + throw new IllegalArgumentException( + "Too many seed candidates for specified population size."); } List population = new ArrayList(populationSize); population.addAll(seedCandidates); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java b/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java index 36a7883f..ad7bd574 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java @@ -26,6 +26,7 @@ /** * Special selection strategy used for interactive evolutionary algorithms. + *

* @param The type of evolved entity that can be selected by this class. * @author Daniel Dyer */ @@ -75,7 +76,7 @@ public InteractiveSelection(Console console, this.maxSelectionsPerGeneration = maxSelectionsPerGeneration; } - + /** * @param console The user interface (graphical, textual or other) used * to present a selection choice to the user. @@ -98,9 +99,6 @@ public InteractiveSelection(Console console, } - /** - * {@inheritDoc} - */ public List select(List> population, boolean naturalFitnessScores, int selectionSize, @@ -108,7 +106,8 @@ public List select(List> population, { if (population.size() < groupSize) { - throw new IllegalArgumentException("Population is too small for selection group size of " + groupSize); + throw new IllegalArgumentException("Population is too small for selection group size of " + + groupSize); } int selectionCount = Math.min(selectionSize, maxSelectionsPerGeneration); @@ -117,7 +116,8 @@ public List select(List> population, { // Pick candidates at random (without replacement). List group = new ArrayList(groupSize); - List> candidates = new ArrayList>(population); + List> candidates = + new ArrayList>(population); Collections.shuffle(candidates); for (int j = 0; j < groupSize; j++) { @@ -135,7 +135,8 @@ public List select(List> population, extendedSelection.addAll(selection); for (int i = 0; i < selectionSize - selectionCount; i++) { - extendedSelection.add(selection.get(selectionCount == 1 ? 0 : rng.nextInt(selectionCount))); + extendedSelection.add(selection.get(selectionCount == 1 ? 0 : rng.nextInt( + selectionCount))); } return extendedSelection; } @@ -149,7 +150,7 @@ public List select(List> population, private S select(List candidates) { List renderedCandidates = new ArrayList(candidates.size()); - for (S candidate : candidates) + for (S candidate: candidates) { renderedCandidates.add(renderer.render(candidate)); } @@ -162,7 +163,6 @@ private S select(List candidates) return candidates.get(selection); } - /** * Renderer that does nothing. Used when the console already supports the * evolved type. diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java b/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java index b8f669a6..2762b806 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java @@ -19,13 +19,12 @@ import org.uncommons.util.reflection.ReflectionUtils; /** - * Adapter class for chaining together two renderers in series to provide - * flexibility. For example, if we have a Long -> Date renderer that turns - * a number of milliseconds since epoch into a Java date, and a Date -> String - * renderer that converts a Java date into its String representation in a - * particular locale, we can combine the two to create a Long -> String renderer - * without having to write a separate implementation of the {@link Renderer} - * interface. + * Adapter class for chaining together two renderers in series to provide flexibility. For example, + * if we have a Long -> Date renderer that turns a number of milliseconds since epoch into a Java + * date, and a Date -> String renderer that converts a Java date into its String representation in a + * particular locale, we can combine the two to create a Long -> String renderer without having to + * write a separate implementation of the {@link Renderer} interface. + *

* @param The input type for the renderer. * @param The output type for the renderer. * @author Daniel Dyer @@ -53,9 +52,6 @@ public RendererAdapter(Renderer renderer1, } - /** - * {@inheritDoc} - */ public S render(T entity) { // This reflection charade is necessary because we can't convince the diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java b/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java index 300dce95..03c55a01 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java @@ -15,33 +15,15 @@ //============================================================================= package org.uncommons.watchmaker.framework.islands; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.uncommons.watchmaker.framework.CandidateFactory; -import org.uncommons.watchmaker.framework.EvaluatedCandidate; -import org.uncommons.watchmaker.framework.EvolutionEngine; -import org.uncommons.watchmaker.framework.EvolutionObserver; -import org.uncommons.watchmaker.framework.EvolutionUtils; -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.SelectionStrategy; -import org.uncommons.watchmaker.framework.TerminationCondition; +import java.util.*; +import java.util.concurrent.*; +import org.uncommons.watchmaker.framework.*; import org.uncommons.watchmaker.framework.termination.GenerationCount; /** * An implementation of island evolution in which multiple independent populations are evolved in * parallel with periodic migration of individuals between islands. + *

* @param The type of entity that is to be evolved. * @author Daniel Dyer */ @@ -51,10 +33,8 @@ public class IslandEvolution private final Migration migration; private final boolean naturalFitness; private final Random rng; - - private final Set> observers - = new CopyOnWriteArraySet>(); - + private final Set> observers = + new CopyOnWriteArraySet>(); private List satisfiedTerminationConditions; @@ -75,22 +55,22 @@ public class IslandEvolution * @see #IslandEvolution(List, Migration, boolean, Random) */ public IslandEvolution(int islandCount, - Migration migration, - CandidateFactory candidateFactory, - EvolutionaryOperator evolutionScheme, - FitnessEvaluator fitnessEvaluator, - SelectionStrategy selectionStrategy, - Random rng) + Migration migration, + CandidateFactory candidateFactory, + EvolutionaryOperator evolutionScheme, + FitnessEvaluator fitnessEvaluator, + SelectionStrategy selectionStrategy, + Random rng) { this(createIslands(islandCount, - candidateFactory, - evolutionScheme, - fitnessEvaluator, - selectionStrategy, - rng), - migration, - fitnessEvaluator.isNatural(), - rng); + candidateFactory, + evolutionScheme, + fitnessEvaluator, + selectionStrategy, + rng), + migration, + fitnessEvaluator.isNatural(), + rng); } @@ -109,9 +89,9 @@ public IslandEvolution(int islandCount, * SelectionStrategy, Random) */ public IslandEvolution(List> islands, - Migration migration, - boolean naturalFitness, - Random rng) + Migration migration, + boolean naturalFitness, + Random rng) { this.islands = islands; this.migration = migration; @@ -124,9 +104,9 @@ public IslandEvolution(List> islands, EvolutionEngine island = islands.get(islandIndex); island.addEvolutionObserver(new EvolutionObserver() { - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { - for (IslandEvolutionObserver islandObserver : observers) + for (IslandEvolutionObserver islandObserver: observers) { islandObserver.islandPopulationUpdate(islandIndex, populationData); } @@ -141,20 +121,21 @@ public void populationUpdate(PopulationData populationData) * been provided already (via the other constructor). */ private static List> createIslands(int islandCount, - CandidateFactory candidateFactory, - EvolutionaryOperator evolutionScheme, - FitnessEvaluator fitnessEvaluator, - SelectionStrategy selectionStrategy, - Random rng) + CandidateFactory candidateFactory, + EvolutionaryOperator evolutionScheme, + FitnessEvaluator fitnessEvaluator, + SelectionStrategy selectionStrategy, + Random rng) { List> islands = new ArrayList>(islandCount); for (int i = 0; i < islandCount; i++) { - GenerationalEvolutionEngine island = new GenerationalEvolutionEngine(candidateFactory, - evolutionScheme, - fitnessEvaluator, - selectionStrategy, - rng); + GenerationalEvolutionEngine island = new GenerationalEvolutionEngine( + candidateFactory, + evolutionScheme, + fitnessEvaluator, + selectionStrategy, + rng); island.setSingleThreaded(true); // Don't need fine-grained concurrency when each island is on a separate thread. islands.add(island); } @@ -189,14 +170,15 @@ private static List> createIslands(int islandCount, * @return The fittest solution found by the evolutionary process on any of the islands. */ public T evolve(int populationSize, - int eliteCount, - int epochLength, - int migrantCount, - TerminationCondition... conditions) + int eliteCount, + int epochLength, + int migrantCount, + TerminationCondition... conditions) { ExecutorService threadPool = Executors.newFixedThreadPool(islands.size()); List> islandPopulations = new ArrayList>(islands.size()); - List> evaluatedCombinedPopulation = new ArrayList>(); + List> evaluatedCombinedPopulation = + new ArrayList>(); PopulationData data = null; List satisfiedConditions = null; @@ -204,18 +186,20 @@ public T evolve(int populationSize, long startTime = System.currentTimeMillis(); while (satisfiedConditions == null) { - List>>> islandEpochs = createEpochTasks(populationSize, - eliteCount, - epochLength, - islandPopulations); + List>>> islandEpochs = + createEpochTasks(populationSize, + eliteCount, + epochLength, + islandPopulations); try { - List>>> futures = threadPool.invokeAll(islandEpochs); + List>>> futures = threadPool.invokeAll( + islandEpochs); evaluatedCombinedPopulation.clear(); - List>> evaluatedPopulations - = new ArrayList>>(islands.size()); - for (Future>> future : futures) + List>> evaluatedPopulations = + new ArrayList>>(islands.size()); + for (Future>> future: futures) { List> evaluatedIslandPopulation = future.get(); evaluatedCombinedPopulation.addAll(evaluatedIslandPopulation); @@ -226,14 +210,14 @@ public T evolve(int populationSize, EvolutionUtils.sortEvaluatedPopulation(evaluatedCombinedPopulation, naturalFitness); data = EvolutionUtils.getPopulationData(evaluatedCombinedPopulation, - naturalFitness, - eliteCount, - currentEpochIndex, - startTime); + naturalFitness, + eliteCount, + currentEpochIndex, + startTime); notifyPopulationChange(data); islandPopulations.clear(); - for (List> evaluatedPopulation : evaluatedPopulations) + for (List> evaluatedPopulation: evaluatedPopulations) { islandPopulations.add(toCandidateList(evaluatedPopulation)); } @@ -260,19 +244,20 @@ public T evolve(int populationSize, * Create the concurrently-executed tasks that perform evolution on each island. */ private List>>> createEpochTasks(int populationSize, - int eliteCount, - int epochLength, - List> islandPopulations) + int eliteCount, + int epochLength, + List> islandPopulations) { - List>>> islandEpochs - = new ArrayList>>>(islands.size()); + List>>> islandEpochs = + new ArrayList>>>(islands.size()); for (int i = 0; i < islands.size(); i++) { islandEpochs.add(new Epoch(islands.get(i), - populationSize, - eliteCount, - islandPopulations.isEmpty() ? Collections.emptyList() : islandPopulations.get(i), - new GenerationCount(epochLength))); + populationSize, + eliteCount, + islandPopulations.isEmpty() ? Collections.emptyList() + : islandPopulations.get(i), + new GenerationCount(epochLength))); } return islandEpochs; } @@ -288,7 +273,7 @@ private List>>> createEpochTasks(int populat private static List toCandidateList(List> evaluatedCandidates) { List candidates = new ArrayList(evaluatedCandidates.size()); - for (EvaluatedCandidate evaluatedCandidate : evaluatedCandidates) + for (EvaluatedCandidate evaluatedCandidate: evaluatedCandidates) { candidates.add(evaluatedCandidate.getCandidate()); } @@ -367,7 +352,7 @@ public void removeEvolutionObserver(final IslandEvolutionObserver obs */ private void notifyPopulationChange(PopulationData data) { - for (IslandEvolutionObserver observer : observers) + for (IslandEvolutionObserver observer: observers) { observer.populationUpdate(data); } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java b/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java index 7613ff2f..b4890ee4 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java @@ -30,9 +30,10 @@ public interface IslandEvolutionObserver extends EvolutionObserver /** * Method called to notify the listener of the state of the population of an individual * island. This will be called once for each generation on each island. + * @param The type of evolved entity present in the population that this data describes. * @param islandIndex Identifies which individual island the data comes from. * Indices start at zero and are sequential. * @param data The latest data from the evolution on the specified island. */ - void islandPopulationUpdate(int islandIndex, PopulationData data); + void islandPopulationUpdate(int islandIndex, PopulationData data); } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java index 4f9bdd3f..609ae2e4 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java @@ -23,8 +23,8 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * bit strings. + * Cross-over with a configurable number of points (fixed or random) for bit strings. + *

* @see BitString * @author Daniel Dyer */ @@ -96,9 +96,6 @@ public BitStringCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(BitString parent1, BitString parent2, @@ -107,7 +104,8 @@ protected List mate(BitString parent1, { if (parent1.getLength() != parent2.getLength()) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException("Cannot perform cross-over with different " + + "length parents."); } BitString offspring1 = parent1.clone(); BitString offspring2 = parent2.clone(); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java index c80647b7..d71240d9 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java @@ -22,8 +22,8 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * arrays of primitive bytes. + * Cross-over with a configurable number of points (fixed or random) for arrays of primitive bytes. + *

* @author Daniel Dyer */ public class ByteArrayCrossover extends AbstractCrossover @@ -94,9 +94,6 @@ public ByteArrayCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(byte[] parent1, byte[] parent2, @@ -105,7 +102,8 @@ protected List mate(byte[] parent1, { if (parent1.length != parent2.length) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException("Cannot perform cross-over with different " + + "length parents."); } byte[] offspring1 = new byte[parent1.length]; System.arraycopy(parent1, 0, offspring1, 0, parent1.length); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java index 8e7dc661..f3f9c32d 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java @@ -22,8 +22,8 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * arrays of primitive chars. + * Cross-over with a configurable number of points (fixed or random) for arrays of primitive chars. + *

* @author Daniel Dyer */ public class CharArrayCrossover extends AbstractCrossover @@ -94,9 +94,6 @@ public CharArrayCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(char[] parent1, char[] parent2, @@ -105,7 +102,8 @@ protected List mate(char[] parent1, { if (parent1.length != parent2.length) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } char[] offspring1 = new char[parent1.length]; System.arraycopy(parent1, 0, offspring1, 0, parent1.length); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java index 459e6a79..73ec556a 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java @@ -22,8 +22,9 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * arrays of primitive doubles. + * Cross-over with a configurable number of points (fixed or random) for arrays of primitive + * doubles. + *

* @author Daniel Dyer */ public class DoubleArrayCrossover extends AbstractCrossover @@ -94,9 +95,6 @@ public DoubleArrayCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(double[] parent1, double[] parent2, @@ -105,7 +103,8 @@ protected List mate(double[] parent1, { if (parent1.length != parent2.length) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } double[] offspring1 = new double[parent1.length]; System.arraycopy(parent1, 0, offspring1, 0, parent1.length); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java index f416ecd8..9dcf15bd 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java @@ -22,8 +22,8 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * arrays of primitive ints. + * Cross-over with a configurable number of points (fixed or random) for arrays of primitive ints. + *

* @author Daniel Dyer */ public class IntArrayCrossover extends AbstractCrossover @@ -94,9 +94,6 @@ public IntArrayCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(int[] parent1, int[] parent2, @@ -105,7 +102,8 @@ protected List mate(int[] parent1, { if (parent1.length != parent2.length) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } int[] offspring1 = new int[parent1.length]; System.arraycopy(parent1, 0, offspring1, 0, parent1.length); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java index 698200df..a12182f0 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java @@ -23,6 +23,7 @@ /** * Variable-point (fixed or random) cross-over for arbitrary lists. + *

* @param The component type of the lists that are combined. * @author Daniel Dyer */ @@ -92,9 +93,6 @@ public ListCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List> mate(List parent1, List parent2, diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java index 5748e791..50c1f717 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java @@ -15,19 +15,16 @@ //============================================================================= package org.uncommons.watchmaker.framework.operators; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.*; import org.uncommons.maths.number.ConstantGenerator; import org.uncommons.maths.number.NumberGenerator; import org.uncommons.maths.random.Probability; /** - * Implements ordered cross-over between arbitrary lists. The algorithm is - * the Partially Mapped Cross-over (PMX) algorithm. - * @param The component type of the lists that are combined. + * Implements ordered cross-over between arbitrary lists. The algorithm is the Partially Mapped + * Cross-over (PMX) algorithm. + *

+ * @param The component type of the lists that are combined. * @author Daniel Dyer */ public class ListOrderCrossover extends AbstractCrossover> @@ -67,21 +64,18 @@ public ListOrderCrossover(NumberGenerator crossoverProbabilityVaria } - - /** - * {@inheritDoc} - */ @Override protected List> mate(List parent1, List parent2, int numberOfCrossoverPoints, Random rng) { - assert numberOfCrossoverPoints == 2 : "Expected number of cross-over points to be 2."; + assert numberOfCrossoverPoints == 2: "Expected number of cross-over points to be 2."; if (parent1.size() != parent2.size()) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } List offspring1 = new ArrayList(parent1); // Use a random-access list for performance. @@ -158,7 +152,8 @@ private boolean isInsideMappedRegion(int position, int endPoint) { boolean enclosed = (position < endPoint && position >= startPoint); - boolean wrapAround = (startPoint > endPoint && (position >= startPoint || position < endPoint)); + boolean wrapAround = (startPoint > endPoint && (position >= startPoint || position + < endPoint)); return enclosed || wrapAround; } } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java index 176bfaa1..6dd97934 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java @@ -23,8 +23,8 @@ import org.uncommons.maths.random.Probability; /** - * Cross-over with a configurable number of points (fixed or random) for - * arrays of reference types. + * Cross-over with a configurable number of points (fixed or random) for arrays of reference types. + *

* @param The component type of the arrays that are being evolved. * @author Daniel Dyer */ @@ -90,31 +90,31 @@ public ObjectArrayCrossover(NumberGenerator crossoverPointsVariable) * than being copied, unchanged, into the output population. */ public ObjectArrayCrossover(NumberGenerator crossoverPointsVariable, - NumberGenerator crossoverProbabilityVariable) + NumberGenerator crossoverProbabilityVariable) { super(crossoverPointsVariable, crossoverProbabilityVariable); } - /** - * {@inheritDoc} - */ @Override protected List mate(T[] parent1, - T[] parent2, - int numberOfCrossoverPoints, - Random rng) + T[] parent2, + int numberOfCrossoverPoints, + Random rng) { if (parent1.length != parent2.length) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } // Create the most specific-type arrays possible. @SuppressWarnings("unchecked") - T[] offspring1 = (T[]) Array.newInstance(parent1.getClass().getComponentType(), parent1.length); + T[] offspring1 = (T[]) Array.newInstance(parent1.getClass().getComponentType(), + parent1.length); System.arraycopy(parent1, 0, offspring1, 0, parent1.length); @SuppressWarnings("unchecked") - T[] offspring2 = (T[]) Array.newInstance(parent2.getClass().getComponentType(), parent2.length); + T[] offspring2 = (T[]) Array.newInstance(parent2.getClass().getComponentType(), + parent2.length); System.arraycopy(parent2, 0, offspring2, 0, parent2.length); // Apply as many cross-overs as required. Object[] temp = new Object[parent1.length]; diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java index e73119ad..938a60fe 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java @@ -22,9 +22,10 @@ import org.uncommons.maths.random.Probability; /** - * Variable-point (fixed or random) cross-over for String candidates. - * This implementation assumes that all candidate Strings are the same - * length. If they are not, an exception will be thrown at runtime. + * Variable-point (fixed or random) cross-over for String candidates. This implementation assumes + * that all candidate Strings are the same length. If they are not, an exception will be thrown at + * runtime. + *

* @author Daniel Dyer */ public class StringCrossover extends AbstractCrossover @@ -95,9 +96,6 @@ public StringCrossover(NumberGenerator crossoverPointsVariable, } - /** - * {@inheritDoc} - */ @Override protected List mate(String parent1, String parent2, @@ -106,7 +104,8 @@ protected List mate(String parent1, { if (parent1.length() != parent2.length()) { - throw new IllegalArgumentException("Cannot perform cross-over with different length parents."); + throw new IllegalArgumentException( + "Cannot perform cross-over with different length parents."); } StringBuilder offspring1 = new StringBuilder(parent1); StringBuilder offspring2 = new StringBuilder(parent2); diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java index 1b12b54c..c59211ab 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java @@ -23,21 +23,22 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - *

A selection strategy that is similar to fitness-proportionate selection - * except that is uses relative fitness rather than absolute fitness in order to - * determine the probability of selection for a given individual (i.e. the actual - * numerical fitness values are ignored and only the ordering of the sorted - * population is considered).

- *

Rank selection is implemented in terms of a mapping function ({@link - * #mapRankToScore(int, int)}) and delegation to a fitness-proportionate selector. The - * mapping function converts ranks into relative fitness scores that are used to - * drive the delegate selector.

+ *

A selection strategy that is similar to fitness-proportionate selection except that is uses + * relative fitness rather than absolute fitness in order to determine the probability of selection + * for a given individual (i.e. the actual numerical fitness values are ignored and only the ordering + * of the sorted population is considered).

Rank selection is implemented in terms of a + * mapping function ({@link + * #mapRankToScore(int, int)}) and delegation to a fitness-proportionate selector. The mapping + * function converts ranks into relative fitness scores that are used to drive the delegate + * selector.

+ *

* @author Daniel Dyer */ public class RankSelection implements SelectionStrategy { private final SelectionStrategy delegate; + /** * Creates a default rank-based selector with a linear * mapping function and selection frequencies that correspond @@ -61,15 +62,13 @@ public RankSelection(SelectionStrategy delegate) } - /** - * {@inheritDoc} - */ public List select(List> population, boolean naturalFitnessScores, int selectionSize, Random rng) { - List> rankedPopulation = new ArrayList>(population.size()); + List> rankedPopulation = + new ArrayList>(population.size()); Iterator> iterator = population.iterator(); int index = -1; while (iterator.hasNext()) @@ -102,9 +101,6 @@ protected double mapRankToScore(int rank, int populationSize) } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java index a964f575..53323222 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java @@ -23,34 +23,31 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - *

Implements selection of n candidates from a population by selecting - * n candidates at random where the probability of each candidate getting - * selected is proportional to its fitness score. This is analogous to each - * candidate being assigned an area on a roulette wheel proportionate to its fitness - * and the wheel being spun i times. Candidates may be selected more than - * once.

- * - *

In some instances, particularly with small population sizes, the randomness - * of selection may result in excessively high occurrences of particular candidates. - * If this is a problem, {@link StochasticUniversalSampling} provides an alternative - * fitness-proportionate strategy for selection.

- * + *

Implements selection of n candidates from a population by selecting n candidates + * at random where the probability of each candidate getting selected is proportional to its fitness + * score. This is analogous to each candidate being assigned an area on a roulette wheel + * proportionate to its fitness and the wheel being spun i times. Candidates may be selected + * more than once.

+ *

In some instances, particularly with small population sizes, the randomness of selection + * may result in excessively high occurrences of particular candidates. If this is a problem, {@link StochasticUniversalSampling} + * provides an alternative fitness-proportionate strategy for selection.

+ *

* @author Daniel Dyer */ public class RouletteWheelSelection implements SelectionStrategy { /** - * Selects the required number of candidates from the population with - * the probability of selecting any particular candidate being proportional - * to that candidate's fitness score. Selection is with replacement (the same - * candidate may be selected multiple times). + * Selects the required number of candidates from the population with the probability of + * selecting any particular candidate being proportional to that candidate's fitness score. + * Selection is with replacement (the same candidate may be selected multiple times). + *

* @param The type of the evolved objects in the population. * @param population The candidates to select from. - * @param naturalFitnessScores True if higher fitness scores indicate fitter - * individuals, false if lower fitness scores indicate fitter individuals. + * @param naturalFitnessScores True if higher fitness scores indicate fitter individuals, false + * if lower fitness scores indicate fitter individuals. * @param selectionSize The number of selections to make. * @param rng A source of randomness. - * @return The selected candidates. + * @return The selected candidates. */ public List select(List> population, boolean naturalFitnessScores, @@ -77,7 +74,8 @@ public List select(List> population, List selection = new ArrayList(selectionSize); for (int i = 0; i < selectionSize; i++) { - double randomFitness = rng.nextDouble() * cumulativeFitnesses[cumulativeFitnesses.length - 1]; + double randomFitness = rng.nextDouble() * cumulativeFitnesses[cumulativeFitnesses.length + - 1]; int index = Arrays.binarySearch(cumulativeFitnesses, randomFitness); if (index < 0) { @@ -107,9 +105,6 @@ private double getAdjustedFitness(double rawFitness, } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java index 814b0b7e..cece91f2 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java @@ -23,19 +23,20 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - * An alternative to straightforward fitness-proportionate selection such as that offered - * by {@link RouletteWheelSelection} and {@link StochasticUniversalSampling}. Uses the - * mean population fitness and fitness standard deviation to adjust individual fitness - * scores. Early on in an evolutionary algorithm this helps to avoid premature convergence - * caused by the dominance of one or two relatively fit candidates in a population of mostly - * unfit individuals. It also helps to amplify minor fitness differences in a more mature - * population where the rate of improvement has slowed. + * An alternative to straightforward fitness-proportionate selection such as that offered by {@link RouletteWheelSelection} + * and {@link StochasticUniversalSampling}. Uses the mean population fitness and fitness standard + * deviation to adjust individual fitness scores. Early on in an evolutionary algorithm this helps to + * avoid premature convergence caused by the dominance of one or two relatively fit candidates in a + * population of mostly unfit individuals. It also helps to amplify minor fitness differences in a + * more mature population where the rate of improvement has slowed. + *

* @author Daniel Dyer */ public class SigmaScaling implements SelectionStrategy { private final SelectionStrategy delegate; + /** * Creates a default sigma-scaled selection strategy. */ @@ -57,22 +58,20 @@ public SigmaScaling(SelectionStrategy delegate) } - /** - * {@inheritDoc} - */ public List select(List> population, boolean naturalFitnessScores, int selectionSize, Random rng) { DataSet statistics = new DataSet(population.size()); - for (EvaluatedCandidate candidate : population) + for (EvaluatedCandidate candidate: population) { statistics.addValue(candidate.getFitness()); } - List> scaledPopulation = new ArrayList>(population.size()); - for (EvaluatedCandidate candidate : population) + List> scaledPopulation = + new ArrayList>(population.size()); + for (EvaluatedCandidate candidate: population) { double scaledFitness = getSigmaScaledFitness(candidate.getFitness(), statistics.getArithmeticMean(), @@ -94,7 +93,8 @@ private double getSigmaScaledFitness(double candidateFitness, } else { - double scaledFitness = 1 + (candidateFitness - populationMeanFitness) / (2 * fitnessStandardDeviation); + double scaledFitness = 1 + (candidateFitness - populationMeanFitness) / (2 + * fitnessStandardDeviation); // Don't allow negative expected frequencies, use an arbitrary low but still positive // frequency of 1 time in 10 for extremely unfit individuals (relative to the remainder // of the population). @@ -103,9 +103,6 @@ private double getSigmaScaledFitness(double candidateFitness, } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java index 7e1bf9b8..f0dc83db 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java @@ -22,24 +22,25 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - * An alternative to {@link RouletteWheelSelection} - * as a fitness-proportionate selection strategy. Ensures that the frequency of selection for - * each candidate is consistent with its expected frequency of selection. + * An alternative to {@link RouletteWheelSelection} as a fitness-proportionate selection strategy. + * Ensures that the frequency of selection for each candidate is consistent with its expected + * frequency of selection. + *

* @author Daniel Dyer */ public class StochasticUniversalSampling implements SelectionStrategy { public List select(List> population, - boolean naturalFitnessScores, - int selectionSize, - Random rng) + boolean naturalFitnessScores, + int selectionSize, + Random rng) { // Calculate the sum of all fitness values. double aggregateFitness = 0; - for (EvaluatedCandidate candidate : population) + for (EvaluatedCandidate candidate: population) { aggregateFitness += getAdjustedFitness(candidate.getFitness(), - naturalFitnessScores); + naturalFitnessScores); } List selection = new ArrayList(selectionSize); @@ -47,13 +48,13 @@ public List select(List> population, double startOffset = rng.nextDouble(); double cumulativeExpectation = 0; int index = 0; - for (EvaluatedCandidate candidate : population) + for (EvaluatedCandidate candidate: population) { // Calculate the number of times this candidate is expected to // be selected on average and add it to the cumulative total // of expected frequencies. cumulativeExpectation += getAdjustedFitness(candidate.getFitness(), - naturalFitnessScores) / aggregateFitness * selectionSize; + naturalFitnessScores) / aggregateFitness * selectionSize; // If f is the expected frequency, the candidate will be selected at // least as often as floor(f) and at most as often as ceil(f). The @@ -84,9 +85,6 @@ private double getAdjustedFitness(double rawFitness, boolean naturalFitness) } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java index 78d8107c..8a418da6 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java @@ -25,18 +25,18 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - * Selection strategy that picks a pair of candidates at random and then - * selects the fitter of the two candidates with probability p, where p - * is the configured selection probability (therefore the probability of - * the less fit candidate being selected is 1 - p). + * Selection strategy that picks a pair of candidates at random and then selects the fitter of the + * two candidates with probability p, where p is the configured selection probability (therefore the + * probability of the less fit candidate being selected is 1 - p). + *

* @author Daniel Dyer */ public class TournamentSelection implements SelectionStrategy { private final NumberGenerator selectionProbability; - private String description = "Tournament Selection"; + /** * Creates a tournament selection strategy that is controlled by the * variable selection probability provided by the specified @@ -50,7 +50,7 @@ public TournamentSelection(NumberGenerator selectionProbability) this.selectionProbability = selectionProbability; } - + /** * Creates a tournament selection strategy with a fixed probability. * @param selectionProbability The probability that the fitter of two randomly @@ -89,24 +89,21 @@ public List select(List> population, { // Select the fitter candidate. selection.add(candidate2.getFitness() > candidate1.getFitness() - ? candidate2.getCandidate() - : candidate1.getCandidate()); + ? candidate2.getCandidate() + : candidate1.getCandidate()); } else { // Select the less fit candidate. selection.add(candidate2.getFitness() > candidate1.getFitness() - ? candidate1.getCandidate() - : candidate2.getCandidate()); + ? candidate1.getCandidate() + : candidate2.getCandidate()); } } return selection; } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java index 594df978..69ce225a 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java @@ -25,18 +25,19 @@ import org.uncommons.watchmaker.framework.SelectionStrategy; /** - * Implements selection of n candidates from a population by simply - * selecting the n candidates with the highest fitness scores (the - * rest are discarded). A candidate is never selected more than once. + * Implements selection of n candidates from a population by simply selecting the n + * candidates with the highest fitness scores (the rest are discarded). A candidate is never selected + * more than once. + *

* @author Daniel Dyer */ public class TruncationSelection implements SelectionStrategy { private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#0.###%"); private final NumberGenerator selectionRatio; - private String description = "Truncation Selection"; + /** * Creates a truncation selection strategy that is controlled by the * variable selection ratio provided by the specified @@ -60,7 +61,8 @@ public TruncationSelection(double selectionRatio) this(new ConstantGenerator(selectionRatio)); if (selectionRatio <= 0 || selectionRatio >= 1) { - throw new IllegalArgumentException("Selection ratio must be greater than 0 and less than 1."); + throw new IllegalArgumentException( + "Selection ratio must be greater than 0 and less than 1."); } this.description = "Truncation Selection (" + PERCENT_FORMAT.format(selectionRatio) + ")"; } @@ -89,8 +91,8 @@ public List select(List> population, List selection = new ArrayList(selectionSize); double ratio = selectionRatio.nextValue(); - assert ratio < 1 && ratio > 0 : "Selection ratio out-of-range: " + ratio; - + assert ratio < 1 && ratio > 0: "Selection ratio out-of-range: " + ratio; + int eligibleCount = (int) Math.round(ratio * population.size()); eligibleCount = eligibleCount > selectionSize ? selectionSize : eligibleCount; @@ -106,9 +108,6 @@ public List select(List> population, } - /** - * {@inheritDoc} - */ @Override public String toString() { diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java index bddea381..3f0de433 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java @@ -20,12 +20,14 @@ /** * Terminates evolution after a pre-determined period of time has elapsed. + *

* @author Daniel Dyer */ public class ElapsedTime implements TerminationCondition { private final long maxDuration; + /** * @param maxDuration The maximum period of time (in milliseconds) before * evolution will be terminated. diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java index a0ec88c9..013d9a7c 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java @@ -20,12 +20,14 @@ /** * Terminates evolution after a set number of generations have passed. + *

* @author Daniel Dyer */ public class GenerationCount implements TerminationCondition { private final int generationCount; + /** * @param generationCount The maximum number of generations that the * evolutionary algorithm will permit before terminating. @@ -39,9 +41,7 @@ public GenerationCount(int generationCount) this.generationCount = generationCount; } - /** - * {@inheritDoc} - */ + public boolean shouldTerminate(PopulationData populationData) { return populationData.getGenerationNumber() + 1 >= generationCount; diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java index b0bab4a6..818060c7 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java @@ -19,8 +19,9 @@ import org.uncommons.watchmaker.framework.TerminationCondition; /** - * A {@link TerminationCondition} that halts evolution if no improvement in fitness - * is observed within a specified number of generations. + * A {@link TerminationCondition} that halts evolution if no improvement in fitness is observed + * within a specified number of generations. + *

* @author Daniel Dyer */ public class Stagnation implements TerminationCondition @@ -28,10 +29,10 @@ public class Stagnation implements TerminationCondition private final int generationLimit; private final boolean naturalFitness; private final boolean usePopulationAverage; - private double bestFitness; private int fittestGeneration; + /** * Creates a {@link TerminationCondition} that will halt evolution after the * specified number of generations passes without any improvement in the population's @@ -68,9 +69,6 @@ public Stagnation(int generationLimit, } - /** - * {@inheritDoc} - */ public boolean shouldTerminate(PopulationData populationData) { double fitness = getFitness(populationData); @@ -94,8 +92,8 @@ public boolean shouldTerminate(PopulationData populationData) private double getFitness(PopulationData populationData) { return usePopulationAverage - ? populationData.getMeanFitness() - : populationData.getBestCandidateFitness(); + ? populationData.getMeanFitness() + : populationData.getBestCandidateFitness(); } diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java index 26c3298a..beae19f2 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java @@ -19,8 +19,9 @@ import org.uncommons.watchmaker.framework.TerminationCondition; /** - * Terminates evolution once at least one candidate in the population has equalled - * or bettered a pre-determined fitness score. + * Terminates evolution once at least one candidate in the population has equalled or bettered a + * pre-determined fitness score. + *

* @author Daniel Dyer */ public class TargetFitness implements TerminationCondition @@ -28,6 +29,7 @@ public class TargetFitness implements TerminationCondition private final double targetFitness; private final boolean natural; + /** * @param targetFitness The fitness score that must be achieved by at least * one individual in the population in order for this condition to be satisfied. @@ -44,9 +46,7 @@ public TargetFitness(double targetFitness, boolean natural) this.natural = natural; } - /** - * {@inheritDoc} - */ + public boolean shouldTerminate(PopulationData populationData) { if (natural) diff --git a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java index dce1aab3..30702b45 100644 --- a/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java +++ b/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java @@ -19,12 +19,12 @@ import org.uncommons.watchmaker.framework.TerminationCondition; /** - * {@link TerminationCondition} implementation that allows for user-initiated - * termination of an evolutionary algorithm. This condition can be used, for - * instance, to provide a button on a GUI that terminates execution. The - * application should retain a reference to the instance after passing it to - * the evolution engine and should invoke the {@link #abort()} method to make - * the evolution terminate at the end of the current generation. + * {@link TerminationCondition} implementation that allows for user-initiated termination of an + * evolutionary algorithm. This condition can be used, for instance, to provide a button on a GUI + * that terminates execution. The application should retain a reference to the instance after passing + * it to the evolution engine and should invoke the {@link #abort()} method to make the evolution + * terminate at the end of the current generation. + *

* @see org.uncommons.watchmaker.swing.AbortControl * @author Daniel Dyer */ @@ -32,9 +32,7 @@ public final class UserAbort implements TerminationCondition { private volatile boolean aborted = false; - /** - * {@inheritDoc} - */ + public boolean shouldTerminate(PopulationData populationData) { return isAborted(); diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java index 6622229c..07c17b97 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java @@ -27,6 +27,7 @@ /** * Unit test for the {@link GenerationalEvolutionEngine} class. + *

* @author Daniel Dyer */ public class GenerationalEvolutionEngineTest @@ -52,11 +53,13 @@ class ElitismObserver implements EvolutionObserver { private PopulationData data; - public void populationUpdate(PopulationData data) + + public void populationUpdate(PopulationData data) { this.data = data; } + public double getAverageFitness() { return data.getMeanFitness(); @@ -77,7 +80,8 @@ public double getAverageFitness() // Then when we have run the evolution, if the elite canidates were preserved they will // lift the average fitness above zero. The exact value of the expected average fitness // is easy to calculate, it is the aggregate fitness divided by the population size. - assert observer.getAverageFitness() == 24d / 10 : "Elite candidates not preserved correctly: " + observer.getAverageFitness(); + assert observer.getAverageFitness() == 24d / 10: "Elite candidates not preserved correctly: " + + observer.getAverageFitness(); engine.removeEvolutionObserver(observer); } @@ -114,7 +118,7 @@ public void testInterrupt() final Thread requestThread = Thread.currentThread(); engine.addEvolutionObserver(new EvolutionObserver() { - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { if (populationData.getElapsedTime() > timeout / 2) { @@ -125,10 +129,10 @@ public void populationUpdate(PopulationData populationData) long startTime = System.currentTimeMillis(); engine.evolve(10, 0, new ElapsedTime(timeout)); long elapsedTime = System.currentTimeMillis() - startTime; - assert Thread.interrupted() : "Thread was not interrupted before timeout."; - assert elapsedTime < timeout : "Engine did not respond to interrupt before timeout."; - assert engine.getSatisfiedTerminationConditions().isEmpty() - : "Interrupted engine should have no satisfied termination conditions."; + assert Thread.interrupted(): "Thread was not interrupted before timeout."; + assert elapsedTime < timeout: "Engine did not respond to interrupt before timeout."; + assert engine.getSatisfiedTerminationConditions().isEmpty(): + "Interrupted engine should have no satisfied termination conditions."; } @@ -138,8 +142,9 @@ public void testGetSatisfiedTerminationConditions() GenerationCount generationsCondition = new GenerationCount(1); engine.evolve(10, 0, generationsCondition); List satisfiedConditions = engine.getSatisfiedTerminationConditions(); - assert satisfiedConditions.size() == 1 : "Wrong number of conditions: " + satisfiedConditions.size(); - assert satisfiedConditions.get(0) == generationsCondition : "Wrong condition returned."; + assert satisfiedConditions.size() == 1: "Wrong number of conditions: " + + satisfiedConditions.size(); + assert satisfiedConditions.get(0) == generationsCondition: "Wrong condition returned."; } @@ -150,7 +155,6 @@ public void testGetSatisfiedTerminationConditionsBeforeStart() engine.getSatisfiedTerminationConditions(); } - /** * Trivial test operator that mutates all integers into zeroes. */ diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java index 98fc00de..55368b42 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java @@ -28,14 +28,14 @@ /** * Unit test for the {@link IslandEvolution} class. + *

* @author Daniel Dyer */ public class IslandEvolutionTest { /** - * This test makes sure that the evolution observer global method only gets invoked at - * the end of each epoch, and that the island method gets invoked for each generation on each - * island. + * This test makes sure that the evolution observer global method only gets invoked at the end + * of each epoch, and that the island method gets invoked for each generation on each island. */ @Test public void testListeners() @@ -45,35 +45,38 @@ public void testListeners() final int generationCount = 5; IslandEvolution islandEvolution = new IslandEvolution(islandCount, - new RingMigration(), - new StubIntegerFactory(), - new IntegerAdjuster(2), - new DummyFitnessEvaluator(), - new RouletteWheelSelection(), - FrameworkTestUtils.getRNG()); + new RingMigration(), + new StubIntegerFactory(), + new IntegerAdjuster(2), + new DummyFitnessEvaluator(), + new RouletteWheelSelection(), + FrameworkTestUtils.getRNG()); final int[] observedEpochCount = new int[1]; final int[] observedGenerationCounts = new int[islandCount]; islandEvolution.addEvolutionObserver(new IslandEvolutionObserver() { - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { observedEpochCount[0]++; } - public void islandPopulationUpdate(int islandIndex, PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + PopulationData populationData) { observedGenerationCounts[islandIndex]++; } }); islandEvolution.evolve(5, 0, 5, 0, new GenerationCount(2)); - assert observedEpochCount[0] == 2 : "Listener should have been notified twice, was " + observedEpochCount[0]; + assert observedEpochCount[0] == 2: "Listener should have been notified twice, was " + + observedEpochCount[0]; for (int i = 0; i < islandCount; i++) { int expected = epochCount * generationCount; - assert observedGenerationCounts[i] == expected - : "Genertion count for island " + i + " should be " + expected + ", is " + observedGenerationCounts[i]; + assert observedGenerationCounts[i] == expected: "Genertion count for island " + i + + " should be " + expected + ", is " + + observedGenerationCounts[i]; } } @@ -82,17 +85,17 @@ public void islandPopulationUpdate(int islandIndex, PopulationData islandEvolution = new IslandEvolution(2, - new RingMigration(), - new StubIntegerFactory(), - new IntegerAdjuster(2), - new DummyFitnessEvaluator(), - new RouletteWheelSelection(), - FrameworkTestUtils.getRNG()); + new RingMigration(), + new StubIntegerFactory(), + new IntegerAdjuster(2), + new DummyFitnessEvaluator(), + new RouletteWheelSelection(), + FrameworkTestUtils.getRNG()); final long timeout = 1000L; final Thread requestThread = Thread.currentThread(); islandEvolution.addEvolutionObserver(new IslandEvolutionObserver() { - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { if (populationData.getElapsedTime() > timeout / 2) { @@ -101,15 +104,18 @@ public void populationUpdate(PopulationData populationData) } - public void islandPopulationUpdate(int islandIndex, PopulationData populationData){} + public void islandPopulationUpdate(int islandIndex, + PopulationData populationData) + { + } }); long startTime = System.currentTimeMillis(); islandEvolution.evolve(10, 0, 10, 0, new ElapsedTime(timeout)); long elapsedTime = System.currentTimeMillis() - startTime; - assert Thread.interrupted() : "Thread was not interrupted before timeout."; - assert elapsedTime < timeout : "Engine did not respond to interrupt before timeout."; - assert islandEvolution.getSatisfiedTerminationConditions().isEmpty() - : "Interrupted islands should have no satisfied termination conditions."; + assert Thread.interrupted(): "Thread was not interrupted before timeout."; + assert elapsedTime < timeout: "Engine did not respond to interrupt before timeout."; + assert islandEvolution.getSatisfiedTerminationConditions().isEmpty(): + "Interrupted islands should have no satisfied termination conditions."; } @@ -117,17 +123,16 @@ public void islandPopulationUpdate(int islandIndex, PopulationData islandEvolution = new IslandEvolution(3, - new RingMigration(), - new StubIntegerFactory(), - new IntegerAdjuster(2), - new DummyFitnessEvaluator(), - new RouletteWheelSelection(), - FrameworkTestUtils.getRNG()); + new RingMigration(), + new StubIntegerFactory(), + new IntegerAdjuster(2), + new DummyFitnessEvaluator(), + new RouletteWheelSelection(), + FrameworkTestUtils.getRNG()); // Should throw an IllegalStateException because evolution has started, let alone terminated. islandEvolution.getSatisfiedTerminationConditions(); } - private static class DummyFitnessEvaluator implements FitnessEvaluator { public double getFitness(Integer candidate, List population) @@ -135,6 +140,7 @@ public double getFitness(Integer candidate, List population) return 0; } + public boolean isNatural() { return true; diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java index ea268765..3eb1286e 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java @@ -15,13 +15,17 @@ //============================================================================= package org.uncommons.watchmaker.framework.termination; +import java.util.ArrayList; +import java.util.List; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.TerminationCondition; /** - * Unit test for termination condition that checks the time taken so far by the - * evolutionary algorithm. + * Unit test for termination condition that checks the time taken so far by the evolutionary + * algorithm. + *

* @author Daniel Dyer */ public class ElapsedTimeTest @@ -30,10 +34,14 @@ public class ElapsedTimeTest public void testElapsedTimes() { TerminationCondition condition = new ElapsedTime(1000); - PopulationData data = new PopulationData(new Object(), 0, 0, 0, true, 2, 0, 0, 100); - assert !condition.shouldTerminate(data) : "Should not terminate before timeout."; - data = new PopulationData(new Object(), 0, 0, 0, true, 2, 0, 0, 1000); - assert condition.shouldTerminate(data) : "Should terminate after timeout."; + List> evaluatedPopulation = + new ArrayList>(); + evaluatedPopulation.add(new EvaluatedCandidate(new Object(), 0)); + PopulationData data = new PopulationData(evaluatedPopulation, 0, 0, true, + 2, 0, 0, 100); + assert !condition.shouldTerminate(data): "Should not terminate before timeout."; + data = new PopulationData(evaluatedPopulation, 0, 0, true, 2, 0, 0, 1000); + assert condition.shouldTerminate(data): "Should terminate after timeout."; } @@ -48,5 +56,4 @@ public void testZeroRatio() { new ElapsedTime(0L); } - } diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java index 64487a28..ae25db46 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java @@ -15,12 +15,16 @@ //============================================================================= package org.uncommons.watchmaker.framework.termination; +import java.util.ArrayList; +import java.util.List; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.TerminationCondition; /** * Unit test for termination condition that checks the number of evolved generations. + *

* @author Daniel Dyer */ public class GenerationCountTest @@ -29,12 +33,16 @@ public class GenerationCountTest public void testGenerationCounts() { TerminationCondition condition = new GenerationCount(5); - PopulationData data = new PopulationData(new Object(), 0, 0, 0, true, 2, 0, 3, 100); + List> evaluatedPopulation = + new ArrayList>(); + evaluatedPopulation.add(new EvaluatedCandidate(new Object(), 0)); + PopulationData data = new PopulationData(evaluatedPopulation, 0, 0, + true, 2, 0, 3, 100); // Generation number 3 is the 4th generation (generation numbers are zero-based). - assert !condition.shouldTerminate(data) : "Should not terminate after 4th generation."; - data = new PopulationData(new Object(), 0, 0, 0, true, 2, 0, 4, 100); + assert !condition.shouldTerminate(data): "Should not terminate after 4th generation."; + data = new PopulationData(evaluatedPopulation, 0, 0, true, 2, 0, 4, 100); // Generation number 4 is the 5th generation (generation numbers are zero-based). - assert condition.shouldTerminate(data) : "Should terminate after 5th generation."; + assert condition.shouldTerminate(data): "Should terminate after 5th generation."; } diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java index 6efe9fb5..bded9ac1 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java @@ -15,12 +15,15 @@ //============================================================================= package org.uncommons.watchmaker.framework.termination; +import com.google.common.collect.ImmutableList; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.TerminationCondition; /** * Unit test for the {@link Stagnation} termination condition. + *

* @author Daniel Dyer */ public class StagnationTest @@ -29,28 +32,49 @@ public class StagnationTest public void testFittestCandidateStagnation() { TerminationCondition stagnation = new Stagnation(2, true); - PopulationData data = new PopulationData(new Object(), 2, 1, 0.1, true, 10, 0, 0, 1); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + PopulationData data = new PopulationData(evaluatedPopulation, 1, 0.1, + true, 10, 0, 0, 1); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve even though mean does. - data = new PopulationData(new Object(), 1.8, 1.2, 0.1, true, 10, 0, 1, 2); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation."; + evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 1.8)); + data = new PopulationData(evaluatedPopulation, 1.2, 0.1, true, 10, 0, 1, 2); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 1 more generation."; // Best doesn't improve even though mean does. - data = new PopulationData(new Object(), 2, 1.5, 0.1, true, 10, 0, 2, 3); - assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement."; + evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + data = new PopulationData(evaluatedPopulation, 1.5, 0.1, true, 10, 0, 2, 3); + assert stagnation.shouldTerminate(data): + "Stagnation should be triggered after 2 generations without improvement."; } + @Test public void testFittestCandidateStagnationNonNatural() { TerminationCondition stagnation = new Stagnation(2, false); - PopulationData data = new PopulationData(new Object(), 2, 1.5, 0.1, true, 10, 0, 0, 1); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + PopulationData data = new PopulationData(evaluatedPopulation, 1.5, + 0.1, true, 10, 0, 0, 1); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve even though mean does. - data = new PopulationData(new Object(), 2.2, 1.2, 0.1, true, 10, 0, 1, 2); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation."; + evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2.2)); + data = new PopulationData(evaluatedPopulation, 1.2, 0.1, true, 10, 0, 1, 2); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 1 more generation."; // Best doesn't improve even though mean does. - data = new PopulationData(new Object(), 2, 1, 0.1, true, 10, 0, 2, 3); - assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement."; + evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + data = new PopulationData(evaluatedPopulation, 1, 0.1, true, 10, 0, 2, 3); + assert stagnation.shouldTerminate(data): + "Stagnation should be triggered after 2 generations without improvement."; } @@ -58,20 +82,30 @@ public void testFittestCandidateStagnationNonNatural() public void testPopulationMeanStagnation() { TerminationCondition stagnation = new Stagnation(2, true, true); - PopulationData data = new PopulationData(new Object(), 2, 1, 0.1, true, 10, 0, 0, 1); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + PopulationData data = new PopulationData(evaluatedPopulation, 1, 0.1, + true, 10, 0, 0, 1); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve but mean does. - data = new PopulationData(new Object(), 2, 1.2, 0.1, true, 10, 0, 1, 2); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + data = new PopulationData(evaluatedPopulation, 1.2, 0.1, true, 10, 0, 1, 2); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve but mean does. - data = new PopulationData(new Object(), 2, 1.5, 0.1, true, 10, 0, 2, 3); + data = new PopulationData(evaluatedPopulation, 1.5, 0.1, true, 10, 0, 2, 3); // Best has stagnated but mean hasn't so shouldn't terminate. - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Now we let the mean stagnate...and let the best candidate get fitter... - data = new PopulationData(new Object(), 2.1, 1.5, 0.1, true, 10, 0, 3, 4); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation."; - data = new PopulationData(new Object(), 2.2, 1.5, 0.1, true, 10, 0, 4, 4); - assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 2.1)); + data = new PopulationData(evaluatedPopulation, 1.5, 0.1, true, 10, 0, 3, 4); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 1 more generation."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 2.2)); + data = new PopulationData(evaluatedPopulation, 1.5, 0.1, true, 10, 0, 4, 4); + assert stagnation.shouldTerminate(data): + "Stagnation should be triggered after 2 generations without improvement."; } @@ -79,19 +113,29 @@ public void testPopulationMeanStagnation() public void testPopulationMeanStagnationNonNatural() { TerminationCondition stagnation = new Stagnation(2, false, true); - PopulationData data = new PopulationData(new Object(), 2, 1.5, 0.1, true, 10, 0, 0, 1); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 2)); + PopulationData data = new PopulationData(evaluatedPopulation, 1.5, + 0.1, true, 10, 0, 0, 1); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve but mean does. - data = new PopulationData(new Object(), 2, 1.2, 0.1, true, 10, 0, 1, 2); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + data = new PopulationData(evaluatedPopulation, 1.2, 0.1, true, 10, 0, 1, 2); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Best doesn't improve but mean does. - data = new PopulationData(new Object(), 2, 1, 0.1, true, 10, 0, 2, 3); + data = new PopulationData(evaluatedPopulation, 1, 0.1, true, 10, 0, 2, 3); // Best has stagnated but mean hasn't so shouldn't terminate. - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations."; + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 2 more generations."; // Now we let the mean stagnate...and let the best candidate get fitter... - data = new PopulationData(new Object(), 2.1, 1.6, 0.1, true, 10, 0, 3, 4); - assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation."; - data = new PopulationData(new Object(), 2.2, 1.5, 0.1, true, 10, 0, 4, 4); - assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 2.1)); + data = new PopulationData(evaluatedPopulation, 1.6, 0.1, true, 10, 0, 3, 4); + assert !stagnation.shouldTerminate(data): + "Stagnation should not be triggered for at least 1 more generation."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 2.2)); + data = new PopulationData(evaluatedPopulation, 1.5, 0.1, true, 10, 0, 4, 4); + assert stagnation.shouldTerminate(data): + "Stagnation should be triggered after 2 generations without improvement."; } } diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java index 738f612f..042e8c5a 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java @@ -15,13 +15,16 @@ //============================================================================= package org.uncommons.watchmaker.framework.termination; +import com.google.common.collect.ImmutableList; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.TerminationCondition; /** - * Unit test for termination condition that checks the best fitness attained so far - * against a pre-determined target. + * Unit test for termination condition that checks the best fitness attained so far against a + * pre-determined target. + *

* @author Daniel Dyer */ public class TargetFitnessTest @@ -30,10 +33,15 @@ public class TargetFitnessTest public void testNaturalFitness() { TerminationCondition condition = new TargetFitness(10.0d, true); - PopulationData data = new PopulationData(new Object(), 5.0d, 4.0d, 0, true, 2, 0, 0, 100); - assert !condition.shouldTerminate(data) : "Should not terminate before target fitness is reached."; - data = new PopulationData(new Object(), 10.0d, 8.0d, 0, true, 2, 0, 0, 100); - assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 5.0)); + PopulationData data = new PopulationData(evaluatedPopulation, 4.0d, 0, + true, 2, 0, 0, 100); + assert !condition.shouldTerminate(data): + "Should not terminate before target fitness is reached."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 10.0)); + data = new PopulationData(evaluatedPopulation, 8.0d, 0, true, 2, 0, 0, 100); + assert condition.shouldTerminate(data): "Should terminate once target fitness is reached."; } @@ -41,9 +49,14 @@ public void testNaturalFitness() public void testNonNaturalFitness() { TerminationCondition condition = new TargetFitness(1.0d, false); - PopulationData data = new PopulationData(new Object(), 5.0d, 4.0d, 0, true, 2, 0, 0, 100); - assert !condition.shouldTerminate(data) : "Should not terminate before target fitness is reached."; - data = new PopulationData(new Object(), 1.0d, 3.1d, 0, true, 2, 0, 0, 100); - assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 5.0)); + PopulationData data = new PopulationData(evaluatedPopulation, 4.0d, 0, + true, 2, 0, 0, 100); + assert !condition.shouldTerminate(data): + "Should not terminate before target fitness is reached."; + evaluatedPopulation = ImmutableList.of(new EvaluatedCandidate(new Object(), 1.0)); + data = new PopulationData(evaluatedPopulation, 3.1d, 0, true, 2, 0, 0, 100); + assert condition.shouldTerminate(data): "Should terminate once target fitness is reached."; } } diff --git a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java index 34f8ce81..13e0fc86 100644 --- a/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java +++ b/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java @@ -15,11 +15,14 @@ //============================================================================= package org.uncommons.watchmaker.framework.termination; +import com.google.common.collect.ImmutableList; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; /** * Unit test for termination condition that checks an abort flag set by the user. + *

* @author Daniel Dyer */ public class UserAbortTest @@ -29,11 +32,14 @@ public void testAbort() { UserAbort condition = new UserAbort(); // This population data should be irrelevant. - PopulationData data = new PopulationData(new Object(), 0, 0, 0, true, 2, 0, 0, 100); - assert !condition.shouldTerminate(data) : "Should not terminate without user abort."; - assert !condition.isAborted() : "Should not be aborted without user intervention."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 0)); + PopulationData data = new PopulationData(evaluatedPopulation, 0, 0, + true, 2, 0, 0, 100); + assert !condition.shouldTerminate(data): "Should not terminate without user abort."; + assert !condition.isAborted(): "Should not be aborted without user intervention."; condition.abort(); - assert condition.shouldTerminate(data) : "Should terminate after user abort."; - assert condition.isAborted() : "Should be aborted after user intervention."; + assert condition.shouldTerminate(data): "Should terminate after user abort."; + assert condition.isAborted(): "Should be aborted after user intervention."; } } diff --git a/nb-configuration.xml b/nb-configuration.xml index e958b93e..2db68b70 100644 --- a/nb-configuration.xml +++ b/nb-configuration.xml @@ -14,10 +14,10 @@ That way multiple projects can share the same settings (useful for formatting ru Any value defined here will override the pom.xml file value but is only applicable to the current project. --> project - 2 - 2 - 2 - false + 4 + 4 + 4 + true 100 words 0 @@ -29,7 +29,6 @@ Any value defined here will override the pom.xml file value but is only applicab WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 true WRAP_IF_LONG WRAP_IF_LONG @@ -44,18 +43,14 @@ Any value defined here will override the pom.xml file value but is only applicab WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - true - 2 - 100 WRAP_IF_LONG true LEAVE_ALONE - 2 - 2 + 4 NEW_LINE - false NEW_LINE LEAVE_ALONE WRAP_IF_LONG + 2 diff --git a/pom.xml b/pom.xml index 6698f38b..ce11af1d 100644 --- a/pom.xml +++ b/pom.xml @@ -57,8 +57,8 @@ maven-compiler-plugin 2.3.2 - 1.6 - 1.6 + 1.5 + 1.5 ${project.build.sourceEncoding} diff --git a/swing/nb-configuration.xml b/swing/nb-configuration.xml index ae35717d..2db68b70 100644 --- a/swing/nb-configuration.xml +++ b/swing/nb-configuration.xml @@ -14,10 +14,10 @@ That way multiple projects can share the same settings (useful for formatting ru Any value defined here will override the pom.xml file value but is only applicable to the current project. --> project - 2 - 2 - 2 - false + 4 + 4 + 4 + true 100 words 0 @@ -29,33 +29,28 @@ Any value defined here will override the pom.xml file value but is only applicab WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - WRAP_IF_LONG true + WRAP_IF_LONG WRAP_IF_LONG - true true + true NEW_LINE - WRAP_IF_LONG WRAP_IF_LONG + WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG - 2 - true - 100 WRAP_IF_LONG true LEAVE_ALONE - 2 - 2 - false + 4 NEW_LINE NEW_LINE LEAVE_ALONE WRAP_IF_LONG + 2 diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java b/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java index fc8b49f2..9b1f23aa 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java @@ -23,13 +23,15 @@ /** * A GUI control that allows the user to abort an evolutionary program. + *

* @author Daniel Dyer */ public class AbortControl implements EvolutionControl { private final JButton control = new JButton("Abort"); private final UserAbort abortCondition = new UserAbort(); - + + public AbortControl() { control.addActionListener(new ActionListener() @@ -76,11 +78,8 @@ public TerminationCondition getTerminationCondition() } - /** - * {@inheritDoc} - */ public final void setDescription(String description) { control.setToolTipText(description); - } + } } diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java b/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java index e304cd36..1e131535 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java @@ -23,8 +23,8 @@ import org.uncommons.maths.number.NumberGenerator; /** - * A GUI control that allows the user to set/update the value of a - * numeric parameter. + * A GUI control that allows the user to set/update the value of a numeric parameter. + *

* @param The numeric type of this control (e.g. Integer, Double). * @author Daniel Dyer */ @@ -34,6 +34,7 @@ public class NumericParameterControl> implement private final JSpinner control; private final AdjustableNumberGenerator numberGenerator; + public NumericParameterControl(T minimum, T maximum, T stepSize, @@ -57,18 +58,12 @@ public void stateChanged(ChangeEvent changeEvent) } - /** - * {@inheritDoc} - */ public JSpinner getControl() { return control; } - /** - * {@inheritDoc} - */ public void reset() { control.setValue(defaultValue); @@ -87,9 +82,6 @@ public NumberGenerator getNumberGenerator() } - /** - * {@inheritDoc} - */ public void setDescription(String description) { control.setToolTipText(description); diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java b/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java index cc2c887b..5c5f4f08 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java @@ -31,6 +31,7 @@ /** * A GUI control that allows the user to set/update the value of a * {@link Probability} parameter. + *

* @author Daniel Dyer */ public class ProbabilityParameterControl implements EvolutionControl @@ -97,7 +98,7 @@ private DecimalFormat createFormat(int decimalPlaces) return new DecimalFormat(formatString.toString()); } - + private JSlider createSlider(Probability initialValue, Probability minimum, Probability maximum) @@ -122,18 +123,12 @@ public void stateChanged(ChangeEvent changeEvent) } - /** - * {@inheritDoc} - */ public JComponent getControl() { return control; } - /** - * {@inheritDoc} - */ public void reset() { int value = (int) Math.round(range * defaultValue.doubleValue()); @@ -154,9 +149,6 @@ public NumberGenerator getNumberGenerator() } - /** - * {@inheritDoc} - */ public void setDescription(String description) { probabilitySlider.setToolTipText(description); diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java b/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java index d9b60b5f..716f97ea 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java @@ -25,19 +25,15 @@ import org.uncommons.maths.random.Probability; import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.SelectionStrategy; -import org.uncommons.watchmaker.framework.selection.RankSelection; -import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection; -import org.uncommons.watchmaker.framework.selection.SigmaScaling; -import org.uncommons.watchmaker.framework.selection.StochasticUniversalSampling; -import org.uncommons.watchmaker.framework.selection.TournamentSelection; -import org.uncommons.watchmaker.framework.selection.TruncationSelection; +import org.uncommons.watchmaker.framework.selection.*; /** * An evolution control for selecting between different {@link SelectionStrategy} implementations. * This control provides a proxy selection strategy that delegates to the currently selected - * strategy. Using this proxy strategy with an {@link org.uncommons.watchmaker.framework.EvolutionEngine} - * means that any change to the combo-box selection is immediately reflected in the selection used - * by the running evolution engine. + * strategy. Using this proxy strategy with an {@link org.uncommons.watchmaker.framework.EvolutionEngine} + * means that any change to the combo-box selection is immediately reflected in the selection used by + * the running evolution engine. + *

* @param A generic type that matches the type associated with the selection strategies. * @author Daniel Dyer */ @@ -62,7 +58,8 @@ public void itemStateChanged(ItemEvent ev) if (ev.getStateChange() == ItemEvent.SELECTED) { @SuppressWarnings("unchecked") - SelectionStrategy delegate = (SelectionStrategy) control.getSelectedItem(); + SelectionStrategy delegate = (SelectionStrategy) control. + getSelectedItem(); selectionStrategy.setDelegate(delegate); } } @@ -78,8 +75,9 @@ public void itemStateChanged(ItemEvent ev) * @param truncationRatio The ratio parameter for {@link TruncationSelection}. * @return A list of selection strategies. */ - public static List> createDefaultOptions(Probability tournamentProbability, - double truncationRatio) + public static List> createDefaultOptions( + Probability tournamentProbability, + double truncationRatio) { List> options = new LinkedList>(); options.add(new RankSelection()); @@ -92,31 +90,22 @@ public static List> createDefaultOptions(Probab } - /** - * {@inheritDoc} - */ public JComboBox getControl() { return control; } - /** - * {@inheritDoc} - */ public void reset() { control.setSelectedIndex(0); } - /** - * {@inheritDoc} - */ public void setDescription(String description) { control.setToolTipText(description); - } + } /** @@ -128,7 +117,6 @@ public SelectionStrategy getSelectionStrategy() return selectionStrategy; } - /** * A {@link SelectionStrategy} implementation that simply delegates to the selection strategy * currently selected by the combobox control. @@ -137,6 +125,7 @@ private class ProxySelectionStrategy implements SelectionStrategy { private volatile SelectionStrategy delegate; + ProxySelectionStrategy(SelectionStrategy delegate) { this.delegate = delegate; @@ -149,9 +138,6 @@ public void setDelegate(SelectionStrategy delegate) } - /** - * {@inheritDoc} - */ public List select(List> population, boolean naturalFitnessScores, int selectionSize, diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java index 0ce6db57..bc03254e 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java @@ -24,15 +24,12 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.uncommons.watchmaker.framework.interactive.Console; /** * Swing-based console for interactive evolutionary algorithms. + *

* @author Daniel Dyer */ public class SwingConsole extends JPanel implements Console @@ -41,6 +38,7 @@ public class SwingConsole extends JPanel implements Console private final Condition selected = lock.newCondition(); private final AtomicInteger selectedIndex = new AtomicInteger(-1); + /** * Creates a console that displays candidates arranged in three columns * (and as many rows as required). @@ -76,7 +74,7 @@ public void run() { removeAll(); int index = -1; - for (JComponent entity : renderedEntities) + for (JComponent entity: renderedEntities) { add(new EntityPanel(entity, ++index)); } @@ -107,7 +105,6 @@ private void waitForSelection() } } - /** * Swing panel that wraps a rendered entity and a button for selecting * that entity. diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java index a089114c..46a5c63c 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java @@ -10,7 +10,7 @@ /** * Limits the update rate of a Swing-based EvolutionObserver. - * + *

* @param the population type * @author Gili Tzabari */ @@ -19,20 +19,23 @@ public class SwingEvolutionObserver implements EvolutionObserver private final EvolutionObserver delegate; private final long delay; private final TimeUnit unit; - private final ScheduledExecutorService timer = + private final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, new ThreadFactory() + { + private final ThreadFactory delegate = Executors.defaultThreadFactory(); + + + public Thread newThread(Runnable r) { - private final ThreadFactory delegate = Executors.defaultThreadFactory(); - public Thread newThread(Runnable r) - { - Thread result = delegate.newThread(r); - result.setDaemon(true); - return result; - } + Thread result = delegate.newThread(r); + result.setDaemon(true); + return result; + } }); private final AtomicReference> latestPopulation = new AtomicReference>(); + /** * Creates a new SwingEvolutionObserver. * @@ -44,7 +47,7 @@ public Thread newThread(Runnable r) * @throws IllegalArgumentException if delay is negative */ public SwingEvolutionObserver(EvolutionObserver delegate, long delay, - TimeUnit unit) + TimeUnit unit) { if (delegate == null) throw new NullPointerException("delegate may not be null"); @@ -52,24 +55,24 @@ public SwingEvolutionObserver(EvolutionObserver delegate, long delay, throw new NullPointerException("unit may not be null"); if (delay < 0) throw new IllegalArgumentException("delay may not be negative: " + delay); - + this.delegate = delegate; this.delay = delay; this.unit = unit; } - public void populationUpdate(PopulationData populationData) + + public void populationUpdate(PopulationData populationData) { if (latestPopulation.getAndSet(populationData) != null) { // An update is already scheduled return; } - - // Schedule an update in 300ms + + // Schedule an update timer.schedule(new Runnable() { - @Override public void run() { delegate.populationUpdate(latestPopulation.getAndSet(null)); diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java index 4a7cf80a..499a0625 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java @@ -7,7 +7,7 @@ /** * Limits the update rate of a Swing-based IslandEvolutionObserver. - * + *

* @param the population type * @author Gili Tzabari */ @@ -16,22 +16,25 @@ public class SwingIslandEvolutionObserver implements IslandEvolutionObserver< private final IslandEvolutionObserver delegate; private final long delay; private final TimeUnit unit; - private final ScheduledExecutorService timer = + private final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, new ThreadFactory() + { + private final ThreadFactory delegate = Executors.defaultThreadFactory(); + + + public Thread newThread(Runnable r) { - private final ThreadFactory delegate = Executors.defaultThreadFactory(); - public Thread newThread(Runnable r) - { - Thread result = delegate.newThread(r); - result.setDaemon(true); - return result; - } + Thread result = delegate.newThread(r); + result.setDaemon(true); + return result; + } }); private final AtomicReference> latestPopulation = new AtomicReference>(); private final ConcurrentHashMap> latestIslandPopulation = new ConcurrentHashMap>(); + /** * Creates a new SwingEvolutionObserver. * @@ -43,7 +46,7 @@ public Thread newThread(Runnable r) * @throws IllegalArgumentException if delay is negative */ public SwingIslandEvolutionObserver(IslandEvolutionObserver delegate, long delay, - TimeUnit unit) + TimeUnit unit) { if (delegate == null) throw new NullPointerException("delegate may not be null"); @@ -51,24 +54,24 @@ public SwingIslandEvolutionObserver(IslandEvolutionObserver delegate, long de throw new NullPointerException("unit may not be null"); if (delay < 0) throw new IllegalArgumentException("delay may not be negative: " + delay); - + this.delegate = delegate; this.delay = delay; this.unit = unit; } - public void populationUpdate(PopulationData populationData) + + public void populationUpdate(PopulationData populationData) { if (latestPopulation.getAndSet(populationData) != null) { // An update is already scheduled return; } - + // Schedule an update timer.schedule(new Runnable() { - @Override public void run() { delegate.populationUpdate(latestPopulation.getAndSet(null)); @@ -76,18 +79,18 @@ public void run() }, delay, unit); } - public void islandPopulationUpdate(final int i, PopulationData populationData) + + public void islandPopulationUpdate(final int i, PopulationData populationData) { if (latestIslandPopulation.put(i, populationData) != null) { // An update is already scheduled return; } - + // Schedule an update timer.schedule(new Runnable() { - @Override public void run() { delegate.islandPopulationUpdate(i, latestIslandPopulation.remove(i)); diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java index bec2e668..6577adde 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java @@ -21,12 +21,7 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.TimeUnit; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JFrame; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.jfree.chart.ChartFactory; import org.jfree.chart.StandardChartTheme; import org.uncommons.watchmaker.framework.PopulationData; @@ -37,25 +32,25 @@ /** * The Evolution Monitor is a component that can be attached to an - * {@link org.uncommons.watchmaker.framework.EvolutionEngine} to provide - * real-time information (in a Swing GUI) about the current state of the - * evolution. + * {@link org.uncommons.watchmaker.framework.EvolutionEngine} to provide real-time information (in a + * Swing GUI) about the current state of the evolution. + *

* @param The type of the evolved entities monitored by this component. * @author Daniel Dyer */ public class EvolutionMonitor implements IslandEvolutionObserver { - private final List> views = new LinkedList>(); - + private final List> views = + new LinkedList>(); private JComponent monitorComponent; private Window window = null; - + private final Renderer solutionRenderer; private final boolean islands; + /** - *

Creates an EvolutionMonitor with a single panel that graphs the fitness scores - * of the population from generation to generation.

- *

If you are using {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}, + *

Creates an EvolutionMonitor with a single panel that graphs the fitness scores of the + * population from generation to generation.

If you are using {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}, * use the {@link #EvolutionMonitor(boolean)} constructor instead, to enable island support.

*/ public EvolutionMonitor() @@ -63,14 +58,15 @@ public EvolutionMonitor() this(false); } - + /** - * Creates an EvolutionMonitor with a single panel that graphs the fitness scores - * of the population from generation to generation. + * Creates an EvolutionMonitor with a single panel that graphs the fitness scores of the + * population from generation to generation. + *

* @param islands Whether the monitor should be configured for displaying data from - * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this - * parameter to false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} - * or if you don't want to display island-specific data for island evolution. + * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this parameter to + * false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} or if + * you don't want to display island-specific data for island evolution. */ public EvolutionMonitor(boolean islands) { @@ -79,16 +75,36 @@ public EvolutionMonitor(boolean islands) /** - * Creates an EvolutionMonitor with a second panel that displays a graphical - * representation of the fittest candidate in the population. + * Creates an EvolutionMonitor with a second panel that displays a graphical representation of + * the fittest candidate in the population. + *

* @param renderer Renders a candidate solution as a JComponent. * @param islands Whether the monitor should be configured for displaying data from - * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this - * parameter to false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} + * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this parameter to + * false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} */ public EvolutionMonitor(final Renderer renderer, boolean islands) + { + this(renderer, null, islands); + } + + + /** + * Creates an EvolutionMonitor with a second panel that displays a graphical representation of + * the fittest candidate in the population. + *

+ * @param renderer Renders a candidate solution as a JComponent. + * @param solutionRenderer Renders the target solution regardless of the input. + * @param islands Whether the monitor should be configured for displaying data from + * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this parameter to + * false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} + */ + public EvolutionMonitor(final Renderer renderer, + Renderer solutionRenderer, + boolean islands) { this.islands = islands; + this.solutionRenderer = solutionRenderer; if (SwingUtilities.isEventDispatchThread()) { init(renderer); @@ -117,7 +133,7 @@ public void run() } } - + private void init(Renderer renderer) { // Make sure all JFreeChart charts are created with the legacy theme @@ -128,11 +144,18 @@ private void init(Renderer renderer) monitorComponent = new JPanel(new BorderLayout()); monitorComponent.add(tabs, BorderLayout.CENTER); - FittestCandidateView candidateView = new FittestCandidateView(renderer); + FittestCandidateView candidateView = new FittestCandidateView(renderer, + solutionRenderer); tabs.add("Fittest Individual", candidateView); views.add(new SwingIslandEvolutionObserver(candidateView, 300, TimeUnit.MILLISECONDS)); + PopulationCandidateView populationView = new PopulationCandidateView(renderer, + solutionRenderer); + tabs.add("Population Candidates", populationView); + views.add(new SwingIslandEvolutionObserver(populationView, 300, + TimeUnit.MILLISECONDS)); + PopulationFitnessView fitnessView = new PopulationFitnessView(islands); tabs.add(islands ? "Global Population" : "Population Fitness", fitnessView); views.add(fitnessView); @@ -151,28 +174,23 @@ private void init(Renderer renderer) StatusBar statusBar = new StatusBar(islands); monitorComponent.add(statusBar, BorderLayout.SOUTH); views.add(new SwingIslandEvolutionObserver(statusBar, - 300, TimeUnit.MILLISECONDS)); + 300, TimeUnit.MILLISECONDS)); } - /** - * {@inheritDoc} - */ - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { - for (IslandEvolutionObserver view : views) + for (IslandEvolutionObserver view: views) { view.populationUpdate(populationData); } } - /** - * {@inheritDoc} - */ - public void islandPopulationUpdate(int islandIndex, PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + PopulationData populationData) { - for (IslandEvolutionObserver view : views) + for (IslandEvolutionObserver view: views) { view.islandPopulationUpdate(islandIndex, populationData); } @@ -186,22 +204,24 @@ public JComponent getGUIComponent() /** - * Displays the evolution monitor component in a new {@link JFrame}. There is no - * need to make sure this method is invoked from the Event Dispatch Thread, the - * method itself ensures that the window is created and displayed from the EDT. + * Displays the evolution monitor component in a new {@link JFrame}. There is no need to make + * sure this method is invoked from the Event Dispatch Thread, the method itself ensures that + * the window is created and displayed from the EDT. + *

* @param title The title for the new frame. - * @param exitOnClose Whether the JVM should exit when the frame is closed. Useful - * if this is the only application window. + * @param exitOnClose Whether the JVM should exit when the frame is closed. Useful if this is + * the only application window. */ public void showInFrame(final String title, - final boolean exitOnClose) - { + final boolean exitOnClose) + { SwingUtilities.invokeLater(new Runnable() { public void run() { JFrame frame = new JFrame(title); - frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE : JFrame.DISPOSE_ON_CLOSE); + frame.setDefaultCloseOperation(exitOnClose ? JFrame.EXIT_ON_CLOSE + : JFrame.DISPOSE_ON_CLOSE); showWindow(frame); } }); @@ -209,16 +229,17 @@ public void run() /** - * Displays the evolution monitor component in a new {@link JDialog}. There is no - * need to make sure this method is invoked from the Event Dispatch Thread, the - * method itself ensures that the window is created and displayed from the EDT. + * Displays the evolution monitor component in a new {@link JDialog}. There is no need to make + * sure this method is invoked from the Event Dispatch Thread, the method itself ensures that + * the window is created and displayed from the EDT. + *

* @param owner The owning frame for the new dialog. * @param title The title for the new dialog. - * @param modal Whether the + * @param modal Whether the */ public void showInDialog(final JFrame owner, - final String title, - final boolean modal) + final String title, + final boolean modal) { SwingUtilities.invokeLater(new Runnable() { @@ -234,6 +255,7 @@ public void run() /** * Helper method for showing the evolution monitor in a frame or dialog. + *

* @param newWindow The frame or dialog used to show the evolution monitor. */ private void showWindow(Window newWindow) diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java index d66c9077..8d9e9650 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java @@ -17,42 +17,43 @@ import java.awt.BorderLayout; import java.awt.Font; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.SwingUtilities; +import java.awt.GridLayout; +import javax.swing.*; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.interactive.Renderer; import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver; /** - * {@link EvolutionMonitor} view for displaying a graphical representation - * of the fittest candidate found so far. This allows us to monitor the - * progress of an evolutionary algorithm. + * {@link EvolutionMonitor} view for displaying a graphical representation of the fittest candidate + * found so far. This allows us to monitor the progress of an evolutionary algorithm. + *

* @param The type of the evolved entity displayed by this component. * @author Daniel Dyer */ class FittestCandidateView extends JPanel implements IslandEvolutionObserver { private static final Font BIG_FONT = new Font("Dialog", Font.BOLD, 16); - private final Renderer renderer; private final JLabel fitnessLabel = new JLabel("N/A", JLabel.CENTER); private final JScrollPane scroller = new JScrollPane(); - + private final Renderer solutionRenderer; + private final JPanel candidates = new JPanel(); private T fittestCandidate = null; + /** - * Creates a Swing view that uses the specified renderer to display - * evolved entities. - * @param renderer A renderer that convert evolved entities of the type - * recognised by this view into Swing components. + * Creates a Swing view that uses the specified renderer to display evolved entities. + *

+ * @param renderer A renderer that convert evolved entities of the type recognised by this view + * into Swing components. + * @param solutionRenderer Renders the target solution regardless of the input. */ - FittestCandidateView(Renderer renderer) + FittestCandidateView(Renderer renderer, + Renderer solutionRenderer) { super(new BorderLayout(0, 10)); this.renderer = renderer; + this.solutionRenderer = solutionRenderer; JPanel header = new JPanel(new BorderLayout()); JLabel label = new JLabel("Fitness", JLabel.CENTER); @@ -68,10 +69,12 @@ class FittestCandidateView extends JPanel implements IslandEvolutionObserver< // Set names for easier indentification in unit tests. fitnessLabel.setName("FitnessLabel"); + + candidates.setLayout(new GridLayout(1, 2)); } - public void populationUpdate(final PopulationData populationData) + public void populationUpdate(final PopulationData populationData) { SwingUtilities.invokeLater(new Runnable() { @@ -86,15 +89,19 @@ public void run() if (populationData.getBestCandidate() != fittestCandidate) { fittestCandidate = populationData.getBestCandidate(); + candidates.removeAll(); + candidates.add(solutionRenderer.render(null)); JComponent renderedCandidate = renderer.render(fittestCandidate); - scroller.setViewportView(renderedCandidate); + candidates.add(renderedCandidate); + scroller.setViewportView(candidates); } } }); } - public void islandPopulationUpdate(int islandIndex, final PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + final PopulationData populationData) { // Do nothing. } diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java index acc45b9e..c038b0ea 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java @@ -42,35 +42,35 @@ /** * An evolution monitor view that gives an insight into how the evolution is progressing on * individual islands. + *

* @author Daniel Dyer */ class IslandsView extends JPanel implements IslandEvolutionObserver { private static final String FITTEST_INDIVIDUAL_LABEL = "Fittest Individual"; private static final String MEAN_FITNESS_LABEL = "Mean Fitness/Standard Deviation"; - private final DefaultCategoryDataset bestDataSet = new DefaultCategoryDataset(); - private final DefaultStatisticalCategoryDataset meanDataSet = new DefaultStatisticalCategoryDataset(); - private final StatisticalLineAndShapeRenderer meanRenderer = new StatisticalLineAndShapeRenderer(); + private final DefaultStatisticalCategoryDataset meanDataSet = + new DefaultStatisticalCategoryDataset(); + private final StatisticalLineAndShapeRenderer meanRenderer = + new StatisticalLineAndShapeRenderer(); private final JFreeChart chart; - private final AtomicInteger islandCount = new AtomicInteger(0); private final Object maxLock = new Object(); private double max = 0; - IslandsView() { super(new BorderLayout()); chart = ChartFactory.createBarChart("Island Population Fitness", - "Island No.", - "Candidate Fitness", - bestDataSet, - PlotOrientation.VERTICAL, - true, // Legend - false, // Tooltips - false); // URLs + "Island No.", + "Candidate Fitness", + bestDataSet, + PlotOrientation.VERTICAL, + true, // Legend + false, // Tooltips + false); // URLs CategoryPlot plot = (CategoryPlot) chart.getPlot(); plot.getDomainAxis().setLowerMargin(0.02); plot.getDomainAxis().setUpperMargin(0.02); @@ -80,18 +80,18 @@ class IslandsView extends JPanel implements IslandEvolutionObserver meanRenderer.setBaseLinesVisible(false); ChartPanel chartPanel = new ChartPanel(chart, - ChartPanel.DEFAULT_WIDTH, - ChartPanel.DEFAULT_HEIGHT, - ChartPanel.DEFAULT_MINIMUM_DRAW_WIDTH, - ChartPanel.DEFAULT_MINIMUM_DRAW_HEIGHT, - ChartPanel.DEFAULT_MAXIMUM_DRAW_WIDTH, - ChartPanel.DEFAULT_MAXIMUM_DRAW_HEIGHT, - false, // Buffered - false, // Properties - true, // Save - true, // Print - false, // Zoom - false); // Tooltips + ChartPanel.DEFAULT_WIDTH, + ChartPanel.DEFAULT_HEIGHT, + ChartPanel.DEFAULT_MINIMUM_DRAW_WIDTH, + ChartPanel.DEFAULT_MINIMUM_DRAW_HEIGHT, + ChartPanel.DEFAULT_MAXIMUM_DRAW_WIDTH, + ChartPanel.DEFAULT_MAXIMUM_DRAW_HEIGHT, + false, // Buffered + false, // Properties + true, // Save + true, // Print + false, // Zoom + false); // Tooltips add(chartPanel, BorderLayout.CENTER); add(createControls(), BorderLayout.SOUTH); } @@ -131,8 +131,8 @@ public void itemStateChanged(ItemEvent itemEvent) } - - public void islandPopulationUpdate(final int islandIndex, final PopulationData populationData) + public void islandPopulationUpdate(final int islandIndex, + final PopulationData populationData) { // Make sure the bars are added to the chart in order of island index, regardless of which island // reports its results first. @@ -170,18 +170,20 @@ public void run() public void run() { chart.setNotify(false); - bestDataSet.setValue(populationData.getBestCandidateFitness(), FITTEST_INDIVIDUAL_LABEL, (Integer) islandIndex); + bestDataSet.setValue(populationData.getBestCandidateFitness(), + FITTEST_INDIVIDUAL_LABEL, (Integer) islandIndex); meanDataSet.remove(MEAN_FITNESS_LABEL, (Integer) islandIndex); meanDataSet.add(populationData.getMeanFitness(), - populationData.getFitnessStandardDeviation(), - MEAN_FITNESS_LABEL, - (Integer) islandIndex); + populationData.getFitnessStandardDeviation(), + MEAN_FITNESS_LABEL, + (Integer) islandIndex); ValueAxis rangeAxis = ((CategoryPlot) chart.getPlot()).getRangeAxis(); // If the range is not sufficient to display all values, enlarge it. synchronized (maxLock) { max = Math.max(max, populationData.getBestCandidateFitness()); - max = Math.max(max, populationData.getMeanFitness() + populationData.getFitnessStandardDeviation()); + max = Math.max(max, populationData.getMeanFitness() + populationData. + getFitnessStandardDeviation()); while (max > rangeAxis.getUpperBound()) { rangeAxis.setUpperBound(rangeAxis.getUpperBound() * 2); @@ -198,7 +200,7 @@ public void run() } - public void populationUpdate(PopulationData populationData) + public void populationUpdate(PopulationData populationData) { synchronized (maxLock) { diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java index 3e4566f7..0f6773e9 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java @@ -19,12 +19,7 @@ import java.awt.FlowLayout; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; -import javax.swing.ButtonGroup; -import javax.swing.JCheckBox; -import javax.swing.JComponent; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartPanel; import org.jfree.chart.JFreeChart; @@ -37,24 +32,22 @@ import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver; /** - * {@link EvolutionMonitor} view for displaying a graph of population fitness data - * over the lifetime of the evolutionary algorithm. + * {@link EvolutionMonitor} view for displaying a graph of population fitness data over the lifetime + * of the evolutionary algorithm. + *

* @author Daniel Dyer */ class PopulationFitnessView extends JPanel implements IslandEvolutionObserver { private static final int SHOW_FIXED_GENERATIONS = 200; - private final XYSeries bestSeries = new XYSeries("Fittest Individual"); private final XYSeries meanSeries; private final XYSeriesCollection dataSet = new XYSeriesCollection(); private final ValueAxis domainAxis; private final ValueAxis rangeAxis; - private final JRadioButton allDataButton = new JRadioButton("All Data", false); private final JCheckBox invertCheckBox = new JCheckBox("Invert Range Axis", false); private final JFreeChart chart; - private double maxY = 1; private double minY = 0; @@ -65,14 +58,15 @@ class PopulationFitnessView extends JPanel implements IslandEvolutionObserver populationData) + public void populationUpdate(final PopulationData populationData) { SwingUtilities.invokeLater(new Runnable() { @@ -193,7 +184,7 @@ public void run() // The graph might be showing data from a previous run, so clear it. meanSeries.clear(); bestSeries.clear(); - } + } meanSeries.add(populationData.getGenerationNumber(), populationData.getMeanFitness()); double best = populationData.getBestCandidateFitness(); bestSeries.add(populationData.getGenerationNumber(), best); @@ -201,8 +192,10 @@ public void run() // We don't use JFreeChart's auto-range for the axes because it is inefficient // (it degrades linearly with the number of items in the data set). Instead we track // the minimum and maximum ourselves. - double high = Math.max(populationData.getMeanFitness(), populationData.getBestCandidateFitness()); - double low = Math.min(populationData.getMeanFitness(), populationData.getBestCandidateFitness()); + double high = Math.max(populationData.getMeanFitness(), populationData. + getBestCandidateFitness()); + double low = Math.min(populationData.getMeanFitness(), populationData. + getBestCandidateFitness()); if (high > maxY) { maxY = high; @@ -221,7 +214,8 @@ public void run() } - public void islandPopulationUpdate(int islandIndex, PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + PopulationData populationData) { // Do nothing. } diff --git a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java index 51c66514..984619d9 100644 --- a/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java +++ b/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java @@ -16,17 +16,14 @@ package org.uncommons.watchmaker.swing.evolutionmonitor; import java.util.concurrent.atomic.AtomicInteger; -import javax.swing.BorderFactory; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JLabel; -import javax.swing.SwingUtilities; +import javax.swing.*; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver; /** - * Status bar component for the evolution monitor. Can also be used separately to - * provide basic status information without having to use the full evolution monitor. + * Status bar component for the evolution monitor. Can also be used separately to provide basic + * status information without having to use the full evolution monitor. + *

* @author Daniel Dyer */ public class StatusBar extends Box implements IslandEvolutionObserver @@ -35,7 +32,6 @@ public class StatusBar extends Box implements IslandEvolutionObserver private final JLabel timeLabel = new JLabel("N/A", JLabel.RIGHT); private final JLabel populationLabel = new JLabel("N/A", JLabel.RIGHT); private final JLabel elitismLabel = new JLabel("N/A", JLabel.RIGHT); - private final AtomicInteger islandPopulationSize = new AtomicInteger(-1); private long elapsedTime; private long epochTime; @@ -52,8 +48,8 @@ public StatusBar() /** * @param islands Whether the status bar should be configured for updates from - * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this - * parameter to false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} + * {@link org.uncommons.watchmaker.framework.islands.IslandEvolution}. Set this parameter to + * false when using a standard {@link org.uncommons.watchmaker.framework.EvolutionEngine} */ public StatusBar(boolean islands) { @@ -79,10 +75,7 @@ public StatusBar(boolean islands) } - /** - * {@inheritDoc} - */ - public void populationUpdate(final PopulationData populationData) + public void populationUpdate(final PopulationData populationData) { SwingUtilities.invokeLater(new Runnable() { @@ -112,11 +105,8 @@ public void run() } - /** - * {@inheritDoc} - */ - public void islandPopulationUpdate(int islandIndex, - final PopulationData populationData) + public void islandPopulationUpdate(int islandIndex, + final PopulationData populationData) { islandPopulationSize.compareAndSet(-1, populationData.getPopulationSize()); SwingUtilities.invokeLater(new Runnable() diff --git a/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java b/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java index 4411423c..0167886e 100644 --- a/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java +++ b/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java @@ -15,10 +15,12 @@ //============================================================================= package org.uncommons.watchmaker.swing.evolutionmonitor; +import com.google.common.collect.ImmutableList; import java.awt.BorderLayout; import java.math.BigDecimal; import javax.swing.JComponent; import javax.swing.JFrame; +import javax.swing.JLabel; import javax.swing.text.JTextComponent; import org.fest.swing.core.BasicRobot; import org.fest.swing.core.Robot; @@ -26,18 +28,21 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; import org.uncommons.watchmaker.framework.interactive.Renderer; import org.uncommons.watchmaker.swing.ObjectSwingRenderer; /** * Unit test for the {@link FittestCandidateView} evolution monitor panel. + *

* @author Daniel Dyer */ public class FittestCandidateViewTest { private Robot robot; + @BeforeMethod public void prepare() { @@ -52,12 +57,19 @@ public void cleanUp() robot = null; } - + @Test(groups = "display-required") public void testUpdate() { Renderer renderer = new ObjectSwingRenderer(); - FittestCandidateView view = new FittestCandidateView(renderer); + FittestCandidateView view = new FittestCandidateView(renderer, + new Renderer() + { + public JComponent render(Object entity) + { + return new JLabel("Target solution goes here"); + } + }); JFrame frame = new JFrame(); frame.add(view, BorderLayout.CENTER); FrameFixture frameFixture = new FrameFixture(robot, frame); @@ -65,30 +77,40 @@ public void testUpdate() frame.validate(); frame.setVisible(true); - view.populationUpdate(new PopulationData(BigDecimal.TEN, 10, 5, 2, true, 5, 0, 1, 100)); + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(BigDecimal.TEN, 10)); + view.populationUpdate(new PopulationData(evaluatedPopulation, 5, 2, true, 5, 0, + 1, 100)); robot.waitForIdle(); // Check displayed fitness. String fitnessText = frameFixture.label("FitnessLabel").text(); - assert fitnessText.equals("10.0") : "Wrong fitness score displayed: " + fitnessText; + assert fitnessText.equals("10.0"): "Wrong fitness score displayed: " + fitnessText; // Check rendered candidate. frameFixture.textBox().requireNotEditable(); String text = frameFixture.textBox().component().getText(); - assert text.equals("10") : "Candidate rendered incorrectly."; + assert text.equals("10"): "Candidate rendered incorrectly."; } /** - * If the view is updated with the same candidate it is already displaying, it should - * avoid the expense of re-rendering it. + * If the view is updated with the same candidate it is already displaying, it should avoid the + * expense of re-rendering it. */ @Test(groups = "display-required", - dependsOnMethods = "testUpdate") + dependsOnMethods = "testUpdate") public void testUpdateSameCandidate() { Renderer renderer = new ObjectSwingRenderer(); - FittestCandidateView view = new FittestCandidateView(renderer); + FittestCandidateView view = new FittestCandidateView(renderer, + new Renderer() + { + public JComponent render(Object entity) + { + return new JLabel("Target solution goes here"); + } + }); JFrame frame = new JFrame(); frame.add(view, BorderLayout.CENTER); FrameFixture frameFixture = new FrameFixture(robot, frame); @@ -96,18 +118,22 @@ public void testUpdateSameCandidate() frame.validate(); frame.setVisible(true); - PopulationData data1 = new PopulationData(BigDecimal.TEN, 10, 5, 2, true, 5, 0, 1, 100); + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(BigDecimal.TEN, 10)); + PopulationData data1 = new PopulationData(evaluatedPopulation, 5, 2, + true, 5, 0, 1, 100); // Render the first time. view.populationUpdate(data1); robot.waitForIdle(); JTextComponent component1 = frameFixture.textBox().component(); // Render the same candidate for the second generation. - PopulationData data2 = new PopulationData(BigDecimal.TEN, 10, 5, 2, true, 5, 0, 2, 100); + PopulationData data2 = new PopulationData(evaluatedPopulation, 5, 2, + true, 5, 0, 2, 100); view.populationUpdate(data2); robot.waitForIdle(); JTextComponent component2 = frameFixture.textBox().component(); - assert component1 == component2 : "Rendered component should be the same."; + assert component1 == component2: "Rendered component should be the same."; } } diff --git a/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java b/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java index 8dc416b8..f2806bfe 100644 --- a/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java +++ b/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java @@ -15,6 +15,7 @@ //============================================================================= package org.uncommons.watchmaker.swing.evolutionmonitor; +import com.google.common.collect.ImmutableList; import java.awt.BorderLayout; import javax.swing.JFrame; import org.fest.swing.core.BasicRobot; @@ -23,16 +24,19 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +import org.uncommons.watchmaker.framework.EvaluatedCandidate; import org.uncommons.watchmaker.framework.PopulationData; /** * Unit test for the {@link StatusBar} class. + *

* @author Daniel Dyer */ public class StatusBarTest { private Robot robot; + @BeforeMethod public void prepare() { @@ -47,7 +51,7 @@ public void cleanUp() robot = null; } - + @Test(groups = "display-required") // Will fail if run in a headless environment. public void testFieldUpdates() { @@ -59,17 +63,27 @@ public void testFieldUpdates() frame.validate(); frameFixture.show(); - assert frameFixture.label("Population").text().equals("N/A") : "Wrong initial text for population label."; - assert frameFixture.label("Elitism").text().equals("N/A") : "Wrong initial text for elitism label."; - assert frameFixture.label("Generations").text().equals("N/A") : "Wrong initial text for generations label."; - assert frameFixture.label("Time").text().equals("N/A") : "Wrong initial text for elapsed time label."; + assert frameFixture.label("Population").text().equals("N/A"): + "Wrong initial text for population label."; + assert frameFixture.label("Elitism").text().equals("N/A"): + "Wrong initial text for elitism label."; + assert frameFixture.label("Generations").text().equals("N/A"): + "Wrong initial text for generations label."; + assert frameFixture.label("Time").text().equals("N/A"): + "Wrong initial text for elapsed time label."; - statusBar.populationUpdate(new PopulationData(new Object(), 10, 8, 2, true, 10, 1, 0, 36610000)); - assert frameFixture.label("Population").text().equals("10") : "Wrong value for popluation label."; - assert frameFixture.label("Elitism").text().equals("1") : "Wrong value for elitism label."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 10)); + statusBar.populationUpdate(new PopulationData(evaluatedPopulation, 8, 2, true, 10, 1, + 0, 36610000)); + assert frameFixture.label("Population").text().equals("10"): + "Wrong value for popluation label."; + assert frameFixture.label("Elitism").text().equals("1"): "Wrong value for elitism label."; // Generation count is number + 1 (because generations start at zero). - assert frameFixture.label("Generations").text().equals("1") : "Wrong value for generations label."; - assert frameFixture.label("Time").text().equals("10:10:10") : "Wrong value for elapsed time label."; + assert frameFixture.label("Generations").text().equals("1"): + "Wrong value for generations label."; + assert frameFixture.label("Time").text().equals("10:10:10"): + "Wrong value for elapsed time label."; } @@ -84,21 +98,32 @@ public void testFieldUpdatesForIslandMode() frame.validate(); frameFixture.show(); - assert frameFixture.label("Population").text().equals("N/A") : "Wrong initial text for population label."; - assert frameFixture.label("Elitism").text().equals("N/A") : "Wrong initial text for elitism label."; - assert frameFixture.label("Generations").text().equals("N/A") : "Wrong initial text for generations label."; - assert frameFixture.label("Time").text().equals("N/A") : "Wrong initial text for elapsed time label."; + assert frameFixture.label("Population").text().equals("N/A"): + "Wrong initial text for population label."; + assert frameFixture.label("Elitism").text().equals("N/A"): + "Wrong initial text for elitism label."; + assert frameFixture.label("Generations").text().equals("N/A"): + "Wrong initial text for generations label."; + assert frameFixture.label("Time").text().equals("N/A"): + "Wrong initial text for elapsed time label."; - statusBar.islandPopulationUpdate(0, new PopulationData(new Object(), 10, 8, 2, true, 10, 1, 0, 36610000)); - statusBar.populationUpdate(new PopulationData(new Object(), 10, 8, 2, true, 50, 1, 0, 36610000)); - assert frameFixture.label("Population").text().equals("5x10") : "Wrong value for popluation label."; - assert frameFixture.label("Elitism").text().equals("5x1") : "Wrong value for elitism label."; + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 10)); + statusBar.islandPopulationUpdate(0, new PopulationData(evaluatedPopulation, 8, 2, + true, 10, 1, 0, 36610000)); + statusBar.populationUpdate(new PopulationData(evaluatedPopulation, 8, 2, true, 50, 1, + 0, 36610000)); + assert frameFixture.label("Population").text().equals("5x10"): + "Wrong value for popluation label."; + assert frameFixture.label("Elitism").text().equals("5x1"): "Wrong value for elitism label."; // Generation count is number + 1 (because generations start at zero). - assert frameFixture.label("Generations").text().equals("1") : "Wrong value for generations label."; - assert frameFixture.label("Time").text().equals("10:10:10") : "Wrong value for elapsed time label."; + assert frameFixture.label("Generations").text().equals("1"): + "Wrong value for generations label."; + assert frameFixture.label("Time").text().equals("10:10:10"): + "Wrong value for elapsed time label."; } - + @Test(groups = "display-required") // Will fail if run in a headless environment. public void testTimeFormat() { @@ -112,8 +137,10 @@ public void testTimeFormat() // Previous test checks two-digit field values, this test checks that single-digit // values and zeros are correctly padded. - statusBar.populationUpdate(new PopulationData(new Object(), 10, 8, 2, true, 10, 1, 0, 1000)); + ImmutableList> evaluatedPopulation = + ImmutableList.of(new EvaluatedCandidate(new Object(), 10)); + statusBar.populationUpdate(new PopulationData(evaluatedPopulation, 8, 2, true, 10, 1, + 0, 1000)); assert frameFixture.label("Time").text().equals("00:00:01"); } - }