summaryrefslogtreecommitdiffstats
path: root/watchmaker
diff options
context:
space:
mode:
authorYohann Roussel <yroussel@google.com>2014-03-19 16:25:37 +0100
committerYohann Roussel <yroussel@google.com>2014-03-20 15:13:33 +0100
commit4eceb95409e844fdc33c9c706e1dc307bfd40303 (patch)
treeee9f4f3fc79f757c79081c336bce4f1782c6ccd8 /watchmaker
parent3d2402901b1a6462e2cf47a6fd09711f327961c3 (diff)
downloadtoolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.zip
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.gz
toolchain_jack-4eceb95409e844fdc33c9c706e1dc307bfd40303.tar.bz2
Initial Jack import.
Change-Id: I953cf0a520195a7187d791b2885848ad0d5a9b43
Diffstat (limited to 'watchmaker')
-rw-r--r--watchmaker/Android.mk33
-rw-r--r--watchmaker/CHANGELOG.txt431
-rw-r--r--watchmaker/LICENCE.txt202
-rw-r--r--watchmaker/NOTICE.txt43
-rw-r--r--watchmaker/README.android7
-rw-r--r--watchmaker/README.txt46
-rw-r--r--watchmaker/book/book.iml12
-rw-r--r--watchmaker/book/src/resources/antenna.jpgbin0 -> 12181 bytes
-rw-r--r--watchmaker/book/src/resources/docbook.css35
-rw-r--r--watchmaker/book/src/resources/travelling_salesman_problem.pngbin0 -> 63665 bytes
-rw-r--r--watchmaker/book/src/xml/book.xml57
-rw-r--r--watchmaker/book/src/xml/classifiers.xml6
-rw-r--r--watchmaker/book/src/xml/distributed.xml13
-rw-r--r--watchmaker/book/src/xml/evolution.xml424
-rw-r--r--watchmaker/book/src/xml/furtherreading.xml6
-rw-r--r--watchmaker/book/src/xml/geneticprogramming.xml6
-rw-r--r--watchmaker/book/src/xml/gui.xml22
-rw-r--r--watchmaker/book/src/xml/interactive.xml6
-rw-r--r--watchmaker/book/src/xml/islands.xml175
-rw-r--r--watchmaker/book/src/xml/multiobjective.xml6
-rw-r--r--watchmaker/book/src/xml/performance.xml157
-rw-r--r--watchmaker/book/src/xml/preface.xml29
-rw-r--r--watchmaker/book/src/xml/salesman.xml17
-rw-r--r--watchmaker/book/src/xml/selection.xml160
-rw-r--r--watchmaker/book/src/xml/steadystate.xml6
-rw-r--r--watchmaker/book/src/xml/sudoku.xml6
-rw-r--r--watchmaker/book/src/xml/watchmaker.xml478
-rw-r--r--watchmaker/book/src/xml/website.xml44
-rw-r--r--watchmaker/build.xml298
-rw-r--r--watchmaker/etc/checks.xml164
-rw-r--r--watchmaker/etc/checkstyle-noframes-sorted.xsl178
-rw-r--r--watchmaker/etc/java.header15
-rw-r--r--watchmaker/etc/testng-headless.xml34
-rw-r--r--watchmaker/etc/testng.xml31
-rw-r--r--watchmaker/examples/examples.iml33
-rw-r--r--watchmaker/examples/nb-configuration.xml61
-rw-r--r--watchmaker/examples/pom.xml58
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java102
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java39
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/Launcher.java79
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/Biomorph.java160
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java241
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactory.java44
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java67
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java98
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/SwingBiomorphRenderer.java111
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/package-info.java21
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java51
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitsExample.java67
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/package-info.java23
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java79
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java214
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java101
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExample.java85
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java268
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java78
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java127
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java90
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Node.java120
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java102
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Simplification.java71
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java80
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRenderer.java141
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossover.java72
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluator.java79
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java115
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutation.java61
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/package-info.java20
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java101
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutation.java97
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java84
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java94
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java78
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java249
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutation.java74
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java113
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java158
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java75
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java91
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java143
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java173
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutation.java75
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java76
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/package-info.java23
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java73
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java119
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/package-info.java22
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/Sudoku.java150
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java283
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuCellRenderer.java162
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java89
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java125
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutation.java206
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuTableModel.java156
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossover.java105
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuView.java76
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/package-info.java20
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java107
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/DistanceLookup.java39
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java326
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionPanel.java119
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java152
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ExecutionPanel.java107
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanel.java114
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ProgressListener.java31
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java74
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanel.java92
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanApplet.java185
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanStrategy.java44
-rw-r--r--watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/package-info.java21
-rw-r--r--watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/lighthouse.jpgbin0 -> 30648 bytes
-rw-r--r--watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/monalisa.jpgbin0 -> 11647 bytes
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/AbstractExampleAppletTest.java94
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/ExamplesTestUtils.java48
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactoryTest.java51
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphTest.java59
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutationTest.java72
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutationTest.java59
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/bits/BitsExampleTest.java34
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/AdditionTest.java112
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ConstantTest.java55
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExampleTest.java50
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElseTest.java248
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IsGreaterTest.java110
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/MultiplicationTest.java157
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ParameterTest.java60
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SimplificationTest.java70
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SubtractionTest.java133
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRendererTest.java51
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossoverTest.java47
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluatorTest.java78
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactoryTest.java66
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutationTest.java76
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutationTest.java104
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddVertexMutationTest.java100
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java115
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutationTest.java75
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java69
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java101
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactoryTest.java59
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java132
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutationTest.java91
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java98
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringEvaluatorTest.java58
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringsExampleTest.java34
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuCellRendererTest.java112
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluatorTest.java77
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuFactoryTest.java142
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutationTest.java127
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTableModelTest.java113
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTest.java97
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTestUtils.java48
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossoverTest.java64
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuViewTest.java117
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesmanTest.java51
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookupTest.java65
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesmanTest.java95
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanelTest.java91
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluatorTest.java70
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanelTest.java119
-rw-r--r--watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/TestDistances.java70
-rw-r--r--watchmaker/framework/framework.iml25
-rw-r--r--watchmaker/framework/nb-configuration.xml61
-rw-r--r--watchmaker/framework/pom.xml52
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java97
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java64
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java38
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java33
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java81
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java81
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java60
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java179
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java341
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java97
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java72
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java109
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java163
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java57
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java115
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java129
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java63
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java112
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java78
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java55
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java132
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java48
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java165
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java49
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java147
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java32
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java76
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java53
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java57
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java58
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java62
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java21
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java177
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java73
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java21
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java53
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java375
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java38
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java67
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java68
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java22
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java184
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java128
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java100
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java70
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java44
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java129
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java87
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java68
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java164
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java105
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java136
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java96
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java119
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java132
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java95
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java22
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java113
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java118
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java114
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java95
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java115
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java117
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java52
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java49
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java113
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java63
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java70
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java19
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java137
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java36
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java56
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java56
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java35
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java198
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java94
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java102
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java68
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java48
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java169
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java37
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java138
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java93
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java125
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java126
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java112
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java30
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java146
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java143
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java55
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java70
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java72
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java82
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java69
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java45
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java126
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java64
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java58
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java75
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java69
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java81
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java77
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java92
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java91
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java54
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java94
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java99
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java124
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java91
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java90
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java97
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java52
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java52
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java97
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java49
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java39
-rw-r--r--watchmaker/nb-configuration.xml61
-rw-r--r--watchmaker/pom.xml77
-rw-r--r--watchmaker/swing/lib/runtime/jfreechart-LICENCE.txt504
-rw-r--r--watchmaker/swing/nb-configuration.xml61
-rw-r--r--watchmaker/swing/pom.xml63
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/swing/ConfigurableLineBorder.java116
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/swing/SpringUtilities.java121
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/swing/SwingBackgroundTask.java147
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/swing/package-info.java21
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java86
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/EvolutionControl.java42
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java97
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ObjectSwingRenderer.java46
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java165
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java170
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java143
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java78
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java98
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java244
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java101
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java122
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/JVMView.java151
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java228
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java165
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/package-info.java20
-rw-r--r--watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/package-info.java23
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java100
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/AbortControlTest.java41
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/NumericParameterControlTest.java75
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ObjectSwingRendererTest.java38
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ProbabilityParameterControlTest.java163
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SelectionStrategyControlTest.java105
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SwingConsoleTest.java67
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitorTest.java107
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java113
-rw-r--r--watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java119
-rw-r--r--watchmaker/swing/swing.iml31
335 files changed, 31198 insertions, 0 deletions
diff --git a/watchmaker/Android.mk b/watchmaker/Android.mk
new file mode 100644
index 0000000..f83b49e
--- /dev/null
+++ b/watchmaker/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2013 The Android Open Source Project
+#
+# 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+#
+# Watchmaker
+#
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, framework/src/java/main)
+
+LOCAL_MODULE := watchmaker-jack
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_JAVA_LIBRARIES := \
+ maths-jack \
+ guava-jack
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
diff --git a/watchmaker/CHANGELOG.txt b/watchmaker/CHANGELOG.txt
new file mode 100644
index 0000000..af02f5f
--- /dev/null
+++ b/watchmaker/CHANGELOG.txt
@@ -0,0 +1,431 @@
+Changes in version 0.7.2
+------------------------
+
+* Fix for StatusBar timer being reset at each epoch in island evolution
+ (ISSUE#19).
+
+* Added RandomMigration as an alternative strategy for island evolution.
+
+
+Changes in version 0.7.1
+------------------------
+
+* Added EvolutionStrategyEngine to provide both (mu+lambda) and (mu,lambda)
+ evolution strategies.
+
+* Use final version of Google Collections 1.0.
+
+* Tweaked concurrency settings in IslandEvolution (no need for more than one
+ thread per island).
+
+* Fixed look-and-feel issues in example applets (make sure all components are
+ created after the look-and-feel is set.
+
+* Reverted 0.7.0 change to generation numbering in AbstractEvolutionEngine.
+ Numbering is correct if the initial population is considered to be the first
+ generation.
+
+
+Changes in version 0.7.0
+------------------------
+
+* Refactored internals to make it easier for the framework to provide different
+ evolutionary algorithm variants. Different types of evolutionary algorithm
+ are supported by different sub-classes of AbstractEvolutionEngine, with
+ different implementations of the nextEvolutionStep method. The main
+ EvolutionEngine implementation is now called GenerationalEvolutionEngine.
+
+* Concurrency control is no longer supported through inheritance. Removed
+ ConcurrentEvolutionEngine and SequentialEvolutionEngine and added the
+ setSingleThreaded method to AbstractEvolutionEngine.
+
+* Added first-class support for steady-state evolution in the form of the new
+ SteadyStateEvolutionEngine.
+
+* Added SigmaScaling selection strategy.
+
+* Added ListInversion evolutionary operator. Randomly reverses subsections of
+ lists.
+
+* Added variable probability constructor to ListOrderCrossover.
+
+* Introduced support for island model evolution.
+
+* Added islands view to the evolution monitor so that the state of individual
+ island populations can be tracked.
+
+* Fixed generation numbering in AbstractEvolutionEngine. It was supposed to be
+ zero-based but was starting at 1, which would cause the GenerationCount
+ termination condition to trigger prematurely.
+
+* Ugraded to Uncommons Maths 1.2.1 to take advantage of optimisations.
+
+* Optimised the implementation of BitStringCrossover to take advantage of
+ faster bit swapping operation in Uncommons Maths 1.2.1.
+
+* Converted BitStringMutation to use a more efficient approach. The
+ probability parameter is now for controlling whether an individual candidate
+ gets mutated, not each separate bit as was the case previously (existing
+ programs should now use higher probabilities accordingly). How many bits get
+ flipped is now controlled via a secondary parameter.
+
+* Upgraded to Google Collections 1.0 RC5.
+
+* Introduced Swing renderer for example GP trees.
+
+
+Changes in version 0.6.2
+------------------------
+
+* Fix for thread leak in ConcurrentEvolutionEngine (ISSUE#16). Added a
+ finalizer to shutdown the thread pool in FitnessEvaluationWorker so that the
+ active threads do not inhibit garbage collection.
+
+* Made the FitnessEvaluationWorker in ConcurrentEvolutionEngine static so that
+ it can be shared by all instances avoiding the unnecessary creation of new
+ thread pools (ISSUE#16).
+
+* Made fitness evaluation threads daemons again (as in 0.6.0). Only standalone
+ (i.e. Terracotta) FitnessEvaluationWorkers now use non-daemon threads. This
+ fixes the problem of the JVM not exiting when the program is finished
+ (ISSUE#17).
+
+* Better thread naming for fitness evaluation worker threads.
+
+
+Changes in version 0.6.1
+------------------------
+
+* Added getSatisfiedTerminationConditions() method to the EvolutionEngine
+ interface. This makes it easier to determine which TerminationCondition
+ caused the evolution to stop (ISSUE#13).
+
+* Improvements to the genetic programming example application.
+
+* Made StatusBar component public so that it can be used independently of the
+ Evolution Monitor.
+
+* Converted applet-based examples so that they can also run as applications in
+ JFrames.
+
+* Introduced Launcher class so that examples can be run easily from the command
+ line.
+
+* Changed the Mona Lisa example to accept the URL of an alternate image when
+ run from the command line.
+
+* Moved NullFitnessEvaluator from interactive package to framework package and
+ made it package scope. This class is an implementation detail and should not
+ be exposed by the API.
+
+* Refactored the ConcurrentEvolutionEngine by moving the code that actually
+ executes FitnessEvaluationTasks into a new class (FitnessEvaluationWorker).
+ This modified design makes it easy to distribute the fitness evaluations
+ using Terracotta (http://www.terracotta.org).
+
+* Documented the restriction that fitness scores are not allowed to be
+ negative. Made sure that the framework throws an appropriate, informative
+ exception if a negative fitness is encountered (ISSUE#15).
+
+* Documented potential pitfall with using EvolutionObservers to update Swing
+ GUIs (ISSUE#14).
+
+
+Changes in version 0.6.0
+------------------------
+
+* Added the option to view only the most recent 200 generations on the
+ population fitness graph of the evolution monitor.
+
+* Fixed a bug that prevented the population fitness view from being reset
+ between runs.
+
+* Added more information to the evolution monitor status bar.
+
+* Added a boolean parameter to the evolution monitor showInFrame method so that
+ you can specify whether closing the frame should terminate the program.
+
+* Enhancements to the Mona Lisa example application (it's now an applet).
+
+* Added methods to the EvolutionEngine interface to allow the entire population
+ to be returned from an evolutionary algorithm rather than just the fittest
+ candidate.
+
+* Introduced CachingFitnessEvaluator, a decorator for standard fitness
+ functions that caches the results of its calculations. If the evaluator is
+ invoked twice for the same candidate, the cached value is returned by the
+ second invocation thus avoiding the expense of recalculating the fitness
+ score. This is a useful optimisation in scenarios where some canidates
+ survive from generation to generation unmodified (elitism is one example of
+ this).
+
+* Introduced dependency on Google Collections (to support the
+ CachingFitnessEvaluator).
+
+* Fixed ProbabilityParameterControl so that it allows the slider to move all
+ the way to the maximum permitted value (ISSUE#10).
+
+* Moved classes from Uncommons Utils module into the main framework module and
+ removed the util module.
+
+* Added SelectionStrategyControl to the Swing module. This control enables the
+ user to change the selection strategy even while the evolution is running.
+
+* Upgraded to Uncommons Maths 1.2, which has its own Probability type, so we
+ now use that and the Probability class in the framework package has been
+ removed.
+
+* Removed call to System.out.println in ConcurrentEvolutionEngine (ISSUE#12).
+
+* Included source code in the release distribution in the form of source JARs
+ for the framework and swing modules (source code for the examples module is
+ already included) (ISSUE#11).
+
+* Modified cross-over implementation constructors to accept a number generator
+ for the probability parameter. This allows variable probabilities to be
+ used.
+
+
+Changes in version 0.5.1
+------------------------
+
+* Simplified generics for EvolutionaryOperators. It's now slightly less
+ flexible but the most common scenarios are less cumbersome since there are
+ no 'super' wildcards to deal with when constructing pipelines and no need to
+ deal with sub-types when implementing your own operators.
+
+* Added generic type parameters to ListCrossover, ListOrderCrossover and
+ ListOrderMutation to be compatible with the above change.
+
+* Added evolutionary art example appliction inspired by Roger Alsing's
+ evolution of the Mona Lisa, see
+ http://rogeralsing.com/2008/12/07/genetic-programming-evolution-of-mona-lisa/
+
+* Added Replacement evolutionary operator that randomly replaces candidates
+ with independent, newly-generated random candidates. Useful for introducing
+ new genetic material into stagnating populations.
+
+* Improved ListCrossover operator so that it can work with parents of variable
+ lengths.
+
+* Added higher-order evolutionary operator, ListOperator that converts an
+ operator of a given type into a operator that works with lists of that type.
+
+* Enhancements to the Evolution Monitor component.
+
+
+Changes in version 0.5.0
+------------------------
+
+* Added wildcard to generic parameter of Console select method.
+
+* Added check to ensure that there is always at least one termination condition
+ specified.
+
+* Improved reflection in RendererAdapter and InteractiveSelection. Previously
+ they tried to access classes that may not have been visible to them.
+
+* Introduced tree-based genetic programming example application.
+
+* Added Probability class. This immutatble value type encapsulates a
+ probability value between zero and one. The class enforces the 0..1 bounds
+ and provides convenient methods for working with probabilities. Using the
+ Probability type is an improvement over the previous approach of using double
+ values since there is now no need to duplicate bounds-checking and other
+ logic throughout the code.
+
+* Introduced new Swing component (ProbabilityParameterControl) for manipulating
+ Probability parameters from a GUI.
+
+* Javadoc improvements.
+
+* Relaxed generic constraints on EvolutionObservers (generic type can now be
+ less specific than the EvolutionEngine type). Added wildcard to
+ populationUpdate method of EvolutionObserver to support this.
+
+* Renamed StandaloneEvolutionEngine to ConcurrentEvolutionEngine.
+
+* Introduced SequentialEvolutionEngine, which performs all work synchronously
+ on the request thread, making it suitable for use in restricted/managed
+ environments that do not permit direct control over threading.
+
+* Use only integer labels for the generations axis on the Evolution Monitor's
+ fitness view.
+
+
+Changes in version 0.4.3
+------------------------
+
+* Introduced Utilities module.
+
+* Extracted Uncommons Maths module into separate project
+ (see https://uncommons-maths.dev.java.net).
+
+* Added new constructor to BitString to simplify the creation of random bit
+ strings.
+
+* Improved algorithm for countSetBits() in BitString. New version is
+ significantly faster (10-15 times faster).
+
+* Moved BitString class from Framework module into Uncommons Maths.
+
+* Added new termination condition to detect when the evolution has stagnated
+ (i.e. the fitness has not improved for a certain number of generations).
+
+* Moved the the Swing-specific classes from the framework module into what was
+ the Uncommons GUI module, which is now the Watchmaker Swing module.
+
+* Introduced experimental Evolution Monitor Swing component.
+
+
+Changes in version 0.4.2
+------------------------
+
+* Added a second parameter to the FitnessEvaluator interface to enable fitness
+ calculations to take into account an individual's environment (the remainder
+ of the population) when assigning a score.
+
+* Minor tweak to improve the distribution of fitness evaluations between
+ threads on multi-processor machines.
+
+* Fix for ISSUE#3 (bad Throwable handling in InteractiveSelection and
+ RendererAdapter)
+
+* Made AbstractEvolutionEngine interruptible.
+
+* Added new constructor to StandaloneEvolutionEngine to allow users to provide
+ a custom ThreadFactory.
+
+* Fix for ISSUE#4 (unreliable final result with non-natural fitness function).
+
+
+Changes in version 0.4.1
+------------------------
+
+* Added new constructors to NumberGenerator implementations to enable the
+ parameters to be dynamic.
+
+* Several more unit tests for significantly improved coverage. Fixed 2 minor
+ bugs detected by these new tests.
+
+* Added some GUI control components to assist in building Swing GUIs for
+ evolutionary programs.
+
+* Moved the SwingConsole class into the new Swing components package
+ (org.uncommons.watchmaker.swing) so that the core framework does not contain
+ any dependencies on a particular presentation layer.
+
+* Added evolutionary Sudoku solver example application.
+
+* Included source code for the examples in the release archives.
+
+
+Changes in version 0.4.0
+------------------------
+
+* Introduced framework support for interactive evolutionary algorithms.
+
+* New example application based on Richard Dawkins' biomorph experiment.
+ Demonstrates the new interactive features of the framework.
+
+* Added new, extremely fast RNG - a Java port of Tony Pasqualoni's cellular
+ automaton RNG (http://home.southernct.edu/~pasqualonia1/ca/report.html).
+
+* Added ordered cross-over evolutionary operator for lists.
+
+* Updated Travelling Salesman applet to optionally use cross-over as well as
+ mutation.
+
+* Introduced TerminationCondition interface and useful default implementations.
+ Changed evolve methods in EvolutionEngine to take one or more conditions
+ instead of explicitly specifying parameters such as number of generations,
+ target fitness and timeout.
+
+
+Changes in version 0.3.0
+------------------------
+
+* Removed java.util.BitSet operators and candidate factory. BitSets are
+ somewhat lacking as a generic bit string for genetic algorithms.
+
+* Introduced a new general-purpose BitString type with an associated candidate
+ factory plus mutation and cross-over operators.
+
+* Added probabilities to cross-over implementations so that parents may
+ sometimes pass through the operator unaltered. A similar effect could have
+ been achieved previously by combining the cross-over operator with a
+ SplitEvolution operator that processed some of the individuals with an
+ IdentityOperator, but this is more straightforward.
+
+* Modified cross-over operators so that the cross-over index is always
+ non-zero. This means all cross-overs are meaningful. With a zero index,
+ each parent is split before the first position, making the cross-over
+ effectively a no-op. This change was required in order to honour the new
+ cross-over probability, and is also sensible in its own right.
+
+* Removed varargs constructor from EvolutionPipeline since it was impossible
+ to invoke without generating a compiler warning about generic array
+ creation.
+
+* Fixed very slightly skewed probability in StringMutation.
+
+* Fixed bug in RouletteWheelSelection.
+
+* Improved Travelling Salesman applet to allow different selection strategies
+ to be applied.
+
+* More unit test cases and improved API documentation.
+
+
+Changes in version 0.2.2
+------------------------
+
+* Renamed classes for generating random values from various probability
+ distributions. Now called "generators" rather than "sequences" in order to
+ avoid confusion with mathematical sequences.
+
+* Changed terminology for different fitness scoring schemes to be consistent
+ with the literature (what was called 'normalised fitness' is now called
+ 'natural fitness').
+
+* Refactored fitness-proportionate selection strategies.
+
+* Fixed tournament selection bug.
+
+* Moved the compound evolutionary operators (SplitEvolution and
+ EvolutionPipeline) introduced in the previous release into the operators
+ package.
+
+* Introduced an IdentityOperator for use with SplitEvolution to enable some
+ candidates to be preserved unchanged (this is different from elitism
+ because it does not depend on fitness).
+
+
+Changes in version 0.2.1
+------------------------
+
+* Reworked evolutionary operators to allow more flexibility. Specifically,
+ evolution can now be split into separate streams, which enables common
+ genetic programming techniques to be used.
+
+* Simplified the use of generics within the API.
+
+* Fixed bug with pre-seeding populations.
+
+
+Changes in version 0.2
+----------------------
+
+* Added support for concurrent fitness evaluations to take advantage of
+ multi-core and multi-processor machines.
+
+* Modified the way elitism is configured (the number of candidates to preserve
+ is now specifed as an argument to the evolve method of the EvolutionEngine).
+
+* Converted the Travelling Salesman example program into an applet that allows
+ parameters to be tweaked and performance to be compared to a brute force
+ implementation.
+
+* Fixed RNG seeding to work in an untrusted applet environment (previously
+ SecurityExceptions were thrown when attempting to access resources for
+ seeding).
diff --git a/watchmaker/LICENCE.txt b/watchmaker/LICENCE.txt
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/watchmaker/LICENCE.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/watchmaker/NOTICE.txt b/watchmaker/NOTICE.txt
new file mode 100644
index 0000000..1967b94
--- /dev/null
+++ b/watchmaker/NOTICE.txt
@@ -0,0 +1,43 @@
+_______________________________________________________________________________
+
+ The Watchmaker Framework for Evolutionary Computation - Version 0.7.0
+ (http://watchmaker.uncommons.org)
+ Copyright 2006-2009 Daniel W. Dyer (http://www.dandyer.co.uk)
+_______________________________________________________________________________
+
+Acknowledgements:
+-----------------
+The Swing GUI library of the Watchmaker Framework depends on David Gilbert's
+JFreeChart library (http://www.jfree.org/jfreechart/). JFreeChart and the
+JCommon library are licensed under the terms of the GNU Lesser General Public
+License (a copy of this licence can be found in the jfreechart-LICENCE.txt file
+included with this distribution).
+
+The Watchmaker Framework depends on the Google Collections library
+(http://code.google.com/p/google-collections/), which itself is released under
+the same licence as the Watchmaker Framework (the Apache Licence Version 2.0).
+
+The Watchmaker Framework depends heavily on the Uncommons Maths library.
+Uncommons Maths is also licensed under the terms of the Apache Licence Version
+2.0. The NOTICE.txt file from the Uncommons Maths distribution is reproduced
+below.
+
+_______________________________________________________________________________
+
+ Uncommons Maths (http://maths.uncommons.org)
+ Copyright 2006-2009 Daniel W. Dyer (http://www.dandyer.co.uk)
+_______________________________________________________________________________
+
+Acknowledgements:
+-----------------
+This software includes a Java port of the cellular automaton pseudorandom
+number generator developed by Tony Pasqualoni
+(http://home.southernct.edu/~pasqualonia1/ca/report.html).
+
+This software includes a Java port of the Mersenne Twister pseudorandom
+number generator developed by Makoto Matsumoto and Takuji Nishimura
+(http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html).
+
+This software also includes modified versions of the PermutationGenerator and
+CombinationGenerator Java classes written by Michael Gilleland
+(http://www.mgilleland.com/).
diff --git a/watchmaker/README.android b/watchmaker/README.android
new file mode 100644
index 0000000..9c3738a
--- /dev/null
+++ b/watchmaker/README.android
@@ -0,0 +1,7 @@
+URL: http://watchmaker.uncommons.org/
+Version: 0.7.1
+License: Apache 2.O
+Description: "Evolutionary Computation Framework."
+
+this code was taken from 9e8109d7fd84b7350a93d160690d91a9847b26a1
+(git clone git://github.com/dwdyer/watchmaker)
diff --git a/watchmaker/README.txt b/watchmaker/README.txt
new file mode 100644
index 0000000..3ddaeb2
--- /dev/null
+++ b/watchmaker/README.txt
@@ -0,0 +1,46 @@
+_______________________________________________________________________________
+
+ The Watchmaker Framework for Evolutionary Computation - Version 0.7.1
+ (http://watchmaker.uncommons.org)
+ Copyright 2006-2010 Daniel W. Dyer (http://www.dandyer.co.uk)
+_______________________________________________________________________________
+
+
+1). Getting Started
+-------------------
+
+Please refer to the included LICENCE.txt and NOTICE.txt files for terms of use.
+
+ User Manual: http://watchmaker.uncommons.org/manual/index.html
+ API Reference: http://watchmaker.uncommons.org/api/index.html
+
+Source code for several example programs is included in the distribution.
+
+The examples can be run with the following command:
+
+ java -jar watchmaker-examples-0.7.1.jar
+
+This will list the names of available example applications. Then just run the
+command again with one of those names as an argument.
+
+
+2). Library Dependencies
+------------------------
+
+The following bundled JAR files are required by all programs that use the
+Watchmaker Framework:
+
+ watchmaker-framework-0.7.1.jar (Apache Licence 2.0)
+ uncommons-maths-1.2.1.jar (Apache Licence 2.0)
+ google-collect-1.0.jar (Apache Licence 2.0)
+
+These additional JAR files are required to use the Watchmaker Framework Swing
+components:
+
+ watchmaker-swing-0.7.1.jar (Apache Licence 2.0)
+ jfreechart-1.0.13.jar (GNU LGPL 2.1)
+ jcommon-1.0.16.jar (GNU LGPL 2.1)
+
+Example applications are included in the watchmaker-examples-0.7.1.jar file.
+This file is not required by other applications that use the Watchmaker
+Framework.
diff --git a/watchmaker/book/book.iml b/watchmaker/book/book.iml
new file mode 100644
index 0000000..d5c0743
--- /dev/null
+++ b/watchmaker/book/book.iml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
+
diff --git a/watchmaker/book/src/resources/antenna.jpg b/watchmaker/book/src/resources/antenna.jpg
new file mode 100644
index 0000000..d706426
--- /dev/null
+++ b/watchmaker/book/src/resources/antenna.jpg
Binary files differ
diff --git a/watchmaker/book/src/resources/docbook.css b/watchmaker/book/src/resources/docbook.css
new file mode 100644
index 0000000..018291b
--- /dev/null
+++ b/watchmaker/book/src/resources/docbook.css
@@ -0,0 +1,35 @@
+a {text-decoration: none;
+ color: #006699;}
+a:visited {color: #003366;}
+body {line-height: 1.8em;
+ font-size: 62.5%;
+ width: 960px;
+ margin-left: auto;
+ margin-right: auto;
+ font-family: Palatino Linotype, Palatino, serif;
+ background-color: #ffffff;
+ color: #444444;}
+dl {margin: 0;}
+h1, h2, h3 {font-weight: bold;
+ font-variant: small-caps;}
+h1 {font-size: 3em;
+ margin-bottom: .6em;}
+h2 {font-size: 2.4em;
+ margin-bottom: .75em;}
+h3 {font-size: 1.8em;
+ margin-bottom: 1em;}
+p, ul {font-size: 1.4em;
+ margin: 1.286em 0;}
+dl, table {font-size: 1.4em;}
+dl dl {font-size: 1em;}
+pre {font-family: Monaco, Courier New, monospace; font-size: 1.2em; margin-bottom: 1.5em;}
+strong {color: #000000;}
+
+h2.subtitle {font-variant: normal;}
+h3.author {font-variant: normal; margin-top: 2em; margin-bottom: 0;}
+.email {font-size: 1.2em;}
+.informalfigure {text-align: center;}
+.mediaobject {display: inline-block;}
+.caption p {margin: 0; text-align: right; font-size: small;}
+
+.navfooter table tr td {width: 33.33%;} \ No newline at end of file
diff --git a/watchmaker/book/src/resources/travelling_salesman_problem.png b/watchmaker/book/src/resources/travelling_salesman_problem.png
new file mode 100644
index 0000000..d6017c9
--- /dev/null
+++ b/watchmaker/book/src/resources/travelling_salesman_problem.png
Binary files differ
diff --git a/watchmaker/book/src/xml/book.xml b/watchmaker/book/src/xml/book.xml
new file mode 100644
index 0000000..24f0a09
--- /dev/null
+++ b/watchmaker/book/src/xml/book.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<book xmlns="http://docbook.org/ns/docbook"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <info>
+ <title>Evolutionary Computation in Java</title>
+ <author>
+ <personname>
+ <firstname>Daniel</firstname>
+ <surname>Dyer</surname>
+ <othername>W.</othername>
+ </personname>
+ <email>dan@uncommons.org</email>
+ <uri>http://www.dandyer.co.uk</uri>
+ </author>
+ <copyright>
+ <year>2008</year>
+ <year>2009</year>
+ <year>2010</year>
+ </copyright>
+ <keywordset>
+ <keyword>algorithms</keyword>
+ <keyword>evolution</keyword>
+ <keyword>evolutionary algorithms</keyword>
+ <keyword>evolutionary computation</keyword>
+ <keyword>genetic algorithms</keyword>
+ <keyword>Java</keyword>
+ <keyword>programming</keyword>
+ <keyword>software</keyword>
+ </keywordset>
+ </info>
+
+ <xi:include href="preface.xml" />
+
+ <!-- Chapters -->
+ <xi:include href="evolution.xml" />
+ <xi:include href="watchmaker.xml" />
+ <xi:include href="salesman.xml" />
+ <xi:include href="selection.xml" />
+ <xi:include href="sudoku.xml" />
+ <xi:include href="islands.xml" />
+ <xi:include href="interactive.xml" />
+ <xi:include href="steadystate.xml" />
+ <xi:include href="geneticprogramming.xml" />
+ <xi:include href="classifiers.xml" />
+ <xi:include href="multiobjective.xml" />
+
+ <!-- Appendices -->
+ <xi:include href="performance.xml" />
+ <xi:include href="gui.xml" />
+ <xi:include href="distributed.xml" />
+ <xi:include href="furtherreading.xml" />
+
+ <index />
+
+</book>
diff --git a/watchmaker/book/src/xml/classifiers.xml b/watchmaker/book/src/xml/classifiers.xml
new file mode 100644
index 0000000..79a433a
--- /dev/null
+++ b/watchmaker/book/src/xml/classifiers.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Learning Classifier Systems</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/distributed.xml b/watchmaker/book/src/xml/distributed.xml
new file mode 100644
index 0000000..08c54c0
--- /dev/null
+++ b/watchmaker/book/src/xml/distributed.xml
@@ -0,0 +1,13 @@
+<appendix xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Distributed Evolutionary Algorithms</title>
+ <section>
+ <title>Running Watchmaker Programs on Hadoop with Apache Mahout</title>
+ <para>TODO</para>
+ </section>
+ <section>
+ <title>Clustering Watchmaker Programs with Terracotta</title>
+ <para>TODO</para>
+ </section>
+</appendix>
diff --git a/watchmaker/book/src/xml/evolution.xml b/watchmaker/book/src/xml/evolution.xml
new file mode 100644
index 0000000..0c134aa
--- /dev/null
+++ b/watchmaker/book/src/xml/evolution.xml
@@ -0,0 +1,424 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>The Power of Evolution</title>
+ <para>
+ Software is normally developed in a very precise, deterministic way. The behaviour of a
+ computer is governed by strict logical rules. A computer invariably does exactly what
+ it is told to do.
+ </para>
+ <para>
+ When writing a program to solve a particular problem, software developers will identify
+ the necessary sub-tasks that the program must perform. Algorithms are chosen and
+ implemented for each task. The completed program becomes a detailed specification of
+ exactly how to get from A to B. Every aspect is carefully designed by its developers
+ who must understand how the various components interact to deliver the program's
+ functionality.
+ </para>
+ <para>
+ This prescriptive approach to solving problems with computers has served us well and is
+ responsible for most of the software applications that we use today. However, it is not
+ without limitations.
+ Solutions to problems are constrained by the intuition, knowledge and prejudices
+ of those who develop the software.
+ <emphasis>The programmers have to know exactly how to solve the problem.</emphasis>
+ </para>
+ <para>
+ Another characteristic of the prescriptive approach that is sometimes problematic is
+ that it is best suited to finding exact answers. Not all problems have exact solutions,
+ and some that do may be too computationally expensive to solve. Sometimes it is
+ more useful to be able to find an approximate answer quickly than to waste time searching
+ for a better solution.
+ </para>
+ <section>
+ <title>What are Evolutionary Algorithms?</title>
+ <indexterm><primary>evolutionary algorithm</primary></indexterm>
+ <indexterm><primary>Darwin, Charles</primary></indexterm>
+ <para>
+ Evolutionary algorithms (EAs) are inspired by the biological model of evolution and
+ natural selection first proposed by Charles Darwin in 1859.
+ In the natural world, evolution helps species adapt to their environments.
+ Environmental factors that influence the survival prospects of an organism
+ include climate, availability of food and the dangers of predators.
+ </para>
+ <indexterm><primary>natural selection</primary></indexterm>
+ <para>
+ Species change over the course of many generations.
+ Mutations occur randomly. Some mutations will be advantageous, but many will be
+ useless or detrimental. Progress comes from the feedback provided by non-random
+ natural selection.
+ For example, organisms that can survive for long periods without water will be
+ more likely to thrive in dry conditions than those that can't.
+ Likewise, animals that can run fast will be more successful at evading predators
+ than their slower rivals.
+ If a random genetic modification helps an organism to survive and to reproduce,
+ that modification will itself survive and spread throughout the population, via
+ the organism's offspring.
+ </para>
+ <para>
+ Evolutionary algorithms are based on a simplified model of this biological evolution.
+ To solve a particular problem we create an environment in which potential
+ solutions can evolve. The environment is shaped by the parameters of the problem
+ and encourages the evolution of good solutions.
+ </para>
+ <indexterm><primary>evolutionary computation</primary></indexterm>
+ <para>
+ The field of Evolutionary Computation encompasses several types of evolutionary
+ algorithm. These include <emphasis>Genetic Algorithms</emphasis> (GAs),
+ <emphasis>Evolution Strategies</emphasis>, <emphasis>Genetic Programming</emphasis>
+ (GP), <emphasis>Evolutionary Programming</emphasis> and <emphasis>Learning
+ Classifier Systems</emphasis>.
+ </para>
+ <indexterm><primary>genetic algorithms</primary></indexterm>
+ <para>
+ The most common type of evolutionary algorithm is the generational genetic
+ algorithm. We'll cover other EA variants in later chapters but, for now,
+ all of the evolutionary algorithms that we meet will be some kind of generational
+ GA.
+ </para>
+ <indexterm><primary>fitness function</primary></indexterm>
+ <indexterm><primary>population</primary></indexterm>
+ <para>
+ The basic outline of a generational GA is as follows (most other EA variants are
+ broadly similar).
+ A <emphasis>population</emphasis> of candidate solutions is iteratively evolved
+ over many <emphasis>generations</emphasis>. Mimicking the concept of
+ natural selection in biology, the survival of candidates (or their offspring)
+ from generation to generation in an EA is governed by a <emphasis>fitness
+ function</emphasis> that evaluates each candidate according to how close it is to
+ the desired outcome, and a <emphasis>selection strategy</emphasis> that favours
+ the better solutions.
+ Over time, the quality of the solutions in the population should improve.
+ If the program is successful, we can terminate the evolution once it has found
+ a solution that is good enough.
+ </para>
+ <section>
+ <title>An Example</title>
+ <para>
+ Now that we have introduced the basic concepts and terminology, I will attempt
+ to illustrate by way of an example. Suppose that we want to use evolution to generate
+ a particular character string, for example "HELLO WORLD". This is a contrived example
+ in as much as it assumes that we don't know how to create such a string and that
+ evolution is the best approach available to us. However, bear with me as this simple
+ example is useful for demonstrating exactly how the evolutionary approach works.
+ </para>
+ <para>
+ Each candidate solution in our population will be a string. We'll use a fixed-length
+ representation so that each string is 11 characters long. Each character in a string
+ will be one of the 27 valid characters (the upper case letters 'A' to 'Z' plus the space
+ character).
+ </para>
+ <para>
+ For the fitness function we'll use the simple approach of assigning a candidate
+ solution one point for each position in the string that has the correct character.
+ For the string "HELLO WORLD" this gives a maximum possible fitness score of 11 (the
+ length of the string).
+ </para>
+ <para>
+ The first task for the evolutionary algorithm is to randomly generate the initial
+ population. We can use any size population that we choose.
+ Typical EA population sizes can vary from tens to thousands of individuals.
+ For this example we will use a population size of 10.
+ After the initialisation of the population we might have the following candidates
+ (fitness scores in brackets):
+ <informalexample>
+ <programlisting>
+ 1. GERZUNFXCEN (1)
+ 2. HSFDAHDMUYZ (1)
+ 3. UQ IGARHGJN (0)
+ 4. ZASIB WSUVP (2)
+ 5. XIXROIUAZBH (1)
+ 6. VDLGCWMBFYA (1)
+ 7. SY YUHYRSEE (0)
+ 8. EUSVBIVFHFK (0)
+ 9. HHENRFZAMZH (1)
+ 10. UJBBDFZPLCN (0)
+ </programlisting>
+ </informalexample>
+ </para>
+ <para>
+ None of these candidate solutions is particularly good. The best (number 4) has just two
+ characters out of eleven that match the target string (the space character and the 'W').
+ </para>
+ <para>
+ The next step is to select candidates based on their fitness and use them to create
+ a new generation. One technique for favouring the selection of fitter candidates over
+ weaker candidates is to assign each candidate a selection probability proportionate to
+ its fitness.
+ </para>
+ <indexterm><primary>fitness-proportionate selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>fitness-proportionate</secondary></indexterm>
+ <para>
+ If we use <emphasis>fitness-proportionate selection</emphasis>, none of the candidates
+ with zero fitness will be selected and the candidate with a fitness of 2 is twice as likely
+ to be selected as any of the candidates with a fitness of 1. For the next step we need to
+ select 10 parents, so it is obvious that some of the fit candidates are going to be selected
+ multiple times.
+ </para>
+ <indexterm><primary>cross-over</primary></indexterm>
+ <para>
+ Now that we have some parents, we can breed the next generation. We do this via a process
+ called <emphasis>cross-over</emphasis>, which is analogous to sexual reproduction in biology.
+ For each pair of parents, a cross-over point is selected randomly. Assuming that the first
+ two randomly selected parents are numbers 2 and 4, if the cross-over occurs after the
+ first four characters, we will get the following offspring:
+ <informalexample>
+ <programlisting>
+ Parent 1: <emphasis role="bold">HSFDAHDMUYZ</emphasis>
+ Parent 2: ZASIB WSUVP
+
+ Offspring 1: <emphasis role="bold">HSFD</emphasis>B WSUVP
+ Offspring 2: ZASI<emphasis role="bold">AHDMUYZ</emphasis>
+ </programlisting>
+ </informalexample>
+ </para>
+ <indexterm><primary>mutation</primary></indexterm>
+ <para>
+ This recombination has given us two new candidates for the next generation, one of which is
+ better than either of the parents (offspring 1 has a fitness score of 3). This shows how
+ cross-over can lead towards better solutions. However, looking at the initial population as
+ a whole, we can see that no combination of cross-overs will ever result in a candidate with
+ a fitness higher than 6. This is because, among all 10 original candidates, there are only 6
+ positions in which we have the correct character. This can be mitigated to some extent by
+ increasing the size of the population. With 100 individuals in the initial population we
+ would be much more likely to have the necessary building blocks for a perfect solution, but
+ there is no guarantee. This is where <emphasis>mutation</emphasis> comes in.
+ </para>
+ <para>
+ Mutation is implemented by modifying each character in a string according to some small
+ probability, say 0.02 or 0.05. This means that any single individual will be changed only
+ slightly by mutation, or perhaps not at all.
+ </para>
+ <para>
+ By applying mutation to each of the offspring produced by cross-over we will occasionally
+ introduce correct characters in new positions. We will also occasionally remove correct
+ characters but these bad mutations are unlikely to survive selection in the next generation,
+ so this is not a big problem. Advantageous mutations will be propagated by cross-over and
+ selection and will quickly spread throughout the population.
+ </para>
+ <para>
+ After repeating this process for dozens or perhaps even hundreds of generations we will
+ eventually converge on our desired solution.
+ </para>
+ <para>
+ This is a convoluted process for finding a string that we already knew to start with.
+ However, as we shall see in the remainder of this book, the evolutionary approach
+ generalises to deal with problems where we don't know what the best solution is and
+ therefore can't encode that knowledge in our fitness function.
+ </para>
+ <para>
+ The important point demonstrated by this example is that we can arrive at a satisfactory
+ solution without having to enumerate every possible candidate in the search space.
+ Even for this trivial example, a brute force search would involve generating and
+ checking approximately 5.6 quadrillion strings.
+ </para>
+ </section>
+ <section>
+ <title>The Outline of an Evolutionary Algorithm</title>
+ <procedure>
+ <step>
+ <title>Genesis</title>
+ <para>
+ Create an initial set (population) of <literal>n</literal> candidate solutions.
+ This may be done entirely randomly or the population may be seeded with some
+ hand-picked candidates.
+ </para>
+ </step>
+ <step>
+ <title>Evaluation</title>
+ <para>
+ Evaluate each member of the population using some fitness function.
+ </para>
+ </step>
+ <step>
+ <title>Survival of the Fittest</title>
+ <indexterm><primary>selection</primary></indexterm>
+ <para>
+ Select a number of members of the evaluated population, favouring those
+ with higher fitness scores. These will be the parents of the next generation.
+ </para>
+ </step>
+ <step>
+ <title>Evolution</title>
+ <indexterm><primary>cross-over</primary></indexterm>
+ <indexterm><primary>mutation</primary></indexterm>
+ <para>
+ Generate a new population of offspring by randomly altering and/or combining
+ elements of the parent candidates. The evolution is performed by one or more
+ <emphasis>evolutionary operators</emphasis>. The most common operators are
+ cross-over and mutation.
+ Cross-over takes two parents, cuts them each into two or more pieces and recombines
+ the pieces to create two new offspring. Mutation copies an individual but with
+ small, random modifications (such as flipping a bit from zero to one).
+ </para>
+ </step>
+ <step>
+ <title>Iteration</title>
+ <para>
+ Repeat steps 2-4 until a satisfactory solution is found or some other termination
+ condition is met (such as the number of generations or elapsed time).
+ </para>
+ </step>
+ </procedure>
+ </section>
+ </section>
+ <section>
+ <title>When are Evolutionary Algorithms Useful?</title>
+ <para>
+ Evolutionary algorithms are typically used to provide good approximate
+ solutions to problems that cannot be solved easily using other techniques.
+ Many optimisation problems fall into this category. It may be too
+ computationally-intensive to find an exact solution but sometimes a near-optimal
+ solution is sufficient. In these situations evolutionary techniques can be
+ effective. Due to their random nature, evolutionary algorithms are never guaranteed
+ to find an optimal solution for any problem, but they will often find a good solution
+ if one exists.
+ </para>
+ <para>
+ One example of this kind of optimisation problem is the challenge of timetabling.
+ Schools and universities must arrange room and staff allocations to suit the needs
+ of their curriculum. There are several constraints that must be satisfied.
+ A member of staff can only be in one place at a time, they can only teach classes
+ that are in their area of expertise, rooms cannot host lessons if they are already
+ occupied, and classes must not clash with other classes taken by the same students.
+ This is a combinatorial problem and known to be NP-Hard.
+ It is not feasible to exhaustively search for the optimal timetable due to the huge
+ amount of computation involved. Instead, heuristics must be used.
+ Genetic algorithms have proven to be a successful way of generating satisfactory
+ solutions to many scheduling problems.
+ </para>
+ <para>
+ Evolutionary algorithms can also be used to tackle problems that humans don't really
+ know how to solve.
+ An EA, free of any human preconceptions or biases, can generate surprising solutions
+ that are comparable to, or better than, the best human-generated efforts.
+ It is merely necessary that we can recognise a good solution if
+ it were presented to us, even if we don't know <emphasis>how</emphasis> to create a
+ good solution.
+ In other words, we need to be able to formulate an effective fitness function.
+ </para>
+ <para>
+ Engineers working for NASA know a lot about physics. They know exactly which
+ characteristics make for a good communications antenna. But the process
+ of designing an antenna so that it has the necessary properties is hard. Even
+ though the engineers know what is required from the final antenna, they may not know
+ how to design the antenna so that it satisfies those requirements.
+ </para>
+ <para>
+ NASA's Evolvable Systems Group has used evolutionary algorithms to successfully
+ evolve antennas for use on satellites. These evolved antennas have irregular shapes
+ with no obvious symmetry (one of these antennas is pictured below).
+ It is unlikely that a human expert would have arrived at such an unconventional design.
+ Despite this, when tested these antennas proved to be extremely well adapted to their
+ purpose.
+ </para>
+ <informalfigure>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="antenna.jpg" format="JPG" width="50%" align="center"/>
+ </imageobject>
+ <caption>
+ <para>
+ <link xlink:href="http://ti.arc.nasa.gov/projects/esg/research/antenna.htm">NASA Evolvable Systems Group</link>
+ </para>
+ </caption>
+ </mediaobject>
+ </informalfigure>
+ <section>
+ <title>Pre-requisites</title>
+ <indexterm><primary>encoding</primary></indexterm>
+ <para>
+ There are two requirements that must be met before an evolutionary algorithm can
+ be used for a particular problem.
+ Firstly, we need a way to encode candidate solutions to the problem. The simplest
+ encoding, and that used by many genetic algorithms, is a bit string. Each candidate
+ is simply a sequence of zeros and ones.
+ This encoding makes cross-over and mutation very straightforward, but that does not
+ mean that you cannot use more complicated representations. In fact, we will see
+ several instances of more advanced candidate representations in later chapters.
+ As long as we can devise a scheme for evolving the candidates, there really is no
+ restriction on the types that we can use.
+ Genetic programming (GP) is a good example of this. GP evolves computer programs
+ represented as syntax trees.
+ </para>
+ <indexterm><primary>fitness function</primary></indexterm>
+ <para>
+ The second requirement for applying evolutionary algorithms is that there must be a
+ way of evaluating partial solutions to the problem - the fitness function. It is
+ not sufficient to evaluate solutions as right or wrong, the fitness score needs to
+ indicate <emphasis>how right</emphasis> or, if your glass is half empty,
+ <emphasis>how wrong</emphasis> a candidate solution is. So a function that returns
+ either 0 or 1 is useless. A function that returns a score on a scale of 1 - 100 is
+ better. We need shades of grey, not just black and white, since this is how the
+ algorithm guides the random evolution to find increasingly better solutions.
+ </para>
+ </section>
+ </section>
+ <section>
+ <title>Implementing Evolutionary Algorithms</title>
+ <para>
+ If an evolutionary algorithm is a good fit for a particular problem, there are plenty of
+ options when it comes to implementing it.
+ You may choose to use a high-level programming language for simplicity, or a low-level
+ language for performance.
+ You could write all of the code yourself from scratch, or you could reuse pre-written
+ components and libraries.
+ In this book we will necessarily be using one particular approach, but it is worth noting
+ that there are alternatives.
+ </para>
+ <section>
+ <title>Choice of Programming Language</title>
+ <para>
+ Evolutionary algorithms can be implemented in any general purpose programming language.
+ Most programmers will simply choose the language that they are most comfortable with.
+ A quick web search will return examples of evolutionary programs written in C, C++,
+ Java, C#, Python, Ruby, Perl, Lisp and several other languages.
+ </para>
+ <para>
+ Performance may be a consideration when choosing a language.
+ Almost all evolutionary algorithms are CPU-bound. For this reason, compiled languages
+ typically offer better EA performance than interpreted languages. For
+ short-lived programs the difference is unlikely to be significant, but for
+ long-running programs it could be considerable.
+ </para>
+ <indexterm><primary>Java</primary></indexterm>
+ <para>
+ If you can recall the title of this book it should come as no surprise that we will be
+ using Java for all of the example code. Java offers a good balance of performance,
+ ease-of-use and a rich standard library.
+ </para>
+ </section>
+ <section>
+ <title>Evolution Frameworks</title>
+ <indexterm><primary>frameworks</primary></indexterm>
+ <para>
+ As we saw above, the basic outline of an evolutionary algorithm is fairly
+ straightforward. It consists of a main loop that performs one generation per iteration,
+ supplemented by a few functions to perform fitness evaluation, selection and
+ mutation/cross-over. When implementing a simple EA, writing this structural code is
+ not particularly onerous. However, if you write many different evolutionary
+ programs, as we will be doing in the remainder of this book, you end up writing code
+ that is very similar over and over again.
+ </para>
+ <para>
+ A good programmer will usually want to extract and reuse this common code.
+ Once you have done this, you have the basis of an evolutionary computation framework.
+ Typically this will consist of an evolution engine that is reusable and that can
+ accept different functions to customise fitness evaluation, selection and evolutionary
+ operators.
+ </para>
+ <para>
+ An alternative to using a home-grown framework is to choose a ready-made one. There
+ are open source evolutionary computation frameworks available for most programming languages.
+ For popular languages, such as C, C++ and Java, there are dozens.
+ </para>
+ <para>
+ The advantage of a
+ ready-made framework that is used by many other programmers is that it will have been well
+ tested and should be free of significant bugs and performance problems. It may also provide
+ advanced features such as parallel and/or distributed processing.
+ </para>
+ </section>
+ </section>
+</chapter>
diff --git a/watchmaker/book/src/xml/furtherreading.xml b/watchmaker/book/src/xml/furtherreading.xml
new file mode 100644
index 0000000..c0ab4c3
--- /dev/null
+++ b/watchmaker/book/src/xml/furtherreading.xml
@@ -0,0 +1,6 @@
+<appendix xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Further Reading</title>
+ <para>TODO</para>
+</appendix>
diff --git a/watchmaker/book/src/xml/geneticprogramming.xml b/watchmaker/book/src/xml/geneticprogramming.xml
new file mode 100644
index 0000000..cb37ed6
--- /dev/null
+++ b/watchmaker/book/src/xml/geneticprogramming.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Genetic Programming</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/gui.xml b/watchmaker/book/src/xml/gui.xml
new file mode 100644
index 0000000..f339f77
--- /dev/null
+++ b/watchmaker/book/src/xml/gui.xml
@@ -0,0 +1,22 @@
+<appendix xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Building Graphical User Interfaces for Evolutionary Programs</title>
+ <subtitle>Using the Watchmaker Swing Module</subtitle>
+ <section>
+ <title>Evolution Controls</title>
+ <para>TODO</para>
+ <section>
+ <title>Number Generators</title>
+ <para>TODO</para>
+ </section>
+ </section>
+ <section>
+ <title>Updating the GUI from an EvolutionObserver</title>
+ <para>TODO</para>
+ </section>
+ <section>
+ <title>The Evolution Monitor</title>
+ <para>TODO</para>
+ </section>
+</appendix>
diff --git a/watchmaker/book/src/xml/interactive.xml b/watchmaker/book/src/xml/interactive.xml
new file mode 100644
index 0000000..fa7369a
--- /dev/null
+++ b/watchmaker/book/src/xml/interactive.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Interactive Evolutionary Algorithms</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/islands.xml b/watchmaker/book/src/xml/islands.xml
new file mode 100644
index 0000000..e7943f8
--- /dev/null
+++ b/watchmaker/book/src/xml/islands.xml
@@ -0,0 +1,175 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Island Models</title>
+ <indexterm><primary>island models</primary></indexterm>
+ <indexterm><primary>Australia</primary></indexterm>
+ <para>
+ In the natural world, populations of organisms might be separated by geography. Left to evolve in isolation
+ over millions of years, vastly different species will occur in different locations. Consider Australia,
+ an island continent protected by its seas. With little opportunity for outside organisms to
+ interfere, and few opportunities for its land-based organisms to migrate to other land masses, Australian
+ wildlife evolved to be distinctly different from that of other continents and countries. The majority of
+ Australia's plant and animal species, including 84% of its mammals, are endemic. They occur nowhere else
+ in the world.
+ </para>
+ <indexterm><primary>Darwin, Charles</primary></indexterm>
+ <indexterm><primary>Galápagos Islands</primary></indexterm>
+ <para>
+ Australia is not the only island to exhibit such levels of endemism. It was a visit to the Galápagos
+ Islands in 1835 that started Charles Darwin on the path to formulating his theory of evolution. Darwin
+ noticed the pronounced differences between the species of mocking birds and tortoises present on the
+ different islands of the archipelago and began to speculate on how such variations might have occurred.
+ </para>
+ <para>
+ In the world of evolutionary computation we can mimic this idea of having multiple isolated populations
+ evolving in parallel. Having additional populations would increase the likelihood of finding a solution that
+ is close to the global optimum. However, it is not just a question of having a larger global population.
+ A system of 10 islands each with a population of 50 individuals is not equivalent to a single island with a
+ population of 500. The reason for this is that the island system partitions the search. If one island
+ prematurely converges on a sub-optimal solution it does not affect the evolution happening on the other
+ islands; they are following their own paths. A single large population does not have this in-built
+ resilience.
+ </para>
+ <section>
+ <title>Migration</title>
+ <indexterm><primary>migration</primary></indexterm>
+ <indexterm><primary>island models</primary><secondary>migration</secondary></indexterm>
+ <para>
+ There is of course no real difference between evolving 10 completely separate islands in parallel and running
+ the same single-population evolution 10 times in a row, other than how the computing resources are utilised.
+ In practice the populations are not kept permanently isolated from each other and there are occasional
+ opportunities for individuals to migrate between islands.
+ </para>
+ <para>
+ In nature external species have been introduced to foreign ecosystems in several ways. In an ice age the waters
+ that previously separated two land masses might freeze providing a route for land animals to migrate to
+ previously unreachable places. Microorganisms and insects have often strayed beyond their usual environment by
+ hitching a ride with larger species.
+ </para>
+ <indexterm><primary>rabbits</primary></indexterm>
+ <indexterm><primary>Austin, Thomas</primary></indexterm>
+ <para>
+ The effect of introducing a foreign species to a new environment can vary. The new species might be
+ ill-adapted to its new surroundings and quickly perish. Alternatively, a lack of natural predators
+ may cause it to flourish, often to the detriment of indigenous species. One such example is the
+ introduction of rabbits to Australia. Australia was a land without rabbits until the arrival of European
+ settlers. An Englishman named Thomas Austin released 24 rabbits into the wild of Victoria in October 1859
+ with the intention of hunting them. If rabbits are famous for one thing it is for reproducing prodigiously.
+ The mild winters allowed year-round breeding and the absence of any natural rabbit predators, such as foxes,
+ allowed the Australian rabbit population to explode unchecked. Within 10 years an annual cull of two million
+ rabbits was having no noticeable effect on rabbit numbers and the habitats of some native animals were being
+ destroyed by the floppy-eared pests. Today there are hundreds of millions of rabbits in Australia, despite
+ efforts to reduce the population, and the name of Thomas Austin is widely cursed for his catastrophic lack
+ of foresight.
+ </para>
+ <para>
+ While such invasions of separate species provide a useful analogy for what can happen when we introduce migration
+ into island model evolutionary algorithms, we are specifically interested in the effects of migration involving
+ genetically different members of the same species. This is because, in our simplified model of evolution,
+ all individuals are compatible and can reproduce. The island model of evolution provides the isolation necessary
+ for diversity to thrive while still providing opportunities for diverse individuals to be combined to produce
+ potentially fitter offspring.
+ </para>
+ <para>
+ In an island model, the isolation of the separate populations often leads to different traits originating on
+ different islands. Migration brings these diverse individuals together occasionally to see what happens when
+ they are combined. Remember that, even if the immigrants are weak, cross-over can result in offspring that are
+ fitter than either of their parents. In this way, the introduction to the population of new genetic building
+ blocks may result in evolutionary progress even if the immigrants themselves are not viable in the new
+ population.
+ </para>
+ </section>
+ <section>
+ <title>Islands in the Watchmaker Framework</title>
+ <indexterm><primary><classname>IslandEvolution</classname></primary></indexterm>
+ <para>
+ The Watchmaker Framework for Evolutionary Computation supports islands models via the
+ <classname>IslandEvolution</classname> class. Each island is a self-contained
+ <classname>EvolutionEngine</classname> just like those we have been using previously for single-population
+ evolutionary algorithms. The evolution is divided into <emphasis>epochs</emphasis>. Each epoch consists
+ of a fixed number of generations that each island completes in isolation. At the end of an epoch migration
+ occurs. Then, if the termination conditions are not yet satisfied, a new epoch begins.
+ </para>
+ <para>
+ The <classname>IslandEvolution</classname> supports pluggable migration strategies via different implementations
+ of the <interfacename>Migration</interfacename> interface. An island version of the string evolution example
+ from <xref linkend="watchmaker_chapter" /> might look something like this:
+ </para>
+ <indexterm><primary><interfacename>Migration</interfacename></primary></indexterm>
+ <indexterm><primary><classname>RingMigration</classname></primary></indexterm>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[IslandEvolution<String> engine
+ = new IslandEvolution<String>(5, // Number of islands.
+ new RingMigration(),
+ candidateFactory,
+ evolutionaryOperator,
+ fitnessEvaluator,
+ selectionStrategy,
+ rng);
+
+engine.evolve(100, // Population size per island.
+ 5, // Elitism for each island.
+ 50, // Epoch length (no. generations).
+ 3, // Migrations from each island at each epoch.
+ new TargetFitness(0, false));]]>
+ </programlisting>
+ </informalexample>
+ <indexterm><primary><interfacename>IslandEvolutionObserver</interfacename></primary></indexterm>
+ <indexterm><primary><methodname>populationUpdate</methodname></primary></indexterm>
+ <indexterm><primary><methodname>islandPopulationUpdate</methodname></primary></indexterm>
+ <para>
+ We can add listeners to an <classname>IslandEvolution</classname> object, just as we can with individual
+ <interfacename>EvolutionEngine</interfacename>s. We use a different interface for this though,
+ <interfacename>IslandEvolutionObserver</interfacename>, which provides two call-backs.
+ The <methodname>populationUpdate</methodname> method reports the global state of the combined population
+ of all islands at the end of each epoch. The <methodname>islandPopulationUpdate</methodname> method reports
+ the state of individual island populations at the end of each generation.
+ </para>
+ <section>
+ <title>Advanced Usage</title>
+ <indexterm><primary><classname>GenerationalEvolutionEngine</classname></primary><secondary>island evolution</secondary></indexterm>
+ <para>
+ In the example code above we specified how many islands we wanted to use and the
+ <classname>IslandEvolution</classname> class created one <classname>GenerationalEvolutionEngine</classname>
+ for each island. Using this approach all of the islands have the same configuration; they use the same
+ candidate factory, evolutionary operator(s) and selection strategy. This is the easiest way to create an
+ island system but it is also possible to construct each island individually for ultimate flexibility.
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[List<EvolutionEngine<String>> islands
+ = new ArrayList<EvolutionEngine<String>>();
+
+// Create individual islands here and add them to the list.
+// ...
+
+IslandEvolution<String> engine
+ = new IslandEvolution<String>(islands,
+ new RingMigration(),
+ false, // Natural fitness?
+ rng);]]>
+ </programlisting>
+ </informalexample>
+ <para>
+ One reason you might choose to construct the islands explicitly is that it makes it possible to configure
+ individual islands differently. You may choose to have different islands use different parameters
+ for evolutionary operators, or even to use different evolutionary operators all together. Alternatively,
+ you could use the same evolutionary operators and parameters but have different selection strategies so that
+ some islands have stronger selection pressure than others. You should generally use the same fitness function
+ for all islands though, otherwise you might get some strange results.
+ </para>
+ <indexterm><primary><classname>SteadyStateEvolutionEngine</classname></primary><secondary>island evolution</secondary></indexterm>
+ <indexterm><primary><classname>EvolutionStrategyEngine</classname></primary><secondary>island evolution</secondary></indexterm>
+ <para>
+ Another possible reason for creating the islands explicitly is so you don't have to use the standard
+ <classname>GenerationalEvolutionEngine</classname> for the islands. You can choose to use any implementation
+ of the <interfacename>EvolutionEngine</interfacename> interface, such as the
+ <classname>SteadyStateEvolutionEngine</classname> class or the <classname>EvolutionStrategyEngine</classname>
+ class. You can even use a mixture of different island types with the same
+ <classname>IslandEvolution</classname> object.
+ </para>
+ </section>
+ </section>
+</chapter>
diff --git a/watchmaker/book/src/xml/multiobjective.xml b/watchmaker/book/src/xml/multiobjective.xml
new file mode 100644
index 0000000..d453c0f
--- /dev/null
+++ b/watchmaker/book/src/xml/multiobjective.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Multi-Objective Optimisations</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/performance.xml b/watchmaker/book/src/xml/performance.xml
new file mode 100644
index 0000000..6f050bb
--- /dev/null
+++ b/watchmaker/book/src/xml/performance.xml
@@ -0,0 +1,157 @@
+<appendix xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Optimising for Performance</title>
+ <para>
+ This appendix lists some suggestions on how to get the best possible performance from your
+ evolutionary Java programs. Much of the advice here applies whether or not you are using the
+ Watchmaker Framework to develop your evolutionary programs.
+ </para>
+ <para>
+ As with all optimisations in software development, the golden rule is don't do it unless you have
+ a demonstrable need for improved performance. Optimisations often introduce complexity and make
+ code harder to maintain. Before starting on any optimisations, always use a profiler to identify
+ the bottlenecks in your application. This will pinpoint the areas where optimisations are most
+ likely to beneficial. It is pointless to expend effort to try to speed up a routine that accounts
+ for only 0.1% of the CPU time.
+ </para>
+ <section>
+ <title>Optimising the Fitness Evaluator</title>
+ <indexterm><primary>fitness function</primary><secondary>optimisation of</secondary></indexterm>
+ <para>
+ For most non-trivial evolutionary algorithms, the bulk of the work is the evaluation of
+ candidate solutions. For this reason the fitness function is often the obvious place to
+ make improvements. A fitness evaluator should do no more work than is absolutely
+ necessary on each invocation. If there is some initialisation that is repeated unnecessarily,
+ consider moving it to the constructor. If similar calculations are performed every time,
+ consider pre-computing the possible results and using a look-up table. When you consider
+ that the evaluator may be invoked millions of times in a single run, it is clear that even
+ small optimisations to the fitness function may add up to substantial reductions in running
+ time.
+ </para>
+ <section>
+ <title>The Caching Fitness Evaluator</title>
+ <indexterm><primary>CachingFitnessEvaluator</primary></indexterm>
+ <indexterm><primary>fitness function</primary><secondary>caching</secondary></indexterm>
+ <indexterm><primary>elitism</primary></indexterm>
+ <para>
+ In some evolutionary programs individuals can survive from generation to generation unmodified.
+ The most obvious example of this is elitism. Individuals that are preserved through elitism
+ will appear unaltered in the next generation and may survive for many generations. Individuals
+ may also survive without modification if the evolutionary operators in use are probabilistic and
+ don't always affect every candidate.
+ </para>
+ <para>
+ If fitness evaluations are expensive, it is wasteful to repeatedly recalculate fitness values
+ for unaltered individuals. The Watchmaker Framework provides the
+ <classname>org.uncommons.watchmaker.framework.CachingFitnessEvaluator</classname> class to
+ address this problem. It acts as a wrapper for your fitness evaluator and caches the results
+ of fitness calculations. If the same candidate is evaluated twice, the cached value is returned
+ the second time thus avoiding the cost of recalculating the fitness score. The cache uses Java's
+ weak references to avoid memory leakage (if the candidate does not survive, the associated cache
+ entry will also be garbage collected).
+ </para>
+ <note>
+ <para>
+ 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
+ <emphasis>isolated</emphasis> and <emphasis>repeatable</emphasis>. 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.
+ Caching should not be used if it is possible for multiple evaluations of the same candidate
+ to return different scores.
+ </para>
+ </note>
+ </section>
+ </section>
+ <section>
+ <title>Minimising the Search Space</title>
+ <para>
+ An evolutionary algorithm is a type of non-deterministic search. The algorithm is
+ searching the space of all possible solutions to find one that is good enough. The
+ larger the search space, the longer it is likely to take to converge on a
+ satisfactory solution.
+ For this reason, anything we can do to constrain the search space, without
+ handicapping the algorithm, is likely to be beneficial. This includes choosing
+ an efficient candidate representation and using evolutionary operators that
+ avoid generating useless or invalid solutions.
+ A little intelligent design can go a long way.
+ </para>
+ </section>
+ <section>
+ <title>Random Number Generators</title>
+ <indexterm><primary>random number generator</primary></indexterm>
+ <indexterm><primary>Random</primary></indexterm>
+ <indexterm><primary>RNG</primary></indexterm>
+ <indexterm><primary>SecureRandom</primary></indexterm>
+ <para>
+ The random number generator (RNG) is a core component of any evolutionary simulation. It is
+ used for selection, for cross-over and for mutation. A slow random number generator can be
+ a bottleneck. Most programming languages provide a mechanism to generate random numbers.
+ Unfortunately, few of them are ideal. The Java standard library includes two RNGs,
+ <classname>java.util.Random</classname> and <classname>java.security.SecureRandom</classname>.
+ These should be avoided for statistical and performance reasons respectively.
+ </para>
+ <indexterm><primary>MersenneTwisterRNG</primary></indexterm>
+ <para>
+ The Watchmaker Framework comes bundled with three high-quality RNGs provided by the Uncommons
+ Maths project. Of these, the <classname>org.uncommons.maths.random.MersenneTwisterRNG</classname>
+ is the most suitable for the majority of evolutionary programs. Alternatively, you can use any
+ third party RNG that is a sub-class of <classname>java.util.Random</classname>.
+ </para>
+ </section>
+ <section>
+ <title>JVM Options</title>
+ <indexterm><primary>Java Virtual Machine</primary></indexterm>
+ <indexterm><primary>JVM</primary></indexterm>
+ <para>
+ The Java Virtual Machine (JVM) is a complex piece of software. It is designed to run a huge
+ variety different programs. As such, its default configuration is not optimised for the
+ particular needs of evolutionary computation. This section lists some of the JVM options
+ that you can tweak to try to achieve better performance.
+ </para>
+ <section>
+ <title>Server VM</title>
+ <indexterm><primary>server VM</primary></indexterm>
+ <para>
+ The Sun JVM provides two modes of operation, one optimised for client applications (the
+ default) and one for server applications. The server VM takes marginally longer to start
+ up but provides substantially better performance for long-running processes and is therefore
+ a better choice for most evolutionary algorithms. The server VM is enabled using the
+ <literal>-server</literal> switch.
+ </para>
+ </section>
+ <section>
+ <title>Garbage Collection</title>
+ <indexterm><primary>garbage collection</primary></indexterm>
+ <para>
+ Evolutionary algorithms create many short-lived objects. Modern JVMs, with their generational
+ garbage collectors, are typically well tuned for this usage pattern. However, you may find
+ that by modifying the settings you are able to improve throughput.
+ </para>
+ <para>
+ Garbage collectors make a trade-off between overall throughput and pause time. For
+ evolutionary algorithms we typically want to maximise throughput, even at the expense of
+ introducing noticeable pauses in the program's execution. What is most important is how soon
+ the program completes, not how smoothly it runs.
+ </para>
+ <para>
+ You can get information on what the garbage collector is doing by starting the JVM with the
+ <literal>-verbosegc</literal> switch. If you find that the program is spending a lot of time
+ collecting garbage, it may be because it is short of memory. If you have sufficient RAM,
+ increasing the maximum size of the Java heap (using the <literal>-Xmx</literal> switch) may
+ improve things.
+ </para>
+ </section>
+ <section>
+ <title>Alternative JVMs</title>
+ <para>
+ Sun Microsystems is not the only provider of virtual machines for Java. If your platform is
+ supported, you may also have the option of using a JVM from BEA, IBM or some other third party.
+ These virtual machines have different performance characteristics and different garbage
+ collector implementations. If you have tried everything else and still need something faster,
+ you may find that a different JVM will perform better. Then again, it may not.
+ </para>
+ </section>
+ </section>
+</appendix>
diff --git a/watchmaker/book/src/xml/preface.xml b/watchmaker/book/src/xml/preface.xml
new file mode 100644
index 0000000..9d2e1ec
--- /dev/null
+++ b/watchmaker/book/src/xml/preface.xml
@@ -0,0 +1,29 @@
+<preface xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Preface</title>
+ <para>
+ This book is intended to be a pragmatic, hands-on guide to implementing evolutionary
+ algorithms. The emphasis is on writing useful evolutionary programs and solving interesting
+ problems. This is not intended to be a thorough theoretical guide. I will introduce the
+ key evolutionary computation concepts and demonstrate their practical application, but I will
+ endeavour to keep abstract theory and mathematics to the minimum necessary.
+ For a more academic treatment of the material, I recommend Melanie Mitchell's
+ <emphasis>An Introduction to Genetic Algorithms</emphasis> and A.E. Eiben and J.E. Smith's
+ <emphasis>Introduction to Evolutionary Computing</emphasis>.
+ </para>
+ <para>
+ Though evolutionary algorithms can be implemented in any general purpose programming language,
+ all of the examples in this book are presented using Java. Java is one of the most widely
+ used programming languages in the world today. As such, many software developers are already
+ familiar with Java and able to understand programs written in it, even if they usually
+ develop in other languages. Java provides a good balance of performance, ease-of-use and
+ a rich standard library.
+ </para>
+ <para>
+ The concepts discussed in this book are not tied to a particular programming language,
+ so there is no reason why you couldn't implement the ideas in another language if you
+ preferred.
+ </para>
+</preface>
+
diff --git a/watchmaker/book/src/xml/salesman.xml b/watchmaker/book/src/xml/salesman.xml
new file mode 100644
index 0000000..9eac594
--- /dev/null
+++ b/watchmaker/book/src/xml/salesman.xml
@@ -0,0 +1,17 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>The Travelling Salesman</title>
+ <mediaobject>
+ <imageobject>
+ <imagedata fileref="travelling_salesman_problem.png"
+ format="PNG" scalefit="1" width="100%" />
+ </imageobject>
+ <caption>
+ <para>
+ <link href="http://xkcd.com/399">http://xkcd.com/399</link>
+ </para>
+ </caption>
+ </mediaobject>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/selection.xml b/watchmaker/book/src/xml/selection.xml
new file mode 100644
index 0000000..95525b1
--- /dev/null
+++ b/watchmaker/book/src/xml/selection.xml
@@ -0,0 +1,160 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd"
+ id="selection_chapter">
+ <title>Selection Strategies &amp; Elitism</title>
+ <para>
+ Selection is an important part of an evolutionary algorithm. Without selection directing the algorithm towards
+ fitter solutions there would be no progress. Selection must favour fitter candidates over weaker candidates but
+ beyond that there are no fixed rules. Furthermore, there is no one strategy that is best for all problems. Some
+ strategies result in fast convergence, others will tend to produce a more thorough exploration of the search space.
+ An evolutionary algorithm that appears ineffective with one selection strategy may be transformed by switching to
+ a strategy with different characteristics. This chapter describes the most commonly used selection strategies
+ (all of these strategies are supported in the Watchmaker Framework for Evolutionary Computation via different
+ implementations of the <classname>SelectionStrategy</classname> interface).
+ </para>
+ <section>
+ <title>Truncation Selection</title>
+ <indexterm><primary>truncation selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>truncation</secondary></indexterm>
+ <para>
+ Truncation selection is the simplest and arguably least useful selection strategy. Truncation selection
+ simply retains the fittest <varname>x</varname>% of the population. These fittest individuals are duplicated so
+ that the population size is maintained. For example, we might select the fittest 25% from a population of 100
+ individuals. In this case we would create four copies of each of the 25 candidates in order to maintain a population
+ of 100 individuals. This is an easy selection strategy to implement but it can result in premature convergence as
+ less fit candidates are ruthlessly culled without being given the opportunity to evolve into something better.
+ Nevertheless, truncation selection can be an effective strategy for certain problems.
+ </para>
+ </section>
+ <section>
+ <title>Fitness-Proportionate Selection</title>
+ <indexterm><primary>fitness-proportionate selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>fitness-proportionate</secondary></indexterm>
+ <para>
+ A better approach to selection is to give every individual a chance of being selected to breed but to make
+ fitter candidates more likely to be chosen than weaker individuals. This is achieved by making an individual's
+ survival probability a function of its fitness score. Such strategies are known as
+ <emphasis>fitness-proportionate selection</emphasis>.
+ </para>
+ <section>
+ <title>Roulette Wheel Selection</title>
+ <indexterm><primary>roulette wheel selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>roulette wheel</secondary></indexterm>
+ <para>
+ The most common fitness-proportionate selection technique is called <emphasis>Roulette Wheel
+ Selection</emphasis>. Conceptually, each member of the population is allocated a section of an imaginary
+ roulette wheel. Unlike a real roulette wheel the sections are different sizes, proportional to the
+ individual's fitness, such that the fittest candidate has the biggest slice of the wheel and the weakest
+ candidate has the smallest. The wheel is then spun and the individual associated with the winning section
+ is selected. The wheel is spun as many times as is necessary to select the full set of parents for the next
+ generation.
+ </para>
+ <para>
+ Using this technique it is possible (probable) that one or more individuals is selected multiple times.
+ That's OK, it's what we want to happen. Remember that we are not selecting the members of the next
+ generation, we are selecting their parents and it is possible for an individual to be a parent multiple times.
+ If there is a particularly fit member of the population we would expect it to be more successful at producing
+ offspring than a weaker rival.
+ </para>
+ </section>
+ <section>
+ <title>Stochastic Universal Sampling</title>
+ <indexterm><primary>stochastic universal sampling</primary></indexterm>
+ <para>
+ <emphasis>Stochastic Universal Sampling</emphasis> is an elaborately-named variation of roulette wheel
+ selection. Stochastic Universal Sampling ensures that the observed selection frequencies of each individual
+ are in line with the expected frequencies. So if we have an individual that occupies 4.5% of the wheel
+ and we select 100 individuals, we would expect on average for that individual to be selected between four
+ and five times. Stochastic Universal Sampling guarantees this. The individual will be selected either four
+ times or five times, not three times, not zero times and not 100 times. Standard roulette wheel selection
+ does not make this guarantee.
+ </para>
+ <para>
+ Stochastic Universal Sampling works by making a single spin of the roulette wheel. This provides a starting
+ position and the first selected individual. The selection process then proceeds by advancing all the way
+ around the wheel in equal sized steps, where the step size is determined by the number of individuals to be
+ selected. So if we are selecting 30 individuals we will advance by
+ <inlineequation><mathphrase>1/30 x 360 degrees</mathphrase></inlineequation> for each selection. Note that
+ this does not mean that every candidate on the wheel will be selected. Some weak individuals will have very
+ thin slices of the wheel and these might be stepped over completely depending on the random starting position.
+ </para>
+ </section>
+ </section>
+ <section>
+ <title>Rank Selection</title>
+ <indexterm><primary>rank selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>rank</secondary></indexterm>
+ <para>
+ <emphasis>Rank Selection</emphasis> is similar to fitness-proportionate selection except that selection
+ probability is proportional to relative fitness rather than absolute fitness. In other words, it doesn't make
+ any difference whether the fittest candidate is ten times fitter than the next fittest or 0.001% fitter. In
+ both cases the selection probabilities would be the same; all that matters is the ranking relative to other
+ individuals.
+ </para>
+ <para>
+ Rank selection will tend to avoid premature convergence by tempering selection
+ pressure for large fitness differentials that occur in early generations. Conversely, by amplifying small
+ fitness differences in later generations, selection pressure is increased compared to alternative selection
+ strategies.
+ </para>
+ </section>
+ <section>
+ <title>Tournament Selection</title>
+ <indexterm><primary>selection</primary><secondary>tournament</secondary></indexterm>
+ <indexterm><primary>tournament selection</primary></indexterm>
+ <para>
+ <emphasis>Tournament Selection</emphasis> is among the most widely used selection strategies in evolutionary
+ algorithms. It works well for a wide range of problems, it can be implemented efficiently, and it is amenable to
+ parallelisation.
+ </para>
+ <para>
+ At its simplest tournament selection involves randomly picking two individuals from the population and staging
+ a tournament to determine which one gets selected. The "tournament" isn't much of a tournament at all, it
+ just involves generating a random value between zero and one and comparing it to a pre-determined selection
+ probability. If the random value is less than or equal to the selection probability, the fitter candidate is
+ selected, otherwise the weaker candidate is chosen. The probability parameter provides a convenient mechanism
+ for adjusting the selection pressure. In practise it is always set to be greater than 0.5 in order to favour
+ fitter candidates. The tournament can be extended to involve more than two individuals if desired.
+ </para>
+ </section>
+ <section>
+ <title>Sigma Scaling</title>
+ <indexterm><primary>selection</primary><secondary>sigma scaling</secondary></indexterm>
+ <indexterm><primary>sigma scaling</primary></indexterm>
+ <para>
+ Like rank selection, <emphasis>Sigma Scaling</emphasis> attempts to moderate selection pressure over time
+ so that it is not too strong in early generations and not too weak once the population has stabilised and
+ fitness differences are smaller. The Greek letter Sigma is used in statistics to denote standard deviation
+ and that's what it means here too. The standard deviation of the population fitness is used to scale the
+ fitness scores so that selection pressure is relatively constant over the lifetime of the evolutionary
+ program.
+ </para>
+ </section>
+ <section>
+ <title>Elitism</title>
+ <indexterm><primary>elitism</primary></indexterm>
+ <para>
+ Sometimes good candidates can be lost when cross-over or mutation results in offspring
+ that are weaker than the parents. Often the EA will re-discover these lost improvements
+ in a subsequent generation but there is no guarantee. To combat this we can use a
+ feature known as <emphasis>elitism</emphasis>. Elitism involves copying a small
+ propotion of the fittest candidates, unchanged, into the next generation. This can
+ sometimes have a dramatic impact on performance by ensuring that the EA does not waste
+ time re-discovering previously discarded partial solutions.
+ Candidate solutions that are preserved unchanged through elitism remain eligible for
+ selection as parents when breeding the remainder of the next generation.
+ </para>
+ <tip>
+ <para>
+ The Watchmaker Framework supports elitism via the second parameter to the
+ <methodname>evolve</methodname> method of an <interfacename>EvolutionEngine</interfacename>.
+ This elite count is the number of candidates in a generation that should be copied
+ unchanged from the previous generation, rather than created via evolution. Collectively
+ these candidates are the <emphasis>elite</emphasis>. So for a population size of 100,
+ setting the elite count to 5 will result in the fittest 5% of each generation being copied,
+ without modification, into the next generation.
+ </para>
+ </tip>
+ </section>
+</chapter>
diff --git a/watchmaker/book/src/xml/steadystate.xml b/watchmaker/book/src/xml/steadystate.xml
new file mode 100644
index 0000000..2e032c8
--- /dev/null
+++ b/watchmaker/book/src/xml/steadystate.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>Steady-State Evolutionary Algorithms</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/sudoku.xml b/watchmaker/book/src/xml/sudoku.xml
new file mode 100644
index 0000000..9c86740
--- /dev/null
+++ b/watchmaker/book/src/xml/sudoku.xml
@@ -0,0 +1,6 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <title>An Evolutionary Sudoku Solver</title>
+ <para>TODO</para>
+</chapter>
diff --git a/watchmaker/book/src/xml/watchmaker.xml b/watchmaker/book/src/xml/watchmaker.xml
new file mode 100644
index 0000000..b18cb39
--- /dev/null
+++ b/watchmaker/book/src/xml/watchmaker.xml
@@ -0,0 +1,478 @@
+<chapter xmlns="http://docbook.org/ns/docbook"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd"
+ id="watchmaker_chapter">
+ <title>The Watchmaker Framework</title>
+ <indexterm significance="preferred"><primary>Watchmaker Framework</primary></indexterm>
+ <para>
+ The Watchmaker Framework for Evolutionary Computation is an extensible, high-performance,
+ object-oriented framework for implementing platform-independent evolutionary algorithms
+ in Java.
+ It is freely available under a permissive Open Source licence. It can be downloaded from
+ <link xlink:href="http://watchmaker.uncommons.org">http://watchmaker.uncommons.org</link>.
+ </para>
+ <para>
+ This chapter introduces the core components of the Watchmaker Framework and shows how
+ they can be used to implement simple evolutionary algorithms such as the "Hello World"
+ example outlined in the previous chapter.
+ </para>
+ <section>
+ <title>The Evolution Engine</title>
+ <indexterm><primary><classname>GenerationalEvolutionEngine</classname></primary></indexterm>
+ <indexterm><primary><interfacename>EvolutionEngine</interfacename></primary></indexterm>
+ <para>
+ The central object of an evolutionary program built with the Watchmaker Framework is
+ the evolution engine.
+ </para>
+ <para>
+ The framework provides multiple implementations of the
+ <interfacename>EvolutionEngine</interfacename> interface, but the one that you will
+ usually want to use is <classname>GenerationalEvolutionEngine</classname>. This is a
+ general-purpose implementation of the evolutionary algorithm outline from chapter 1.
+ </para>
+ <para>
+ An <interfacename>EvolutionEngine</interfacename> has a single generic type parameter
+ that indicates the type of object that it can evolve.
+ For the "Hello World" program we need to be able to evolve Java strings.
+ Code that creates an engine that can evolve strings would look something like this:
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[EvolutionEngine<String> engine
+ = new GenerationalEvolutionEngine<String>(candidateFactory,
+ evolutionaryOperator,
+ fitnessEvaluator,
+ selectionStrategy,
+ rng);]]>
+ </programlisting>
+ </informalexample>
+ <para>
+ Once you have created an <interfacename>EvolutionEngine</interfacename>, your program
+ is as simple as calling the <methodname>evolve</methodname> method with appropriate
+ arguments.
+ However, as you can see from the code snippet above, there is a little bit of work to
+ be done first in order to create an <interfacename>EvolutionEngine</interfacename> that
+ is configured appropriately for the given problem.
+ The constructor of the <classname>GenerationalEvolutionEngine</classname> class requires
+ five objects. These are:
+ </para>
+ <itemizedlist>
+ <listitem>A Candidate Factory</listitem>
+ <listitem>An Evolutionary Operator</listitem>
+ <listitem>A Fitness Evaluator</listitem>
+ <listitem>A Selection Strategy</listitem>
+ <listitem>A Random Number Generator</listitem>
+ </itemizedlist>
+ </section>
+ <section>
+ <title>The Candidate Factory</title>
+ <indexterm significance="preferred"><primary><interfacename>CandidateFactory</interfacename></primary></indexterm>
+ <para>
+ The first object that needs to be plugged into the evolution engine is a candidate
+ factory. Every evolutionary simulation must start with an initial population of
+ candidate solutions and the <interfacename>CandidateFactory</interfacename> interface
+ is the mechanism by which the evolution engine creates this population.
+ </para>
+ <para>
+ A candidate factory implementation has an associated type. It can only create
+ objects of that type. The type must match the type of the evolution engine that
+ it is plugged into.
+ You can write your own implementation of <interfacename>CandidateFactory</interfacename>
+ for your program or, if you are using a common type such as strings, lists or
+ arrays, you may be able to use a ready-made factory from the
+ <package>org.uncommons.watchmaker.framework.factories</package> package.
+ </para>
+ <indexterm><primary><classname>StringFactory</classname></primary></indexterm>
+ <para>
+ For our "Hello World" program, we can use the provided
+ <classname>StringFactory</classname>:
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[// Define the set of permitted characters (A-Z plus space).
+char[] chars = new char[27];
+for (char c = 'A'; c <= 'Z'; c++)
+{
+ chars[c - 'A'] = c;
+}
+chars[26] = ' ';
+
+// Factory for random 11-character Strings.
+CandidateFactory<String> factory = new StringFactory(chars, 11);]]>
+ </programlisting>
+ </informalexample>
+ <tip>
+ <indexterm significance="preferred"><primary><classname>AbstractCandidateFactory</classname></primary></indexterm>
+ <para>
+ When writing your own <interfacename>CandidateFactory</interfacename> implementations,
+ it is easiest to extend the provided <classname>AbstractCandidateFactory</classname>
+ base class since there is then only a single method that must be implemented.
+ </para>
+ </tip>
+ </section>
+ <section>
+ <title>Evolutionary Operators</title>
+ <indexterm significance="preferred"><primary><interfacename>EvolutionaryOperator</interfacename></primary></indexterm>
+ <para>
+ Evolutionary operators are the components that perform the actual evolution of a
+ population. Cross-over is an evolutionary operator, as is mutation.
+ </para>
+ <para>
+ In the Watchmaker Framework, evolutionary operators are defined in terms of the
+ <interfacename>EvolutionaryOperator</interfacename> interface. This interface
+ declares a single method that takes a list of selected individuals and returns a
+ list of evolved individuals. Some operators (i.e. mutation) will process one
+ individual at a time, whereas others will process individuals in groups
+ (cross-over processes two individuals at a time).
+ </para>
+ <indexterm><primary><classname>StringCrossover</classname></primary></indexterm>
+ <indexterm><primary><classname>StringMutation</classname></primary></indexterm>
+ <para>
+ As with candidate factories, evolutionary operators have associated types that
+ must be compatible with the type of the evolution engine that they are used with.
+ And, as with candidate factories, the framework provides several ready-made operators
+ for common types. These can be found in the
+ <package>org.uncommons.watchmaker.framework.operators</package> package. The
+ cross-over and mutation operators that we need for our "Hello World" program are
+ provided by the <classname>StringCrossover</classname> and
+ <classname>StringMutation</classname> classes.
+ </para>
+ <section>
+ <title>The Evolution Pipeline</title>
+ <indexterm significance="preferred"><primary><classname>EvolutionPipeline</classname></primary></indexterm>
+ <para>
+ Alert readers will have noticed that the evolution engine constructor only accepts
+ a single evolutionary operator. So how can we use both cross-over and mutation?
+ The answer is provided by the <classname>EvolutionPipeline</classname> operator.
+ This is a compound evolutionary operator that chains together multiple operators of
+ the same type.
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[List<EvolutionaryOperator<String>> operators
+ = new LinkedList<EvolutionaryOperator<String>>();
+operators.add(new StringCrossover());
+operators.add(new StringMutation(chars, new Probability(0.02)));
+
+EvolutionaryOperator<String> pipeline
+ = new EvolutionPipeline<String>(operators);]]>
+ </programlisting>
+ </informalexample>
+ <note>
+ <para>
+ The evolution pipeline is just one of many useful operators included
+ in the <package>org.uncommons.watchmaker.framework.operators</package> package.
+ Elaborate evolution schemes can be constructed from combinations of these
+ operators.
+ Users of the Watchmaker Framework should take a few minutes to browse the API
+ documentation and familiarise themselves with the available classes.
+ </para>
+ </note>
+ </section>
+ </section>
+ <section>
+ <title>The Fitness Evaluator</title>
+ <indexterm significance="preferred"><primary><interfacename>FitnessEvaluator</interfacename></primary></indexterm>
+ <para>
+ So far we've been able to build our evolutionary program by simply combining instances
+ of classes provided by the framework. There is one part of the program that we will
+ always have to write for ourselves though and that is the fitness function, which is
+ necessarily different for every program.
+ </para>
+ <para>
+ In the Watchmaker Framework, a fitness function is written by implementing the
+ <interfacename>FitnessEvaluator</interfacename> interface. The
+ <methodname>getFitness</methodname> method of this interface takes a candidate solution
+ and returns its fitness score as a Java double. The method actually takes two
+ arguments, but we can ignore the second for now.
+ </para>
+ <para>
+ The listing below is a fitness evaluator for the "Hello World" program. It
+ simply assigns one point for each character in the candidate string that
+ matches the corresponding position in the target string.
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[public class StringEvaluator implements FitnessEvaluator<String>
+{
+ private final String targetString = "HELLO WORLD";
+
+ /**
+ * Assigns one "fitness point" for every character in the
+ * candidate String that matches the corresponding position in
+ * the target string.
+ */
+ public double getFitness(String candidate,
+ List<? extends String> population)
+ {
+ int matches = 0;
+ for (int i = 0; i < candidate.length(); i++)
+ {
+ if (candidate.charAt(i) == targetString.charAt(i))
+ {
+ ++matches;
+ }
+ }
+ return matches;
+ }
+
+ public boolean isNatural()
+ {
+ return true;
+ }
+}]]>
+ </programlisting>
+ </informalexample>
+ <indexterm><primary>fitness function</primary><secondary>natural</secondary></indexterm>
+ <indexterm><primary>natural fitness</primary></indexterm>
+ <para>
+ By some fitness measures, a higher value indicates a fitter solution. In other
+ cases a lower value is better. The <methodname>isNatural</methodname> method
+ of a fitness evaluator simply specifies which scenario applies. In Watchmaker
+ Framework terminology, a <emphasis>natural</emphasis> fitness function is one that
+ returns higher values for fitter individuals.
+ </para>
+ </section>
+ <section>
+ <title>Selection Strategy</title>
+ <indexterm><primary>selection</primary></indexterm>
+ <indexterm><primary>SelectionStrategy</primary></indexterm>
+ <para>
+ Selection is a key ingredient in any evolutionary algorithm. It's what determines
+ which individuals survive to reproduce and which are discarded. All we've said about
+ selection so far is that it should favour fitter individuals. This definition permits
+ several different implementations. The Watchmaker Framework includes all of the most
+ common selection strategies in the
+ <package>org.uncommons.watchmaker.framework.selection</package> package. These are
+ sufficient for most evolutionary algorithms but, if necessary, it is straightforward
+ to write your own implementation of the <interfacename>SelectionStrategy</interfacename>
+ interface.
+ </para>
+ <indexterm><primary>RouletteWheelSelection</primary></indexterm>
+ <para>
+ Some selection strategies work better than others for certain problems. Often a little
+ trial-and-error is required to pick the best option. We will delve into the details of
+ various selection strategies in <xref linkend="selection_chapter" />, but for now we will
+ just create an instance of the <classname>RouletteWheelSelection</classname> class and use
+ that for our "Hello World" application.
+ </para>
+ <indexterm><primary>fitness-proportionate selection</primary></indexterm>
+ <indexterm><primary>roulette wheel selection</primary></indexterm>
+ <indexterm><primary>selection</primary><secondary>fitness-proportionate</secondary></indexterm>
+ <indexterm><primary>selection</primary><secondary>roulette wheel</secondary></indexterm>
+ <para>
+ <emphasis>Roulette wheel selection</emphasis> is the most common type of
+ <emphasis>fitness-proportionate selection</emphasis>.
+ It gives all individuals a chance of being selected but favours the fitter
+ individuals since an individual's selection probability is derived from its
+ fitness score.
+ </para>
+ </section>
+ <section>
+ <title>Random Number Generator</title>
+ <indexterm><primary>random number generator</primary></indexterm>
+ <indexterm><primary>Random</primary></indexterm>
+ <indexterm><primary>RNG</primary></indexterm>
+ <indexterm><primary>SecureRandom</primary></indexterm>
+ <para>
+ The final dependency that must be satisfied in order to create an evolution engine
+ is the random number generator (RNG). An evolution engine has a single RNG that it
+ passes to its candidate factory, evolutionary operator and selection strategy.
+ An ideal RNG is both fast and statistically random. We <emphasis>could</emphasis>
+ use the standard Java RNG, <classname>java.util.Random</classname>, but its output
+ is not as random as it should be. The other RNG in the standard library,
+ <classname>java.security.SecureRandom</classname> is much better in this respect
+ but can be slow.
+ </para>
+ <indexterm><primary>MersenneTwisterRNG</primary></indexterm>
+ <para>
+ Fortunately, the Watchmaker Framework provides alternatives. The
+ <classname>org.uncommons.maths.random.MersenneTwisterRNG</classname> random number
+ generator is both fast and statistically sound. It is usually the best choice
+ when creating an evolution engine.
+ </para>
+ </section>
+ <section>
+ <title>Completing the Jigsaw</title>
+ <para>
+ We've now got all of the necessary pieces to complete the "Hello World" example
+ application. Assuming that you've already created the
+ <classname>StringEvaluator</classname> class (defined above) in a separate file,
+ the code needed to create the evolution engine looks like this:
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[// Create a factory to generate random 11-character Strings.
+char[] chars = new char[27];
+for (char c = 'A'; c <= 'Z'; c++)
+{
+ chars[c - 'A'] = c;
+}
+chars[26] = ' ';
+CandidateFactory<String> factory = new StringFactory(chars, 11);
+
+// Create a pipeline that applies cross-over then mutation.
+List<EvolutionaryOperator<String>> operators
+ = new LinkedList<EvolutionaryOperator<String>>();
+operators.add(new StringCrossover())
+operators.add(new StringMutation(chars, new Probability(0.02)));
+EvolutionaryOperator<String> pipeline
+ = new EvolutionPipeline<String>(operators);
+
+FitnessEvaluator<String> fitnessEvaluator = new StringEvaluator();
+SelectionStrategy<Object> selection = new RouletteWheelSelection();
+Random rng = new MersenneTwisterRNG();
+
+EvolutionEngine<String> engine
+ = new GenerationalEvolutionEngine<String>(factory,
+ pipeline,
+ fitnessEvaluator,
+ selection,
+ rng);]]>
+ </programlisting>
+ </informalexample>
+ <indexterm><primary>evolve method</primary></indexterm>
+ <indexterm><primary>population</primary><secondary>size of</secondary></indexterm>
+ <para>
+ The listing above only creates the evolution engine, it does not perform any
+ evolution. For that we need to call the <methodname>evolve</methodname> method.
+ The <methodname>evolve</methodname> method takes three parameters. The first
+ is the size of the population. This is the number of candidate solutions that
+ exist at any time. A bigger population will often result in a satisfactory
+ solution being found in fewer generations. On the other hand, the processing
+ of each generation will take longer because there are more individuals to deal
+ with. For the "Hello World" program, a population size of 10 is fine.
+ </para>
+ <para>
+ The second parameter is concerned with <emphasis>elitism</emphasis>. Elitism
+ is explained in <xref linkend="selection_chapter" />. For now, just use a value of zero.
+ The final varargs parameter specifies one or more termination conditions.
+ </para>
+ <section>
+ <title>Termination Conditions</title>
+ <indexterm><primary>TerminationCondition</primary></indexterm>
+ <indexterm><primary>TargetFitness</primary></indexterm>
+ <para>
+ Termination conditions make the evolution stop. There are a few reasons why
+ we would like the evolution to stop. The most obvious is because we have found the
+ solution that we are looking for. In the case of the "Hello World" program, that
+ is when we have found the target string. The target string has a fitness score of
+ 11 so we use the <classname>TargetFitness</classname> condition.
+ </para>
+ <para>
+ To complete the evolutionary "Hello World" application, add the following two lines:
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[String result = engine.evolve(10, 0, new TargetFitness(11, true));
+System.out.println(result);]]>
+ </programlisting>
+ </informalexample>
+ <note>
+ <indexterm><primary>ElapsedTime</primary></indexterm>
+ <indexterm><primary>GenerationCount</primary></indexterm>
+ <indexterm><primary>Stagnation</primary></indexterm>
+ <para>
+ When we move on to less trivial evolutionary programs, we will rarely be able to
+ specify the outcome so precisely. The
+ <package>org.uncommons.watchmaker.framework.termination</package> package includes
+ other termination conditions that can be used. For example, we may want the program
+ to run for a certain period of time, or a certain number of generations, and then
+ return the best solution it has found up until that point. The
+ <classname>ElapsedTime</classname> and <classname>GenerationCount</classname>
+ conditions provide this functionality. Alternatively, we may want the program to
+ continue as long as it is finding progressively better solutions. The
+ <classname>Stagnation</classname> condition will terminate the evolution after a
+ set number of generations pass without any improvement in the fitness of the fittest
+ candidate.
+ If multiple termination conditions are specified, the evolution will stop as soon
+ as any one of them is satisfied.
+ </para>
+ </note>
+ </section>
+ <section>
+ <title>Evolution Observers</title>
+ <para>
+ Compile and run the above code and, perhaps after a brief pause, you'll see the
+ following output:
+ </para>
+ <informalexample>
+ <programlisting>
+<![CDATA[ HELLO WORLD]]>
+ </programlisting>
+ </informalexample>
+ <indexterm><primary>EvolutionObserver</primary></indexterm>
+ <para>
+ This is quite probably the most convoluted "Hello World" program you'll ever write.
+ It also gives no hints as to its evolutionary nature. We can make the program more
+ interesting by adding an <interfacename>EvolutionObserver</interfacename> to report
+ on the progress of the evolution at the end of each generation. Add the following
+ code to your program before the call to the <methodname>evolve</methodname> method:
+ </para>
+ <informalexample>
+ <programlisting language="java">
+<![CDATA[engine.addEvolutionObserver(new EvolutionObserver<String>()
+{
+ public void populationUpdate(PopulationData<? extends String> data)
+ {
+ System.out.printf("Generation %d: %s\n",
+ data.getGenerationNumber(),
+ data.getBestCandidate());
+ }
+});]]>
+ </programlisting>
+ </informalexample>
+ <para>
+ Re-compile the program and run it again. This time you'll see all of the steps
+ taken to arrive at the target string:
+ </para>
+ <informalexample>
+ <programlisting>
+ Generation 0: JIKDORHOQZJ
+ Generation 1: ULLLFQWZPXG
+ Generation 2: UEULKFVFZLS
+ Generation 3: KLLLFKZGRLS
+ Generation 4: HLLLFKZGRLS
+ Generation 5: HEDPOYWOZLS
+ Generation 6: HEULKIWWZLD
+ Generation 7: HPRLOYWOZLS
+ Generation 8: HEULOYWOZLS
+ Generation 9: HEULOYWORLS
+ Generation 10: HEULOYWORLS
+ Generation 11: HPLLK WQRLH
+ Generation 12: HEBLOYWQRLS
+ Generation 13: HEULOYWOBLA
+ Generation 14: HEBLOIWMRLD
+ Generation 15: HEBLOIWMRLD
+ Generation 16: HEYLFNWQRLD
+ Generation 17: HEBLOIWORLS
+ Generation 18: HEBLOIWORLT
+ Generation 19: HEBLOKWGRLD
+ Generation 20: HELLAYWORLS
+ Generation 21: HELHOIWORLT
+ Generation 22: HEWLOIWORLS
+ Generation 23: HEBLOYCORLD
+ Generation 24: HELLKQWORLD
+ Generation 25: HELLOIWORLT
+ Generation 26: HELLOIWORLS
+ Generation 27: HELLKQWORLD
+ Generation 28: HELLFYWORLD
+ Generation 29: HELLOIWORLD
+ Generation 30: HELLOIWORLD
+ Generation 31: HELLOIWORLD
+ Generation 32: HELLOIWORLD
+ Generation 33: HELLOIWORLD
+ Generation 34: HELLOIWORLD
+ Generation 35: HELLOIWDRLD
+ Generation 36: HELLOIWORLD
+ Generation 37: HELLOIWORLD
+ Generation 38: HELLOPWORLD
+ Generation 39: HELLOIWORLD
+ Generation 40: HELLO WORLD
+ HELLO WORLD
+ </programlisting>
+ </informalexample>
+ </section>
+ </section>
+</chapter>
diff --git a/watchmaker/book/src/xml/website.xml b/watchmaker/book/src/xml/website.xml
new file mode 100644
index 0000000..724e586
--- /dev/null
+++ b/watchmaker/book/src/xml/website.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<book xmlns="http://docbook.org/ns/docbook"
+ xmlns:xi="http://www.w3.org/2001/XInclude"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://docbook.org/ns/docbook http://www.docbook.org/xml/5.0/xsd/docbook.xsd">
+ <info>
+ <title>Evolutionary Computation in Java</title>
+ <subtitle>A Practical Guide to the Watchmaker Framework</subtitle>
+ <author>
+ <personname>
+ <firstname>Daniel</firstname>
+ <surname>Dyer</surname>
+ <othername>W.</othername>
+ </personname>
+ <email>dan@uncommons.org</email>
+ <uri>http://www.dandyer.co.uk</uri>
+ </author>
+ <copyright>
+ <year>2008</year>
+ <year>2009</year>
+ <year>2010</year>
+ </copyright>
+ <keywordset>
+ <keyword>algorithms</keyword>
+ <keyword>evolution</keyword>
+ <keyword>evolutionary algorithms</keyword>
+ <keyword>evolutionary computation</keyword>
+ <keyword>genetic algorithms</keyword>
+ <keyword>Java</keyword>
+ <keyword>programming</keyword>
+ <keyword>software</keyword>
+ </keywordset>
+ </info>
+
+ <!-- Chapters -->
+ <xi:include href="evolution.xml" />
+ <xi:include href="watchmaker.xml" />
+ <xi:include href="selection.xml" />
+ <xi:include href="islands.xml" />
+
+ <!-- Appendices -->
+ <xi:include href="performance.xml" />
+
+</book>
diff --git a/watchmaker/build.xml b/watchmaker/build.xml
new file mode 100644
index 0000000..6292b47
--- /dev/null
+++ b/watchmaker/build.xml
@@ -0,0 +1,298 @@
+<!--===========================================================================
+ 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.
+ ==========================================================================-->
+<project name="watchmaker"
+ xmlns:uncommons="antlib:org.uncommons.antlib"
+ default="dist"
+ basedir=".">
+ <description>Ant build file for the Watchmaker framework.</description>
+
+
+<!-- ==================================================================
+ GLOBAL BUILD PROPERTIES
+=================================================================== -->
+
+ <!-- Project-global locations. -->
+ <property name="conf.dir" value="etc" />
+ <property name="lib.dir" value="lib" />
+ <property name="lib.compiletime" value="${lib.dir}/compiletime" />
+ <property name="lib.runtime" value="${lib.dir}/runtime" />
+ <property name="dist.dir" value="dist" />
+ <property name="docs.dir" value="docs" />
+ <property name="checkstyle-results.dir" value="${docs.dir}/checkstyle" />
+ <property name="release.dir" value="release" />
+ <!-- The "website" directory is a symlink to a copy of the Git repository
+ at http://github.com/dwdyer/watchmaker-website. -->
+ <property name="web.dir" value="website" />
+ <property name="temp.dir" value="temp" />
+
+ <!-- Per-module locations. -->
+ <property name="src.dir" value="src" />
+ <property name="java.dir" value="${src.dir}/java" />
+ <property name="build.dir" value="build"/>
+
+
+ <!-- Classpath for compilation and tests. -->
+ <path id="tool.path">
+ <fileset dir="${lib.dir}" includes="**/*.jar" />
+ </path>
+
+ <taskdef uri="antlib:org.uncommons.antlib"
+ resource="org/uncommons/antlib/antlib.xml"
+ classpathref="tool.path"/>
+
+
+ <property name="version" value="0.7.2"/>
+ <property name="artifact.identifier" value="watchmaker-framework-${version}"/>
+
+ <!-- This is the minimum coverage percentage (for both lines and
+ branches) that will be tolerated. This is used to prevent
+ regressions in coverage. The threshold will be raised as
+ test coverage improves. -->
+ <property name="minimum.coverage" value="80" />
+
+
+<!-- ==================================================================
+ TARGETS FOR BUILDING THE SOFTWARE
+=================================================================== -->
+
+ <!-- Builds everything from scratch. -->
+ <target name="all"
+ depends="clean, dist, test, docs"
+ description="Builds everything, excluding docs, from scratch."/>
+
+
+ <!-- Deletes all directories and files created by the build process. -->
+ <target name="clean"
+ description="Remove all files created by the build process." >
+ <delete dir="${docs.dir}" />
+ <delete dir="${dist.dir}" />
+ <delete dir="${release.dir}" />
+ <delete dir="${temp.dir}" />
+ <uncommons:clean module="examples" />
+ <uncommons:clean module="framework" />
+ <uncommons:clean module="swing" />
+ <uncommons:clean module="book" />
+ </target>
+
+
+ <!-- Builds the core framework JAR. -->
+ <target name="framework"
+ description="Build the framework module.">
+ <uncommons:compile module="framework" />
+ <uncommons:jar module="framework" jarfile="${artifact.identifier}.jar" />
+ </target>
+
+
+ <!-- Builds the GUI module. -->
+ <target name="swing"
+ depends="framework"
+ description="Build the GUI module.">
+ <uncommons:compile module="swing" />
+ <uncommons:jar module="swing" jarfile="watchmaker-swing-${version}.jar" />
+ </target>
+
+
+ <!-- Builds the examples JAR, which depends on each of the other modules. -->
+ <target name="examples"
+ depends="framework, swing"
+ description="Build the examples.">
+ <uncommons:compile module="examples" />
+ <uncommons:jar module="examples"
+ jarfile="watchmaker-examples-${version}.jar"
+ classpath="${artifact.identifier}.jar lib/uncommons-maths-1.2.2.jar lib/google-collect-1.0.jar watchmaker-swing-${version}.jar lib/jfreechart-1.0.13.jar lib/jcommon-1.0.16.jar"
+ mainclass="org.uncommons.watchmaker.examples.Launcher" />
+ </target>
+
+
+ <!-- Copy all necessary files to distribution directory. -->
+ <target name="dist"
+ depends="framework, swing, examples"
+ description="Generate the project distribution." >
+ <uncommons:dist />
+ <mkdir dir="${dist.dir}/src" />
+ <copy todir="${dist.dir}/src" flatten="true">
+ <fileset dir="." includes="**/${build.dir}/*-src.jar"/>
+ </copy>
+ </target>
+
+
+ <!-- Build source JAR files for inclusion in the release. -->
+ <target name="source" description="Build source JARs.">
+ <uncommons:source module="framework" jarfile="${artifact.identifier}-src.jar" />
+ <uncommons:source module="swing" jarfile="watchmaker-swing-${version}-src.jar" />
+ </target>
+
+
+ <!-- Create the release artifacts. -->
+ <target name="release"
+ depends="clean, source, dist, test, checkstyle, docs"
+ description="Creates the release archives.">
+ <uncommons:release name="${artifact.identifier}">
+ <additionalcontents>
+ <tarfileset dir="examples/${java.dir}/main"
+ prefix="${artifact.identifier}/examples/src"
+ includes="**/*" />
+ </additionalcontents>
+ </uncommons:release>
+ </target>
+
+
+ <target name="release-maven"
+ depends="clean, dist"
+ description="Deploys the software to the Java.net Maven repository.">
+ <uncommons:maven-deploy module="framework"
+ version="${version}"
+ username="${maven.user}"
+ password="${maven.password}"/>
+ <uncommons:maven-deploy module="swing"
+ version="${version}"
+ username="${maven.user}"
+ password="${maven.password}"/>
+ </target>
+
+
+
+<!-- ==================================================================
+ TARGETS FOR GENERATING TEST REPORTS & DOCUMENTATION
+ =================================================================== -->
+
+ <!-- Runs unit tests for all modules. -->
+ <target name="test"
+ depends="dist"
+ description="Run the unit test suite.">
+ <!-- Don't run FEST tests in a headless environment (they will fail) -->
+ <condition property="tests.file" value="testng-headless.xml" else="testng.xml">
+ <isset property="headless" />
+ </condition>
+ <uncommons:test suites="${conf.dir}/${tests.file}"
+ headless="${headless}"
+ title="Watchmaker Framework Unit Test Report"
+ mincoverage="${minimum.coverage}" />
+ </target>
+
+
+ <target name="checkstyle"
+ depends="dist"
+ description="Check that coding standard are adhered to.">
+ <taskdef resource="checkstyletask.properties" classpathref="tool.path"/>
+ <mkdir dir="${checkstyle-results.dir}" />
+
+ <!-- Compiled classes must be available on the classpath to work-around this
+ bug (http://jira.codehaus.org/browse/MPCHECKSTYLE-20). That is why this
+ target depends on the 'dist' target. -->
+ <path id="checkstyle.path">
+ <fileset dir="${dist.dir}" includes="**/*.jar" />
+ </path>
+
+ <checkstyle config="${conf.dir}/checks.xml"
+ failonviolation="false"
+ classpathref="checkstyle.path">
+ <fileset dir="." defaultexcludes="yes">
+ <include name="**/${java.dir}/main/org/uncommons/**/*.java"/>
+ </fileset>
+ <formatter type="xml" tofile="${checkstyle-results.dir}/checkstyle_report.xml"/>
+ </checkstyle>
+ <xslt in="${checkstyle-results.dir}/checkstyle_report.xml"
+ out="${checkstyle-results.dir}/checkstyle_report.html"
+ style="${conf.dir}/checkstyle-noframes-sorted.xsl" />
+ </target>
+
+
+ <!-- Generates API documentation for all modules. -->
+ <target name="docs"
+ description="Generates Javadoc API documentation for all modules.">
+ <uncommons:javadoc title="Watchmaker Framework for Evolutionary Computation API"
+ version="${version}"
+ excludes="examples/**/*,framework/${java.dir}/main/org/uncommons/util/**/*">
+ <additionalconfig>
+ <group title="Watchmaker Evolution Framework" packages="org.uncommons.watchmaker.framework:org.uncommons.watchmaker.framework.*"/>
+ <group title="Watchmaker Swing Classes" packages="org.uncommons.swing:org.uncommons.swing.*:org.uncommons.watchmaker.swing:org.uncommons.watchmaker.swing.*"/>
+ <link href="http://maths.uncommons.org/api/"/>
+ </additionalconfig>
+ </uncommons:javadoc>
+ </target>
+
+
+ <target name="book" description="Generates the PDF user guide.">
+ <mkdir dir="book/${build.dir}" />
+ <uncommons:docbook classpathref="tool.path"
+ source="book/${src.dir}/xml/book.xml"
+ format="pdf"
+ outputDir="book/${build.dir}" >
+ <parameter name="paper.type" value="A4" />
+ <parameter name="highlight.source" value="1" />
+ <parameter name="img.src.path" value="./book/src/resources/" />
+ </uncommons:docbook>
+ </target>
+
+
+<!-- ==================================================================
+ TARGETS FOR UPDATING THE PROJECT WEBSITE
+ =================================================================== -->
+
+ <target name="website-docs"
+ description="Re-builds the website Javadocs."
+ depends="dist">
+ <!-- Delete all existing HTML files and then regenerate the docs over the top. -->
+ <delete>
+ <fileset dir="${web.dir}">
+ <include name="api/**/*.html" />
+ <include name="manual/**/*" />
+ </fileset>
+ </delete>
+
+ <!-- Refresh the API documentation tree for the Watchmaker Framework website. -->
+ <uncommons:javadoc dir="${web.dir}/api"
+ title="Watchmaker Framework for Evolutionary Computation API"
+ version="${version}"
+ excludes="examples/**/*,framework/${java.dir}/main/org/uncommons/util/**/*">
+ <additionalconfig>
+ <group title="Watchmaker Evolution Framework" packages="org.uncommons.watchmaker.framework:org.uncommons.watchmaker.framework.*"/>
+ <group title="Watchmaker Swing Classes" packages="org.uncommons.swing:org.uncommons.swing.*:org.uncommons.watchmaker.swing:org.uncommons.watchmaker.swing.*"/>
+ <link href="http://maths.uncommons.org/api/"/>
+ </additionalconfig>
+ </uncommons:javadoc>
+
+ <copy todir="${web.dir}" file="./CHANGELOG.txt">
+ <filterset>
+ <filter token="VERSION" value="${version}"/>
+ </filterset>
+ </copy>
+
+ <!-- Copy latest jars into website examples directory. -->
+ <copy todir="${web.dir}/examples">
+ <fileset dir="${dist.dir}" includes="*.jar" />
+ </copy>
+ <copy todir="${web.dir}/examples/lib">
+ <fileset dir="${dist.dir}/${lib.dir}" includes="*.jar" />
+ </copy>
+
+ <!-- Generate user manual from DocBook source. -->
+ <uncommons:docbook classpathref="tool.path"
+ source="book/${src.dir}/xml/website.xml"
+ format="html"
+ chunked="true"
+ outputDir="${web.dir}/manual" >
+ <parameter name="highlight.source" value="1" />
+ <parameter name="img.src.path" value="./" />
+ <parameter name="html.stylesheet" value="docbook.css" />
+ </uncommons:docbook>
+ <copy todir="${web.dir}/manual">
+ <fileset dir="book/${src.dir}/resources" includes="**/*"/>
+ </copy>
+ </target>
+
+</project>
diff --git a/watchmaker/etc/checks.xml b/watchmaker/etc/checks.xml
new file mode 100644
index 0000000..b9e5e82
--- /dev/null
+++ b/watchmaker/etc/checks.xml
@@ -0,0 +1,164 @@
+<?xml version="1.0"?>
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.2//EN"
+ "http://www.puppycrawl.com/dtds/configuration_1_2.dtd">
+
+<!-- Checkstyle configuration for uncommons.org Coding Standards. -->
+<module name="Checker">
+
+ <property name="severity" value="error"/>
+
+ <module name="NewlineAtEndOfFile"/>
+
+ <module name="TreeWalker">
+
+ <!-- Documentation. -->
+ <module name="Header">
+ <property name="headerFile" value="${conf.dir}/java.header"/>
+ <property name="ignoreLines" value="2"/>
+ </module>
+ <module name="JavadocMethod">
+ <property name="severity" value="warning"/>
+ <property name="scope" value="protected"/>
+ <property name="allowUndeclaredRTE" value="true" />
+ <property name="allowThrowsTagsForSubclasses" value="true" />
+ <property name="allowMissingPropertyJavadoc" value="true" />
+ </module>
+ <module name="JavadocStyle"/>
+ <module name="JavadocType"/>
+ <module name="JavadocVariable">
+ <property name="severity" value="warning"/>
+ <property name="scope" value="package"/>
+ </module>
+
+
+ <!-- Imports. -->
+ <module name="AvoidStarImport"/>
+ <module name="IllegalImport"/>
+ <module name="RedundantImport"/>
+ <module name="UnusedImports"/>
+ <module name="ImportOrder" />
+
+
+ <!-- Length. -->
+ <module name="AnonInnerLength">
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="FileLength"/>
+ <module name="LineLength">
+ <property name="max" value="120"/>
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="MethodLength"/>
+ <module name="ParameterNumber">
+ <!-- Constuctors can have as many as needed, only check methods. -->
+ <property name="tokens" value="METHOD_DEF"/>
+ <property name="severity" value="warning"/>
+ </module>
+
+
+ <!-- Naming conventions. -->
+ <module name="ConstantName"/>
+ <module name="LocalFinalVariableName"/>
+ <module name="LocalVariableName"/>
+ <module name="MemberName"/>
+ <module name="MethodName"/>
+ <module name="PackageName"/>
+ <module name="ParameterName"/>
+ <module name="StaticVariableName"/>
+ <module name="TypeName"/>
+
+
+ <!-- Whitespace. -->
+ <module name="EmptyForIteratorPad"/>
+ <module name="Indentation">
+ <!-- This should be an error rather than a warning but it won't accept single-line case statements. -->
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="MethodParamPad"/>
+ <module name="NoWhitespaceAfter"/>
+ <module name="NoWhitespaceBefore"/>
+ <module name="OperatorWrap"/>
+ <module name="ParenPad"/>
+ <module name="TypecastParenPad"/>
+ <module name="TabCharacter"/>
+ <module name="WhitespaceAfter"/>
+ <module name="WhitespaceAround">
+ <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR, BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN, EQUAL, GE, GT, LAND, LCURLY, LE, LITERAL_ASSERT, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE, LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN, LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS, MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION, RCURLY, SL, SLIST, SL_ASSIGN, SR, SR_ASSIGN, STAR, STAR_ASSIGN, TYPE_EXTENSION_AND"/>
+ </module>
+
+
+ <!-- Modifiers. -->
+ <module name="ModifierOrder"/>
+ <module name="RedundantModifier"/>
+
+
+ <!-- Blocks. -->
+ <module name="AvoidNestedBlocks">
+ <property name="allowInSwitchCase" value="true"/>
+ </module>
+ <module name="EmptyBlock">
+ <property name="option" value="text"/>
+ </module>
+ <module name="LeftCurly">
+ <property name="option" value="nl"/>
+ </module>
+ <module name="NeedBraces"/>
+ <module name="RightCurly">
+ <property name="option" value="alone"/>
+ </module>
+
+
+ <!-- Exceptions. -->
+ <module name="IllegalCatch">
+ <property name="severity" value="warning"/>
+ </module>
+
+
+ <!-- Inheritance -->
+ <module name="SuperClone"/>
+ <module name="SuperFinalize"/>
+
+
+ <!-- Probable bugs or confusing constructs. -->
+ <module name="BooleanExpressionComplexity"/>
+ <module name="CovariantEquals"/>
+ <module name="DefaultComesLast"/>
+ <module name="DoubleCheckedLocking"/>
+ <module name="EmptyStatement"/>
+ <module name="EqualsHashCode"/>
+ <module name="HiddenField">
+ <property name="ignoreConstructorParameter" value="true"/>
+ <property name="ignoreSetter" value="true"/>
+ </module>
+ <module name="InnerAssignment"/>
+ <module name="MissingSwitchDefault"/>
+ <module name="ModifiedControlVariable"/>
+ <module name="RedundantThrows"/>
+ <module name="SimplifyBooleanExpression"/>
+ <module name="SimplifyBooleanReturn"/>
+ <module name="StringLiteralEquality"/>
+ <module name="UpperEll"/>
+
+
+ <!-- Class design. -->
+ <module name="FinalClass"/>
+ <module name="HideUtilityClassConstructor">
+ <property name="severity" value="warning"/>
+ </module>
+ <module name="InterfaceIsType">
+ <property name="allowMarkerInterfaces" value="false" />
+ </module>
+ <module name="PackageDeclaration"/>
+ <module name="VisibilityModifier">
+ <property name="protectedAllowed" value="true" />
+ </module>
+
+
+ <!-- Miscellaneous style issues. -->
+ <module name="ArrayTypeStyle"/>
+ <module name="DeclarationOrder"/>
+ <module name="MultipleVariableDeclarations"/>
+
+ </module>
+
+</module>
diff --git a/watchmaker/etc/checkstyle-noframes-sorted.xsl b/watchmaker/etc/checkstyle-noframes-sorted.xsl
new file mode 100644
index 0000000..e1aff38
--- /dev/null
+++ b/watchmaker/etc/checkstyle-noframes-sorted.xsl
@@ -0,0 +1,178 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:output method="html" indent="yes"/>
+<xsl:decimal-format decimal-separator="." grouping-separator="," />
+
+<xsl:key name="files" match="file" use="@name" />
+
+<!-- Checkstyle XML Style Sheet by Stephane Bailliez <sbailliez@apache.org> -->
+<!-- Part of the Checkstyle distribution found at http://checkstyle.sourceforge.net -->
+<!-- Usage (generates checkstyle_report.html): -->
+<!-- <checkstyle failonviolation="false" config="${check.config}"> -->
+<!-- <fileset dir="${src.dir}" includes="**/*.java"/> -->
+<!-- <formatter type="xml" toFile="${doc.dir}/checkstyle_report.xml"/> -->
+<!-- </checkstyle> -->
+<!-- <style basedir="${doc.dir}" destdir="${doc.dir}" -->
+<!-- includes="checkstyle_report.xml" -->
+<!-- style="${doc.dir}/checkstyle-noframes-sorted.xsl"/> -->
+
+<xsl:template match="checkstyle">
+ <html>
+ <head>
+ <style type="text/css">
+ .bannercell {
+ border: 0px;
+ padding: 0px;
+ }
+ body {
+ margin-left: 10;
+ margin-right: 10;
+ font:normal 80% arial,helvetica,sanserif;
+ background-color:#FFFFFF;
+ color:#000000;
+ }
+ .a td {
+ background: #efefef;
+ }
+ .b td {
+ background: #fff;
+ }
+ th, td {
+ text-align: left;
+ vertical-align: top;
+ }
+ th {
+ font-weight:bold;
+ background: #ccc;
+ color: black;
+ }
+ table, th, td {
+ font-size:100%;
+ border: none
+ }
+ table.log tr td, tr th {
+
+ }
+ h2 {
+ font-weight:bold;
+ font-size:140%;
+ margin-bottom: 5;
+ }
+ h3 {
+ font-size:100%;
+ font-weight:bold;
+ background: #525D76;
+ color: white;
+ text-decoration: none;
+ padding: 5px;
+ margin-right: 2px;
+ margin-left: 2px;
+ margin-bottom: 0;
+ }
+ </style>
+ </head>
+ <body>
+ <a name="top"></a>
+ <!-- jakarta logo -->
+ <table border="0" cellpadding="0" cellspacing="0" width="100%">
+ <tr>
+ <td class="bannercell" rowspan="2">
+ <!--a href="http://jakarta.apache.org/">
+ <img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>
+ </a-->
+ </td>
+ <td class="text-align:right"><h2>CheckStyle Audit</h2></td>
+ </tr>
+ <tr>
+ <td class="text-align:right">Designed for use with <a href='http://checkstyle.sourceforge.net/'>CheckStyle</a> and <a href='http://jakarta.apache.org'>Ant</a>.</td>
+ </tr>
+ </table>
+ <hr size="1"/>
+
+ <!-- Summary part -->
+ <xsl:apply-templates select="." mode="summary"/>
+ <hr size="1" width="100%" align="left"/>
+
+ <!-- Package List part -->
+ <xsl:apply-templates select="." mode="filelist"/>
+ <hr size="1" width="100%" align="left"/>
+
+ <!-- For each package create its part -->
+ <xsl:apply-templates select="file[@name and generate-id(.) = generate-id(key('files', @name))]" />
+
+ <hr size="1" width="100%" align="left"/>
+
+
+ </body>
+ </html>
+</xsl:template>
+
+
+
+ <xsl:template match="checkstyle" mode="filelist">
+ <h3>Files</h3>
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+ <tr>
+ <th>Name</th>
+ <th>Errors</th>
+ </tr>
+ <xsl:for-each select="file[@name and generate-id(.) = generate-id(key('files', @name))]">
+ <xsl:sort data-type="number" order="descending" select="count(key('files', @name)/error)"/>
+ <xsl:variable name="errorCount" select="count(error)"/>
+ <tr>
+ <xsl:call-template name="alternated-row"/>
+ <td><a href="#f-{@name}"><xsl:value-of select="@name"/></a></td>
+ <td><xsl:value-of select="$errorCount"/></td>
+ </tr>
+ </xsl:for-each>
+ </table>
+ </xsl:template>
+
+
+ <xsl:template match="file">
+ <a name="f-{@name}"></a>
+ <h3>File <xsl:value-of select="@name"/></h3>
+
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+ <tr>
+ <th>Error Description</th>
+ <th>Line</th>
+ </tr>
+ <xsl:for-each select="key('files', @name)/error">
+ <xsl:sort data-type="number" order="ascending" select="@line"/>
+ <tr>
+ <xsl:call-template name="alternated-row"/>
+ <td><xsl:value-of select="@message"/></td>
+ <td><xsl:value-of select="@line"/></td>
+ </tr>
+ </xsl:for-each>
+ </table>
+ <a href="#top">Back to top</a>
+ </xsl:template>
+
+
+ <xsl:template match="checkstyle" mode="summary">
+ <h3>Summary</h3>
+ <xsl:variable name="fileCount" select="count(file[@name and generate-id(.) = generate-id(key('files', @name))])"/>
+ <xsl:variable name="errorCount" select="count(file/error)"/>
+ <table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
+ <tr>
+ <th>Files</th>
+ <th>Errors</th>
+ </tr>
+ <tr>
+ <xsl:call-template name="alternated-row"/>
+ <td><xsl:value-of select="$fileCount"/></td>
+ <td><xsl:value-of select="$errorCount"/></td>
+ </tr>
+ </table>
+ </xsl:template>
+
+ <xsl:template name="alternated-row">
+ <xsl:attribute name="class">
+ <xsl:if test="position() mod 2 = 1">a</xsl:if>
+ <xsl:if test="position() mod 2 = 0">b</xsl:if>
+ </xsl:attribute>
+ </xsl:template>
+</xsl:stylesheet>
+
+
diff --git a/watchmaker/etc/java.header b/watchmaker/etc/java.header
new file mode 100644
index 0000000..61bdfc5
--- /dev/null
+++ b/watchmaker/etc/java.header
@@ -0,0 +1,15 @@
+//=============================================================================
+// 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.
+//=============================================================================
diff --git a/watchmaker/etc/testng-headless.xml b/watchmaker/etc/testng-headless.xml
new file mode 100644
index 0000000..262a7a8
--- /dev/null
+++ b/watchmaker/etc/testng-headless.xml
@@ -0,0 +1,34 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="Unit Tests" verbose="1" >
+
+ <test name="Evolution Framework" >
+ <packages>
+ <package name="org.uncommons.watchmaker.framework.*" />
+ </packages>
+ </test>
+
+ <test name="Swing Module" >
+ <!-- Don't run FEST tests in a headless environment. -->
+ <groups><run><exclude name="display-required" /></run></groups>
+ <packages>
+ <package name="org.uncommons.swing.*" />
+ <package name="org.uncommons.watchmaker.swing.*" />
+ </packages>
+ </test>
+
+ <test name="Examples" >
+ <!-- Don't run FEST tests in a headless environment. -->
+ <groups><run><exclude name="display-required" /></run></groups>
+ <packages>
+ <package name="org.uncommons.watchmaker.examples.*" />
+ </packages>
+ </test>
+
+ <test name="Utilities Module" >
+ <packages>
+ <package name="org.uncommons.util.*" />
+ </packages>
+ </test>
+
+</suite>
diff --git a/watchmaker/etc/testng.xml b/watchmaker/etc/testng.xml
new file mode 100644
index 0000000..70a2bfb
--- /dev/null
+++ b/watchmaker/etc/testng.xml
@@ -0,0 +1,31 @@
+<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
+
+<suite name="Unit Tests" verbose="1" >
+
+ <test name="Evolution Framework" >
+ <packages>
+ <package name="org.uncommons.watchmaker.framework.*" />
+ </packages>
+ </test>
+
+ <test name="Swing Module" >
+ <packages>
+ <package name="org.uncommons.swing.*" />
+ <package name="org.uncommons.watchmaker.swing.*" />
+ </packages>
+ </test>
+
+ <test name="Examples" >
+ <packages>
+ <package name="org.uncommons.watchmaker.examples.*" />
+ </packages>
+ </test>
+
+ <test name="Utilities Module" >
+ <packages>
+ <package name="org.uncommons.util.*" />
+ </packages>
+ </test>
+
+</suite>
+ \ No newline at end of file
diff --git a/watchmaker/examples/examples.iml b/watchmaker/examples/examples.iml
new file mode 100644
index 0000000..6f94484
--- /dev/null
+++ b/watchmaker/examples/examples.iml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java/main" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/java/resources" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/java/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="swing" />
+ <orderEntry type="module" module-name="framework" />
+ <orderEntry type="library" name="Maven: org.uncommons.maths:uncommons-maths:1.2.2" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jcommon:1.0.12" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jfreechart:1.0.13" level="project" />
+ <orderEntry type="library" name="Maven: com.google.collections:google-collections:1.0" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.testng:testng:6.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: junit:junit:3.8.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.beanshell:bsh:2.0b4" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: com.beust:jcommander:1.12" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.yaml:snakeyaml:1.6" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-swing:1.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-assert:1.2" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-util:1.1.3" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-reflect:1.2" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: net.jcip:jcip-annotations:1.0" level="project" />
+ </component>
+</module>
+
diff --git a/watchmaker/examples/nb-configuration.xml b/watchmaker/examples/nb-configuration.xml
new file mode 100644
index 0000000..ae35717
--- /dev/null
+++ b/watchmaker/examples/nb-configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>words</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>0</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>
+ </properties>
+</project-shared-configuration>
diff --git a/watchmaker/examples/pom.xml b/watchmaker/examples/pom.xml
new file mode 100644
index 0000000..773b7d2
--- /dev/null
+++ b/watchmaker/examples/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--===========================================================================
+ 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.
+ ==========================================================================-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.uncommons.watchmaker</groupId>
+ <artifactId>watchmaker</artifactId>
+ <version>0.7.2</version>
+ </parent>
+ <artifactId>watchmaker-examples</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>watchmaker-swing</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easytesting</groupId>
+ <artifactId>fest-swing</artifactId>
+ <version>1.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src/java/main</sourceDirectory>
+ <testSourceDirectory>src/java/test</testSourceDirectory>
+ <resources>
+ <resource>
+ <directory>src/java/resources</directory>
+ </resource>
+ </resources>
+ </build>
+</project>
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java
new file mode 100644
index 0000000..8bf600a
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/AbstractExampleApplet.java
@@ -0,0 +1,102 @@
+//=============================================================================
+// 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;
+
+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;
+
+/**
+ * Base class for examples that run as applets.
+ * @author Daniel Dyer
+ */
+public abstract class AbstractExampleApplet extends JApplet
+{
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void init()
+ {
+ configure(this);
+ }
+
+
+ /**
+ * Configure the program to display its GUI in the specified container.
+ * @param container The container to place the GUI components in.
+ */
+ private void configure(final Container container)
+ {
+ try
+ {
+ // Use invokeAndWait so that we can be sure that initialisation is complete
+ // before continuing.
+ SwingUtilities.invokeAndWait(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ }
+ catch (Exception ex)
+ {
+ // This should never happen as we are installing a known look-and-feel.
+ System.err.println("Failed to load System look-and-feel.");
+ }
+ prepareGUI(container);
+ }
+ });
+ }
+ catch (InterruptedException ex)
+ {
+ // Restore interrupt flag.
+ Thread.currentThread().interrupt();
+ }
+ catch (InvocationTargetException ex)
+ {
+ ex.getCause().printStackTrace();
+ JOptionPane.showMessageDialog(container, ex.getCause(), "Error Occurred", JOptionPane.ERROR_MESSAGE);
+ }
+ }
+
+
+ /**
+ * Implemented in sub-classes to initialise and layout the GUI.
+ * @param container The container that this method should add components to.
+ */
+ protected abstract void prepareGUI(Container container);
+
+
+ /**
+ * Display this example program using a JFrame as the top-level GUI container (rather
+ * than running the example as an applet).
+ * @param title The text to use for the frame's title bar.
+ */
+ protected void displayInFrame(String title)
+ {
+ JFrame frame = new JFrame(title);
+ frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ configure(frame);
+ frame.pack();
+ frame.setVisible(true);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java
new file mode 100644
index 0000000..5c47146
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/EvolutionLogger.java
@@ -0,0 +1,39 @@
+//=============================================================================
+// 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;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver;
+
+/**
+ * Trivial evolution observer for displaying information at the end
+ * of each generation.
+ * @param <T> The type of entity being evolved.
+ * @author Daniel Dyer
+ */
+public class EvolutionLogger<T> implements IslandEvolutionObserver<T>
+{
+ public void populationUpdate(PopulationData<? extends T> data)
+ {
+ System.out.println("Generation " + data.getGenerationNumber() + ": " + data.getBestCandidateFitness());
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends T> populationData)
+ {
+ // Do nothing.
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/Launcher.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/Launcher.java
new file mode 100644
index 0000000..b4b5b7e
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/Launcher.java
@@ -0,0 +1,79 @@
+//=============================================================================
+// 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;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import org.uncommons.util.reflection.ReflectionUtils;
+import org.uncommons.watchmaker.examples.biomorphs.BiomorphApplet;
+import org.uncommons.watchmaker.examples.bits.BitsExample;
+import org.uncommons.watchmaker.examples.geneticprogramming.GeneticProgrammingExample;
+import org.uncommons.watchmaker.examples.monalisa.MonaLisaApplet;
+import org.uncommons.watchmaker.examples.strings.StringsExample;
+import org.uncommons.watchmaker.examples.sudoku.SudokuApplet;
+import org.uncommons.watchmaker.examples.travellingsalesman.TravellingSalesmanApplet;
+
+/**
+ * Launcher for Watchmaker example applications.
+ * @author Daniel Dyer
+ */
+public class Launcher
+{
+ private static final Map<String, Class<?>> EXAMPLES = new LinkedHashMap<String, Class<?>>();
+ static
+ {
+ EXAMPLES.put("biomorphs", BiomorphApplet.class);
+ EXAMPLES.put("bits", BitsExample.class);
+ EXAMPLES.put("gp", GeneticProgrammingExample.class);
+ EXAMPLES.put("monalisa", MonaLisaApplet.class);
+ EXAMPLES.put("salesman", TravellingSalesmanApplet.class);
+ EXAMPLES.put("strings", StringsExample.class);
+ EXAMPLES.put("sudoku", SudokuApplet.class);
+ }
+
+
+ private Launcher()
+ {
+ // Prevents instantiation of launcher class.
+ }
+
+
+ /**
+ * Launch the specified example application from the command-line.
+ * @param args First item is the name of the example to run. Any subsequent arguments are passed
+ * on to the specific example.
+ */
+ public static void main(String[] args)
+ {
+ Class<?> exampleClass = args.length > 0 ? EXAMPLES.get(args[0]) : null;
+ if (exampleClass == null)
+ {
+ System.err.println("First argument must be the name of an example, i.e. one of "
+ + Arrays.toString(EXAMPLES.keySet().toArray()));
+ System.exit(1);
+ }
+
+ // All args except the first one should be passed to the example application.
+ String[] appArgs = new String[args.length - 1];
+ System.arraycopy(args, 1, appArgs, 0, appArgs.length);
+
+ // Invoke the main method for the selected example application.
+ Method main = ReflectionUtils.findKnownMethod(exampleClass, "main", String[].class);
+ ReflectionUtils.invokeUnchecked(main, exampleClass, new Object[]{appArgs});
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/Biomorph.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/Biomorph.java
new file mode 100644
index 0000000..7fe6fd5
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/Biomorph.java
@@ -0,0 +1,160 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.Arrays;
+
+/**
+ * <p>Candidate representation for a biomorph. We could just as easily have
+ * used an array of integers or a bit string representation with Gray codes
+ * but for clarity we will use this more object-oriented representation that
+ * conveniently combines state and related logic.</p>
+ *
+ * <p>The Biomporph class encapsulates 9 genes as described by Dawkins in his
+ * "The Evolution of Evolvability" paper.</p>
+ *
+ * @author Daniel Dyer
+ */
+public final class Biomorph
+{
+ /** The total number of genes that make up a biomorph. */
+ public static final int GENE_COUNT = 9;
+ /** The minimum permitted value for most genes. */
+ public static final int GENE_MIN = -5;
+ /** The maximum permitted value for most genes. */
+ public static final int GENE_MAX = 5;
+ /** The index of the gene that controls biomporph size. */
+ public static final int LENGTH_GENE_INDEX = 8;
+ /** The minimum permitted value for the length gene. */
+ public static final int LENGTH_GENE_MIN = 1;
+ /** The maximum permitted value for the length gene. */
+ public static final int LENGTH_GENE_MAX = 7;
+
+ private final int[] genes;
+
+ private int[][] phenotype;
+
+
+ /**
+ * Creates a biomorph with the specified genes.
+ * @param genes A 9-element array. The final element must be a positive
+ * value.
+ */
+ public Biomorph(int[] genes)
+ {
+ if (genes.length != GENE_COUNT)
+ {
+ throw new IllegalArgumentException("Biomorph must have " + GENE_COUNT + " genes.");
+ }
+ this.genes = genes.clone();
+ }
+
+
+ /**
+ * Returns an array of integers that represent the graphical pattern
+ * determined by the biomorph's genes.
+ * @return A 2-dimensional array containing the 8-element dx and dy
+ * arrays required to draw the biomorph.
+ */
+ public int[][] getPatternPhenotype()
+ {
+ if (phenotype == null)
+ {
+ // Decode the genes as per Dawkins' rules.
+ int[] dx = new int[GENE_COUNT - 1];
+ dx[3] = genes[0];
+ dx[4] = genes[1];
+ dx[5] = genes[2];
+
+ dx[1] = -dx[3];
+ dx[0] = -dx[4];
+ dx[7] = -dx[5];
+
+ dx[2] = 0;
+ dx[6] = 0;
+
+ int[] dy = new int[GENE_COUNT - 1];
+ dy[2] = genes[3];
+ dy[3] = genes[4];
+ dy[4] = genes[5];
+ dy[5] = genes[6];
+ dy[6] = genes[7];
+
+ dy[0] = dy[4];
+ dy[1] = dy[3];
+ dy[7] = dy[5];
+
+ phenotype = new int[][]{dx, dy};
+ }
+ return phenotype;
+ }
+
+
+ /**
+ * @return The value of the gene that controls the size of this biomorph.
+ */
+ public int getLengthPhenotype()
+ {
+ return genes[LENGTH_GENE_INDEX];
+ }
+
+
+ /**
+ * Returns the 9 genes that make up the biomorph's genotype.
+ * @return A 9-element array containing this biomorph's genes.
+ */
+ public int[] getGenotype()
+ {
+ return genes.clone();
+ }
+
+
+ /**
+ * Compares the genes of two Biomporph objects and returns true if they are
+ * identical.
+ * @param obj The object to compare with this one.
+ * @return True if the argument is a Biomoprh instance and the 2
+ * biomorphs have the same genes, false otherwise.
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass())
+ {
+ return false;
+ }
+
+ Biomorph biomorph = (Biomorph) obj;
+
+ return Arrays.equals(genes, biomorph.genes);
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #equals(Object)}. Biomorphs
+ * with identical genes return identical hash codes.
+ * @return This object's hash code.
+ */
+ @Override
+ public int hashCode()
+ {
+ return Arrays.hashCode(genes);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java
new file mode 100644
index 0000000..0766422
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphApplet.java
@@ -0,0 +1,241 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+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 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.interactive.InteractiveSelection;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+import org.uncommons.watchmaker.swing.SwingConsole;
+
+/**
+ * Watchmaker Framework implementation of Dawkin's biomorph program.
+ * @author Daniel Dyer
+ */
+public class BiomorphApplet extends AbstractExampleApplet
+{
+ private Renderer<Biomorph, JComponent> renderer;
+ private SwingConsole console;
+ private JDialog selectionDialog;
+ private JPanel biomorphHolder;
+
+
+ /**
+ * Initialise and layout the GUI.
+ * @param container The Swing component that will contain the GUI controls.
+ */
+ @Override
+ protected void prepareGUI(Container container)
+ {
+ renderer = new SwingBiomorphRenderer();
+ console = new SwingConsole(5);
+ selectionDialog = new JDialog((JFrame) null, "Biomorph Selection", true);
+ biomorphHolder = new JPanel(new GridLayout(1, 1));
+
+ container.add(new ControlPanel(), BorderLayout.WEST);
+ container.add(biomorphHolder, BorderLayout.CENTER);
+ biomorphHolder.setBorder(BorderFactory.createTitledBorder("Last Evolved Biomorph"));
+ biomorphHolder.add(new JLabel("Nothing generated yet.", JLabel.CENTER));
+ selectionDialog.add(console, BorderLayout.CENTER);
+ selectionDialog.setSize(800, 600);
+ selectionDialog.validate();
+ }
+
+
+ /**
+ * Helper method to create a background task for running the interactive evolutionary
+ * algorithm.
+ * @param populationSize How big the population used by the created evolution engine
+ * should be.
+ * @param generationCount How many generations to use when the evolution engine is
+ * invoked.
+ * @param random If true use random mutation, otherwise use Dawkins mutation.
+ * @return A Swing task that will execute on a background thread and update
+ * the GUI when it is done.
+ */
+ private SwingBackgroundTask<Biomorph> createTask(final int populationSize,
+ final int generationCount,
+ final boolean random)
+ {
+ return new SwingBackgroundTask<Biomorph>()
+ {
+ @Override
+ protected Biomorph performTask()
+ {
+ EvolutionaryOperator<Biomorph> mutation = random ? new RandomBiomorphMutation(new Probability(0.12d))
+ : new DawkinsBiomorphMutation();
+ InteractiveSelection<Biomorph> selection = new InteractiveSelection<Biomorph>(console,
+ renderer,
+ populationSize,
+ 1);
+ EvolutionEngine<Biomorph> engine = new GenerationalEvolutionEngine<Biomorph>(new BiomorphFactory(),
+ mutation,
+ selection,
+ new MersenneTwisterRNG());
+ engine.addEvolutionObserver(new GenerationTracker());
+ return engine.evolve(populationSize,
+ 0,
+ new GenerationCount(generationCount));
+ }
+
+ @Override
+ protected void postProcessing(Biomorph result)
+ {
+ selectionDialog.setVisible(false);
+ biomorphHolder.removeAll();
+ biomorphHolder.add(renderer.render(result));
+ biomorphHolder.revalidate();
+ }
+ };
+ }
+
+
+ /**
+ * Entry point for running this example as an application rather than an applet.
+ * @param args Program arguments (ignored).
+ */
+ 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<Biomorph>
+ {
+ public void populationUpdate(final PopulationData<? extends Biomorph> populationData)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ selectionDialog.setTitle("Biomorph Selection - Generation "
+ + (populationData.getGenerationNumber() + 1));
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Panel for controlling the evolutionary algorithm parameters.
+ */
+ private final class ControlPanel extends JPanel
+ {
+ private JSpinner populationSpinner;
+ private JSpinner generationsSpinner;
+ private JComboBox mutationCombo;
+
+ ControlPanel()
+ {
+ super(new BorderLayout());
+ add(createInputPanel(), BorderLayout.NORTH);
+ add(createButtonPanel(), BorderLayout.SOUTH);
+ setBorder(BorderFactory.createTitledBorder("Evolution Controls"));
+ }
+
+
+ private JComponent createInputPanel()
+ {
+ JPanel inputPanel = new JPanel(new SpringLayout());
+ JLabel populationLabel = new JLabel("Population Size: ");
+ populationSpinner = new JSpinner(new SpinnerNumberModel(18, 2, 25, 1));
+ populationSpinner.setEnabled(false);
+ populationLabel.setLabelFor(populationSpinner);
+ inputPanel.add(populationLabel);
+ inputPanel.add(populationSpinner);
+ JLabel generationsLabel = new JLabel("Number of Generations: ");
+ generationsSpinner = new JSpinner(new SpinnerNumberModel(20, 1, 100, 1));
+ generationsLabel.setLabelFor(generationsSpinner);
+ inputPanel.add(generationsLabel);
+ inputPanel.add(generationsSpinner);
+ JLabel mutationLabel = new JLabel("Mutation Type: ");
+ mutationCombo = new JComboBox(new String[]{"Dawkins (Non-random)", "Random"});
+ mutationCombo.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent itemEvent)
+ {
+ if (mutationCombo.getSelectedIndex() == 0)
+ {
+ populationSpinner.setValue(18);
+ populationSpinner.setEnabled(false);
+ }
+ else
+ {
+ populationSpinner.setEnabled(true);
+ }
+ }
+ });
+ inputPanel.add(mutationLabel);
+ inputPanel.add(mutationCombo);
+
+ SpringUtilities.makeCompactGrid(inputPanel, 3, 2, 30, 6, 6, 6);
+
+ return inputPanel;
+ }
+
+
+ private JComponent createButtonPanel()
+ {
+ JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton startButton = new JButton("Start");
+ startButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ createTask((Integer) populationSpinner.getValue(),
+ (Integer) generationsSpinner.getValue(),
+ mutationCombo.getSelectedIndex() == 1).execute();
+ selectionDialog.setVisible(true);
+ }
+ });
+ buttonPanel.add(startButton);
+ return buttonPanel;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactory.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactory.java
new file mode 100644
index 0000000..d081e40
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactory.java
@@ -0,0 +1,44 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.Random;
+import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory;
+
+/**
+ * Candidate factory for creating random biomorphs.
+ * @author Daniel Dyer
+ */
+public class BiomorphFactory extends AbstractCandidateFactory<Biomorph>
+{
+ /**
+ * Generates a random biomorph by providing a random value for each gene.
+ * @param rng The source of randomness used to generate the biomoprh.
+ * @return A randomly-generated biomorph.
+ */
+ public Biomorph generateRandomCandidate(Random rng)
+ {
+ int[] genes = new int[Biomorph.GENE_COUNT];
+ for (int i = 0; i < Biomorph.GENE_COUNT - 1; i++)
+ {
+ // First 8 genes have values between -5 and 5.
+ genes[i] = rng.nextInt(11) - 5;
+ }
+ // Last genes ha a value between 1 and 7.
+ genes[Biomorph.LENGTH_GENE_INDEX] = rng.nextInt(Biomorph.LENGTH_GENE_MAX) + 1;
+ return new Biomorph(genes);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java
new file mode 100644
index 0000000..f718a76
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutation.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+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.
+ * @author Daniel Dyer
+ */
+public class DawkinsBiomorphMutation implements EvolutionaryOperator<Biomorph>
+{
+ /**
+ * 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}
+ */
+ public List<Biomorph> apply(List<Biomorph> selectedCandidates, Random rng)
+ {
+ List<Biomorph> mutatedPopulation = new ArrayList<Biomorph>(selectedCandidates.size());
+ int mutatedGene = 0;
+ int mutation = 1;
+ for (Biomorph b : selectedCandidates)
+ {
+ int[] genes = b.getGenotype();
+
+ mutation *= -1; // Alternate between incrementing and decrementing.
+ if (mutation == 1) // After gene has been both incremented and decremented, move to next one.
+ {
+ 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;
+ if (genes[mutatedGene] > max)
+ {
+ genes[mutatedGene] = min;
+ }
+ else if (genes[mutatedGene] < min)
+ {
+ genes[mutatedGene] = max;
+ }
+
+ mutatedPopulation.add(new Biomorph(genes));
+ }
+ return mutatedPopulation;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java
new file mode 100644
index 0000000..a98cb55
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutation.java
@@ -0,0 +1,98 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation operator for biomorphs. Mutates each individual gene
+ * according to some mutation probability.
+ * @author Daniel Dyer
+ */
+public class RandomBiomorphMutation implements EvolutionaryOperator<Biomorph>
+{
+ private final Probability mutationProbability;
+
+ /**
+ * @param mutationProbability The probability that a given gene
+ * is changed.
+ */
+ public RandomBiomorphMutation(Probability mutationProbability)
+ {
+ this.mutationProbability = mutationProbability;
+ }
+
+
+ /**
+ * Randomly mutate each selected candidate.
+ * @param selectedCandidates {@inheritDoc}
+ * @param rng {@inheritDoc}
+ * @return {@inheritDoc}
+ */
+ public List<Biomorph> apply(List<Biomorph> selectedCandidates, Random rng)
+ {
+ List<Biomorph> mutatedPopulation = new ArrayList<Biomorph>(selectedCandidates.size());
+ for (Biomorph biomorph : selectedCandidates)
+ {
+ mutatedPopulation.add(mutateBiomorph(biomorph, rng));
+ }
+ return mutatedPopulation;
+ }
+
+
+ /**
+ * Mutates a single biomorph.
+ * @param biomorph The biomorph to mutate.
+ * @param rng The source of randomness to use for mutation.
+ * @return A mutated version of the biomorph.
+ */
+ private Biomorph mutateBiomorph(Biomorph biomorph, Random rng)
+ {
+ int[] genes = biomorph.getGenotype();
+ 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))
+ {
+ boolean increase = rng.nextBoolean();
+ genes[i] += (increase ? 1 : -1);
+ if (genes[i] > Biomorph.GENE_MAX)
+ {
+ genes[i] = Biomorph.GENE_MIN;
+ }
+ else if (genes[i] < Biomorph.GENE_MIN)
+ {
+ genes[i] = Biomorph.GENE_MAX;
+ }
+ }
+ }
+ boolean increase = rng.nextBoolean();
+ genes[Biomorph.LENGTH_GENE_INDEX] += (increase ? 1 : -1);
+ if (genes[Biomorph.LENGTH_GENE_INDEX] > Biomorph.LENGTH_GENE_MAX)
+ {
+ genes[Biomorph.LENGTH_GENE_INDEX] = Biomorph.LENGTH_GENE_MIN;
+ }
+ else if (genes[Biomorph.LENGTH_GENE_INDEX] < Biomorph.LENGTH_GENE_MIN)
+ {
+ genes[Biomorph.LENGTH_GENE_INDEX] = Biomorph.LENGTH_GENE_MAX;
+ }
+ return new Biomorph(genes);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/SwingBiomorphRenderer.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/SwingBiomorphRenderer.java
new file mode 100644
index 0000000..d6d8e82
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/SwingBiomorphRenderer.java
@@ -0,0 +1,111 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import javax.swing.JComponent;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+
+/**
+ * Renders Biomorphs as Swing components.
+ * @author Daniel Dyer
+ */
+public class SwingBiomorphRenderer implements Renderer<Biomorph, JComponent>
+{
+ /**
+ * Renders an evolved biomorph as a component that can be displayed
+ * in a Swing GUI.
+ * @param biomorph The biomorph to render.
+ * @return A component that displays a visual representation of the
+ * biomorph.
+ */
+ public JComponent render(Biomorph biomorph)
+ {
+ return new BiomorphView(biomorph);
+ }
+
+
+ /**
+ * A Swing component that can display a visual representation of a
+ * biomorph.
+ */
+ private static final class BiomorphView extends JComponent
+ {
+ private final Biomorph biomorph;
+
+ BiomorphView(Biomorph biomorph)
+ {
+ this.biomorph = biomorph;
+ Dimension size = new Dimension(200, 200);
+ setMinimumSize(size);
+ setPreferredSize(size);
+ }
+
+
+ @Override
+ protected void paintComponent(Graphics graphics)
+ {
+ super.paintComponent(graphics);
+ if (graphics instanceof Graphics2D)
+ {
+ ((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+
+ int[][] pattern = biomorph.getPatternPhenotype();
+ int depth = biomorph.getLengthPhenotype();
+
+ drawTree(graphics,
+ getSize().width / 2,
+ getSize().height / 2,
+ depth,
+ 2, // Initial direction should be 2 or 6 to ensure horizontal symmetry.
+ pattern[0], // dx
+ pattern[1]); // dy
+ }
+
+
+ /**
+ * Recursive method for drawing tree branches.
+ */
+ private void drawTree(Graphics graphics,
+ int x,
+ int y,
+ int length,
+ int direction,
+ int[] dx,
+ int[] dy)
+ {
+ // Make sure direction wraps round in the range 0 - 7.
+ direction = (direction + 8) % 8;
+
+ int x2 = x + length * dx[direction];
+ int y2 = y + length * dy[direction];
+
+ graphics.drawLine(x, y, x2, y2);
+
+ if (length > 0)
+ {
+ // Recursively draw the left and right branches of the tree.
+ drawTree(graphics, x2, y2, length - 1, direction - 1, dx, dy);
+ drawTree(graphics, x2, y2, length - 1, direction + 1, dx, dy);
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/package-info.java
new file mode 100644
index 0000000..1f73010
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/biomorphs/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An evolutionary simulation along the lines of the Biomporph program described
+ * by Richard Dawkins in his book, The Blind Watchmaker.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.biomorphs;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java
new file mode 100644
index 0000000..3a6421c
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitStringEvaluator.java
@@ -0,0 +1,51 @@
+//=============================================================================
+// 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.bits;
+
+import java.util.List;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * A fitness evaluator that simply counts the number of ones in a bit
+ * string.
+ * @see BitString
+ * @author Daniel Dyer
+ */
+public class BitStringEvaluator implements FitnessEvaluator<BitString>
+{
+ /**
+ * 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.
+ */
+ public double getFitness(BitString candidate,
+ List<? extends BitString> population)
+ {
+ return candidate.countSetBits();
+ }
+
+
+ /**
+ * Always returns true. A higher score indicates a fitter individual.
+ * @return True.
+ */
+ public boolean isNatural()
+ {
+ return true;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitsExample.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitsExample.java
new file mode 100644
index 0000000..4b6a1c8
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/BitsExample.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// 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.bits;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.random.MersenneTwisterRNG;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.EvolutionLogger;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
+import org.uncommons.watchmaker.framework.factories.BitStringFactory;
+import org.uncommons.watchmaker.framework.operators.BitStringCrossover;
+import org.uncommons.watchmaker.framework.operators.BitStringMutation;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.TargetFitness;
+
+/**
+ * An implementation of the first exercise (page 31) from the book An Introduction to
+ * Genetic Algorithms, by Melanie Mitchell. The algorithm evolves bit strings and the
+ * fitness function simply counts the number of ones in the bit string. The evolution
+ * should therefore converge on strings that consist only of ones.
+ * @author Daniel Dyer
+ */
+public class BitsExample
+{
+ private static final int BITS = 20;
+
+ public static void main(String[] args)
+ {
+ evolveBits(BITS);
+ }
+
+
+ public static BitString evolveBits(int length)
+ {
+ List<EvolutionaryOperator<BitString>> operators = new ArrayList<EvolutionaryOperator<BitString>>(2);
+ operators.add(new BitStringCrossover(1, new Probability(0.7d)));
+ operators.add(new BitStringMutation(new Probability(0.01d)));
+ EvolutionaryOperator<BitString> pipeline = new EvolutionPipeline<BitString>(operators);
+ GenerationalEvolutionEngine<BitString> engine = new GenerationalEvolutionEngine<BitString>(new BitStringFactory(length),
+ pipeline,
+ new BitStringEvaluator(),
+ new RouletteWheelSelection(),
+ new MersenneTwisterRNG());
+ engine.setSingleThreaded(true); // Performs better for very trivial fitness evaluations.
+ engine.addEvolutionObserver(new EvolutionLogger<BitString>());
+ return engine.evolve(100, // 100 individuals in each generation.
+ 0, // Don't use elitism.
+ new TargetFitness(length, true)); // Continue until a perfect match is found.
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/package-info.java
new file mode 100644
index 0000000..058458b
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/bits/package-info.java
@@ -0,0 +1,23 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An implementation of the first exercise from the book An Introduction to Genetic
+ * Algorithms, by Melanie Mitchell. The algorithm evolves bit strings and the
+ * fitness function simply counts the number of ones in the bit string. The evolution
+ * should therefore converge on strings that consist only of ones.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.bits;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java
new file mode 100644
index 0000000..641510c
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Addition.java
@@ -0,0 +1,79 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+/**
+ * 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}).
+ * @param left The first operand.
+ * @param right The second operand.
+ */
+ public Addition(Node left, Node right)
+ {
+ super(left, right, '+');
+ }
+
+
+ /**
+ * Evaluates the two sub-trees and returns the sum of these two values.
+ * @param programParameters Program parameters (ignored by the addition operator
+ * but may be used in evaluating the sub-trees).
+ * @return The sum of the values of both child nodes.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return left.evaluate(programParameters) + right.evaluate(programParameters);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node simplify()
+ {
+ Node simplifiedLeft = left.simplify();
+ Node simplifiedRight = right.simplify();
+ // Adding zero is pointless, the expression can be reduced to its other argument.
+ if (simplifiedRight instanceof Constant && simplifiedRight.evaluate(NO_ARGS) == 0)
+ {
+ return simplifiedLeft;
+ }
+ else if (simplifiedLeft instanceof Constant && simplifiedLeft.evaluate(NO_ARGS) == 0)
+ {
+ return simplifiedRight;
+ }
+ // If the two arguments are constants, we can simplify by calculating the result, it won't
+ // ever change.
+ else if (simplifiedLeft instanceof Constant && simplifiedRight instanceof Constant)
+ {
+ return new Constant(simplifiedLeft.evaluate(NO_ARGS) + simplifiedRight.evaluate(NO_ARGS));
+ }
+ else if (simplifiedLeft != left || simplifiedRight != right)
+ {
+ return new Addition(simplifiedLeft, simplifiedRight);
+ }
+ else
+ {
+ return this;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java
new file mode 100644
index 0000000..2273f74
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/BinaryNode.java
@@ -0,0 +1,214 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.lang.reflect.Constructor;
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.util.reflection.ReflectionUtils;
+
+/**
+ * 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. */
+ protected final Node left;
+ /** The second argument to the binary function. */
+ protected final Node right;
+
+ private final char symbol;
+
+
+ /**
+ * @param left The first argument to the binary function.
+ * @param right The second argument to the binary function.
+ * @param symbol A single character that indicates the type of function.
+ */
+ protected BinaryNode(Node left, Node right, char symbol)
+ {
+ this.left = left;
+ this.right = right;
+ this.symbol = symbol;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLabel()
+ {
+ return String.valueOf(symbol);
+ }
+
+
+ /**
+ * The arity of a binary node is two.
+ * @return 2
+ */
+ public int getArity()
+ {
+ return 2;
+ }
+
+
+ /**
+ * The depth of a binary node is the depth of its deepest sub-tree plus one.
+ * @return The depth of the tree rooted at this node.
+ */
+ public int getDepth()
+ {
+ return 1 + Math.max(left.getDepth(), right.getDepth());
+ }
+
+
+ /**
+ * The width of a binary node is the sum of the widths of its two sub-trees.
+ * @return The width of the tree rooted at this node.
+ */
+ public int getWidth()
+ {
+ return left.getWidth() + right.getWidth();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int countNodes()
+ {
+ return 1 + left.countNodes() + right.countNodes();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node getNode(int index)
+ {
+ if (index == 0)
+ {
+ return this;
+ }
+ int leftNodes = left.countNodes();
+ if (index <= leftNodes)
+ {
+ return left.getNode(index - 1);
+ }
+ else
+ {
+ return right.getNode(index - leftNodes - 1);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node getChild(int index)
+ {
+ switch (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)
+ {
+ return newNode;
+ }
+
+ int leftNodes = left.countNodes();
+ if (index <= leftNodes)
+ {
+ return newInstance(left.replaceNode(index - 1, newNode), right);
+ }
+ else
+ {
+ return newInstance(left, right.replaceNode(index - leftNodes - 1, newNode));
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String print()
+ {
+ StringBuilder buffer = new StringBuilder("(");
+ buffer.append(left.print());
+ buffer.append(' ');
+ buffer.append(symbol);
+ buffer.append(' ');
+ buffer.append(right.print());
+ buffer.append(')');
+ return buffer.toString();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory)
+ {
+ if (mutationProbability.nextEvent(rng))
+ {
+ return treeFactory.generateRandomCandidate(rng);
+ }
+ else
+ {
+ Node newLeft = left.mutate(rng, mutationProbability, treeFactory);
+ Node newRight = right.mutate(rng, mutationProbability, treeFactory);
+ if (newLeft != left && newRight != right)
+ {
+ return newInstance(newLeft, newRight);
+ }
+ else
+ {
+ // Tree has not changed.
+ return this;
+ }
+ }
+ }
+
+
+ private Node newInstance(Node newLeft, Node newRight)
+ {
+ Constructor<? extends BinaryNode> constructor = ReflectionUtils.findKnownConstructor(this.getClass(),
+ Node.class,
+ Node.class);
+ return ReflectionUtils.invokeUnchecked(constructor, newLeft, newRight);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return print();
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java
new file mode 100644
index 0000000..2a07538
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Constant.java
@@ -0,0 +1,101 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.text.DecimalFormat;
+
+/**
+ * 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;
+
+
+ /**
+ * Creates a constant-valued node.
+ * @param constant The value that this node will always evaluate to.
+ */
+ public Constant(double constant)
+ {
+ this.constant = constant;
+ this.label = NUMBER_FORMAT.format(constant);
+ }
+
+
+ /**
+ * @param programParameters The parameters passed to the program (ignored by this node).
+ * @return The numeric value of this constant.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return constant;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLabel()
+ {
+ return label;
+ }
+
+
+ /**
+ * @return The String representation of this constant.
+ */
+ public String print()
+ {
+ return String.valueOf(constant);
+ }
+
+
+ /**
+ * Two constants are equal if they have the same numeric value.
+ * @param other The object that this object is compared to.
+ * @return True if the constants are equivalent, false otherwise.
+ */
+ @Override
+ public boolean equals(Object other)
+ {
+ if (this == other)
+ {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass())
+ {
+ return false;
+ }
+ return Double.compare(((Constant) other).constant, constant) == 0;
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #equals(Object)}.
+ * @return This object's hash code.
+ */
+ @Override
+ public int hashCode()
+ {
+ long temp = constant != +0.0d ? Double.doubleToLongBits(constant) : 0L;
+ return (int) (temp ^ (temp >>> 32));
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExample.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExample.java
new file mode 100644
index 0000000..c060d49
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExample.java
@@ -0,0 +1,85 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.uncommons.maths.random.MersenneTwisterRNG;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.EvolutionLogger;
+import org.uncommons.watchmaker.framework.EvolutionEngine;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.TargetFitness;
+
+/**
+ * Simple tree-based genetic programming application based on the first example
+ * in Chapter 11 of Toby Segaran's Programming Collective Intelligence.
+ * @author Daniel Dyer
+ */
+public class GeneticProgrammingExample
+{
+ // This data describes the problem. For each pair of inputs, the generated program
+ // should return the associated output. The goal of this appliction is to generalise
+ // the examples into an equation.
+ private static final Map<double[], Double> TEST_DATA = new HashMap<double[], Double>();
+ static
+ {
+ TEST_DATA.put(new double[]{26, 35}, 829.0d);
+ TEST_DATA.put(new double[]{8, 24}, 141.0d);
+ TEST_DATA.put(new double[]{20, 1}, 467.0d);
+ TEST_DATA.put(new double[]{33, 11}, 1215.0d);
+ TEST_DATA.put(new double[]{37, 16}, 1517.0d);
+ }
+
+
+ public static void main(String[] args)
+ {
+ Node program = evolveProgram(TEST_DATA);
+ System.out.println(program.print());
+ }
+
+
+ /**
+ * Evolve a function to fit the specified data.
+ * @param data A map from input values to expected output values.
+ * @return A program that generates the correct outputs for all specified
+ * sets of input.
+ */
+ public static Node evolveProgram(Map<double[], Double> data)
+ {
+ TreeFactory factory = new TreeFactory(2, // Number of parameters passed into each program.
+ 4, // Maximum depth of generated trees.
+ Probability.EVENS, // Probability that a node is a function node.
+ new Probability(0.6d)); // Probability that other nodes are params rather than constants.
+ List<EvolutionaryOperator<Node>> operators = new ArrayList<EvolutionaryOperator<Node>>(3);
+ operators.add(new TreeMutation(factory, new Probability(0.4d)));
+ operators.add(new TreeCrossover());
+ operators.add(new Simplification());
+ TreeEvaluator evaluator = new TreeEvaluator(data);
+ EvolutionEngine<Node> engine = new GenerationalEvolutionEngine<Node>(factory,
+ new EvolutionPipeline<Node>(operators),
+ evaluator,
+ new RouletteWheelSelection(),
+ new MersenneTwisterRNG());
+ engine.addEvolutionObserver(new EvolutionLogger<Node>());
+ return engine.evolve(1000, 5, new TargetFitness(0d, evaluator.isNatural()));
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java
new file mode 100644
index 0000000..54abc4c
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElse.java
@@ -0,0 +1,268 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Simple conditional program {@link Node}.
+ * @author Daniel Dyer
+ */
+public class IfThenElse implements Node
+{
+ private final Node condition;
+ 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
+ * the {@literal otherwise} node is returned.
+ * @param then This node is evaluated if the {@literal condition} node has a
+ * value greater than zero.
+ * @param otherwise This node is evaluated if the {@literal condition} node has a
+ * value less than or equal to zero.
+ */
+ public IfThenElse(Node condition, Node then, Node otherwise)
+ {
+ this.condition = condition;
+ this.then = then;
+ this.otherwise = otherwise;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLabel()
+ {
+ return "if";
+ }
+
+
+ /**
+ * The arity of a ternary node is three.
+ * @return 3
+ */
+ public int getArity()
+ {
+ return 3;
+ }
+
+
+ /**
+ * {@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)
+ {
+ return this;
+ }
+ int conditionNodes = condition.countNodes();
+ if (index <= conditionNodes)
+ {
+ return condition.getNode(index - 1);
+ }
+ else
+ {
+ int thenNodes = then.countNodes();
+ if (index <= conditionNodes + thenNodes)
+ {
+ return then.getNode(index - conditionNodes - 1);
+ }
+ else
+ {
+ return otherwise.getNode(index - conditionNodes - thenNodes - 1);
+ }
+ }
+ }
+
+
+ /**
+ * {@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);
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node replaceNode(int index, Node newNode)
+ {
+ if (index == 0)
+ {
+ return newNode;
+ }
+
+ int conditionNodes = condition.countNodes();
+ if (index <= conditionNodes)
+ {
+ return new IfThenElse(condition.replaceNode(index - 1, newNode), then, otherwise);
+ }
+ else
+ {
+ int thenNodes = then.countNodes();
+ if (index <= conditionNodes + thenNodes)
+ {
+ return new IfThenElse(condition, then.replaceNode(index - conditionNodes - 1, newNode), otherwise);
+ }
+ else
+ {
+ return new IfThenElse(condition,
+ then,
+ otherwise.replaceNode(index - conditionNodes - thenNodes - 1, newNode));
+ }
+ }
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ * Operates on three other nodes. The first is an expression to evaluate.
+ * Which of the other two nodes is evaluated and returned depends on whether
+ * this node evaluates to greater than zero or not.
+ * @param programParameters Program parameters (ignored by the conditional operator
+ * but may be used in evaluating child nodes).
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return condition.evaluate(programParameters) > 0 // If...
+ ? 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))
+ {
+ return treeFactory.generateRandomCandidate(rng);
+ }
+ else
+ {
+ Node newCondition = condition.mutate(rng, mutationProbability, treeFactory);
+ Node newThen = then.mutate(rng, mutationProbability, treeFactory);
+ Node newOtherwise = otherwise.mutate(rng, mutationProbability, treeFactory);
+ if (newCondition != condition || newThen != then || newOtherwise != otherwise)
+ {
+ return new IfThenElse(newCondition, newThen, newOtherwise);
+ }
+ else
+ {
+ // Tree has not changed.
+ return this;
+ }
+ }
+
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return print();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node simplify()
+ {
+ Node simplifiedCondition = condition.simplify();
+
+ // If the condition is constant then the expression can be replaced by the branch that
+ // always gets evaluated.
+ if (simplifiedCondition instanceof Constant)
+ {
+ return simplifiedCondition.evaluate(null) > 0 ? then.simplify() : otherwise.simplify();
+ }
+ else
+ {
+ Node simplifiedThen = then.simplify();
+ Node simplifiedOtherwise = otherwise.simplify();
+ // If both branches are identical, the condition is irrelevant.
+ if (simplifiedThen.equals(simplifiedOtherwise))
+ {
+ return simplifiedThen;
+ }
+ // Only create a new node if something has actually changed, otherwise return the existing node.
+ if (simplifiedCondition != condition || simplifiedThen != then || simplifiedOtherwise != otherwise)
+ {
+ return new IfThenElse(simplifiedCondition, simplifiedThen, simplifiedOtherwise);
+ }
+ else
+ {
+ return this;
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java
new file mode 100644
index 0000000..bd23472
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/IsGreater.java
@@ -0,0 +1,78 @@
+//=============================================================================
+// 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.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.
+ * @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.
+ * @param left The first operand.
+ * @param right The second operand.
+ */
+ public IsGreater(Node left, Node right)
+ {
+ super(left, right, '>');
+ }
+
+
+ /**
+ * Returns a value of one if the value of the first node is greater than the value of
+ * the second node. Returns a value of zero otherwise.
+ * @param programParameters The parameters passed to this program (ignored by the
+ * IsGreater node but may be used in the evaluation of child nodes).
+ * @return One or zero depending on the relative values of the two child nodes.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return left.evaluate(programParameters) > right.evaluate(programParameters) ? 1 : 0;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node simplify()
+ {
+ Node simplifiedLeft = left.simplify();
+ Node simplifiedRight = right.simplify();
+ // If the two arguments are exactly equivalent, one cannot be greater than the other.
+ if (simplifiedLeft.equals(simplifiedRight))
+ {
+ return new Constant(0);
+ }
+ // If the two arguments are constants, we can simplify by calculating the result, it won't
+ // ever change.
+ else if (simplifiedLeft instanceof Constant && simplifiedRight instanceof Constant)
+ {
+ return new Constant(simplifiedLeft.evaluate(NO_ARGS) > simplifiedRight.evaluate(NO_ARGS) ? 1 : 0);
+ }
+ else if (simplifiedLeft != left || simplifiedRight != right)
+ {
+ return new IsGreater(simplifiedLeft, simplifiedRight);
+ }
+ else
+ {
+ return this;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java
new file mode 100644
index 0000000..cf8718a
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/LeafNode.java
@@ -0,0 +1,127 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * 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()
+ {
+ return 0;
+ }
+
+
+ /**
+ * Leaf nodes always have a depth of 1 since they have no child nodes.
+ * @return 1
+ */
+ public int getDepth()
+ {
+ return 1;
+ }
+
+
+ /**
+ * Leaf nodes always have a width of 1 since they have no child nodes.
+ * @return 1
+ */
+ public int getWidth()
+ {
+ return 1;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int countNodes()
+ {
+ return 1;
+ }
+
+
+ public Node getNode(int index)
+ {
+ if (index != 0)
+ {
+ throw new IndexOutOfBoundsException("Invalid node index: " + index);
+ }
+ return this;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node getChild(int index)
+ {
+ throw new IndexOutOfBoundsException("Leaf nodes have no children.");
+ }
+
+
+ public Node replaceNode(int index, Node newNode)
+ {
+ if (index != 0)
+ {
+ throw new IndexOutOfBoundsException("Invalid node index: " + index);
+ }
+ return newNode;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory)
+ {
+ if (mutationProbability.nextEvent(rng))
+ {
+ return treeFactory.generateRandomCandidate(rng);
+ }
+ else
+ {
+ // Node is unchanged.
+ return this;
+ }
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return print();
+ }
+
+
+ /**
+ * Returns this node (leaf nodes cannot be simplified).
+ * @return This node, unmodified.
+ */
+ public Node simplify()
+ {
+ return this;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java
new file mode 100644
index 0000000..bd5dd60
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Multiplication.java
@@ -0,0 +1,90 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+/**
+ * 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}).
+ * @param left The first operand.
+ * @param right The second operand.
+ */
+ public Multiplication(Node left, Node right)
+ {
+ super(left, right, '*');
+ }
+
+
+ /**
+ * Evaluates the two sub-trees and returns the product of these two values.
+ * @param programParameters Program parameters (ignored by the multiplication operator
+ * but may be used in evaluating the sub-trees).
+ * @return The sum of the values of both child nodes.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return left.evaluate(programParameters) * right.evaluate(programParameters);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node simplify()
+ {
+ Node simplifiedLeft = left.simplify();
+ Node simplifiedRight = right.simplify();
+ // If the two arguments are constants, we can simplify by calculating the result, it won't
+ // ever change.
+ if (simplifiedLeft instanceof Constant && simplifiedRight instanceof Constant)
+ {
+ return new Constant(simplifiedLeft.evaluate(NO_ARGS) * simplifiedRight.evaluate(NO_ARGS));
+ }
+ // Multiplying by one is pointless, the expression can be reduced to its other argument.
+ else if (simplifiedRight instanceof Constant)
+ {
+ double constant = simplifiedRight.evaluate(NO_ARGS);
+ if (constant == 1)
+ {
+ return simplifiedLeft;
+ }
+ else if (constant == 0)
+ {
+ return new Constant(0);
+ }
+ }
+ else if (simplifiedLeft instanceof Constant)
+ {
+ double constant = simplifiedLeft.evaluate(NO_ARGS);
+ if (constant == 1)
+ {
+ return simplifiedRight;
+ }
+ else if (constant == 0)
+ {
+ return new Constant(0);
+ }
+ }
+ return simplifiedLeft != left || simplifiedRight != right
+ ? new Multiplication(simplifiedLeft, simplifiedRight)
+ : this;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Node.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Node.java
new file mode 100644
index 0000000..7df1685
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Node.java
@@ -0,0 +1,120 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Operations supported by the different types of nodes that make up
+ * genetic program trees.
+ * @author Daniel Dyer
+ */
+public interface Node
+{
+ /**
+ * Recursively evaluates the (sub-)tree represented by this node (including any
+ * child nodes) and returns a numeric value.
+ * @param programParameters Program parameters (possibly used by this node and/or
+ * in the evaluation of child nodes).
+ * @return The result of evaluating this node and all of its children.
+ */
+ double evaluate(double[] programParameters);
+
+ /**
+ * Recursively builds a string representation of the tree rooted at this node.
+ * @return A string representation of this tree.
+ */
+ String print();
+
+ /**
+ * @return A short String that represents the function or value represented by this node.
+ */
+ String getLabel();
+
+ /**
+ * If this is a function (non-leaf) node, how many arguments does it take? For
+ * leaf nodes the answer is zero.
+ * @return The arity of this function, or zero if this node is a leaf node.
+ * @see #countNodes()
+ */
+ int getArity();
+
+ /**
+ * @return The number of levels of nodes that make up this tree.
+ * @see #getWidth()
+ */
+ int getDepth();
+
+ /**
+ * Work out how wide (in nodes) this tree is. Used primarily for laying out a
+ * visual representation. A leaf node has a width of 1. A binary node's width
+ * is the sum of the widths of its sub-trees.
+ * @return The maximum width of this tree.
+ * @see #getDepth()
+ * @see #getArity()
+ */
+ int getWidth();
+
+ /**
+ * @return The total number of nodes in this tree (recursively counts the nodes
+ * for each sub-node of this node).
+ * @see #getArity()
+ */
+ int countNodes();
+
+ /**
+ * Retrieves a sub-node from this tree.
+ * @param index The index of a node. Index 0 is the root node. Nodes are numbered
+ * depth-first, right-to-left.
+ * @return The node at the specified position.
+ */
+ Node getNode(int index);
+
+ /**
+ * Retrieves a direct sub-node from this tree.
+ * @param index The index of a child node. Index 0 is the first child. Nodes are numbered
+ * right-to-left, grandchild nodes are not included.
+ * @return The node at the specified position.
+ */
+ Node getChild(int index);
+
+ /**
+ * Returns a new tree that is identical to this tree except with the specified node
+ * replaced.
+ * @param index The index of the node to replace.
+ * @param newNode The replacement node.
+ * @return A new tree with the node at the specified index replaced by
+ * {@code newNode}.
+ */
+ Node replaceNode(int index, Node newNode);
+
+ /**
+ * Helper method for the {@link TreeMutation} evolutionary operator.
+ * @param rng A source of randomness.
+ * @param mutationProbability The probability that a given node will be mutated.
+ * @param treeFactory A factory for creating the new sub-trees needed for mutation.
+ * @return The mutated node (or the same node if no mutation occurred).
+ */
+ Node mutate(Random rng, Probability mutationProbability, TreeFactory treeFactory);
+
+ /**
+ * Reduce this program tree to its simplest equivalent form.
+ * @return A simplification of this program tree, or this program tree unodified if it
+ * cannot be simplified.
+ */
+ Node simplify();
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java
new file mode 100644
index 0000000..575bd1d
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Parameter.java
@@ -0,0 +1,102 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+/**
+ * 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.
+ */
+ public Parameter(int parameterIndex)
+ {
+ this.parameterIndex = parameterIndex;
+ }
+
+
+ /**
+ * Returns the value of one of the program parameters.
+ * @param programParameters The parameters to this program.
+ * @return The program parameter at the index condigured for this node.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ if (parameterIndex >= programParameters.length)
+ {
+ throw new IllegalArgumentException("Invalid parameter index: " + parameterIndex);
+ }
+ return programParameters[parameterIndex];
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getLabel()
+ {
+ return "P" + parameterIndex;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * For a parameter node the String representation is simply "arg0", "arg1", etc.
+ * depending on which program parameter it refers to.
+ */
+ public String print()
+ {
+ return "arg" + parameterIndex;
+ }
+
+
+ /**
+ * Two parameters are equal if they evaluate to the same program argument
+ * (i.e. they have the same parameter index).
+ * @param other The object that this object is compared to.
+ * @return True if the parameters are equivalent, false otherwise.
+ */
+ @Override
+ public boolean equals(Object other)
+ {
+ if (this == other)
+ {
+ return true;
+ }
+ if (other == null || getClass() != other.getClass())
+ {
+ return false;
+ }
+ return parameterIndex == ((Parameter) other).parameterIndex;
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #equals(Object)}.
+ * @return This object's hash code.
+ */
+ @Override
+ public int hashCode()
+ {
+ return parameterIndex;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Simplification.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Simplification.java
new file mode 100644
index 0000000..f44864b
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Simplification.java
@@ -0,0 +1,71 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Evolutionary operator for GP trees that reduces them to their simplest form.
+ * @author Daniel Dyer
+ */
+public class Simplification implements EvolutionaryOperator<Node>
+{
+ private final Probability probability;
+
+
+ /**
+ * Creates a simplification operator with a probability of 1 (i.e. all
+ * canidates will be simplified).
+ */
+ public Simplification()
+ {
+ this(Probability.ONE);
+ }
+
+
+ /**
+ * Creates a simplfication operator that has the specified probability of being
+ * applied to any individual candidate.
+ * @param probability The probability that this operator will attempt to simplify
+ * any single expression.
+ */
+ public Simplification(Probability probability)
+ {
+ this.probability = probability;
+ }
+
+
+ /**
+ * Simplify the expressions represented by the candidates. Each expression
+ * is simplified according to the configured probability.
+ * @param selectedCandidates The individuals to evolve.
+ * @param rng A source of randomness.
+ * @return The (possibly) simplified candidates.
+ */
+ public List<Node> apply(List<Node> selectedCandidates, Random rng)
+ {
+ List<Node> evolved = new ArrayList<Node>(selectedCandidates.size());
+ for (Node node : selectedCandidates)
+ {
+ evolved.add(probability.nextEvent(rng) ? node.simplify() : node);
+ }
+ return evolved;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java
new file mode 100644
index 0000000..97083af
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/Subtraction.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+/**
+ * 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}.
+ * @param left The first operand.
+ * @param right The second operand.
+ */
+ public Subtraction(Node left, Node right)
+ {
+ super(left, right, '-');
+ }
+
+
+ /**
+ * Evaluates the two sub-trees and returns the difference between these two values.
+ * @param programParameters Program parameters (ignored by the subtraction operator
+ * but may be used in evaluating the sub-trees).
+ * @return The difference between the values of the two child nodes.
+ */
+ public double evaluate(double[] programParameters)
+ {
+ return left.evaluate(programParameters) - right.evaluate(programParameters);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node simplify()
+ {
+ Node simplifiedLeft = left.simplify();
+ Node simplifiedRight = right.simplify();
+ // If the two arguments are identical then the result will always be zero.
+ if (simplifiedLeft.equals(simplifiedRight))
+ {
+ return new Constant(0);
+ }
+ // Subtracting zero is pointless, the expression can be reduced to its lefthand side.
+ else if (simplifiedRight instanceof Constant && simplifiedRight.evaluate(NO_ARGS) == 0)
+ {
+ return simplifiedLeft;
+ }
+ // If the two arguments are constants, we can simplify by calculating the result, it won't
+ // ever change.
+ else if (simplifiedLeft instanceof Constant && simplifiedRight instanceof Constant)
+ {
+ return new Constant(simplifiedLeft.evaluate(NO_ARGS) - simplifiedRight.evaluate(NO_ARGS));
+ }
+ else if (simplifiedLeft != left || simplifiedRight != right)
+ {
+ return new Subtraction(simplifiedLeft, simplifiedRight);
+ }
+ else
+ {
+ return this;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRenderer.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRenderer.java
new file mode 100644
index 0000000..fd16238
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRenderer.java
@@ -0,0 +1,141 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import javax.swing.JComponent;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+
+/**
+ * A Swing renderer for genetic programming trees.
+ * @author Daniel Dyer
+ */
+public class SwingGPTreeRenderer implements Renderer<Node, JComponent>
+{
+ /**
+ * Renders a GP tree as a Swing component.
+ * @param tree The root node of the GP tree to render.
+ * @return A {@link JComponent} that displays a graphical representation of the tree.
+ */
+ public JComponent render(Node tree)
+ {
+ return new GPTreeView(tree);
+ }
+
+
+ private static final class GPTreeView extends JComponent
+ {
+ // Allow 30 pixels for each node horizontally.
+ private static final int NODE_WIDTH = 30;
+ // Allow 50 pixels for each node vertically.
+ private static final int NODE_HEIGHT = 50;
+ private static final int CIRCLE_RADIUS = 9;
+ private static final int CIRCLE_DIAMETER = CIRCLE_RADIUS * 2;
+
+ private final Node rootNode;
+
+
+ GPTreeView(Node rootNode)
+ {
+ this.rootNode = rootNode;
+ int minHeight = rootNode.getDepth() * NODE_HEIGHT;
+ int minWidth = rootNode.getWidth() * NODE_WIDTH;
+ Dimension size = new Dimension(minWidth, minHeight);
+ setMinimumSize(size);
+ setPreferredSize(size);
+ }
+
+
+ @Override
+ protected void paintComponent(Graphics graphics)
+ {
+ super.paintComponent(graphics);
+ if (graphics instanceof Graphics2D)
+ {
+ ((Graphics2D) graphics).setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ RenderingHints.VALUE_ANTIALIAS_ON);
+ }
+ // Center the tree if the component is bigger than required.
+ int offset = (getSize().width - getMinimumSize().width) / 2;
+ drawNode(rootNode, offset, 0, graphics);
+ }
+
+
+ /**
+ * Recursively draw the specified node and its children.
+ * @param node The sub-tree to draw.
+ * @param x The left edge of the area in which this tree is drawn.
+ * @param y The top edge of the area in which this tree is drawn.
+ * @param graphics The target for drawing.
+ */
+ private void drawNode(Node node, int x, int y, Graphics graphics)
+ {
+ int start = x + node.getWidth() * NODE_WIDTH / 2;
+ if (node instanceof Constant)
+ {
+ graphics.setColor(Color.YELLOW);
+ graphics.fillRoundRect(start - (NODE_WIDTH / 2 - 2),
+ y,
+ NODE_WIDTH - 4,
+ CIRCLE_DIAMETER,
+ CIRCLE_RADIUS,
+ CIRCLE_RADIUS);
+ graphics.setColor(Color.BLACK);
+ graphics.drawRoundRect(start - (NODE_WIDTH / 2 - 2),
+ y,
+ NODE_WIDTH - 4,
+ CIRCLE_DIAMETER,
+ CIRCLE_RADIUS,
+ CIRCLE_RADIUS);
+ }
+ else if (node instanceof Parameter)
+ {
+ graphics.setColor(Color.GREEN);
+ graphics.fillRect(start - CIRCLE_RADIUS, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
+ graphics.setColor(Color.BLACK);
+ graphics.drawRect(start - CIRCLE_RADIUS, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
+ }
+ else
+ {
+ graphics.setColor(Color.WHITE);
+ graphics.fillOval(start - CIRCLE_RADIUS, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
+ graphics.setColor(Color.BLACK);
+ graphics.drawOval(start - CIRCLE_RADIUS, y, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
+ }
+ FontMetrics metrics = graphics.getFontMetrics();
+ int stringWidth = metrics.stringWidth(node.getLabel());
+ graphics.drawString(node.getLabel(),
+ (int) Math.round(start - (double) stringWidth / 2),
+ (int) Math.round(y + CIRCLE_RADIUS + (double) metrics.getHeight() / 2 - metrics.getDescent()));
+
+ int xOffset = x;
+ for (int i = 0; i < node.getArity(); i++)
+ {
+ Node child = node.getChild(i);
+ drawNode(child, xOffset, y + NODE_HEIGHT, graphics);
+ graphics.drawLine(start,
+ y + CIRCLE_DIAMETER, xOffset + (child.getWidth() * NODE_WIDTH / 2),
+ y + NODE_HEIGHT);
+ xOffset += child.getWidth() * NODE_WIDTH;
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossover.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossover.java
new file mode 100644
index 0000000..8fe711e
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossover.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.operators.AbstractCrossover;
+
+/**
+ * Cross-over operator for the trees of {@link Node}s used in the genetic
+ * programming example application.
+ * @author Daniel Dyer
+ */
+public class TreeCrossover extends AbstractCrossover<Node>
+{
+ /**
+ * Creates a single-point cross-over operator.
+ */
+ public TreeCrossover()
+ {
+ super(1);
+ }
+
+
+ /**
+ * Swaps randomly selected sub-trees between the two parents.
+ * @param parent1 The first parent.
+ * @param parent2 The second parent.
+ * @param numberOfCrossoverPoints The number of cross-overs to perform.
+ * @param rng A source of randomness.
+ * @return A list of two offspring, generated by swapping sub-trees
+ * between the two parents.
+ */
+ @Override
+ protected List<Node> mate(Node parent1,
+ Node parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ List<Node> offspring = new ArrayList<Node>(2);
+ Node offspring1 = parent1;
+ Node offspring2 = parent2;
+
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ int crossoverPoint1 = rng.nextInt(parent1.countNodes());
+ Node subTree1 = parent1.getNode(crossoverPoint1);
+ int crossoverPoint2 = rng.nextInt(parent2.countNodes());
+ Node subTree2 = parent2.getNode(crossoverPoint2);
+ offspring1 = parent1.replaceNode(crossoverPoint1, subTree2);
+ offspring2 = parent2.replaceNode(crossoverPoint2, subTree1);
+ }
+
+ offspring.add(offspring1);
+ offspring.add(offspring2);
+ return offspring;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluator.java
new file mode 100644
index 0000000..57e718b
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluator.java
@@ -0,0 +1,79 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.List;
+import java.util.Map;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * Fitness function for the genetic programming example application.
+ * An evolved program tree is tested against a table of inputs and associated
+ * expected outputs. If the evolved program correctly calculates the right answer
+ * for all sets of inputs then it has a fitness of zero. Otherwise, its fitness
+ * is an error value that indicates how accurate it was (the larger the combined
+ * error value, the less accurate the function is).
+ * @author Daniel Dyer
+ */
+public class TreeEvaluator implements FitnessEvaluator<Node>
+{
+ private final Map<double[], Double> data;
+
+
+ /**
+ * @param data Each row is consists of a set of inputs and an expected output (the
+ * last item in the row is the output).
+ */
+ public TreeEvaluator(Map<double[], Double> data)
+ {
+ this.data = data;
+ }
+
+
+ /**
+ * If the evolved program correctly calculates the right answer
+ * for all sets of inputs then it has a fitness of zero. Otherwise, its fitness
+ * is an error value that indicates how accurate it was (the larger the combined
+ * error value, the less accurate the function is).
+ * The combined error value is calculated by summing the squares of each individual
+ * error (the difference between the expected output and the actual output).
+ * @param candidate The program tree to evaluate.
+ * @param population Ignored by this implementation.
+ * @return The fitness score for the specified candidate.
+ */
+ public double getFitness(Node candidate, List<? extends Node> population)
+ {
+ double error = 0;
+ for (Map.Entry<double[], Double> entry : data.entrySet())
+ {
+ double actualValue = candidate.evaluate(entry.getKey());
+ double diff = actualValue - entry.getValue();
+ error += (diff * diff);
+ }
+ return error;
+ }
+
+
+ /**
+ * This fitness evaluator is a minimising function. A fitness of zero
+ * indicates a perfect solution.
+ * @return false
+ */
+ public boolean isNatural()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java
new file mode 100644
index 0000000..abb935d
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactory.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+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.
+ * @author Daniel Dyer
+ */
+public class TreeFactory extends AbstractCandidateFactory<Node>
+{
+ // 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;
+
+
+ /**
+ * @param parameterCount The number of program parameters that each
+ * generated program tree can will be provided when executed.
+ * @param maxDepth The maximum depth of generated trees.
+ * @param functionProbability The probability (between 0 and 1) that a
+ * randomly-generated node will be a function node rather than a value
+ * (parameter or constant) node.
+ * @param parameterProbability The probability that a randomly-generated
+ * non-function node will be a parameter node rather than a constant node.
+ */
+ public TreeFactory(int parameterCount,
+ int maxDepth,
+ Probability functionProbability,
+ Probability parameterProbability)
+ {
+ if (parameterCount < 0)
+ {
+ throw new IllegalArgumentException("Parameter count must be greater than or equal to 0.");
+ }
+ if (maxDepth < 1)
+ {
+ throw new IllegalArgumentException("Max depth must be at least 1.");
+ }
+
+ this.parameterCount = parameterCount;
+ this.maximumDepth = maxDepth;
+ this.functionProbability = functionProbability;
+ this.parameterProbability = parameterProbability;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Node generateRandomCandidate(Random rng)
+ {
+ return makeNode(rng, maximumDepth);
+ }
+
+
+ /**
+ * Recursively constructs a tree of Nodes, up to the specified maximum depth.
+ * @param rng The RNG used to random create nodes.
+ * @param maxDepth The maximum depth of the generated tree.
+ * @return A tree of nodes.
+ */
+ private Node makeNode(Random rng, int maxDepth)
+ {
+ if (functionProbability.nextEvent(rng) && maxDepth > 1)
+ {
+ // Max depth for sub-trees is one less than max depth for this node.
+ 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));
+ }
+ }
+ else if (parameterProbability.nextEvent(rng))
+ {
+ return new Parameter(rng.nextInt(parameterCount));
+ }
+ else
+ {
+ return new Constant(rng.nextInt(11));
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutation.java
new file mode 100644
index 0000000..75f64b5
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutation.java
@@ -0,0 +1,61 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation operator for the trees of {@link Node}s used in the genetic
+ * programming example application.
+ * @author Daniel Dyer
+ */
+public class TreeMutation implements EvolutionaryOperator<Node>
+{
+ private final TreeFactory treeFactory;
+
+ private final Probability mutationProbability;
+
+ /**
+ * The tree mutation operator requires a {@link TreeFactory} because
+ * the process of mutation involves creating new sub-trees. The same
+ * TreeFactory that is used to create the initial population should be
+ * used.
+ * @param treeFactory Used to generate the new sub-trees required for mutation.
+ * @param mutationProbability The probability that any given node in a tree is
+ * mutated by this operator.
+ */
+ public TreeMutation(TreeFactory treeFactory,
+ Probability mutationProbability)
+ {
+ this.treeFactory = treeFactory;
+ this.mutationProbability = mutationProbability;
+ }
+
+
+ public List<Node> apply(List<Node> selectedCandidates, Random rng)
+ {
+ List<Node> mutatedPopulation = new ArrayList<Node>(selectedCandidates.size());
+ for (Node tree : selectedCandidates)
+ {
+ mutatedPopulation.add(tree.mutate(rng, mutationProbability, treeFactory));
+ }
+ return mutatedPopulation;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/package-info.java
new file mode 100644
index 0000000..2aa8b11
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/geneticprogramming/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * A simple example of tree-based genetic programming.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.geneticprogramming;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java
new file mode 100644
index 0000000..490ed92
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AbstractVertexMutation.java
@@ -0,0 +1,101 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Base class for mutation operators that modify the points of polygons in an
+ * image.
+ * @author Daniel Dyer
+ */
+abstract class AbstractVertexMutation implements EvolutionaryOperator<ColouredPolygon>
+{
+ private final Dimension canvasSize;
+ private final NumberGenerator<Probability> mutationProbability;
+
+
+ /**
+ * @param mutationProbability A {@link NumberGenerator} that controls the probability
+ * that a polygon's points will be mutated.
+ * @param canvasSize The size of the canvas. Used to constrain the positions of the points.
+ */
+ protected AbstractVertexMutation(NumberGenerator<Probability> mutationProbability,
+ Dimension canvasSize)
+ {
+ this.mutationProbability = mutationProbability;
+ this.canvasSize = canvasSize;
+ }
+
+
+ /**
+ * @return The dimensions of the target image.
+ */
+ protected Dimension getCanvasSize()
+ {
+ return canvasSize;
+ }
+
+
+ /**
+ * @return The {@link NumberGenerator} that provides the mutation probability.
+ */
+ protected NumberGenerator<Probability> getMutationProbability()
+ {
+ return mutationProbability;
+ }
+
+
+ /**
+ * 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.
+ */
+ public List<ColouredPolygon> apply(List<ColouredPolygon> polygons, Random rng)
+ {
+ List<ColouredPolygon> newPolygons = new ArrayList<ColouredPolygon>(polygons.size());
+ for (ColouredPolygon polygon : polygons)
+ {
+ List<Point> newVertices = mutateVertices(polygon.getVertices(), rng);
+ newPolygons.add(newVertices == polygon.getVertices()
+ ? polygon
+ : 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.
+ */
+ protected abstract List<Point> mutateVertices(List<Point> vertices, Random rng);
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutation.java
new file mode 100644
index 0000000..183a8b7
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutation.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// 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.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Randomly mutates the polygons that make up an image by adding a polygon
+ * according to some probability.
+ * @author Daniel Dyer
+ */
+public class AddPolygonMutation implements EvolutionaryOperator<List<ColouredPolygon>>
+{
+ private final NumberGenerator<Probability> addPolygonProbability;
+ private final PolygonImageFactory factory;
+ private final int maxPolygons;
+
+
+
+ /**
+ * @param addPolygonProbability A {@link NumberGenerator} that controls the probability
+ * that a polygon will be added.
+ * @param factory Used to create new polygons.
+ * @param maxPolygons The maximum number of polygons permitted in an image (must be at least 2).
+ */
+ public AddPolygonMutation(NumberGenerator<Probability> addPolygonProbability,
+ PolygonImageFactory factory,
+ int maxPolygons)
+ {
+ if (maxPolygons < 2)
+ {
+ throw new IllegalArgumentException("Max polygons must be > 1.");
+ }
+ this.addPolygonProbability = addPolygonProbability;
+ this.factory = factory;
+ this.maxPolygons = maxPolygons;
+ }
+
+
+ /**
+ * @param addPolygonProbability The probability that a polygon will be removed.
+ * @param factory Used to create new polygons.
+ * @param maxPolygons The maximum number of polygons permitted in an image (must be at least 2).
+ */
+ public AddPolygonMutation(Probability addPolygonProbability,
+ PolygonImageFactory factory,
+ int maxPolygons)
+ {
+ this(new ConstantGenerator<Probability>(addPolygonProbability),
+ factory,
+ maxPolygons);
+ }
+
+
+ public List<List<ColouredPolygon>> apply(List<List<ColouredPolygon>> selectedCandidates, Random rng)
+ {
+ List<List<ColouredPolygon>> mutatedCandidates = new ArrayList<List<ColouredPolygon>>(selectedCandidates.size());
+ for (List<ColouredPolygon> candidate : selectedCandidates)
+ {
+ // A single polygon is added with the configured probability, unless
+ // we already have the maximum permitted number of polygons.
+ if (candidate.size() < maxPolygons && addPolygonProbability.nextValue().nextEvent(rng))
+ {
+ List<ColouredPolygon> newPolygons = new ArrayList<ColouredPolygon>(candidate);
+ newPolygons.add(rng.nextInt(newPolygons.size() + 1),
+ factory.createRandomPolygon(rng));
+ mutatedCandidates.add(newPolygons);
+ }
+ else // Nothing changed.
+ {
+ mutatedCandidates.add(candidate);
+ }
+ }
+ return mutatedCandidates;
+ }
+
+
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java
new file mode 100644
index 0000000..decebdd
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AddVertexMutation.java
@@ -0,0 +1,84 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.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.
+ */
+ public AddVertexMutation(Dimension canvasSize,
+ NumberGenerator<Probability> mutationProbability)
+ {
+ super(mutationProbability, 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.
+ */
+ public AddVertexMutation(Dimension canvasSize,
+ Probability mutationProbability)
+ {
+ this(canvasSize, new ConstantGenerator<Probability>(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.
+ * @param vertices A list of the points that make up the polygon.
+ * @param rng A source of randomness.
+ * @return A mutated list of points.
+ */
+ @Override
+ protected List<Point> mutateVertices(List<Point> 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<Point> newVertices = new ArrayList<Point>(vertices);
+ newVertices.add(rng.nextInt(newVertices.size()),
+ new Point(rng.nextInt(getCanvasSize().width),
+ rng.nextInt(getCanvasSize().height)));
+ return newVertices;
+ }
+ else // Nothing changed.
+ {
+ return vertices;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java
new file mode 100644
index 0000000..0549cc6
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutation.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.Maths;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.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<? extends Number> 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.
+ */
+ public AdjustVertexMutation(Dimension canvasSize,
+ NumberGenerator<Probability> mutationProbability,
+ NumberGenerator<? extends Number> changeAmount)
+ {
+ super(mutationProbability, canvasSize);
+ this.changeAmount = changeAmount;
+ }
+
+
+ /**
+ * @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.
+ */
+ public AdjustVertexMutation(Dimension canvasSize,
+ Probability mutationProbability,
+ NumberGenerator<? extends Number> changeAmount)
+ {
+ this(canvasSize, new ConstantGenerator<Probability>(mutationProbability), changeAmount);
+ }
+
+
+ @Override
+ protected List<Point> mutateVertices(List<Point> vertices, Random rng)
+ {
+ // A single point is modified with the configured probability.
+ if (getMutationProbability().nextValue().nextEvent(rng))
+ {
+ List<Point> newVertices = new ArrayList<Point>(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);
+ 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);
+ newVertices.set(index, new Point(newX, newY));
+ return newVertices;
+ }
+ else // Nothing changed.
+ {
+ return vertices;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java
new file mode 100644
index 0000000..eb8c481
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ColouredPolygon.java
@@ -0,0 +1,78 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Point;
+import java.awt.Polygon;
+import java.util.List;
+
+/**
+ * A coloured polygon consists of a set of vertices and a colour that is used to
+ * fill the polygon when it is rendered.
+ * @author Daniel Dyer
+ */
+public class ColouredPolygon
+{
+ private final Color colour;
+ private final List<Point> vertices;
+ private final Polygon polygon;
+
+
+ /**
+ * Creates a new filled polygon with the specified points and colour.
+ * @param colour The colour of this polygon. The alpha channel is used
+ * to specify the polygons translucency.
+ * @param vertices The points that define the polygon's outline.
+ */
+ public ColouredPolygon(Color colour, List<Point> vertices)
+ {
+ this.colour = colour;
+ this.vertices = vertices;
+ this.polygon = new Polygon();
+ for (Point point : vertices)
+ {
+ polygon.addPoint(point.x, point.y);
+ }
+ }
+
+
+ /**
+ * @return The colour that this polygon should be rendered.
+ */
+ public Color getColour()
+ {
+ return colour;
+ }
+
+
+ /**
+ * @return A list of this polygon's vertices.
+ */
+ public List<Point> getVertices()
+ {
+ return vertices;
+ }
+
+
+ /**
+ * @return The AWT shape used for rendering.
+ */
+ public Polygon getPolygon()
+ {
+ return polygon;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java
new file mode 100644
index 0000000..4a0db99
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MonaLisaApplet.java
@@ -0,0 +1,249 @@
+//=============================================================================
+// 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.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+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 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.interactive.Renderer;
+import org.uncommons.watchmaker.framework.selection.TournamentSelection;
+import org.uncommons.watchmaker.framework.termination.Stagnation;
+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.
+ * @author Daniel Dyer
+ */
+public class MonaLisaApplet extends AbstractExampleApplet
+{
+ private static final String IMAGE_PATH = "org/uncommons/watchmaker/examples/monalisa/monalisa.jpg";
+
+ private ProbabilitiesPanel probabilitiesPanel;
+ private EvolutionMonitor<List<ColouredPolygon>> monitor;
+ private JButton startButton;
+ private AbortControl abort;
+ private JSpinner populationSpinner;
+ private JSpinner elitismSpinner;
+ private ProbabilityParameterControl selectionPressureControl;
+ private BufferedImage targetImage;
+
+
+ @Override
+ public void init()
+ {
+ try
+ {
+ URL imageURL = MonaLisaApplet.class.getClassLoader().getResource(IMAGE_PATH);
+ targetImage = ImageIO.read(imageURL);
+ super.init();
+ }
+ catch (IOException ex)
+ {
+ ex.printStackTrace();
+ JOptionPane.showMessageDialog(this, ex, "Failed to Load Image", JOptionPane.ERROR_MESSAGE);
+
+ }
+ }
+
+
+ /**
+ * 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"));
+ JPanel controls = new JPanel(new BorderLayout());
+ controls.add(createParametersPanel(), BorderLayout.NORTH);
+ controls.add(probabilitiesPanel, BorderLayout.SOUTH);
+ container.add(controls, BorderLayout.NORTH);
+
+ Renderer<List<ColouredPolygon>, JComponent> renderer = new PolygonImageSwingRenderer(targetImage);
+ monitor = new EvolutionMonitor<List<ColouredPolygon>>(renderer, false);
+ container.add(monitor.getGUIComponent(), BorderLayout.CENTER);
+ }
+
+
+ private JComponent createParametersPanel()
+ {
+ Box parameters = Box.createHorizontalBox();
+ parameters.add(Box.createHorizontalStrut(10));
+ final JLabel populationLabel = new JLabel("Population Size: ");
+ parameters.add(populationLabel);
+ parameters.add(Box.createHorizontalStrut(10));
+ populationSpinner = new JSpinner(new SpinnerNumberModel(10, 2, 1000, 1));
+ populationSpinner.setMaximumSize(populationSpinner.getMinimumSize());
+ parameters.add(populationSpinner);
+ parameters.add(Box.createHorizontalStrut(10));
+ final JLabel elitismLabel = new JLabel("Elitism: ");
+ parameters.add(elitismLabel);
+ parameters.add(Box.createHorizontalStrut(10));
+ elitismSpinner = new JSpinner(new SpinnerNumberModel(2, 1, 1000, 1));
+ elitismSpinner.setMaximumSize(elitismSpinner.getMinimumSize());
+ parameters.add(elitismSpinner);
+ parameters.add(Box.createHorizontalStrut(10));
+
+ parameters.add(new JLabel("Selection Pressure: "));
+ parameters.add(Box.createHorizontalStrut(10));
+ selectionPressureControl = new ProbabilityParameterControl(Probability.EVENS,
+ Probability.ONE,
+ 2,
+ new Probability(0.7));
+ parameters.add(selectionPressureControl.getControl());
+ parameters.add(Box.createHorizontalStrut(10));
+
+ startButton = new JButton("Start");
+ abort = new AbortControl();
+ startButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent ev)
+ {
+ abort.getControl().setEnabled(true);
+ populationLabel.setEnabled(false);
+ populationSpinner.setEnabled(false);
+ elitismLabel.setEnabled(false);
+ elitismSpinner.setEnabled(false);
+ startButton.setEnabled(false);
+ new EvolutionTask((Integer) populationSpinner.getValue(),
+ (Integer) elitismSpinner.getValue(),
+ abort.getTerminationCondition(),
+ new Stagnation(1000, false)).execute();
+ }
+ });
+ abort.getControl().setEnabled(false);
+ parameters.add(startButton);
+ parameters.add(abort.getControl());
+ parameters.add(Box.createHorizontalStrut(10));
+
+ parameters.setBorder(BorderFactory.createTitledBorder("Parameters"));
+ return parameters;
+ }
+
+
+ /**
+ * 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.
+ */
+ 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);
+ gui.targetImage = ImageIO.read(imageURL);
+ gui.displayInFrame("Watchmaker Framework - Mona Lisa Example");
+ }
+
+
+ /**
+ * The task that acutally performs the evolution.
+ */
+ private class EvolutionTask extends SwingBackgroundTask<List<ColouredPolygon>>
+ {
+ private final int populationSize;
+ private final int eliteCount;
+ private final TerminationCondition[] terminationConditions;
+
+
+ EvolutionTask(int populationSize, int eliteCount, TerminationCondition... terminationConditions)
+ {
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.terminationConditions = terminationConditions;
+ }
+
+
+ @Override
+ protected List<ColouredPolygon> performTask() throws Exception
+ {
+ Dimension canvasSize = new Dimension(targetImage.getWidth(), targetImage.getHeight());
+
+ Random rng = new XORShiftRNG();
+ FitnessEvaluator<List<ColouredPolygon>> evaluator
+ = new CachingFitnessEvaluator<List<ColouredPolygon>>(new PolygonImageEvaluator(targetImage));
+ PolygonImageFactory factory = new PolygonImageFactory(canvasSize);
+ EvolutionaryOperator<List<ColouredPolygon>> pipeline
+ = probabilitiesPanel.createEvolutionPipeline(factory, canvasSize, rng);
+
+ SelectionStrategy<Object> selection = new TournamentSelection(selectionPressureControl.getNumberGenerator());
+ EvolutionEngine<List<ColouredPolygon>> engine
+ = new GenerationalEvolutionEngine<List<ColouredPolygon>>(factory,
+ pipeline,
+ evaluator,
+ selection,
+ rng);
+ engine.addEvolutionObserver(monitor);
+
+ return engine.evolve(populationSize, eliteCount, terminationConditions);
+ }
+
+
+ @Override
+ protected void postProcessing(List<ColouredPolygon> result)
+ {
+ abort.reset();
+ abort.getControl().setEnabled(false);
+ populationSpinner.setEnabled(true);
+ elitismSpinner.setEnabled(true);
+ startButton.setEnabled(true);
+ }
+
+
+ @Override
+ protected void onError(Throwable throwable)
+ {
+ super.onError(throwable);
+ postProcessing(null);
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutation.java
new file mode 100644
index 0000000..1b91f73
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutation.java
@@ -0,0 +1,74 @@
+//=============================================================================
+// 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.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Randomly mutates an image by swapping the z-order of two of its polygons
+ * according to some probability.
+ * @author Daniel Dyer
+ */
+public class MovePolygonMutation implements EvolutionaryOperator<List<ColouredPolygon>>
+{
+ private final NumberGenerator<Probability> movePolygonProbability;
+
+
+ /**
+ * @param movePolygonProbability A {@link NumberGenerator} that controls the probability
+ * that a polygon will be replaced.
+ */
+ public MovePolygonMutation(NumberGenerator<Probability> movePolygonProbability)
+ {
+ this.movePolygonProbability = movePolygonProbability;
+ }
+
+
+ /**
+ * @param replacePolygonProbability The probability that a polygon will be replaced.
+ */
+ public MovePolygonMutation(Probability replacePolygonProbability)
+ {
+ this(new ConstantGenerator<Probability>(replacePolygonProbability));
+ }
+
+
+ public List<List<ColouredPolygon>> apply(List<List<ColouredPolygon>> selectedCandidates, Random rng)
+ {
+ List<List<ColouredPolygon>> mutatedCandidates = new ArrayList<List<ColouredPolygon>>(selectedCandidates.size());
+ for (List<ColouredPolygon> candidate : selectedCandidates)
+ {
+ if (movePolygonProbability.nextValue().nextEvent(rng))
+ {
+ List<ColouredPolygon> newPolygons = new ArrayList<ColouredPolygon>(candidate);
+ ColouredPolygon polygon = newPolygons.remove(rng.nextInt(newPolygons.size()));
+ newPolygons.add(rng.nextInt(newPolygons.size()) + 1, polygon);
+ mutatedCandidates.add(newPolygons);
+ }
+ else // Nothing changed.
+ {
+ mutatedCandidates.add(candidate);
+ }
+ }
+ return mutatedCandidates;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java
new file mode 100644
index 0000000..0434646
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutation.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// 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.Color;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.Maths;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+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.
+ * @author Daniel Dyer
+ */
+public class PolygonColourMutation implements EvolutionaryOperator<ColouredPolygon>
+{
+ private final NumberGenerator<Probability> mutationProbability;
+ private final NumberGenerator<Double> mutationAmount;
+
+
+ /**
+ * @param mutationProbability A {@link NumberGenerator} that controls 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.
+ */
+ public PolygonColourMutation(NumberGenerator<Probability> mutationProbability,
+ NumberGenerator<Double> mutationAmount)
+ {
+ this.mutationProbability = mutationProbability;
+ this.mutationAmount = mutationAmount;
+ }
+
+
+ /**
+ * @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.
+ */
+ public PolygonColourMutation(Probability mutationProbability,
+ NumberGenerator<Double> mutationAmount)
+ {
+ this(new ConstantGenerator<Probability>(mutationProbability), mutationAmount);
+ }
+
+
+ public List<ColouredPolygon> apply(List<ColouredPolygon> polygons, Random rng)
+ {
+ List<ColouredPolygon> newPolygons = new ArrayList<ColouredPolygon>(polygons.size());
+ for (ColouredPolygon polygon : polygons)
+ {
+ Color newColour = mutateColour(polygon.getColour(), rng);
+ newPolygons.add(newColour == polygon.getColour()
+ ? polygon
+ : new ColouredPolygon(newColour, polygon.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
+ {
+ return colour;
+ }
+ }
+
+
+ /**
+ * 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());
+ mutatedComponent = Maths.restrictRange(mutatedComponent, 0, 255);
+ return mutatedComponent;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java
new file mode 100644
index 0000000..acc3e1b
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluator.java
@@ -0,0 +1,158 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.geom.AffineTransform;
+import java.awt.image.AffineTransformOp;
+import java.awt.image.BufferedImage;
+import java.awt.image.Raster;
+import java.util.List;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+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.
+ * @author Daniel Dyer
+ */
+public class PolygonImageEvaluator implements FitnessEvaluator<List<ColouredPolygon>>
+{
+ // 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<Renderer<List<ColouredPolygon>, BufferedImage>> threadLocalRenderer
+ = new ThreadLocal<Renderer<List<ColouredPolygon>, BufferedImage>>();
+
+ private final int width;
+ private final int height;
+ private final AffineTransform transform;
+ private final int[] targetPixels;
+
+
+ /**
+ * 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.
+ */
+ public PolygonImageEvaluator(BufferedImage targetImage)
+ {
+ // Scale the image down so that its smallest dimension is 100 pixels. For large images this drastically
+ // 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;
+ }
+
+ 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);
+ }
+
+
+ /**
+ * 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.
+ */
+ private BufferedImage convertImage(BufferedImage image)
+ {
+ if (image.getType() == BufferedImage.TYPE_INT_RGB)
+ {
+ return image;
+ }
+ else
+ {
+ BufferedImage newImage = new BufferedImage(image.getWidth(),
+ image.getHeight(),
+ BufferedImage.TYPE_INT_RGB);
+ newImage.getGraphics().drawImage(image, 0, 0, null);
+ 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.
+ * @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).
+ */
+ public double getFitness(List<ColouredPolygon> candidate,
+ List<? extends List<ColouredPolygon>> population)
+ {
+ // Use one renderer per thread because they are not thread safe.
+ Renderer<List<ColouredPolygon>, BufferedImage> renderer = threadLocalRenderer.get();
+ if (renderer == null)
+ {
+ renderer = new PolygonImageRenderer(new Dimension(width, height),
+ false,
+ transform);
+ 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);
+ double fitness = 0;
+ for (int i = 0; i < targetPixels.length; i++)
+ {
+ fitness += comparePixels(targetPixels[i], candidatePixelValues[i]);
+ }
+
+ return fitness;
+ }
+
+
+ private double comparePixels(int p1, int p2)
+ {
+ int deltaR = ((p1 >>> 16) & 0xFF) - ((p2 >>> 16) & 0xFF);
+ int deltaG = ((p1 >>> 8) & 0xFF) - ((p2 >>> 8) & 0xFF);
+ int deltaB = (p1 & 0xFF) - (p2 & 0xFF);
+ return Math.sqrt(deltaR * deltaR + deltaG * deltaG + deltaB * deltaB);
+ }
+
+
+ public boolean isNatural()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java
new file mode 100644
index 0000000..9613172
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactory.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory;
+
+/**
+ * Creates random polygon-based images.
+ * @author Daniel Dyer
+ */
+public class PolygonImageFactory extends AbstractCandidateFactory<List<ColouredPolygon>>
+{
+ /**
+ * Each image must have at least 2 polygons.
+ */
+ static final int MINIMUM_POLYGON_COUNT = 2;
+
+ /**
+ * Each polygon must have at least 3 points.
+ */
+ static final int MINIMUM_VERTEX_COUNT = 3;
+
+ private final Dimension canvasSize;
+
+ /**
+ * @param canvasSize The size of the canvas on which the image will be rendered.
+ * All polygons must fit within its bounds.
+ */
+ public PolygonImageFactory(Dimension canvasSize)
+ {
+ this.canvasSize = canvasSize;
+ }
+
+
+ public List<ColouredPolygon> generateRandomCandidate(Random rng)
+ {
+ List<ColouredPolygon> polygons = new ArrayList<ColouredPolygon>(MINIMUM_POLYGON_COUNT);
+ for (int i = 0; i < MINIMUM_POLYGON_COUNT; i++)
+ {
+ polygons.add(createRandomPolygon(rng));
+ }
+ return polygons;
+ }
+
+
+ ColouredPolygon createRandomPolygon(Random rng)
+ {
+ List<Point> vertices = new ArrayList<Point>(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));
+ return new ColouredPolygon(colour, vertices);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java
new file mode 100644
index 0000000..876c9f0
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageRenderer.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.geom.AffineTransform;
+import java.awt.image.BufferedImage;
+import java.util.List;
+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.
+ * @author Daniel Dyer
+ */
+public class PolygonImageRenderer implements Renderer<List<ColouredPolygon>, BufferedImage>
+{
+ private static final AffineTransform IDENTITY_TRANSFORM = new AffineTransform();
+
+ private final Dimension targetSize;
+ private final AffineTransform transform;
+ private final BufferedImage image;
+ private final Graphics2D graphics;
+
+
+ /**
+ * @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}.
+ */
+ 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.graphics = image.createGraphics();
+ if (antialias)
+ {
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+ 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.
+ */
+ public BufferedImage render(List<ColouredPolygon> entity)
+ {
+ // Need to set the background before applying the transform.
+ graphics.setTransform(IDENTITY_TRANSFORM);
+ graphics.setColor(Color.GRAY);
+ graphics.fillRect(0, 0, targetSize.width, targetSize.height);
+ if (transform != null)
+ {
+ graphics.setTransform(transform);
+ }
+ for (ColouredPolygon polygon : entity)
+ {
+ graphics.setColor(polygon.getColour());
+ graphics.fillPolygon(polygon.getPolygon());
+ }
+ return image;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java
new file mode 100644
index 0000000..1ce7e05
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/PolygonImageSwingRenderer.java
@@ -0,0 +1,143 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+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.
+ * @author Daniel Dyer
+ */
+public class PolygonImageSwingRenderer implements Renderer<List<ColouredPolygon>, JComponent>
+{
+ private final BufferedImage targetImage;
+ private final Renderer<List<ColouredPolygon>, BufferedImage> delegate;
+
+
+ /**
+ * @param targetImage This image is displayed on the left side of the
+ * JComponent.
+ */
+ public PolygonImageSwingRenderer(BufferedImage targetImage)
+ {
+ // 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);
+ }
+
+
+ /**
+ * 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.
+ */
+ public JComponent render(List<ColouredPolygon> entity)
+ {
+ return new ImageComponent(entity);
+ }
+
+
+ /**
+ * 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<ColouredPolygon> candidate;
+ private final Dimension minimumSize;
+
+
+ ImageComponent(List<ColouredPolygon> candidate)
+ {
+ this.candidate = candidate;
+ this.minimumSize = new Dimension(targetImage.getWidth() * 2 + GAP,
+ targetImage.getHeight() + FOOTER);
+ }
+
+ @Override
+ public Dimension getPreferredSize()
+ {
+ return minimumSize;
+ }
+
+ @Override
+ public Dimension getMinimumSize()
+ {
+ return minimumSize;
+ }
+
+
+ @Override
+ 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);
+
+ clip.setColor(getForeground());
+ String info = candidate.size() + " polygons, " + countVertices(candidate) + " vertices";
+ FontMetrics fontMetrics = clip.getFontMetrics();
+ int width = fontMetrics.stringWidth(info);
+ int height = Math.round(fontMetrics.getLineMetrics(info, clip).getHeight());
+ clip.drawString(info,
+ candidateImage.getWidth() - width,
+ candidateImage.getHeight() + height);
+ }
+
+
+ /**
+ * 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<ColouredPolygon> image)
+ {
+ int count = 0;
+ for (ColouredPolygon polygon : image)
+ {
+ count += polygon.getVertices().size();
+ }
+ return count;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java
new file mode 100644
index 0000000..5267be3
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanel.java
@@ -0,0 +1,173 @@
+//=============================================================================
+// 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.Dimension;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.SpringLayout;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.GaussianGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.swing.SpringUtilities;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.operators.ListCrossover;
+import org.uncommons.watchmaker.framework.operators.ListOperator;
+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.
+ * @author Daniel Dyer
+ */
+class ProbabilitiesPanel extends JPanel
+{
+ private static final Probability ONE_TENTH = new Probability(0.1d);
+
+ private final ProbabilityParameterControl addPolygonControl;
+ private final ProbabilityParameterControl removePolygonControl;
+ private final ProbabilityParameterControl movePolygonControl;
+ private final ProbabilityParameterControl crossOverControl;
+ private final ProbabilityParameterControl addVertexControl;
+ private final ProbabilityParameterControl removeVertexControl;
+ private final ProbabilityParameterControl moveVertexControl;
+ private final ProbabilityParameterControl changeColourControl;
+
+
+ ProbabilitiesPanel()
+ {
+ super(new SpringLayout());
+ addPolygonControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.02));
+ add(new JLabel("Add Polygon: "));
+ add(addPolygonControl.getControl());
+ addPolygonControl.setDescription("For each IMAGE, the probability that a new "
+ + "randomly-generated polygon will be added.");
+
+ addVertexControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.01));
+ add(new JLabel("Add Vertex: "));
+ add(addVertexControl.getControl());
+ addVertexControl.setDescription("For each POLYGON, the probability that a new "
+ + "randomly-generated vertex will be added.");
+
+ removePolygonControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.02));
+ add(new JLabel("Remove Polygon: "));
+ add(removePolygonControl.getControl());
+ removePolygonControl.setDescription("For each IMAGE, the probability that a "
+ + "randomly-selected polygon will be discarded.");
+
+ removeVertexControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.01));
+ add(new JLabel("Remove Vertex: "));
+ add(removeVertexControl.getControl());
+ removeVertexControl.setDescription("For each POLYGON, the probability that a "
+ + "randomly-selected vertex will be discarded.");
+
+ movePolygonControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.01));
+ 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.");
+
+ moveVertexControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.03));
+ add(new JLabel("Move Vertex: "));
+ add(moveVertexControl.getControl());
+ moveVertexControl.setDescription("For each POLYGON, the probability that a randomly-selected "
+ + "vertex will be displaced.");
+
+ crossOverControl = new ProbabilityParameterControl(Probability.ZERO,
+ Probability.ONE,
+ 2,
+ Probability.ONE);
+ 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.");
+
+ changeColourControl = new ProbabilityParameterControl(Probability.ZERO,
+ ONE_TENTH,
+ 3,
+ new Probability(0.01));
+ add(new JLabel("Change Colour: "));
+ add(changeColourControl.getControl());
+ 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");
+ removePolygonControl.getControl().setName("RemovePolygon");
+ movePolygonControl.getControl().setName("MovePolygon");
+ crossOverControl.getControl().setName("Cross-over");
+ addVertexControl.getControl().setName("AddVertex");
+ removeVertexControl.getControl().setName("RemoveVertex");
+ moveVertexControl.getControl().setName("MoveVertex");
+ changeColourControl.getControl().setName("ChangeColour");
+
+ SpringUtilities.makeCompactGrid(this, 4, 4, 10, 0, 10, 0);
+ }
+
+
+ /**
+ * 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.
+ * @return A complex evolutionary operator constructed from simpler operators.
+ */
+ public EvolutionaryOperator<List<ColouredPolygon>> createEvolutionPipeline(PolygonImageFactory factory,
+ Dimension canvasSize,
+ Random rng)
+ {
+ List<EvolutionaryOperator<List<ColouredPolygon>>> operators
+ = new LinkedList<EvolutionaryOperator<List<ColouredPolygon>>>();
+ operators.add(new ListCrossover<ColouredPolygon>(new ConstantGenerator<Integer>(2),
+ crossOverControl.getNumberGenerator()));
+ operators.add(new RemovePolygonMutation(removePolygonControl.getNumberGenerator()));
+ operators.add(new MovePolygonMutation(movePolygonControl.getNumberGenerator()));
+ operators.add(new ListOperator<ColouredPolygon>(new RemoveVertexMutation(canvasSize,
+ removeVertexControl.getNumberGenerator())));
+ operators.add(new ListOperator<ColouredPolygon>(new AdjustVertexMutation(canvasSize,
+ moveVertexControl.getNumberGenerator(),
+ new GaussianGenerator(0, 3, rng))));
+ operators.add(new ListOperator<ColouredPolygon>(new AddVertexMutation(canvasSize,
+ addVertexControl.getNumberGenerator())));
+ operators.add(new ListOperator<ColouredPolygon>(new PolygonColourMutation(changeColourControl.getNumberGenerator(),
+ new GaussianGenerator(0, 20, rng))));
+ operators.add(new AddPolygonMutation(addPolygonControl.getNumberGenerator(), factory, 50));
+ return new EvolutionPipeline<List<ColouredPolygon>>(operators);
+ }
+
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutation.java
new file mode 100644
index 0000000..d1d33bb
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutation.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// 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.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Randomly mutates the polygons that make up an image by removing a polygon
+ * according to some probability.
+ * @author Daniel Dyer
+ */
+public class RemovePolygonMutation implements EvolutionaryOperator<List<ColouredPolygon>>
+{
+ private final NumberGenerator<Probability> removePolygonProbability;
+
+ /**
+ * @param removePolygonProbability A {@link NumberGenerator} that controls the probability
+ * that a polygon will be removed.
+ */
+ public RemovePolygonMutation(NumberGenerator<Probability> removePolygonProbability)
+ {
+ this.removePolygonProbability = removePolygonProbability;
+ }
+
+
+ /**
+ * @param removePolygonProbability The probability that a polygon will be removed.
+ */
+ public RemovePolygonMutation(Probability removePolygonProbability)
+ {
+ this(new ConstantGenerator<Probability>(removePolygonProbability));
+ }
+
+
+ public List<List<ColouredPolygon>> apply(List<List<ColouredPolygon>> selectedCandidates, Random rng)
+ {
+ List<List<ColouredPolygon>> mutatedCandidates = new ArrayList<List<ColouredPolygon>>(selectedCandidates.size());
+ for (List<ColouredPolygon> candidate : selectedCandidates)
+ {
+ // A single polygon is removed with the configured probability, unless
+ // we already have the minimum permitted number of polygons.
+ if (candidate.size() > PolygonImageFactory.MINIMUM_POLYGON_COUNT
+ && removePolygonProbability.nextValue().nextEvent(rng))
+ {
+ List<ColouredPolygon> newPolygons = new ArrayList<ColouredPolygon>(candidate);
+ newPolygons.remove(rng.nextInt(newPolygons.size()));
+ mutatedCandidates.add(newPolygons);
+ }
+ else // Nothing changed.
+ {
+ mutatedCandidates.add(candidate);
+ }
+ }
+ return mutatedCandidates;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java
new file mode 100644
index 0000000..4ec530d
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutation.java
@@ -0,0 +1,76 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.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.
+ */
+ public RemoveVertexMutation(Dimension canvasSize,
+ NumberGenerator<Probability> mutationProbability)
+ {
+ super(mutationProbability, 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.
+ */
+ public RemoveVertexMutation(Dimension canvasSize,
+ Probability mutationProbability)
+ {
+ this(canvasSize, new ConstantGenerator<Probability>(mutationProbability));
+ }
+
+
+ @Override
+ protected List<Point> mutateVertices(List<Point> 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<Point> newVertices = new ArrayList<Point>(vertices);
+ newVertices.remove(rng.nextInt(newVertices.size()));
+ return newVertices;
+ }
+ else // Nothing changed.
+ {
+ return vertices;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/package-info.java
new file mode 100644
index 0000000..1602c91
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/monalisa/package-info.java
@@ -0,0 +1,23 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An evolutionary art example application 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.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.monalisa;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java
new file mode 100644
index 0000000..ac56c9f
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringEvaluator.java
@@ -0,0 +1,73 @@
+//=============================================================================
+// 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.strings;
+
+import java.util.List;
+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.
+ * @author Daniel Dyer
+ */
+public class StringEvaluator implements FitnessEvaluator<String>
+{
+ private final String targetString;
+
+
+ /**
+ * Creates a {@link FitnessEvaluator} that calculates scores
+ * for Strings based on how close they are to a target String.
+ * @param targetString The target of the evolution.
+ */
+ public StringEvaluator(String targetString)
+ {
+ this.targetString = targetString;
+ }
+
+
+ /**
+ * Assigns one "penalty point" for every character in the candidate
+ * string that differs from the corresponding position in the target
+ * string.
+ * @param candidate The evolved string to evaluate.
+ * @param population {@inheritDoc}
+ * @return The fitness score (how many characters are wrong) of the
+ * specified string.
+ */
+ public double getFitness(String candidate,
+ List<? extends String> population)
+ {
+ int errors = 0;
+ for (int i = 0; i < candidate.length(); i++)
+ {
+ if (candidate.charAt(i) != targetString.charAt(i))
+ {
+ ++errors;
+ }
+ }
+ return errors;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isNatural()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java
new file mode 100644
index 0000000..d8425fa
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/StringsExample.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// 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.strings;
+
+import java.util.ArrayList;
+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.factories.StringFactory;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.operators.StringCrossover;
+import org.uncommons.watchmaker.framework.operators.StringMutation;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+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.
+ * @author Daniel Dyer
+ */
+public final class StringsExample
+{
+ private static final char[] ALPHABET = new char[27];
+ static
+ {
+ for (char c = 'A'; c <= 'Z'; c++)
+ {
+ ALPHABET[c - 'A'] = c;
+ }
+ ALPHABET[26] = ' ';
+ }
+
+
+ /**
+ * Entry point for the sample application. Any data specified on the
+ * command line is considered to be the target String. If no target is
+ * specified, a default of "HELLOW WORLD" is used instead.
+ * @param args The target String (as an array of words).
+ */
+ public static void main(String[] args)
+ {
+ String target = args.length == 0 ? "HELLO WORLD" : convertArgs(args);
+ String result = evolveString(target);
+ System.out.println("Evolution result: " + result);
+ }
+
+
+ public static String evolveString(String target)
+ {
+ StringFactory factory = new StringFactory(ALPHABET, target.length());
+ List<EvolutionaryOperator<String>> operators = new ArrayList<EvolutionaryOperator<String>>(2);
+ operators.add(new StringMutation(ALPHABET, new Probability(0.02d)));
+ operators.add(new StringCrossover());
+ EvolutionaryOperator<String> pipeline = new EvolutionPipeline<String>(operators);
+ EvolutionEngine<String> engine = new GenerationalEvolutionEngine<String>(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));
+ }
+
+
+ /**
+ * Converts an arguments array into a single String of words
+ * separated by spaces.
+ * @param args The command-line arguments.
+ * @return A single String made from the command line data.
+ */
+ private static String convertArgs(String[] args)
+ {
+ StringBuilder result = new StringBuilder();
+ for (int i = 0; i < args.length; i++)
+ {
+ result.append(args[i]);
+ if (i < args.length - 1)
+ {
+ result.append(' ');
+ }
+ }
+ return result.toString().toUpperCase();
+ }
+
+
+ /**
+ * Trivial evolution observer for displaying information at the end
+ * of each generation.
+ */
+ private static class EvolutionLogger implements EvolutionObserver<String>
+ {
+ public void populationUpdate(PopulationData<? extends String> data)
+ {
+ System.out.printf("Generation %d: %s\n",
+ data.getGenerationNumber(),
+ data.getBestCandidate());
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/package-info.java
new file mode 100644
index 0000000..f5f4489
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/strings/package-info.java
@@ -0,0 +1,22 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Simple example that demonstrates how to use the evolution framework.
+ * Evolves candidates of type String, attempting to find a particular
+ * target phrase.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.strings;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/Sudoku.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/Sudoku.java
new file mode 100644
index 0000000..9319177
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/Sudoku.java
@@ -0,0 +1,150 @@
+//=============================================================================
+// 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.sudoku;
+
+/**
+ * A potential solution for a Sudoku puzzle.
+ * @author Daniel Dyer
+ */
+public final class Sudoku
+{
+ /** The dimensions (in cells) of the puzzle square. */
+ public static final int SIZE = 9;
+ private static final int MIN_VALUE = 1;
+ private static final int MAX_VALUE = SIZE;
+
+ private final Cell[][] cells;
+
+
+ /**
+ * Creates a sudoku solution from a 2-dimensional array of cells.
+ * @param cells The cells that make-up this Sudoku grid.
+ */
+ public Sudoku(Cell[][] cells)
+ {
+ if (cells.length != SIZE)
+ {
+ throw new IllegalArgumentException("Sudoku must have 9 rows.");
+ }
+ if (cells[0].length != SIZE) // Should really check all rows because the array may not be square.
+ {
+ throw new IllegalArgumentException("Sudoku must have 9 columns.");
+ }
+ this.cells = cells;
+ }
+
+
+ /**
+ * Queries the value of a particular cell.
+ * @param row The row index of the cell.
+ * @param column The column index of the cell.
+ * @return The value (1 - 9) of the specified cell.
+ */
+ public int getValue(int row, int column)
+ {
+ return cells[row][column].getValue();
+ }
+
+
+ /**
+ * Checks whether a particular cell is a 'given' or not.
+ * @param row The row index of the cell.
+ * @param column The column index of the cell.
+ * @return True if the value in the identified cell is fixed (a 'given' cell),
+ * false otherwise.
+ */
+ public boolean isFixed(int row, int column)
+ {
+ return cells[row][column].isFixed();
+ }
+
+
+ /**
+ * Returns an array of cells that make up a row. The array
+ * returned is a clone of the underlying data structure and
+ * therefore can be modified without affecting this object.
+ * @param row The index of the row to return.
+ * @return A row of cells from this Sudoku grid.
+ */
+ public Cell[] getRow(int row)
+ {
+ return cells[row].clone();
+ }
+
+
+ /**
+ * Renders the Sudoku grid as a multi-line String.
+ * @return The String representation of this grid.
+ */
+ @Override
+ public String toString()
+ {
+ StringBuilder buffer = new StringBuilder();
+ for (Cell[] row : cells)
+ {
+ for (Cell cell : row)
+ {
+ buffer.append(' ');
+ buffer.append(cell.getValue());
+ }
+ buffer.append('\n');
+ }
+ return buffer.toString();
+ }
+
+
+ /**
+ * A single cell in a sudoku grid. Contains a value and may be fixed (i.e. it is
+ * one of the given cells at the start).
+ */
+ public static final class Cell
+ {
+ private final int value;
+ private final boolean fixed;
+
+ /**
+ * @param value The value (1 - 9) contained in this cell.
+ * @param fixed Whether or not this cell's value is fixed (a 'given').
+ */
+ public Cell(int value, boolean fixed)
+ {
+ if (value < MIN_VALUE || value > MAX_VALUE)
+ {
+ throw new IllegalArgumentException("Value must be between 1 and 9.");
+ }
+ this.value = value;
+ this.fixed = fixed;
+ }
+
+
+ /**
+ * @return The value contained in this cell.
+ */
+ public int getValue()
+ {
+ return value;
+ }
+
+
+ /**
+ * @return True if this cell is a 'given', false otherwise.
+ */
+ public boolean isFixed()
+ {
+ return fixed;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java
new file mode 100644
index 0000000..2a856e9
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuApplet.java
@@ -0,0 +1,283 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+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 org.uncommons.maths.random.DiscreteUniformGenerator;
+import org.uncommons.maths.random.MersenneTwisterRNG;
+import org.uncommons.maths.random.PoissonGenerator;
+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.SelectionStrategy;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.selection.TournamentSelection;
+import org.uncommons.watchmaker.framework.termination.TargetFitness;
+import org.uncommons.watchmaker.swing.AbortControl;
+import org.uncommons.watchmaker.swing.ProbabilityParameterControl;
+import org.uncommons.watchmaker.swing.SwingEvolutionObserver;
+import org.uncommons.watchmaker.swing.evolutionmonitor.StatusBar;
+
+/**
+ * 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 SelectionStrategy<Object> selectionStrategy;
+
+ private SudokuView sudokuView;
+ private JButton solveButton;
+ private JComboBox puzzleCombo;
+ private JSpinner populationSizeSpinner;
+ private AbortControl abortControl;
+ private StatusBar statusBar;
+
+
+ /**
+ * Initialise and layout the GUI.
+ * @param container The Swing component that will contain the GUI controls.
+ */
+ @Override
+ protected void prepareGUI(Container container)
+ {
+ sudokuView = new SudokuView();
+ container.add(createControls(), BorderLayout.NORTH);
+ container.add(sudokuView, BorderLayout.CENTER);
+ statusBar = new StatusBar();
+ container.add(statusBar, BorderLayout.SOUTH);
+ sudokuView.setPuzzle(EASY_PUZZLE);
+ }
+
+
+ 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"});
+ innerPanel.add(puzzleCombo);
+ puzzleCombo.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent ev)
+ {
+ sudokuView.setPuzzle(PUZZLES[puzzleCombo.getSelectedIndex()]);
+ }
+ });
+ innerPanel.add(new JLabel("Selection Pressure: "));
+ ProbabilityParameterControl selectionPressure = new ProbabilityParameterControl(Probability.EVENS,
+ Probability.ONE,
+ 2,
+ new Probability(0.85d));
+
+ selectionStrategy = new TournamentSelection(selectionPressure.getNumberGenerator());
+ innerPanel.add(selectionPressure.getControl());
+ innerPanel.add(new JLabel("Population Size: "));
+ populationSizeSpinner = new JSpinner(new SpinnerNumberModel(500, 10, 50000, 1));
+ innerPanel.add(populationSizeSpinner);
+ SpringUtilities.makeCompactGrid(innerPanel, 3, 2, 0, 6, 6, 6);
+ innerPanel.setBorder(BorderFactory.createTitledBorder("Configuration"));
+ controls.add(innerPanel, BorderLayout.CENTER);
+ controls.add(createButtonPanel(), BorderLayout.SOUTH);
+ return controls;
+ }
+
+
+ private JComponent createButtonPanel()
+ {
+ JPanel buttonPanel = new JPanel(new FlowLayout());
+ solveButton = new JButton("Solve");
+ solveButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent ev)
+ {
+ int populationSize = (Integer) populationSizeSpinner.getValue();
+ puzzleCombo.setEnabled(false);
+ populationSizeSpinner.setEnabled(false);
+ solveButton.setEnabled(false);
+ abortControl.reset();
+ createTask(sudokuView.getPuzzle(),
+ populationSize,
+ (int) Math.round(populationSize * 0.05)).execute(); // Elite count is 5%.
+ }
+ });
+
+ buttonPanel.add(solveButton);
+ abortControl = new AbortControl();
+ buttonPanel.add(abortControl.getControl());
+ abortControl.getControl().setEnabled(false);
+ return buttonPanel;
+ }
+
+
+ /**
+ * Helper method to create a background task for running the interactive evolutionary
+ * algorithm.
+ * @return A Swing task that will execute on a background thread and update
+ * the GUI when it is done.
+ */
+ private SwingBackgroundTask<Sudoku> createTask(final String[] puzzle,
+ final int populationSize,
+ final int eliteCount)
+ {
+ return new SwingBackgroundTask<Sudoku>()
+ {
+ @Override
+ protected Sudoku performTask()
+ {
+ Random rng = new MersenneTwisterRNG();
+ List<EvolutionaryOperator<Sudoku>> operators = new ArrayList<EvolutionaryOperator<Sudoku>>(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)));
+
+ EvolutionaryOperator<Sudoku> pipeline = new EvolutionPipeline<Sudoku>(operators);
+
+ EvolutionEngine<Sudoku> engine = new GenerationalEvolutionEngine<Sudoku>(new SudokuFactory(puzzle),
+ pipeline,
+ new SudokuEvaluator(),
+ selectionStrategy,
+ rng);
+ engine.addEvolutionObserver(new SwingEvolutionObserver<Sudoku>(new GridViewUpdater(),
+ 100,
+ 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.
+ }
+
+
+ @Override
+ protected void postProcessing(Sudoku result)
+ {
+ puzzleCombo.setEnabled(true);
+ populationSizeSpinner.setEnabled(true);
+ solveButton.setEnabled(true);
+ abortControl.getControl().setEnabled(false);
+ }
+ };
+ }
+
+
+
+ /**
+ * Evolution observer for displaying information at the end of
+ * each generation.
+ */
+ private class GridViewUpdater implements EvolutionObserver<Sudoku>
+ {
+ public void populationUpdate(final PopulationData<? extends Sudoku> data)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ sudokuView.setSolution(data.getBestCandidate());
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Entry point for running this example as an application rather than an applet.
+ * @param args Program arguments (ignored).
+ */
+ public static void main(String[] args)
+ {
+ new SudokuApplet().displayInFrame("Watchmaker Framework - Sudoku Example");
+ }
+
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuCellRenderer.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuCellRenderer.java
new file mode 100644
index 0000000..ebfa15a
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuCellRenderer.java
@@ -0,0 +1,162 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Font;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.table.DefaultTableCellRenderer;
+import org.uncommons.swing.ConfigurableLineBorder;
+
+/**
+ * Renders Sudoku cells in a JTable. As well as displaying the cell value,
+ * grid lines are drawn to conform to normal Sudoku conventions and incorrect
+ * solutions have their invalid cells colour-coded.
+ * @author Daniel Dyer
+ */
+class SudokuCellRenderer extends DefaultTableCellRenderer
+{
+ private static final Font VARIABLE_CELL_FONT = new Font("Monospaced", Font.PLAIN, 32);
+ private static final Font FIXED_CELL_FONT = new Font("Monospaced", Font.BOLD, 34);
+ private static final Color VARIABLE_TEXT_COLOUR = Color.DARK_GRAY;
+ private static final Color FIXED_TEXT_COLOUR = Color.BLACK;
+
+ private static final Color[] CONFLICT_COLOURS = {Color.WHITE, Color.YELLOW, Color.ORANGE, Color.RED};
+
+ private static final Border TOP_BORDER = new ConfigurableLineBorder(true, false, false, false, 1);
+ private static final Border BOTTOM_BORDER = new ConfigurableLineBorder(false, false, true, false, 1);
+ private static final Border LEFT_BORDER = new ConfigurableLineBorder(false, true, false, false, 1);
+ private static final Border RIGHT_BORDER = new ConfigurableLineBorder(false, false, false, true, 1);
+ private static final Border TOP_LEFT_BORDER = new ConfigurableLineBorder(true, true, false, false, 1);
+ private static final Border TOP_RIGHT_BORDER = new ConfigurableLineBorder(true, false, false, true, 1);
+ private static final Border BOTTOM_LEFT_BORDER = new ConfigurableLineBorder(false, true, true, false, 1);
+ private static final Border BOTTOM_RIGHT_BORDER = new ConfigurableLineBorder(false, false, true, true, 1);
+
+
+ SudokuCellRenderer()
+ {
+ setHorizontalAlignment(JLabel.CENTER);
+ }
+
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ int row,
+ int column)
+ {
+ Component component = super.getTableCellRendererComponent(table,
+ value,
+ false,
+ false,
+ row,
+ column);
+ SudokuTableModel model = (SudokuTableModel) table.getModel();
+ Sudoku sudoku = model.getSudoku();
+ if (sudoku != null)
+ {
+ if (sudoku.isFixed(row, column))
+ {
+ component.setFont(FIXED_CELL_FONT);
+ component.setForeground(FIXED_TEXT_COLOUR);
+ }
+ else
+ {
+ component.setFont(VARIABLE_CELL_FONT);
+ component.setForeground(VARIABLE_TEXT_COLOUR);
+ }
+
+ int conflicts = 0;
+ // Calculate conflicts in columns (there should be no conflicts in rows
+ // because of the constraints enforced by the evolutionary operators).
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ if (i != row && model.getValueAt(i, column).equals(value))
+ {
+ ++conflicts;
+ }
+ }
+ // Calculate conflicts in sub-grid.
+ int band = row / 3;
+ int bandStart = band * 3;
+ int stack = column / 3;
+ int stackStart = stack * 3;
+ for (int i = bandStart; i < bandStart + 3; i++)
+ {
+ for (int j = stackStart; j < stackStart + 3; j++)
+ {
+ if (i != row && j != column && model.getValueAt(i, j).equals(value))
+ {
+ ++conflicts;
+ }
+ }
+ }
+
+ // Color the cell based on how "wrong" it is.
+ component.setBackground(CONFLICT_COLOURS[Math.min(conflicts, CONFLICT_COLOURS.length - 1)]);
+ }
+ else
+ {
+ // If the model is in puzzle mode, then all non-null cells are 'givens'.
+ component.setFont(FIXED_CELL_FONT);
+ component.setForeground(FIXED_TEXT_COLOUR);
+ }
+
+ ((JComponent) component).setBorder(getBorder(row, column));
+
+
+ return component;
+ }
+
+
+ /**
+ * Get appropriate border for cell based on its position in the grid.
+ */
+ private Border getBorder(int row, int column)
+ {
+ if (row % 3 == 2)
+ {
+ switch (column % 3)
+ {
+ case 2: return BOTTOM_RIGHT_BORDER;
+ case 0: return BOTTOM_LEFT_BORDER;
+ default: return BOTTOM_BORDER;
+ }
+ }
+ else if (row % 3 == 0)
+ {
+ switch (column % 3)
+ {
+ case 2: return TOP_RIGHT_BORDER;
+ case 0: return TOP_LEFT_BORDER;
+ default: return TOP_BORDER;
+ }
+ }
+
+ switch (column % 3)
+ {
+ case 2: return RIGHT_BORDER;
+ case 0: return LEFT_BORDER;
+ default: return null;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java
new file mode 100644
index 0000000..ca44394
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluator.java
@@ -0,0 +1,89 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+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.
+ * @author Daniel Dyer
+ */
+public class SudokuEvaluator implements FitnessEvaluator<Sudoku>
+{
+ /**
+ * 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.
+ */
+ public double getFitness(Sudoku candidate,
+ List<? extends Sudoku> population)
+ {
+ // We can assume that there are no duplicates in any rows because
+ // the candidate factory and evolutionary operators that we use do
+ // not permit rows to contain duplicates.
+
+ int fitness = 0;
+
+ // Check columns for duplicates.
+ Set<Integer> values = new HashSet<Integer>(Sudoku.SIZE * 2); // Big enough to avoid re-hashing.
+ for (int column = 0; column < Sudoku.SIZE; column++)
+ {
+ for (int row = 0; row < Sudoku.SIZE; row++)
+ {
+ values.add(candidate.getValue(row, column));
+ }
+ fitness += (Sudoku.SIZE - values.size());
+ values.clear();
+ }
+
+ // Check sub-grids for duplicates.
+ for (int band = 0; band < Sudoku.SIZE; band += 3)
+ {
+ for (int stack = 0; stack < Sudoku.SIZE; stack += 3)
+ {
+ for (int row = band; row < band + 3; row++)
+ {
+ for (int column = stack; column < stack + 3; column++)
+ {
+ values.add(candidate.getValue(row, column));
+ }
+ }
+ fitness += (Sudoku.SIZE - values.size());
+ values.clear();
+ }
+ }
+ return fitness;
+ }
+
+
+ /**
+ * @return false
+ */
+ public boolean isNatural()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java
new file mode 100644
index 0000000..d96ed1a
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuFactory.java
@@ -0,0 +1,125 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+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).
+ * @author Daniel Dyer
+ */
+public class SudokuFactory extends AbstractCandidateFactory<Sudoku>
+{
+ private static final List<Integer> VALUES = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
+
+ private final Sudoku.Cell[][] template;
+ private final List<List<Integer>> nonFixedValues = new ArrayList<List<Integer>>(Sudoku.SIZE);
+
+
+ /**
+ * Creates a factory for generating random candidate solutions
+ * for a specified Sudoku puzzle.
+ * @param pattern An array of Strings. Each element represents
+ * one row in the puzzle. Each character represents a single
+ * cell. Permitted characters are the digits '1' to '9' (each
+ * of which represents a fixed cell in the pattern) or the '.'
+ * character, which represents an empty cell.
+ * @throws IllegalArgumentException If {@literal pattern} does not
+ * consist of nine Strings with nine characters ('0' to '9', or '.')
+ * in each.
+ */
+ public SudokuFactory(String... pattern)
+ {
+ if (pattern.length != Sudoku.SIZE)
+ {
+ throw new IllegalArgumentException("Sudoku layout must have " + Sudoku.SIZE + " rows.");
+ }
+
+ this.template = new Sudoku.Cell[Sudoku.SIZE][Sudoku.SIZE];
+
+ // Keep track of which values in each row are not 'givens'.
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ nonFixedValues.add(new ArrayList<Integer>(VALUES));
+ }
+
+ for (int i = 0; i < pattern.length; i++)
+ {
+ char[] rowPattern = pattern[i].toCharArray();
+ if (rowPattern.length != Sudoku.SIZE)
+ {
+ throw new IllegalArgumentException("Sudoku layout must have " + Sudoku.SIZE + " cells in each row.");
+ }
+ for (int j = 0; j < rowPattern.length; j++)
+ {
+ char c = rowPattern[j];
+ if (c >= '1' && c <= '9') // Cell is a 'given'.
+ {
+ int value = c - '0'; // Convert char to in that it represents..
+ template[i][j] = new Sudoku.Cell(value, true);
+ List<Integer> rowValues = nonFixedValues.get(i);
+ int index = Collections.binarySearch(rowValues, value);
+ rowValues.remove(index);
+ }
+ else if (c != '.')
+ {
+ throw new IllegalArgumentException("Unexpected character at (" + i + ", " + j + "): " + c);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * The generated potential solution is guaranteed to have no
+ * duplicates in any row but could have duplicates in a column or sub-grid.
+ */
+ public Sudoku generateRandomCandidate(Random rng)
+ {
+ // Clone the template as the basis for this grid.
+ Sudoku.Cell[][] rows = template.clone();
+ for (int i = 0; i < rows.length; i++)
+ {
+ rows[i] = template[i].clone();
+ }
+
+ // Fill-in the non-fixed cells.
+ for (int i = 0; i < rows.length; i++)
+ {
+ List<Integer> rowValues = nonFixedValues.get(i);
+ Collections.shuffle(rowValues);
+ int index = 0;
+ for (int j = 0; j < rows[i].length; j++)
+ {
+ if (rows[i][j] == null)
+ {
+ rows[i][j] = new Sudoku.Cell(rowValues.get(index), false);
+ ++index;
+ }
+ }
+ }
+ return new Sudoku(rows);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutation.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutation.java
new file mode 100644
index 0000000..029ae2b
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutation.java
@@ -0,0 +1,206 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutates rows in a potential Sudoku solution by manipulating the order
+ * of non-fixed cells in much the same way as the
+ * {@link org.uncommons.watchmaker.framework.operators.ListOrderMutation}
+ * operator does in the Travelling Salesman example.
+ * @author Daniel Dyer
+ */
+public class SudokuRowMutation implements EvolutionaryOperator<Sudoku>
+{
+ private final NumberGenerator<Integer> mutationCountVariable;
+ private final NumberGenerator<Integer> mutationAmountVariable;
+
+ // These look-up tables keep track of which values are fixed in which columns
+ // and sub-grids. Because the values are fixed, they are the same for all
+ // potential solutions, so we cache the information here to minimise the amount
+ // of processing that needs to be done for each mutation. There is no need to
+ // worry about rows since the mutation ensures that rows are always valid.
+ private final boolean[][] columnFixedValues = new boolean[Sudoku.SIZE][Sudoku.SIZE];
+ private final boolean[][] subGridFixedValues = new boolean[Sudoku.SIZE][Sudoku.SIZE];
+ private boolean cached = false;
+
+ /**
+ * Default is one mutation per candidate.
+ */
+ public SudokuRowMutation()
+ {
+ this(1, 1);
+ }
+
+ /**
+ * @param mutationCount The constant number of mutations
+ * to apply to each row in a Sudoku solution.
+ * @param mutationAmount The constant number of positions by
+ * which a list element will be displaced as a result of mutation.
+ */
+ public SudokuRowMutation(int mutationCount, int mutationAmount)
+ {
+ this(new ConstantGenerator<Integer>(mutationCount),
+ new ConstantGenerator<Integer>(mutationAmount));
+ if (mutationCount < 1)
+ {
+ throw new IllegalArgumentException("Mutation count must be at least 1.");
+ }
+ else if (mutationAmount < 1)
+ {
+ throw new IllegalArgumentException("Mutation amount must be at least 1.");
+ }
+ }
+
+
+ /**
+ * Typically the mutation count will be from a Poisson distribution.
+ * The mutation amount can be from any discrete probability distribution
+ * and can include negative values.
+ * @param mutationCount A random variable that provides a number
+ * of mutations that will be applied to each row in an individual.
+ * @param mutationAmount A random variable that provides a number
+ * of positions by which to displace an element when mutating.
+ */
+ public SudokuRowMutation(NumberGenerator<Integer> mutationCount,
+ NumberGenerator<Integer> mutationAmount)
+ {
+ this.mutationCountVariable = mutationCount;
+ this.mutationAmountVariable = mutationAmount;
+ }
+
+
+ public List<Sudoku> apply(List<Sudoku> selectedCandidates, Random rng)
+ {
+ if (!cached)
+ {
+ buildCache(selectedCandidates.get(0));
+ }
+
+ List<Sudoku> mutatedCandidates = new ArrayList<Sudoku>(selectedCandidates.size());
+ for (Sudoku sudoku : selectedCandidates)
+ {
+ mutatedCandidates.add(mutate(sudoku, rng));
+ }
+ return mutatedCandidates;
+ }
+
+
+ private Sudoku mutate(Sudoku sudoku, Random rng)
+ {
+ Sudoku.Cell[][] newRows = new Sudoku.Cell[Sudoku.SIZE][];
+ // Mutate each row in turn.
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ newRows[i] = new Sudoku.Cell[Sudoku.SIZE];
+ System.arraycopy(sudoku.getRow(i), 0, newRows[i], 0, Sudoku.SIZE);
+ }
+
+ int mutationCount = Math.abs(mutationCountVariable.nextValue());
+ while (mutationCount > 0)
+ {
+ int row = rng.nextInt(Sudoku.SIZE);
+ int fromIndex = rng.nextInt(Sudoku.SIZE);
+ int mutationAmount = mutationAmountVariable.nextValue();
+ int toIndex = (fromIndex + mutationAmount) % Sudoku.SIZE;
+
+ // Make sure we're not trying to mutate a 'given'.
+ if (!newRows[row][fromIndex].isFixed()
+ && !newRows[row][toIndex].isFixed()
+ // ...or trying to introduce a duplicate of a given value.
+ && (!isIntroducingFixedConflict(sudoku, row, fromIndex, toIndex)
+ || isRemovingFixedConflict(sudoku, row, fromIndex, toIndex)))
+ {
+ // Swap the randomly selected element with the one that is the
+ // specified displacement distance away.
+ Sudoku.Cell temp = newRows[row][fromIndex];
+ newRows[row][fromIndex] = newRows[row][toIndex];
+ newRows[row][toIndex] = temp;
+ --mutationCount;
+ }
+ }
+
+ return new Sudoku(newRows);
+ }
+
+
+ private void buildCache(Sudoku sudoku)
+ {
+ for (int row = 0; row < Sudoku.SIZE; row++)
+ {
+ for (int column = 0; column < Sudoku.SIZE; column++)
+ {
+ if (sudoku.isFixed(row, column))
+ {
+ columnFixedValues[column][sudoku.getValue(row, column) - 1] = true;
+ subGridFixedValues[convertToSubGrid(row, column)][sudoku.getValue(row, column) - 1] = true;
+ }
+ }
+ }
+ cached = true;
+ }
+
+
+ /**
+ * Checks whether the proposed mutation would introduce a duplicate of a fixed value
+ * into a column or sub-grid.
+ */
+ private boolean isIntroducingFixedConflict(Sudoku sudoku,
+ int row,
+ int fromIndex,
+ int toIndex)
+ {
+ return columnFixedValues[fromIndex][sudoku.getValue(row, toIndex) - 1]
+ || columnFixedValues[toIndex][sudoku.getValue(row, fromIndex) - 1]
+ || subGridFixedValues[convertToSubGrid(row, fromIndex)][sudoku.getValue(row, toIndex) - 1]
+ || subGridFixedValues[convertToSubGrid(row, toIndex)][sudoku.getValue(row, fromIndex) - 1];
+ }
+
+
+ /**
+ * Checks whether the proposed mutation would remove a duplicate of a fixed value
+ * from a column or sub-grid.
+ */
+ private boolean isRemovingFixedConflict(Sudoku sudoku,
+ int row,
+ int fromIndex,
+ int toIndex)
+ {
+ return columnFixedValues[fromIndex][sudoku.getValue(row, fromIndex) - 1]
+ || columnFixedValues[toIndex][sudoku.getValue(row, toIndex) - 1]
+ || subGridFixedValues[convertToSubGrid(row, fromIndex)][sudoku.getValue(row, fromIndex) - 1]
+ || subGridFixedValues[convertToSubGrid(row, toIndex)][sudoku.getValue(row, toIndex) - 1];
+ }
+
+
+
+ /**
+ * Returns the index of the sub-grid that the specified cells belongs to.
+ * @return A number between 0 (top left) and 8 (bottom right).
+ */
+ private int convertToSubGrid(int row, int column)
+ {
+ int band = row / 3;
+ int stack = column / 3;
+ return band * 3 + stack;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuTableModel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuTableModel.java
new file mode 100644
index 0000000..0249f7f
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuTableModel.java
@@ -0,0 +1,156 @@
+//=============================================================================
+// 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.sudoku;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * {@link javax.swing.table.TableModel} for displaying a Sudoku
+ * grid in a {@link javax.swing.JTable}.
+ * @author Daniel Dyer
+ */
+class SudokuTableModel extends AbstractTableModel
+{
+ // In puzzle mode, the user can edit the given cells and all other
+ // cells are blank. In solution mode, all cells have values and are
+ // uneditable.
+ private boolean puzzleMode = true;
+
+ // Solution mode data.
+ private Sudoku sudoku;
+
+ // Puzzle mode data.
+ private final Character[][] cells = new Character[Sudoku.SIZE][Sudoku.SIZE];
+
+
+ /**
+ * Sets the Sudoku grid represented by this table model.
+ */
+ public void setSudoku(Sudoku sudoku)
+ {
+ this.sudoku = sudoku;
+ puzzleMode = false;
+ fireTableRowsUpdated(0, getRowCount() - 1);
+ }
+
+
+ /**
+ * @return The Sudoku grid represented by this table model.
+ */
+ public Sudoku getSudoku()
+ {
+ return sudoku;
+ }
+
+
+ public int getRowCount()
+ {
+ return Sudoku.SIZE;
+ }
+
+
+ public int getColumnCount()
+ {
+ return Sudoku.SIZE;
+ }
+
+
+ public Object getValueAt(int row, int column)
+ {
+ if (puzzleMode)
+ {
+ return cells[row][column];
+ }
+ else
+ {
+ return sudoku == null ? null : sudoku.getValue(row, column);
+ }
+ }
+
+
+ @Override
+ public boolean isCellEditable(int row, int column)
+ {
+ return puzzleMode;
+ }
+
+
+ @Override
+ public void setValueAt(Object object, int row, int column)
+ {
+ Character value = (Character) object;
+ if (!(value == null || (value >= '1' && value <= '9')))
+ {
+ throw new IllegalArgumentException("Invalid character: " + value);
+ }
+ cells[row][column] = value;
+ fireTableCellUpdated(row, column);
+ }
+
+
+ /**
+ * Sets all cells at once using the same patterns as supported by
+ * {@link SudokuFactory}.
+ * @param pattern A String representation of a Sudoku puzzle. Each element
+ * in the array represents a single row. There are 9 elements and each has 9
+ * characters, one per cell. Number cells are represented by the characters
+ * '0' to '9' and blank cells are represented by dots.
+ */
+ public void setPattern(String[] pattern)
+ {
+ if (pattern.length != Sudoku.SIZE)
+ {
+ throw new IllegalArgumentException("Pattern must have " + Sudoku.SIZE + " rows.");
+ }
+ for (int row = 0; row < pattern.length; row++)
+ {
+ String patternRow = pattern[row];
+ if (patternRow.length() != Sudoku.SIZE)
+ {
+ throw new IllegalArgumentException("Row must have " + Sudoku.SIZE + " columns.");
+ }
+ for (int column = 0; column < patternRow.toCharArray().length; column++)
+ {
+ char c = patternRow.toCharArray()[column];
+ cells[row][column] = c == '.' ? null : c;
+ }
+ }
+ sudoku = null;
+ puzzleMode = true;
+ fireTableRowsUpdated(0, getRowCount() - 1);
+ }
+
+
+ /**
+ * @return A Sudoku puzzle in the pattern format used by {@link SudokuFactory}.
+ */
+ public String[] getPattern()
+ {
+ String[] pattern = new String[Sudoku.SIZE];
+ for (int i = 0; i < cells.length; i++)
+ {
+ Character[] row = cells[i];
+ StringBuilder rowString = new StringBuilder();
+ for (Character c : row)
+ {
+ rowString.append(c == null ? '.' : c);
+ }
+ pattern[i] = rowString.toString();
+ }
+ return pattern;
+ }
+
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossover.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossover.java
new file mode 100644
index 0000000..e5c790f
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossover.java
@@ -0,0 +1,105 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.operators.AbstractCrossover;
+
+/**
+ * Performs cross-over between Sudoku grids by re-combining rows from parents
+ * to form new offspring. Rows are copied intact, only columns are disrupted
+ * by this cross-over.
+ * @author Daniel Dyer
+ */
+public class SudokuVerticalCrossover extends AbstractCrossover<Sudoku>
+{
+ /**
+ * Single-point cross-over.
+ */
+ public SudokuVerticalCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Multiple-point cross-over (fixed number of points).
+ * @param crossoverPoints The fixed number of cross-overs applied to each
+ * pair of parents.
+ */
+ public SudokuVerticalCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Multiple-point cross-over (variable number of points).
+ * @param crossoverPointsVariable Provides the (possibly variable) number of
+ * cross-overs applied to each pair of parents.
+ */
+ public SudokuVerticalCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Applies cross-over to a pair of parents. Cross-over is performed vertically
+ * (each offspring consists of some rows from {@literal parent1} and some rows
+ * from {@literal parent2}).
+ * @param parent1 The first parent.
+ * @param parent2 The second parent.
+ * @param numberOfCrossoverPoints The number of cross-overs to perform.
+ * @param rng The RNG used to select the cross-over points.
+ * @return A list containing a pair of offspring.
+ */
+ @Override
+ protected List<Sudoku> mate(Sudoku parent1,
+ Sudoku parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ Sudoku.Cell[][] offspring1 = new Sudoku.Cell[Sudoku.SIZE][];
+ Sudoku.Cell[][] offspring2 = new Sudoku.Cell[Sudoku.SIZE][];
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ offspring1[i] = parent1.getRow(i);
+ offspring2[i] = parent2.getRow(i);
+ }
+
+ // Apply as many cross-overs as required.
+ Sudoku.Cell[][] temp = new Sudoku.Cell[Sudoku.SIZE][];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(Sudoku.SIZE - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+
+ List<Sudoku> result = new ArrayList<Sudoku>(2);
+ result.add(new Sudoku(offspring1));
+ result.add(new Sudoku(offspring2));
+ return result;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuView.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuView.java
new file mode 100644
index 0000000..1f8f269
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/SudokuView.java
@@ -0,0 +1,76 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import javax.swing.BorderFactory;
+import javax.swing.DefaultCellEditor;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableColumnModel;
+
+/**
+ * A component for displaying Sudoku puzzles and solutions.
+ * @author Daniel Dyer
+ */
+class SudokuView extends JPanel
+{
+ private final SudokuTableModel sudokuTableModel = new SudokuTableModel();
+
+ SudokuView()
+ {
+ super(new BorderLayout());
+ JTable sudokuTable = new JTable(sudokuTableModel);
+ sudokuTable.setRowHeight(40);
+ sudokuTable.setGridColor(Color.GRAY);
+ sudokuTable.setShowGrid(true);
+ TableColumnModel columnModel = sudokuTable.getColumnModel();
+ TableCellRenderer renderer = new SudokuCellRenderer();
+ JComboBox valueCombo = new JComboBox(new Object[]{null, '1', '2', '3', '4', '5', '6', '7', '8', '9'});
+ TableCellEditor editor = new DefaultCellEditor(valueCombo);
+ for (int i = 0; i < columnModel.getColumnCount(); i++)
+ {
+ TableColumn column = columnModel.getColumn(i);
+ column.setCellRenderer(renderer);
+ column.setCellEditor(editor);
+ }
+ add(sudokuTable, BorderLayout.CENTER);
+ setBorder(BorderFactory.createTitledBorder("Puzzle/Solution"));
+ }
+
+
+ public void setSolution(Sudoku sudoku)
+ {
+ sudokuTableModel.setSudoku(sudoku);
+ }
+
+
+ public void setPuzzle(String[] pattern)
+ {
+ sudokuTableModel.setPattern(pattern);
+ }
+
+
+ public String[] getPuzzle()
+ {
+ return sudokuTableModel.getPattern();
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/package-info.java
new file mode 100644
index 0000000..bd69653
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/sudoku/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An evolutionary Sudoku solver.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.sudoku;
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java
new file mode 100644
index 0000000..312e6d4
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesman.java
@@ -0,0 +1,107 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.uncommons.maths.combinatorics.PermutationGenerator;
+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).
+ * @author Daniel Dyer
+ */
+public class BruteForceTravellingSalesman implements TravellingSalesmanStrategy
+{
+ private final DistanceLookup distances;
+
+
+ /**
+ * @param distances Information about the distances between cities.
+ */
+ 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
+ * possible routes are equivalent to one of these routes since start city
+ * is irrelevant in determining the shortest cycle.
+ * @param cities The list of destinations, each of which must be visited
+ * once.
+ * @param progressListener Call-back for receiving the status of the
+ * algorithm as it progresses. May be null.
+ * @return The shortest route that visits each of the specified cities once.
+ */
+ public List<String> calculateShortestRoute(Collection<String> cities,
+ ProgressListener progressListener)
+ {
+ Iterator<String> iterator = cities.iterator();
+ String startCity = iterator.next();
+ Collection<String> destinations = new ArrayList<String>(cities.size() - 1);
+ while (iterator.hasNext())
+ {
+ destinations.add(iterator.next());
+ }
+
+ FitnessEvaluator<List<String>> evaluator = new RouteEvaluator(distances);
+ PermutationGenerator<String> generator = new PermutationGenerator<String>(destinations);
+ long totalPermutations = generator.getTotalPermutations();
+ long count = 0;
+ List<String> shortestRoute = null;
+ double shortestDistance = Double.POSITIVE_INFINITY;
+ List<String> currentRoute = new ArrayList<String>(cities.size());
+ while (generator.hasMore())
+ {
+ List<String> route = generator.nextPermutationAsList(currentRoute);
+ route.add(0, startCity);
+ double distance = evaluator.getFitness(route, null);
+ if (distance < shortestDistance)
+ {
+ shortestDistance = distance;
+ shortestRoute = new ArrayList<String>(route);
+ }
+ ++count;
+ if (count % 1000 == 0 && progressListener != null)
+ {
+ progressListener.updateProgress(((double) count) / totalPermutations * 100);
+ }
+ }
+ if (progressListener != null)
+ {
+ progressListener.updateProgress(100); // Finished.
+ }
+ return shortestRoute;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/DistanceLookup.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/DistanceLookup.java
new file mode 100644
index 0000000..9013d75
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/DistanceLookup.java
@@ -0,0 +1,39 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.List;
+
+/**
+ * Strategy interface for providing distances between cities in the
+ * Travelling Salesman problem.
+ * @author Daniel Dyer
+ */
+public interface DistanceLookup
+{
+ /**
+ * @return The list of cities that this object knows about.
+ */
+ List<String> getKnownCities();
+
+ /**
+ * Looks-up the distance between two cities.
+ * @param startingCity The city to start from.
+ * @param destinationCity The city to end in.
+ * @return The distance (in kilometres) between the two cities.
+ */
+ int getDistance(String startingCity, String destinationCity);
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java
new file mode 100644
index 0000000..547347f
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookup.java
@@ -0,0 +1,326 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 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<String, Map<String, Integer>> DISTANCES = new HashMap<String, Map<String, Integer>>(15);
+ static
+ {
+ // Distances are in km as the crow flies (from http://www.indo.com/distance/)
+
+ Map<String, Integer> amsterdam = new HashMap<String, Integer>(20);
+ amsterdam.put("Amsterdam", 0);
+ amsterdam.put("Athens", 2162);
+ amsterdam.put("Berlin", 576);
+ amsterdam.put("Brussels", 171);
+ amsterdam.put("Copenhagen", 622);
+ amsterdam.put("Dublin", 757);
+ amsterdam.put("Helsinki", 1506);
+ amsterdam.put("Lisbon", 1861);
+ amsterdam.put("London", 356);
+ amsterdam.put("Luxembourg", 318);
+ amsterdam.put("Madrid", 1477);
+ amsterdam.put("Paris", 429);
+ amsterdam.put("Rome", 1304);
+ amsterdam.put("Stockholm", 1132);
+ amsterdam.put("Vienna", 938);
+ DISTANCES.put("Amsterdam", amsterdam);
+
+ Map<String, Integer> athens = new HashMap<String, Integer>(20);
+ athens.put("Amsterdam", 2162);
+ athens.put("Athens", 0);
+ athens.put("Berlin", 1801);
+ athens.put("Brussels", 2089);
+ athens.put("Copenhagen", 2140);
+ athens.put("Dublin", 2860);
+ athens.put("Helsinki", 2464);
+ athens.put("Lisbon", 2854);
+ athens.put("London", 2391);
+ athens.put("Luxembourg", 1901);
+ athens.put("Madrid", 2374);
+ athens.put("Paris", 2097);
+ athens.put("Rome", 1040);
+ athens.put("Stockholm", 2410);
+ athens.put("Vienna", 1280);
+ DISTANCES.put("Athens", athens);
+
+ Map<String, Integer> berlin = new HashMap<String, Integer>(20);
+ berlin.put("Amsterdam", 576);
+ berlin.put("Athens", 1801);
+ berlin.put("Berlin", 0);
+ berlin.put("Brussels", 648);
+ berlin.put("Copenhagen", 361);
+ berlin.put("Dublin", 1315);
+ berlin.put("Helsinki", 1108);
+ berlin.put("Lisbon", 2310);
+ berlin.put("London", 929);
+ berlin.put("Luxembourg", 595);
+ berlin.put("Madrid", 1866);
+ berlin.put("Paris", 877);
+ berlin.put("Rome", 1185);
+ berlin.put("Stockholm", 818);
+ berlin.put("Vienna", 525);
+ DISTANCES.put("Berlin", berlin);
+
+ Map<String, Integer> brussels = new HashMap<String, Integer>(20);
+ brussels.put("Amsterdam", 171);
+ brussels.put("Athens", 2089);
+ brussels.put("Berlin", 648);
+ brussels.put("Brussels", 0);
+ brussels.put("Copenhagen", 764);
+ brussels.put("Dublin", 780);
+ brussels.put("Helsinki", 1649);
+ brussels.put("Lisbon", 1713);
+ brussels.put("London", 321);
+ brussels.put("Luxembourg", 190);
+ brussels.put("Madrid", 1315);
+ brussels.put("Paris", 266);
+ brussels.put("Rome", 1182);
+ brussels.put("Stockholm", 1284);
+ brussels.put("Vienna", 917);
+ DISTANCES.put("Brussels", brussels);
+
+ Map<String, Integer> copenhagen = new HashMap<String, Integer>(20);
+ copenhagen.put("Amsterdam", 622);
+ copenhagen.put("Athens", 2140);
+ copenhagen.put("Berlin", 361);
+ copenhagen.put("Brussels", 764);
+ copenhagen.put("Copenhagen", 0);
+ copenhagen.put("Dublin", 1232);
+ copenhagen.put("Helsinki", 885);
+ copenhagen.put("Lisbon", 2477);
+ copenhagen.put("London", 953);
+ copenhagen.put("Luxembourg", 799);
+ copenhagen.put("Madrid", 2071);
+ copenhagen.put("Paris", 1028);
+ copenhagen.put("Rome", 1540);
+ copenhagen.put("Stockholm", 526);
+ copenhagen.put("Vienna", 876);
+ DISTANCES.put("Copenhagen", copenhagen);
+
+ Map<String, Integer> dublin = new HashMap<String, Integer>(20);
+ dublin.put("Amsterdam", 757);
+ dublin.put("Athens", 2860);
+ dublin.put("Berlin", 1315);
+ dublin.put("Brussels", 780);
+ dublin.put("Copenhagen", 1232);
+ dublin.put("Dublin", 0);
+ dublin.put("Helsinki", 2021);
+ dublin.put("Lisbon", 1652);
+ dublin.put("London", 469);
+ dublin.put("Luxembourg", 961);
+ dublin.put("Madrid", 1458);
+ dublin.put("Paris", 787);
+ dublin.put("Rome", 1903);
+ dublin.put("Stockholm", 1625);
+ dublin.put("Vienna", 1687);
+ DISTANCES.put("Dublin", dublin);
+
+ Map<String, Integer> helsinki = new HashMap<String, Integer>(20);
+ helsinki.put("Amsterdam", 1506);
+ helsinki.put("Athens", 2464);
+ helsinki.put("Berlin", 1108);
+ helsinki.put("Brussels", 1649);
+ helsinki.put("Copenhagen", 885);
+ helsinki.put("Dublin", 2021);
+ helsinki.put("Helsinki", 0);
+ helsinki.put("Lisbon", 3362);
+ helsinki.put("London", 1823);
+ helsinki.put("Luxembourg", 1667);
+ helsinki.put("Madrid", 2949);
+ helsinki.put("Paris", 1912);
+ helsinki.put("Rome", 2202);
+ helsinki.put("Stockholm", 396);
+ helsinki.put("Vienna", 1439);
+ DISTANCES.put("Helsinki", helsinki);
+
+ Map<String, Integer> lisbon = new HashMap<String, Integer>(20);
+ lisbon.put("Amsterdam", 1861);
+ lisbon.put("Athens", 2854);
+ lisbon.put("Berlin", 2310);
+ lisbon.put("Brussels", 1713);
+ lisbon.put("Copenhagen", 2477);
+ lisbon.put("Dublin", 1652);
+ lisbon.put("Helsinki", 3362);
+ lisbon.put("Lisbon", 0);
+ lisbon.put("London", 1585);
+ lisbon.put("Luxembourg", 1716);
+ lisbon.put("Madrid", 501);
+ lisbon.put("Paris", 1452);
+ lisbon.put("Rome", 1873);
+ lisbon.put("Stockholm", 2993);
+ lisbon.put("Vienna", 2300);
+ DISTANCES.put("Lisbon", lisbon);
+
+ Map<String, Integer> london = new HashMap<String, Integer>(20);
+ london.put("Amsterdam", 356);
+ london.put("Athens", 2391);
+ london.put("Berlin", 929);
+ london.put("Brussels", 321);
+ london.put("Copenhagen", 953);
+ london.put("Dublin", 469);
+ london.put("Helsinki", 1823);
+ london.put("Lisbon", 1585);
+ london.put("London", 0);
+ london.put("Luxembourg", 494);
+ london.put("Madrid", 1261);
+ london.put("Paris", 343);
+ london.put("Rome", 1444);
+ london.put("Stockholm", 1436);
+ london.put("Vienna", 1237);
+ DISTANCES.put("London", london);
+
+ Map<String, Integer> luxembourg = new HashMap<String, Integer>(20);
+ luxembourg.put("Amsterdam", 318);
+ luxembourg.put("Athens", 1901);
+ luxembourg.put("Berlin", 595);
+ luxembourg.put("Brussels", 190);
+ luxembourg.put("Copenhagen", 799);
+ luxembourg.put("Dublin", 961);
+ luxembourg.put("Helsinki", 1667);
+ luxembourg.put("Lisbon", 1716);
+ luxembourg.put("London", 494);
+ luxembourg.put("Luxembourg", 0);
+ luxembourg.put("Madrid", 1282);
+ luxembourg.put("Paris", 294);
+ luxembourg.put("Rome", 995);
+ luxembourg.put("Stockholm", 1325);
+ luxembourg.put("Vienna", 761);
+ DISTANCES.put("Luxembourg", luxembourg);
+
+ Map<String, Integer> madrid = new HashMap<String, Integer>(20);
+ madrid.put("Amsterdam", 1477);
+ madrid.put("Athens", 2374);
+ madrid.put("Berlin", 1866);
+ madrid.put("Brussels", 1315);
+ madrid.put("Copenhagen", 2071);
+ madrid.put("Dublin", 1458);
+ madrid.put("Helsinki", 2949);
+ madrid.put("Lisbon", 501);
+ madrid.put("London", 1261);
+ madrid.put("Luxembourg", 1282);
+ madrid.put("Madrid", 0);
+ madrid.put("Paris", 1050);
+ madrid.put("Rome", 1377);
+ madrid.put("Stockholm", 2596);
+ madrid.put("Vienna", 1812);
+ DISTANCES.put("Madrid", madrid);
+
+ Map<String, Integer> paris = new HashMap<String, Integer>(20);
+ paris.put("Amsterdam", 429);
+ paris.put("Athens", 2097);
+ paris.put("Berlin", 877);
+ paris.put("Brussels", 266);
+ paris.put("Copenhagen", 1028);
+ paris.put("Dublin", 787);
+ paris.put("Helsinki", 1912);
+ paris.put("Lisbon", 1452);
+ paris.put("London", 343);
+ paris.put("Luxembourg", 294);
+ paris.put("Madrid", 1050);
+ paris.put("Paris", 0);
+ paris.put("Rome", 1117);
+ paris.put("Stockholm", 1549);
+ paris.put("Vienna", 1037);
+ DISTANCES.put("Paris", paris);
+
+ Map<String, Integer> rome = new HashMap<String, Integer>(20);
+ rome.put("Amsterdam", 1304);
+ rome.put("Athens", 1040);
+ rome.put("Berlin", 1185);
+ rome.put("Brussels", 1182);
+ rome.put("Copenhagen", 1540);
+ rome.put("Dublin", 1903);
+ rome.put("Helsinki", 2202);
+ rome.put("Lisbon", 1873);
+ rome.put("London", 1444);
+ rome.put("Luxembourg", 995);
+ rome.put("Madrid", 1377);
+ rome.put("Paris", 1117);
+ rome.put("Rome", 0);
+ rome.put("Stockholm", 1984);
+ rome.put("Vienna", 765);
+ DISTANCES.put("Rome", rome);
+
+ Map<String, Integer> stockholm = new HashMap<String, Integer>(20);
+ stockholm.put("Amsterdam", 1132);
+ stockholm.put("Athens", 2410);
+ stockholm.put("Berlin", 818);
+ stockholm.put("Brussels", 1284);
+ stockholm.put("Copenhagen", 526);
+ stockholm.put("Dublin", 1625);
+ stockholm.put("Helsinki", 396);
+ stockholm.put("Lisbon", 2993);
+ stockholm.put("London", 1436);
+ stockholm.put("Luxembourg", 1325);
+ stockholm.put("Madrid", 2596);
+ stockholm.put("Paris", 1549);
+ stockholm.put("Rome", 1984);
+ stockholm.put("Stockholm", 0);
+ stockholm.put("Vienna", 1247);
+ DISTANCES.put("Stockholm", stockholm);
+
+ Map<String, Integer> vienna = new HashMap<String, Integer>(20);
+ vienna.put("Amsterdam", 938);
+ vienna.put("Athens", 1280);
+ vienna.put("Berlin", 525);
+ vienna.put("Brussels", 917);
+ vienna.put("Copenhagen", 876);
+ vienna.put("Dublin", 1687);
+ vienna.put("Helsinki", 1439);
+ vienna.put("Lisbon", 2300);
+ vienna.put("London", 1237);
+ vienna.put("Luxembourg", 761);
+ vienna.put("Madrid", 1812);
+ vienna.put("Paris", 1037);
+ vienna.put("Rome", 765);
+ vienna.put("Stockholm", 1247);
+ vienna.put("Vienna", 0);
+ DISTANCES.put("Vienna", vienna);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<String> getKnownCities()
+ {
+ List<String> cities = new ArrayList<String>(DISTANCES.keySet());
+ Collections.sort(cities);
+ return cities;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public int getDistance(String startingCity, String destinationCity)
+ {
+ return DISTANCES.get(startingCity).get(destinationCity);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionPanel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionPanel.java
new file mode 100644
index 0000000..82089d5
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionPanel.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.FlowLayout;
+import java.util.List;
+import javax.swing.JCheckBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.SpringLayout;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.swing.SpringUtilities;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+import org.uncommons.watchmaker.swing.SelectionStrategyControl;
+
+/**
+ * Controls for configuring an {@link EvolutionaryTravellingSalesman} object.
+ * @author Daniel Dyer
+ */
+final class EvolutionPanel extends JPanel
+{
+ private final JLabel populationLabel;
+ private final JSpinner populationSpinner;
+ private final JLabel elitismLabel;
+ private final JSpinner elitismSpinner;
+ private final JLabel generationsLabel;
+ private final JSpinner generationsSpinner;
+ private final JLabel selectionLabel;
+ private final SelectionStrategyControl<List<String>> selectionControl;
+ private final JCheckBox crossoverCheckbox;
+ private final JCheckBox mutationCheckbox;
+ private final DistanceLookup distances;
+
+ EvolutionPanel(DistanceLookup distances)
+ {
+ super(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ this.distances = distances;
+ JPanel innerPanel = new JPanel(new SpringLayout());
+
+ populationLabel = new JLabel("Population Size: ");
+ populationSpinner = new JSpinner(new SpinnerNumberModel(300, 2, 10000, 1));
+ populationLabel.setLabelFor(populationSpinner);
+ innerPanel.add(populationLabel);
+ innerPanel.add(populationSpinner);
+
+ elitismLabel = new JLabel("Elitism: ");
+ elitismSpinner = new JSpinner(new SpinnerNumberModel(3, 0, 10000, 1));
+ elitismLabel.setLabelFor(elitismSpinner);
+ innerPanel.add(elitismLabel);
+ innerPanel.add(elitismSpinner);
+
+ generationsLabel = new JLabel("Number of Generations: ");
+ generationsSpinner = new JSpinner(new SpinnerNumberModel(100, 1, 10000, 1));
+ generationsLabel.setLabelFor(generationsSpinner);
+ innerPanel.add(generationsLabel);
+ innerPanel.add(generationsSpinner);
+
+ selectionLabel = new JLabel("Selection Strategy: ");
+ List<SelectionStrategy<? super List<String>>> strategies
+ = SelectionStrategyControl.createDefaultOptions(new Probability(0.95d), 0.5d);
+ this.selectionControl = new SelectionStrategyControl<List<String>>(strategies);
+ innerPanel.add(selectionLabel);
+ selectionControl.getControl().setSelectedIndex(selectionControl.getControl().getItemCount() - 1);
+ innerPanel.add(selectionControl.getControl());
+
+ crossoverCheckbox = new JCheckBox("Cross-over", true);
+ mutationCheckbox = new JCheckBox("Mutation", true);
+
+ innerPanel.add(crossoverCheckbox);
+ innerPanel.add(mutationCheckbox);
+
+ SpringUtilities.makeCompactGrid(innerPanel, 5, 2, 30, 6, 6, 6);
+ add(innerPanel);
+ }
+
+
+ @Override
+ public void setEnabled(boolean b)
+ {
+ populationLabel.setEnabled(b);
+ populationSpinner.setEnabled(b);
+ elitismLabel.setEnabled(b);
+ elitismSpinner.setEnabled(b);
+ generationsLabel.setEnabled(b);
+ generationsSpinner.setEnabled(b);
+ selectionLabel.setEnabled(b);
+ selectionControl.getControl().setEnabled(b);
+ crossoverCheckbox.setEnabled(b);
+ mutationCheckbox.setEnabled(b);
+ super.setEnabled(b);
+ }
+
+
+ public TravellingSalesmanStrategy getStrategy()
+ {
+ return new EvolutionaryTravellingSalesman(distances,
+ selectionControl.getSelectionStrategy(),
+ (Integer) populationSpinner.getValue(),
+ (Integer) elitismSpinner.getValue(),
+ (Integer) generationsSpinner.getValue(),
+ crossoverCheckbox.isSelected(),
+ mutationCheckbox.isSelected());
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java
new file mode 100644
index 0000000..6814101
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesman.java
@@ -0,0 +1,152 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+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.factories.ListPermutationFactory;
+import org.uncommons.watchmaker.framework.operators.EvolutionPipeline;
+import org.uncommons.watchmaker.framework.operators.ListOrderCrossover;
+import org.uncommons.watchmaker.framework.operators.ListOrderMutation;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * Evolutionary algorithm for finding (approximate) solutions to the
+ * travelling salesman problem.
+ * @author Daniel Dyer
+ */
+public class EvolutionaryTravellingSalesman implements TravellingSalesmanStrategy
+{
+ private final DistanceLookup distances;
+ private final SelectionStrategy<? super List<String>> selectionStrategy;
+ private final int populationSize;
+ private final int eliteCount;
+ private final int generationCount;
+ private final boolean crossover;
+ private final boolean mutation;
+
+ /**
+ * Creates an evolutionary Travelling Salesman solver with the
+ * specified configuration.
+ * @param distances Information about the distances between cities.
+ * @param selectionStrategy The selection implementation to use for
+ * the evolutionary algorithm.
+ * @param populationSize The number of candidates in the population
+ * of evolved routes.
+ * @param eliteCount The number of candidates to preserve via elitism
+ * at each generation.
+ * @param generationCount The number of iterations of evolution to perform.
+ * @param crossover Whether or not to use a cross-over operator in the evolution.
+ * @param mutation Whether or not to use a mutation operator in the evolution.
+ */
+ public EvolutionaryTravellingSalesman(DistanceLookup distances,
+ SelectionStrategy<? super List<String>> 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.");
+ }
+ this.distances = distances;
+ this.selectionStrategy = selectionStrategy;
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.generationCount = generationCount;
+ this.crossover = crossover;
+ this.mutation = mutation;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getDescription()
+ {
+ String selectionName = selectionStrategy.toString();
+ return "Evolution (pop: " + populationSize + ", gen: " + generationCount
+ + ", elite: " + eliteCount + ", " + selectionName + ")";
+ }
+
+
+ /**
+ * Calculates the shortest route using a generational evolutionary
+ * algorithm with a single ordered mutation operator and truncation
+ * selection.
+ * @param cities The list of destinations, each of which must be visited
+ * once.
+ * @param progressListener Call-back for receiving the status of the
+ * algorithm as it progresses.
+ * @return The (approximate) shortest route that visits each of the
+ * specified cities once.
+ */
+ public List<String> calculateShortestRoute(Collection<String> cities,
+ final ProgressListener progressListener)
+ {
+ Random rng = new MersenneTwisterRNG();
+
+ // Set-up evolution pipeline (cross-over followed by mutation).
+ List<EvolutionaryOperator<List<String>>> operators = new ArrayList<EvolutionaryOperator<List<String>>>(2);
+ if (crossover)
+ {
+ operators.add(new ListOrderCrossover<String>());
+ }
+ if (mutation)
+ {
+ operators.add(new ListOrderMutation<String>(new PoissonGenerator(1.5, rng),
+ new PoissonGenerator(1.5, rng)));
+ }
+
+ EvolutionaryOperator<List<String>> pipeline = new EvolutionPipeline<List<String>>(operators);
+
+ CandidateFactory<List<String>> candidateFactory
+ = new ListPermutationFactory<String>(new LinkedList<String>(cities));
+ EvolutionEngine<List<String>> engine
+ = new GenerationalEvolutionEngine<List<String>>(candidateFactory,
+ pipeline,
+ new RouteEvaluator(distances),
+ selectionStrategy,
+ rng);
+ if (progressListener != null)
+ {
+ engine.addEvolutionObserver(new EvolutionObserver<List<String>>()
+ {
+ public void populationUpdate(PopulationData<? extends List<String>> data)
+ {
+ progressListener.updateProgress(((double) data.getGenerationNumber() + 1) / generationCount * 100);
+ }
+ });
+ }
+ return engine.evolve(populationSize,
+ eliteCount,
+ new GenerationCount(generationCount));
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ExecutionPanel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ExecutionPanel.java
new file mode 100644
index 0000000..e4d5be0
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ExecutionPanel.java
@@ -0,0 +1,107 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.ActionListener;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JPanel;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingUtilities;
+
+/**
+ * Panel for controlling the execution of the Travelling Salesman applet.
+ * Contains controls for starting and stopping the route-finding algorithms
+ * and for displaying progress and results.
+ * @author Daniel Dyer
+ */
+final class ExecutionPanel extends JPanel implements ProgressListener
+{
+ private final JButton startButton;
+ private final JTextArea output;
+ private final JScrollPane scroller;
+ private final JProgressBar progressBar;
+
+
+ ExecutionPanel()
+ {
+ super(new BorderLayout());
+ JPanel controlPanel = new JPanel(new BorderLayout());
+ startButton = new JButton("Start");
+ controlPanel.add(startButton, BorderLayout.WEST);
+ progressBar = new JProgressBar(0, 100);
+ controlPanel.add(progressBar, BorderLayout.CENTER);
+ add(controlPanel, BorderLayout.NORTH);
+ output = new JTextArea();
+ output.setEditable(false);
+ output.setLineWrap(true);
+ output.setWrapStyleWord(true);
+ output.setFont(new Font("Monospaced", Font.PLAIN, 12));
+ scroller = new JScrollPane(output);
+ scroller.setBorder(BorderFactory.createTitledBorder("Results"));
+ add(scroller, BorderLayout.CENTER);
+ }
+
+
+ /**
+ * Adds logic to the start button so that something happens when
+ * it is clicked.
+ * @param actionListener The action to perform when the button is
+ * clicked.
+ */
+ public void addActionListener(ActionListener actionListener)
+ {
+ startButton.addActionListener(actionListener);
+ }
+
+
+ /**
+ * Updates the position of the progress bar.
+ */
+ public void updateProgress(final double percentComplete)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ progressBar.setValue((int) percentComplete);
+ }
+ });
+ }
+
+
+ /**
+ * Appends the specified text to this panel's text area.
+ * @param text The text to append.
+ */
+ public void appendOutput(String text)
+ {
+ output.append(text);
+ }
+
+
+ @Override
+ public void setEnabled(boolean b)
+ {
+ startButton.setEnabled(b);
+ scroller.setEnabled(b);
+ super.setEnabled(b);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanel.java
new file mode 100644
index 0000000..0e5c154
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanel.java
@@ -0,0 +1,114 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JPanel;
+
+/**
+ * Component for selecting which cities are to be visited by the
+ * travelling salesman.
+ * @author Daniel Dyer
+ */
+final class ItineraryPanel extends JPanel
+{
+ private final Collection<JCheckBox> checkBoxes;
+ private final JButton selectAllButton;
+ private final JButton clearButton;
+
+ ItineraryPanel(List<String> cities)
+ {
+ super(new BorderLayout());
+
+ JPanel checkBoxPanel = new JPanel(new GridLayout(0, 1));
+ checkBoxes = new ArrayList<JCheckBox>(cities.size());
+ for (String city : cities)
+ {
+ JCheckBox checkBox = new JCheckBox(city, false);
+ checkBox.setName(city); // Helps to find the checkbox from a unit test.
+ checkBoxes.add(checkBox);
+ checkBoxPanel.add(checkBox);
+ }
+ add(checkBoxPanel, BorderLayout.CENTER);
+
+ JPanel buttonPanel = new JPanel(new GridLayout(2, 1));
+ selectAllButton = new JButton("Select All");
+ selectAllButton.setName("All");
+ buttonPanel.add(selectAllButton);
+ clearButton = new JButton("Clear Selection");
+ clearButton.setName("None");
+ buttonPanel.add(clearButton);
+ ActionListener buttonListener = new ActionListener()
+ {
+
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ boolean select = actionEvent.getSource() == selectAllButton;
+ for (JCheckBox checkBox : checkBoxes)
+ {
+ checkBox.setSelected(select);
+ }
+ }
+ };
+ selectAllButton.addActionListener(buttonListener);
+ clearButton.addActionListener(buttonListener);
+ add(buttonPanel, BorderLayout.SOUTH);
+
+ setBorder(BorderFactory.createTitledBorder("Itinerary"));
+ }
+
+
+ /**
+ * Returns the cities that have been selected as part of the itinerary.
+ * @return A list of cities.
+ */
+ public Collection<String> getSelectedCities()
+ {
+ Set<String> cities = new TreeSet<String>();
+ for (JCheckBox checkBox : checkBoxes)
+ {
+ if (checkBox.isSelected())
+ {
+ cities.add(checkBox.getText());
+ }
+ }
+ return cities;
+ }
+
+
+ @Override
+ public void setEnabled(boolean b)
+ {
+ for (JCheckBox checkBox : checkBoxes)
+ {
+ checkBox.setEnabled(b);
+ }
+ selectAllButton.setEnabled(b);
+ clearButton.setEnabled(b);
+ super.setEnabled(b);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ProgressListener.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ProgressListener.java
new file mode 100644
index 0000000..661a5e5
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/ProgressListener.java
@@ -0,0 +1,31 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+/**
+ * Call-back interface for keeping track of the progress of a
+ * {@link TravellingSalesmanStrategy} implementation.
+ * @author Daniel Dyer
+ */
+public interface ProgressListener
+{
+ /**
+ * Call-back method that informs the implementing object
+ * of the current completion percentage.
+ * @param percentComplete A percentage between 0 and 100.
+ */
+ void updateProgress(double percentComplete);
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java
new file mode 100644
index 0000000..144d593
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluator.java
@@ -0,0 +1,74 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.List;
+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.
+ * @author Daniel Dyer
+ */
+public class RouteEvaluator implements FitnessEvaluator<List<String>>
+{
+ private final DistanceLookup distances;
+
+
+ /**
+ * @param distances Provides distances between a set of cities.
+ */
+ public RouteEvaluator(DistanceLookup distances)
+ {
+ this.distances = 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
+ * each city in order and returns to the starting point.
+ */
+ public double getFitness(List<String> candidate,
+ List<? extends List<String>> population)
+ {
+ int totalDistance = 0;
+ int cityCount = candidate.size();
+ for (int i = 0; i < cityCount; i++)
+ {
+ int nextIndex = i < cityCount - 1 ? i + 1 : 0;
+ totalDistance += distances.getDistance(candidate.get(i),
+ candidate.get(nextIndex));
+ }
+ return totalDistance;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * Returns false since shorter distances represent fitter candidates.
+ * @return false
+ */
+ public boolean isNatural()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanel.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanel.java
new file mode 100644
index 0000000..99f727d
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanel.java
@@ -0,0 +1,92 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import javax.swing.BorderFactory;
+import javax.swing.ButtonGroup;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+
+/**
+ * Panel for configuring a route-finding strategy for the travelling
+ * salesman problem.
+ * @author Daniel Dyer
+ */
+final class StrategyPanel extends JPanel
+{
+ private final DistanceLookup distances;
+ private final JRadioButton evolutionOption;
+ private final JRadioButton bruteForceOption;
+ private final EvolutionPanel evolutionPanel;
+
+ /**
+ * Creates a panel with components for controlling the route-finding
+ * strategy.
+ * @param distances Data used by the strategy in order to calculate
+ * shortest routes.
+ */
+ StrategyPanel(DistanceLookup distances)
+ {
+ super(new BorderLayout());
+ this.distances = distances;
+ evolutionOption = new JRadioButton("Evolution", true);
+ evolutionOption.setName("EvolutionOption"); // Helps to find the radio button from a unit test.
+ bruteForceOption = new JRadioButton("Brute Force", false);
+ bruteForceOption.setName("BruteForceOption"); // Helps to find the radio button from a unit test.
+ evolutionOption.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent itemEvent)
+ {
+ evolutionPanel.setEnabled(evolutionOption.isSelected());
+ }
+ });
+ ButtonGroup strategyGroup = new ButtonGroup();
+ strategyGroup.add(evolutionOption);
+ strategyGroup.add(bruteForceOption);
+ evolutionPanel = new EvolutionPanel(distances);
+ evolutionPanel.setName("EvolutionPanel"); // Helps to find the panel from a unit test.
+ add(evolutionOption, BorderLayout.NORTH);
+ add(evolutionPanel, BorderLayout.CENTER);
+ add(bruteForceOption, BorderLayout.SOUTH);
+ setBorder(BorderFactory.createTitledBorder("Route-Finding Strategy"));
+ }
+
+
+ public TravellingSalesmanStrategy getStrategy()
+ {
+ if (bruteForceOption.isSelected())
+ {
+ return new BruteForceTravellingSalesman(distances);
+ }
+ else
+ {
+ return evolutionPanel.getStrategy();
+ }
+ }
+
+
+ @Override
+ public void setEnabled(boolean b)
+ {
+ evolutionOption.setEnabled(b);
+ bruteForceOption.setEnabled(b);
+ evolutionPanel.setEnabled(b && evolutionOption.isSelected());
+ super.setEnabled(b);
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanApplet.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanApplet.java
new file mode 100644
index 0000000..96490b2
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanApplet.java
@@ -0,0 +1,185 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Collection;
+import java.util.List;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import org.uncommons.swing.SwingBackgroundTask;
+import org.uncommons.watchmaker.examples.AbstractExampleApplet;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * Applet for comparing evolutionary and brute force approaches to the
+ * Travelling Salesman problem.
+ * @author Daniel Dyer
+ */
+public final class TravellingSalesmanApplet extends AbstractExampleApplet
+{
+ private final DistanceLookup distances = new EuropeanDistanceLookup();
+ private final FitnessEvaluator<List<String>> evaluator = new RouteEvaluator(distances);
+
+ private ItineraryPanel itineraryPanel;
+ private StrategyPanel strategyPanel;
+ private ExecutionPanel executionPanel;
+
+
+ /**
+ * Initialise and layout the GUI.
+ * @param container The Swing component that will contain the GUI controls.
+ */
+ @Override
+ protected void prepareGUI(Container container)
+ {
+ itineraryPanel = new ItineraryPanel(distances.getKnownCities());
+ strategyPanel = new StrategyPanel(distances);
+ executionPanel = new ExecutionPanel();
+
+ container.add(itineraryPanel, BorderLayout.WEST);
+ JPanel innerPanel = new JPanel(new BorderLayout());
+ innerPanel.add(strategyPanel, BorderLayout.NORTH);
+ innerPanel.add(executionPanel, BorderLayout.CENTER);
+ container.add(innerPanel, BorderLayout.CENTER);
+
+ executionPanel.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ Collection<String> cities = itineraryPanel.getSelectedCities();
+ if (cities.size() < 4)
+ {
+ JOptionPane.showMessageDialog(TravellingSalesmanApplet.this,
+ "Itinerary must include at least 4 cities.",
+ "Error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ else
+ {
+ try
+ {
+ setEnabled(false);
+ createTask(cities).execute();
+ }
+ catch (IllegalArgumentException ex)
+ {
+ JOptionPane.showMessageDialog(TravellingSalesmanApplet.this,
+ ex.getMessage(),
+ "Error",
+ JOptionPane.ERROR_MESSAGE);
+ setEnabled(true);
+ }
+ }
+ }
+ });
+ container.validate();
+ }
+
+
+ /**
+ * Helper method to create a background task for running the travelling
+ * salesman algorithm.
+ * @param cities The set of cities to generate a route for.
+ * @return A Swing task that will execute on a background thread and update
+ * the GUI when it is done.
+ */
+ private SwingBackgroundTask<List<String>> createTask(final Collection<String> cities)
+ {
+ final TravellingSalesmanStrategy strategy = strategyPanel.getStrategy();
+ return new SwingBackgroundTask<List<String>>()
+ {
+ private long elapsedTime = 0;
+
+ @Override
+ protected List<String> performTask()
+ {
+ long startTime = System.currentTimeMillis();
+ List<String> result = strategy.calculateShortestRoute(cities, executionPanel);
+ elapsedTime = System.currentTimeMillis() - startTime;
+ return result;
+ }
+
+ @Override
+ protected void postProcessing(List<String> result)
+ {
+ executionPanel.appendOutput(createResultString(strategy.getDescription(),
+ result,
+ evaluator.getFitness(result, null),
+ elapsedTime));
+ setEnabled(true);
+ }
+ };
+ }
+
+
+ /**
+ * Helper method for formatting a result as a string for display.
+ */
+ private String createResultString(String strategyDescription,
+ List<String> shortestRoute,
+ double distance,
+ long elapsedTime)
+ {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append('[');
+ buffer.append(strategyDescription);
+ buffer.append("]\n");
+ buffer.append("ROUTE: ");
+ for (String s : shortestRoute)
+ {
+ buffer.append(s);
+ buffer.append(" -> ");
+ }
+ buffer.append(shortestRoute.get(0));
+ buffer.append('\n');
+ buffer.append("TOTAL DISTANCE: ");
+ buffer.append(String.valueOf(distance));
+ buffer.append("km\n");
+ buffer.append("(Search Time: ");
+ double seconds = (double) elapsedTime / 1000;
+ buffer.append(String.valueOf(seconds));
+ buffer.append(" seconds)\n\n");
+ return buffer.toString();
+ }
+
+
+ /**
+ * Toggles whether the controls are enabled for input or not.
+ * @param b Enables the controls if this flag is true, disables them otherwise.
+ */
+ @Override
+ public void setEnabled(boolean b)
+ {
+ itineraryPanel.setEnabled(b);
+ strategyPanel.setEnabled(b);
+ executionPanel.setEnabled(b);
+ super.setEnabled(b);
+ }
+
+
+ /**
+ * Entry point for running this example as an application rather than an applet.
+ * @param args Program arguments (ignored).
+ */
+ public static void main(String[] args)
+ {
+ new TravellingSalesmanApplet().displayInFrame("Watchmaker Framework - Travelling Salesman Example");
+ }
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanStrategy.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanStrategy.java
new file mode 100644
index 0000000..82f75c8
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/TravellingSalesmanStrategy.java
@@ -0,0 +1,44 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Defines methods that must be implemented by classes that provide
+ * solutions to the Travelling Salesman problem.
+ * @author Daniel Dyer
+ */
+public interface TravellingSalesmanStrategy
+{
+ /**
+ * @return A description of the strategy.
+ */
+ String getDescription();
+
+ /**
+ * Calculates the shortest round trip distance that visits each
+ * of the specified cities once and returns to the starting point.
+ * @param cities The destination that must each be visited for the route
+ * to be valid.
+ * @param progressListener A call-back for keeping track of the route-finding
+ * algorithm's progress.
+ * @return The shortest route found for the given list of destinations.
+ */
+ List<String> calculateShortestRoute(Collection<String> cities,
+ ProgressListener progressListener);
+}
diff --git a/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/package-info.java b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/package-info.java
new file mode 100644
index 0000000..59f25f5
--- /dev/null
+++ b/watchmaker/examples/src/java/main/org/uncommons/watchmaker/examples/travellingsalesman/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An evolutionary approach to tackling the well-known Travelling Salesman
+ * problem.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.examples.travellingsalesman;
diff --git a/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/lighthouse.jpg b/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/lighthouse.jpg
new file mode 100644
index 0000000..fcd57f6
--- /dev/null
+++ b/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/lighthouse.jpg
Binary files differ
diff --git a/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/monalisa.jpg b/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/monalisa.jpg
new file mode 100644
index 0000000..61ae626
--- /dev/null
+++ b/watchmaker/examples/src/java/resources/org/uncommons/watchmaker/examples/monalisa/monalisa.jpg
Binary files differ
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/AbstractExampleAppletTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/AbstractExampleAppletTest.java
new file mode 100644
index 0000000..f762147
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/AbstractExampleAppletTest.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// 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;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.SwingUtilities;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.core.matcher.FrameMatcher;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link AbstractExampleAppletTest} class.
+ * @author Daniel Dyer
+ */
+public class AbstractExampleAppletTest
+{
+ private Robot robot;
+
+ @BeforeMethod
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testPreparationOnEDT()
+ {
+ final boolean[] onEDT = new boolean[1];
+ AbstractExampleApplet applet = new AbstractExampleApplet()
+ {
+ @Override
+ protected void prepareGUI(Container container)
+ {
+ onEDT[0] = SwingUtilities.isEventDispatchThread();
+ }
+ };
+ applet.init();
+ assert onEDT[0] : "Prepare method was not called on Event Dispatch Thread.";
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testDisplayInFrame()
+ {
+ AbstractExampleApplet applet = new AbstractExampleApplet()
+ {
+ @Override
+ protected void prepareGUI(Container container)
+ {
+ JLabel label = new JLabel("Test");
+ label.setName("Test");
+ container.add(label, BorderLayout.CENTER);
+ }
+ };
+ applet.displayInFrame("ExampleFrame");
+ robot.waitForIdle();
+ // There ought to be a visible frame containing the example GUI.
+ JFrame frame = (JFrame) robot.finder().find(FrameMatcher.withTitle("ExampleFrame").andShowing());
+ frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ assert frameFixture.label("Test").component().isShowing() : "GUI not displayed correctly.";
+ frameFixture.close();
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/ExamplesTestUtils.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/ExamplesTestUtils.java
new file mode 100644
index 0000000..5affc17
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/ExamplesTestUtils.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// 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;
+
+import java.util.Random;
+import org.uncommons.maths.random.XORShiftRNG;
+
+/**
+ * Utility methods for Watchmaker examples unit tests. Provides
+ * access to shared resources used by tests.
+ * @author Daniel Dyer
+ */
+public final class ExamplesTestUtils
+{
+ private static final Random RNG = new XORShiftRNG();
+
+ private ExamplesTestUtils()
+ {
+ // Prevent instantiation.
+ }
+
+
+ /**
+ * Returns the singleton RNG shared by all tests. It might be preferable
+ * to have a separate RNG for each test (for true separation) but this
+ * causes problems. Seeding dozens of RNGs can exhaust the system's
+ * available entropy (the Uncommons Maths RNGs seed themselves from
+ * /dev/random by default).
+ * @return A random number generator.
+ */
+ public static Random getRNG()
+ {
+ return RNG;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactoryTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactoryTest.java
new file mode 100644
index 0000000..c1d1cce
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphFactoryTest.java
@@ -0,0 +1,51 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Unit test for the biomorph candidate factory.
+ * @author Daniel Dyer
+ */
+public class BiomorphFactoryTest
+{
+ /**
+ * Ensures that biomorphs created by the factory are valid.
+ */
+ @Test
+ public void testValidity()
+ {
+ CandidateFactory<Biomorph> factory = new BiomorphFactory();
+ List<Biomorph> biomorphs = factory.generateInitialPopulation(20, ExamplesTestUtils.getRNG());
+ for (Biomorph biomorph : biomorphs)
+ {
+ // Returns 9 genes, last one is the length gene.
+ int[] genes = biomorph.getGenotype();
+ for (int i = 0; i < Biomorph.GENE_COUNT - 1; i++)
+ {
+ assert genes[i] >= Biomorph.GENE_MIN && genes[i] <= Biomorph.GENE_MAX
+ : "Gene " + i + " is out of range: " + genes[i];
+ }
+ int length = biomorph.getLengthPhenotype();
+ assert length >= Biomorph.LENGTH_GENE_MIN && length <= Biomorph.LENGTH_GENE_MAX
+ : "Length gene is out of range: " + length;
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphTest.java
new file mode 100644
index 0000000..b62c8a2
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/BiomorphTest.java
@@ -0,0 +1,59 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.Arrays;
+import org.testng.annotations.Test;
+
+/**
+ * Some basic sanity checks for the {@link Biomorph} type used in the
+ * interactive evolution example program.
+ * @author Daniel Dyer
+ */
+public class BiomorphTest
+{
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInsufficientGenes()
+ {
+ new Biomorph(new int[Biomorph.GENE_COUNT - 1]);
+ }
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManyGenes()
+ {
+ new Biomorph(new int[Biomorph.GENE_COUNT + 1]);
+ }
+
+
+ @Test
+ public void testEquality()
+ {
+ Biomorph biomorph1 = new Biomorph(new int[Biomorph.GENE_COUNT]);
+ Biomorph biomorph2 = new Biomorph(new int[Biomorph.GENE_COUNT]);
+ int[] genes = new int[Biomorph.GENE_COUNT];
+ Arrays.fill(genes, 2);
+ Biomorph biomorph3 = new Biomorph(genes);
+
+ assert biomorph1.equals(biomorph1) : "Equality must be reflexive.";
+ assert biomorph1.equals(biomorph2) : "Biomorphs with identical genes should be considered equal.";
+ assert biomorph2.equals(biomorph1) : "Equality must be reflective.";
+ assert biomorph1.hashCode() == biomorph2.hashCode() : "Equal objects must have identical hash codes.";
+ assert !biomorph1.equals(biomorph3) : "Biomorphs with different genes should not be considered equal.";
+
+ assert !biomorph1.equals(null) : "No object should be considered equal to a null reference.";
+ assert !biomorph3.equals(genes) : "Biomorphs should not be considered equal to objects of different types.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutationTest.java
new file mode 100644
index 0000000..09efe3e
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/DawkinsBiomorphMutationTest.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for non-random mutation used by Biomorph example application.
+ * @author Daniel Dyer
+ */
+public class DawkinsBiomorphMutationTest
+{
+ /**
+ * Ensure that each possible mutation occurs exactly once.
+ */
+ @Test
+ public void testMutations()
+ {
+ Biomorph source = new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 1, 2, 8});
+ List<Biomorph> originalPopulation = new ArrayList<Biomorph>(18);
+ for (int i = 0; i < 18; i++)
+ {
+ originalPopulation.add(source);
+ }
+ EvolutionaryOperator<Biomorph> mutation = new DawkinsBiomorphMutation();
+ List<Biomorph> mutatedPopulation = mutation.apply(originalPopulation,
+ ExamplesTestUtils.getRNG()); // RNG should be ignored.
+ assert mutatedPopulation.size() == originalPopulation.size() : "Mutated population is wrong size.";
+ // Lazy way of checking for duplicates. Add all mutations to a set. If there are any
+ // duplicates, the size of the set will be shorter than the list.
+ Set<Biomorph> distinctBiomorphs = new HashSet<Biomorph>(mutatedPopulation);
+ assert distinctBiomorphs.size() == mutatedPopulation.size() : "Mutated population contains duplicates.";
+ // Check for each of the expected mutations (mutations should differ from the original in only one gene).
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{5, -4, -3, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-4, -4, -3, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -5, -3, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -3, -3, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -4, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -2, -2, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -3, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -1, -1, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -2, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, 0, 0, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, -1, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 1, 1, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 0, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 2, 2, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 1, 1, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 1, 3, 8})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 1, 2, 7})) : "Missing mutation.";
+ assert distinctBiomorphs.contains(new Biomorph(new int[]{-5, -4, -3, -2, -1, 0, 1, 2, 1})) : "Missing mutation.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutationTest.java
new file mode 100644
index 0000000..5c0890d
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/biomorphs/RandomBiomorphMutationTest.java
@@ -0,0 +1,59 @@
+//=============================================================================
+// 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.biomorphs;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for random mutation operator for biomorphs.
+ * @author Daniel Dyer
+ */
+public class RandomBiomorphMutationTest
+{
+ @Test
+ public void testValidity()
+ {
+ EvolutionaryOperator<Biomorph> mutation = new RandomBiomorphMutation(Probability.ONE); // Mutate every gene.
+ List<Biomorph> population = new ArrayList<Biomorph>(3);
+ population.add(new Biomorph(new int[]{5, -4, -3, -2, -1, 0, 1, 2, 8}));
+ population.add(new Biomorph(new int[]{-5, 4, 4, -5, 5, 3, 0, 2, 2}));
+ population.add(new Biomorph(new int[]{-4, -1, 0, 0, 3, 0, 4, 5, 4}));
+ Random rng = ExamplesTestUtils.getRNG();
+ for (int i = 0; i < 20; i++) // Perform several mutations to cover more possibilties.
+ {
+ population = mutation.apply(population, rng);
+ for (Biomorph biomorph : population)
+ {
+ // Returns 9 genes, last one is the length gene.
+ int[] genes = biomorph.getGenotype();
+ for (int j = 0; j < Biomorph.GENE_COUNT - 1; j++)
+ {
+ assert genes[j] >= Biomorph.GENE_MIN && genes[j] <= Biomorph.GENE_MAX
+ : "Gene " + j + " is out of range: " + genes[j];
+ }
+ int length = biomorph.getLengthPhenotype();
+ assert length >= Biomorph.LENGTH_GENE_MIN && length <= Biomorph.LENGTH_GENE_MAX
+ : "Length gene is out of range: " + length;
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/bits/BitsExampleTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/bits/BitsExampleTest.java
new file mode 100644
index 0000000..737ed55
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/bits/BitsExampleTest.java
@@ -0,0 +1,34 @@
+//=============================================================================
+// 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.bits;
+
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+
+/**
+ * Simple unit test for the bits example. Makes sure that the evolution engine
+ * eventually returns the expected result.
+ * @author Daniel Dyer
+ */
+public class BitsExampleTest
+{
+ @Test
+ public void testEvolution()
+ {
+ BitString result = BitsExample.evolveBits(8);
+ assert result.toString().equals("11111111") : "Wrong result returned: " + result.toString();
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/AdditionTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/AdditionTest.java
new file mode 100644
index 0000000..1ad9a46
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/AdditionTest.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the {@link Addition} node type.
+ * @author Daniel Dyer
+ */
+public class AdditionTest
+{
+ @Test
+ public void testEvaluation()
+ {
+ Node node = new Addition(new Constant(1), new Parameter(0));
+ double value = node.evaluate(new double[]{3}); // 1 + 3
+ assert value == 4 : "Wrong result: " + value;
+ }
+
+ @Test
+ public void testStringRepresentation()
+ {
+ Node node = new Addition(new Constant(1), new Constant(3));
+ assert node.print().equals("(1.0 + 3.0)") : "Wrong string representation: " + node.print();
+ }
+
+
+ /**
+ * If the arguments to the add function are both constants then the addition node
+ * should be replaced by a constant node containing the evaluation of this sum.
+ */
+ @Test
+ public void testSimplifyConstants()
+ {
+ Node node = new Addition(new Constant(7), new Constant(5));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 12;
+
+ }
+
+
+
+ /**
+ * Test that simplification doesn't cause any problems when the expression is already as simple
+ * as possible.
+ */
+ @Test
+ public void testSimplifySimplest()
+ {
+ Node node = new Addition(new Parameter(0), new Constant(1));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified.
+ */
+ @Test
+ public void testSimplifySubNode()
+ {
+ Node node = new Addition(new Parameter(0),
+ new Addition(new Constant(3), new Constant(2)));
+ Node simplified = node.simplify();
+ assert simplified instanceof Addition
+ : "Simplified node should be Addition, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.countNodes() < node.countNodes() : "Should be fewer nodes after simplification.";
+ }
+
+
+ @Test
+ public void testSimplifyAddZero()
+ {
+ Node node = new Addition(new Parameter(0), new Constant(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Parameter
+ : "Simplified node should be Parameter, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ }
+
+
+ @Test
+ public void testSimplifyAddToZero()
+ {
+ Node node = new Addition(new Constant(0), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Parameter
+ : "Simplified node should be Parameter, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ConstantTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ConstantTest.java
new file mode 100644
index 0000000..1a5eb36
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ConstantTest.java
@@ -0,0 +1,55 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link Constant} node type.
+ * @author Daniel Dyer
+ */
+public class ConstantTest
+{
+ @Test
+ public void testEquality()
+ {
+ Constant zero = new Constant(0);
+ Constant one = new Constant(1);
+ Constant anotherOne = new Constant(1);
+
+ assert zero.equals(zero) : "Equality must be reflexive.";
+ assert one.equals(anotherOne) : "Same-valued constants must be equal.";
+ assert anotherOne.equals(one) : "Equality must be symmetric.";
+ assert one.hashCode() == anotherOne.hashCode() : "Equal objects must have equal hash codes.";
+ assert !zero.equals(one) : "Different valued constants must be non-equal.";
+ assert !zero.equals(null) : "No non-null object should not be considered equal to null.";
+ assert !zero.equals(Double.valueOf(0)) : "Objects of different types should not be equal.";
+ }
+
+
+ @Test(expectedExceptions = IndexOutOfBoundsException.class)
+ public void testGetInvalidNodeIndex()
+ {
+ new Constant(1).getNode(1); // Should throw an exception.
+ }
+
+
+ @Test(expectedExceptions = IndexOutOfBoundsException.class)
+ public void testReplaceInvalidNodeIndex()
+ {
+ new Constant(1).replaceNode(1, new Constant(2)); // Should throw an exception.
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExampleTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExampleTest.java
new file mode 100644
index 0000000..3d254fd
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/GeneticProgrammingExampleTest.java
@@ -0,0 +1,50 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.testng.annotations.Test;
+
+/**
+ * Test for the {@link GeneticProgrammingExample} example application.
+ * @author Daniel Dyer
+ */
+public class GeneticProgrammingExampleTest
+{
+ @Test
+ public void testApplication()
+ {
+ Map<double[], Double> testData = new HashMap<double[], Double>();
+ testData.put(new double[]{26, 35}, 165.0d);
+ testData.put(new double[]{8, 24}, 64.0d);
+ testData.put(new double[]{20, 1}, 101.0d);
+ testData.put(new double[]{33, 11}, 176.0d);
+ testData.put(new double[]{37, 16}, 201.0d);
+
+ Node evolvedProgram = GeneticProgrammingExample.evolveProgram(testData);
+
+ // Check that evolved program works for test data.
+ double result1 = evolvedProgram.evaluate(new double[]{8, 24});
+ assert result1 == 64.0d : "Incorrect result: " + result1;
+
+ // Check that the evolved program generalises.
+ double result2 = evolvedProgram.evaluate(new double[]{10, 7});
+ assert result2 == 57.0d : "Incorrect result: " + result2;
+ double result3 = evolvedProgram.evaluate(new double[]{13, 22});
+ assert result3 == 87.0d : "Incorrect result: " + result3;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElseTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElseTest.java
new file mode 100644
index 0000000..13ab813
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IfThenElseTest.java
@@ -0,0 +1,248 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+
+/**
+ * Unit test for the {@link IfThenElse} node type.
+ * @author Daniel Dyer
+ */
+public class IfThenElseTest
+{
+ @Test
+ public void testIfBranch()
+ {
+ Node node = new IfThenElse(new Constant(1),
+ new Constant(5),
+ new Constant(10));
+ // Condition (1) is true, so result should be 5.
+ double value = node.evaluate(new double[0]);
+ assert value == 5 : "Wrong answer: " + value;
+ }
+
+
+ @Test
+ public void testElseBranch()
+ {
+ Node node = new IfThenElse(new Constant(0),
+ new Constant(5),
+ new Constant(10));
+ // Condition (0) is true, so result should be 10.
+ double value = node.evaluate(new double[0]);
+ assert value == 10 : "Wrong answer: " + value;
+ }
+
+
+ /**
+ * Count nodes when the sub-trees each consist of only a single leaf node.
+ */
+ @Test
+ public void testCountNodesSimple()
+ {
+ Node node = new IfThenElse(new Constant(1), new Constant(2), new Constant(3));
+ int nodeCount = node.countNodes();
+ assert nodeCount == 4 : "Tree has 4 nodes (root + 3 children), not " + nodeCount;
+ }
+
+
+ /**
+ * Count nodes when the sub-trees consist of non-leaf nodes.
+ */
+ @Test
+ public void testCountNodesComplex()
+ {
+ Node node = new IfThenElse(new Addition(new Constant(1), new Constant(2)),
+ new Subtraction(new Constant(3), new Constant(4)),
+ new Multiplication(new Constant(5), new Constant(6)));
+ int nodeCount = node.countNodes();
+ assert nodeCount == 10 : "Tree has 10 nodes (root + 3 children and 6 grand-children), not " + nodeCount;
+ }
+
+
+ @Test
+ public void testGetNode()
+ {
+ Node condition = new Constant(1);
+ Node then = new Constant(2);
+ Constant sub1 = new Constant(3);
+ Constant sub2 = new Constant(4);
+ Node otherwise = new Addition(sub1, sub2);
+ Node node = new IfThenElse(condition, then, otherwise);
+
+ assert node.getNode(0) == node : "Node zero should be root node.";
+ assert node.getNode(1) == condition : "Node one should be condition node.";
+ assert node.getNode(2) == then : "Node one should be then node.";
+ assert node.getNode(3) == otherwise : "Node one should be else node.";
+ assert node.getNode(4) == sub1 : "Node 4 should be first sub-node of else node.";
+ assert node.getNode(5) == sub2 : "Node 5 should be second sub-node of else node.";
+ }
+
+
+ @Test
+ public void testReplaceRootNode()
+ {
+ Node node = new IfThenElse(new Constant(1), new Constant(2), new Constant(3));
+ Constant constant = new Constant(0);
+ Node newNode = node.replaceNode(0, constant);
+ assert newNode == constant : "Root node should be replaced by new node.";
+ }
+
+
+ @Test(dependsOnMethods = "testIfBranch")
+ public void testReplaceConditionNode()
+ {
+ Node node = new IfThenElse(new Constant(1), new Constant(2), new Constant(3));
+ Node newNode = node.replaceNode(1, new Constant(0));
+ assert newNode instanceof IfThenElse : "Replacing condition node should not change type of root node.";
+ assert newNode.evaluate(BinaryNode.NO_ARGS) == 3d : "Changing condition node should change evaluation.";
+ }
+
+
+ @Test(dependsOnMethods = "testIfBranch")
+ public void testReplaceThenNode()
+ {
+ Node node = new IfThenElse(new Constant(1), new Constant(2), new Constant(3));
+ Node newNode = node.replaceNode(2, new Constant(4));
+ assert newNode instanceof IfThenElse : "Replacing then node should not change type of root node.";
+ assert newNode.evaluate(BinaryNode.NO_ARGS) == 4d : "Changing then node should change evaluation.";
+ }
+
+
+ @Test(dependsOnMethods = "testElseBranch")
+ public void testReplaceElseNode()
+ {
+ Node node = new IfThenElse(new Constant(0), new Constant(2), new Constant(3));
+ Node newNode = node.replaceNode(3, new Constant(5));
+ assert newNode instanceof IfThenElse : "Replacing else node should not change type of root node.";
+ assert newNode.evaluate(BinaryNode.NO_ARGS) == 5d : "Changing then node should change evaluation.";
+ }
+
+
+ /**
+ * Test replacing of nodes which are not direct sub-nodes of this node but are further down
+ * the tree.
+ */
+ @Test(dependsOnMethods = "testReplaceElseNode")
+ public void testReplaceSubNodes()
+ {
+ Node node = new IfThenElse(new Constant(0), new Constant(2), new Addition(new Constant(3), new Constant(4)));
+ // Replace both of the constants summed by the addition node on the else branch.
+ Node newNode = node.replaceNode(4, new Constant(5));
+ newNode = newNode.replaceNode(5, new Constant(6));
+ assert newNode instanceof IfThenElse : "Replacing sub-nodes should not change type of root node.";
+ assert newNode.evaluate(BinaryNode.NO_ARGS) == 11d : "Changing sub-nodes should change evaluation.";
+ }
+
+
+ @Test
+ public void testZeroProbabilityMutation()
+ {
+ Node node = new IfThenElse(new Constant(0), new Constant(2), new Addition(new Constant(3), new Constant(4)));
+ double value = node.evaluate(BinaryNode.NO_ARGS);
+ String string = node.print();
+
+ Node mutated = node.mutate(ExamplesTestUtils.getRNG(), Probability.ZERO, null);
+ assert mutated == node : "Node should not have changed.";
+ assert value == mutated.evaluate(BinaryNode.NO_ARGS) : "Node should not have been altered.";
+ assert string.equals(mutated.print()) : "Node should not have been altered.";
+ }
+
+
+ @Test
+ public void testSimplify()
+ {
+ Node node = new IfThenElse(new Constant(0), new Constant(1), new Constant(2));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified.
+ */
+ @Test
+ public void testSimplifySubNode()
+ {
+ Node node = new IfThenElse(new Constant(4),
+ new IfThenElse(new Constant(1), new Constant(2), new Constant(3)),
+ new Constant(5));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 2;
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified and that these simplfications are taken into
+ * account when optimising the parent node (in other words the child nodes should be
+ * simplified first).
+ */
+ @Test
+ public void testSimplifyComplex()
+ {
+ Node node = new IfThenElse(new IfThenElse(new Constant(1), new Constant(2), new Constant(3)),
+ new Constant(4),
+ new Constant(5));
+ Node simplified = node.simplify();
+ // It is not sufficient to simplify this to an IfThenElse with a constant for each node,
+ // the fact that the condition node can be replaced by a Constant should result in the whole
+ // tree being reduced to a single constant (with a value of 4).
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 4;
+ }
+
+
+ /**
+ * Test that simplification doesn't cause any problems when the expression is already as simple
+ * as possible.
+ */
+ @Test
+ public void testSimplifySimplest()
+ {
+ Node node = new IfThenElse(new Parameter(0), new Constant(1), new Constant(2));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * If the two branches (then and else) are identical, it doesn't matter what the condition is,
+ * the tree can be replaced by either one of the branches.
+ */
+ @Test
+ public void testSimplifyIdenticalBranches()
+ {
+ Node node = new IfThenElse(new Parameter(0),
+ new Constant(2),
+ new Constant(2));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{1}; // Need one argument for the parameter node to use.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(args) == 2;
+
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IsGreaterTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IsGreaterTest.java
new file mode 100644
index 0000000..91baeec
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/IsGreaterTest.java
@@ -0,0 +1,110 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the {@link IsGreater} node type.
+ * @author Daniel Dyer
+ */
+public class IsGreaterTest
+{
+ /**
+ * If two nodes are equal then neither is greater than the other.
+ */
+ @Test
+ public void testBothNodesEqual()
+ {
+ Node node = new IsGreater(new Constant(1), new Constant(1));
+ assert node.evaluate(new double[0]) == 0 : "First node should not be greater than second.";
+ }
+
+
+ @Test
+ public void testFirstNodeGreater()
+ {
+ Node node = new IsGreater(new Constant(2), new Constant(1));
+ assert node.evaluate(new double[0]) > 0 : "First node should be greater than second.";
+ }
+
+
+ @Test
+ public void testSecondNodeGreater()
+ {
+ Node node = new IsGreater(new Constant(-1), new Constant(1));
+ assert node.evaluate(new double[0]) == 0 : "First node should be less than second.";
+ }
+
+
+ /**
+ * If the arguments to the IsGreater function are both constants then the node
+ * should be replaced by a constant node containing the evaluation of this expression.
+ */
+ @Test
+ public void testSimplifyConstants()
+ {
+ Node node = new IsGreater(new Constant(7), new Constant(5));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 1;
+
+ }
+
+
+ @Test
+ public void testSimplifyIdenticalArguments()
+ {
+ Node node = new IsGreater(new Parameter(0), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ }
+
+
+ /**
+ * Test that simplification doesn't cause any problems when the expression is already as simple
+ * as possible.
+ */
+ @Test
+ public void testSimplifySimplest()
+ {
+ Node node = new IsGreater(new Parameter(0), new Constant(1));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified.
+ */
+ @Test
+ public void testSimplifySubNode()
+ {
+ Node node = new IsGreater(new Parameter(0),
+ new IsGreater(new Constant(3), new Constant(2)));
+ Node simplified = node.simplify();
+ assert simplified instanceof IsGreater
+ : "Simplified node should be IsGreater, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.countNodes() < node.countNodes() : "Should be fewer nodes after simplification.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/MultiplicationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/MultiplicationTest.java
new file mode 100644
index 0000000..f81c367
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/MultiplicationTest.java
@@ -0,0 +1,157 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the {@link Multiplication} node type.
+ * @author Daniel Dyer
+ */
+public class MultiplicationTest
+{
+ @Test
+ public void testEvaluation()
+ {
+ Node node = new Multiplication(new Constant(5), new Constant(2));
+ double value = node.evaluate(new double[0]);
+ assert value == 10 : "Wrong result: " + value;
+ }
+
+
+ @Test
+ public void testStringRepresentation()
+ {
+ Node node = new Multiplication(new Constant(5), new Parameter(0));
+ assert node.print().equals("(5.0 * arg0)") : "Wrong string representation: " + node.print();
+ }
+
+
+ /**
+ * If the arguments to the multiply function are both constants then the multiplication node
+ * should be replaced by a constant node containing the evaluation of this product.
+ */
+ @Test
+ public void testSimplifyConstants()
+ {
+ Node node = new Multiplication(new Constant(3), new Constant(4));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 12;
+
+ }
+
+
+ /**
+ * If the lefthand argument is zero, the result will always be zero regardless of the righthand
+ * argument.
+ */
+ @Test
+ public void testSimplifyMultiplyZero()
+ {
+ Node node = new Multiplication(new Constant(0), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 0;
+
+ }
+
+
+ /**
+ * If the righthand argument is zero, the result will always be zero regardless of the lefthand
+ * argument.
+ */
+ @Test
+ public void testSimplifyMultiplyByZero()
+ {
+ Node node = new Multiplication(new Parameter(0), new Constant(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 0;
+ }
+
+
+ /**
+ * If the lefthand argument is one, the result can be reduced to the righthand argument.
+ */
+ @Test
+ public void testSimplifyMultiplyOne()
+ {
+ Node node = new Multiplication(new Constant(1), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Parameter
+ : "Simplified node should be Parameter, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(args) == 5;
+
+ }
+
+
+ /**
+ * If the righthand argument is one, the result can be reduced to the lefthand argument.
+ */
+ @Test
+ public void testSimplifyMultiplyByOne()
+ {
+ Node node = new Multiplication(new Parameter(0), new Constant(1));
+ Node simplified = node.simplify();
+ assert simplified instanceof Parameter
+ : "Simplified node should be Parameter, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(args) == 5;
+ }
+
+
+ /**
+ * Test that simplification doesn't cause any problems when the expression is already as simple
+ * as possible.
+ */
+ @Test
+ public void testSimplifySimplest()
+ {
+ Node node = new Multiplication(new Parameter(0), new Constant(2));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified.
+ */
+ @Test
+ public void testSimplifySubNode()
+ {
+ Node node = new Multiplication(new Parameter(0),
+ new Multiplication(new Constant(3), new Constant(2)));
+ Node simplified = node.simplify();
+ assert simplified instanceof Multiplication
+ : "Simplified node should be Multiplication, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.countNodes() < node.countNodes() : "Should be fewer nodes after simplification.";
+ }
+
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ParameterTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ParameterTest.java
new file mode 100644
index 0000000..7324242
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/ParameterTest.java
@@ -0,0 +1,60 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the {@link Parameter} node type.
+ * @author Daniel Dyer
+ */
+public class ParameterTest
+{
+ @Test
+ public void testParameterSelection()
+ {
+ Parameter parameter = new Parameter(2);
+ double value = parameter.evaluate(new double[]{0, 1, 2, 3});
+ assert value == 2 : "Incorect argument selected: " + value;
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidParameterSelection()
+ {
+ Parameter parameter = new Parameter(2);
+ // There is only one argument, so trying to select the 3rd one should
+ // result in an IllegalArgumentException.
+ parameter.evaluate(new double[]{0});
+ }
+
+
+ @Test
+ public void testEquality()
+ {
+ Parameter zero = new Parameter(0);
+ Parameter one = new Parameter(1);
+ Parameter anotherOne = new Parameter(1);
+
+ assert zero.equals(zero) : "Equality must be reflexive.";
+ assert one.equals(anotherOne) : "Same-index parameters must be equal.";
+ assert anotherOne.equals(one) : "Equality must be symmetric.";
+ assert one.hashCode() == anotherOne.hashCode() : "Equal objects must have equal hash codes.";
+ assert !zero.equals(one) : "Different index parameters must be non-equal.";
+ assert !zero.equals(null) : "No non-null object should not be considered equal to null.";
+ assert !zero.equals(Integer.valueOf(0)) : "Objects of different types should not be equal.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SimplificationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SimplificationTest.java
new file mode 100644
index 0000000..f439021
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SimplificationTest.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+
+/**
+ * Unit test for the {@link Simplification} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class SimplificationTest
+{
+ /**
+ * When the probability is 1, all candidates should be processed.
+ */
+ @Test
+ public void testProbabilityOne()
+ {
+ Node node1 = new Addition(new Constant(1), new Constant(1));
+ Node node2 = new Subtraction(new Constant(5), new Constant(4));
+ Node node3 = new Multiplication(new Constant(3), new Constant(3));
+ List<Node> population = Arrays.asList(node1, node2, node3);
+
+ Simplification simplification = new Simplification();
+ List<Node> evolved = simplification.apply(population, ExamplesTestUtils.getRNG());
+ assert evolved.size() == population.size() : "Output should be same size as input.";
+ for (Node node : evolved)
+ {
+ assert node instanceof Constant : "Node was not simplified.";
+ }
+ }
+
+
+ /**
+ * When the probability is 0, no candidates should be processed.
+ */
+ @Test
+ public void testProbabilityZero()
+ {
+ Node node1 = new Addition(new Constant(1), new Constant(1));
+ Node node2 = new Subtraction(new Constant(5), new Constant(4));
+ Node node3 = new Multiplication(new Constant(3), new Constant(3));
+ List<Node> population = Arrays.asList(node1, node2, node3);
+
+ Simplification simplification = new Simplification(Probability.ZERO);
+ List<Node> evolved = simplification.apply(population, ExamplesTestUtils.getRNG());
+ assert evolved.size() == population.size() : "Output should be same size as input.";
+ for (Node node : evolved)
+ {
+ assert !(node instanceof Constant) : "Node should not have been simplified.";
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SubtractionTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SubtractionTest.java
new file mode 100644
index 0000000..1682b25
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SubtractionTest.java
@@ -0,0 +1,133 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the {@link Subtraction} node type.
+ * @author Daniel Dyer
+ */
+public class SubtractionTest
+{
+ @Test
+ public void testEvaluation()
+ {
+ Node node = new Subtraction(new Constant(7), new Constant(3));
+ double value = node.evaluate(new double[0]);
+ assert value == 4 : "Wrong result: " + value;
+ }
+
+
+ @Test
+ public void testStringRepresentation()
+ {
+ Node node = new Subtraction(new Constant(7), new Constant(3));
+ assert node.print().equals("(7.0 - 3.0)") : "Wrong string representation: " + node.print();
+ }
+
+
+ /**
+ * If the arguments to the subtract function are both constants then the subtract node
+ * should be replaced by a constant node containing the evaluation of this sum.
+ */
+ @Test
+ public void testSimplifyConstants()
+ {
+ Node node = new Subtraction(new Constant(7), new Constant(5));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == node.evaluate(BinaryNode.NO_ARGS) : "Simplified answer differs.";
+ assert simplified.evaluate(BinaryNode.NO_ARGS) == 2;
+ }
+
+
+ /**
+ * If the arguments to the subtract function are identical, even if they are not constant,
+ * then the answer will always be zero, so this node should be replaced by the constant zero.
+ */
+ @Test
+ public void testSimplifyIdenticalArguments()
+ {
+ Node node = new Subtraction(new Parameter(0), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Constant
+ : "Simplified node should be Constant, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(args) == 0;
+ }
+
+
+ /**
+ * Test that simplification doesn't cause any problems when the expression is already as simple
+ * as possible.
+ */
+ @Test
+ public void testSimplifySimplest()
+ {
+ Node node = new Subtraction(new Parameter(0), new Constant(1));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * If the second argument is zero, the experession can be replaced by its lefthand side.
+ */
+ @Test
+ public void testSimplifySubtractZero()
+ {
+ Node node = new Subtraction(new Parameter(0), new Constant(0));
+ Node simplified = node.simplify();
+ assert simplified instanceof Parameter
+ : "Simplified node should be Parameter, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.evaluate(args) == 5;
+ }
+
+
+ /**
+ * But if the first argument is zero, the experession should not be simplified as it has
+ * the effect of negating the second argument.
+ */
+ @Test
+ public void testSimplifySubtractFromZero()
+ {
+ Node node = new Subtraction(new Constant(0), new Parameter(0));
+ Node simplified = node.simplify();
+ assert simplified == node : "Expression should not have been changed.";
+ }
+
+
+ /**
+ * Make sure that sub-nodes are simplified.
+ */
+ @Test
+ public void testSimplifySubNode()
+ {
+ Node node = new Subtraction(new Parameter(0),
+ new Subtraction(new Constant(3), new Constant(2)));
+ Node simplified = node.simplify();
+ assert simplified instanceof Subtraction
+ : "Simplified node should be Subtraction, is " + simplified.getClass().getSimpleName();
+ double[] args = new double[]{5}; // Provides a value for the parameter nodes.
+ assert simplified.evaluate(args) == node.evaluate(args) : "Simplified answer differs.";
+ assert simplified.countNodes() < node.countNodes() : "Should be fewer nodes after simplification.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRendererTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRendererTest.java
new file mode 100644
index 0000000..c0ac452
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/SwingGPTreeRendererTest.java
@@ -0,0 +1,51 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.awt.Dimension;
+import javax.swing.JComponent;
+import org.testng.annotations.Test;
+
+/**
+ * Basic unit test for the {@link SwingGPTreeRenderer} class.
+ * @author Daniel Dyer
+ */
+public class SwingGPTreeRendererTest
+{
+ // There's not much we can effectively test for this class, but we can at least
+ // make sure the generated component has the correct dimensions.
+ @Test
+ public void testSizes()
+ {
+ SwingGPTreeRenderer renderer = new SwingGPTreeRenderer();
+
+ // Simple case.
+ Node tree = new Constant(2);
+ JComponent component = renderer.render(tree);
+ Dimension minSize = component.getMinimumSize();
+ assert minSize.width == 30 : "Wrong width: " + minSize.width;
+ assert minSize.height == 50 : "Wrong height: " + minSize.height;
+
+ // More complicated case.
+ tree = new IfThenElse(new Parameter(0),
+ new Addition(new Constant(2), new Parameter(1)),
+ new Constant(5));
+ component = renderer.render(tree);
+ minSize = component.getMinimumSize();
+ assert minSize.width == 120 : "Wrong width: " + minSize.width;
+ assert minSize.height == 150 : "Wrong height: " + minSize.height;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossoverTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossoverTest.java
new file mode 100644
index 0000000..5fdf777
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeCrossoverTest.java
@@ -0,0 +1,47 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+
+/**
+ * Unit test for the {@link TreeCrossover} evolutionary operator used by
+ * the genetic programming example application.
+ * @author Daniel Dyer
+ */
+public class TreeCrossoverTest
+{
+ /**
+ * This is just a simple sanity check. Cross-over should result in the same
+ * number of individuals, all constructed from the same set of nodes that existed
+ * in the parent generation (but perhaps connected differently).
+ */
+ @Test
+ public void testCrossover()
+ {
+ TreeCrossover crossover = new TreeCrossover();
+ Node tree1 = new Multiplication(new Constant(1), new Constant(2));
+ Node tree2 = new Subtraction(new Parameter(0), new Parameter(1));
+
+ List<Node> offspring = crossover.apply(Arrays.asList(tree1, tree2), ExamplesTestUtils.getRNG());
+ assert offspring.size() == 2 : "Should be 2 offspring after cross-over.";
+ int totalNodeCount = offspring.get(0).countNodes() + offspring.get(1).countNodes();
+ assert totalNodeCount == 6 : "Should be exactly 6 nodes in total, is " + totalNodeCount;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluatorTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluatorTest.java
new file mode 100644
index 0000000..7aec257
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeEvaluatorTest.java
@@ -0,0 +1,78 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * Unit test for the {@link FitnessEvaluator} used
+ * in the genetic programming example applicaiton.
+ * @author Daniel Dyer
+ */
+public class TreeEvaluatorTest
+{
+ /**
+ * A function that perfectly generates the correct output for all inputs
+ * should have a fitness of zero.
+ */
+ @Test
+ public void testPerfectFunction()
+ {
+ Map<double[], Double> data = new HashMap<double[], Double>();
+ // Data for multiplication program.
+ data.put(new double[]{5d, 3d}, 15d);
+ data.put(new double[]{3d, 8d}, 24d);
+ data.put(new double[]{7d, 2d}, 14d);
+
+ FitnessEvaluator<Node> evaluator = new TreeEvaluator(data);
+
+ // Program that multiplies its two inputs together.
+ Node program = new Multiplication(new Parameter(0), new Parameter(1));
+
+ double fitness = evaluator.getFitness(program, Arrays.asList(program));
+ assert fitness == 0 : "Correct program should have zero fitness.";
+ }
+
+
+ /**
+ * A function that doesn't generate the correct output for all inputs
+ * should have a non-zero fitness.
+ */
+ @Test
+ public void testIncorrectFunction()
+ {
+ Map<double[], Double> data = new HashMap<double[], Double>();
+ // Data for multiplication program.
+ data.put(new double[]{5d, 3d}, 15d);
+ data.put(new double[]{3d, 8d}, 24d);
+ data.put(new double[]{7d, 2d}, 14d);
+
+ FitnessEvaluator<Node> evaluator = new TreeEvaluator(data);
+
+ // Program that multiplies its first input by 3 (will give the correct answer
+ // for the first set of inputs but the wrong answer for the other two).
+ Node program = new Multiplication(new Parameter(0), new Constant(3d));
+
+ double fitness = evaluator.getFitness(program, Arrays.asList(program));
+ // Error on second example is 15, error on third is 7.
+ // 15^2 + 7^2 = 225 + 49 = 274
+ assert fitness == 274d : "Wrong fitness for incorrect program.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactoryTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactoryTest.java
new file mode 100644
index 0000000..d4ed9e6
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeFactoryTest.java
@@ -0,0 +1,66 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Unit test for the {@link TreeFactory} used by the gentic programming
+ * example.
+ * @author Daniel Dyer
+ */
+public class TreeFactoryTest
+{
+ @Test
+ public void testMaxDepth()
+ {
+ final int maxDepth = 3;
+ CandidateFactory<Node> factory = new TreeFactory(2,
+ maxDepth,
+ new Probability(0.6),
+ Probability.EVENS);
+ List<Node> trees = factory.generateInitialPopulation(20, ExamplesTestUtils.getRNG());
+ for (Node tree : trees)
+ {
+ // Make sure that each tree is no bigger than the maximum permitted.
+ assert tree.getDepth() <= maxDepth : "Generated tree is too deep: " + tree.getDepth();
+ }
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidParameterCount()
+ {
+ new TreeFactory(-1,
+ 1,
+ Probability.EVENS,
+ Probability.EVENS); // Should throw an exception, parameter count can't be negative.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMaxDepth()
+ {
+ new TreeFactory(1,
+ 0,
+ Probability.EVENS,
+ Probability.EVENS); // Should throw an exception, depth must be at least one.
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutationTest.java
new file mode 100644
index 0000000..3bf6b0f
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/geneticprogramming/TreeMutationTest.java
@@ -0,0 +1,76 @@
+//=============================================================================
+// 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.geneticprogramming;
+
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link TreeMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class TreeMutationTest
+{
+ /**
+ * If the mutation doesn't happen, the candidates should be returned unaltered.
+ */
+ @Test
+ public void testNoMutations()
+ {
+ TreeFactory treeFactory = new TreeFactory(0, 3, Probability.EVENS, Probability.EVENS);
+ EvolutionaryOperator<Node> mutation = new TreeMutation(treeFactory,
+ Probability.ZERO); // Zero probability means no mutations.
+ List<Node> candidates = Arrays.<Node>asList(new Addition(new Constant(3), new Constant(4)));
+ List<Node> result = mutation.apply(candidates, ExamplesTestUtils.getRNG());
+ assert result.size() == 1 : "Wrong number of trees returned: " + result.size();
+ assert candidates.get(0) == result.get(0) : "Tree should have been returned unmodified.";
+ }
+
+
+ /**
+ * If the mutation doesn't happen, the candidates should be returned unaltered.
+ */
+ @Test
+ public void testSomeMutations()
+ {
+ TreeFactory treeFactory = new TreeFactory(1, 4, new Probability(0.6d), new Probability(0.2d));
+ EvolutionaryOperator<Node> mutation = new TreeMutation(treeFactory,
+ Probability.ONE); // Probability of 1 means guaranteed mutations.
+ List<Node> candidates = treeFactory.generateInitialPopulation(20, ExamplesTestUtils.getRNG());
+
+ Map<Node, Node> distinctTrees = new IdentityHashMap<Node, Node>();
+ for (Node node : candidates)
+ {
+ distinctTrees.put(node, node);
+ }
+
+ List<Node> result = mutation.apply(candidates, ExamplesTestUtils.getRNG());
+ assert result.size() == 20 : "Wrong number of trees returned: " + result.size();
+ for (Node node : result)
+ {
+ distinctTrees.put(node, node);
+ }
+
+ // If none of the original trees are returned, we should have 40 distict trees.
+ assert distinctTrees.size() == 40 : "New trees should have been returned.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutationTest.java
new file mode 100644
index 0000000..53c7aea
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddPolygonMutationTest.java
@@ -0,0 +1,104 @@
+//=============================================================================
+// 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.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link AddPolygonMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class AddPolygonMutationTest
+{
+ private final PolygonImageFactory factory = new PolygonImageFactory(new Dimension(200, 200));
+
+ @Test
+ public void testAddPolygon()
+ {
+ List<ColouredPolygon> image = factory.generateRandomCandidate(ExamplesTestUtils.getRNG());
+ assert image.size() == 2 : "Image should have 2 polygons";
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new AddPolygonMutation(Probability.ONE,
+ factory,
+ 5);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() + 1 : "Image should have 1 extra polygon after mutation.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ List<ColouredPolygon> image = factory.generateRandomCandidate(ExamplesTestUtils.getRNG());
+ assert image.size() == 2 : "Image should have 2 polygons";
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new AddPolygonMutation(Probability.ZERO,
+ factory,
+ 5);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have same number of polygons.";
+ assert evolved.get(0) == image : "Image should not have been changed at all.";
+ }
+
+
+ /**
+ * If the image already has the maximum permitted number of polygons, extra polygons
+ * should not be added by mutation.
+ */
+ @Test
+ public void testAddMaxPolygons()
+ {
+ List<ColouredPolygon> image = Arrays.asList(factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()));
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new AddPolygonMutation(Probability.ONE,
+ factory,
+ 3);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have no more than the maximum number of polygons.";
+ assert evolved.get(0) == image : "Image should not have been changed at all.";
+ }
+
+
+ /**
+ * An image must have at least 2 polygons. The configured maximum must respect this.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMaximum()
+ {
+ new AddPolygonMutation(Probability.ONE, factory, 1); // Invalid, should throw IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddVertexMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddVertexMutationTest.java
new file mode 100644
index 0000000..50579a8
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AddVertexMutationTest.java
@@ -0,0 +1,100 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link AddVertexMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class AddVertexMutationTest
+{
+ private final Dimension canvasSize = new Dimension(200, 200);
+ private final PolygonImageFactory factory = new PolygonImageFactory(canvasSize);
+
+ @Test
+ public void testAddVertex()
+ {
+ ColouredPolygon polygon = factory.createRandomPolygon(ExamplesTestUtils.getRNG());
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new AddVertexMutation(canvasSize,
+ Probability.ONE);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ List<Point> vertices = evolved.get(0).getVertices();
+ assert vertices.size() == polygon.getVertices().size() + 1
+ : "Polygon should have 1 extra point after mutation.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ ColouredPolygon polygon = factory.createRandomPolygon(ExamplesTestUtils.getRNG());
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new AddVertexMutation(canvasSize,
+ Probability.ZERO);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have no extra points after mutation.";
+ assert evolvedPolygon == polygon : "Polygon should not have been changed at all.";
+ }
+
+
+ /**
+ * If the image already has the maximum permitted number of points, extra points
+ * should not be added by mutation.
+ */
+ @Test
+ public void testAddMaxPoints()
+ {
+ List<Point> points = new ArrayList<Point>(AddVertexMutation.MAX_VERTEX_COUNT);
+ for (int i = 0; i < AddVertexMutation.MAX_VERTEX_COUNT; i++)
+ {
+ points.add(new Point(i, i));
+ }
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new AddVertexMutation(canvasSize,
+ Probability.ONE);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have no extra points after mutation.";
+ assert evolvedPolygon == polygon : "Polygon should not have been changed at all.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java
new file mode 100644
index 0000000..a4b8142
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/AdjustVertexMutationTest.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link AdjustVertexMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class AdjustVertexMutationTest
+{
+ private final Dimension canvasSize = new Dimension(200, 200);
+
+ @Test
+ public void testAdjustVertex()
+ {
+ final Point point1 = new Point(1, 1);
+ final Point point2 = new Point(2, 2);
+ final Point point3 = new Point(3, 3);
+ List<Point> points = Arrays.asList(point1, point2, point3);
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ final int amount = 5;
+ EvolutionaryOperator<ColouredPolygon> mutation = new AdjustVertexMutation(canvasSize,
+ Probability.ONE,
+ new ConstantGenerator<Integer>(amount));
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have same number of points after mutation.";
+
+ if (vertices.get(0) != point1)
+ {
+ Point newPoint = vertices.get(0);
+ assert newPoint.x == point1.x + 5 : "X-coordinate not mutated properly.";
+ assert newPoint.y == point1.y + 5 : "Y-coordinate not mutated properly.";
+ // If the first point is different the other two should be the same.
+ assert vertices.get(1) == point2 : "Second point should be unchanged.";
+ assert vertices.get(2) == point3 : "Third point should be unchanged.";
+ }
+ else if (vertices.get(1) != point2)
+ {
+ Point newPoint = vertices.get(1);
+ assert newPoint.x == point2.x + 5 : "X-coordinate not mutated properly.";
+ assert newPoint.y == point2.y + 5 : "Y-coordinate not mutated properly.";
+ // If the second point is different the other two should be the same.
+ assert vertices.get(0) == point1 : "First point should be unchanged.";
+ assert vertices.get(2) == point3 : "Third point should be unchanged.";
+ }
+ else if (vertices.get(2) != point3)
+ {
+ Point newPoint = vertices.get(2);
+ assert newPoint.x == point3.x + 5 : "X-coordinate not mutated properly.";
+ assert newPoint.y == point3.y + 5 : "Y-coordinate not mutated properly.";
+ // If the third point is different the other two should be the same.
+ assert vertices.get(0) == point1 : "Third point should be unchanged.";
+ assert vertices.get(1) == point2 : "Second point should be unchanged.";
+ }
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ Point point1 = new Point(1, 1);
+ Point point2 = new Point(2, 2);
+ Point point3 = new Point(3, 3);
+ List<Point> points = Arrays.asList(point1, point2, point3);
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new AdjustVertexMutation(canvasSize,
+ Probability.ZERO,
+ new ConstantGenerator<Integer>(1));
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have same number of points after mutation.";
+ assert evolvedPolygon == polygon : "Polygon should not have been changed at all.";
+ assert vertices.get(0) == point1 : "First point should be unchanged.";
+ assert vertices.get(1) == point2 : "Second point should be unchanged.";
+ assert vertices.get(2) == point3 : "Third point should be unchanged.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutationTest.java
new file mode 100644
index 0000000..cf679e8
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/MovePolygonMutationTest.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// 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.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link MovePolygonMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class MovePolygonMutationTest
+{
+ private final PolygonImageFactory factory = new PolygonImageFactory(new Dimension(200, 200));
+
+ @Test
+ public void testMovePolygon()
+ {
+ List<ColouredPolygon> image = Arrays.asList(factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()));
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new MovePolygonMutation(Probability.ONE);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have same number of polygons after mutation.";
+ // Can't reliably test that the order was mutated because the random selection may have moved
+ // the polygon back to its original index.
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ ColouredPolygon polygon1 = factory.createRandomPolygon(ExamplesTestUtils.getRNG());
+ ColouredPolygon polygon2 = factory.createRandomPolygon(ExamplesTestUtils.getRNG());
+ ColouredPolygon polygon3 = factory.createRandomPolygon(ExamplesTestUtils.getRNG());
+ List<ColouredPolygon> image = Arrays.asList(polygon1, polygon2, polygon3);
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new MovePolygonMutation(Probability.ZERO);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have same number of polygons.";
+ assert evolved.get(0) == image : "Image should not have been changed at all.";
+ assert evolved.get(0).get(0) == polygon1 : "First polygon should not have moved.";
+ assert evolved.get(0).get(1) == polygon2 : "Second polygon should not have moved.";
+ assert evolved.get(0).get(2) == polygon3 : "Third polygon should not have moved.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java
new file mode 100644
index 0000000..bbdb1f9
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonColourMutationTest.java
@@ -0,0 +1,69 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Point;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+
+/**
+ * Unit test for the {@link PolygonColourMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class PolygonColourMutationTest
+{
+ @Test
+ public void testColourMutation()
+ {
+ PolygonColourMutation mutation = new PolygonColourMutation(Probability.ONE, // Guaranteed mutation.
+ new ConstantGenerator<Double>(1d));
+ // A grey triangle.
+ final ColouredPolygon polygon = new ColouredPolygon(new Color(128, 128, 128, 128),
+ Arrays.asList(new Point(0, 0),
+ new Point(50, 50),
+ new Point(0, 75)));
+ List<ColouredPolygon> image = Arrays.asList(polygon);
+ List<ColouredPolygon> 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();
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ PolygonColourMutation mutation = new PolygonColourMutation(Probability.ZERO,
+ new ConstantGenerator<Double>(1d));
+ // A grey triangle.
+ Color originalColour = new Color(128, 128, 128, 128);
+ final ColouredPolygon polygon = new ColouredPolygon(originalColour,
+ Arrays.asList(new Point(0, 0),
+ new Point(50, 50),
+ new Point(0, 75)));
+ List<ColouredPolygon> image = Arrays.asList(polygon);
+ List<ColouredPolygon> mutatedImage = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert mutatedImage.get(0) == polygon : "Polygon should not have changed at all.";
+ assert mutatedImage.get(0).getColour() == originalColour : "Colour should not have changed at all.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java
new file mode 100644
index 0000000..fbd94e8
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageEvaluatorTest.java
@@ -0,0 +1,101 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.MersenneTwisterRNG;
+
+/**
+ * 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.
+ */
+ @Test(groups = "display-required")
+ public void testPerfectMatch()
+ {
+ Dimension canvasSize = new Dimension(100, 100);
+ PolygonImageFactory factory = new PolygonImageFactory(canvasSize);
+ List<ColouredPolygon> image = factory.generateRandomCandidate(new MersenneTwisterRNG());
+
+ BufferedImage targetImage = new PolygonImageRenderer(canvasSize, false, null).render(image);
+ PolygonImageEvaluator evaluator = new PolygonImageEvaluator(targetImage);
+
+ double fitness = evaluator.getFitness(image, null);
+ assert fitness == 0 : "Fitness should be zero when image is an exact match.";
+ }
+
+
+ /**
+ * An image that is different to the target image should have a non-zero fitness.
+ */
+ @Test(groups = "display-required")
+ public void testDifferentImages()
+ {
+ Dimension canvasSize = new Dimension(100, 100);
+ List<ColouredPolygon> 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))));
+ List<ColouredPolygon> 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);
+
+ double fitness = evaluator.getFitness(candidateImage, null);
+ 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.
+ */
+ @Test(groups = "display-required")
+ public void testImageConversion()
+ {
+ Dimension canvasSize = new Dimension(100, 100);
+ PolygonImageFactory factory = new PolygonImageFactory(canvasSize);
+ List<ColouredPolygon> image = factory.generateRandomCandidate(new MersenneTwisterRNG());
+
+ BufferedImage targetImage = new PolygonImageRenderer(canvasSize, false, 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);
+
+ double fitness = evaluator.getFitness(image, null);
+ assert fitness == 0 : "Fitness should be zero when image is an exact match.";
+ }
+
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactoryTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactoryTest.java
new file mode 100644
index 0000000..15b2db4
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/PolygonImageFactoryTest.java
@@ -0,0 +1,59 @@
+//=============================================================================
+// 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.Dimension;
+import java.awt.Point;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Unit test for {@link PolygonImageFactory}.
+ * @author Daniel Dyer
+ */
+public class PolygonImageFactoryTest
+{
+ /**
+ * Make sure that the generated images have the correct number of polygons,
+ * that each polygon has the correct number of points and that all of the
+ * points fall within the bounds of the specified canvas.
+ */
+ @Test
+ public void testConstraints()
+ {
+ final int width = 100;
+ final int height = 50;
+ CandidateFactory<List<ColouredPolygon>> factory = new PolygonImageFactory(new Dimension(width, height));
+ List<List<ColouredPolygon>> candidates = factory.generateInitialPopulation(20, ExamplesTestUtils.getRNG());
+ for (List<ColouredPolygon> image : candidates)
+ {
+ assert image.size() == PolygonImageFactory.MINIMUM_POLYGON_COUNT
+ : "Wrong number of polygons: " + image.size();
+ for (ColouredPolygon polygon : image)
+ {
+ assert polygon.getVertices().size() == PolygonImageFactory.MINIMUM_VERTEX_COUNT
+ : "Wrong number of vertices: " + polygon.getVertices().size();
+ for (Point point : polygon.getVertices())
+ {
+ assert point.x >= 0 && point.x < width : "X out-of-range: " + point.x;
+ assert point.y >= 0 && point.y < height : "Y out-of-range: " + point.y;
+ }
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java
new file mode 100644
index 0000000..3e44663
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/ProbabilitiesPanelTest.java
@@ -0,0 +1,132 @@
+//=============================================================================
+// 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.BorderLayout;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.JFrame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+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()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod(groups = "display-required")
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+ @Test(groups = "display-required")
+ public void testSetAllProbabilitiesToZero()
+ {
+ ProbabilitiesPanel panel = new ProbabilitiesPanel();
+ JFrame frame = new JFrame();
+ frame.add(panel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(700, 300);
+ frame.validate();
+
+ frameFixture.show();
+ frameFixture.panel("AddPolygon").slider().slideToMinimum();
+ frameFixture.panel("RemovePolygon").slider().slideToMinimum();
+ frameFixture.panel("MovePolygon").slider().slideToMinimum();
+ frameFixture.panel("Cross-over").slider().slideToMinimum();
+ frameFixture.panel("AddVertex").slider().slideToMinimum();
+ frameFixture.panel("RemoveVertex").slider().slideToMinimum();
+ frameFixture.panel("MoveVertex").slider().slideToMinimum();
+ frameFixture.panel("ChangeColour").slider().slideToMinimum();
+
+ Dimension canvasSize = new Dimension(100, 100);
+ PolygonImageFactory factory = new PolygonImageFactory(canvasSize);
+ EvolutionaryOperator<List<ColouredPolygon>> operator = panel.createEvolutionPipeline(factory,
+ canvasSize,
+ ExamplesTestUtils.getRNG());
+ List<ColouredPolygon> candidate1 = Arrays.asList(new ColouredPolygon(Color.WHITE,
+ Arrays.asList(new Point(1, 1))));
+ List<ColouredPolygon> candidate2 = Arrays.asList(new ColouredPolygon(Color.BLACK,
+ Arrays.asList(new Point(2, 2))));
+ List<List<ColouredPolygon>> population = new ArrayList<List<ColouredPolygon>>(2);
+ population.add(candidate1);
+ population.add(candidate2);
+
+ List<List<ColouredPolygon>> evolved = operator.apply(population,
+ 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.";
+ }
+
+
+ private boolean checkEquals(List<ColouredPolygon> candidate1,
+ List<ColouredPolygon> candidate2)
+ {
+ if (candidate1.size() != candidate2.size())
+ {
+ return false;
+ }
+ for (int i = 0; i < candidate1.size(); i++)
+ {
+ ColouredPolygon polygon1 = candidate1.get(i);
+ ColouredPolygon polygon2 = candidate2.get(i);
+ if (!polygon1.getColour().equals(polygon2.getColour()))
+ {
+ return false;
+ }
+ List<Point> vertices1 = polygon1.getVertices();
+ List<Point> vertices2 = polygon2.getVertices();
+ if (vertices1.size() != vertices2.size())
+ {
+ return false;
+ }
+ for (int j = 0; j < vertices1.size(); j++)
+ {
+ Point point1 = vertices1.get(j);
+ Point point2 = vertices2.get(j);
+ if (point1.x != point2.x || point1.y != point2.y)
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutationTest.java
new file mode 100644
index 0000000..bcd5f6c
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemovePolygonMutationTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// 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.Dimension;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link RemovePolygonMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class RemovePolygonMutationTest
+{
+ private final PolygonImageFactory factory = new PolygonImageFactory(new Dimension(200, 200));
+
+ @Test
+ public void testRemovePolygon()
+ {
+ List<ColouredPolygon> image = Arrays.asList(factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()));
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new RemovePolygonMutation(Probability.ONE);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() - 1 : "Image should have 1 fewer polygon after mutation.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ List<ColouredPolygon> image = Arrays.asList(factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()),
+ factory.createRandomPolygon(ExamplesTestUtils.getRNG()));
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new RemovePolygonMutation(Probability.ZERO);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have same number of polygons.";
+ assert evolved.get(0) == image : "Image should not have been changed at all.";
+ }
+
+
+ /**
+ * If the image already has the minimum permitted number of polygons, further polygons
+ * should not be removed by mutation.
+ */
+ @Test
+ public void testRemoveMinPolygons()
+ {
+ List<ColouredPolygon> image = factory.generateRandomCandidate(ExamplesTestUtils.getRNG());
+ assert image.size() == 2 : "Image should have 2 polygons";
+ List<List<ColouredPolygon>> list = new ArrayList<List<ColouredPolygon>>(1);
+ list.add(image);
+
+ EvolutionaryOperator<List<ColouredPolygon>> mutation = new RemovePolygonMutation(Probability.ONE);
+
+ List<List<ColouredPolygon>> evolved = mutation.apply(list, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Population size should not be altered by mutation.";
+ assert evolved.get(0).size() == image.size() : "Image should have no fewer than the minimum number of polygons.";
+ assert evolved.get(0) == image : "Image should not have been changed at all.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java
new file mode 100644
index 0000000..e204d55
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/monalisa/RemoveVertexMutationTest.java
@@ -0,0 +1,98 @@
+//=============================================================================
+// 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.Color;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for the {@link RemoveVertexMutation} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class RemoveVertexMutationTest
+{
+ private final Dimension canvasSize = new Dimension(200, 200);
+
+ @Test
+ public void testRemoveVertex()
+ {
+ List<Point> points = Arrays.asList(new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(4, 4));
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new RemoveVertexMutation(canvasSize,
+ Probability.ONE);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ List<Point> vertices = evolved.get(0).getVertices();
+ assert vertices.size() == polygon.getVertices().size() - 1
+ : "Polygon should have 1 fewer point after mutation.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ List<Point> points = Arrays.asList(new Point(1, 1), new Point(2, 2), new Point(3, 3), new Point(4, 4));
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new RemoveVertexMutation(canvasSize,
+ Probability.ZERO);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have no fewer points after mutation.";
+ assert evolvedPolygon == polygon : "Polygon should not have been changed at all.";
+ }
+
+
+ /**
+ * If the image already has the minimum permitted number of points, further points
+ * should not be removed by mutation.
+ */
+ @Test
+ public void testAddMaxPoints()
+ {
+ List<Point> points = Arrays.asList(new Point(1, 1), new Point(2, 2), new Point(3, 3));
+ ColouredPolygon polygon = new ColouredPolygon(Color.RED, points);
+ List<ColouredPolygon> image = new ArrayList<ColouredPolygon>(1);
+ image.add(polygon);
+
+ EvolutionaryOperator<ColouredPolygon> mutation = new RemoveVertexMutation(canvasSize,
+ Probability.ONE);
+
+ List<ColouredPolygon> evolved = mutation.apply(image, ExamplesTestUtils.getRNG());
+ assert evolved.size() == 1 : "Polygon count should not be altered by mutation.";
+ ColouredPolygon evolvedPolygon = evolved.get(0);
+ List<Point> vertices = evolvedPolygon.getVertices();
+ assert vertices.size() == polygon.getVertices().size() : "Polygon should have no fewer points after mutation.";
+ assert evolvedPolygon == polygon : "Polygon should not have been changed at all.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringEvaluatorTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringEvaluatorTest.java
new file mode 100644
index 0000000..54a3dd4
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringEvaluatorTest.java
@@ -0,0 +1,58 @@
+//=============================================================================
+// 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.strings;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * Unit test for fitness function used by the strings example.
+ * @author Daniel Dyer
+ */
+public class StringEvaluatorTest
+{
+ @Test
+ public void testIdentical()
+ {
+ String target = "abcdefgh";
+ String candidate = "abcdefgh";
+ FitnessEvaluator<String> evaluator = new StringEvaluator(target);
+ int score = (int) evaluator.getFitness(candidate, null);
+ assert score == 0 : "Fitness should be zero for identical strings, is " + score;
+ }
+
+
+ @Test
+ public void testCompletelyDifferent()
+ {
+ String target = "abcdefgh";
+ String candidate = "ijklmnop";
+ FitnessEvaluator<String> evaluator = new StringEvaluator(target);
+ int score = (int) evaluator.getFitness(candidate, null);
+ assert score == target.length() : "Fitness should be " + target.length() + ", is " + score;
+ }
+
+
+ @Test
+ public void testPartialSolution()
+ {
+ String target = "abcdefgh";
+ String candidate = "abcdxxxx";
+ FitnessEvaluator<String> evaluator = new StringEvaluator(target);
+ int score = (int) evaluator.getFitness(candidate, null);
+ assert score == 4 : "Fitness score should be 4, is " + score;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringsExampleTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringsExampleTest.java
new file mode 100644
index 0000000..ab54121
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/strings/StringsExampleTest.java
@@ -0,0 +1,34 @@
+//=============================================================================
+// 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.strings;
+
+import org.testng.annotations.Test;
+
+/**
+ * Simple unit test for the Strings example. Makes sure that the evolution engine
+ * eventually returns the target String.
+ * @author Daniel Dyer
+ */
+public class StringsExampleTest
+{
+ @Test
+ public void testEvolution()
+ {
+ String target = "WATCHMAKER";
+ String result = StringsExample.evolveString(target);
+ assert result.equals(target) : "Evolution returned wrong result: " + result;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuCellRendererTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuCellRendererTest.java
new file mode 100644
index 0000000..e1f3df3
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuCellRendererTest.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.awt.Color;
+import javax.swing.JLabel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link SudokuCellRenderer} class.
+ * @author Daniel Dyer
+ */
+public class SudokuCellRendererTest
+{
+ private JTable table;
+
+
+ @Test
+ public void testRenderFixedCells()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ // Set the 2 lefthand cells in the top row.
+ model.setValueAt('1', 0, 0);
+ model.setValueAt('2', 0, 1);
+
+ JTable table = new JTable(model);
+ TableCellRenderer renderer = new SudokuCellRenderer();
+
+ JLabel cell1 = (JLabel) renderer.getTableCellRendererComponent(table, '1', false, false, 0, 0);
+ assert cell1.getText().equals("1") : "Wrong text at cell 1: " + cell1.getText();
+ assert cell1.getFont().isBold() : "Fixed cells should be rendered in bold.";
+ JLabel cell2 = (JLabel) renderer.getTableCellRendererComponent(table, '2', false, false, 0, 1);
+ assert cell2.getText().equals("2") : "Wrong text at cell 2: " + cell2.getText();
+ assert cell2.getFont().isBold() : "Fixed cells should be rendered in bold.";
+ // Check an empty cell, it should have no text.
+ JLabel cell3 = (JLabel) renderer.getTableCellRendererComponent(table, null, false, false, 0, 2);
+ assert cell3.getText().length() == 0 : "Wrong text at cell 3: " + cell3.getText();
+ }
+
+
+ @BeforeClass
+ public void createTable()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ model.setSudoku(SudokuTestUtils.createSudoku(new int[][]{{3, 2, 4, 8, 9, 1, 7, 5, 6},
+ {6, 9, 7, 1, 5, 2, 8, 4, 3},
+ {8, 1, 5, 7, 3, 6, 4, 2, 9},
+ {5, 2, 6, 9, 7, 4, 3, 1, 8},
+ {4, 9, 8, 1, 2, 5, 6, 7, 3},
+ {8, 7, 1, 3, 4, 2, 9, 6, 5},
+ {2, 6, 3, 4, 8, 7, 5, 9, 1},
+ {1, 3, 5, 8, 4, 9, 2, 6, 7},
+ {7, 4, 2, 1, 5, 3, 9, 8, 6}}));
+ table = new JTable(model);
+ }
+
+
+ @Test
+ public void testRenderCellWithNoConflicts()
+ {
+ TableCellRenderer renderer = new SudokuCellRenderer();
+ JLabel cell = (JLabel) renderer.getTableCellRendererComponent(table, 1, false, false, 2, 1);
+ assert cell.getText().equals("1") : "Wrong value at cell (2, 1): " + cell.getText();
+ assert cell.getBackground() == Color.WHITE : "Cell without conflicts should be white.";
+ }
+
+
+ @Test
+ public void testRenderCellWithOneConflict()
+ {
+ TableCellRenderer renderer = new SudokuCellRenderer();
+ JLabel cell = (JLabel) renderer.getTableCellRendererComponent(table, 3, false, false, 7, 1);
+ assert cell.getText().equals("3") : "Wrong value at cell (7, 1): " + cell.getText();
+ assert cell.getBackground() == Color.YELLOW : "Cell with one conflict should be yellow.";
+ }
+
+
+ @Test
+ public void testRenderCellWithTwoConflicts()
+ {
+ TableCellRenderer renderer = new SudokuCellRenderer();
+ JLabel cell = (JLabel) renderer.getTableCellRendererComponent(table, 6, false, false, 8, 8);
+ assert cell.getText().equals("6") : "Wrong value at cell (8, 8): " + cell.getText();
+ assert cell.getBackground() == Color.ORANGE : "Cell with two conflicts should be orange.";
+ }
+
+
+ @Test
+ public void testRenderCellWithThreeConflicts()
+ {
+ TableCellRenderer renderer = new SudokuCellRenderer();
+ JLabel cell = (JLabel) renderer.getTableCellRendererComponent(table, 1, false, false, 1, 3);
+ assert cell.getText().equals("1") : "Wrong value at cell (1, 3): " + cell.getText();
+ assert cell.getBackground() == Color.RED : "Cell with two conflicts should be red.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluatorTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluatorTest.java
new file mode 100644
index 0000000..38864fd
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuEvaluatorTest.java
@@ -0,0 +1,77 @@
+//=============================================================================
+// 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.sudoku;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+
+/**
+ * Unit test for sudoku fitness evaluator.
+ * @author Daniel Dyer
+ */
+public class SudokuEvaluatorTest
+{
+ /**
+ * Ensure that the evaluator returns zero for a correct solution.
+ */
+ @Test
+ public void testCorrectSolution()
+ {
+ Sudoku sudoku = SudokuTestUtils.createSudoku(new int[][]
+ {
+ {1, 2, 8, 5, 4, 3, 9, 6, 7},
+ {7, 6, 4, 9, 2, 8, 5, 1, 3},
+ {3, 9, 5, 7, 6, 1, 2, 4, 8},
+ {6, 1, 9, 4, 8, 5, 7, 3, 2},
+ {5, 8, 3, 6, 7, 2, 1, 9, 4},
+ {4, 7, 2, 3, 1, 9, 8, 5, 6},
+ {8, 5, 1, 2, 3, 6, 4, 7, 9},
+ {9, 4, 6, 8, 5, 7, 3, 2, 1},
+ {2, 3, 7, 1, 9, 4, 6, 8, 5}
+ });
+ FitnessEvaluator<Sudoku> evaluator = new SudokuEvaluator();
+ int fitness = (int) evaluator.getFitness(sudoku, null);
+ assert fitness == 0 : "Fitness should be zero for correct solution, is " + fitness;
+ }
+
+
+ /**
+ * Ensure that the evaluator returns zero for a correct solution.
+ */
+ @Test
+ public void testDuplicates()
+ {
+ // This sudoku as 4 invalid columns (0, 1, 3 and 5) and 2 invalid
+ // sub-grids (bottom-left and bottom-center).
+ Sudoku sudoku = SudokuTestUtils.createSudoku(new int[][]
+ {
+ {2, 1, 8, 5, 4, 3, 9, 6, 7},
+ {7, 6, 4, 9, 2, 8, 5, 1, 3},
+ {3, 9, 5, 7, 6, 1, 2, 4, 8},
+ {6, 1, 9, 4, 8, 5, 7, 3, 2},
+ {5, 8, 3, 6, 7, 2, 1, 9, 4},
+ {4, 7, 2, 3, 1, 9, 8, 5, 6},
+ {8, 5, 2, 1, 3, 6, 4, 7, 9},
+ {9, 4, 6, 8, 5, 7, 3, 2, 1},
+ {2, 3, 7, 1, 9, 4, 6, 8, 5}
+ });
+ FitnessEvaluator<Sudoku> evaluator = new SudokuEvaluator();
+ int fitness = (int) evaluator.getFitness(sudoku, null);
+ assert fitness == 6 : "Fitness should be 6, is " + fitness;
+ }
+
+
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuFactoryTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuFactoryTest.java
new file mode 100644
index 0000000..f81cfa6
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuFactoryTest.java
@@ -0,0 +1,142 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Unit test for sudoku candidate solution factory.
+ * @author Daniel Dyer
+ */
+public class SudokuFactoryTest
+{
+ /**
+ * Checks to make sure that the givens are correctly placed and that each row
+ * contains each value exactly once.
+ */
+ @Test
+ public void testValidity()
+ {
+ CandidateFactory<Sudoku> factory = new SudokuFactory(".9.......",
+ ".........",
+ "........5",
+ "....2....",
+ ".........",
+ ".........",
+ ".........",
+ "...1.....",
+ "........9");
+
+ List<Sudoku> population = factory.generateInitialPopulation(20, ExamplesTestUtils.getRNG());
+ for (Sudoku sudoku : population)
+ {
+ // Check givens are correctly placed.
+ assert sudoku.isFixed(2, 8) : "Cell (2, 8) should be fixed.";
+ assert sudoku.getValue(2, 8) == 5 : "Cell (2, 8) should contain 5.";
+ assert sudoku.isFixed(7, 3) : "Cell (7, 3) should be fixed.";
+ assert sudoku.getValue(7, 3) == 1 : "Cell (7, 3) should contain 1.";
+ assert sudoku.isFixed(3, 4) : "Cell (3, 4) should be fixed.";
+ assert sudoku.getValue(3, 4) == 2 : "Cell (3, 4) should contain 2.";
+ assert sudoku.isFixed(0, 1) : "Cell (0, 1) should be fixed.";
+ assert sudoku.getValue(0, 1) == 9 : "Cell (0, 1) should contain 9.";
+ assert sudoku.isFixed(8, 8) : "Cell (8, 8) should be fixed.";
+ assert sudoku.getValue(8, 8) == 9 : "Cell (8, 8) should contain 9.";
+
+ // Check that each row has no duplicates.
+ Set<Integer> set = new HashSet<Integer>();
+ for (int i = 0; i < 9; i++)
+ {
+ Sudoku.Cell[] row = sudoku.getRow(i);
+ for (Sudoku.Cell cell : row)
+ {
+ set.add(cell.getValue());
+ }
+ if (set.size() < 9)
+ {
+ System.out.println(sudoku);
+ assert false : "Row " + i + " contains duplicates.";
+ }
+ }
+ }
+ }
+
+
+ /**
+ * If the pattern used to create a Sudoku factory contains any characters
+ * other than the values 1 - 9 or dots (which represent empty cells), then
+ * an appropriate exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidPatternChars()
+ {
+ // This pattern is the right size but contains an invalid character.
+ String[] pattern = {"....9....",
+ "2..3.....",
+ "........1",
+ "....a....", // Invalid character on this line.
+ "....4....",
+ ".........",
+ ".........",
+ ".........",
+ "........."};
+ new SudokuFactory(pattern);
+ }
+
+
+ /**
+ * If the pattern used to create a Sudoku factory contains the wrong number
+ * of rows then an appropriate exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWrongNumberOfRows()
+ {
+ // This pattern contains only valid characters and has the right number
+ // of columns but doesn't have enough rows.
+ String[] pattern = {"....9....",
+ "2..3.....",
+ "........1",
+ ".........",
+ "........."};
+ new SudokuFactory(pattern);
+ }
+
+
+ /**
+ * If the pattern used to create a Sudoku factory contains the wrong number
+ * of columns then an appropriate exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWrongNumberOfColumns()
+ {
+ // This pattern contains only valid characters and has the right number
+ // of rows but has too many columns in some
+ String[] pattern = {"....9....",
+ "2..3.....",
+ "........1",
+ ".........",
+ ".........7",
+ ".........",
+ ".4.......6",
+ "..1.3....",
+ "........8"};
+ new SudokuFactory(pattern);
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutationTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutationTest.java
new file mode 100644
index 0000000..9386e83
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuRowMutationTest.java
@@ -0,0 +1,127 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for Sudoku mutation operator.
+ * @author Daniel Dyer
+ */
+public class SudokuRowMutationTest
+{
+ /**
+ * Tests to ensure that rows are still valid after mutation. Each row
+ * should contain each value 1-9 exactly once.
+ */
+ @Test
+ public void testValidity()
+ {
+ EvolutionaryOperator<Sudoku> mutation = new SudokuRowMutation(8, 1);
+ List<Sudoku> population = Arrays.asList(SudokuTestUtils.createSudoku(new int[][]
+ {
+ {1, 2, 8, 5, 4, 3, 9, 6, 7},
+ {7, 6, 4, 9, 2, 8, 5, 1, 3},
+ {3, 9, 5, 7, 6, 1, 2, 4, 8},
+ {6, 1, 9, 4, 8, 5, 7, 3, 2},
+ {5, 8, 3, 6, 7, 2, 1, 9, 4},
+ {4, 7, 2, 3, 1, 9, 8, 5, 6},
+ {8, 5, 1, 2, 3, 6, 4, 7, 9},
+ {9, 4, 6, 8, 5, 7, 3, 2, 1},
+ {2, 3, 7, 1, 9, 4, 6, 8, 5}
+ }));
+ final Set<Integer> counts = new HashSet<Integer>(Sudoku.SIZE);
+ for (int i = 0; i < 20; i++)
+ {
+ population = mutation.apply(population, ExamplesTestUtils.getRNG());
+ assert population.size() == 1 : "Population size changed after mutation(s).";
+ Sudoku mutatedSudoku = population.get(0);
+ for (int j = 0; j < Sudoku.SIZE; j++)
+ {
+ Sudoku.Cell[] row = mutatedSudoku.getRow(j);
+ assert row.length == Sudoku.SIZE : "Row length is invalid: " + row.length;
+ for (Sudoku.Cell cell : row)
+ {
+ int value = cell.getValue();
+ assert value > 0 && value <= Sudoku.SIZE : "Cell value out of range: " + value;
+ counts.add(value);
+ }
+ assert counts.size() == Sudoku.SIZE : "Row contains duplicates.";
+ counts.clear();
+ }
+ }
+ }
+
+
+ /**
+ * Check that the mutation never modifies the value of fixed cells.
+ */
+ @Test(dependsOnMethods = "testValidity")
+ public void testFixedConstraints()
+ {
+ EvolutionaryOperator<Sudoku> mutation = new SudokuRowMutation(8, 1);
+ Sudoku.Cell[][] cells = new Sudoku.Cell[Sudoku.SIZE][Sudoku.SIZE];
+ // One cell in each row is fixed (cell 1 in row 1, cell 2 in row 2, etc.)
+ for (int row = 0; row < Sudoku.SIZE; row++)
+ {
+ for (int column = 0; column < Sudoku.SIZE; column++)
+ {
+ cells[row][column] = new Sudoku.Cell(column + 1, column == row);
+ }
+ }
+ List<Sudoku> population = Arrays.asList(new Sudoku(cells));
+ for (int i = 0; i < 100; i++) // 100 generations of mutation.
+ {
+ population = mutation.apply(population, ExamplesTestUtils.getRNG());
+ Sudoku sudoku = population.get(0);
+ for (int row = 0; row < Sudoku.SIZE; row++)
+ {
+ for (int column = 0; column < Sudoku.SIZE; column++)
+ {
+ if (row == column)
+ {
+ assert sudoku.isFixed(row, column) : "Fixed cell has become unfixed.";
+ assert sudoku.getValue(row, column) == (row + 1) : "Fixed cell has changed value.";
+ }
+ else
+ {
+ assert !sudoku.isFixed(row, column) : "Unfixed cell has become fixed.";
+ }
+ }
+ }
+ }
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMutationCount()
+ {
+ new SudokuRowMutation(0, 1); // Should throw an IllegalArgumentException.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMutationAmount()
+ {
+ new SudokuRowMutation(1, 0); // Should throw an IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTableModelTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTableModelTest.java
new file mode 100644
index 0000000..30bbf18
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTableModelTest.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// 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.sudoku;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the table model used by the Sudoku example application.
+ * @author Daniel Dyer
+ */
+public class SudokuTableModelTest
+{
+ private static final String[] TEST_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"};
+
+ /**
+ * Makes sure that the table model class correctly converts to and from the
+ * String patterns used by {@link SudokuFactory}.
+ */
+ @Test
+ public void testPatternConversions()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ model.setPattern(TEST_PUZZLE);
+ // Change a cell, to ensure we don't get exactly the same pattern back.
+ model.setValueAt('1', 8, 7);
+ String[] newPattern = model.getPattern();
+ // Make sure the pattern is correct.
+ for (int i = 0; i < 8; i++) // Check the first 8 rows, which haven't changed.
+ {
+ assert newPattern[i].equals(TEST_PUZZLE[i]) : "Row " + i + " incorrect: " + newPattern[i];
+ }
+ // Check modified row.
+ assert newPattern[8].equals("6.4...312") : "Row 8 incorrect: " + newPattern[8];
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManyRowsInPattern()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ model.setPattern(new String[]{".........",
+ ".........",
+ ".........",
+ ".........",
+ ".........",
+ ".........",
+ ".........",
+ ".........",
+ ".........",
+ "........."}); // 10 rows should trigger an IllegalArgumentException.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManyColumnsInPattern()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ model.setPattern(new String[]{"..........",
+ "..........",
+ "..........",
+ "..........",
+ "..........",
+ "..........",
+ "..........",
+ "..........",
+ ".........."}); // 10 columns in each row should trigger an IllegalArgumentException.
+ }
+
+
+ @Test
+ public void testGetValueAt()
+ {
+ SudokuTableModel model = new SudokuTableModel();
+ model.setSudoku(SudokuTestUtils.createSudoku(new int[][]{{1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9}}));
+ for (int row = 0; row < model.getRowCount(); row++)
+ {
+ for (int column = 0; column < model.getColumnCount(); column++)
+ {
+ int actualCell = (Integer) model.getValueAt(row, column);
+ assert actualCell == column + 1: "Wrong value at " + row + ", " + column + ": " + actualCell;
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTest.java
new file mode 100644
index 0000000..798b0fa
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTest.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// 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.sudoku;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link Sudoku} data structure.
+ * @author Daniel Dyer
+ */
+public class SudokuTest
+{
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWrongNumberOfRows()
+ {
+ // This grid has one row and nine columns. Constructing it should throw
+ // an IllegalArgumentException.
+ new Sudoku(new Sudoku.Cell[][]{{new Sudoku.Cell(1, false),
+ new Sudoku.Cell(2, false),
+ new Sudoku.Cell(3, false),
+ new Sudoku.Cell(4, false),
+ new Sudoku.Cell(5, false),
+ new Sudoku.Cell(6, false),
+ new Sudoku.Cell(7, false),
+ new Sudoku.Cell(8, false),
+ new Sudoku.Cell(9, false)}});
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWrongNumberOfColumns()
+ {
+ // This grid has nine rows and one column. Constructing it should throw
+ // an IllegalArgumentException.
+ new Sudoku(new Sudoku.Cell[][]{{new Sudoku.Cell(1, false)},
+ {new Sudoku.Cell(2, false)},
+ {new Sudoku.Cell(3, false)},
+ {new Sudoku.Cell(4, false)},
+ {new Sudoku.Cell(5, false)},
+ {new Sudoku.Cell(6, false)},
+ {new Sudoku.Cell(7, false)},
+ {new Sudoku.Cell(8, false)},
+ {new Sudoku.Cell(9, false)}});
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testCellValueTooLow()
+ {
+ new Sudoku.Cell(0, false);
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testCellValueTooHigh()
+ {
+ new Sudoku.Cell(10, false);
+ }
+
+
+ @Test
+ public void testToString()
+ {
+ Sudoku sudoku = SudokuTestUtils.createSudoku(new int[][]{{1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9},
+ {1, 2, 3, 4, 5, 6, 7, 8, 9}});
+ String text = sudoku.toString();
+ assert text.equals(" 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n" +
+ " 1 2 3 4 5 6 7 8 9\n") : "Wrong string representation:\n" + text;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTestUtils.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTestUtils.java
new file mode 100644
index 0000000..58dc4ea
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuTestUtils.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// 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.sudoku;
+
+/**
+ * Utility methods that help in unit tests.
+ * @author Daniel Dyer
+ */
+class SudokuTestUtils
+{
+ private SudokuTestUtils()
+ {
+ // Prevents instantiation.
+ }
+
+
+ /**
+ * Convenience method for creating a Sudoku object from a 2D array of ints.
+ * Ignores which cells are fixed and which are not.
+ * @param values A 9x9 2-dimensional array that contains values (1-9) for
+ * each of the cells in a Sudoku grid.
+ */
+ public static Sudoku createSudoku(int[][] values)
+ {
+ Sudoku.Cell[][] cells = new Sudoku.Cell[Sudoku.SIZE][Sudoku.SIZE];
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ for (int j = 0; j < Sudoku.SIZE; j++)
+ {
+ cells[i][j] = new Sudoku.Cell(values[i][j], false);
+ }
+ }
+ return new Sudoku(cells);
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossoverTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossoverTest.java
new file mode 100644
index 0000000..4f430be
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuVerticalCrossoverTest.java
@@ -0,0 +1,64 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.examples.ExamplesTestUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Unit test for Sudoku cross-over operator.
+ * @author Daniel Dyer
+ */
+public class SudokuVerticalCrossoverTest
+{
+ /**
+ * Ensures that the simplest configuration (a single cross-over point)
+ * works as expected.
+ */
+ @Test
+ public void testSinglePointCrossover()
+ {
+ EvolutionaryOperator<Sudoku> crossover = new SudokuVerticalCrossover();
+ int[] forwards = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+ int[] backwards = {9, 8, 7, 6, 5, 4, 3, 2, 1};
+ int[][] parent1 = new int[Sudoku.SIZE][];
+ int[][] parent2 = new int[Sudoku.SIZE][];
+ for (int i = 0; i < Sudoku.SIZE; i++)
+ {
+ parent1[i] = forwards;
+ parent2[i] = backwards;
+ }
+ List<Sudoku> population = Arrays.asList(SudokuTestUtils.createSudoku(parent1),
+ SudokuTestUtils.createSudoku(parent2));
+ population = crossover.apply(population, ExamplesTestUtils.getRNG());
+ assert population.size() == 2 : "Population size changed after cross-over.";
+ Sudoku offspring1 = population.get(0);
+ Sudoku offspring2 = population.get(1);
+ // Two groups of rows should be different from each other (they come from different parents).
+ assert !Arrays.equals(offspring1.getRow(0), offspring1.getRow(8))
+ : "Row 0 should not match row 8.";
+ assert !Arrays.equals(offspring2.getRow(0), offspring2.getRow(8))
+ : "Row 0 should not match row 8.";
+ // The two offspring should have inherited different rows.
+ assert !Arrays.equals(offspring1.getRow(0), offspring2.getRow(0))
+ : "Offspring 1 row 0 should not match offspring 2 row 0.";
+ assert !Arrays.equals(offspring1.getRow(8), offspring2.getRow(8))
+ : "Offspring 1 row 8 should not match offspring 2 row 8.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuViewTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuViewTest.java
new file mode 100644
index 0000000..999a92a
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/sudoku/SudokuViewTest.java
@@ -0,0 +1,117 @@
+//=============================================================================
+// 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.sudoku;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.data.TableCell;
+import org.fest.swing.fixture.FrameFixture;
+import org.fest.swing.fixture.JTableCellFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link SudokuView} class.
+ * @author Daniel Dyer
+ */
+public class SudokuViewTest
+{
+ private static final String[] TEST_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 Robot robot;
+
+ @BeforeMethod(groups = "display-required")
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod(groups = "display-required")
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required")
+ public void testDisplayPuzzle()
+ {
+ SudokuView view = new SudokuView();
+ JFrame frame = new JFrame();
+ frame.add(view, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(400, 400);
+ frame.validate();
+
+ frameFixture.show();
+ view.setPuzzle(TEST_PUZZLE);
+
+ // Check a non-empty cell.
+ JTableCellFixture cell1 = frameFixture.table().cell(TableCell.row(0).column(0));
+ cell1.requireEditable();
+ cell1.requireValue("4");
+
+ // And an empty cell.
+ JTableCellFixture cell2 = frameFixture.table().cell(TableCell.row(0).column(1));
+ cell2.requireEditable();
+ cell2.requireValue("");
+ }
+
+
+ @Test(groups = "display-required")
+ public void testDisplaySolution()
+ {
+ SudokuView view = new SudokuView();
+ JFrame frame = new JFrame();
+ frame.add(view, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(400, 400);
+ frame.validate();
+
+ frameFixture.show();
+ Sudoku sudoku = SudokuTestUtils.createSudoku(new int[][]
+ {
+ {1, 2, 8, 5, 4, 3, 9, 6, 7},
+ {7, 6, 4, 9, 2, 8, 5, 1, 3},
+ {3, 9, 5, 7, 6, 1, 2, 4, 8},
+ {6, 1, 9, 4, 8, 5, 7, 3, 2},
+ {5, 8, 3, 6, 7, 2, 1, 9, 4},
+ {4, 7, 2, 3, 1, 9, 8, 5, 6},
+ {8, 5, 1, 2, 3, 6, 4, 7, 9},
+ {9, 4, 6, 8, 5, 7, 3, 2, 1},
+ {2, 3, 7, 1, 9, 4, 6, 8, 5}
+ });
+ view.setSolution(sudoku);
+
+ JTableCellFixture cell1 = frameFixture.table().cell(TableCell.row(0).column(0));
+ cell1.requireNotEditable();
+ cell1.requireValue("1");
+ }
+
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesmanTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesmanTest.java
new file mode 100644
index 0000000..ee7a144
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/BruteForceTravellingSalesmanTest.java
@@ -0,0 +1,51 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+
+/**
+ * Test for the brute force solution to the travelling salesman
+ * problem.
+ * @author Daniel Dyer
+ */
+public class BruteForceTravellingSalesmanTest
+{
+ /**
+ * Make sure that the brute force implementation returns the correct result.
+ */
+ @Test
+ public void testEvaluation()
+ {
+ DistanceLookup data = new TestDistances();
+ TravellingSalesmanStrategy strategy = new BruteForceTravellingSalesman(data);
+ List<String> cities = Arrays.asList("City1", "City2", "City3", "City4");
+ List<String> route = strategy.calculateShortestRoute(cities, null);
+ assert route.size() == 4 : "Route is wrong length: " + route.size();
+ assert route.contains("City1") : "Route does not contain City1.";
+ assert route.contains("City2") : "Route does not contain City2.";
+ assert route.contains("City3") : "Route does not contain City3.";
+ assert route.contains("City4") : "Route does not contain City4.";
+ double distance = 0;
+ distance += data.getDistance(route.get(0), route.get(1));
+ distance += data.getDistance(route.get(1), route.get(2));
+ distance += data.getDistance(route.get(2), route.get(3));
+ distance += data.getDistance(route.get(3), route.get(0));
+ assert (long) distance == 47 : "Incorrect shortest route: " + distance;
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookupTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookupTest.java
new file mode 100644
index 0000000..debdc96
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EuropeanDistanceLookupTest.java
@@ -0,0 +1,65 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the distance look-ups for European cities that is used by
+ * the Travelling Salesman applet.
+ * @author Daniel Dyer
+ */
+public class EuropeanDistanceLookupTest
+{
+ /**
+ * Check to make sure that each city's distance to itself is zero.
+ */
+ @Test
+ public void testSelfDistances()
+ {
+ DistanceLookup europe = new EuropeanDistanceLookup();
+ List<String> cities = europe.getKnownCities();
+ for (String city : cities)
+ {
+ assert europe.getDistance(city, city) == 0 : city + " distance to self is non-zero.";
+ }
+ }
+
+
+ /**
+ * Check to make sure that for every pair of cities, the distance in one
+ * direction is equal to the distance in the other direction.
+ */
+ @Test
+ public void testReturnDistances()
+ {
+ DistanceLookup europe = new EuropeanDistanceLookup();
+ Queue<String> cities = new LinkedList<String>(europe.getKnownCities());
+ while (!cities.isEmpty())
+ {
+ String startCity = cities.remove();
+ for(String city : cities)
+ {
+ int outDistance = europe.getDistance(startCity, city);
+ int returnDistance = europe.getDistance(city, startCity);
+ assert outDistance == returnDistance : "Return distance mismatch for " + startCity + " and " + city;
+ }
+ }
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesmanTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesmanTest.java
new file mode 100644
index 0000000..c2ece30
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/EvolutionaryTravellingSalesmanTest.java
@@ -0,0 +1,95 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.selection.TruncationSelection;
+
+/**
+ * Unit test for the evolutionary approach to the travelling salesman problem.
+ * This test does not check that the correct result is returned (we would need
+ * to run the full evolutionary algorithm for that, and even then optimality
+ * is not guaranteed). This just ensures that nothing invalid happens and
+ * provides coverage for the classes involved.
+ * @author Daniel Dyer
+ */
+public class EvolutionaryTravellingSalesmanTest
+{
+ private final DistanceLookup data = new TestDistances();
+
+ /**
+ * Ensure that the algorithm behaves when configured to use mutation.
+ */
+ @Test
+ public void testWithMutation()
+ {
+ TravellingSalesmanStrategy strategy = new EvolutionaryTravellingSalesman(data,
+ new TruncationSelection(0.5),
+ 10, // Small population.
+ 0, // No elitism.
+ 3, // Only a few generations.
+ false, // Cross-over.
+ true); // Mutation.
+ List<String> cities = Arrays.asList("City1", "City2", "City3", "City4");
+ List<String> route = strategy.calculateShortestRoute(cities, null);
+ assert route.size() == 4 : "Route is wrong length: " + route.size();
+ assert route.contains("City1") : "Route does not contain City1.";
+ assert route.contains("City2") : "Route does not contain City2.";
+ assert route.contains("City3") : "Route does not contain City3.";
+ assert route.contains("City4") : "Route does not contain City4.";
+ }
+
+
+ /**
+ * Ensure that the algorithm behaves when configured to use cross-over.
+ */
+ @Test
+ public void testWithCrossover()
+ {
+ TravellingSalesmanStrategy strategy = new EvolutionaryTravellingSalesman(data,
+ new TruncationSelection(0.5),
+ 10, // Small population.
+ 0, // No elitism.
+ 3, // Only a few generations.
+ true, // Cross-over
+ false); // Mutation.
+ List<String> cities = Arrays.asList("City1", "City2", "City3", "City4");
+ List<String> route = strategy.calculateShortestRoute(cities, null);
+ assert route.size() == 4 : "Route is wrong length: " + route.size();
+ assert route.contains("City1") : "Route does not contain City1.";
+ assert route.contains("City2") : "Route does not contain City2.";
+ assert route.contains("City3") : "Route does not contain City3.";
+ assert route.contains("City4") : "Route does not contain City4.";
+ }
+
+
+ /**
+ * The strategy must ensure that at least one of cross-over or mutation is chosen.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoEvolution()
+ {
+ new EvolutionaryTravellingSalesman(data,
+ new TruncationSelection(0.5),
+ 10,
+ 0,
+ 3,
+ false,
+ false); // Should throw an IllegalArgumentException as no operators are enabled.
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanelTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanelTest.java
new file mode 100644
index 0000000..9f2244d
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/ItineraryPanelTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import java.util.Collection;
+import javax.swing.JFrame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Basic unit test for the {@link ItineraryPanel} class. Makes sure that the
+ * buttons operate as expected so that the right cities are returned.
+ * @author Daniel Dyer
+ */
+public class ItineraryPanelTest
+{
+ private static final TestDistances CITIES = new TestDistances();
+
+ private Robot robot;
+
+ @BeforeMethod
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testSelectAll()
+ {
+ ItineraryPanel itineraryPanel = new ItineraryPanel(CITIES.getKnownCities());
+ JFrame frame = new JFrame();
+ frame.add(itineraryPanel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(100, 300);
+ frame.validate();
+ frame.setVisible(true);
+ assert itineraryPanel.getSelectedCities().isEmpty() : "Should be no cities selected initially.";
+ frameFixture.button("All").click();
+ Collection<String> selectedCities = itineraryPanel.getSelectedCities();
+ assert selectedCities.size() == CITIES.getKnownCities().size()
+ : "All cities should be selected after button click.";
+ }
+
+
+ @Test(groups = "display-required", // Will fail if run in a headless environment.
+ dependsOnMethods = "testSelectAll")
+ public void testSelectNone()
+ {
+ ItineraryPanel itineraryPanel = new ItineraryPanel(CITIES.getKnownCities());
+ JFrame frame = new JFrame();
+ frame.add(itineraryPanel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(100, 300);
+ frame.validate();
+ frame.setVisible(true);
+ frameFixture.button("All").click();
+ Collection<String> selectedCities = itineraryPanel.getSelectedCities();
+ assert selectedCities.size() == CITIES.getKnownCities().size()
+ : "All cities should be selected after all button click.";
+ frameFixture.button("None").click();
+ assert itineraryPanel.getSelectedCities().isEmpty()
+ : "No cities should be selected after clear button is clicked.";
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluatorTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluatorTest.java
new file mode 100644
index 0000000..b6ca0ee
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/RouteEvaluatorTest.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the route evaluator used by both Travelling Salesman
+ * implementations. Checks to make sure that route distances are
+ * calculated correctly.
+ * @author Daniel Dyer
+ */
+public class RouteEvaluatorTest
+{
+ /**
+ * Test different routes to make sure the distances are calculated correctly.
+ */
+ @Test
+ public void testDistanceCalculations()
+ {
+ RouteEvaluator evaluator = new RouteEvaluator(new TestDistances());
+
+ List<String> route1 = Arrays.asList("City4", "City1", "City3", "City2");
+ // Expected distance is sum of distances between adjacent cities on route
+ // including returning to the start city.
+ int expectedDistance1 = 8 + 5 + 13 + 21;
+ int actualDistance1 = (int) evaluator.getFitness(route1, null);
+ assert actualDistance1 == expectedDistance1 : "Distance should be " + expectedDistance1 + ", was " + actualDistance1;
+
+ List<String> route2 = Arrays.asList("City3", "City4", "City2", "City1");
+ // Expected distance is sum of distances between adjacent cities on route
+ // including returning to the start city.
+ int expectedDistance2 = 34 + 21 + 3 + 5;
+ int actualDistance2 = (int) evaluator.getFitness(route2, null);
+ assert actualDistance2 == expectedDistance2 : "Distance should be " + expectedDistance2 + ", was " + actualDistance2;
+ }
+
+
+ /**
+ * Make sure the route evaluator works for end cases.
+ */
+ @Test
+ public void testSingleCityRoute()
+ {
+ RouteEvaluator evaluator = new RouteEvaluator(new TestDistances());
+
+ List<String> route = Arrays.asList("City1");
+ // Expected distance is sum of distances between adjacent cities on route
+ // including returning to the start city.
+ int distance = (int) evaluator.getFitness(route, null);
+ assert distance == 0 : "Distance should be 0, was " + distance;
+ }
+
+
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanelTest.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanelTest.java
new file mode 100644
index 0000000..00519d9
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/StrategyPanelTest.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Basic unit test for the {@link StrategyPanel} used by the Travelling
+ * Salesman example applet. Makes sure that it returns the correct type
+ * of solver strategy depending on the radio button settings.
+ * @author Daniel Dyer
+ */
+public class StrategyPanelTest
+{
+ private static final TestDistances CITIES = new TestDistances();
+
+ private Robot robot;
+
+ @BeforeMethod
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required")
+ public void testBruteForceOption()
+ {
+ StrategyPanel strategyPanel = new StrategyPanel(CITIES);
+ JFrame frame = new JFrame();
+ frame.add(strategyPanel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(500, 300);
+ frame.validate();
+ frame.setVisible(true);
+ robot.waitForIdle();
+
+ // Evolution controls should be enabled by default.
+ frameFixture.panel("EvolutionPanel").requireEnabled();
+
+ frameFixture.radioButton("BruteForceOption").click();
+
+ // Evolution controls should be disabled when brute force option is selected.
+ frameFixture.panel("EvolutionPanel").requireDisabled();
+
+ TravellingSalesmanStrategy strategy = strategyPanel.getStrategy();
+ assert strategy instanceof BruteForceTravellingSalesman : "Wrong strategy class: " + strategy.getClass();
+ }
+
+
+ @Test(groups = "display-required")
+ public void testEvolutionOption()
+ {
+ StrategyPanel strategyPanel = new StrategyPanel(CITIES);
+ JFrame frame = new JFrame();
+ frame.add(strategyPanel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(500, 300);
+ frame.validate();
+ frame.setVisible(true);
+ robot.waitForIdle();
+
+ frameFixture.radioButton("EvolutionOption").click();
+ TravellingSalesmanStrategy strategy = strategyPanel.getStrategy();
+ assert strategy instanceof EvolutionaryTravellingSalesman : "Wrong strategy class: " + strategy.getClass();
+ }
+
+
+ @Test(groups = "display-required")
+ public void testDisablePanel()
+ {
+ StrategyPanel strategyPanel = new StrategyPanel(CITIES);
+ JFrame frame = new JFrame();
+ frame.add(strategyPanel, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(500, 300);
+ frame.validate();
+ frame.setVisible(true);
+ robot.waitForIdle();
+
+ // Components should be enabled initially.
+ frameFixture.radioButton("EvolutionOption").requireEnabled();
+ frameFixture.panel("EvolutionPanel").requireEnabled();
+ frameFixture.radioButton("BruteForceOption").requireEnabled();
+
+ strategyPanel.setEnabled(false);
+ frameFixture.radioButton("EvolutionOption").requireDisabled();
+ frameFixture.panel("EvolutionPanel").requireDisabled();
+ frameFixture.radioButton("BruteForceOption").requireDisabled();
+ }
+}
diff --git a/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/TestDistances.java b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/TestDistances.java
new file mode 100644
index 0000000..cc7da3a
--- /dev/null
+++ b/watchmaker/examples/src/java/test/org/uncommons/watchmaker/examples/travellingsalesman/TestDistances.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// 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.travellingsalesman;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * Test data for travelling salesman unit tests. The shortest route between each
+ * of the four cities in this data set is 47km.
+ */
+final class TestDistances implements DistanceLookup
+{
+ private static final Map<String, Map<String, Integer>> distances = new TreeMap<String, Map<String, Integer>>();
+ static
+ {
+ Map<String, Integer> city1 = new TreeMap<String, Integer>();
+ city1.put("City1", 0);
+ city1.put("City2", 3);
+ city1.put("City3", 5);
+ city1.put("City4", 8);
+ distances.put("City1", city1);
+
+ Map<String, Integer> city2 = new TreeMap<String, Integer>();
+ city2.put("City1", 3);
+ city2.put("City2", 0);
+ city2.put("City3", 13);
+ city2.put("City4", 21);
+ distances.put("City2", city2);
+
+ Map<String, Integer> city3 = new TreeMap<String, Integer>();
+ city3.put("City1", 5);
+ city3.put("City2", 13);
+ city3.put("City3", 0);
+ city3.put("City4", 34);
+ distances.put("City3", city3);
+
+ Map<String, Integer> city4 = new TreeMap<String, Integer>();
+ city4.put("City1", 8);
+ city4.put("City2", 21);
+ city4.put("City3", 34);
+ city4.put("City4", 0);
+ distances.put("City4", city4);
+ }
+
+ public List<String> getKnownCities()
+ {
+ return new ArrayList<String>(distances.keySet());
+ }
+
+ public int getDistance(String startingCity, String destinationCity)
+ {
+ return distances.get(startingCity).get(destinationCity);
+ }
+}
diff --git a/watchmaker/framework/framework.iml b/watchmaker/framework/framework.iml
new file mode 100644
index 0000000..82c64ca
--- /dev/null
+++ b/watchmaker/framework/framework.iml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java/main" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/java/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="Maven: org.uncommons.maths:uncommons-maths:1.2.2" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jcommon:1.0.12" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jfreechart:1.0.8" level="project" />
+ <orderEntry type="library" name="Maven: com.google.collections:google-collections:1.0" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.testng:testng:6.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: junit:junit:3.8.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.beanshell:bsh:2.0b4" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: com.beust:jcommander:1.12" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.yaml:snakeyaml:1.6" level="project" />
+ </component>
+</module>
+
diff --git a/watchmaker/framework/nb-configuration.xml b/watchmaker/framework/nb-configuration.xml
new file mode 100644
index 0000000..ae35717
--- /dev/null
+++ b/watchmaker/framework/nb-configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>words</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>0</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>
+ </properties>
+</project-shared-configuration>
diff --git a/watchmaker/framework/pom.xml b/watchmaker/framework/pom.xml
new file mode 100644
index 0000000..51ecd7b
--- /dev/null
+++ b/watchmaker/framework/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--===========================================================================
+ 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.
+ ==========================================================================-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.uncommons.watchmaker</groupId>
+ <artifactId>watchmaker</artifactId>
+ <version>0.7.2</version>
+ </parent>
+ <artifactId>watchmaker-framework</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.uncommons.maths</groupId>
+ <artifactId>uncommons-maths</artifactId>
+ <version>1.2.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.collections</groupId>
+ <artifactId>google-collections</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src/java/main</sourceDirectory>
+ <testSourceDirectory>src/java/test</testSourceDirectory>
+ </build>
+</project>
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java
new file mode 100644
index 0000000..ed35b70
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// 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.util.concurrent;
+
+import java.util.concurrent.ThreadFactory;
+import org.uncommons.util.id.IDSource;
+import org.uncommons.util.id.IntSequenceIDSource;
+import org.uncommons.util.id.StringPrefixIDSource;
+
+/**
+ * Thread factory that creates threads for use by a
+ * {@link java.util.concurrent.ThreadPoolExecutor}. The factory can be
+ * configured to customise the names, priority and daemon status of created
+ * threads.
+ * @author Daniel Dyer
+ */
+public class ConfigurableThreadFactory implements ThreadFactory
+{
+ /**
+ * A default exception handler that simply logs the stack trace of the exception.
+ */
+ private static final Thread.UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER
+ = new Thread.UncaughtExceptionHandler()
+ {
+ public void uncaughtException(Thread thread, Throwable throwable)
+ {
+ // Log any exceptions thrown.
+ throwable.printStackTrace();
+ }
+ };
+
+
+ private final IDSource<String> nameGenerator;
+ private final int priority;
+ private final boolean daemon;
+ private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
+
+ /**
+ * @param namePrefix The String prefix used to assign identifiers to created threads.
+ * @param priority The initial priority for created threads.
+ * @param daemon Whether or not created threads should be daemon threads or user threads.
+ * The JVM exits when the only threads running are all daemon threads.
+ */
+ public ConfigurableThreadFactory(String namePrefix,
+ int priority,
+ boolean daemon)
+ {
+ this(namePrefix, priority, daemon, DEFAULT_EXCEPTION_HANDLER);
+ }
+
+
+ /**
+ * @param namePrefix The String prefix used to assign identifiers to created threads.
+ * @param priority The initial priority for created threads.
+ * @param daemon Whether or not created threads should be daemon threads or user threads.
+ * The JVM exits when the only threads running are all daemon threads.
+ * @param uncaughtExceptionHandler A strategy for dealing with uncaught exceptions.
+ */
+ public ConfigurableThreadFactory(String namePrefix,
+ int priority,
+ boolean daemon,
+ Thread.UncaughtExceptionHandler uncaughtExceptionHandler)
+ {
+ this.nameGenerator = new StringPrefixIDSource(namePrefix + '-', new IntSequenceIDSource());
+ this.priority = priority;
+ this.daemon = daemon;
+ this.uncaughtExceptionHandler = uncaughtExceptionHandler;
+ }
+
+
+ /**
+ * Creates a new thread configured according to this factory's parameters.
+ * @param runnable The runnable to be executed by the new thread.
+ * @return The created thread.
+ */
+ public Thread newThread(Runnable runnable)
+ {
+ Thread thread = new Thread(runnable, nameGenerator.nextID());
+ thread.setPriority(priority);
+ thread.setDaemon(daemon);
+ thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
+ return thread;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java
new file mode 100644
index 0000000..9ff32d1
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Concurrency utility classes.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.concurrent;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java
new file mode 100644
index 0000000..91635a9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java
@@ -0,0 +1,64 @@
+//=============================================================================
+// 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.util.id;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>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.</p>
+ * <p>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).</p>
+ * @author Daniel Dyer
+ */
+public final class CompositeIDSource implements IDSource<Long>
+{
+ private final Lock lock = new ReentrantLock();
+ private final long top32bits;
+ private final IDSource<Integer> sequence = new IntSequenceIDSource();
+
+
+ /**
+ * @param topPart The most significant 32 bits to use for the 64-bit IDs generated
+ * by this source. All IDs generated by this source will have the same top 4 bytes.
+ */
+ public CompositeIDSource(int topPart)
+ {
+ top32bits = ((long) topPart) << 32;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Long nextID()
+ {
+ lock.lock();
+ try
+ {
+ // Top part is value provided in constructor, lower 32 bits are from the sequence.
+ return (top32bits + sequence.nextID());
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java
new file mode 100644
index 0000000..ae5d642
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java
@@ -0,0 +1,38 @@
+//=============================================================================
+// 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.util.id;
+
+import java.io.Serializable;
+
+/**
+ * Defines operations for classes that generate unique identifiers. Generated IDs must
+ * be of a {@link java.io.Serializable} type. The strategy used will vary between
+ * implementations. It may be a straightforward sequence or a more complex, less predictable
+ * algorithm.
+ * @param <T> The type of ID returned by this source.
+ * @author Daniel Dyer
+ */
+public interface IDSource<T extends Serializable>
+{
+ /**
+ * Implementing classes are responsible for synchronization if concurrent invocations
+ * of this method are required.
+ * @return The next ID.
+ * @throws IDSourceExhaustedException If this ID source cannot generate any more
+ * unique IDs.
+ */
+ T nextID();
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java
new file mode 100644
index 0000000..fbf7d3f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java
@@ -0,0 +1,33 @@
+//=============================================================================
+// 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.util.id;
+
+/**
+ * Unchecked exception thrown if an {@link IDSource} runs out of unique IDs.
+ * @author Daniel Dyer
+ */
+public class IDSourceExhaustedException extends RuntimeException
+{
+ public IDSourceExhaustedException(String string)
+ {
+ super(string);
+ }
+
+ public IDSourceExhaustedException(String string, Throwable throwable)
+ {
+ super(string, throwable);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java
new file mode 100644
index 0000000..bf6c3cb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// 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.util.id;
+
+import java.util.concurrent.locks.Lock;
+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.
+ * @author Daniel Dyer
+ */
+public final class IntSequenceIDSource implements IDSource<Integer>
+{
+ private static final long SECONDS_IN_HOUR = 3600L;
+
+ private final Lock lock = new ReentrantLock();
+ private final long startTime;
+ private int lastID = -1;
+
+
+ /**
+ * @param firstValue The value at which to start the sequence (must
+ * be non-negative).
+ */
+ public IntSequenceIDSource(int firstValue)
+ {
+ if (firstValue < 0)
+ {
+ throw new IllegalArgumentException("Initial value must be non-negative.");
+ }
+ lastID = firstValue - 1;
+ startTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * Creates a sequence that starts at zero.
+ */
+ public IntSequenceIDSource()
+ {
+ this(0);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Integer nextID()
+ {
+ lock.lock();
+ try
+ {
+ if (lastID == Integer.MAX_VALUE)
+ {
+ long hours = (System.currentTimeMillis() - startTime) / SECONDS_IN_HOUR;
+ throw new IDSourceExhaustedException("32-bit ID source exhausted after " + hours + " hours.");
+ }
+ ++lastID;
+ return lastID;
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java
new file mode 100644
index 0000000..3ea85a9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// 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.util.id;
+
+import java.util.concurrent.locks.Lock;
+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.
+ * @author Daniel Dyer
+ */
+public final class LongSequenceIDSource implements IDSource<Long>
+{
+ private static final int SECONDS_IN_DAY = 86400;
+
+ private final Lock lock = new ReentrantLock();
+ private final long startTime;
+ private long lastID = -1;
+
+
+ /**
+ * @param firstValue The value at which to start the sequence (must
+ * be non-negative).
+ */
+ public LongSequenceIDSource(long firstValue)
+ {
+ if (firstValue < 0)
+ {
+ throw new IllegalArgumentException("Initial value must be non-negative.");
+ }
+ lastID = firstValue - 1;
+ startTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * Creates a sequence that starts at zero.
+ */
+ public LongSequenceIDSource()
+ {
+ this(0);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Long nextID()
+ {
+ lock.lock();
+ try
+ {
+ if (lastID == Long.MAX_VALUE)
+ {
+ long days = (System.currentTimeMillis() - startTime) / SECONDS_IN_DAY;
+ throw new IDSourceExhaustedException("64-bit ID source exhausted after " + days + " days.");
+ }
+ ++lastID;
+ return lastID;
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java
new file mode 100644
index 0000000..c4d683c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java
@@ -0,0 +1,60 @@
+//=============================================================================
+// 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.util.id;
+
+import java.util.concurrent.locks.Lock;
+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.
+ * @author Daniel Dyer
+ */
+public class StringPrefixIDSource implements IDSource<String>
+{
+ private final Lock lock = new ReentrantLock();
+ 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.
+ */
+ public StringPrefixIDSource(String prefix, IDSource<?> source)
+ {
+ this.prefix = prefix;
+ this.source = source;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String nextID()
+ {
+ lock.lock();
+ try
+ {
+ StringBuilder output = new StringBuilder(prefix);
+ output.append(source.nextID());
+ return output.toString();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java
new file mode 100644
index 0000000..323b7d3
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Strategies for providing unique identifiers of different types.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.id;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java
new file mode 100644
index 0000000..e104798
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java
@@ -0,0 +1,179 @@
+//=============================================================================
+// 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.util.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Helper methods to simplify code that uses reflection. These methods handle the
+ * checked exceptions and throw only unchecked exceptions. They are useful for dealing
+ * with reflection when we know that there is no chance of a checked exception. We can
+ * use this class and avoid all of the boiler-plate exception handling.
+ * @author Daniel Dyer
+ */
+public final class ReflectionUtils
+{
+ private ReflectionUtils()
+ {
+ // Prevents instantiation.
+ }
+
+
+ /**
+ * Invokes the specified method without throwing any checked exceptions.
+ * This is only valid for methods that are not declared to throw any checked
+ * exceptions. Any unchecked exceptions thrown by the specified method will be
+ * re-thrown (in their original form, not wrapped in an InvocationTargetException
+ * as would be the case for a normal reflective invocation).
+ * @param method The method to invoke. Both the method and its class must have
+ * been declared public and non-abstract, otherwise they will be inaccessible.
+ * @param target The object on which to invoke the method.
+ * @param arguments The method arguments.
+ * @param <T> The return type of the method. The compiler can usually infer the
+ * correct type.
+ * @return The result of invoking the method, or null if the method is void.
+ */
+ public static <T> T invokeUnchecked(Method method, Object target, Object... arguments)
+ {
+ try
+ {
+ @SuppressWarnings("unchecked")
+ T result = (T) method.invoke(target, arguments);
+ return result;
+ }
+ catch (IllegalAccessException ex)
+ {
+ // This cannot happen if the method is public.
+ throw new IllegalArgumentException("Method " + method.getName() + " is not publicly accessible.", ex);
+ }
+ catch (InvocationTargetException ex)
+ {
+ // If the method is not declared to throw any checked exceptions,
+ // the worst that can happen is a RuntimeException or an Error (we can,
+ // and should, re-throw both).
+ if (ex.getCause() instanceof Error)
+ {
+ throw (Error) ex.getCause();
+ }
+ else
+ {
+ throw (RuntimeException) ex.getCause();
+ }
+ }
+ }
+
+
+ /**
+ * Invokes the specified constructor without throwing any checked exceptions.
+ * This is only valid for constructors that are not declared to throw any checked
+ * exceptions. Any unchecked exceptions thrown by the specified constructor will be
+ * re-thrown (in their original form, not wrapped in an InvocationTargetException
+ * as would be the case for a normal reflective invocation).
+ * @param constructor The constructor to invoke. Both the constructor and its
+ * class must have been declared public, and the class must not be abstract,
+ * otherwise they will be inaccessible.
+ * @param arguments The method arguments.
+ * @param <T> The return type of the method. The compiler can usually infer the
+ * correct type.
+ * @return The object created by invoking the specified constructor with the specified
+ * arguments.
+ */
+ public static <T> T invokeUnchecked(Constructor<T> constructor, Object... arguments)
+ {
+ try
+ {
+ return constructor.newInstance(arguments);
+ }
+ catch (IllegalAccessException ex)
+ {
+ // This cannot happen if the constructor is public.
+ throw new IllegalArgumentException("Constructor is not publicly accessible.", ex);
+ }
+ catch (InstantiationException ex)
+ {
+ // This can only happen if the constructor belongs to an
+ // abstract class.
+ throw new IllegalArgumentException("Constructor is part of an abstract class.", ex);
+ }
+ catch (InvocationTargetException ex)
+ {
+ // If the method is not declared to throw any checked exceptions,
+ // the worst that can happen is a RuntimeException or an Error (we can,
+ // and should, re-throw both).
+ if (ex.getCause() instanceof Error)
+ {
+ throw (Error) ex.getCause();
+ }
+ else
+ {
+ throw (RuntimeException) ex.getCause();
+ }
+ }
+ }
+
+
+ /**
+ * Looks up a method that is explicitly identified. This method should only
+ * be used for methods that definitely exist. It does not throw the checked
+ * NoSuchMethodException. If the method does not exist, it will instead fail
+ * with an unchecked IllegalArgumentException.
+ * @param aClass The class in which the method exists.
+ * @param name The name of the method.
+ * @param paramTypes The types of the method's parameters.
+ * @return The identified method.
+ */
+ public static Method findKnownMethod(Class<?> aClass,
+ String name,
+ Class<?>... paramTypes)
+ {
+ try
+ {
+ return aClass.getMethod(name, paramTypes);
+ }
+ catch (NoSuchMethodException ex)
+ {
+ // This cannot happen if the method is correctly identified.
+ throw new IllegalArgumentException("Method " + name + " does not exist in class " + aClass.getName(), ex);
+ }
+ }
+
+
+ /**
+ * Looks up a constructor that is explicitly identified. This method should only
+ * be used for constructors that definitely exist. It does not throw the checked
+ * NoSuchMethodException. If the constructor does not exist, it will instead fail
+ * with an unchecked IllegalArgumentException.
+ * @param <T> The type of object that the constructor creates.
+ * @param aClass The class in which the constructor exists.
+ * @param paramTypes The types of the constructor's parameters.
+ * @return The identified constructor.
+ */
+ public static <T> Constructor<T> findKnownConstructor(Class<T> aClass,
+ Class<?>... paramTypes)
+ {
+ try
+ {
+ return aClass.getConstructor(paramTypes);
+ }
+ catch (NoSuchMethodException ex)
+ {
+ // This cannot happen if the method is correctly identified.
+ throw new IllegalArgumentException("Specified constructor does not exist in class " + aClass.getName(), ex);
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java
new file mode 100644
index 0000000..253ac44
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Utilities for simplifying code that uses reflection.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.reflection;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
new file mode 100644
index 0000000..486f10d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
@@ -0,0 +1,341 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Base class for {@link EvolutionEngine} implementations.
+ * @param <T> The type of entity evolved by the evolution engine.
+ * @author Daniel Dyer
+ * @see CandidateFactory
+ * @see FitnessEvaluator
+ */
+public abstract class AbstractEvolutionEngine<T> implements EvolutionEngine<T>
+{
+ // A single multi-threaded worker is shared among multiple evolution engine instances.
+ private static FitnessEvaluationWorker concurrentWorker = null;
+
+ private final Set<EvolutionObserver<? super T>> observers = new CopyOnWriteArraySet<EvolutionObserver<? super T>>();
+
+ private final Random rng;
+ private final CandidateFactory<T> candidateFactory;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+
+ private volatile boolean singleThreaded = false;
+
+ private List<TerminationCondition> satisfiedTerminationConditions;
+
+
+ /**
+ * Creates a new evolution engine by specifying the various components required by
+ * an evolutionary algorithm.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ protected AbstractEvolutionEngine(CandidateFactory<T> candidateFactory,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ Random rng)
+ {
+ this.candidateFactory = candidateFactory;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.rng = rng;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolve(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ seedCandidates,
+ conditions).get(0).getCandidate();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ if (eliteCount < 0 || eliteCount >= populationSize)
+ {
+ throw new IllegalArgumentException("Elite count must be non-negative and less than population size.");
+ }
+ if (conditions.length == 0)
+ {
+ throw new IllegalArgumentException("At least one TerminationCondition must be specified.");
+ }
+
+ satisfiedTerminationConditions = null;
+ int currentGenerationIndex = 0;
+ long startTime = System.currentTimeMillis();
+
+ List<T> population = candidateFactory.generateInitialPopulation(populationSize,
+ seedCandidates,
+ rng);
+
+ // Calculate the fitness scores for each member of the initial population.
+ List<EvaluatedCandidate<T>> evaluatedPopulation = evaluatePopulation(population);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ PopulationData<T> data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+
+ List<TerminationCondition> satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ while (satisfiedConditions == null)
+ {
+ ++currentGenerationIndex;
+ evaluatedPopulation = nextEvolutionStep(evaluatedPopulation, eliteCount, rng);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+ satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ }
+ this.satisfiedTerminationConditions = satisfiedConditions;
+ return evaluatedPopulation;
+ }
+
+
+ /**
+ * This method performs a single step/iteration of the evolutionary process.
+ * @param evaluatedPopulation The population at the beginning of the process.
+ * @param eliteCount The number of the fittest individuals that must be preserved.
+ * @param rng A source of randomness.
+ * @return The updated population after the evolutionary process has proceeded
+ * by one step/iteration.
+ */
+ protected abstract List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng);
+
+
+ /**
+ * Takes a population, assigns a fitness score to each member and returns
+ * the members with their scores attached, sorted in descending order of
+ * fitness (descending order of fitness score for natural scores, ascending
+ * order of scores for non-natural scores).
+ * @param population The population to evaluate (each candidate is assigned
+ * a fitness score).
+ * @return The evaluated population (a list of candidates with attached fitness
+ * scores).
+ */
+ protected List<EvaluatedCandidate<T>> evaluatePopulation(List<T> population)
+ {
+ List<EvaluatedCandidate<T>> evaluatedPopulation = new ArrayList<EvaluatedCandidate<T>>(population.size());
+
+ if (singleThreaded) // Do fitness evaluations on the request thread.
+ {
+ for (T candidate : population)
+ {
+ evaluatedPopulation.add(new EvaluatedCandidate<T>(candidate,
+ fitnessEvaluator.getFitness(candidate, population)));
+ }
+ }
+ else
+ {
+ // Divide the required number of fitness evaluations equally among the
+ // available processors and coordinate the threads so that we do not
+ // proceed until all threads have finished processing.
+ try
+ {
+ List<T> unmodifiablePopulation = Collections.unmodifiableList(population);
+ List<Future<EvaluatedCandidate<T>>> results = new ArrayList<Future<EvaluatedCandidate<T>>>(population.size());
+ // Submit tasks for execution and wait until all threads have finished fitness evaluations.
+ for (T candidate : population)
+ {
+ results.add(getSharedWorker().submit(new FitnessEvalutationTask<T>(fitnessEvaluator,
+ candidate,
+ unmodifiablePopulation)));
+ }
+ for (Future<EvaluatedCandidate<T>> result : results)
+ {
+ evaluatedPopulation.add(result.get());
+ }
+ }
+ catch (ExecutionException ex)
+ {
+ throw new IllegalStateException("Fitness evaluation task execution failed.", ex);
+ }
+ catch (InterruptedException ex)
+ {
+ // Restore the interrupted status, allows methods further up the call-stack
+ // to abort processing if appropriate.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return evaluatedPopulation;
+ }
+
+
+
+ /**
+ * <p>Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the evolution engine. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.</p>
+ *
+ * <p>If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.</p>
+ *
+ * <p>If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.</p>
+ *
+ * @throws IllegalStateException If this method is invoked on an evolution engine before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ public List<TerminationCondition> getSatisfiedTerminationConditions()
+ {
+ if (satisfiedTerminationConditions == null)
+ {
+ throw new IllegalStateException("EvolutionEngine has not terminated.");
+ }
+ else
+ {
+ return Collections.unmodifiableList(satisfiedTerminationConditions);
+ }
+ }
+
+
+ /**
+ * Adds a listener to receive status updates on the evolution progress.
+ * Updates are dispatched synchronously on the request thread. Observers should
+ * complete their processing and return in a timely manner to avoid holding up
+ * the evolution.
+ * @param observer An evolution observer call-back.
+ * @see #removeEvolutionObserver(EvolutionObserver)
+ */
+ public void addEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.add(observer);
+ }
+
+
+ /**
+ * Removes an evolution progress listener.
+ * @param observer An evolution observer call-back.
+ * @see #addEvolutionObserver(EvolutionObserver)
+ */
+ public void removeEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.remove(observer);
+ }
+
+
+ /**
+ * Send the population data to all registered observers.
+ * @param data Information about the current state of the population.
+ */
+ private void notifyPopulationChange(PopulationData<T> data)
+ {
+ for (EvolutionObserver<? super T> observer : observers)
+ {
+ observer.populationUpdate(data);
+ }
+ }
+
+
+ /**
+ * By default, fitness evaluations are performed on separate threads (as many as there are
+ * available cores/processors). Use this method to force evaluation to occur synchronously
+ * on the request thread. This is useful in restricted environments where programs are not
+ * permitted to start or control threads. It might also lead to better performance for
+ * programs that have extremely lightweight/trivial fitness evaluations.
+ * @param singleThreaded If true, fitness evaluations will be performed synchronously on the
+ * request thread. If false, fitness evaluations will be performed by worker threads.
+ */
+ public void setSingleThreaded(boolean singleThreaded)
+ {
+ this.singleThreaded = singleThreaded;
+ }
+
+
+ /**
+ * Lazily create the multi-threaded worker for fitness evaluations.
+ */
+ private static synchronized FitnessEvaluationWorker getSharedWorker()
+ {
+ if (concurrentWorker == null)
+ {
+ concurrentWorker = new FitnessEvaluationWorker();
+ }
+ return concurrentWorker;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java
new file mode 100644
index 0000000..ed3d8d2
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import com.google.common.collect.MapMaker;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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.</p>
+ *
+ * <p>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 <em>isolated</em> 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.</p>
+ * @param <T> The type of evolvable entity that can be evaluated.
+ *
+ * @author Daniel Dyer
+ */
+public class CachingFitnessEvaluator<T> implements FitnessEvaluator<T>
+{
+ private final FitnessEvaluator<T> 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<T, Double> cache = new MapMaker().weakKeys().makeMap();
+
+
+ /**
+ * Creates a caching fitness evaluator that wraps the specified evaluator.
+ * @param delegate The fitness evaluator that performs the actual calculations.
+ */
+ public CachingFitnessEvaluator(FitnessEvaluator<T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation performs a cache look-up every time it is invoked. If the
+ * fitness evaluator has already calculated the fitness score for the specified
+ * candidate that score is returned without delegating to the wrapped evaluator.</p>
+ */
+ public double getFitness(T candidate, List<? extends T> population)
+ {
+ Double fitness = cache.get(candidate);
+ if (fitness == null)
+ {
+ fitness = delegate.getFitness(candidate, population);
+ cache.put(candidate, fitness);
+ }
+ return fitness;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isNatural()
+ {
+ return delegate.isNatural();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java
new file mode 100644
index 0000000..e20c552
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Creates new populations of candidates. For most implementations it
+ * will be easiest just to extend {@link org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory} and
+ * implement the method to generate a single random candidate.
+ * @param <T> The type of evolvable entity created by the factory.
+ * @author Daniel Dyer
+ */
+public interface CandidateFactory<T>
+{
+ /**
+ * Creates an initial population of candidates. If more 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 create.
+ * @param rng The random number generator to use when creating the initial
+ * candidates.
+ * @return An initial population of candidate solutions.
+ */
+ List<T> generateInitialPopulation(int populationSize,
+ Random rng);
+
+ /**
+ * Sometimes it is desirable to seed the initial population with some
+ * known good candidates, or partial solutions, in order to provide some
+ * hints for the evolution process. This method generates an initial
+ * population, seeded with some initial candidates. If the number of seed
+ * candidates is less than the required population size, the factory should
+ * generate additional candidates to fill the remaining spaces in the
+ * population.
+ * @param populationSize The size of the initial population.
+ * @param seedCandidates Candidates to seed the population with. Number
+ * of candidates must be no bigger than the population size.
+ * @param rng The random number generator to use when creating additional
+ * candidates to fill the population when the number of seed candidates is
+ * insufficient. This can be null if and only if the number of seed
+ * candidates provided is sufficient to fully populate the initial population.
+ * @return An initial population of candidate solutions, including the
+ * specified seed candidates.
+ */
+ List<T> generateInitialPopulation(int populationSize,
+ Collection<T> seedCandidates,
+ Random rng);
+
+ /**
+ * Randomly create a single candidate solution.
+ * @param rng The random number generator to use when creating the random
+ * candidate.
+ * @return A randomly-initialised candidate.
+ */
+ T generateRandomCandidate(Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java
new file mode 100644
index 0000000..904ebd8
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java
@@ -0,0 +1,109 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Immutable wrapper class for associating a candidate solution with its
+ * fitness score.
+ * @author Daniel Dyer.
+ * @param <T> The candidate type.
+ */
+public final class EvaluatedCandidate<T> implements Comparable<EvaluatedCandidate<T>>
+{
+ private final T candidate;
+ private final double fitness;
+
+
+ /**
+ * @param candidate The evolved candidate.
+ * @param fitness The candidates fitness score.
+ */
+ public EvaluatedCandidate(T candidate, double fitness)
+ {
+ if (fitness < 0)
+ {
+ throw new IllegalArgumentException("Fitness score must be greater than or equal to zero.");
+ }
+ this.candidate = candidate;
+ this.fitness = fitness;
+ }
+
+
+ /**
+ * @return The evolved candidate solution.
+ */
+ public T getCandidate()
+ {
+ return candidate;
+ }
+
+
+ /**
+ * @return The fitness score for the associated candidate.
+ */
+ public double getFitness()
+ {
+ return fitness;
+ }
+
+
+ /**
+ * Compares this candidate's fitness score with that of the specified
+ * candidate.
+ * @param evaluatedCandidate The candidate to compare scores with.
+ * @return -1, 0 or 1 if this candidate's score is less than, equal to,
+ * or greater than that of the specified candidate. The comparison applies
+ * to the raw numerical score and does not consider whether that score is
+ * a natural fitness score or not.
+ */
+ public int compareTo(EvaluatedCandidate<T> evaluatedCandidate)
+ {
+ return Double.compare(fitness, evaluatedCandidate.getFitness());
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #compareTo(EvaluatedCandidate)}.
+ * @param o The object to check for equality.
+ * @return true If this object is logically equivalent to {code o}.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ final EvaluatedCandidate<?> that = (EvaluatedCandidate<?>) o;
+ return Double.compare(that.getFitness(), fitness) == 0;
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #equals(Object)}.
+ * @return This object's hash code.
+ */
+ @Override
+ public int hashCode()
+ {
+ final long temp = fitness == 0.0d ? 0L : Double.doubleToLongBits(fitness);
+ return (int) (temp ^ (temp >>> 32));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java
new file mode 100644
index 0000000..4f53a69
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java
@@ -0,0 +1,163 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Operations for classes that provide an evolution implementation.
+ * @param <T> The type of entity evolved by the evolution engine.
+ * @author Daniel Dyer
+ */
+public interface EvolutionEngine<T>
+{
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return the fittest candidate from the final generation. To return the
+ * entire population rather than just the fittest candidate, use the
+ * {@link #evolvePopulation(int, int, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ */
+ T evolve(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return the fittest candidate from the final generation. To return the
+ * entire population rather than just the fittest candidate, use the
+ * {@link #evolvePopulation(int, int, Collection, TerminationCondition[])}
+ * method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param seedCandidates A set of candidates to seed the population with. The size of
+ * this collection must be no greater than the specified population size.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int,int,TerminationCondition[])
+ */
+ T evolve(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return all of the candidates from the final generation. To return just the
+ * fittest candidate rather than the entire population, use the
+ * {@link #evolve(int, int, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ * @see #evolvePopulation(int, int, Collection, TerminationCondition[])
+ */
+ List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return all of the candidates from the final generation. To return just the
+ * fittest candidate rather than the entire population, use the
+ * {@link #evolve(int, int, Collection, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param seedCandidates A set of candidates to seed the population with. The size of
+ * this collection must be no greater than the specified population size.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ * @see #evolvePopulation(int, int, Collection, TerminationCondition[])
+ */
+ List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Adds a listener to receive status updates on the evolution progress.
+ * @param observer An evolution observer call-back.
+ * @see #removeEvolutionObserver(EvolutionObserver)
+ */
+ void addEvolutionObserver(EvolutionObserver<? super T> observer);
+
+
+ /**
+ * Removes an evolution progress listener.
+ * @param observer An evolution observer call-back.
+ * @see #addEvolutionObserver(EvolutionObserver)
+ */
+ void removeEvolutionObserver(EvolutionObserver<? super T> observer);
+
+
+ /**
+ * Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the evolution engine. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.
+ *
+ * If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.
+ *
+ * If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.
+ *
+ * @throws IllegalStateException If this method is invoked on an evolution engine before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ List<TerminationCondition> getSatisfiedTerminationConditions();
+} \ No newline at end of file
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java
new file mode 100644
index 0000000..fe3a165
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java
@@ -0,0 +1,57 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * <p>Call-back interface so that programs can monitor the state of a
+ * long-running evolutionary algorithm.</p>
+ * <p>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.</p>
+ * <p><strong>Using an EvolutionObserver to update a Swing GUI:</strong>
+ * 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.</p>
+ * <p>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.</p>
+ * <p>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.</p>
+ * @param <T> 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<T>
+{
+ /**
+ * Invoked when the state of the population has changed (typically
+ * at the end of a generation).
+ * @param data Statistics about the state of the current generation.
+ */
+ void populationUpdate(PopulationData<? extends T> data);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java
new file mode 100644
index 0000000..c639b2c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * <p>General purpose engine for implementing Evolution Strategies. Both (μ+λ) and (μ,λ)
+ * strategies are supported (choose which to use by setting the boolean constructor parameter).</p>
+ *
+ * <p>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.</p>
+
+ * @param <T> The type of entity that is to be evolved.
+ * @see GenerationalEvolutionEngine
+ * @see SteadyStateEvolutionEngine
+ * @author Daniel Dyer
+ */
+public class EvolutionStrategyEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final boolean plusSelection;
+ private final int offspringMultiplier;
+
+
+ /**
+ * Creates a new engine for an evolution strategy.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param plusSelection If true this object implements a (μ+λ) evolution strategy rather
+ * than (μ,λ). With plus-selection the parents are eligible for survival. With
+ * comma-selection only the offspring survive.
+ * @param offspringMultiplier How many offspring to create for each member of the parent
+ * population. This parameter effectively defines a multiplier for μ that gives λ.
+ * We define λ in this indirect way because we don't know the value of μ until
+ * it is passed as an argument to one of the evolve methods.
+ * For a 1+1 ES this parameter would be set to one. For other evolution strategies
+ * a higher value might be better. Eiben & Smith suggest 7 as a good value.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public EvolutionStrategyEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ boolean plusSelection,
+ int offspringMultiplier,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.evolutionScheme = evolutionScheme;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.plusSelection = plusSelection;
+ this.offspringMultiplier = offspringMultiplier;
+ }
+
+
+ /**
+ * This method performs a single step/iteration of the evolutionary process.
+ * @param evaluatedPopulation The population at the beginning of the process.
+ * @param eliteCount Ignored by evolution strategies. Elitism is implicit in a (μ+λ)
+ * ES and undesirable for a (μ,λ) ES.
+ * @param rng A source of randomness.
+ * @return The updated population after the evolution strategy has advanced.
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> 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.";
+
+ // Select candidates that will be operated on to create the offspring.
+ int offspringCount = offspringMultiplier * evaluatedPopulation.size();
+ List<T> parents = new ArrayList<T>(offspringCount);
+ for (int i = 0; i < offspringCount; i++)
+ {
+ parents.add(evaluatedPopulation.get(rng.nextInt(evaluatedPopulation.size())).getCandidate());
+ }
+
+ // Then evolve the parents.
+ List<T> offspring = evolutionScheme.apply(parents, rng);
+
+ List<EvaluatedCandidate<T>> evaluatedOffspring = evaluatePopulation(offspring);
+ if (plusSelection) // Plus-selection means parents are considered for survival as well as offspring.
+ {
+ evaluatedOffspring.addAll(evaluatedPopulation);
+ }
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedOffspring, fitnessEvaluator.isNatural());
+ // Retain the fittest of the candidates that are eligible for survival.
+ return evaluatedOffspring.subList(0, evaluatedPopulation.size());
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java
new file mode 100644
index 0000000..2838e31
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java
@@ -0,0 +1,129 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+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.
+ * @author Daniel Dyer
+ */
+public final class EvolutionUtils
+{
+ private EvolutionUtils()
+ {
+ // Prevents instantiation of utility class.
+ }
+
+
+ /**
+ * Given data about the current population and a set of termination conditions, determines
+ * whether or not the evolution should continue.
+ * @param data The current state of the population.
+ * @param conditions One or more termination conditions. The evolution should not continue if
+ * any of these is satisfied.
+ * @param <T> The type of entity that is being evolved.
+ * @return A list of satisfied termination conditions if the evolution has reached some
+ * pre-specified state, an empty list if the evolution should stop because of a thread
+ * interruption, or null if the evolution should continue.
+ */
+ public static <T> List<TerminationCondition> shouldContinue(PopulationData<T> data,
+ TerminationCondition... conditions)
+ {
+ // If the thread has been interrupted, we should abort and return whatever
+ // result we currently have.
+ if (Thread.currentThread().isInterrupted())
+ {
+ return Collections.emptyList();
+ }
+ // Otherwise check the termination conditions for the evolution.
+ List<TerminationCondition> satisfiedConditions = new LinkedList<TerminationCondition>();
+ for (TerminationCondition condition : conditions)
+ {
+ if (condition.shouldTerminate(data))
+ {
+ satisfiedConditions.add(condition);
+ }
+ }
+ return satisfiedConditions.isEmpty() ? null : satisfiedConditions;
+ }
+
+
+ /**
+ * Sorts an evaluated population in descending order of fitness
+ * (descending order of fitness score for natural scores, ascending
+ * order of scores for non-natural scores).
+ *
+ * @param evaluatedPopulation The population to be sorted (in-place).
+ * @param naturalFitness True if higher fitness scores mean fitter individuals, false otherwise.
+ * @param <T> The type of entity that is being evolved.
+ */
+ public static <T> void sortEvaluatedPopulation(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ boolean naturalFitness)
+ {
+ // Sort candidates in descending order according to fitness.
+ if (naturalFitness) // Descending values for natural fitness.
+ {
+ Collections.sort(evaluatedPopulation, Collections.reverseOrder());
+ }
+ else // Ascending values for non-natural fitness.
+ {
+ Collections.sort(evaluatedPopulation);
+ }
+ }
+
+
+
+ /**
+ * Gets data about the current population, including the fittest candidate
+ * and statistics about the population as a whole.
+ *
+ * @param evaluatedPopulation Population of candidate solutions with their
+ * associated fitness scores.
+ * @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 <T> The type of entity that is being evolved.
+ * @return Statistics about the current generation of evolved individuals.
+ */
+ public static <T> PopulationData<T> getPopulationData(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ boolean naturalFitness,
+ int eliteCount,
+ int iterationNumber,
+ long startTime)
+ {
+ DataSet stats = new DataSet(evaluatedPopulation.size());
+ for (EvaluatedCandidate<T> candidate : evaluatedPopulation)
+ {
+ stats.addValue(candidate.getFitness());
+ }
+ return new PopulationData<T>(evaluatedPopulation.get(0).getCandidate(),
+ evaluatedPopulation.get(0).getFitness(),
+ stats.getArithmeticMean(),
+ stats.getStandardDeviation(),
+ naturalFitness,
+ stats.getSize(),
+ eliteCount,
+ iterationNumber,
+ System.currentTimeMillis() - startTime);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java
new file mode 100644
index 0000000..e733f06
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java
@@ -0,0 +1,63 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * <p>An evolutionary operator is a function that takes a population of
+ * candidates as an argument and returns a new population that is the
+ * result of applying a transformation to the original population.</p>
+ * <p><strong>An implementation of this class must not modify any of
+ * the selected candidate objects passed in.</strong> Doing so will
+ * affect the correct operation of the {@link EvolutionEngine}. Instead
+ * the operator should create and return new candidate objects. The
+ * operator is not required to create copies of unmodified individuals
+ * (for efficiency these may be returned directly).</p>
+ * @param <T> The type of evolvable entity that this operator accepts.
+ * @author Daniel Dyer
+ */
+public interface EvolutionaryOperator<T>
+{
+ /**
+ * <p>Apply the operation to each entry in the list of selected
+ * candidates. It is important to note that this method operates on
+ * the list of candidates returned by the selection strategy and not
+ * on the current population. Each entry in the list (not each
+ * individual - the list may contain the same individual more than
+ * once) must be operated on exactly once.</p>
+ *
+ * <p>Implementing classes should not assume any particular ordering
+ * (or lack of ordering) for the selection. If ordering or
+ * shuffling is required, it should be performed by the implementing
+ * class. The implementation should not re-order the list provided
+ * but instead should make a copy of the list and re-order that.
+ * The ordering of the selection should be totally irrelevant for
+ * operators that process each candidate in isolation, such as mutation.
+ * It should only be an issue for operators, such as cross-over, that
+ * deal with multiple candidates in a single operation.</p>
+ * <p><strong>The operator must not modify any of the candidates passed
+ * in</strong>. Instead it should return a list that contains evolved
+ * copies of those candidates (umodified candidates can be included in
+ * the results without having to be copied).</p>
+ * @param selectedCandidates The individuals to evolve.
+ * @param rng A source of randomness for stochastic operators (most
+ * operators will be stochastic).
+ * @return The evolved individuals.
+ */
+ List<T> apply(List<T> selectedCandidates, Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java
new file mode 100644
index 0000000..9c6b410
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.uncommons.util.concurrent.ConfigurableThreadFactory;
+import org.uncommons.util.id.IDSource;
+import org.uncommons.util.id.IntSequenceIDSource;
+import org.uncommons.util.id.StringPrefixIDSource;
+
+/**
+ * This is the class that actually runs the fitness evaluation tasks created by a
+ * {@link EvolutionEngine}. This responsibility is abstracted away from
+ * the evolution engine to permit the possibility of creating multiple instances
+ * across several machines, all fed by a single shared work queue, using Terracotta
+ * (http://www.terracotta.org) or similar.
+ * @author Daniel Dyer
+ */
+public class FitnessEvaluationWorker
+{
+ // Provide each worker instance with a unique name with which to prefix its threads.
+ private static final IDSource<String> WORKER_ID_SOURCE = new StringPrefixIDSource("FitnessEvaluationWorker",
+ new IntSequenceIDSource());
+
+ /**
+ * Share this field to use Terracotta to distribute fitness evaluations.
+ */
+ private final LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
+
+
+ /**
+ * Thread pool that performs concurrent fitness evaluations.
+ */
+ private final ThreadPoolExecutor executor;
+
+
+ /**
+ * Creates a FitnessEvaluationWorker that uses daemon threads.
+ */
+ FitnessEvaluationWorker()
+ {
+ this(true);
+ }
+
+
+ /**
+ * @param daemonWorkerThreads If true, any worker threads created will be daemon threads.
+ */
+ private FitnessEvaluationWorker(boolean daemonWorkerThreads)
+ {
+ ConfigurableThreadFactory threadFactory = new ConfigurableThreadFactory(WORKER_ID_SOURCE.nextID(),
+ Thread.NORM_PRIORITY,
+ daemonWorkerThreads);
+ this.executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
+ Runtime.getRuntime().availableProcessors(),
+ 60,
+ TimeUnit.SECONDS,
+ workQueue,
+ threadFactory);
+ executor.prestartAllCoreThreads();
+ }
+
+
+ public <T> Future<EvaluatedCandidate<T>> submit(FitnessEvalutationTask<T> task)
+ {
+ return executor.submit(task);
+ }
+
+
+ /**
+ * Entry-point for running this class standalone, as an additional node for fitness evaluations.
+ * If this method is invoked without using Terracotta (or similar) to share the work queue, the
+ * program will do nothing.
+ * @param args Program arguments, should be empty.
+ */
+ public static void main(String[] args)
+ {
+ // The program will not exit immediately upon completion of the main method because
+ // the worker is configured to use non-daemon threads that keep the JVM alive.
+ new FitnessEvaluationWorker(false);
+ }
+
+
+ /**
+ * A FitnessWorker cannot be garbage-collected if its thread pool has not been shutdown.
+ * This method, invoked on garabage collection (or maybe not at all), shuts down the thread
+ * pool so that the threads can be released.
+ * @throws Throwable Any exception or error that occurs during finalisation.
+ */
+ @Override
+ protected void finalize() throws Throwable
+ {
+ executor.shutdown();
+ super.finalize();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java
new file mode 100644
index 0000000..9a7c791
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java
@@ -0,0 +1,78 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * Calculates the fitness score of a given candidate of the appropriate type.
+ * Fitness evaluations may be executed concurrently and therefore any access
+ * to mutable shared state should be properly synchronised.
+ * @param <T> The type of evolvable entity that can be evaluated.
+ * @author Daniel Dyer
+ */
+public interface FitnessEvaluator<T>
+{
+ /**
+ * Calculates a fitness score for the given candidate. Whether
+ * a higher score indicates a fitter candidate or not depends on
+ * whether the fitness scores are natural (see {@link #isNatural}).
+ * This method must always return a value greater than or equal to
+ * zero. Framework behaviour is undefined for negative fitness scores.
+ * @param candidate The candidate solution to calculate fitness for.
+ * @param population The entire population. This will include the
+ * specified candidate. This is provided for fitness evaluators that
+ * evaluate individuals in the context of the population that they are
+ * part of (e.g. a program that evolves game-playing strategies may wish
+ * to play each strategy against each of the others). This parameter
+ * can be ignored by simple fitness evaluators. When iterating
+ * over the population, a simple reference equality check (==) can be
+ * used to identify which member of the population is the specified
+ * candidate.
+ * @return The fitness score for the specified candidate. Must always be
+ * a non-negative value regardless of natural or non-natural evaluation is
+ * being used.
+ */
+ double getFitness(T candidate,
+ List<? extends T> population);
+
+ /**
+ * <p>Specifies whether this evaluator generates <i>natural</i> fitness
+ * scores or not.</p>
+ * <p>Natural fitness scores are those in which the fittest
+ * individual in a population has the highest fitness value. In this
+ * case the algorithm is attempting to maximise fitness scores.
+ * There need not be a specified maximum possible value.</p>
+ * <p>In contrast, <i>non-natural</i> fitness evaluation results in fitter
+ * individuals being assigned lower scores than weaker individuals.
+ * In the case of non-natural fitness, the algorithm is attempting to
+ * minimise fitness scores.</p>
+ * <p>An example of a situation in which non-natural fitness scores are
+ * preferable is when the fitness corresponds to a cost and the algorithm
+ * is attempting to minimise that cost.</p>
+ * <p>The terminology of <i>natural</i> and <i>non-natural</i> fitness scores
+ * is introduced by the Watchmaker Framework to describe the two types of fitness
+ * scoring that exist within the framework. It does not correspond to either
+ * <i>standardised fitness</i> or <i>normalised fitness</i> in the EA
+ * literature. Standardised fitness evaluation generates non-natural
+ * scores with a score of zero corresponding to the best possible fitness.
+ * Normalised fitness evaluation is similar to standardised fitness but
+ * with the scores adjusted to fall within the range 0 - 1.</p>
+ * @return True if a high fitness score means a fitter candidate
+ * or false if a low fitness score means a fitter candidate.
+ */
+ boolean isNatural();
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java
new file mode 100644
index 0000000..6f3f827
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java
@@ -0,0 +1,55 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Callable task for performing parallel fitness evaluations.
+ * @param <T> The type of entity for which fitness is calculated.
+ * @author Daniel Dyer
+ */
+class FitnessEvalutationTask<T> implements Callable<EvaluatedCandidate<T>>
+{
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final T candidate;
+ private final List<T> population;
+
+ /**
+ * Creates a task for performing fitness evaluations.
+ * @param fitnessEvaluator The fitness function used to determine candidate fitness.
+ * @param candidate The candidate to evaluate.
+ * @param population The entire current population. This will include all
+ * of the candidates to evaluate along with any other individuals that are
+ * not being evaluated by this task.
+ */
+ FitnessEvalutationTask(FitnessEvaluator<? super T> fitnessEvaluator,
+ T candidate,
+ List<T> population)
+ {
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.candidate = candidate;
+ this.population = population;
+ }
+
+
+ public EvaluatedCandidate<T> call()
+ {
+ return new EvaluatedCandidate<T>(candidate,
+ fitnessEvaluator.getFitness(candidate, population));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java
new file mode 100644
index 0000000..3e43915
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java
@@ -0,0 +1,132 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.interactive.InteractiveSelection;
+
+/**
+ * <p>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.</p>
+ *
+ * <p>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).</p>
+ *
+ * <p>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.</p>
+ *
+ * @param <T> The type of entity that is to be evolved.
+ * @see SteadyStateEvolutionEngine
+ * @see EvolutionStrategyEngine
+ * @author Daniel Dyer
+ */
+public class GenerationalEvolutionEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final SelectionStrategy<? super T> selectionStrategy;
+
+ /**
+ * Creates a new evolution engine by specifying the various components required by
+ * a generational evolutionary algorithm.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param selectionStrategy A strategy for selecting which candidates survive to
+ * be evolved.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public GenerationalEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.evolutionScheme = evolutionScheme;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.selectionStrategy = selectionStrategy;
+ }
+
+
+ /**
+ * Creates a new evolution engine for an interactive evolutionary algorithm. It
+ * is not necessary to specify a fitness evaluator for interactive evolution.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param selectionStrategy Interactive selection strategy configured with appropriate
+ * console.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public GenerationalEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ InteractiveSelection<T> selectionStrategy,
+ Random rng)
+ {
+ this(candidateFactory,
+ evolutionScheme,
+ new NullFitnessEvaluator(), // No fitness evaluations to perform.
+ selectionStrategy,
+ rng);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng)
+ {
+ List<T> population = new ArrayList<T>(evaluatedPopulation.size());
+
+ // First perform any elitist selection.
+ List<T> elite = new ArrayList<T>(eliteCount);
+ Iterator<EvaluatedCandidate<T>> iterator = evaluatedPopulation.iterator();
+ while (elite.size() < eliteCount)
+ {
+ elite.add(iterator.next().getCandidate());
+ }
+ // Then select candidates that will be operated on to create the evolved
+ // portion of the next generation.
+ population.addAll(selectionStrategy.select(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ evaluatedPopulation.size() - eliteCount,
+ rng));
+ // Then evolve the population.
+ population = evolutionScheme.apply(population, rng);
+ // When the evolution is finished, add the elite to the population.
+ population.addAll(elite);
+ return evaluatePopulation(population);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java
new file mode 100644
index 0000000..0fe0d3a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * 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<Object>
+{
+ /**
+ * Returns a score of zero, regardless of the candidate being evaluated.
+ * @param candidate The individual to evaluate.
+ * @param population {@inheritDoc}
+ * @return Zero.
+ */
+ public double getFitness(Object candidate,
+ List<?> population)
+ {
+ return 0;
+ }
+
+ /**
+ * Always returns true. However, the return value of this method is
+ * irrelevant since no meaningful fitness scores are produced.
+ * @return True.
+ */
+ public boolean isNatural()
+ {
+ return true;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java
new file mode 100644
index 0000000..a04f5bb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java
@@ -0,0 +1,165 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Immutable data object containing statistics about the state of
+ * an evolved population and a reference to the fittest candidate
+ * solution in the population.
+ * @param <T> The type of evolved entity present in the population
+ * that this data describes.
+ * @see EvolutionObserver
+ * @author Daniel Dyer
+ */
+public final class PopulationData<T>
+{
+ private final T bestCandidate;
+ private final double bestCandidateFitness;
+ private final double meanFitness;
+ private final double fitnessStandardDeviation;
+ private final boolean naturalFitness;
+ private final int populationSize;
+ private final int eliteCount;
+ private final int generationNumber;
+ private final long elapsedTime;
+
+ /**
+ * @param bestCandidate The fittest candidate present in the population.
+ * @param bestCandidateFitness The fitness score for the fittest candidate
+ * in the population.
+ * @param meanFitness The arithmetic mean of fitness scores for each member
+ * of the population.
+ * @param fitnessStandardDeviation A measure of the variation in fitness
+ * scores.
+ * @param naturalFitness True if higher fitness scores are better, false
+ * otherwise.
+ * @param populationSize The number of individuals in the population.
+ * @param eliteCount The number of candidates preserved via elitism.
+ * @param generationNumber The (zero-based) number of the last generation
+ * that was processed.
+ * @param elapsedTime The number of milliseconds since the start of the
+ */
+ public PopulationData(T bestCandidate,
+ double bestCandidateFitness,
+ double meanFitness,
+ double fitnessStandardDeviation,
+ boolean naturalFitness,
+ int populationSize,
+ int eliteCount,
+ int generationNumber,
+ long elapsedTime)
+ {
+ this.bestCandidate = bestCandidate;
+ this.bestCandidateFitness = bestCandidateFitness;
+ this.meanFitness = meanFitness;
+ this.fitnessStandardDeviation = fitnessStandardDeviation;
+ this.naturalFitness = naturalFitness;
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.generationNumber = generationNumber;
+ this.elapsedTime = elapsedTime;
+ }
+
+
+ /**
+ * @return The fittest candidate present in the population.
+ * @see #getBestCandidateFitness()
+ */
+ public T getBestCandidate()
+ {
+ return bestCandidate;
+ }
+
+
+ /**
+ * @return The fitness score of the fittest candidate.
+ * @see #getBestCandidateFitness()
+ */
+ public double getBestCandidateFitness()
+ {
+ return bestCandidateFitness;
+ }
+
+
+ /**
+ * Returns the average fitness score of population members.
+ * @return The arithmetic mean fitness of individual candidates.
+ */
+ public double getMeanFitness()
+ {
+ return meanFitness;
+ }
+
+
+ /**
+ * Returns a statistical measure of variation in fitness scores within
+ * the population.
+ * @return Population standard deviation for fitness scores.
+ */
+ public double getFitnessStandardDeviation()
+ {
+ return fitnessStandardDeviation;
+ }
+
+
+ /**
+ * Indicates whether the fitness scores are natural or non-natural.
+ * @return True if higher fitness scores indicate fitter individuals, false
+ * otherwise.
+ */
+ public boolean isNaturalFitness()
+ {
+ return naturalFitness;
+ }
+
+
+ /**
+ * @return The number of individuals in the current population.
+ */
+ public int getPopulationSize()
+ {
+ return populationSize;
+ }
+
+
+ /**
+ * @return The number of candidates preserved via elitism.
+ */
+ public int getEliteCount()
+ {
+ return eliteCount;
+ }
+
+
+ /**
+ * @return The number of this generation (zero-based).
+ */
+ public int getGenerationNumber()
+ {
+ return generationNumber;
+ }
+
+
+ /**
+ * Returns the amount of time (in milliseconds) since the
+ * start of the evolutionary algorithm's execution.
+ * @return How long (in milliseconds) the algorithm has been running.
+ */
+ public long getElapsedTime()
+ {
+ return elapsedTime;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java
new file mode 100644
index 0000000..a91f0fa
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Strategy interface for "natural" selection.
+ * @param <T> The type of evolved entity that we are selecting.
+ * @author Daniel Dyer
+ */
+public interface SelectionStrategy<T>
+{
+ /**
+ * <p>Select the specified number of candidates from the population.
+ * Implementations may assume that the population is sorted in descending
+ * order according to fitness (so the fittest individual is the first item
+ * in the list).</p>
+ * <p>It is an error to call this method with an empty or null population.</p>
+ * @param <S> The type of evolved entity that we are selecting, a sub-type of T.
+ * @param population The population from which to select.
+ * @param naturalFitnessScores Whether higher fitness values represent fitter
+ * individuals or not.
+ * @param selectionSize The number of individual selections to make (not necessarily
+ * the number of distinct candidates to select, since the same individual may
+ * potentially be selected more than once).
+ * @param rng Source of randomness for stochastic selection strategies.
+ * @return A list containing the selected candidates. Some individual canidates may
+ * potentially have been selected multiple times.
+ */
+ <S extends T> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java
new file mode 100644
index 0000000..3321db8
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java
@@ -0,0 +1,147 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+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.
+ *
+ * @param <T> The type of entity that is to be evolved.
+ * @see GenerationalEvolutionEngine
+ * @see EvolutionStrategyEngine
+ * @author Daniel Dyer
+ */
+public class SteadyStateEvolutionEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final SelectionStrategy<? super T> selectionStrategy;
+ 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.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The evolutionary operator that modifies the population. The
+ * number of candidates used as input is controlled by the {@code selectionSize} parameter.
+ * The number of candidates that will be outputted depends on the implementation. Typically
+ * it will be the same as the input size, but this is not necessary. In fact, for steady-state
+ * evolution, it is typical that the output size is always 1, regardless of the input size, so
+ * that only one member of the population is replaced at a time. To acheive this using cross-over
+ * requires a cross-over implementation that returns only one offspring, rather than the normal
+ * two.
+ * @param fitnessEvaluator The fitness function.
+ * @param selectionStrategy The strategy for selecting which candidate(s) will be
+ * the parent(s) when evolving individuals.
+ * @param selectionSize How many parent candidates are required by the evolution scheme.
+ * This controls how many individuals will be provided to the evolutionary operator at
+ * each iteration. If you are just using mutation, this will typically be 1. For
+ * cross-over, two separate parents are required, so this must be set to 2.
+ * @param forceSingleCandidateUpdate Some evolutionary operators, specifically cross-over
+ * operators, generate more than one evolved individual. A true steady-state algorithm will
+ * only replace one individual at a time. Setting this parameter to true forces the evolution
+ * to discard any additional generated offspring so that for each iteration of the algorithm
+ * there is only one updated individual. This allows cross-over operators that were designed
+ * for generational evolutionary algorithms to be reused for steady-state evolution. A more
+ * efficient, but less straightforward, alternative would be to implement a steady-state-specific
+ * cross-over operator that returns only a single evolved individual. Setting this parameter to
+ * false permits multiple candidates to be replaced per iteration, depending on the specifics of
+ * the evolutionary operator(s).
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public SteadyStateEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ int selectionSize,
+ boolean forceSingleCandidateUpdate,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.evolutionScheme = evolutionScheme;
+ this.selectionStrategy = selectionStrategy;
+ this.selectionSize = selectionSize;
+ this.forceSingleCandidateUpdate = forceSingleCandidateUpdate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng)
+ {
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ List<T> selectedCandidates = selectionStrategy.select(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ selectionSize,
+ rng);
+ List<EvaluatedCandidate<T>> offspring = evaluatePopulation(evolutionScheme.apply(selectedCandidates, rng));
+
+ doReplacement(evaluatedPopulation, offspring, eliteCount, rng);
+
+ return evaluatedPopulation;
+ }
+
+
+ /**
+ * Add the offspring to the population, removing the same number of existing individuals to make
+ * space for them.
+ * This method randomly chooses which individuals should be replaced, but it can be over-ridden
+ * in sub-classes if alternative behaviour is required.
+ * @param existingPopulation The full popultation, sorted in descending order of fitness.
+ * @param newCandidates The (unsorted) newly-created individual(s) that should replace existing members
+ * of the population.
+ * @param eliteCount The number of the fittest individuals that should be exempt from being replaced.
+ * @param rng A source of randomness.
+ */
+ protected void doReplacement(List<EvaluatedCandidate<T>> existingPopulation,
+ List<EvaluatedCandidate<T>> newCandidates,
+ int eliteCount,
+ Random rng)
+ {
+ 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)
+ {
+ // 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,
+ newCandidates.get(rng.nextInt(newCandidates.size())));
+ }
+ else
+ {
+ for (EvaluatedCandidate<T> 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);
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java
new file mode 100644
index 0000000..173cc1e
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java
@@ -0,0 +1,32 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Interface for implementing conditions used to terminate evolutionary algorithms.
+ * @author Daniel Dyer
+ */
+public interface TerminationCondition
+{
+ /**
+ * The condition is queried via this method to determine whether or not evolution
+ * should finish at the current point.
+ * @param populationData Information about the current state of evolution. This may
+ * be used to determine whether evolution should continue or not.
+ * @return true if evolution should be terminated, false otherwise.
+ */
+ boolean shouldTerminate(PopulationData<?> populationData);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java
new file mode 100644
index 0000000..5a03985
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java
@@ -0,0 +1,76 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Convenient base class for implementations of
+ * {@link org.uncommons.watchmaker.framework.CandidateFactory}.
+ * @param <T> The type of entity evolved by this engine.
+ * @author Daniel Dyer
+ */
+public abstract class AbstractCandidateFactory<T> implements CandidateFactory<T>
+{
+ /**
+ * 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.
+ * @return A randomly generated initial population of candidate solutions.
+ */
+ public List<T> generateInitialPopulation(int populationSize, Random rng)
+ {
+ List<T> population = new ArrayList<T>(populationSize);
+ for (int i = 0; i < populationSize; i++)
+ {
+ population.add(generateRandomCandidate(rng));
+ }
+ return Collections.unmodifiableList(population);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * If the number of seed candidates is less than the required population
+ * size, the remainder of the population will be generated randomly via
+ * the {@link #generateRandomCandidate(Random)} method.
+ */
+ public List<T> generateInitialPopulation(int populationSize,
+ Collection<T> seedCandidates,
+ Random rng)
+ {
+ if (seedCandidates.size() > populationSize)
+ {
+ throw new IllegalArgumentException("Too many seed candidates for specified population size.");
+ }
+ List<T> population = new ArrayList<T>(populationSize);
+ population.addAll(seedCandidates);
+ for (int i = seedCandidates.size(); i < populationSize; i++)
+ {
+ population.add(generateRandomCandidate(rng));
+ }
+ return Collections.unmodifiableList(population);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java
new file mode 100644
index 0000000..3dd4fc7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java
@@ -0,0 +1,53 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+
+/**
+ * General purpose candidate factory for generating bit strings for
+ * genetic algorithms.
+ * @see BitString
+ * @author Daniel Dyer
+ */
+public class BitStringFactory extends AbstractCandidateFactory<BitString>
+{
+ private final int length;
+
+
+ /**
+ * @param length The length of all bit strings created by this
+ * factory.
+ */
+ public BitStringFactory(int length)
+ {
+ this.length = length;
+ }
+
+
+ /**
+ * Generates a random bit string, with a uniform distribution of
+ * ones and zeroes.
+ * @param rng The source of randomness for setting the bits.
+ * @return A random bit string of the length configured for this
+ * factory.
+ */
+ public BitString generateRandomCandidate(Random rng)
+ {
+ return new BitString(length, rng);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java
new file mode 100644
index 0000000..406b899
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java
@@ -0,0 +1,57 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Generates random candidates from a set of elements. Each candidate is a random
+ * permutation of the full set of elements.
+ * @param <T> The component type of the lists created by this factory.
+ * @author Daniel Dyer
+ */
+public class ListPermutationFactory<T> extends AbstractCandidateFactory<List<T>>
+{
+ private final List<T> elements;
+
+ /**
+ * Creates a factory that creates lists that contain each of the specified
+ * elements exactly once. The ordering of those elements within generated
+ * lists is random.
+ * @param elements The elements to permute.
+ */
+ public ListPermutationFactory(List<T> elements)
+ {
+ this.elements = elements;
+ }
+
+
+ /**
+ * Generates a random permutation from the configured elements.
+ * @param rng A source of randomness used to generate the random
+ * permutation.
+ * @return A random permutation.
+ */
+ public List<T> generateRandomCandidate(Random rng)
+ {
+ List<T> candidate = new ArrayList<T>(elements);
+ Collections.shuffle(candidate, rng);
+ return candidate;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java
new file mode 100644
index 0000000..ab62b59
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java
@@ -0,0 +1,58 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Generates random candidates from a set of elements. Each candidate is a random
+ * permutation of the full set of elements.
+ * @author Daniel Dyer
+ * @param <T> The element type of the arrays created.
+ */
+public class ObjectArrayPermutationFactory<T> extends AbstractCandidateFactory<T[]>
+{
+ private final T[] elements;
+
+ /**
+ * Creates a factory that creates arrays that contain each of the specified
+ * elements exactly once. The ordering of those elements within generated
+ * arrays is random.
+ * @param elements The elements to permute.
+ */
+ public ObjectArrayPermutationFactory(T[] elements)
+ {
+ this.elements = elements.clone();
+ }
+
+
+ /**
+ * Generates a random permutation from the configured elements.
+ * @param rng A source of randomness used to generate the random
+ * permutation.
+ * @return A random permutation.
+ */
+ public T[] generateRandomCandidate(Random rng)
+ {
+ T[] candidate = elements.clone();
+ List<T> list = Arrays.asList(candidate);
+ Collections.shuffle(list, rng);
+ return list.toArray(candidate);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java
new file mode 100644
index 0000000..364e9fd
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java
@@ -0,0 +1,62 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+
+/**
+ * General-purpose candidate factory for EAs that use a fixed-length String encoding.
+ * Generates random strings of a fixed length from a given alphabet.
+ * @author Daniel Dyer
+ */
+public class StringFactory extends AbstractCandidateFactory<String>
+{
+ private final char[] alphabet;
+ private final int stringLength;
+
+ /**
+ * @param alphabet The set of characters that can legally occur within a
+ * string generated by this factory.
+ * @param stringLength The fixed length of all strings generated by this
+ * factory.
+ */
+ public StringFactory(char[] alphabet,
+ int stringLength)
+ {
+ this.alphabet = alphabet.clone();
+ this.stringLength = stringLength;
+ }
+
+
+ /**
+ * Generates a random string of a pre-configured length. Each character
+ * is randomly selected from the pre-configured alphabet. The same
+ * character may appear multiple times and some characters may not appear
+ * at all.
+ * @param rng A source of randomness used to select characters to make up
+ * the string.
+ * @return A randomly generated string.
+ */
+ public String generateRandomCandidate(Random rng)
+ {
+ char[] chars = new char[stringLength];
+ for (int i = 0; i < stringLength; i++)
+ {
+ chars[i] = alphabet[rng.nextInt(alphabet.length)];
+ }
+ return new String(chars);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java
new file mode 100644
index 0000000..7a91d34
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Provides convenient general-purpose {@link org.uncommons.watchmaker.framework.CandidateFactory}
+ * implementations for common candidate representations such as strings, lists and arrays.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.factories;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java
new file mode 100644
index 0000000..80c106b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.List;
+
+/**
+ * A console provides users with a mechanism for interacting with an
+ * evolutionary algorithm.
+ * @param <T> The type of entity that can be presented by this console.
+ * Evolutionary algorithms that evolve a different type can work with
+ * a console via a {@link Renderer} that performs the necessary conversions.
+ * @author Daniel Dyer
+ */
+public interface Console<T>
+{
+ /**
+ * @param renderedEntities A list of the suitably transformed entities
+ * that will be presented to the user for selection.
+ * @return The index of the selected entity.
+ */
+ int select(List<? extends T> renderedEntities);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java
new file mode 100644
index 0000000..36a7883
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java
@@ -0,0 +1,177 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.util.reflection.ReflectionUtils;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Special selection strategy used for interactive evolutionary algorithms.
+ * @param <T> The type of evolved entity that can be selected by this class.
+ * @author Daniel Dyer
+ */
+public class InteractiveSelection<T> implements SelectionStrategy<T>
+{
+ private final Console<?> console;
+ private final Renderer<T, ?> renderer;
+ private final int groupSize;
+ private final int maxSelectionsPerGeneration;
+
+
+ /**
+ * @param <R> The type of object that can be displayed by the specified
+ * console. The specified renderer must be able to map evolved entities
+ * into objects of this type.
+ * @param console The user interface (graphical, textual or other) used
+ * to present a selection choice to the user.
+ * @param renderer A renderer used to map the evolved entities to objects
+ * that can be processed by the supplied console.
+ * @param groupSize The number of candidates to present to the user at
+ * once (the user selects one from this number).
+ * @param maxSelectionsPerGeneration The maximum number of selections that
+ * the user will be asked to make for each generation of the evolutionary
+ * algorithm. If this number is lower than the required selection size,
+ * the user's selections will be repeated to make up the shortfall. The
+ * purpose of this setting is two-fold. Firstly it minimises user fatigue.
+ * Secondly, it can be used to increase selection pressure. In the extreme
+ * case, a setting of 1 will ensure that members of the subsequent generation
+ * are all descended from a single parent.
+ */
+ public <R> InteractiveSelection(Console<R> console,
+ Renderer<T, R> renderer,
+ int groupSize,
+ int maxSelectionsPerGeneration)
+ {
+ if (groupSize < 2)
+ {
+ throw new IllegalArgumentException("Group size must be at least 2.");
+ }
+ if (maxSelectionsPerGeneration < 1)
+ {
+ throw new IllegalArgumentException("Maximum selections must be 1 or more.");
+ }
+ this.console = console;
+ this.renderer = renderer;
+ this.groupSize = groupSize;
+ this.maxSelectionsPerGeneration = maxSelectionsPerGeneration;
+ }
+
+
+ /**
+ * @param console The user interface (graphical, textual or other) used
+ * to present a selection choice to the user.
+ * @param groupSize The number of candidates to present to the user at
+ * once (the user selects one from this number).
+ * @param maxSelectionsPerGeneration The maximum number of selections that
+ * the user will be asked to make for each generation of the evolutionary
+ * algorithm. If this number is lower than the required selection size,
+ * the user's selections will be repeated to make up the shortfall. The
+ * purpose of this setting is two-fold. Firstly it minimises user fatigue.
+ * Secondly, it can be used to increase selection pressure. In the extreme
+ * case, a setting of 1 will ensure that members of the subsequent generation
+ * are all descended from a single parent.
+ */
+ public InteractiveSelection(Console<T> console,
+ int groupSize,
+ int maxSelectionsPerGeneration)
+ {
+ this(console, new NoOpRenderer<T>(), groupSize, maxSelectionsPerGeneration);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S extends T> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ if (population.size() < groupSize)
+ {
+ throw new IllegalArgumentException("Population is too small for selection group size of " + groupSize);
+ }
+
+ int selectionCount = Math.min(selectionSize, maxSelectionsPerGeneration);
+ List<S> selection = new ArrayList<S>(selectionCount);
+ for (int i = 0; i < selectionCount; i++)
+ {
+ // Pick candidates at random (without replacement).
+ List<S> group = new ArrayList<S>(groupSize);
+ List<EvaluatedCandidate<S>> candidates = new ArrayList<EvaluatedCandidate<S>>(population);
+ Collections.shuffle(candidates);
+ for (int j = 0; j < groupSize; j++)
+ {
+ group.add(candidates.get(j).getCandidate());
+ }
+ // Get the user to pick which one should survive to reproduce.
+ selection.add(select(group));
+ }
+
+ // If the selection is not big enough, extend it by randomly duplicating some
+ // of the selections.
+ if (selectionCount < selectionSize)
+ {
+ List<S> extendedSelection = new ArrayList<S>(selectionSize);
+ extendedSelection.addAll(selection);
+ for (int i = 0; i < selectionSize - selectionCount; i++)
+ {
+ extendedSelection.add(selection.get(selectionCount == 1 ? 0 : rng.nextInt(selectionCount)));
+ }
+ return extendedSelection;
+ }
+ else
+ {
+ return selection;
+ }
+ }
+
+
+ private <S extends T> S select(List<S> candidates)
+ {
+ List<Object> renderedCandidates = new ArrayList<Object>(candidates.size());
+ for (S candidate : candidates)
+ {
+ renderedCandidates.add(renderer.render(candidate));
+ }
+ Method consoleSelectMethod = ReflectionUtils.findKnownMethod(Console.class,
+ "select",
+ List.class);
+ Integer selection = ReflectionUtils.invokeUnchecked(consoleSelectMethod,
+ console,
+ renderedCandidates);
+ return candidates.get(selection);
+ }
+
+
+ /**
+ * Renderer that does nothing. Used when the console already supports the
+ * evolved type.
+ */
+ private static final class NoOpRenderer<T> implements Renderer<T, T>
+ {
+ public T render(T entity)
+ {
+ return entity;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java
new file mode 100644
index 0000000..9360763
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+/**
+ * Maps objects of one type to objects of a different type. For example,
+ * this class could be used to render dates as Strings or to render arrays
+ * as GUI list components.
+ * @param <T> The input type for the renderer.
+ * @param <S> The output type for the renderer.
+ * @author Daniel Dyer
+ */
+public interface Renderer<T, S>
+{
+ /**
+ * Renders an object of one type as an instance of another. For example,
+ * if the generic types of this renderer are Date and String, this method
+ * would return a String representation of a Date.
+ * @param entity An object to render as a different type.
+ * @return A rendering of the parameter.
+ */
+ S render(T entity);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java
new file mode 100644
index 0000000..b8f669a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java
@@ -0,0 +1,73 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.lang.reflect.Method;
+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.
+ * @param <T> The input type for the renderer.
+ * @param <S> The output type for the renderer.
+ * @author Daniel Dyer
+ */
+public class RendererAdapter<T, S> implements Renderer<T, S>
+{
+ private final Renderer<T, ?> renderer1;
+ private final Renderer<?, S> renderer2;
+
+
+ /**
+ * Creates an adapter that feeds the output of renderer1 into renderer2.
+ * @param <R> The intermediate type when transforming objects of type T to
+ * objects of type S.
+ * @param renderer1 A renderer that will translate an object of the input type
+ * (T) into an object of the intermediate type (R).
+ * @param renderer2 A renderer that will translate an object of the intermediate type
+ * (R) into an object of the output type (S).
+ */
+ public <R> RendererAdapter(Renderer<T, ? extends R> renderer1,
+ Renderer<R, S> renderer2)
+ {
+ this.renderer1 = renderer1;
+ this.renderer2 = renderer2;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public S render(T entity)
+ {
+ // This reflection charade is necessary because we can't convince the
+ // compiler that the output of renderer1 is compatible with the input
+ // of renderer2 without exposing a redundant "intermediate" type parameter
+ // in the class definition. I don't what to do that, I'd rather have
+ // the ugliness encapsulated here than complicate code that uses this class.
+ Method renderMethod = ReflectionUtils.findKnownMethod(Renderer.class,
+ "render",
+ Object.class);
+ return ReflectionUtils.<S>invokeUnchecked(renderMethod,
+ renderer2,
+ renderer1.render(entity));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java
new file mode 100644
index 0000000..516de90
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Classes for implementing interactive evolutionary algorithms. In interactive
+ * evolutionary algorithms, user-guided selection is used instead of selection based
+ * on automated fitness evaluations.
+ */
+package org.uncommons.watchmaker.framework.interactive;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java
new file mode 100644
index 0000000..d4cd5d1
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java
@@ -0,0 +1,53 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.EvolutionEngine;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * @author Daniel Dyer
+ */
+class Epoch<T> implements Callable<List<EvaluatedCandidate<T>>>
+{
+ private final EvolutionEngine<T> island;
+ private final int populationSize;
+ private final int eliteCount;
+ private final List<T> seedCandidates;
+ private final TerminationCondition[] terminationConditions;
+
+ Epoch(EvolutionEngine<T> island,
+ int populationSize,
+ int eliteCount,
+ List<T> seedCandidates,
+ TerminationCondition... terminationConditions)
+ {
+ this.island = island;
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.seedCandidates = seedCandidates;
+ this.terminationConditions = terminationConditions;
+ }
+
+
+ public List<EvaluatedCandidate<T>> call() throws Exception
+ {
+ return island.evolvePopulation(populationSize, eliteCount, seedCandidates, terminationConditions);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java
new file mode 100644
index 0000000..300dce9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java
@@ -0,0 +1,375 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.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 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 <T> The type of entity that is to be evolved.
+ * @author Daniel Dyer
+ */
+public class IslandEvolution<T>
+{
+ private final List<EvolutionEngine<T>> islands;
+ private final Migration migration;
+ private final boolean naturalFitness;
+ private final Random rng;
+
+ private final Set<IslandEvolutionObserver<? super T>> observers
+ = new CopyOnWriteArraySet<IslandEvolutionObserver<? super T>>();
+
+ private List<TerminationCondition> satisfiedTerminationConditions;
+
+
+ /**
+ * Create an island system with the specified number of identically-configured islands.
+ * If you want more fine-grained control over the configuration of each island, use the
+ * {@link #IslandEvolution(List, Migration, boolean, Random)} constructor, which accepts
+ * a list of pre-created islands (each is an instance of {@link EvolutionEngine}).
+ * @param islandCount The number of separate islands that will be part of the system.
+ * @param migration A migration strategy for moving individuals between islands at the
+ * end of an epoch.
+ * @param candidateFactory Generates the initial population for each island.
+ * @param evolutionScheme The evolutionary operator, or combination of evolutionary operators,
+ * used on each island.
+ * @param fitnessEvaluator The fitness function used on each island.
+ * @param selectionStrategy The selection strategy used on each island.
+ * @param rng A source of randomness, used by all islands.
+ * @see #IslandEvolution(List, Migration, boolean, Random)
+ */
+ public IslandEvolution(int islandCount,
+ Migration migration,
+ CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ this(createIslands(islandCount,
+ candidateFactory,
+ evolutionScheme,
+ fitnessEvaluator,
+ selectionStrategy,
+ rng),
+ migration,
+ fitnessEvaluator.isNatural(),
+ rng);
+ }
+
+
+ /**
+ * Create an island evolution system from a list of pre-configured islands. This constructor
+ * gives more control over the configuration of individual islands than the alternative constructor.
+ * The other constructor should be used where possible to avoid having to explicitly create each
+ * island.
+ * @param islands A list of pre-configured islands.
+ * @param migration A migration strategy for moving individuals between islands at the
+ * end of an epoch.
+ * @param naturalFitness If true, indicates that higher fitness values mean fitter
+ * individuals. If false, indicates that fitter individuals will have lower scores.
+ * @param rng A source of randomness, used by all islands.
+ * @see #IslandEvolution(int, Migration, CandidateFactory, EvolutionaryOperator, FitnessEvaluator,
+ * SelectionStrategy, Random)
+ */
+ public IslandEvolution(List<EvolutionEngine<T>> islands,
+ Migration migration,
+ boolean naturalFitness,
+ Random rng)
+ {
+ this.islands = islands;
+ this.migration = migration;
+ this.naturalFitness = naturalFitness;
+ this.rng = rng;
+
+ for (int i = 0; i < islands.size(); i++)
+ {
+ final int islandIndex = i;
+ EvolutionEngine<T> island = islands.get(islandIndex);
+ island.addEvolutionObserver(new EvolutionObserver<T>()
+ {
+ public void populationUpdate(PopulationData<? extends T> populationData)
+ {
+ for (IslandEvolutionObserver<? super T> islandObserver : observers)
+ {
+ islandObserver.islandPopulationUpdate(islandIndex, populationData);
+ }
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Helper method used by the constructor to create the individual islands if they haven't
+ * been provided already (via the other constructor).
+ */
+ private static <T> List<EvolutionEngine<T>> createIslands(int islandCount,
+ CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ List<EvolutionEngine<T>> islands = new ArrayList<EvolutionEngine<T>>(islandCount);
+ for (int i = 0; i < islandCount; i++)
+ {
+ GenerationalEvolutionEngine<T> island = new GenerationalEvolutionEngine<T>(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);
+ }
+ return islands;
+ }
+
+
+ /**
+ * <p>Start the evolutionary process on each island and return the fittest candidate so far at the point
+ * any of the termination conditions is satisfied.</p>
+ *
+ * <p><em>If you interrupt the request thread before this method returns, the
+ * method will return prematurely (with the best individual found so far).
+ * After returning in this way, the current thread's interrupted flag
+ * will be set. It is preferable to use an appropritate
+ * {@link org.uncommons.watchmaker.framework.TerminationCondition} rather than interrupting the evolution in
+ * this way.</em></p>
+ *
+ * @param populationSize The population size <em>for each island</em>. Therefore, if you have 5 islands,
+ * setting this parameter to 200 will result in 1000 individuals overall, 200 on each island.
+ * @param eliteCount The number of candidates preserved via elitism <em>on each island</em>. In elitism,
+ * a sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param epochLength The number of generations that make up an epoch. Islands evolve independently for
+ * this number of generations and then migration occurs at the end of the epoch and the next epoch starts.
+ * @param migrantCount The number of individuals that will be migrated from each island at the end of each
+ * epoch.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @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)
+ {
+ ExecutorService threadPool = Executors.newFixedThreadPool(islands.size());
+ List<List<T>> islandPopulations = new ArrayList<List<T>>(islands.size());
+ List<EvaluatedCandidate<T>> evaluatedCombinedPopulation = new ArrayList<EvaluatedCandidate<T>>();
+
+ PopulationData<T> data = null;
+ List<TerminationCondition> satisfiedConditions = null;
+ int currentEpochIndex = 0;
+ long startTime = System.currentTimeMillis();
+ while (satisfiedConditions == null)
+ {
+ List<Callable<List<EvaluatedCandidate<T>>>> islandEpochs = createEpochTasks(populationSize,
+ eliteCount,
+ epochLength,
+ islandPopulations);
+ try
+ {
+ List<Future<List<EvaluatedCandidate<T>>>> futures = threadPool.invokeAll(islandEpochs);
+
+ evaluatedCombinedPopulation.clear();
+ List<List<EvaluatedCandidate<T>>> evaluatedPopulations
+ = new ArrayList<List<EvaluatedCandidate<T>>>(islands.size());
+ for (Future<List<EvaluatedCandidate<T>>> future : futures)
+ {
+ List<EvaluatedCandidate<T>> evaluatedIslandPopulation = future.get();
+ evaluatedCombinedPopulation.addAll(evaluatedIslandPopulation);
+ evaluatedPopulations.add(evaluatedIslandPopulation);
+ }
+
+ migration.migrate(evaluatedPopulations, migrantCount, rng);
+
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedCombinedPopulation, naturalFitness);
+ data = EvolutionUtils.getPopulationData(evaluatedCombinedPopulation,
+ naturalFitness,
+ eliteCount,
+ currentEpochIndex,
+ startTime);
+ notifyPopulationChange(data);
+
+ islandPopulations.clear();
+ for (List<EvaluatedCandidate<T>> evaluatedPopulation : evaluatedPopulations)
+ {
+ islandPopulations.add(toCandidateList(evaluatedPopulation));
+ }
+ ++currentEpochIndex;
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (ExecutionException ex)
+ {
+ throw new IllegalStateException(ex);
+ }
+ satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ }
+ threadPool.shutdownNow();
+
+ this.satisfiedTerminationConditions = satisfiedConditions;
+ return evaluatedCombinedPopulation.get(0).getCandidate();
+ }
+
+
+ /**
+ * Create the concurrently-executed tasks that perform evolution on each island.
+ */
+ private List<Callable<List<EvaluatedCandidate<T>>>> createEpochTasks(int populationSize,
+ int eliteCount,
+ int epochLength,
+ List<List<T>> islandPopulations)
+ {
+ List<Callable<List<EvaluatedCandidate<T>>>> islandEpochs
+ = new ArrayList<Callable<List<EvaluatedCandidate<T>>>>(islands.size());
+ for (int i = 0; i < islands.size(); i++)
+ {
+ islandEpochs.add(new Epoch<T>(islands.get(i),
+ populationSize,
+ eliteCount,
+ islandPopulations.isEmpty() ? Collections.<T>emptyList() : islandPopulations.get(i),
+ new GenerationCount(epochLength)));
+ }
+ return islandEpochs;
+ }
+
+
+ /**
+ * Convert a list of {@link EvaluatedCandidate}s into a simple list of candidates.
+ * @param evaluatedCandidates The population of candidate objects to relieve of their
+ * evaluation wrappers.
+ * @param <T> The type of entity that is being evolved.
+ * @return The candidates, stripped of their fitness scores.
+ */
+ private static <T> List<T> toCandidateList(List<EvaluatedCandidate<T>> evaluatedCandidates)
+ {
+ List<T> candidates = new ArrayList<T>(evaluatedCandidates.size());
+ for (EvaluatedCandidate<T> evaluatedCandidate : evaluatedCandidates)
+ {
+ candidates.add(evaluatedCandidate.getCandidate());
+ }
+ return candidates;
+ }
+
+
+ /**
+ * <p>Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the island evolution. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.</p>
+ *
+ * <p>If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.</p>
+ *
+ * <p>If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.</p>
+ *
+ * @throws IllegalStateException If this method is invoked on an island system before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ public List<TerminationCondition> getSatisfiedTerminationConditions()
+ {
+ if (satisfiedTerminationConditions == null)
+ {
+ throw new IllegalStateException("EvolutionEngine has not terminated.");
+ }
+ else
+ {
+ return Collections.unmodifiableList(satisfiedTerminationConditions);
+ }
+ }
+
+
+ /**
+ * <p>Adds an observer to the evolution. Observers will receives two types of updates:
+ * updates from each individual island at the end of each generation, and updates for
+ * the combined global population at the end of each epoch.</p>
+ *
+ * <p>Updates are dispatched synchronously on the request thread. Observers should
+ * complete their processing and return in a timely manner to avoid holding up
+ * the evolution.</p>
+ *
+ * @param observer The callback that will be notified at the end of each generation and epoch.
+ *
+ * @see #removeEvolutionObserver(IslandEvolutionObserver)
+ */
+ public void addEvolutionObserver(final IslandEvolutionObserver<? super T> observer)
+ {
+ observers.add(observer);
+ }
+
+
+ /**
+ * Remove the specified observer.
+ * @param observer The observer to remove (if it is registered).
+ *
+ * @see #addEvolutionObserver(IslandEvolutionObserver)
+ */
+ public void removeEvolutionObserver(final IslandEvolutionObserver<? super T> observer)
+ {
+ observers.remove(observer);
+ }
+
+
+ /**
+ * Send the population data to all registered observers.
+ * @param data Information about the current state of the population.
+ */
+ private void notifyPopulationChange(PopulationData<T> data)
+ {
+ for (IslandEvolutionObserver<? super T> observer : observers)
+ {
+ observer.populationUpdate(data);
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java
new file mode 100644
index 0000000..7613ff2
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java
@@ -0,0 +1,38 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import org.uncommons.watchmaker.framework.EvolutionObserver;
+import org.uncommons.watchmaker.framework.PopulationData;
+
+/**
+ * A specialisation of {@link org.uncommons.watchmaker.framework.EvolutionObserver} that, as well as
+ * receiving global population updates (at the end of each epoch), can receive individual island
+ * population updates (at the end of each generation on each island).
+ * @param <T> The type of entity being evolved.
+ * @author Daniel Dyer
+ */
+public interface IslandEvolutionObserver<T> extends EvolutionObserver<T>
+{
+ /**
+ * 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 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<? extends T> data);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java
new file mode 100644
index 0000000..e9b985f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Strategy interface for different ways of migrating individuals between islands
+ * in {@link IslandEvolution}.
+ * @author Daniel Dyer
+ */
+public interface Migration
+{
+ /**
+ * @param islandPopulations The populations of each island in the system.
+ * @param migrantCount The number of individuals to move from each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of the individual members of the island populations.
+ */
+ <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java
new file mode 100644
index 0000000..13e3bab
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Migrates a fixed number of candidates away from each island. Which individuals are migrated is determined
+ * randomly and which islands they move to is also random. This contrasts with the more ordered migration offered
+ * by {@link RingMigration}. If the migration count is greater than one, it is possible (probable) that migrants
+ * from the same island will be moved to different islands. It is also possible that when a migrant's destination is
+ * randomly chosen, it gets sent back to the island that it came from.
+ * @author Daniel Dyer
+ */
+public class RandomMigration implements Migration
+{
+ /**
+ * Migrates a fixed number of candidates away from each island. Which individuals are migrated is determined
+ * randomly and which islands they move to is also random. If the migration count is greater than one, it is
+ * possible (probable) that migrants from the same island will be moved to different islands. It is also possible
+ * that when a migrant's destination is randomly chosen, it gets sent back to the island that it came from.
+ * @param islandPopulations A list of the populations of each island.
+ * @param migrantCount The number of (randomly selected) individuals to be moved on from
+ * each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of entity being evolved.
+ */
+ public <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng)
+ {
+ List<EvaluatedCandidate<T>> migrants = new ArrayList<EvaluatedCandidate<T>>(migrantCount * islandPopulations.size());
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ Collections.shuffle(island, rng);
+ for (int i = 0; i < migrantCount; i++)
+ {
+ migrants.add(island.remove(island.size() - 1));
+ }
+ }
+ Collections.shuffle(migrants);
+ Iterator<EvaluatedCandidate<T>> iterator = migrants.iterator();
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ for (int i = 0; i < migrantCount; i++)
+ {
+ island.add(iterator.next());
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java
new file mode 100644
index 0000000..ca31cf9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Migrates a fixed number of individuals from each island to the adjacent island.
+ * Operates as if the islands are arranged in a ring with migration occurring in a
+ * clockwise direction. The individuals to be migrated are chosen completely at random.
+ * @author Daniel Dyer
+ */
+public class RingMigration implements Migration
+{
+ /**
+ * Migrates a fixed number of individuals from each island to the adjacent island.
+ * Operates as if the islands are arranged in a ring with migration occurring in a
+ * clockwise direction. The individuals to be migrated are chosen completely at random.
+ * @param islandPopulations A list of the populations of each island.
+ * @param migrantCount The number of (randomly selected) individuals to be moved on from
+ * each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of entity being evolved.
+ */
+ public <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng)
+ {
+ // The first batch of immigrants is from the last island to the first.
+ List<EvaluatedCandidate<T>> lastIsland = islandPopulations.get(islandPopulations.size() - 1);
+ Collections.shuffle(lastIsland, rng);
+ List<EvaluatedCandidate<T>> migrants = lastIsland.subList(lastIsland.size() - migrantCount, lastIsland.size());
+
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ // Migrants from the last island are immigrants for this island.
+ List<EvaluatedCandidate<T>> immigrants = migrants;
+ if (island != lastIsland) // We've already migrated individuals from the last island.
+ {
+ // Select the migrants that will move to the next island to make room for the immigrants here.
+ // Randomise the population so that there is no bias concerning which individuals are migrated.
+ Collections.shuffle(island, rng);
+ migrants = new ArrayList<EvaluatedCandidate<T>>(island.subList(island.size() - migrantCount, island.size()));
+ }
+ // Copy the immigrants over the last members of the population (those that are themselves
+ // migrating to the next island).
+ for (int i = 0; i < immigrants.size(); i++)
+ {
+ island.set(island.size() - migrantCount + i, immigrants.get(i));
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java
new file mode 100644
index 0000000..4509026
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java
@@ -0,0 +1,22 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * An implementation of island model evolution. Manages parallel evolution across
+ * multiple {@link org.uncommons.watchmaker.framework.EvolutionEngine}s (islands)
+ * with periodic migration between them.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.islands;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java
new file mode 100644
index 0000000..b0a9fba
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java
@@ -0,0 +1,184 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Generic base class for cross-over implementations. Supports all
+ * cross-over processes that operate on a pair of parent candidates.
+ * @param <T> The type of evolved candidates that are operated on by
+ * this cross-over implementation.
+ * @author Daniel Dyer
+ */
+public abstract class AbstractCrossover<T> implements EvolutionaryOperator<T>
+{
+ private final NumberGenerator<Integer> crossoverPointsVariable;
+ private final NumberGenerator<Probability> crossoverProbabilityVariable;
+
+ /**
+ * Sets up a fixed-point cross-over implementation. Cross-over is
+ * applied to all pairs of parents. To apply cross-over only to a
+ * proportion of parent pairs, use the {@link #AbstractCrossover(int, Probability)}
+ * constructor.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ protected AbstractCrossover(int crossoverPoints)
+ {
+ this(crossoverPoints, Probability.ONE);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a fixed number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbability} parameter.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population. Must be in the range
+ * {@literal 0 < crossoverProbability <= 1}
+ */
+ protected AbstractCrossover(int crossoverPoints,
+ Probability crossoverProbability)
+ {
+ this(new ConstantGenerator<Integer>(crossoverPoints),
+ new ConstantGenerator<Probability>(crossoverProbability));
+ if (crossoverPoints <= 0)
+ {
+ throw new IllegalArgumentException("Number of cross-over points must be positive.");
+ }
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to all pairs of parents. To apply cross-over
+ * only to a proportion of parent pairs, use the
+ * {@link #AbstractCrossover(NumberGenerator, NumberGenerator)} constructor.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ protected AbstractCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ this(crossoverPointsVariable,
+ new ConstantGenerator<Probability>(Probability.ONE));
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ protected AbstractCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ this.crossoverPointsVariable = crossoverPointsVariable;
+ this.crossoverProbabilityVariable = crossoverProbabilityVariable;
+ }
+
+
+ /**
+ * Applies the cross-over operation to the selected candidates. Pairs of
+ * candidates are chosen randomly and subjected to cross-over to produce
+ * a pair of offspring candidates.
+ * @param selectedCandidates The evolved individuals that have survived to
+ * be eligible to reproduce.
+ * @param rng A source of randomness used to determine the location of
+ * cross-over points.
+ * @return The combined set of evolved offspring generated by applying
+ * cross-over to the the selected candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ // Shuffle the collection before applying each operation so that the
+ // evolution is not influenced by any ordering artifacts from previous
+ // operations.
+ List<T> selectionClone = new ArrayList<T>(selectedCandidates);
+ Collections.shuffle(selectionClone, rng);
+
+ List<T> result = new ArrayList<T>(selectedCandidates.size());
+ Iterator<T> iterator = selectionClone.iterator();
+ while (iterator.hasNext())
+ {
+ T parent1 = iterator.next();
+ if (iterator.hasNext())
+ {
+ T parent2 = iterator.next();
+ // Randomly decide (according to the current cross-over probability)
+ // whether to perform cross-over for these 2 parents.
+ int crossoverPoints = crossoverProbabilityVariable.nextValue().nextEvent(rng)
+ ? crossoverPointsVariable.nextValue()
+ : 0;
+ if (crossoverPoints > 0)
+ {
+ result.addAll(mate(parent1, parent2, crossoverPoints, rng));
+ }
+ else
+ {
+ // If there is no cross-over to perform, just add the parents to the
+ // results unaltered.
+ result.add(parent1);
+ result.add(parent2);
+ }
+ }
+ else
+ {
+ // If we have an odd number of selected candidates, we can't pair up
+ // the last one so just leave it unmodified.
+ result.add(parent1);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Perform cross-over on a pair of parents to generate a pair of offspring.
+ * @param parent1 One of two individuals that provides the source material
+ * for generating offspring.
+ * @param parent2 One of two individuals that provides the source material
+ * for generating offspring.
+ * @param numberOfCrossoverPoints The number of cross-overs performed on the
+ * two parents.
+ * @param rng A source of randomness used to determine the location of
+ * cross-over points.
+ * @return A list containing two evolved offspring.
+ */
+ protected abstract List<T> mate(T parent1,
+ T parent2,
+ int numberOfCrossoverPoints,
+ Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java
new file mode 100644
index 0000000..4f9bdd3
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java
@@ -0,0 +1,128 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * bit strings.
+ * @see BitString
+ * @author Daniel Dyer
+ */
+public class BitStringCrossover extends AbstractCrossover<BitString>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public BitStringCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public BitStringCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public BitStringCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public BitStringCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public BitStringCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<BitString> mate(BitString parent1,
+ BitString parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.getLength() != parent2.getLength())
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ BitString offspring1 = parent1.clone();
+ BitString offspring2 = parent2.clone();
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.getLength() - 1));
+ offspring1.swapSubstring(offspring2, 0, crossoverIndex);
+ }
+ List<BitString> result = new ArrayList<BitString>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java
new file mode 100644
index 0000000..fe849f9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java
@@ -0,0 +1,100 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation of individual bits in a {@link BitString} according to some
+ * probability.
+ * @see org.uncommons.maths.binary.BitString
+ * @author Daniel Dyer
+ */
+public class BitStringMutation implements EvolutionaryOperator<BitString>
+{
+ private final NumberGenerator<Probability> mutationProbability;
+ private final NumberGenerator<Integer> mutationCount;
+
+
+ /**
+ * Creates a mutation operator for bit strings with the specified probability that a given
+ * bit string will be mutated, with exactly one bit being flipped.
+ * @param mutationProbability The probability of a candidate being mutated.
+ */
+ public BitStringMutation(Probability mutationProbability)
+ {
+ this(new ConstantGenerator<Probability>(mutationProbability),
+ new ConstantGenerator<Integer>(1));
+ }
+
+
+ /**
+ * Creates a mutation operator for bit strings, with the probability that any
+ * given bit will be flipped governed by the specified number generator.
+ * @param mutationProbability The (possibly variable) probability of a candidate
+ * bit string being mutated at all.
+ * @param mutationCount The (possibly variable) number of bits that will be flipped
+ * on any candidate bit string that is selected for mutation.
+ */
+ public BitStringMutation(NumberGenerator<Probability> mutationProbability,
+ NumberGenerator<Integer> mutationCount)
+ {
+ this.mutationProbability = mutationProbability;
+ this.mutationCount = mutationCount;
+ }
+
+
+ public List<BitString> apply(List<BitString> selectedCandidates, Random rng)
+ {
+ List<BitString> mutatedPopulation = new ArrayList<BitString>(selectedCandidates.size());
+ for (BitString b : selectedCandidates)
+ {
+ mutatedPopulation.add(mutateBitString(b, rng));
+ }
+ return mutatedPopulation;
+ }
+
+
+ /**
+ * Mutate a single bit string. Zero or more bits may be flipped. The
+ * probability of any given bit being flipped is governed by the probability
+ * generator configured for this mutation operator.
+ * @param bitString The bit string to mutate.
+ * @param rng A source of randomness.
+ * @return The mutated bit string.
+ */
+ private BitString mutateBitString(BitString bitString, Random rng)
+ {
+ if (mutationProbability.nextValue().nextEvent(rng))
+ {
+ BitString mutatedBitString = bitString.clone();
+ int mutations = mutationCount.nextValue();
+ for (int i = 0; i < mutations; i++)
+ {
+ mutatedBitString.flipBit(rng.nextInt(mutatedBitString.getLength()));
+ }
+ return mutatedBitString;
+ }
+ return bitString;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java
new file mode 100644
index 0000000..c80647b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive bytes.
+ * @author Daniel Dyer
+ */
+public class ByteArrayCrossover extends AbstractCrossover<byte[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ByteArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ByteArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ByteArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ByteArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public ByteArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<byte[]> mate(byte[] parent1,
+ byte[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ 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);
+ byte[] offspring2 = new byte[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ byte[] temp = new byte[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<byte[]> result = new ArrayList<byte[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java
new file mode 100644
index 0000000..8e7dc66
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive chars.
+ * @author Daniel Dyer
+ */
+public class CharArrayCrossover extends AbstractCrossover<char[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public CharArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public CharArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public CharArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public CharArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public CharArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<char[]> mate(char[] parent1,
+ char[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ 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);
+ char[] offspring2 = new char[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ char[] temp = new char[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<char[]> result = new ArrayList<char[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java
new file mode 100644
index 0000000..459e6a7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive doubles.
+ * @author Daniel Dyer
+ */
+public class DoubleArrayCrossover extends AbstractCrossover<double[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public DoubleArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public DoubleArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public DoubleArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public DoubleArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public DoubleArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<double[]> mate(double[] parent1,
+ double[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ 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);
+ double[] offspring2 = new double[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ double[] temp = new double[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<double[]> result = new ArrayList<double[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java
new file mode 100644
index 0000000..eafff64
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>A compound evolutionary operator that applies multiple operators (of the
+ * same type) in series.</p>
+ *
+ * <p>By combining EvolutionPipeline operators with {@link SplitEvolution} operators,
+ * elaborate evolutionary schemes can be constructed.</p>
+ *
+ * @param <T> The type of evolved candidate that this pipeline operates on.
+ * @author Daniel Dyer
+ */
+public class EvolutionPipeline<T> implements EvolutionaryOperator<T>
+{
+ private final List<EvolutionaryOperator<T>> pipeline;
+
+
+ /**
+ * Creates a pipeline consisting of the specified operators in
+ * the order that they are supplied.
+ * @param pipeline An ordered list of operators that make up the
+ * pipeline.
+ */
+ public EvolutionPipeline(List<EvolutionaryOperator<T>> pipeline)
+ {
+ if (pipeline.isEmpty())
+ {
+ throw new IllegalArgumentException("Pipeline must contain at least one operator.");
+ }
+ this.pipeline = new ArrayList<EvolutionaryOperator<T>>(pipeline);
+ }
+
+
+ /**
+ * Applies each operation in the pipeline in turn to the selection.
+ * @param selectedCandidates The candidates to subjected to evolution.
+ * @param rng A source of randomness used by all stochastic processes in
+ * the pipeline.
+ * @return A list of evolved candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ List<T> population = selectedCandidates;
+ for (EvolutionaryOperator<T> operator : pipeline)
+ {
+ population = operator.apply(population, rng);
+ }
+ return population;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java
new file mode 100644
index 0000000..ff31177
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java
@@ -0,0 +1,44 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Evolutionary operator that simply returns the selected candidates unaltered.
+ * This can be useful when combined with {@link SplitEvolution} so that a
+ * proportion of the selected candidates can be copied unaltered into the next
+ * generation while the remainder are evolved.
+ * @param <T> The type of evolvable entity that this operator is used with.
+ * @author Daniel Dyer
+ */
+public class IdentityOperator<T> implements EvolutionaryOperator<T>
+{
+ /**
+ * Returns the selected candidates unaltered.
+ * @param selectedCandidates The candidates to "evolve" (or do
+ * nothing to in this case).
+ * @param rng A source of randomness (not used).
+ * @return The unaltered candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ return new ArrayList<T>(selectedCandidates);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java
new file mode 100644
index 0000000..f416ecd
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive ints.
+ * @author Daniel Dyer
+ */
+public class IntArrayCrossover extends AbstractCrossover<int[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public IntArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public IntArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public IntArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public IntArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public IntArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<int[]> mate(int[] parent1,
+ int[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ 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);
+ int[] offspring2 = new int[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ int[] temp = new int[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<int[]> result = new ArrayList<int[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java
new file mode 100644
index 0000000..698200d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java
@@ -0,0 +1,129 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Variable-point (fixed or random) cross-over for arbitrary lists.
+ * @param <T> The component type of the lists that are combined.
+ * @author Daniel Dyer
+ */
+public class ListCrossover<T> extends AbstractCrossover<List<T>>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ListCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ListCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ListCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<List<T>> mate(List<T> parent1,
+ List<T> parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ List<T> offspring1 = new ArrayList<T>(parent1); // Use a random-access list for performance.
+ List<T> offspring2 = new ArrayList<T>(parent2);
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int max = Math.min(parent1.size(), parent2.size());
+ if (max > 1) // Don't perform cross-over if there aren't at least 2 elements in each list.
+ {
+ int crossoverIndex = (1 + rng.nextInt(max - 1));
+ for (int j = 0; j < crossoverIndex; j++)
+ {
+ T temp = offspring1.get(j);
+ offspring1.set(j, offspring2.get(j));
+ offspring2.set(j, temp);
+ }
+ }
+ }
+ List<List<T>> result = new ArrayList<List<T>>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java
new file mode 100644
index 0000000..09e1602
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java
@@ -0,0 +1,87 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * An evolutionary operator that randomly reverses a subsection of a list.
+ * @author Daniel Dyer
+ * @param <T> The type of entity being evolved.
+ */
+public class ListInversion<T> implements EvolutionaryOperator<List<T>>
+{
+ private final NumberGenerator<Probability> inversionProbability;
+
+
+ /**
+ * @param inversionProbability The probability that an individual list will have some
+ * subsection inverted.
+ */
+ public ListInversion(Probability inversionProbability)
+ {
+ this(new ConstantGenerator<Probability>(inversionProbability));
+ }
+
+
+ /**
+ * @param inversionProbability A variable that controls the probability that an
+ * individual list will have some subsection inverted.
+ */
+ public ListInversion(NumberGenerator<Probability> inversionProbability)
+ {
+ this.inversionProbability = inversionProbability;
+ }
+
+
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> result = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> candidate : selectedCandidates)
+ {
+ if (inversionProbability.nextValue().nextEvent(rng))
+ {
+ List<T> newCandidate = new ArrayList<T>(candidate);
+ int length = newCandidate.size();
+ int start = rng.nextInt(length);
+ int offset = 2 + rng.nextInt(length - 2); // Make sure segment length is at least 2.
+ int end = (start + offset) % length;
+ int segmentLength = end - start;
+ if (segmentLength < 0)
+ {
+ segmentLength += length;
+ }
+ for (int i = 0; i < segmentLength / 2; i++)
+ {
+ Collections.swap(newCandidate, (start + i) % length, (end - i + length) % length);
+ }
+ result.add(newCandidate);
+ }
+ else
+ {
+ result.add(candidate);
+ }
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java
new file mode 100644
index 0000000..cb75511
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>A higher-order evolutionary operator that is applied to populations made
+ * up of lists. In such populations, each candidate solution is itself a list
+ * and this operator is applied to the list contents rather than the candidate.
+ * It is analogous to the map function in functional programming languages.</p>
+ *
+ * <p>For example, if the evolved population consists of candidates that are
+ * lists of strings, we could use a ListOperator to wrap an operator of type
+ * String and convert it to an operator that works with lists of Strings.</p>
+ *
+ * @param <T> The element type of the lists to be mutated.
+ * @author Daniel Dyer
+ */
+public class ListOperator <T> implements EvolutionaryOperator<List<T>>
+{
+ private final EvolutionaryOperator<T> delegate;
+
+ /**
+ * @param delegate The evolutionary operator that will be applied to each
+ * list candidate.
+ */
+ public ListOperator(EvolutionaryOperator<T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * Applies the configured operator to each list candidate, operating on the elements
+ * that make up a candidate rather than on the list of candidates.
+ * candidates and returns the results.
+ * @param selectedCandidates A list of list candidates.
+ * @param rng A source of randomness.
+ * @return The result of applying the configured operator to each element
+ * in each list candidates.
+ */
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> output = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> item : selectedCandidates)
+ {
+ output.add(delegate.apply(item, rng));
+ }
+ return output;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java
new file mode 100644
index 0000000..5748e79
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java
@@ -0,0 +1,164 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+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 <T> The component type of the lists that are combined.
+ * @author Daniel Dyer
+ */
+public class ListOrderCrossover<T> extends AbstractCrossover<List<T>>
+{
+ /**
+ * Creates a cross-over operator with a cross-over probability of 1.
+ */
+ public ListOrderCrossover()
+ {
+ this(Probability.ONE);
+ }
+
+
+ /**
+ * Creates a cross-over operator with the specified cross-over probability.
+ * @param crossoverProbability The probability that cross-over will be performed
+ * for any given pair.
+ */
+ public ListOrderCrossover(Probability crossoverProbability)
+ {
+ super(2, // Requires exactly two cross-over points.
+ crossoverProbability);
+ }
+
+
+ /**
+ * Creates a cross-over operator where cross-over may or may not be applied to a
+ * given pair of parents depending on the {@code crossoverProbability}.
+ * @param crossoverProbabilityVariable The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListOrderCrossover(NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(new ConstantGenerator<Integer>(2), // Requires exactly two cross-over points.
+ crossoverProbabilityVariable);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<List<T>> mate(List<T> parent1,
+ List<T> parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ 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.");
+ }
+
+ List<T> offspring1 = new ArrayList<T>(parent1); // Use a random-access list for performance.
+ List<T> offspring2 = new ArrayList<T>(parent2);
+
+ int point1 = rng.nextInt(parent1.size());
+ int point2 = rng.nextInt(parent1.size());
+
+ int length = point2 - point1;
+ if (length < 0)
+ {
+ length += parent1.size();
+ }
+
+ Map<T, T> mapping1 = new HashMap<T, T>(length * 2); // Big enough map to avoid re-hashing.
+ Map<T, T> mapping2 = new HashMap<T, T>(length * 2);
+ for (int i = 0; i < length; i++)
+ {
+ int index = (i + point1) % parent1.size();
+ T item1 = offspring1.get(index);
+ T item2 = offspring2.get(index);
+ offspring1.set(index, item2);
+ offspring2.set(index, item1);
+ mapping1.put(item1, item2);
+ mapping2.put(item2, item1);
+ }
+
+ checkUnmappedElements(offspring1, mapping2, point1, point2);
+ checkUnmappedElements(offspring2, mapping1, point1, point2);
+
+ List<List<T>> result = new ArrayList<List<T>>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+
+
+ /**
+ * Checks elements that are outside of the partially mapped section to
+ * see if there are any duplicate items in the list. If there are, they
+ * are mapped appropriately.
+ */
+ private void checkUnmappedElements(List<T> offspring,
+ Map<T, T> mapping,
+ int mappingStart,
+ int mappingEnd)
+ {
+ for (int i = 0; i < offspring.size(); i++)
+ {
+ if (!isInsideMappedRegion(i, mappingStart, mappingEnd))
+ {
+ T mapped = offspring.get(i);
+ while (mapping.containsKey(mapped))
+ {
+ mapped = mapping.get(mapped);
+ }
+ offspring.set(i, mapped);
+ }
+ }
+ }
+
+
+ /**
+ * Checks whether a given list position is within the partially mapped
+ * region used for cross-over.
+ * @param position The list position to check.
+ * @param startPoint The starting index (inclusive) of the mapped region.
+ * @param endPoint The end index (exclusive) of the mapped region.
+ * @return True if the specified position is in the mapped region, false
+ * otherwise.
+ */
+ private boolean isInsideMappedRegion(int position,
+ int startPoint,
+ int endPoint)
+ {
+ boolean enclosed = (position < endPoint && position >= startPoint);
+ boolean wrapAround = (startPoint > endPoint && (position >= startPoint || position < endPoint));
+ return enclosed || wrapAround;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java
new file mode 100644
index 0000000..a218510
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java
@@ -0,0 +1,105 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * A special mutation implementation that instead of changing the
+ * genes of the candidate, re-orders them. A single mutation involves
+ * swapping a random element in the list with the element immediately
+ * after it. This operation can either apply a fixed number of
+ * mutations to each candidate or it can draw values from a random
+ * sequence, typically a poisson distribution (see
+ * {@link org.uncommons.maths.random.PoissonGenerator}), to determine how
+ * many mutations to apply.
+ * @param <T> The component type of the lists that are mutated.
+ * @author Daniel Dyer
+ */
+public class ListOrderMutation<T> implements EvolutionaryOperator<List<T>>
+{
+ private final NumberGenerator<Integer> mutationCountVariable;
+ private final NumberGenerator<Integer> mutationAmountVariable;
+
+ /**
+ * Default is one mutation per candidate.
+ */
+ public ListOrderMutation()
+ {
+ this(1, 1);
+ }
+
+ /**
+ * @param mutationCount The constant number of mutations
+ * to apply to each individual in the population.
+ * @param mutationAmount The constant number of positions by
+ * which a list element will be displaced as a result of mutation.
+ */
+ public ListOrderMutation(int mutationCount, int mutationAmount)
+ {
+ this(new ConstantGenerator<Integer>(mutationCount),
+ new ConstantGenerator<Integer>(mutationAmount));
+ }
+
+
+ /**
+ * Typically the mutation count will be from a Poisson distribution.
+ * The mutation amount can be from any discrete probability distribution
+ * and can include negative values.
+ * @param mutationCount A random variable that provides a number
+ * of mutations that will be applied to each individual.
+ * @param mutationAmount A random variable that provides a number
+ * of positions by which to displace an element when mutating.
+ */
+ public ListOrderMutation(NumberGenerator<Integer> mutationCount,
+ NumberGenerator<Integer> mutationAmount)
+ {
+ this.mutationCountVariable = mutationCount;
+ this.mutationAmountVariable = mutationAmount;
+ }
+
+
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> result = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> candidate : selectedCandidates)
+ {
+ List<T> newCandidate = new ArrayList<T>(candidate);
+ int mutationCount = Math.abs(mutationCountVariable.nextValue());
+ for (int i = 0; i < mutationCount; i++)
+ {
+ int fromIndex = rng.nextInt(newCandidate.size());
+ int mutationAmount = mutationAmountVariable.nextValue();
+ int toIndex = (fromIndex + mutationAmount) % newCandidate.size();
+ if (toIndex < 0)
+ {
+ toIndex += newCandidate.size();
+ }
+ // Swap the randomly selected element with the one that is the
+ // specified displacement distance away.
+ Collections.swap(newCandidate, fromIndex, toIndex);
+ }
+ result.add(newCandidate);
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java
new file mode 100644
index 0000000..176bfaa
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java
@@ -0,0 +1,136 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of reference types.
+ * @param <T> The component type of the arrays that are being evolved.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayCrossover<T> extends AbstractCrossover<T[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ObjectArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ObjectArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ObjectArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ObjectArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public ObjectArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<T[]> mate(T[] parent1,
+ T[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ 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);
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ @SuppressWarnings("unchecked")
+ 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];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<T[]> result = new ArrayList<T[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java
new file mode 100644
index 0000000..94b8b5f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java
@@ -0,0 +1,96 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * An evolutionary operator that replaces individuals with randomly-generated
+ * new individuals, according to some specified probability. The new individuals
+ * are not derived from the selected individuals, they are completely random. This
+ * operator provides a way to prevent stagnation by occassionally introducing
+ * new genetic material into the population.
+ * @param <T> The type of evolvable entity that this operator applies to.
+ * @author Daniel Dyer
+ */
+public class Replacement<T> implements EvolutionaryOperator<T>
+{
+ private final CandidateFactory<T> factory;
+ private final NumberGenerator<Probability> replacementProbability;
+
+
+ /**
+ * Creates a replacement operator that replaces individuals according to
+ * the specified probability. New individuals are obtained from the factory
+ * provided.
+ * @param factory A source of new individuals.
+ * @param replacementProbability The probability that any given individual will
+ * be replaced by a new individual. This should typically be quite low. If it is
+ * too high, it will undermine the evolutionary progress.
+ */
+ public Replacement(CandidateFactory<T> factory,
+ Probability replacementProbability)
+ {
+ this(factory, new ConstantGenerator<Probability>(replacementProbability));
+ }
+
+
+ /**
+ * Creates a replacement operator that replaces individuals according to
+ * a variable probability. New individuals are obtained from the factory
+ * provided.
+ * @param factory A source of new individuals.
+ * @param replacementProbability A {@link NumberGenerator} that provides
+ * a probability of replacement. The probablity may be constant, or it may change
+ * over time. The probability should typically be quite low. If it is too high,
+ * it will undermine the evolutionary progress.
+ */
+ public Replacement(CandidateFactory<T> factory,
+ NumberGenerator<Probability> replacementProbability)
+ {
+ this.factory = factory;
+ this.replacementProbability = replacementProbability;
+ }
+
+
+ /**
+ * Randomly replace zero or more of the selected candidates with new,
+ * independent individuals that are randomly created.
+ * @param selectedCandidates The selected candidates, some of these may be
+ * discarded and replaced with new individuals.
+ * @param rng A source of randomness.
+ * @return The remaining candidates after some (or none) have been replaced
+ * with new individuals.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ List<T> output = new ArrayList<T>(selectedCandidates.size());
+ for (T candidate : selectedCandidates)
+ {
+ output.add(replacementProbability.nextValue().nextEvent(rng)
+ ? factory.generateRandomCandidate(rng)
+ : candidate);
+ }
+ return output;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java
new file mode 100644
index 0000000..4c6d5fb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>Compound evolutionary operator that allows the evolution of a population
+ * to be split into two separate streams. A percentage of the population
+ * will be evolved according to one specified operator and the remainder
+ * according to another operator. When both streams have been executed, the
+ * resulting offspring will be returned as a single combined population.</p>
+ *
+ * <p>This kind of separation is common in a genetic programming context where,
+ * for example, 10% of the population is mutated and the remaining 90%
+ * undergoes cross-over independently.</p>
+ *
+ * <p>To split evolution into more than two streams, multiple SplitEvolution operators
+ * can be combined. By combining SplitEvolution operators with
+ * {@link EvolutionPipeline} operators, elaborate evolutionary schemes can be
+ * constructed.</p>
+ *
+ * @param <T> The type of evolved entity dealt with by this operator.
+ * @author Daniel Dyer
+ */
+public class SplitEvolution<T> implements EvolutionaryOperator<T>
+{
+ private final EvolutionaryOperator<T> operator1;
+ private final EvolutionaryOperator<T> operator2;
+ private final NumberGenerator<Double> weightVariable;
+
+ /**
+ * @param operator1 The operator that will apply to the first part of the
+ * population (as determined by the {@code weight} parameter).
+ * @param operator2 The operator that will apply to the second part of the
+ * population (as determined by the {@code weight} parameter).
+ * @param weight The proportion (as a real number between zero and 1 exclusive)
+ * of the population that will be evolved by {@code operator1}. The
+ * remainder will be evolved by {@code operator2}.
+ */
+ public SplitEvolution(EvolutionaryOperator<T> operator1,
+ EvolutionaryOperator<T> operator2,
+ double weight)
+ {
+ this(operator1, operator2, new ConstantGenerator<Double>(weight));
+ if (weight <= 0 || weight >= 1)
+ {
+ throw new IllegalArgumentException("Split ratio must be greater than 0 and less than 1.");
+ }
+ }
+
+
+ /**
+ * @param operator1 The operator that will apply to the first part of the
+ * population (as determined by the {@code weightVariable} parameter).
+ * @param operator2 The operator that will apply to the second part of the
+ * population (as determined by the {@code weightVariable} parameter).
+ * @param weightVariable A random variable that provides the ratio for
+ * dividing the population between the two evolutionary streams. Must
+ * only generate values in the range {@literal 0 < ratio < 1}.
+ */
+ public SplitEvolution(EvolutionaryOperator<T> operator1,
+ EvolutionaryOperator<T> operator2,
+ NumberGenerator<Double> weightVariable)
+ {
+ this.operator1 = operator1;
+ this.operator2 = operator2;
+ this.weightVariable = weightVariable;
+ }
+
+
+ /**
+ * Applies one evolutionary operator to part of the population and another
+ * to the remainder. Returns a list combining the output of both. Which
+ * candidates are submitted to which stream is determined randomly.
+ * @param selectedCandidates A list of the candidates that survived to be
+ * eligible for evolution.
+ * @param rng A source of randomness passed to each of the two delegate
+ * evolutionary operators.
+ * @return The combined results from the two streams of evolution.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ double ratio = weightVariable.nextValue();
+ int size = (int) Math.round(ratio * selectedCandidates.size());
+
+ // Shuffle the collection before applying each operation so that the
+ // split is not influenced by any ordering artifacts from previous
+ // operations.
+ List<T> selectionClone = new ArrayList<T>(selectedCandidates);
+ Collections.shuffle(selectionClone, rng);
+
+ List<T> list1 = selectionClone.subList(0, size);
+ List<T> list2 = selectionClone.subList(size, selectedCandidates.size());
+ List<T> result = new ArrayList<T>(selectedCandidates.size());
+ result.addAll(operator1.apply(list1, rng));
+ result.addAll(operator2.apply(list2, rng));
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java
new file mode 100644
index 0000000..e73119a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java
@@ -0,0 +1,132 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+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.
+ * @author Daniel Dyer
+ */
+public class StringCrossover extends AbstractCrossover<String>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public StringCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public StringCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public StringCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public StringCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public StringCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<String> mate(String parent1,
+ String parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length() != parent2.length())
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ StringBuilder offspring1 = new StringBuilder(parent1);
+ StringBuilder offspring2 = new StringBuilder(parent2);
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length() - 1));
+ for (int j = 0; j < crossoverIndex; j++)
+ {
+ char temp = offspring1.charAt(j);
+ offspring1.setCharAt(j, offspring2.charAt(j));
+ offspring2.setCharAt(j, temp);
+ }
+ }
+ List<String> result = new ArrayList<String>(2);
+ result.add(offspring1.toString());
+ result.add(offspring2.toString());
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java
new file mode 100644
index 0000000..6e2e485
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java
@@ -0,0 +1,95 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation of individual characters in a string according to some
+ * probability.
+ * @author Daniel Dyer
+ */
+public class StringMutation implements EvolutionaryOperator<String>
+{
+ private final char[] alphabet;
+ private final NumberGenerator<Probability> mutationProbability;
+
+ /**
+ * Creates a mutation operator that is applied with the given
+ * probability and draws its characters from the specified alphabet.
+ * @param alphabet The permitted values for each character in a string.
+ * @param mutationProbability The probability that a given character
+ * is changed.
+ */
+ public StringMutation(char[] alphabet, Probability mutationProbability)
+ {
+ this(alphabet, new ConstantGenerator<Probability>(mutationProbability));
+ }
+
+
+ /**
+ * Creates a mutation operator that is applied with the given
+ * probability and draws its characters from the specified alphabet.
+ * @param alphabet The permitted values for each character in a string.
+ * @param mutationProbability The (possibly variable) probability that a
+ * given character is changed.
+ */
+ public StringMutation(char[] alphabet,
+ NumberGenerator<Probability> mutationProbability)
+ {
+ this.alphabet = alphabet.clone();
+ this.mutationProbability = mutationProbability;
+ }
+
+
+ public List<String> apply(List<String> selectedCandidates, Random rng)
+ {
+ List<String> mutatedPopulation = new ArrayList<String>(selectedCandidates.size());
+ for (String s : selectedCandidates)
+ {
+ mutatedPopulation.add(mutateString(s, rng));
+ }
+ return mutatedPopulation;
+ }
+
+
+ /**
+ * Mutate a single string. Zero or more characters may be modified. The
+ * probability of any given character being modified is governed by the
+ * probability generator configured for this mutation operator.
+ * @param s The string to mutate.
+ * @param rng A source of randomness.
+ * @return The mutated string.
+ */
+ private String mutateString(String s, Random rng)
+ {
+ StringBuilder buffer = new StringBuilder(s);
+ for (int i = 0; i < buffer.length(); i++)
+ {
+ if (mutationProbability.nextValue().nextEvent(rng))
+ {
+ buffer.setCharAt(i, alphabet[rng.nextInt(alphabet.length)]);
+ }
+ }
+ return buffer.toString();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java
new file mode 100644
index 0000000..ee33629
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Provides several ready-to-use standard evolutionary operators for commonly
+ * used data types such as arrays, lists and strings.
+ */
+package org.uncommons.watchmaker.framework.operators;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java
new file mode 100644
index 0000000..4f3f095
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java
@@ -0,0 +1,22 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * This package provides a framework for evolutionary computation. It defines generic
+ * interfaces for evolutionary operators, fitness functions and selection strategies.
+ * It also provides an all-purpose {@link org.uncommons.watchmaker.framework.EvolutionEngine}.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java
new file mode 100644
index 0000000..1b12b54
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * <p>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).</p>
+ * <p>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.</p>
+ * @author Daniel Dyer
+ */
+public class RankSelection implements SelectionStrategy<Object>
+{
+ private final SelectionStrategy<Object> delegate;
+
+ /**
+ * Creates a default rank-based selector with a linear
+ * mapping function and selection frequencies that correspond
+ * to expected values.
+ */
+ public RankSelection()
+ {
+ this(new StochasticUniversalSampling());
+ }
+
+
+ /**
+ * Creates a rank-based selector with a linear mapping function and
+ * configurable delegate for performing the proportionate selection.
+ * @param delegate The proportionate selector that will be delegated
+ * to after converting rankings into relative fitness scores.
+ */
+ public RankSelection(SelectionStrategy<Object> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<EvaluatedCandidate<S>> rankedPopulation = new ArrayList<EvaluatedCandidate<S>>(population.size());
+ Iterator<EvaluatedCandidate<S>> iterator = population.iterator();
+ int index = -1;
+ while (iterator.hasNext())
+ {
+ S candidate = iterator.next().getCandidate();
+ rankedPopulation.add(new EvaluatedCandidate<S>(candidate,
+ mapRankToScore(++index,
+ population.size())));
+ }
+ return delegate.select(rankedPopulation, true, selectionSize, rng);
+ }
+
+
+ /**
+ * <p>Maps a population index to a relative pseudo-fitness score that can be used for
+ * fitness-proportionate selection. The general contract for the mapping function
+ * {@code f} is: {@code f(rank) >= f(rank + 1)} for all legal values of
+ * {@code rank}, assuming natural scores.</p>
+ * <p>The default mapping function is a simple linear transformation, but this
+ * can be over-ridden in sub-classes. Alternative implementations can be linear or
+ * non-linear and either natural or non-natural.</p>
+ * @param rank A zero-based index into the population
+ * {@code (0 <= rank < populationSize)}.
+ * @param populationSize The number of individuals in the population.
+ * @return {@code populationSize - rank}
+ */
+ protected double mapRankToScore(int rank, int populationSize)
+ {
+ return populationSize - rank;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Rank Selection";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java
new file mode 100644
index 0000000..a964f57
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java
@@ -0,0 +1,118 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * <p>Implements selection of <i>n</i> candidates from a population by selecting
+ * <i>n</i> 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 <n>i</n> times. Candidates may be selected more than
+ * once.</p>
+ *
+ * <p>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.</p>
+ *
+ * @author Daniel Dyer
+ */
+public class RouletteWheelSelection implements SelectionStrategy<Object>
+{
+ /**
+ * 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 <S> 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 selectionSize The number of selections to make.
+ * @param rng A source of randomness.
+ * @return The selected candidates.
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ // Record the cumulative fitness scores. It doesn't matter whether the
+ // population is sorted or not. We will use these cumulative scores to work out
+ // an index into the population. The cumulative array itself is implicitly
+ // sorted since each element must be greater than the previous one. The
+ // numerical difference between an element and the previous one is directly
+ // proportional to the probability of the corresponding candidate in the population
+ // being selected.
+ double[] cumulativeFitnesses = new double[population.size()];
+ cumulativeFitnesses[0] = getAdjustedFitness(population.get(0).getFitness(),
+ naturalFitnessScores);
+ for (int i = 1; i < population.size(); i++)
+ {
+ double fitness = getAdjustedFitness(population.get(i).getFitness(),
+ naturalFitnessScores);
+ cumulativeFitnesses[i] = cumulativeFitnesses[i - 1] + fitness;
+ }
+
+ List<S> selection = new ArrayList<S>(selectionSize);
+ for (int i = 0; i < selectionSize; i++)
+ {
+ double randomFitness = rng.nextDouble() * cumulativeFitnesses[cumulativeFitnesses.length - 1];
+ int index = Arrays.binarySearch(cumulativeFitnesses, randomFitness);
+ if (index < 0)
+ {
+ // Convert negative insertion point to array index.
+ index = Math.abs(index + 1);
+ }
+ selection.add(population.get(index).getCandidate());
+ }
+ return selection;
+ }
+
+
+ private double getAdjustedFitness(double rawFitness,
+ boolean naturalFitness)
+ {
+ if (naturalFitness)
+ {
+ return rawFitness;
+ }
+ else
+ {
+ // If standardised fitness is zero we have found the best possible
+ // solution. The evolutionary algorithm should not be continuing
+ // after finding it.
+ return rawFitness == 0 ? Double.POSITIVE_INFINITY : 1 / rawFitness;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Roulette Wheel Selection";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java
new file mode 100644
index 0000000..814b0b7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java
@@ -0,0 +1,114 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.statistics.DataSet;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+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.
+ * @author Daniel Dyer
+ */
+public class SigmaScaling implements SelectionStrategy<Object>
+{
+ private final SelectionStrategy<Object> delegate;
+
+ /**
+ * Creates a default sigma-scaled selection strategy.
+ */
+ public SigmaScaling()
+ {
+ this(new StochasticUniversalSampling());
+ }
+
+
+ /**
+ * Creates a sigma-scaled selection strategy that delegates to the specified selection
+ * strategy after adjusting individual fitness scores using sigma-scaling.
+ * @param delegate The proportionate selector that will be delegated
+ * to after fitness scores have been adjusted using sigma-scaling.
+ */
+ public SigmaScaling(SelectionStrategy<Object> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ DataSet statistics = new DataSet(population.size());
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ statistics.addValue(candidate.getFitness());
+ }
+
+ List<EvaluatedCandidate<S>> scaledPopulation = new ArrayList<EvaluatedCandidate<S>>(population.size());
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ double scaledFitness = getSigmaScaledFitness(candidate.getFitness(),
+ statistics.getArithmeticMean(),
+ statistics.getStandardDeviation());
+ scaledPopulation.add(new EvaluatedCandidate<S>(candidate.getCandidate(),
+ scaledFitness));
+ }
+ return delegate.select(scaledPopulation, naturalFitnessScores, selectionSize, rng);
+ }
+
+
+ private double getSigmaScaledFitness(double candidateFitness,
+ double populationMeanFitness,
+ double fitnessStandardDeviation)
+ {
+ if (fitnessStandardDeviation == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ 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).
+ return scaledFitness > 0 ? scaledFitness : 0.1;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Sigma Scaling";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java
new file mode 100644
index 0000000..7e1bf9b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java
@@ -0,0 +1,95 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+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.
+ * @author Daniel Dyer
+ */
+public class StochasticUniversalSampling implements SelectionStrategy<Object>
+{
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ // Calculate the sum of all fitness values.
+ double aggregateFitness = 0;
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ aggregateFitness += getAdjustedFitness(candidate.getFitness(),
+ naturalFitnessScores);
+ }
+
+ List<S> selection = new ArrayList<S>(selectionSize);
+ // Pick a random offset between 0 and 1 as the starting point for selection.
+ double startOffset = rng.nextDouble();
+ double cumulativeExpectation = 0;
+ int index = 0;
+ for (EvaluatedCandidate<S> 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;
+
+ // 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
+ // actual count depends on the random starting offset.
+ while (cumulativeExpectation > startOffset + index)
+ {
+ selection.add(candidate.getCandidate());
+ index++;
+ }
+ }
+ return selection;
+ }
+
+
+ private double getAdjustedFitness(double rawFitness, boolean naturalFitness)
+ {
+ if (naturalFitness)
+ {
+ return rawFitness;
+ }
+ else
+ {
+ // If standardised fitness is zero we have found the best possible
+ // solution. The evolutionary algorithm should not be continuing
+ // after finding it.
+ return rawFitness == 0 ? Double.POSITIVE_INFINITY : 1 / rawFitness;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Stochastic Universal Sampling";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java
new file mode 100644
index 0000000..78d8107
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+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).
+ * @author Daniel Dyer
+ */
+public class TournamentSelection implements SelectionStrategy<Object>
+{
+ private final NumberGenerator<Probability> selectionProbability;
+
+ private String description = "Tournament Selection";
+
+ /**
+ * Creates a tournament selection strategy that is controlled by the
+ * variable selection probability provided by the specified
+ * {@link NumberGenerator}.
+ * @param selectionProbability A number generator that produces values in
+ * the range {@literal 0.5 < p < 1}. These values are used as the probability
+ * of the fittest candidate being selected in any given tournament.
+ */
+ public TournamentSelection(NumberGenerator<Probability> selectionProbability)
+ {
+ this.selectionProbability = selectionProbability;
+ }
+
+
+ /**
+ * Creates a tournament selection strategy with a fixed probability.
+ * @param selectionProbability The probability that the fitter of two randomly
+ * chosen candidates will be selected. Since this is a probability it must be
+ * between 0.0 and 1.0. This implementation adds the further restriction that
+ * the probability must be greater than 0.5 since any lower value would favour
+ * weaker candidates over strong ones, negating the "survival of the fittest"
+ * aspect of the evolutionary algorithm.
+ */
+ public TournamentSelection(Probability selectionProbability)
+ {
+ this(new ConstantGenerator<Probability>(selectionProbability));
+ if (selectionProbability.doubleValue() <= 0.5)
+ {
+ throw new IllegalArgumentException("Selection threshold must be greater than 0.5.");
+ }
+ this.description = "Tournament Selection (p = " + selectionProbability.toString() + ')';
+ }
+
+
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<S> selection = new ArrayList<S>(selectionSize);
+ for (int i = 0; i < selectionSize; i++)
+ {
+ // Pick two candidates at random.
+ EvaluatedCandidate<S> candidate1 = population.get(rng.nextInt(population.size()));
+ EvaluatedCandidate<S> candidate2 = population.get(rng.nextInt(population.size()));
+
+ // Use a random value to decide wether to select the fitter individual or the weaker one.
+ boolean selectFitter = selectionProbability.nextValue().nextEvent(rng);
+ if (selectFitter == naturalFitnessScores)
+ {
+ // Select the fitter candidate.
+ selection.add(candidate2.getFitness() > candidate1.getFitness()
+ ? candidate2.getCandidate()
+ : candidate1.getCandidate());
+ }
+ else
+ {
+ // Select the less fit candidate.
+ selection.add(candidate2.getFitness() > candidate1.getFitness()
+ ? candidate1.getCandidate()
+ : candidate2.getCandidate());
+ }
+ }
+ return selection;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return description;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java
new file mode 100644
index 0000000..594df97
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java
@@ -0,0 +1,117 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Implements selection of <i>n</i> candidates from a population by simply
+ * selecting the <i>n</i> 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<Object>
+{
+ private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#0.###%");
+ private final NumberGenerator<Double> selectionRatio;
+
+ private String description = "Truncation Selection";
+
+ /**
+ * Creates a truncation selection strategy that is controlled by the
+ * variable selection ratio provided by the specified
+ * {@link NumberGenerator}.
+ * @param selectionRatio A number generator that produces values in
+ * the range {@literal 0 < r < 1}. These values are used to determine
+ * the proportion of the population that is retained in any given selection.
+ */
+ public TruncationSelection(NumberGenerator<Double> selectionRatio)
+ {
+ this.selectionRatio = selectionRatio;
+ }
+
+
+ /**
+ * @param selectionRatio The proportion of the highest ranked candidates to
+ * select from the population. The value must be positive and less than 1.
+ */
+ public TruncationSelection(double selectionRatio)
+ {
+ this(new ConstantGenerator<Double>(selectionRatio));
+ if (selectionRatio <= 0 || selectionRatio >= 1)
+ {
+ throw new IllegalArgumentException("Selection ratio must be greater than 0 and less than 1.");
+ }
+ this.description = "Truncation Selection (" + PERCENT_FORMAT.format(selectionRatio) + ")";
+ }
+
+
+ /**
+ * Selects the fittest candidates. If the selectionRatio results in
+ * fewer selected candidates than required, then these candidates are
+ * selected multiple times to make up the shortfall.
+ * @param population The population of evolved and evaluated candidates
+ * from which to select.
+ * @param naturalFitnessScores Whether higher fitness values represent fitter
+ * individuals or not.
+ * @param selectionSize The number of candidates to select from the
+ * evolved population.
+ * @param rng A source of randomness (not used by this selection
+ * implementation since truncation selection is deterministic).
+ * @param <S> The type of evolved entity that is being selected.
+ * @return The selected candidates.
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<S> selection = new ArrayList<S>(selectionSize);
+
+ double ratio = selectionRatio.nextValue();
+ assert ratio < 1 && ratio > 0 : "Selection ratio out-of-range: " + ratio;
+
+ int eligibleCount = (int) Math.round(ratio * population.size());
+ eligibleCount = eligibleCount > selectionSize ? selectionSize : eligibleCount;
+
+ do
+ {
+ int count = Math.min(eligibleCount, selectionSize - selection.size());
+ for (int i = 0; i < count; i++)
+ {
+ selection.add(population.get(i).getCandidate());
+ }
+ } while (selection.size() < selectionSize);
+ return selection;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return description;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java
new file mode 100644
index 0000000..73f682a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Various selection strategies for use with evolutionary algorithms.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.selection;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java
new file mode 100644
index 0000000..bddea38
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * 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.
+ */
+ public ElapsedTime(long maxDuration)
+ {
+ if (maxDuration <= 0)
+ {
+ throw new IllegalArgumentException("Duration must be positive.");
+ }
+ this.maxDuration = maxDuration;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * This implementation terminates evolution if the pre-configured maximum
+ * permitted time has elapsed.
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return populationData.getElapsedTime() >= maxDuration;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java
new file mode 100644
index 0000000..a0ec88c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * 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.
+ */
+ public GenerationCount(int generationCount)
+ {
+ if (generationCount <= 0)
+ {
+ throw new IllegalArgumentException("Generation count must be positive.");
+ }
+ this.generationCount = generationCount;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return populationData.getGenerationNumber() + 1 >= generationCount;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java
new file mode 100644
index 0000000..b0bab4a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+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.
+ * @author Daniel Dyer
+ */
+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
+ * fittest individual.
+ * @param generationLimit The number of generations without improvement that
+ * will lead to termination.
+ * @param naturalFitness True if higher fitness scores are better, false otherwise.
+ */
+ public Stagnation(int generationLimit,
+ boolean naturalFitness)
+ {
+ this(generationLimit, naturalFitness, false);
+ }
+
+
+ /**
+ * Creates a {@link TerminationCondition} that will halt evolution after the
+ * specified number of generations passes without any improvement in the population's
+ * fitness (either the fittest individual or the mean fitness of the entire population,
+ * depending on the final parameter).
+ * @param generationLimit The number of generations without improvement that
+ * will lead to termination.
+ * @param naturalFitness True if higher fitness scores are better, false otherwise.
+ * @param usePopulationAverage If true uses the mean fitness of the population as the
+ * criteria, otherwise uses the fittest individual.
+ */
+ public Stagnation(int generationLimit,
+ boolean naturalFitness,
+ boolean usePopulationAverage)
+ {
+ this.generationLimit = generationLimit;
+ this.naturalFitness = naturalFitness;
+ this.usePopulationAverage = usePopulationAverage;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ double fitness = getFitness(populationData);
+ if (populationData.getGenerationNumber() == 0 || hasFitnessImproved(fitness))
+ {
+ bestFitness = fitness;
+ fittestGeneration = populationData.getGenerationNumber();
+ }
+
+ return populationData.getGenerationNumber() - fittestGeneration >= generationLimit;
+ }
+
+
+ /**
+ * Determines the fitness of the current population (either best fitness or
+ * mean fitness depending on how the termination condition is configured).
+ * @param populationData Data about the current generation.
+ * @return The fitness measure used to decide whether the evolution has stagnated
+ * or not.
+ */
+ private double getFitness(PopulationData<?> populationData)
+ {
+ return usePopulationAverage
+ ? populationData.getMeanFitness()
+ : populationData.getBestCandidateFitness();
+ }
+
+
+ /**
+ * Determine whether the population fitness is better than the best seen so far.
+ * @param fitness The fitness of the current population (either best fitness or mean
+ * fitness depending on how the termination condition is configured).
+ * @return True if the fitness has improved in the current generation, false otherwise.
+ */
+ private boolean hasFitnessImproved(double fitness)
+ {
+ return (naturalFitness && fitness > bestFitness)
+ || (!naturalFitness && fitness < bestFitness);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java
new file mode 100644
index 0000000..26c3298
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java
@@ -0,0 +1,63 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+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.
+ * @author Daniel Dyer
+ */
+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.
+ * @param natural Whether fitness scores are natural or non-natural. If fitness
+ * is natural, the condition will be satisfied if any individual has a fitness
+ * that is greater than or equal to the target fitness. If fitness is non-natural,
+ * the condition will be satisfied in any individual has a fitness that is less
+ * than or equal to the target fitness.
+ * @see org.uncommons.watchmaker.framework.FitnessEvaluator
+ */
+ public TargetFitness(double targetFitness, boolean natural)
+ {
+ this.targetFitness = targetFitness;
+ this.natural = natural;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ if (natural)
+ {
+ // If we're using "natural" fitness scores, higher values are better.
+ return populationData.getBestCandidateFitness() >= targetFitness;
+ }
+ else
+ {
+ // If we're using "non-natural" fitness scores, lower values are better.
+ return populationData.getBestCandidateFitness() <= targetFitness;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java
new file mode 100644
index 0000000..dce1aab
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+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.
+ * @see org.uncommons.watchmaker.swing.AbortControl
+ * @author Daniel Dyer
+ */
+public final class UserAbort implements TerminationCondition
+{
+ private volatile boolean aborted = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return isAborted();
+ }
+
+
+ /**
+ * Aborts any evolutionary algorithms that monitor this termination condition
+ * instance.
+ */
+ public void abort()
+ {
+ aborted = true;
+ }
+
+
+ /**
+ * @return true if the {@link #abort()} method has been invoked, false otherwise.
+ */
+ public boolean isAborted()
+ {
+ return aborted;
+ }
+
+
+ /**
+ * Resets the abort condition to false so that it may be reused.
+ */
+ public void reset()
+ {
+ aborted = false;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java
new file mode 100644
index 0000000..e68cb9d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java
@@ -0,0 +1,19 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Configurable conditions for terminating evolutionary algorithms.
+ */
+package org.uncommons.watchmaker.framework.termination;
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java
new file mode 100644
index 0000000..8aaae2f
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java
@@ -0,0 +1,137 @@
+//=============================================================================
+// 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.util.concurrent;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the general-purpose thread factory implementation.
+ * @author Daniel Dyer
+ */
+public class ConfigurableThreadFactoryTest
+{
+ @Test
+ public void testDaemonThreads()
+ {
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MIN_PRIORITY,
+ true);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ // Do nothing.
+ }
+ };
+ Thread thread1 = threadFactory.newThread(doNothing);
+ assert thread1.getName().startsWith("Test") : "Wrong thread name: " + thread1.getName();
+ assert thread1.getPriority() == Thread.MIN_PRIORITY : "Wrong priority: " + thread1.getPriority();
+ assert thread1.isDaemon() : "Thread should be a daemon.";
+
+ // Second thread should have a different name.
+ Thread thread2 = threadFactory.newThread(doNothing);
+ assert thread2.getName().startsWith("Test") : "Wrong thread name: " + thread2.getName();
+ assert !thread1.getName().equals(thread2.getName()) : "Thread names should be different.";
+ }
+
+
+ @Test
+ public void testNonDaemonThreads()
+ {
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ // Do nothing.
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ assert thread.getName().startsWith("Test") : "Wrong thread name: " + thread.getName();
+ assert thread.getPriority() == Thread.MAX_PRIORITY : "Wrong priority: " + thread.getPriority();
+ assert !thread.isDaemon() : "Thread should not be a daemon.";
+ }
+
+
+ @Test
+ public void testDefaultExceptionHandler() throws InterruptedException
+ {
+ // Intercept std. err.
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(byteStream));
+
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ throw new IllegalStateException("This is a test.");
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ thread.start();
+ thread.join();
+
+ String output = byteStream.toString();
+ assert output.startsWith("java.lang.IllegalStateException") : "Exception handler failed to log exception.";
+ }
+
+
+ @Test
+ public void testCustomExceptionHandler() throws InterruptedException
+ {
+ ExceptionHandler exceptionHandler = new ExceptionHandler();
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false,
+ exceptionHandler);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ throw new IllegalStateException("This is a test.");
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ thread.start();
+ thread.join();
+ assert exceptionHandler.getExceptionCount() == 1 : "Exception not thrown.";
+ }
+
+
+ private static final class ExceptionHandler implements Thread.UncaughtExceptionHandler
+ {
+ private final AtomicInteger count = new AtomicInteger();
+
+ public void uncaughtException(Thread thread, Throwable throwable)
+ {
+ count.incrementAndGet();
+ }
+
+ public int getExceptionCount()
+ {
+ return count.get();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java
new file mode 100644
index 0000000..79946c7
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// 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.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for composite ID source.
+ * @author Daniel Dyer
+ */
+public class CompositeIDSourceTest
+{
+ @Test
+ public void testCombination()
+ {
+ int topPart = 15;
+ IDSource<Long> idSource = new CompositeIDSource(topPart);
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ assert 64424509440L == firstID : "First ID should be 2^36 - 2^32 (or 15 shifted left 32 places).";
+ assert secondID == firstID + 1 : "Second ID should be first ID plus 1.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java
new file mode 100644
index 0000000..e9ae567
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java
@@ -0,0 +1,56 @@
+//=============================================================================
+// 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.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for 32-bit ID sequence.
+ * @author Daniel Dyer
+ */
+public class IntSequenceIDSourceTest
+{
+ @Test
+ public void testSequence()
+ {
+ IntSequenceIDSource idSource = new IntSequenceIDSource();
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ long thirdID = idSource.nextID();
+ assert firstID == 0 : "First ID should be 0.";
+ assert secondID == firstID + 1 : "Second ID should be 1 more than first ID.";
+ assert thirdID == secondID + 1 : "Third ID should be 1 more than second ID.";
+ }
+
+
+ @Test(expectedExceptions = IDSourceExhaustedException.class)
+ public void testExhaustion()
+ {
+ IDSource<Integer> idSource = new IntSequenceIDSource(Integer.MAX_VALUE);
+ // Should be able to get one ID from this.
+ int id = idSource.nextID();
+ assert id == Integer.MAX_VALUE : "Incorrect initial value: " + id;
+ // But the next invocation should result in an exception.
+ idSource.nextID();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidInitialValue()
+ {
+ new IntSequenceIDSource(-1);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java
new file mode 100644
index 0000000..4cfe0e0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java
@@ -0,0 +1,56 @@
+//=============================================================================
+// 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.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for 64-bit ID sequence.
+ * @author Daniel Dyer
+ */
+public class LongSequenceIDSourceTest
+{
+ @Test
+ public void testSequence()
+ {
+ LongSequenceIDSource idSource = new LongSequenceIDSource();
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ long thirdID = idSource.nextID();
+ assert firstID == 0 : "First ID should be 0.";
+ assert secondID == firstID + 1 : "Second ID should be 1 more than first ID.";
+ assert thirdID == secondID + 1 : "Third ID should be 1 more than second ID.";
+ }
+
+
+ @Test(expectedExceptions = IDSourceExhaustedException.class)
+ public void testExhaustion()
+ {
+ IDSource<Long> idSource = new LongSequenceIDSource(Long.MAX_VALUE);
+ // Should be able to get one ID from this.
+ long id = idSource.nextID();
+ assert id == Long.MAX_VALUE : "Incorrect initial value: " + id;
+ // But the next invocation should result in an exception.
+ idSource.nextID();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidInitialValue()
+ {
+ new LongSequenceIDSource(-1);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java
new file mode 100644
index 0000000..546c78c
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java
@@ -0,0 +1,35 @@
+//=============================================================================
+// 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.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for prefixed ID source.
+ * @author Daniel Dyer
+ */
+public class StringPrefixIDSourceTest
+{
+ @Test
+ public void testPrefix()
+ {
+ IDSource<String> idSource = new StringPrefixIDSource("Watchmaker", new IntSequenceIDSource());
+ String id1 = idSource.nextID();
+ assert id1.equals("Watchmaker0") : "Wrong ID: " + id1;
+ String id2 = idSource.nextID();
+ assert id2.equals("Watchmaker1") : "Wrong ID: " + id2;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java
new file mode 100644
index 0000000..5d660e0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java
@@ -0,0 +1,198 @@
+//=============================================================================
+// 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.util.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link ReflectionUtils}.
+ * @author Daniel Dyer
+ */
+public class ReflectionUtilsTest
+{
+ @Test
+ public void testFindMethod()
+ {
+ Method toString = ReflectionUtils.findKnownMethod(Object.class, "toString");
+ assert toString.getName().equals("toString") : "Wrong method returned: " + toString.getName();
+ // Make sure that the method returned is from the correct class and not some other class.
+ Class<?> declaringClass = toString.getDeclaringClass();
+ assert declaringClass.equals(Object.class) : "Method from wrong class returned: " + declaringClass.getName();
+ }
+
+
+ @Test(dependsOnMethods = "testFindMethod")
+ public void testSuccessfulInvocation()
+ {
+ Method toString = ReflectionUtils.findKnownMethod(Object.class, "toString");
+ String result = ReflectionUtils.invokeUnchecked(toString, "Hello");
+ assert result.equals("Hello") : "Wrong value returned by method.";
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoSuchMethod()
+ {
+ ReflectionUtils.findKnownMethod(Object.class, "noSuchMethod");
+ }
+
+
+ /**
+ * Invoking a method that throws a RuntimeException should result in that
+ * exception being thrown back to the caller. The RuntimeException should not
+ * be wrapped in an InvocationTargetException as would be the case for a
+ * normal reflective invocation.
+ */
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = ArithmeticException.class)
+ public void testMethodRuntimeExceptions()
+ {
+ Method divide = ReflectionUtils.findKnownMethod(BigDecimal.class, "divide", BigDecimal.class);
+ ReflectionUtils.invokeUnchecked(divide, BigDecimal.ONE, BigDecimal.ZERO); // Should throw ArithmeticException.
+ }
+
+
+ /**
+ * Invoking a method that throws an Error should result in that
+ * error being thrown back to the caller. The Error should not
+ * be wrapped in an InvocationTargetException as would be the case for a
+ * normal reflective invocation.
+ */
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = InternalError.class)
+ public void testErrors()
+ {
+ class ErrorTest
+ {
+ public void doError()
+ {
+ throw new InternalError();
+ }
+ }
+ Method error = ReflectionUtils.findKnownMethod(ErrorTest.class, "doError");
+ ReflectionUtils.invokeUnchecked(error, new ErrorTest()); // Should throw InternalError.
+ }
+
+
+ @Test
+ public void testFindConstructor()
+ {
+ Constructor<Object> defaultConstructor = ReflectionUtils.findKnownConstructor(Object.class);
+ // Make sure that the method returned is from the correct class and not some other class.
+ Class<?> declaringClass = defaultConstructor.getDeclaringClass();
+ assert declaringClass.equals(Object.class) : "Constructor from wrong class returned: " + declaringClass.getName();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoSuchConstructor()
+ {
+ ReflectionUtils.findKnownConstructor(Object.class, String.class);
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalStateException.class)
+ public void testExceptionInConstructor()
+ {
+ Constructor<ExceptionTest> constructor = ReflectionUtils.findKnownConstructor(ExceptionTest.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = InternalError.class)
+ public void testErrorInConstructor()
+ {
+ Constructor<ErrorTest> constructor = ReflectionUtils.findKnownConstructor(ErrorTest.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw error.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testConstructingAbstractClass()
+ {
+ Constructor<Abstract> constructor = ReflectionUtils.findKnownConstructor(Abstract.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw an exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testUnconstructable() throws NoSuchMethodException
+ {
+ Constructor<Unconstructable> constructor = Unconstructable.class.getDeclaredConstructor();
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw an exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testInaccessibleMethod() throws NoSuchMethodException
+ {
+ Method method = Inaccessible.class.getDeclaredMethod("inaccessibleMethod");
+ ReflectionUtils.invokeUnchecked(method, new Inaccessible()); // Should throw an exception.
+ }
+
+
+ private static class ExceptionTest
+ {
+ public ExceptionTest()
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+
+ private static class ErrorTest
+ {
+ public ErrorTest()
+ {
+ throw new InternalError();
+ }
+ }
+
+
+ private abstract static class Abstract
+ {
+ public Abstract()
+ {
+ // Do nothing.
+ }
+ }
+
+
+ private static class Unconstructable
+ {
+ private Unconstructable()
+ {
+ // Do nothing.
+ }
+ }
+
+
+ private static class Inaccessible
+ {
+ private void inaccessibleMethod()
+ {
+ // Do nothing.
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java
new file mode 100644
index 0000000..f77bcff
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link CachingFitnessEvaluator} wrapper.
+ * @author Daniel Dyer
+ */
+public class CachingFitnessEvaluatorTest
+{
+ @Test
+ public void testCacheMiss()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ double fitness = evaluator.getFitness("Test1", Collections.<String>emptyList());
+ assert fitness == 1 : "Wrong fitness: " + fitness;
+ // Different candidate so shouldn't return a cached value.
+ fitness = evaluator.getFitness("Test2", Collections.<String>emptyList());
+ assert fitness == 2 : "Wrong fitness: " + fitness;
+
+ }
+
+
+ @Test
+ public void testCacheHit()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ double fitness = evaluator.getFitness("Test", Collections.<String>emptyList());
+ assert fitness == 1 : "Wrong fitness: " + fitness;
+ fitness = evaluator.getFitness("Test", Collections.<String>emptyList());
+ // If the value is found in the cache it won't have changed. If it is recalculated, it will have.
+ assert fitness == 1 : "Expected cached value (1), got " + fitness;
+ }
+
+
+ @Test
+ public void testNatural()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ assert evaluator.isNatural() : "Wrapper for natural scores should also be natural.";
+ }
+
+
+ @Test
+ public void testNonNatural()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(false));
+ assert !evaluator.isNatural() : "Wrapper for non-natural scores should also be non-natural.";
+ }
+
+
+ /**
+ * This breaks the rules for the caching evaluator in that it is not repeatable
+ * (it returns different values when invoked multiple times for the same candidate),
+ * but it allows us to see whether we are getting a cached value or a new value.
+ */
+ private static final class IncrementingEvaluator implements FitnessEvaluator<String>
+ {
+ private final boolean natural;
+ private int count = 0;
+
+ IncrementingEvaluator(boolean natural)
+ {
+ this.natural = natural;
+ }
+
+ public double getFitness(String candidate, List<? extends String> population)
+ {
+ return ++count;
+ }
+
+ public boolean isNatural()
+ {
+ return natural;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java
new file mode 100644
index 0000000..c0d74d4
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java
@@ -0,0 +1,102 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the simple {@link EvaluatedCandidate} class. Ensures that
+ * the equals and hashCode methods function correctly.
+ * @author Daniel Dyer
+ */
+public class EvaluatedCandidateTest
+{
+ @Test
+ public void testEquality()
+ {
+ // Equality is determined only by fitness score, the actual candidate
+ // representation is irrelevant. These two candidates should be considered
+ // equal.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("BBBB", 5);
+
+ assert candidate1.equals(candidate1) : "Equality must be reflexive.";
+ assert candidate2.equals(candidate2) : "Equality must be reflexive.";
+
+ assert candidate1.hashCode() == candidate2.hashCode() : "Hash codes must be identical for equal objects.";
+ assert candidate1.compareTo(candidate2) == 0 : "compareTo() must be consistent with equals()";
+
+ assert candidate1.equals(candidate2) : "Candidates with equal fitness should be equal.";
+ assert candidate2.equals(candidate1) : "Equality must be symmetric.";
+ }
+
+
+ @Test
+ public void testNotEqual()
+ {
+ // Equality is determined only by fitness score, the actual candidate
+ // representation is irrelevant. These two candidates should be considered
+ // unequal.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("AAAA", 7);
+
+ assert !candidate1.equals(candidate2) : "Candidates with equal fitness should be equal.";
+ assert !candidate2.equals(candidate1) : "Equality must be symmetric.";
+
+ assert candidate1.compareTo(candidate2) != 0 : "compareTo() must be consistent with equals()";
+ }
+
+
+ @Test
+ public void testNullEquality()
+ {
+ EvaluatedCandidate<String> candidate = new EvaluatedCandidate<String>("AAAA", 5);
+ assert !candidate.equals(null) : "Object must not be considered equal to null reference.";
+ }
+
+
+ @Test
+ public void testDifferentClassEquality()
+ {
+ EvaluatedCandidate<String> candidate = new EvaluatedCandidate<String>("AAAA", 5);
+ assert !candidate.equals(new Object()) : "Object must not be equal to instances of other classes.";
+ }
+
+
+ /**
+ * Comparisons are based only on fitness score.
+ */
+ @Test(dependsOnMethods = "testEquality")
+ public void testComparisons()
+ {
+ // Only test greater than and less than comparisons here since we've already
+ // done equality.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("AAAA", 7);
+ assert candidate1.compareTo(candidate2) < 0 : "Incorrect ordering.";
+ assert candidate2.compareTo(candidate1) > 0 : "Incorrect ordering.";
+ }
+
+
+ /**
+ * Negative fitness scores are not supported. An informative exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNegativeFitness()
+ {
+ new EvaluatedCandidate<String>("ABC", -1); // Should throw an exception.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java
new file mode 100644
index 0000000..f07c354
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+
+/**
+ * Unit test for {@link EvolutionStrategyEngine} class.
+ * @author Daniel Dyer
+ */
+public class EvolutionStrategyEngineTest
+{
+ @Test
+ public void testOnePlusOneEvolutionStrategy()
+ {
+ EvolutionStrategyEngine<Integer> engine = new EvolutionStrategyEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(-1),
+ new IntegerEvaluator(),
+ true,
+ 1,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1));
+
+ List<EvaluatedCandidate<Integer>> evolvedPopulation
+ = engine.nextEvolutionStep(population, 0, FrameworkTestUtils.getRNG());
+ assert evolvedPopulation.size() == 1 : "Population size should be 1, is " + evolvedPopulation.size();
+ // The offspring is less fit than the parent (due to the -1 operator) so the parent should be retained.
+ assert evolvedPopulation.get(0).getCandidate() == 1 : "Wrong individual after evolution.";
+ }
+
+
+ @Test
+ public void testOneCommaOneEvolutionStrategy()
+ {
+ EvolutionStrategyEngine<Integer> engine = new EvolutionStrategyEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(-1),
+ new IntegerEvaluator(),
+ false,
+ 1,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1));
+
+ List<EvaluatedCandidate<Integer>> evolvedPopulation
+ = engine.nextEvolutionStep(population, 0, FrameworkTestUtils.getRNG());
+ assert evolvedPopulation.size() == 1 : "Population size should be 1, is " + evolvedPopulation.size();
+ // The offspring is less fit than the parent (due to the -1 operator) but the parent is not allowed to survive.
+ assert evolvedPopulation.get(0).getCandidate() == 0 : "Wrong individual after evolution.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java
new file mode 100644
index 0000000..316cd85
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Random;
+import org.uncommons.maths.random.XORShiftRNG;
+
+/**
+ * Utility methods for Watchmaker Framework unit tests. Provides
+ * access to shared resources used by tests.
+ * @author Daniel Dyer
+ */
+public final class FrameworkTestUtils
+{
+ private static final Random RNG = new XORShiftRNG();
+
+ private FrameworkTestUtils()
+ {
+ // Prevent instantiation.
+ }
+
+
+ /**
+ * Returns the singleton RNG shared by all tests. It might be preferable
+ * to have a separate RNG for each test (for true separation) but this
+ * causes problems. Seeding dozens of RNGs can exhaust the system's
+ * available entropy (the Uncommons Maths RNGs seed themselves from
+ * /dev/random by default).
+ * @return A random number generator.
+ */
+ public static Random getRNG()
+ {
+ return RNG;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java
new file mode 100644
index 0000000..6622229
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java
@@ -0,0 +1,169 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.ElapsedTime;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * Unit test for the {@link GenerationalEvolutionEngine} class.
+ * @author Daniel Dyer
+ */
+public class GenerationalEvolutionEngineTest
+{
+ private EvolutionEngine<Integer> engine;
+
+
+ @BeforeMethod
+ public void prepareEngine()
+ {
+ this.engine = new GenerationalEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerZeroMaker(),
+ new IntegerEvaluator(),
+ new RouletteWheelSelection(),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ @Test
+ public void testElitism()
+ {
+ class ElitismObserver implements EvolutionObserver<Integer>
+ {
+ private PopulationData<? extends Integer> data;
+
+ public void populationUpdate(PopulationData<? extends Integer> data)
+ {
+ this.data = data;
+ }
+
+ public double getAverageFitness()
+ {
+ return data.getMeanFitness();
+ }
+ }
+ ElitismObserver observer = new ElitismObserver();
+ engine.addEvolutionObserver(observer);
+ List<Integer> elite = new ArrayList<Integer>(3);
+ // Add the following seed candidates, all better than any others that can possibly
+ // get into the population (since every other candidate will always be zero).
+ elite.add(7); // This candidate should be discarded by elitism.
+ elite.add(11);
+ elite.add(13);
+ engine.evolve(10,
+ 2,
+ elite,
+ new GenerationCount(2)); // Do at least 2 generations because the first is just the initial population.
+ // 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();
+ engine.removeEvolutionObserver(observer);
+ }
+
+
+ /**
+ * The number of candidates preserved by elitism must be less than the total
+ * population size.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEliteCountTooHigh()
+ {
+ engine.evolve(10, 10, new GenerationCount(10)); // Should throw exception because elite is too big.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEliteCountTooLow()
+ {
+ engine.evolvePopulation(10, -1, new GenerationCount(10)); // Should throw exception because elite is negative.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoTerminationConditions()
+ {
+ engine.evolve(10, 0); // Should throw exception because there are no termination conditions.
+ }
+
+
+ @Test
+ public void testInterrupt()
+ {
+ final long timeout = 1000L;
+ final Thread requestThread = Thread.currentThread();
+ engine.addEvolutionObserver(new EvolutionObserver<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ if (populationData.getElapsedTime() > timeout / 2)
+ {
+ requestThread.interrupt();
+ }
+ }
+ });
+ 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.";
+ }
+
+
+ @Test
+ public void testGetSatisfiedTerminationConditions()
+ {
+ GenerationCount generationsCondition = new GenerationCount(1);
+ engine.evolve(10, 0, generationsCondition);
+ List<TerminationCondition> satisfiedConditions = engine.getSatisfiedTerminationConditions();
+ assert satisfiedConditions.size() == 1 : "Wrong number of conditions: " + satisfiedConditions.size();
+ assert satisfiedConditions.get(0) == generationsCondition : "Wrong condition returned.";
+ }
+
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testGetSatisfiedTerminationConditionsBeforeStart()
+ {
+ // Should throw an IllegalStateException because evolution hasn't started, let alone terminated.
+ engine.getSatisfiedTerminationConditions();
+ }
+
+
+ /**
+ * Trivial test operator that mutates all integers into zeroes.
+ */
+ private static final class IntegerZeroMaker implements EvolutionaryOperator<Integer>
+ {
+ public List<Integer> apply(List<Integer> selectedCandidates, Random rng)
+ {
+ List<Integer> result = new ArrayList<Integer>(selectedCandidates.size());
+ for (int i = 0; i < selectedCandidates.size(); i++)
+ {
+ result.add(0);
+ }
+ return result;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java
new file mode 100644
index 0000000..fc54bcb
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java
@@ -0,0 +1,37 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * Trivial fitness evaluator for integers. Used by unit tests.
+ * @author Daniel Dyer
+ */
+final class IntegerEvaluator implements FitnessEvaluator<Integer>
+{
+
+ public double getFitness(Integer candidate,
+ List<? extends Integer> population)
+ {
+ return candidate;
+ }
+
+ public boolean isNatural()
+ {
+ return true;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java
new file mode 100644
index 0000000..6678269
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java
@@ -0,0 +1,138 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+
+/**
+ * Unit test for the {@link SteadyStateEvolutionEngine} class.
+ * @author Daniel Dyer
+ */
+public class SteadyStateEvolutionEngineTest
+{
+ /**
+ * A single iteration should update only a single candidate.
+ */
+ @Test
+ public void testIncrementalEvolution()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(5),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 1,
+ true,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0));
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 0,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ int unchangedCount = 0;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 1)
+ {
+ ++unchangedCount;
+ }
+ }
+ assert unchangedCount == 4 : "Should be 4 out of 5 candidates unchanged, is " + unchangedCount;
+ }
+
+
+ /**
+ * Even if the evolutionary operator generates multiple offspring, only a single individual should be
+ * replaced if the forceSingleUpdate flag is set.
+ */
+ @Test
+ public void testForcedSingleCandidateUpdate()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(5),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 2,
+ true, // Force single update.
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0));
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 0,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ int unchangedCount = 0;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 1)
+ {
+ ++unchangedCount;
+ }
+ }
+ assert unchangedCount == 4 : "Should be 4 out of 5 candidates unchanged, is " + unchangedCount;
+ }
+
+
+
+ @Test
+ public void testElitism()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(10),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 1,
+ true,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1),
+ new EvaluatedCandidate<Integer>(2, 2),
+ new EvaluatedCandidate<Integer>(3, 3),
+ new EvaluatedCandidate<Integer>(4, 4),
+ new EvaluatedCandidate<Integer>(5, 5));
+ // The fittest candidate should always be preserved.
+ for (int i = 0; i < 20; i++) // Once is not enough to be confident.
+ {
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 1,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ boolean found = false;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 5)
+ {
+ found = true;
+ break;
+ }
+ }
+ assert found : "Elite candidate should be preserved.";
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java
new file mode 100644
index 0000000..0e06412
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java
@@ -0,0 +1,93 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for bit string candidate factory.
+ * @author Daniel Dyer
+ */
+public class BitStringFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ List<BitString> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ validatePopulation(population);
+ }
+
+
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ BitString seed1 = new BitString("1111100000");
+ BitString seed2 = new BitString("1010101010");
+ List<BitString> seeds = new ArrayList<BitString>(2);
+ seeds.add(seed1);
+ seeds.add(seed2);
+ List<BitString> population = factory.generateInitialPopulation(populationSize,
+ seeds,
+ FrameworkTestUtils.getRNG());
+
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ BitString candidate = new BitString(candidateLength);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList(candidate, candidate, candidate),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ private void validatePopulation(List<BitString> population)
+ {
+ // Make sure the correct number of candidates were generated.
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ // Make sure that each individual is the right length.
+ for (BitString bitString : population)
+ {
+ assert bitString.getLength() == candidateLength : "Bit string is wrong length: " + bitString.getLength();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java
new file mode 100644
index 0000000..b2df2e9
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java
@@ -0,0 +1,125 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the list permutation candidate factory. Checks that it
+ * correctly generates populations of permutations.
+ * @author Daniel Dyer
+ */
+public class ListPermutationFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+ private final List<Integer> elements = new ArrayList<Integer>(candidateLength);
+ {
+ for (int i = 1; i <= candidateLength; i++)
+ {
+ elements.add(i);
+ }
+ }
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+ List<List<Integer>> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+
+ List<Integer> seed1 = new ArrayList<Integer>(elements);
+ List<Integer> seed2 = new ArrayList<Integer>(elements);
+ Collections.reverse(elements);
+ List<List<Integer>> seeds = new ArrayList<List<Integer>>(2);
+ seeds.add(seed1);
+ seeds.add(seed2);
+
+ List<List<Integer>> population = factory.generateInitialPopulation(populationSize,
+ seeds,
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+
+ List<List<Integer>> seeds = new LinkedList<List<Integer>>();
+ seeds.add(elements);
+ seeds.add(elements);
+ seeds.add(elements);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2, seeds, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Make sure each candidate is valid (contains each element exactly once).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<List<Integer>> population)
+ {
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ for (List<Integer> candidate : population)
+ {
+ assert candidate.size() == candidateLength : "Wrong size candidate generated: " + candidate.size();
+ for (int i = 1; i < candidateLength; i++)
+ {
+ assert candidate.contains(i) : "Candidate is missing element " + i + ".";
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java
new file mode 100644
index 0000000..e4145c1
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java
@@ -0,0 +1,126 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the array permutation candidate factory. Checks that it
+ * correctly generates populations of permutations.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayPermutationFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+ private final Integer[] elements = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+ List<Integer[]> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+
+ Integer[] seed1 = elements.clone();
+ Integer[] seed2 = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ List<Integer[]> population = factory.generateInitialPopulation(populationSize,
+ Arrays.asList(seed1, seed2),
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList(elements, elements, elements),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Make sure each candidate is valid (contains each element exactly once).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<Integer[]> population)
+ {
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ for (Integer[] candidate : population)
+ {
+ assert candidate.length == candidateLength : "Wrong size candidate generated: " + candidate.length;
+ for (int i = 1; i < candidateLength; i++)
+ {
+ assert contains(candidate, i) : "Candidate is missing element " + i + ".";
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the specified element occurs in the specified array.
+ */
+ private boolean contains(Integer[] array, int element)
+ {
+ for (int i : array)
+ {
+ if (i == element)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java
new file mode 100644
index 0000000..350a024
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * @author Daniel Dyer
+ */
+public class StringFactoryTest
+{
+ private final int candidateLength = 8;
+ private final int populationSize = 5;
+
+ private final char[] alphabet = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+ List<String> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+
+ String seed1 = "cdefghij";
+ String seed2 = "bbbbbbbb";
+
+ List<String> population = factory.generateInitialPopulation(populationSize,
+ Arrays.asList(seed1, seed2),
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList("abcdefgh", "ijklmnop", "qrstuvwx"),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+
+
+ /**
+ * Make sure each candidate is valid (is the right length and contains only
+ * valid characters).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<String> population)
+ {
+ for (String candidate : population)
+ {
+ assert candidate.length() == candidateLength : "Wrong length candidate: " + candidate.length();
+ for (char c : candidate.toCharArray())
+ {
+ assert c >= 'a' && c <= 'j' : "Invalid character: " + c;
+ }
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java
new file mode 100644
index 0000000..ecfe422
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java
@@ -0,0 +1,30 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+
+/**
+ * Stub candidate factory for tests. Always returns zero-valued integers.
+ * @author Daniel Dyer
+ */
+public final class StubIntegerFactory extends AbstractCandidateFactory<Integer>
+{
+ public Integer generateRandomCandidate(Random rng)
+ {
+ return 0;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java
new file mode 100644
index 0000000..a3f26b6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java
@@ -0,0 +1,146 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for user-guided selection strategy.
+ * @author Daniel Dyer
+ */
+public class InteractiveSelectionTest
+{
+ @Test
+ public void testSingleSelectionPerGeneration()
+ {
+ final int groupSize = 2;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 1);
+ List<EvaluatedCandidate<Integer>> population = new ArrayList<EvaluatedCandidate<Integer>>(5);
+ population.add(new EvaluatedCandidate<Integer>(1, 0));
+ population.add(new EvaluatedCandidate<Integer>(2, 0));
+ population.add(new EvaluatedCandidate<Integer>(3, 0));
+ population.add(new EvaluatedCandidate<Integer>(4, 0));
+ population.add(new EvaluatedCandidate<Integer>(5, 0));
+
+ List<Integer> selection = strategy.select(population, true, 3, FrameworkTestUtils.getRNG());
+ assert selection.size() == 3 : "Incorrect selection size: " + selection.size();
+ assert console.getSelectionCount() == 1 : "Wrong number of user selections: " + console.getSelectionCount();
+ // All 3 selected individuals should be the same since the strategy doubles up
+ // selections when configured to restrict the number of user choices per generation.
+ assert selection.get(0).equals(selection.get(1)) : "Incorrect selection.";
+ assert selection.get(1).equals(selection.get(2)) : "Incorrect selection.";
+ assert selection.get(0).equals(selection.get(2)) : "Incorrect selection.";
+ }
+
+
+ @Test
+ public void testMultipleSelectionsPerGeneration()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 3);
+ List<EvaluatedCandidate<Integer>> population = new ArrayList<EvaluatedCandidate<Integer>>(5);
+ population.add(new EvaluatedCandidate<Integer>(1, 0));
+ population.add(new EvaluatedCandidate<Integer>(2, 0));
+ population.add(new EvaluatedCandidate<Integer>(3, 0));
+ population.add(new EvaluatedCandidate<Integer>(4, 0));
+ population.add(new EvaluatedCandidate<Integer>(5, 0));
+
+ List<Integer> selection = strategy.select(population, true, 3, FrameworkTestUtils.getRNG());
+ assert selection.size() == 3 : "Incorrect selection size.";
+ assert console.getSelectionCount() == 3 : "Wrong number of user selections: " + console.getSelectionCount();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMaxSelections()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ // This should throw an exception because max selections must be at least 1.
+ new InteractiveSelection<Integer>(console, groupSize, 0);
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidGroupSize()
+ {
+ final int groupSize = 1;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ // This should throw an exception because group size must be at least 2.
+ new InteractiveSelection<Integer>(console, groupSize, 1);
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testGroupSizeTooBigForPopulation()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 1);
+ List<EvaluatedCandidate<Integer>> population = new LinkedList<EvaluatedCandidate<Integer>>();
+ population.add(new EvaluatedCandidate<Integer>(1, 1.0));
+ population.add(new EvaluatedCandidate<Integer>(1, 2.0));
+ // This should fail because a population of 2 is not big enough with a
+ // group size of 5.
+ strategy.select(population, true, 2, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Automated test console implementation that simply selects an
+ * individual at random.
+ */
+ private final class RandomConsole<T> implements Console<T>
+ {
+ private final int expectedGroupSize;
+
+ /** Count how many times the select method is called. */
+ private int selectionCount = 0;
+
+ RandomConsole(int expectedGroupSize)
+ {
+ this.expectedGroupSize = expectedGroupSize;
+ }
+
+
+ public int select(List<? extends T> renderedEntities)
+ {
+ assert renderedEntities.size() == expectedGroupSize : "Wrong selection group size.";
+ ++selectionCount;
+ return FrameworkTestUtils.getRNG().nextInt(renderedEntities.size());
+ }
+
+
+ public int getSelectionCount()
+ {
+ return selectionCount;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java
new file mode 100644
index 0000000..717f65e
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.Date;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test to ensure that renderer chaining works correctly.
+ * @author Daniel Dyer
+ */
+public class RendererAdapterTest
+{
+ @Test
+ public void testChaining()
+ {
+ Renderer<Long, Date> longToDate = new TimestampToDateRenderer();
+ Renderer<Date, String> dateToString = new DateToStringRenderer();
+
+ long currentTime = System.currentTimeMillis();
+ Date date = longToDate.render(currentTime);
+ String expectedOutput = dateToString.render(date);
+
+ Renderer<Long, String> longToString = new RendererAdapter<Long, String>(longToDate,
+ dateToString);
+ String actualOutput = longToString.render(currentTime);
+ assert actualOutput.equals(expectedOutput) : "Actual/expected output mismatch: " + actualOutput;
+ }
+
+
+ /**
+ * Example renderer for converting a number of milliseconds since 00:00 on 1st
+ * January 1970 into a Java {@link Date} object.
+ */
+ private static final class TimestampToDateRenderer implements Renderer<Long, Date>
+ {
+ public Date render(Long timestamp)
+ {
+ return new Date(timestamp);
+ }
+ }
+
+
+ /**
+ * Example renderer for converting a Java {@link Date} object into a date String.
+ */
+ private static final class DateToStringRenderer implements Renderer<Date, String>
+ {
+ public String render(Date date)
+ {
+ return date.toString();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java
new file mode 100644
index 0000000..98fc00d
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java
@@ -0,0 +1,143 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.ElapsedTime;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * 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.
+ */
+ @Test
+ public void testListeners()
+ {
+ final int islandCount = 3;
+ final int epochCount = 2;
+ final int generationCount = 5;
+
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(islandCount,
+ 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<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ observedEpochCount[0]++;
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends Integer> 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];
+ 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];
+ }
+ }
+
+
+ @Test
+ public void testInterrupt()
+ {
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(2,
+ 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<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ if (populationData.getElapsedTime() > timeout / 2)
+ {
+ requestThread.interrupt();
+ }
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends Integer> 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.";
+ }
+
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testGetSatisfiedTerminationConditionsBeforeStart()
+ {
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(3,
+ 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<Integer>
+ {
+ public double getFitness(Integer candidate, List<? extends Integer> population)
+ {
+ return 0;
+ }
+
+ public boolean isNatural()
+ {
+ return true;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java
new file mode 100644
index 0000000..3564a31
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java
@@ -0,0 +1,55 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Utility methods used by unit tests for migration strategies.
+ * @author Daniel Dyer
+ */
+class MigrationTestUtils
+{
+ private MigrationTestUtils()
+ {
+ // Prevents instantiation.
+ }
+
+
+ public static <T> List<EvaluatedCandidate<T>> createTestPopulation(T... members)
+ {
+ List<EvaluatedCandidate<T>> population = new ArrayList<EvaluatedCandidate<T>>(members.length);
+ for (T member : members)
+ {
+ population.add(new EvaluatedCandidate<T>(member, 0));
+ }
+ return population;
+ }
+
+
+ public static void testPopulationContents(List<EvaluatedCandidate<String>> actualPopulation,
+ String... expectedPopulation)
+ {
+ assert actualPopulation.size() == expectedPopulation.length : "Wrong population size after migration.";
+ for (int i = 0; i < actualPopulation.size(); i++)
+ {
+ assert actualPopulation.get(i).getCandidate().equals(expectedPopulation[i]) : "Wrong value at index " + i;
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java
new file mode 100644
index 0000000..566f68b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link RingMigration} class.
+ * @author Daniel Dyer
+ */
+public class RandomMigrationTest
+{
+ /**
+ * Make sure that nothing strange happens when there is no migration.
+ */
+ @Test
+ public void testZeroMigration()
+ {
+ Migration migration = new RandomMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 0, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3 : "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "B", "B", "B");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "C", "C", "C");
+ }
+
+
+ /**
+ * Make sure that nothing strange happens when the entire island is migrated.
+ */
+ @Test
+ public void testNonZeroMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 3, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3: "Wrong number of populations after migration.";
+ // Each population should still have 3 members (but it's not sure which members).
+ assert islandPopulations.get(0).size() == 3 : "Wrong population size.";
+ assert islandPopulations.get(1).size() == 3 : "Wrong population size.";
+ assert islandPopulations.get(2).size() == 3 : "Wrong population size.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java
new file mode 100644
index 0000000..ce86058
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link RingMigration} class.
+ * @author Daniel Dyer
+ */
+public class RingMigrationTest
+{
+ /**
+ * Make sure that nothing strange happens when there is no migration.
+ */
+ @Test
+ public void testZeroMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 0, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3 : "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "B", "B", "B");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "C", "C", "C");
+ }
+
+
+ /**
+ * Make sure that nothing strange happens when the entire island is migrated.
+ */
+ @Test
+ public void testFullMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 3, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3: "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "C", "C", "C");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "B", "B", "B");
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java
new file mode 100644
index 0000000..58d4127
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.factories.BitStringFactory;
+
+/**
+ * Unit test for cross-over applied to bit strings.
+ * @author Daniel Dyer
+ */
+public class BitStringCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<BitString> operator = new BitStringCrossover();
+ CandidateFactory<BitString> factory = new BitStringFactory(50);
+ List<BitString> population = factory.generateInitialPopulation(2, FrameworkTestUtils.getRNG());
+ // Test to make sure that cross-over correctly preserves all genetic material
+ // originally present in the population and does not introduce anything new.
+ int totalSetBits = population.get(0).countSetBits() + population.get(1).countSetBits();
+ for (int i = 0; i < 50; i++) // Test several generations.
+ {
+ population = operator.apply(population, FrameworkTestUtils.getRNG());
+ int setBits = population.get(0).countSetBits() + population.get(1).countSetBits();
+ assert setBits == totalSetBits : "Total number of set bits in population changed during cross-over.";
+ }
+ }
+
+
+ /**
+ * The {@link BitStringCrossover} operator is only defined to work on populations
+ * containing Strings of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length Strings should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<BitString> crossover = new BitStringCrossover(new ConstantGenerator<Integer>(1));
+ List<BitString> population = new ArrayList<BitString>(2);
+ population.add(new BitString(32, FrameworkTestUtils.getRNG()));
+ population.add(new BitString(33, FrameworkTestUtils.getRNG()));
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java
new file mode 100644
index 0000000..ccc5d9b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for mutation of bit strings.
+ * @author Daniel Dyer
+ */
+public class BitStringMutationTest
+{
+ /**
+ * Ensures that mutation occurs correctly. Because of the random
+ * aspect we can't actually make many assertions. This just ensures
+ * that there are no unexpected exceptions and that the length of
+ * the bit strings remains as expected.
+ */
+ @Test
+ public void testRandomMutation()
+ {
+ EvolutionaryOperator<BitString> mutation = new BitStringMutation(Probability.EVENS);
+ BitString original = new BitString("111100101");
+ List<BitString> population = Arrays.asList(original);
+ for (int i = 0; i < 20; i++) // Perform several iterations to get different mutations.
+ {
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ BitString mutated = population.get(0);
+ assert mutated.getLength() == 9 : "Mutated bit string changed length.";
+ }
+ }
+
+
+ /**
+ * Ensures that mutation occurs correctly. Uses a probability of 1 to
+ * make the outcome predictable (all bits will be flipped).
+ */
+ @Test
+ public void testSingleBitMutation()
+ {
+ BitString original = new BitString("111100101");
+ EvolutionaryOperator<BitString> mutation = new BitStringMutation(Probability.ONE);
+ List<BitString> population = Arrays.asList(original);
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ BitString mutated = population.get(0);
+ assert !mutated.equals(original) : "Mutation should be different from original.";
+ assert mutated.getLength() == 9 : "Mutated bit string changed length.";
+ int set = mutated.countSetBits();
+ int unset = mutated.countUnsetBits();
+ assert set == 5 || set == 7 : "Mutated bit string has wrong number of 1s: " + set;
+ assert unset == 2 || unset == 4 : "Mutated bit string has wrong number of 0s: " + unset;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java
new file mode 100644
index 0000000..82a4b02
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with byte arrays.
+ * @author Daniel Dyer
+ */
+public class ByteArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<byte[]> crossover = new ByteArrayCrossover();
+ List<byte[]> population = new ArrayList<byte[]>(4);
+ population.add(new byte[]{1, 2, 3, 4, 5});
+ population.add(new byte[]{6, 7, 8, 9, 10});
+ population.add(new byte[]{11, 12, 13, 14, 15});
+ population.add(new byte[]{16, 17, 18, 19, 20});
+ Set<Byte> values = new HashSet<Byte>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (byte[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (byte value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link ByteArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<byte[]> crossover = new ByteArrayCrossover(1, Probability.ONE);
+ List<byte[]> population = new ArrayList<byte[]>(2);
+ population.add(new byte[]{1, 2, 3, 4, 5});
+ population.add(new byte[]{2, 4, 8, 10, 12, 14, 16});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java
new file mode 100644
index 0000000..54e6c61
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with character arrays.
+ * @author Daniel Dyer
+ */
+public class CharArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<char[]> crossover = new CharArrayCrossover();
+ List<char[]> population = new ArrayList<char[]>(4);
+ population.add(new char[]{'a', 'b', 'c', 'd', 'e'});
+ population.add(new char[]{'f', 'g', 'h', 'i', 'j'});
+ population.add(new char[]{'k', 'l', 'm', 'n', 'o'});
+ population.add(new char[]{'p', 'q', 'r', 's', 't'});
+ Set<Character> values = new HashSet<Character>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (char[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (char value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link CharArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<char[]> crossover = new CharArrayCrossover(1, Probability.ONE);
+ List<char[]> population = new ArrayList<char[]>(2);
+ population.add(new char[]{'a', 'b', 'c', 'd', 'e'});
+ population.add(new char[]{'f', 'g', 'h', 'i', 'j', 'k'});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java
new file mode 100644
index 0000000..9b46c61
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java
@@ -0,0 +1,82 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with floating point arrays.
+ * @author Daniel Dyer
+ */
+public class DoubleArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<double[]> crossover = new DoubleArrayCrossover(new ConstantGenerator<Integer>(1),
+ new ConstantGenerator<Probability>(Probability.ONE));
+ List<double[]> population = new ArrayList<double[]>(4);
+ population.add(new double[]{1.1d, 2.2d, 3.3d, 4.4d, 5.5d});
+ population.add(new double[]{6.6d, 7.7d, 8.8d, 9.9d, 10});
+ population.add(new double[]{11, 12, 13, 14, 15});
+ population.add(new double[]{16, 17, 18, 19, 20});
+ Set<Double> values = new HashSet<Double>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (double[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (double value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link DoubleArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<double[]> crossover = new DoubleArrayCrossover();
+ List<double[]> population = new ArrayList<double[]>(2);
+ population.add(new double[]{1.1d, 2.2d, 3.3d, 4.4d, 5.5d});
+ population.add(new double[]{6.6d, 7.7d, 8.8d});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java
new file mode 100644
index 0000000..469f6e6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java
@@ -0,0 +1,69 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for compound, sequential evolutionary schemes.
+ * @author Daniel Dyer
+ */
+public class EvolutionPipelineTest
+{
+ /**
+ * Make sure that multiple operators in a pipeline are applied correctly
+ * to the population and validate the cumulative effects.
+ */
+ @Test
+ public void testCompoundEvolution()
+ {
+ List<Integer> population = new ArrayList<Integer>(10);
+ for (int i = 10; i <= 100; i += 10)
+ {
+ population.add(i);
+ }
+ // Increment 30% of the numbers and decrement the other 70%.
+ List<EvolutionaryOperator<Integer>> operators = new ArrayList<EvolutionaryOperator<Integer>>(2);
+ operators.add(new IntegerAdjuster(1));
+ operators.add(new IntegerAdjuster(3));
+ EvolutionPipeline<Integer> evolutionScheme = new EvolutionPipeline<Integer>(operators);
+ population = evolutionScheme.apply(population, FrameworkTestUtils.getRNG());
+ // Net result should be each candidate increased by 4.
+ int aggregate = 0;
+ for (Integer i : population)
+ {
+ aggregate += i;
+ assert (i % 10 == 4) : "Candidate should have increased by 4, is " + i;
+ }
+ assert aggregate == 590 : "Aggregate should be 590 after mutations, is " + aggregate;
+ }
+
+
+ /**
+ * An empty pipeline is not allowed. An exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEmptyPipeline()
+ {
+ List<EvolutionaryOperator<String>> operators = Collections.emptyList();
+ new EvolutionPipeline<String>(operators); // Should throw an IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java
new file mode 100644
index 0000000..2ec9ef0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with integer arrays.
+ * @author Daniel Dyer
+ */
+public class IntArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<int[]> crossover = new IntArrayCrossover();
+ List<int[]> population = new ArrayList<int[]>(4);
+ population.add(new int[]{1, 2, 3, 4, 5});
+ population.add(new int[]{6, 7, 8, 9, 10});
+ population.add(new int[]{11, 12, 13, 14, 15});
+ population.add(new int[]{16, 17, 18, 19, 20});
+ Set<Integer> values = new HashSet<Integer>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (int[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (int value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link IntArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<int[]> crossover = new IntArrayCrossover(1, Probability.ONE);
+ List<int[]> population = new ArrayList<int[]>(2);
+ population.add(new int[]{1, 2, 3, 4, 5});
+ population.add(new int[]{2});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java
new file mode 100644
index 0000000..e90b4a9
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java
@@ -0,0 +1,45 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Trivial test operator that mutates all integers by adding a fixed offset.
+ * @author Daniel Dyer
+ */
+public final class IntegerAdjuster implements EvolutionaryOperator<Integer>
+{
+ private final int adjustment;
+
+ public IntegerAdjuster(int adjustment)
+ {
+ this.adjustment = adjustment;
+ }
+
+ public List<Integer> apply(List<Integer> selectedCandidates, Random rng)
+ {
+ List<Integer> result = new ArrayList<Integer>(selectedCandidates.size());
+ for (Integer i : selectedCandidates)
+ {
+ result.add(i + adjustment);
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java
new file mode 100644
index 0000000..3bfc297
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java
@@ -0,0 +1,126 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with arbitrary lists.
+ * @author Daniel Dyer
+ */
+public class ListCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>();
+ List<List<Integer>> population = new ArrayList<List<Integer>>(4);
+ population.add(Arrays.asList(1, 2, 3, 4, 5));
+ population.add(Arrays.asList(6, 7, 8, 9, 10));
+ population.add(Arrays.asList(11, 12, 13, 14, 15));
+ population.add(Arrays.asList(16, 17, 18, 19, 20));
+ Set<Integer> values = new HashSet<Integer>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (List<Integer> individual : population)
+ {
+ assert individual.size() == 5 : "Invalid candidate length: " + individual.size();
+ values.addAll(individual);
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * When applied to lists of different lenghts, the {@link ListCrossover} operator
+ * should pick a cross-over point that exists in both lists. Therefore, the two
+ * offspring will be the lengths of the two parents.
+ */
+ @Test
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(1, Probability.ONE);
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
+ population.add(parent1);
+ List<Integer> parent2 = Arrays.asList(9, 10, 11);
+ population.add(parent2);
+
+ List<List<Integer>> offspring = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert offspring.size() == 2 : "Should be 2 offspring, is " + offspring.size();
+ // Should be 1 child of length 8 and one of length 3. Don't know which order though
+ // as parents are shuffled before cross-over is applied.
+ assert (offspring.get(0).size() == parent1.size() && offspring.get(1).size() == parent2.size())
+ || (offspring.get(0).size() == parent2.size() && offspring.get(1).size() == parent1.size())
+ : "Offspring are wrong lengths after cross-over.";
+ }
+
+
+ /**
+ * When the probability determines that no cross-over should be performed, the two parents
+ * should just be copied into the next generation unchanged.
+ */
+ @Test
+ public void testZeroProbability()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(new ConstantGenerator<Integer>(1),
+ new ConstantGenerator<Probability>(Probability.ZERO));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(4);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5);
+ List<Integer> parent2 = Arrays.asList(6, 7, 8, 9, 10);
+ population.add(parent1);
+ population.add(parent2);
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.contains(parent1) : "Parent should survive unaltered.";
+ assert population.contains(parent2) : "Parent should survive unaltered.";
+ }
+
+
+ /**
+ * If one or both of the parent lists has only one element, it can't participate in
+ * meaningful cross-over. In practice this situation is unlikely to occur (most
+ * programs won't be evolving single-element lists), but the operator should handle
+ * it gracefully.
+ */
+ @Test
+ public void testParentTooShort()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(new ConstantGenerator<Integer>(1));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3);
+ List<Integer> parent2 = Arrays.asList(4); // Too short for cross-over.
+ population.add(parent1);
+ population.add(parent2);
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.contains(parent1) : "Parent should survive unaltered.";
+ assert population.contains(parent2) : "Parent should survive unaltered.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java
new file mode 100644
index 0000000..0a5a6a7
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java
@@ -0,0 +1,64 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link ListInversion} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ListInversionTest
+{
+ @Test
+ public void testZeroProbability()
+ {
+ EvolutionaryOperator<List<Integer>> inversion = new ListInversion<Integer>(Probability.ZERO);
+ @SuppressWarnings("unchecked")
+ List<List<Integer>> selection = Arrays.asList(Arrays.asList(1, 2, 3));
+ List<List<Integer>> evolvedSelection = inversion.apply(selection, FrameworkTestUtils.getRNG());
+ assert evolvedSelection.size() == 1 : "Wrong number of individuals after evolution: " + evolvedSelection.size();
+ assert evolvedSelection.get(0) == selection.get(0) : "Candidate should not have been modified.";
+ }
+
+
+ @Test
+ public void testInversion()
+ {
+ EvolutionaryOperator<List<Integer>> inversion = new ListInversion<Integer>(Probability.ONE);
+ @SuppressWarnings("unchecked")
+ List<List<Integer>> selection = Arrays.asList(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
+ for (int i = 0; i < 50; i++) // Try several times so that different random numbers are generated.
+ {
+ List<List<Integer>> evolvedSelection = inversion.apply(selection, FrameworkTestUtils.getRNG());
+
+ // After inversion, candidate should have same elements but not in the same order.
+ assert evolvedSelection.size() == 1 : "Wrong number of individuals after evolution: " + evolvedSelection.size();
+ assert evolvedSelection.get(0).size() == selection.get(0).size() : "Candidate length should be unchanged.";
+ assert !Arrays.deepEquals(evolvedSelection.get(0).toArray(), selection.get(0).toArray())
+ : "Candidate should have been modified.";
+ assert new HashSet<Integer>(evolvedSelection.get(0)).size() == 8
+ : "Evolved candidate should contain each element once.";
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java
new file mode 100644
index 0000000..4cd22f0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java
@@ -0,0 +1,58 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link ListOperator} high-order evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ListOperatorTest
+{
+ /**
+ * Make sure that the delegate operator is applied to each list in the
+ * population.
+ */
+ @Test
+ public void testApplication()
+ {
+ ListOperator<Integer> operator = new ListOperator<Integer>(new IntegerAdjuster(1));
+ List<List<Integer>> selection = new ArrayList<List<Integer>>(3);
+ selection.add(Arrays.asList(1, 2, 3));
+ selection.add(Arrays.asList(4, 5, 6));
+ selection.add(Arrays.asList(7, 8, 9));
+
+ List<List<Integer>> mutations = operator.apply(selection, FrameworkTestUtils.getRNG());
+ assert mutations.size() == 3 : "Wrong number of candidates after list operation: " + selection.size();
+
+ // Each element in each candidate list should have been incremented by the delegate operator.
+ for (int i = 0; i < selection.size(); i++)
+ {
+ List<Integer> original = selection.get(i);
+ List<Integer> mutation = mutations.get(i);
+ assert original.size() == mutation.size() : "Mutation size mismatch: " + mutation.size();
+ for (int j = 0; j < original.size(); j++)
+ {
+ assert mutation.get(j) == original.get(j) + 1 : "List value not mutated correctly.";
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java
new file mode 100644
index 0000000..22725b3
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test to validate the operation of the {@link ListOrderCrossover} operator.
+ * @author Daniel Dyer
+ */
+public class ListOrderCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<List<Integer>> operator = new ListOrderCrossover<Integer>();
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
+ List<Integer> parent2 = Arrays.asList(3, 7, 5, 1, 6, 8, 2, 4);
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ population.add(parent1);
+ population.add(parent2);
+
+ for (int i = 0; i < 50; i++) // Do several cross-overs to check different cross-over points.
+ {
+ population = operator.apply(population, FrameworkTestUtils.getRNG());
+ for (List<Integer> offspring : population)
+ {
+ for (int j = 1; j <= 8; j++)
+ {
+ assert offspring.contains(j) : "Evolved candidate missing required element " + j;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * The {@link ListOrderCrossover} operator is only defined to work on populations
+ * containing lists of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length lists should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<List<Integer>> crossover
+ = new ListOrderCrossover<Integer>(new ConstantGenerator<Probability>(Probability.ONE));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ population.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
+ population.add(Arrays.asList(9, 10, 11));
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java
new file mode 100644
index 0000000..6612d4b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java
@@ -0,0 +1,69 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test to validate the operation of the {@link ListOrderMutation} operator.
+ * @author Daniel Dyer
+ */
+public class ListOrderMutationTest
+{
+ @Test
+ public void testMutation()
+ {
+ ListOrderMutation<Character> operator = new ListOrderMutation<Character>();
+ List<Character> candidate = new ArrayList<Character>(5);
+ candidate.add('a');
+ candidate.add('b');
+ candidate.add('c');
+ candidate.add('d');
+ candidate.add('e');
+ List<List<Character>> population = new ArrayList<List<Character>>(1);
+ population.add(candidate);
+ List<List<Character>> mutatedPopulation = operator.apply(population, FrameworkTestUtils.getRNG());
+ assert mutatedPopulation.size() == population.size() : "Population size should be unchanged.";
+ List<Character> mutatedCandidate = mutatedPopulation.get(0);
+ Reporter.log("Original: " + Arrays.toString(candidate.toArray(new Character[candidate.size()])));
+ Reporter.log("Mutation: " + Arrays.toString(mutatedCandidate.toArray(new Character[mutatedCandidate.size()])));
+ assert mutatedCandidate.size() == candidate.size() : "Mutated candidate should be same length as original.";
+ // Mutated candidate should have same elements but in a different order.
+ int matchingPositions = 0;
+ for (int i = 0; i < candidate.size(); i++)
+ {
+ if (candidate.get(i).equals(mutatedCandidate.get(i)))
+ {
+ ++matchingPositions;
+ }
+ else
+ {
+ // If positions don't match, an adjacent character should be a match.
+ int nextPosition = (i + 1) % candidate.size();
+ int previousPosition = ((i - 1) + candidate.size()) % candidate.size();
+ boolean matchAdjacent = candidate.get(i).equals(mutatedCandidate.get(nextPosition))
+ ^ candidate.get(i).equals(mutatedCandidate.get(previousPosition));
+ assert matchAdjacent : "Mutated characters not in expected positions.";
+ }
+ }
+ assert matchingPositions == candidate.size() - 2 : "All but 2 positions should be unchanged.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java
new file mode 100644
index 0000000..04160d6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with arrays of objects.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<String[]> crossover = new ObjectArrayCrossover<String>();
+ List<String[]> population = new ArrayList<String[]>(4);
+ population.add(new String[]{"1", "2", "3", "4", "5"});
+ population.add(new String[]{"6", "7", "8", "9", "10"});
+ population.add(new String[]{"11", "12", "13", "14", "15"});
+ population.add(new String[]{"16", "17", "18", "19", "20"});
+ Set<String> values = new HashSet<String>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (String[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (String value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link ObjectArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<String[]> crossover = new ObjectArrayCrossover<String>(1, Probability.ONE);
+ List<String[]> population = new ArrayList<String[]>(2);
+ population.add(new String[]{"1", "2", "3", "4", "5"});
+ population.add(new String[]{"6", "7", "8", "9", "10", "11", "12"});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java
new file mode 100644
index 0000000..7a4ba59
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java
@@ -0,0 +1,77 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory;
+
+/**
+ * Unit test for the {@link Replacement} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ReplacementTest
+{
+ @Test
+ public void testReplacement()
+ {
+ IntegerFactory factory = new IntegerFactory();
+ List<Integer> candidates = Arrays.asList(10, 11, 12);
+ Replacement<Integer> replacement = new Replacement<Integer>(factory,
+ Probability.ONE);
+ // Numbers will be replaced with lower ones.
+ List<Integer> output = replacement.apply(candidates, FrameworkTestUtils.getRNG());
+ assert output.size() == candidates.size() : "Candidate list should be same size.";
+ assert !output.contains(10) : "Candidate should have been replaced.";
+ assert !output.contains(11) : "Candidate should have been replaced.";
+ assert !output.contains(12) : "Candidate should have been replaced.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ IntegerFactory factory = new IntegerFactory();
+ List<Integer> candidates = Arrays.asList(10, 11, 12);
+ Replacement<Integer> replacement = new Replacement<Integer>(factory,
+ Probability.ZERO);
+ // Numbers will be replaced with lower ones.
+ List<Integer> output = replacement.apply(candidates, FrameworkTestUtils.getRNG());
+ assert output.size() == candidates.size() : "Candidate list should be same size.";
+ assert output.contains(10) : "Candidate should not have been replaced.";
+ assert output.contains(11) : "Candidate should not have been replaced.";
+ assert output.contains(12) : "Candidate should not have been replaced.";
+ }
+
+
+ /**
+ * Non-random factory, for test purposes.
+ */
+ protected static final class IntegerFactory extends AbstractCandidateFactory<Integer>
+ {
+ private int count = 0;
+
+ public Integer generateRandomCandidate(Random rng)
+ {
+ return ++count;
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java
new file mode 100644
index 0000000..f9773ca
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java
@@ -0,0 +1,92 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for forked evolutionary schemes.
+ * @author Daniel Dyer
+ */
+public class SplitEvolutionTest
+{
+ /**
+ * Make sure that the correct proportions are mutated correctly.
+ */
+ @Test
+ public void testSplit()
+ {
+ List<Integer> population = new ArrayList<Integer>(10);
+ for (int i = 10; i <= 100; i += 10)
+ {
+ population.add(i);
+ }
+ // Increment 30% of the numbers and decrement the other 70%.
+ SplitEvolution<Integer> evolutionScheme = new SplitEvolution<Integer>(new IntegerAdjuster(1),
+ new IntegerAdjuster(-1),
+ 0.3d);
+ population = evolutionScheme.apply(population, FrameworkTestUtils.getRNG());
+ int aggregate = 0;
+ int incrementedCount = 0;
+ int decrementedCount = 0;
+ for (Integer i : population)
+ {
+ aggregate += i;
+ if (i % 10 == 1)
+ {
+ ++incrementedCount;
+ }
+ else if (i % 10 == 9)
+ {
+ ++decrementedCount;
+ }
+ else
+ {
+ assert false : "Mutation failed.";
+ }
+ }
+ assert incrementedCount == 3 : "Should be 3 incremented candidates, is " + incrementedCount;
+ assert decrementedCount == 7 : "Should be 7 decremented candidates, is " + decrementedCount;
+ assert aggregate == 546 : "Aggregate should be 546 after mutation, is " + aggregate;
+ }
+
+
+ /**
+ * Make sure that the split cannot be set-up with a negative weight. If
+ * this is attempted, an IllegalArgumentException should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNegativeWeight()
+ {
+ // Negative weight.
+ new SplitEvolution<Integer>(new IntegerAdjuster(1), new IdentityOperator<Integer>(), -0.01d);
+ }
+
+
+ /**
+ * Make sure that the split cannot be set-up with a weight greater than 1. If
+ * this is attempted, an IllegalArgumentException should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWeightTooHigh()
+ {
+ // Weight too high (must be less than or equal to one).
+ new SplitEvolution<Integer>(new IntegerAdjuster(1), new IdentityOperator<Integer>(), 1.01d);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java
new file mode 100644
index 0000000..3a74155
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with Strings.
+ * @author Daniel Dyer
+ */
+public class StringCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<String> crossover = new StringCrossover();
+ List<String> population = new ArrayList<String>(4);
+ population.add("abcde");
+ population.add("fghij");
+ population.add("klmno");
+ population.add("pqrst");
+ Set<Character> values = new HashSet<Character>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (String individual : population)
+ {
+ assert individual.length() == 5 : "Invalid candidate length: " + individual.length();
+ for (char value : individual.toCharArray())
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link StringCrossover} operator is only defined to work on populations
+ * containing Strings of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length Strings should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<String> crossover = new StringCrossover(1, Probability.ONE);
+ List<String> population = new ArrayList<String>(2);
+ population.add("abcde");
+ population.add("fghijklm");
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Number of cross-over points must be greater than zero otherwise the operator
+ * is a no-op.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroCrossoverPoints()
+ {
+ new StringCrossover(0, Probability.EVENS); // Should throw an IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java
new file mode 100644
index 0000000..7827452
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java
@@ -0,0 +1,54 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for string mutation operator.
+ * @author Daniel Dyer
+ */
+public class StringMutationTest
+{
+ private final char[] alphabet = {'a', 'b', 'c', 'd'};
+
+ @Test
+ public void testMutation()
+ {
+ StringMutation mutation = new StringMutation(alphabet, Probability.EVENS);
+ String individual1 = "abcd";
+ String individual2 = "abab";
+ String individual3 = "cccc";
+ List<String> population = Arrays.asList(individual1, individual2, individual3);
+ for (int i = 0; i < 20; i++) // Perform several iterations.
+ {
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 3 : "Population size changed after mutation: " + population.size();
+ for (String individual : population) // Check that each individual is still valid.
+ {
+ assert individual.length() == 4 : "Individual size changed after mutation: " + individual.length();
+ for (char c : individual.toCharArray())
+ {
+ assert c >= 'a' && c <= 'd' : "Mutation introduced invalid character: " + c;
+ }
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java
new file mode 100644
index 0000000..a2f140a
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for rank-proportionate selection.
+ * @author Daniel Dyer
+ */
+public class RankSelectionTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RankSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 1 && steveCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RankSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 1 && garyCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java
new file mode 100644
index 0000000..b0b8282
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java
@@ -0,0 +1,99 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for roulette selection strategy. We cannot easily test
+ * that the correct candidates are returned because of the random aspect
+ * of the selection, but we can at least make sure the right number of
+ * candidates are selected.
+ * @author Daniel Dyer
+ */
+public class RouletteWheelSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, true, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ /**
+ * Make sure that the code still functions for non-natural fitness scores even
+ * when one of them is a zero (a perfect score).
+ */
+ @Test
+ public void testNonNaturalFitnessPerfectSolution()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java
new file mode 100644
index 0000000..9d736d3
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java
@@ -0,0 +1,124 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for the {@link SigmaScaling} selection strategy.
+ * @author Daniel Dyer
+ */
+public class SigmaScalingTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 1 && steveCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * If all fitness scores are equal, standard deviation is zero. Test that this case
+ * works correctly.
+ */
+ @Test
+ public void testNoVariance()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 4.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 4.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 4.0);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount == 1 : "Candidate selected wrong number of times (should be 1, was " + steveCount + ")";
+ assert johnCount == 1 : "Candidate selected wrong number of times (should be 1, was " + johnCount + ")";
+ assert maryCount == 1 : "Candidate selected wrong number of times (should be 1, was " + maryCount + ")";
+ assert garyCount == 1 : "Candidate selected wrong number of times (should be 1, was " + garyCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 1 && garyCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java
new file mode 100644
index 0000000..6480bf8
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for fitness proportionate selection where observed selection
+ * frequencies correspond to expected frequencies.
+ * @author Daniel Dyer
+ */
+public class StochasticUniversalSamplingTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new StochasticUniversalSampling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 2 && steveCount <= 3 : "Candidate selected wrong number of times (should be 2 or 3, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2 : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new StochasticUniversalSampling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 2 && garyCount <= 3 : "Candidate selected wrong number of times (should be 2 or 3, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2 : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java
new file mode 100644
index 0000000..68f3b38
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java
@@ -0,0 +1,90 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for tournament selection strategy. We cannot easily test
+ * that the correct candidates are returned because of the random aspect
+ * of the selection, but we can at least make sure the right number of
+ * candidates are selected.
+ * @author Daniel Dyer
+ */
+public class TournamentSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TournamentSelection(new Probability(0.7d));
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ for (int i = 0; i < 20; i++) // Run several iterations so that we get different tournament outcomes.
+ {
+ List<String> selection = selector.select(population, true, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TournamentSelection(new Probability(0.7d));
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations so that we get different tournament outcomes.
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+
+ /**
+ * The probability of selecting the fitter of two candidates must be greater than 0.5 to be
+ * useful (if it is not, there is no selection pressure, or the pressure is in favour of weaker
+ * candidates, which is counter-productive) . This test ensures that an appropriate exception
+ * is thrown if the probability is 0.5 or less. Not throwing an exception is an error because
+ * it permits undetected bugs in evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testProbabilityTooLow()
+ {
+ new TournamentSelection(Probability.EVENS);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java
new file mode 100644
index 0000000..3e33d06
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for truncation selection strategy. Ensures the
+ * correct candidates are selected.
+ * @author Daniel Dyer
+ */
+public class TruncationSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TruncationSelection(0.5d);
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 2, null);
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ assert selection.contains(steve.getCandidate()) : "Best candidate not selected.";
+ assert selection.contains(mary.getCandidate()) : "Second best candidate not selected.";
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TruncationSelection(0.5d);
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 2, null);
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ assert selection.contains(gary.getCandidate()) : "Best candidate not selected.";
+ assert selection.contains(john.getCandidate()) : "Second best candidate not selected.";
+ }
+
+
+ /**
+ * The selection ratio must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the ratio is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new TruncationSelection(0d);
+ }
+
+
+ /**
+ * The selection ratio must be less than 1 to be useful. This test
+ * ensures that an appropriate exception is thrown if the ratio is too high.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testRatioTooHigh()
+ {
+ new TruncationSelection(1d);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java
new file mode 100644
index 0000000..ea26876
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+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.
+ * @author Daniel Dyer
+ */
+public class ElapsedTimeTest
+{
+ @Test
+ public void testElapsedTimes()
+ {
+ TerminationCondition condition = new ElapsedTime(1000);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 0, 100);
+ assert !condition.shouldTerminate(data) : "Should not terminate before timeout.";
+ data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 0, 1000);
+ assert condition.shouldTerminate(data) : "Should terminate after timeout.";
+ }
+
+
+ /**
+ * The duration must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the duration is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new ElapsedTime(0L);
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java
new file mode 100644
index 0000000..64487a2
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+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
+{
+ @Test
+ public void testGenerationCounts()
+ {
+ TerminationCondition condition = new GenerationCount(5);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 0, 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<Object>(new Object(), 0, 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.";
+ }
+
+
+ /**
+ * The generation count must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the count is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new GenerationCount(0);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java
new file mode 100644
index 0000000..6efe9fb
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+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
+{
+ @Test
+ public void testFittestCandidateStagnation()
+ {
+ TerminationCondition stagnation = new Stagnation(2, true);
+ PopulationData<Object> data = new PopulationData<Object>(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.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(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.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(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.";
+ }
+
+ @Test
+ public void testFittestCandidateStagnationNonNatural()
+ {
+ TerminationCondition stagnation = new Stagnation(2, false);
+ PopulationData<Object> data = new PopulationData<Object>(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.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(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.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1, 0.1, true, 10, 0, 2, 3);
+ assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement.";
+ }
+
+
+ @Test
+ public void testPopulationMeanStagnation()
+ {
+ TerminationCondition stagnation = new Stagnation(2, true, true);
+ PopulationData<Object> data = new PopulationData<Object>(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.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(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.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 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.";
+ // Now we let the mean stagnate...and let the best candidate get fitter...
+ data = new PopulationData<Object>(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<Object>(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.";
+ }
+
+
+ @Test
+ public void testPopulationMeanStagnationNonNatural()
+ {
+ TerminationCondition stagnation = new Stagnation(2, false, true);
+ PopulationData<Object> data = new PopulationData<Object>(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.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(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.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 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.";
+ // Now we let the mean stagnate...and let the best candidate get fitter...
+ data = new PopulationData<Object>(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<Object>(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.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java
new file mode 100644
index 0000000..738f612
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+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.
+ * @author Daniel Dyer
+ */
+public class TargetFitnessTest
+{
+ @Test
+ public void testNaturalFitness()
+ {
+ TerminationCondition condition = new TargetFitness(10.0d, true);
+ PopulationData<Object> data = new PopulationData<Object>(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<Object>(new Object(), 10.0d, 8.0d, 0, true, 2, 0, 0, 100);
+ assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached.";
+ }
+
+
+ @Test
+ public void testNonNaturalFitness()
+ {
+ TerminationCondition condition = new TargetFitness(1.0d, false);
+ PopulationData<Object> data = new PopulationData<Object>(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<Object>(new Object(), 1.0d, 3.1d, 0, true, 2, 0, 0, 100);
+ assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java
new file mode 100644
index 0000000..34f8ce8
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java
@@ -0,0 +1,39 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+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
+{
+ @Test
+ public void testAbort()
+ {
+ UserAbort condition = new UserAbort();
+ // This population data should be irrelevant.
+ PopulationData<Object> data = new PopulationData<Object>(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.";
+ condition.abort();
+ assert condition.shouldTerminate(data) : "Should terminate after user abort.";
+ assert condition.isAborted() : "Should be aborted after user intervention.";
+ }
+}
diff --git a/watchmaker/nb-configuration.xml b/watchmaker/nb-configuration.xml
new file mode 100644
index 0000000..e958b93
--- /dev/null
+++ b/watchmaker/nb-configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>words</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>0</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>
+ </properties>
+</project-shared-configuration>
diff --git a/watchmaker/pom.xml b/watchmaker/pom.xml
new file mode 100644
index 0000000..6698f38
--- /dev/null
+++ b/watchmaker/pom.xml
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.sonatype.oss</groupId>
+ <artifactId>oss-parent</artifactId>
+ <version>7</version>
+ </parent>
+ <groupId>org.uncommons.watchmaker</groupId>
+ <artifactId>watchmaker</artifactId>
+ <version>0.7.2</version>
+ <packaging>pom</packaging>
+ <name>watchmaker</name>
+ <description>Watchmaker Framework - Root Project</description>
+ <url>http://watchmaker.uncommons.org</url>
+
+ <licenses>
+ <license>
+ <name>Apache License, Version 2.0</name>
+ <url>http://www.apache.org/licenses/LICENSE-2.0.html</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+
+ <scm>
+ <url>https://github.com/dwdyer/watchmaker</url>
+ <connection>scm:git://github.com/dwdyer/watchmaker.git</connection>
+ <developerConnection>scm:git:ssh://git@github.com:dwdyer/watchmaker.git</developerConnection>
+ </scm>
+
+ <developers>
+ <developer>
+ <id>dwdyer</id>
+ <name>Dan Dyer</name>
+ <roles>
+ <role>committer</role>
+ </roles>
+ </developer>
+ <developer>
+ <id>cowwoc</id>
+ <name>Gili Tzabari</name>
+ <roles>
+ <role>contributor</role>
+ </roles>
+ </developer>
+ </developers>
+
+ <modules>
+ <module>framework</module>
+ <module>swing</module>
+ <module>examples</module>
+ </modules>
+
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.3.2</version>
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>2.4.3</version>
+ <configuration>
+ <encoding>${project.build.sourceEncoding}</encoding>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project> \ No newline at end of file
diff --git a/watchmaker/swing/lib/runtime/jfreechart-LICENCE.txt b/watchmaker/swing/lib/runtime/jfreechart-LICENCE.txt
new file mode 100644
index 0000000..b1e3f5a
--- /dev/null
+++ b/watchmaker/swing/lib/runtime/jfreechart-LICENCE.txt
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/watchmaker/swing/nb-configuration.xml b/watchmaker/swing/nb-configuration.xml
new file mode 100644
index 0000000..ae35717
--- /dev/null
+++ b/watchmaker/swing/nb-configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>words</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>0</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>
+ </properties>
+</project-shared-configuration>
diff --git a/watchmaker/swing/pom.xml b/watchmaker/swing/pom.xml
new file mode 100644
index 0000000..690f0db
--- /dev/null
+++ b/watchmaker/swing/pom.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--===========================================================================
+ 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.
+ ==========================================================================-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.uncommons.watchmaker</groupId>
+ <artifactId>watchmaker</artifactId>
+ <version>0.7.2</version>
+ </parent>
+ <artifactId>watchmaker-swing</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>watchmaker-framework</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.uncommons.maths</groupId>
+ <artifactId>uncommons-maths</artifactId>
+ <version>1.2.2</version>
+ </dependency>
+ <dependency>
+ <groupId>jfree</groupId>
+ <artifactId>jfreechart</artifactId>
+ <version>1.0.13</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.easytesting</groupId>
+ <artifactId>fest-swing</artifactId>
+ <version>1.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src/java/main</sourceDirectory>
+ <testSourceDirectory>src/java/test</testSourceDirectory>
+ </build>
+</project> \ No newline at end of file
diff --git a/watchmaker/swing/src/java/main/org/uncommons/swing/ConfigurableLineBorder.java b/watchmaker/swing/src/java/main/org/uncommons/swing/ConfigurableLineBorder.java
new file mode 100644
index 0000000..79bbdf2
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/swing/ConfigurableLineBorder.java
@@ -0,0 +1,116 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.Component;
+import java.awt.Graphics;
+import java.awt.Insets;
+import javax.swing.border.Border;
+
+/**
+ * Border class that allows each of the four sides of the border to be enabled
+ * or disabled independently.
+ * @author Daniel Dyer
+ */
+public class ConfigurableLineBorder implements Border
+{
+ private final boolean top;
+ private final boolean left;
+ private final boolean bottom;
+ private final boolean right;
+ private final int thickness;
+ private final Insets insets;
+
+
+ /**
+ * @param top Whether or not to draw the border on the top edge.
+ * @param left Whether or not to draw the border on the left edge.
+ * @param bottom Whether or not to draw the border on the bottom edge.
+ * @param right Whether or not to draw the border on the right edge.
+ * @param thickness The width (in pixels) of each side of the border.
+ */
+ public ConfigurableLineBorder(boolean top,
+ boolean left,
+ boolean bottom,
+ boolean right,
+ int thickness)
+ {
+ this.top = top;
+ this.left = left;
+ this.bottom = bottom;
+ this.right = right;
+ this.thickness = thickness;
+ this.insets = new Insets(top ? thickness : 0,
+ left ? thickness : 0,
+ bottom ? thickness : 0,
+ right ? thickness : 0);
+ }
+
+
+ /**
+ * Renders borders for the specified component based on the configuration
+ * of this border object.
+ * @param component The component for which the border is painted.
+ * @param graphics A {@link Graphics} object to use for painting.
+ * @param x The X-coordinate of the top left point of the border rectangle.
+ * @param y The Y-coordinate of the top left point of the border rectangle.
+ * @param width The width of the border rectangle.
+ * @param height The height of the border rectangle.
+ */
+ public void paintBorder(Component component,
+ Graphics graphics,
+ int x,
+ int y,
+ int width,
+ int height)
+ {
+ if (top)
+ {
+ graphics.fillRect(x, y, width, thickness);
+ }
+ if (bottom)
+ {
+ graphics.fillRect(x, y + height - thickness, width, thickness);
+ }
+ if (left)
+ {
+ graphics.fillRect(x, y, thickness, height);
+ }
+ if (right)
+ {
+ graphics.fillRect(x + width - thickness, y, thickness, height);
+ }
+ }
+
+
+ /**
+ * @param component The component for which the border is painted.
+ * @return Insets for the current border configuration.
+ */
+ public Insets getBorderInsets(Component component)
+ {
+ return insets;
+ }
+
+
+ /**
+ * @return false
+ */
+ public boolean isBorderOpaque()
+ {
+ return false;
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/swing/SpringUtilities.java b/watchmaker/swing/src/java/main/org/uncommons/swing/SpringUtilities.java
new file mode 100644
index 0000000..43add37
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/swing/SpringUtilities.java
@@ -0,0 +1,121 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.Component;
+import java.awt.Container;
+import javax.swing.Spring;
+import javax.swing.SpringLayout;
+
+/**
+ * Utility methods for creating form-style or grid-style layouts with SpringLayout.
+ * Modified version of the class presented in the Sun Swing tutorial
+ * (http://java.sun.com/docs/books/tutorial/uiswing/layout/examples/SpringUtilities.java).
+ */
+public final class SpringUtilities
+{
+ private SpringUtilities()
+ {
+ // Private constructor prevents instantiation of utility class.
+ }
+
+
+ /**
+ * Aligns the first {@code rows} * {@code cols} components of {@code parent}
+ * in a grid. Each component in a column is as wide as the maximum preferred
+ * width of the components in that column; height is similarly determined for
+ * each row. The parent is made just big enough to fit them all.
+ * @param parent The container to layout.
+ * @param rows number of rows
+ * @param columns number of columns
+ * @param initialX x location to start the grid at
+ * @param initialY y location to start the grid at
+ * @param xPad x padding between cells
+ * @param yPad y padding between cells
+ */
+ public static void makeCompactGrid(Container parent,
+ int rows,
+ int columns,
+ int initialX,
+ int initialY,
+ int xPad,
+ int yPad)
+ {
+ if (!(parent.getLayout() instanceof SpringLayout))
+ {
+ throw new IllegalArgumentException("The first argument to makeCompactGrid must use SpringLayout.");
+ }
+ SpringLayout layout = (SpringLayout) parent.getLayout();
+
+ // Align all cells in each column and make them the same width.
+ Spring x = Spring.constant(initialX);
+ for (int c = 0; c < columns; c++)
+ {
+ Spring width = Spring.constant(0);
+ for (int r = 0; r < rows; r++)
+ {
+ width = Spring.max(width,
+ getConstraintsForCell(r, c, parent, columns).getWidth());
+ }
+ for (int r = 0; r < rows; r++)
+ {
+ SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, columns);
+ constraints.setX(x);
+ constraints.setWidth(width);
+ }
+ x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad)));
+ }
+
+ // Align all cells in each row and make them the same height.
+ Spring y = Spring.constant(initialY);
+ for (int r = 0; r < rows; r++)
+ {
+ Spring height = Spring.constant(0);
+ for (int c = 0; c < columns; c++)
+ {
+ height = Spring.max(height,
+ getConstraintsForCell(r, c, parent, columns).getHeight());
+ }
+ for (int c = 0; c < columns; c++)
+ {
+ SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, columns);
+ constraints.setY(y);
+ constraints.setHeight(height);
+ }
+ y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad)));
+ }
+
+ // Set the parent's size.
+ SpringLayout.Constraints parentConstraints = layout.getConstraints(parent);
+ parentConstraints.setConstraint(SpringLayout.SOUTH, y);
+ parentConstraints.setConstraint(SpringLayout.EAST, x);
+ }
+
+
+ /**
+ * Helper method for {@link #makeCompactGrid(Container, int, int, int, int, int, int)}.
+ * @return The constraints for the specified cell.
+ */
+ private static SpringLayout.Constraints getConstraintsForCell(int row,
+ int col,
+ Container parent,
+ int cols)
+ {
+ SpringLayout layout = (SpringLayout) parent.getLayout();
+ Component c = parent.getComponent(row * cols + col);
+ return layout.getConstraints(c);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/swing/SwingBackgroundTask.java b/watchmaker/swing/src/java/main/org/uncommons/swing/SwingBackgroundTask.java
new file mode 100644
index 0000000..e8fc73c
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/swing/SwingBackgroundTask.java
@@ -0,0 +1,147 @@
+//=============================================================================
+// 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.swing;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.swing.JOptionPane;
+import javax.swing.SwingUtilities;
+
+/**
+ * A task that is executed on a background thread and then updates
+ * a Swing GUI. A task may only be executed once.
+ * @author Daniel Dyer
+ * @param <V> Type of result generated by the task.
+ */
+public abstract class SwingBackgroundTask<V>
+{
+ // Used to assign thread IDs to make threads easier to identify when debugging.
+ private static final AtomicInteger INSTANCE_COUNT = new AtomicInteger(0);
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private final int id;
+
+
+ protected SwingBackgroundTask()
+ {
+ this.id = INSTANCE_COUNT.getAndIncrement();
+ }
+
+
+ /**
+ * Asynchronous call that begins execution of the task and returns immediately.
+ * The {@link #performTask()} method will be invoked on a background thread and,
+ * when it has completed, {@link #postProcessing(Object)} will be invoked on the
+ * Event Dispatch Thread (or, if there is an exception, {@link #onError(Throwable)}
+ * will be invoked instead - also on the EDT).
+ * @see #performTask()
+ * @see #postProcessing(Object)
+ * @see #onError(Throwable)
+ * @see #waitForCompletion()
+ */
+ public void execute()
+ {
+ Runnable task = new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ final V result = performTask();
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ postProcessing(result);
+ latch.countDown();
+ }
+ });
+ }
+ // If an exception occurs performing the task, we need
+ // to handle it.
+ catch (final Throwable throwable)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ onError(throwable);
+ latch.countDown();
+ }
+ });
+ }
+ }
+ };
+ new Thread(task, "SwingBackgroundTask-" + id).start();
+ }
+
+
+ /**
+ * Waits for the execution of this task to complete. If the {@link #execute()}
+ * method has not yet been invoked, this method will block indefinitely.
+ * @throws InterruptedException If the thread executing the task
+ * is interrupted.
+ */
+ public void waitForCompletion() throws InterruptedException
+ {
+ latch.await();
+ }
+
+
+ /**
+ * Performs the processing of the task and returns a result.
+ * Implement in sub-classes to provide the task logic. This method will
+ * run on a background thread and not on the Event Dispatch Thread and
+ * therefore should not manipulate any Swing components.
+ * @return The result of executing this task.
+ * @throws Exception The task may throw an exception, in which case
+ * the {@link #onError(Throwable)} method will be invoked instead of
+ * {@link #postProcessing(Object)}.
+ */
+ protected abstract V performTask() throws Exception;
+
+
+ /**
+ * This method is invoked, on the Event Dispatch Thread, after the task
+ * has been executed.
+ * This empty default implementation should be over-ridden in sub-classes
+ * in order to provide GUI updates that should occur following successful
+ * task completion.
+ * @param result The result from the {@link #performTask()} method.
+ */
+ protected void postProcessing(V result)
+ {
+ // Over-ride in sub-class.
+ }
+
+
+ /**
+ * This method is invoked, on the Event Dispatch Thread, if there is an
+ * exception or error executing the {@link #performTask()} method.
+ * This default implementation displays a message dialog with details of the
+ * exception. It may be over-ridden in sub-classes.
+ * @param throwable The exception or error that was thrown while executing
+ * the task.
+ */
+ protected void onError(Throwable throwable)
+ {
+ throwable.printStackTrace();
+ JOptionPane.showMessageDialog(null,
+ throwable.getMessage(),
+ throwable.getClass().getName(),
+ JOptionPane.ERROR_MESSAGE);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/swing/package-info.java b/watchmaker/swing/src/java/main/org/uncommons/swing/package-info.java
new file mode 100644
index 0000000..92ec475
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/swing/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * Utility classes and generic components for building Swing graphical
+ * user interfaces.
+ * @author Daniel Dyer
+ */
+package org.uncommons.swing;
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java
new file mode 100644
index 0000000..fc8b49f
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/AbortControl.java
@@ -0,0 +1,86 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import javax.swing.JButton;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+import org.uncommons.watchmaker.framework.termination.UserAbort;
+
+/**
+ * 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()
+ {
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ control.setEnabled(false);
+ abortCondition.abort();
+ }
+ });
+ setDescription("Terminate the evolution on completion of the current generation.");
+ }
+
+
+ /**
+ * @return A button that, when pressed, will trigger the abort condition
+ * associated with this control.
+ */
+ public JButton getControl()
+ {
+ return control;
+ }
+
+
+ /**
+ * Enables the GUI control and resets the abort condition ready
+ * for use.
+ */
+ public void reset()
+ {
+ control.setEnabled(true);
+ abortCondition.reset();
+ }
+
+
+ /**
+ * @return A {@link TerminationCondition} that is tied to this control. It can
+ * be passed to an {@link org.uncommons.watchmaker.framework.EvolutionEngine}
+ * so that the evolution is aborted when this control is invoked.
+ */
+ public TerminationCondition getTerminationCondition()
+ {
+ return abortCondition;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public final void setDescription(String description)
+ {
+ control.setToolTipText(description);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/EvolutionControl.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/EvolutionControl.java
new file mode 100644
index 0000000..0a59534
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/EvolutionControl.java
@@ -0,0 +1,42 @@
+//=============================================================================
+// 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.swing;
+
+import javax.swing.JComponent;
+
+/**
+ * Common interface for GUI controls for evolutionary programs.
+ * @author Daniel Dyer
+ */
+public interface EvolutionControl
+{
+ /**
+ * @return The GUI component used by this control.
+ */
+ JComponent getControl();
+
+ /**
+ * Resets the control to its initial configuration.
+ */
+ void reset();
+
+ /**
+ * Provides a textual description of the purpose of the control. This may be displayed
+ * somewhere on the GUI component (typically as tooltip text).
+ * @param description The description of the control.
+ */
+ void setDescription(String description);
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java
new file mode 100644
index 0000000..e304cd3
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/NumericParameterControl.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// 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.swing;
+
+import javax.swing.JSpinner;
+import javax.swing.SpinnerNumberModel;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.uncommons.maths.number.AdjustableNumberGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+
+/**
+ * A GUI control that allows the user to set/update the value of a
+ * numeric parameter.
+ * @param <T> The numeric type of this control (e.g. Integer, Double).
+ * @author Daniel Dyer
+ */
+public class NumericParameterControl<T extends Number & Comparable<T>> implements EvolutionControl
+{
+ private final T defaultValue;
+ private final JSpinner control;
+ private final AdjustableNumberGenerator<T> numberGenerator;
+
+ public NumericParameterControl(T minimum,
+ T maximum,
+ T stepSize,
+ T initialValue)
+ {
+ this.defaultValue = initialValue;
+ this.numberGenerator = new AdjustableNumberGenerator<T>(this.defaultValue);
+ control = new JSpinner(new SpinnerNumberModel(initialValue,
+ minimum,
+ maximum,
+ stepSize));
+ control.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent changeEvent)
+ {
+ @SuppressWarnings("unchecked")
+ T value = (T) control.getValue();
+ numberGenerator.setValue(value);
+ }
+ });
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public JSpinner getControl()
+ {
+ return control;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void reset()
+ {
+ control.setValue(defaultValue);
+ control.setEnabled(true);
+ }
+
+
+ /**
+ * Returns a number generator that simply returns the current value contained
+ * in the spinner field.
+ * @return A number generator that can be used to control an evolutionary program.
+ */
+ public NumberGenerator<T> getNumberGenerator()
+ {
+ return numberGenerator;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setDescription(String description)
+ {
+ control.setToolTipText(description);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ObjectSwingRenderer.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ObjectSwingRenderer.java
new file mode 100644
index 0000000..d915d87
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ObjectSwingRenderer.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.swing;
+
+import javax.swing.JComponent;
+import javax.swing.JTextArea;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+
+/**
+ * A default {@link Renderer} implementation that can display any object
+ * as a Swing component. It simply converts the object into its String
+ * representation (via the {@link Object#toString()} method) and shows
+ * that in a text area.
+ * @author Daniel Dyer
+ */
+public class ObjectSwingRenderer implements Renderer<Object, JComponent>
+{
+ /**
+ * Calls {@link Object#toString()} on the specified entity and creates
+ * a {@link JTextArea} containing that text.
+ * @param entity The evolved entity to render.
+ * @return A text area containing the string representation of the entity.
+ */
+ public JComponent render(Object entity)
+ {
+ JTextArea text = new JTextArea(entity.toString());
+ text.setEditable(false);
+ text.setBackground(null);
+ text.setLineWrap(true);
+ text.setWrapStyleWord(true);
+ return text;
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java
new file mode 100644
index 0000000..cc2c887
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/ProbabilityParameterControl.java
@@ -0,0 +1,165 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.BorderLayout;
+import java.text.DecimalFormat;
+import javax.swing.JComponent;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JSlider;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+import org.uncommons.maths.Maths;
+import org.uncommons.maths.number.AdjustableNumberGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * 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
+{
+ private final Probability defaultValue;
+ private final int range;
+ private final JComponent control;
+ private final JSlider probabilitySlider;
+ private final JLabel valueLabel = new JLabel();
+ private final AdjustableNumberGenerator<Probability> numberGenerator;
+ private final DecimalFormat format;
+
+
+ /**
+ * Creates a control with a default range of 0..1 and a default granularity
+ * of 2 decimal places.
+ * @param defaultValue The default probability value.
+ */
+ public ProbabilityParameterControl(Probability defaultValue)
+ {
+ this(Probability.ZERO, Probability.ONE, 2, defaultValue);
+ }
+
+
+ /**
+ * @param minimum The minimum probability that this control will permit.
+ * @param maximum The maximum probability that this control will permit.
+ * @param decimalPlaces The granularity of the control.
+ * @param initialValue The default probability.
+ */
+ public ProbabilityParameterControl(Probability minimum,
+ Probability maximum,
+ int decimalPlaces,
+ Probability initialValue)
+ {
+ if (initialValue.compareTo(minimum) < 0 || initialValue.compareTo(maximum) > 0)
+ {
+ throw new IllegalArgumentException("Initial value must respect minimum and maximum.");
+ }
+ if (decimalPlaces < 1)
+ {
+ throw new IllegalArgumentException("Number of decimal places must be >= 1.");
+ }
+ this.format = createFormat(decimalPlaces);
+ this.defaultValue = initialValue;
+ this.numberGenerator = new AdjustableNumberGenerator<Probability>(this.defaultValue);
+ this.range = (int) Maths.raiseToPower(10, decimalPlaces);
+ this.probabilitySlider = createSlider(initialValue, minimum, maximum);
+ probabilitySlider.setName("Slider"); // For easy look-up in unit tests.
+ this.control = new JPanel(new BorderLayout());
+ control.add(probabilitySlider, BorderLayout.CENTER);
+ valueLabel.setText(format.format(defaultValue));
+ control.add(valueLabel, BorderLayout.WEST);
+ }
+
+
+ private DecimalFormat createFormat(int decimalPlaces)
+ {
+ StringBuilder formatString = new StringBuilder("0.");
+ for (int i = 0; i < decimalPlaces; i++)
+ {
+ formatString.append('0');
+ }
+ return new DecimalFormat(formatString.toString());
+ }
+
+
+ private JSlider createSlider(Probability initialValue,
+ Probability minimum,
+ Probability maximum)
+ {
+ int value = (int) Math.round(range * initialValue.doubleValue());
+ int min = (int) Math.round(range * minimum.doubleValue());
+ int max = (int) Math.round(range * maximum.doubleValue());
+ final JSlider slider = new JSlider(min, max, value);
+ slider.addChangeListener(new ChangeListener()
+ {
+ public void stateChanged(ChangeEvent changeEvent)
+ {
+ Probability probability = new Probability((double) slider.getValue() / range);
+ numberGenerator.setValue(probability);
+ valueLabel.setText(format.format(probability));
+ }
+ });
+ slider.setMajorTickSpacing(10);
+ slider.setMinorTickSpacing(5);
+ slider.setPaintTicks(true);
+ return slider;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public JComponent getControl()
+ {
+ return control;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void reset()
+ {
+ int value = (int) Math.round(range * defaultValue.doubleValue());
+ probabilitySlider.setValue(value);
+ valueLabel.setText(format.format(defaultValue));
+ numberGenerator.setValue(defaultValue);
+ }
+
+
+ /**
+ * Returns a number generator that simply returns the current probability value
+ * represented by the position of the slider control.
+ * @return A number generator that can be used to control an evolutionary program.
+ */
+ public NumberGenerator<Probability> getNumberGenerator()
+ {
+ return numberGenerator;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setDescription(String description)
+ {
+ probabilitySlider.setToolTipText(description);
+ valueLabel.setToolTipText(description);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java
new file mode 100644
index 0000000..d9b60b5
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SelectionStrategyControl.java
@@ -0,0 +1,170 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Vector;
+import javax.swing.JComboBox;
+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;
+
+/**
+ * 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.
+ * @param <T> A generic type that matches the type associated with the selection strategies.
+ * @author Daniel Dyer
+ */
+public class SelectionStrategyControl<T> implements EvolutionControl
+{
+ private final JComboBox control;
+ private final ProxySelectionStrategy selectionStrategy;
+
+
+ /**
+ * Creates a control for choosing between a specified set of selection strategies.
+ * @param options The selection strategies to choose from.
+ */
+ public SelectionStrategyControl(List<SelectionStrategy<? super T>> options)
+ {
+ this.control = new JComboBox(new Vector<SelectionStrategy<? super T>>(options));
+ this.selectionStrategy = new ProxySelectionStrategy(options.get(0));
+ this.control.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent ev)
+ {
+ if (ev.getStateChange() == ItemEvent.SELECTED)
+ {
+ @SuppressWarnings("unchecked")
+ SelectionStrategy<? super T> delegate = (SelectionStrategy<? super T>) control.getSelectedItem();
+ selectionStrategy.setDelegate(delegate);
+ }
+ }
+ });
+ }
+
+
+ /**
+ * Creates a list containing one instance of each of the standard selection strategies.
+ * These strategies are {@link RankSelection}, {@link RouletteWheelSelection},
+ * {@link StochasticUniversalSampling}, {@link TournamentSelection} and {@link TruncationSelection}.
+ * @param tournamentProbability The probability parameter for {@link TournamentSelection}.
+ * @param truncationRatio The ratio parameter for {@link TruncationSelection}.
+ * @return A list of selection strategies.
+ */
+ public static <T> List<SelectionStrategy<? super T>> createDefaultOptions(Probability tournamentProbability,
+ double truncationRatio)
+ {
+ List<SelectionStrategy<? super T>> options = new LinkedList<SelectionStrategy<? super T>>();
+ options.add(new RankSelection());
+ options.add(new RouletteWheelSelection());
+ options.add(new SigmaScaling());
+ options.add(new StochasticUniversalSampling());
+ options.add(new TournamentSelection(tournamentProbability));
+ options.add(new TruncationSelection(truncationRatio));
+ return options;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public JComboBox getControl()
+ {
+ return control;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void reset()
+ {
+ control.setSelectedIndex(0);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setDescription(String description)
+ {
+ control.setToolTipText(description);
+ }
+
+
+ /**
+ * @return A proxied {@link SelectionStrategy} that delegates to whichever
+ * concrete selection strategy is currently selected.
+ */
+ public SelectionStrategy<T> getSelectionStrategy()
+ {
+ return selectionStrategy;
+ }
+
+
+ /**
+ * A {@link SelectionStrategy} implementation that simply delegates to the selection strategy
+ * currently selected by the combobox control.
+ */
+ private class ProxySelectionStrategy implements SelectionStrategy<T>
+ {
+ private volatile SelectionStrategy<? super T> delegate;
+
+ ProxySelectionStrategy(SelectionStrategy<? super T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ public void setDelegate(SelectionStrategy<? super T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S extends T> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ return delegate.select(population, naturalFitnessScores, selectionSize, rng);
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return delegate.toString();
+ }
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java
new file mode 100644
index 0000000..0ce6db5
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingConsole.java
@@ -0,0 +1,143 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.BorderLayout;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+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 org.uncommons.watchmaker.framework.interactive.Console;
+
+/**
+ * Swing-based console for interactive evolutionary algorithms.
+ * @author Daniel Dyer
+ */
+public class SwingConsole extends JPanel implements Console<JComponent>
+{
+ private final Lock lock = new ReentrantLock();
+ 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).
+ */
+ public SwingConsole()
+ {
+ this(3);
+ }
+
+
+ /**
+ * Creates a console with a configurable number of columns.
+ * @param columns The number of columns to use when displaying the
+ * candidates for selection.
+ */
+ public SwingConsole(int columns)
+ {
+ super(new GridLayout(0, columns));
+ }
+
+
+ /**
+ * This method blocks and therefore must not be invoked from the Event
+ * Dispatch Thread.
+ * {@inheritDoc}
+ */
+ public int select(final List<? extends JComponent> renderedEntities)
+ {
+ selectedIndex.set(-1);
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ removeAll();
+ int index = -1;
+ for (JComponent entity : renderedEntities)
+ {
+ add(new EntityPanel(entity, ++index));
+ }
+ revalidate();
+ }
+ });
+ waitForSelection();
+ return selectedIndex.get();
+ }
+
+
+ /**
+ * Wait until the user has made a selection.
+ */
+ private void waitForSelection()
+ {
+ lock.lock();
+ try
+ {
+ while (selectedIndex.get() < 0)
+ {
+ selected.awaitUninterruptibly();
+ }
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+
+
+ /**
+ * Swing panel that wraps a rendered entity and a button for selecting
+ * that entity.
+ */
+ private class EntityPanel extends JPanel
+ {
+ EntityPanel(JComponent entityComponent, final int index)
+ {
+ super(new BorderLayout());
+ add(entityComponent, BorderLayout.CENTER);
+ JButton selectButton = new JButton("Select");
+ selectButton.setName("Selection-" + index); // This helps to find the button from a unit test.
+ selectButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent actionEvent)
+ {
+ lock.lock();
+ try
+ {
+ selectedIndex.set(index);
+ selected.signalAll();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+ });
+ add(selectButton, BorderLayout.SOUTH);
+ setBorder(BorderFactory.createEtchedBorder());
+ }
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java
new file mode 100644
index 0000000..6e76daa
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingEvolutionObserver.java
@@ -0,0 +1,78 @@
+package org.uncommons.watchmaker.swing;
+
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.uncommons.util.concurrent.ConfigurableThreadFactory;
+import org.uncommons.watchmaker.framework.EvolutionObserver;
+import org.uncommons.watchmaker.framework.PopulationData;
+
+/**
+ * Limits the update rate of a Swing-based {@link EvolutionObserver}.
+ *
+ * @param <T> The population type.
+ * @author Gili Tzabari
+ */
+public class SwingEvolutionObserver<T> implements EvolutionObserver<T>
+{
+ private final EvolutionObserver<T> delegate;
+ private final long delay;
+ private final TimeUnit unit;
+ private final ConfigurableThreadFactory threadFactory = new ConfigurableThreadFactory("SwingEvolutionObserver",
+ Thread.NORM_PRIORITY,
+ true);
+ private final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, threadFactory);
+
+ private final AtomicReference<PopulationData<? extends T>> latestPopulation
+ = new AtomicReference<PopulationData<? extends T>>();
+
+
+ /**
+ * Creates a new SwingEvolutionObserver.
+ *
+ * @param delegate The underlying EvolutionObserver to update.
+ * @param delay The amount of time to wait before updating the underlying {@link EvolutionObserver}.
+ * @param unit The time unit of delay.
+ * @throws NullPointerException If delegate or unit are null.
+ * @throws IllegalArgumentException If delay is negative.
+ */
+ public SwingEvolutionObserver(EvolutionObserver<T> delegate, long delay, TimeUnit unit)
+ {
+ if (delegate == null)
+ {
+ throw new NullPointerException("delegate may not be null");
+ }
+ if (unit == null)
+ {
+ 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<? extends T> populationData)
+ {
+ if (latestPopulation.getAndSet(populationData) != null)
+ {
+ // An update is already scheduled.
+ return;
+ }
+
+ // Schedule an update in 300ms.
+ timer.schedule(new Runnable()
+ {
+ public void run()
+ {
+ delegate.populationUpdate(latestPopulation.getAndSet(null));
+ }
+ }, delay, unit);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java
new file mode 100644
index 0000000..f840f82
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/SwingIslandEvolutionObserver.java
@@ -0,0 +1,98 @@
+package org.uncommons.watchmaker.swing;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import org.uncommons.util.concurrent.ConfigurableThreadFactory;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver;
+
+/**
+ * Limits the update rate of a Swing-based {@link IslandEvolutionObserver}.
+ *
+ * @param <T> The population type.
+ * @author Gili Tzabari
+ */
+public class SwingIslandEvolutionObserver<T> implements IslandEvolutionObserver<T>
+{
+ private final IslandEvolutionObserver<T> delegate;
+ private final long delay;
+ private final TimeUnit unit;
+ private final ConfigurableThreadFactory threadFactory = new ConfigurableThreadFactory("SwingIslandEvolutionObserver",
+ Thread.NORM_PRIORITY,
+ true);
+ private final ScheduledExecutorService timer = Executors.newScheduledThreadPool(1, threadFactory);
+ private final AtomicReference<PopulationData<? extends T>> latestPopulation
+ = new AtomicReference<PopulationData<? extends T>>();
+ private final ConcurrentHashMap<Integer, PopulationData<? extends T>> latestIslandPopulation
+ = new ConcurrentHashMap<Integer, PopulationData<? extends T>>();
+
+ /**
+ * Creates a new SwingIslandEvolutionObserver.
+ *
+ * @param delegate The underlying {@link IslandEvolutionObserver} to update.
+ * @param delay The amount of time to wait before updating the underlying {@link IslandEvolutionObserver}.
+ * @param unit The time unit of delay.
+ * @throws NullPointerException If delegate or unit are null.
+ * @throws IllegalArgumentException If delay is negative.
+ */
+ public SwingIslandEvolutionObserver(IslandEvolutionObserver<T> delegate, long delay, TimeUnit unit)
+ {
+ if (delegate == null)
+ {
+ throw new NullPointerException("delegate may not be null");
+ }
+ if (unit == null)
+ {
+ 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<? extends T> populationData)
+ {
+ if (latestPopulation.getAndSet(populationData) != null)
+ {
+ // An update is already scheduled.
+ return;
+ }
+
+ // Schedule an update.
+ timer.schedule(new Runnable()
+ {
+ public void run()
+ {
+ delegate.populationUpdate(latestPopulation.getAndSet(null));
+ }
+ }, delay, unit);
+ }
+
+
+ public void islandPopulationUpdate(final int islandIndex, PopulationData<? extends T> populationData)
+ {
+ if (latestIslandPopulation.put(islandIndex, populationData) != null)
+ {
+ // An update is already scheduled.
+ return;
+ }
+
+ // Schedule an update.
+ timer.schedule(new Runnable()
+ {
+ public void run()
+ {
+ delegate.islandPopulationUpdate(islandIndex, latestIslandPopulation.remove(islandIndex));
+ }
+ }, delay, unit);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java
new file mode 100644
index 0000000..73e7bcb
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitor.java
@@ -0,0 +1,244 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BorderLayout;
+import java.awt.Window;
+import java.lang.reflect.InvocationTargetException;
+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 org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver;
+import org.uncommons.watchmaker.swing.ObjectSwingRenderer;
+import org.uncommons.watchmaker.swing.SwingIslandEvolutionObserver;
+
+/**
+ * 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.
+ * @param <T> The type of the evolved entities monitored by this component.
+ * @author Daniel Dyer
+ */
+public class EvolutionMonitor<T> implements IslandEvolutionObserver<T>
+{
+ private final List<IslandEvolutionObserver<? super T>> views = new LinkedList<IslandEvolutionObserver<? super T>>();
+
+ private JComponent monitorComponent;
+ private Window window = null;
+
+ private final boolean islands;
+
+ /**
+ * <p>Creates an EvolutionMonitor with a single panel that graphs the fitness scores
+ * of the population from generation to generation.</p>
+ * <p>If you are using {@link org.uncommons.watchmaker.framework.islands.IslandEvolution},
+ * use the {@link #EvolutionMonitor(boolean)} constructor instead, to enable island support.</p>
+ */
+ public EvolutionMonitor()
+ {
+ this(false);
+ }
+
+
+ /**
+ * 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.
+ */
+ public EvolutionMonitor(boolean islands)
+ {
+ this(new ObjectSwingRenderer(), 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 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<? super T, JComponent> renderer, boolean islands)
+ {
+ this.islands = islands;
+ if (SwingUtilities.isEventDispatchThread())
+ {
+ init(renderer);
+ }
+ else
+ {
+ try
+ {
+ SwingUtilities.invokeAndWait(new Runnable()
+ {
+ public void run()
+ {
+ init(renderer);
+ }
+ });
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(ex);
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new IllegalStateException(ex);
+ }
+ }
+ }
+
+
+ private void init(Renderer<? super T, JComponent> renderer)
+ {
+ JTabbedPane tabs = new JTabbedPane();
+ monitorComponent = new JPanel(new BorderLayout());
+ monitorComponent.add(tabs, BorderLayout.CENTER);
+
+ FittestCandidateView<T> candidateView = new FittestCandidateView<T>(renderer);
+ tabs.add("Fittest Individual", candidateView);
+ views.add(new SwingIslandEvolutionObserver<T>(candidateView, 300, TimeUnit.MILLISECONDS));
+
+ PopulationFitnessView fitnessView = new PopulationFitnessView(islands);
+ tabs.add(islands ? "Global Population" : "Population Fitness", fitnessView);
+ views.add(fitnessView);
+
+ if (islands)
+ {
+ IslandsView islandsView = new IslandsView();
+ tabs.add("Island Populations", islandsView);
+ views.add(new SwingIslandEvolutionObserver<Object>(islandsView, 300, TimeUnit.MILLISECONDS));
+ }
+
+ JVMView jvmView = new JVMView();
+ tabs.add("JVM Memory", jvmView);
+
+ StatusBar statusBar = new StatusBar(islands);
+ monitorComponent.add(statusBar, BorderLayout.SOUTH);
+ views.add(new SwingIslandEvolutionObserver<Object>(statusBar, 300, TimeUnit.MILLISECONDS));
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void populationUpdate(PopulationData<? extends T> populationData)
+ {
+ for (IslandEvolutionObserver<? super T> view : views)
+ {
+ view.populationUpdate(populationData);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends T> populationData)
+ {
+ for (IslandEvolutionObserver<? super T> view : views)
+ {
+ view.islandPopulationUpdate(islandIndex, populationData);
+ }
+ }
+
+
+ public JComponent getGUIComponent()
+ {
+ return monitorComponent;
+ }
+
+
+ /**
+ * 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.
+ */
+ public void showInFrame(final String title,
+ 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);
+ showWindow(frame);
+ }
+ });
+ }
+
+
+ /**
+ * 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
+ */
+ public void showInDialog(final JFrame owner,
+ final String title,
+ final boolean modal)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ JDialog dialog = new JDialog(owner, title, modal);
+ dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
+ showWindow(dialog);
+ }
+ });
+ }
+
+
+ /**
+ * 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)
+ {
+ if (window != null)
+ {
+ window.remove(getGUIComponent());
+ window.setVisible(false);
+ window.dispose();
+ window = null;
+ }
+ newWindow.add(getGUIComponent(), BorderLayout.CENTER);
+ newWindow.pack();
+ newWindow.setVisible(true);
+ this.window = newWindow;
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java
new file mode 100644
index 0000000..48e14eb
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateView.java
@@ -0,0 +1,101 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+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 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.
+ * @param <T> The type of the evolved entity displayed by this component.
+ * @author Daniel Dyer
+ */
+class FittestCandidateView<T> extends JPanel implements IslandEvolutionObserver<T>
+{
+ private static final Font BIG_FONT = new Font("Dialog", Font.BOLD, 16);
+
+ private final Renderer<? super T, JComponent> renderer;
+ private final JLabel fitnessLabel = new JLabel("N/A", JLabel.CENTER);
+ private final JScrollPane scroller = new JScrollPane();
+
+ 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.
+ */
+ FittestCandidateView(Renderer<? super T, JComponent> renderer)
+ {
+ super(new BorderLayout(0, 10));
+ this.renderer = renderer;
+
+ JPanel header = new JPanel(new BorderLayout());
+ JLabel label = new JLabel("Fitness", JLabel.CENTER);
+ header.add(label, BorderLayout.NORTH);
+ fitnessLabel.setFont(BIG_FONT);
+ header.add(fitnessLabel, BorderLayout.CENTER);
+ add(header, BorderLayout.NORTH);
+
+ scroller.setBackground(null);
+ scroller.getViewport().setBackground(null);
+ scroller.setBorder(null);
+ add(scroller, BorderLayout.CENTER);
+
+ // Set names for easier identification in unit tests.
+ fitnessLabel.setName("FitnessLabel");
+ }
+
+
+ public void populationUpdate(final PopulationData<? extends T> populationData)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ fitnessLabel.setText(String.valueOf(populationData.getBestCandidateFitness()));
+ // If the fittest candidate is already displayed (because it was the fittest
+ // candidate in the previous generation), don't incur the expense of rendering
+ // it again. Note that we still have to update the fitness score label above
+ // because the fitness may be different even if the candidate isn't (in the
+ // case where fitness is evaluated against other members of the population).
+ if (populationData.getBestCandidate() != fittestCandidate)
+ {
+ fittestCandidate = populationData.getBestCandidate();
+ JComponent renderedCandidate = renderer.render(fittestCandidate);
+ scroller.setViewportView(renderedCandidate);
+ }
+ }
+ });
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, final PopulationData<? extends T> populationData)
+ {
+ // Do nothing.
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java
new file mode 100644
index 0000000..fe784ca
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/IslandsView.java
@@ -0,0 +1,122 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BorderLayout;
+import java.awt.FlowLayout;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.SwingUtilities;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.islands.IslandEvolutionObserver;
+
+/**
+ * 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<Object>
+{
+ 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 AtomicInteger islandCount = new AtomicInteger(0);
+ private final Object maxLock = new Object();
+ private double max = 0;
+
+
+
+ IslandsView()
+ {
+ super(new BorderLayout());
+ add(createControls(), BorderLayout.SOUTH);
+ }
+
+
+ /**
+ * Creates the GUI controls for toggling graph display options.
+ * @return A component that can be added to the main panel.
+ */
+ private JComponent createControls()
+ {
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+ final JCheckBox meanCheckBox = new JCheckBox("Show Mean and Standard Deviation", false);
+ meanCheckBox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent itemEvent)
+ {
+ }
+ });
+ controls.add(meanCheckBox);
+
+ return controls;
+ }
+
+
+
+ public void islandPopulationUpdate(final int islandIndex, final PopulationData<? extends Object> populationData)
+ {
+ // Make sure the bars are added to the chart in order of island index, regardless of which island
+ // reports its results first.
+ if (islandIndex >= islandCount.get())
+ {
+ try
+ {
+ SwingUtilities.invokeAndWait(new Runnable()
+ {
+ public void run()
+ {
+ // Don't need synchronisation here because SwingUtilities queues these updates
+ // and if a second update gets queued, the loop will be a no-op so it's not a problem.
+ for (Integer i = islandCount.get(); i <= islandIndex; i++)
+ {
+ bestDataSet.addValue(0, FITTEST_INDIVIDUAL_LABEL, i);
+ meanDataSet.add(0, 0, MEAN_FITNESS_LABEL, i);
+ islandCount.incrementAndGet();
+ }
+ }
+ });
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (InvocationTargetException ex)
+ {
+ throw new IllegalStateException(ex.getCause());
+ }
+ }
+ }
+
+
+ public void populationUpdate(PopulationData<? extends Object> populationData)
+ {
+ synchronized (maxLock)
+ {
+ max = 0;
+ }
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/JVMView.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/JVMView.java
new file mode 100644
index 0000000..7d80de4
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/JVMView.java
@@ -0,0 +1,151 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BasicStroke;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.FlowLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.lang.management.ManagementFactory;
+import java.lang.management.MemoryMXBean;
+import java.lang.management.MemoryUsage;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JPanel;
+import javax.swing.Timer;
+import org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.DateAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.chart.plot.ValueMarker;
+import org.jfree.data.time.Second;
+import org.jfree.data.time.TimeSeries;
+import org.jfree.data.time.TimeSeriesCollection;
+import org.jfree.ui.RectangleAnchor;
+import org.jfree.ui.TextAnchor;
+
+/**
+ * Evolution monitor panel that displays information about the current
+ * state of the Java Virtual machine that is running the program.
+ * @author Daniel Dyer
+ */
+class JVMView extends JPanel
+{
+ private static final int MEGABYTE = 1048576;
+
+ private final TimeSeries memoryUsageSeries = new TimeSeries("Memory Usage");
+ private final TimeSeries heapSizeSeries = new TimeSeries("Heap Size");
+
+ private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
+
+ JVMView()
+ {
+ super(new BorderLayout());
+ double maxMemory = (double) memoryBean.getHeapMemoryUsage().getMax() / MEGABYTE;
+
+ ChartPanel heapPanel = new ChartPanel(createHeapChart(maxMemory),
+ false, // Properties
+ true, // Save
+ true, // Print
+ false, // Zoom
+ true); // Tooltips
+ heapPanel.setMouseZoomable(false);
+ add(heapPanel, BorderLayout.CENTER);
+ add(createControls(), BorderLayout.SOUTH);
+
+ Timer timer = new Timer(5000, new ActionListener()
+ {
+ public void actionPerformed(ActionEvent e)
+ {
+ addMemoryDataPoint();
+ }
+ });
+
+ // Plot start values.
+ addMemoryDataPoint();
+
+ timer.start();
+ }
+
+
+ private JFreeChart createHeapChart(double maxMemory)
+ {
+ TimeSeriesCollection dataSet = new TimeSeriesCollection();
+ dataSet.addSeries(memoryUsageSeries);
+ dataSet.addSeries(heapSizeSeries);
+ JFreeChart chart = ChartFactory.createXYAreaChart("JVM Heap",
+ "Time",
+ "Megabytes",
+ dataSet,
+ PlotOrientation.VERTICAL,
+ true, // Legend.
+ false, // Tooltips.
+ false);
+ DateAxis timeAxis = new DateAxis("Time");
+ timeAxis.setLowerMargin(0);
+ timeAxis.setUpperMargin(0);
+ chart.getXYPlot().setDomainAxis(timeAxis);
+ chart.getXYPlot().getRangeAxis().setLowerBound(0);
+ chart.getXYPlot().getRangeAxis().setUpperBound(maxMemory * 1.1); // Add 10% to leave room for marker.
+
+ // Add a horizontal marker to indicate the heap growth limit.
+ ValueMarker marker = new ValueMarker(maxMemory, Color.BLACK, new BasicStroke(1));
+ marker.setLabel("Maximum Permitted Heap Size (adjust with -Xmx)");
+ marker.setLabelTextAnchor(TextAnchor.BOTTOM_RIGHT);
+ marker.setLabelAnchor(RectangleAnchor.RIGHT);
+ chart.getXYPlot().addRangeMarker(marker);
+
+ chart.getXYPlot().getRenderer().setSeriesPaint(0, Color.RED);
+ chart.getXYPlot().getRenderer().setSeriesPaint(1, new Color(0, 128, 0, 128));
+
+ return chart;
+ }
+
+
+ /**
+ * Creates the GUI controls for toggling graph display options.
+ * @return A component that can be added to the main panel.
+ */
+ private JComponent createControls()
+ {
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+ JButton gcButton = new JButton("Request GC");
+ gcButton.setToolTipText("Perform garbage collection (the JVM may ignore this request).");
+ gcButton.addActionListener(new ActionListener()
+ {
+ public void actionPerformed(ActionEvent ev)
+ {
+ memoryBean.gc();
+ }
+ });
+ controls.add(gcButton);
+ return controls;
+ }
+
+
+
+ private void addMemoryDataPoint()
+ {
+ MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
+ double usedMegabytes = (double) heapUsage.getUsed() / MEGABYTE;
+ Second second = new Second();
+ memoryUsageSeries.add(second, usedMegabytes);
+ heapSizeSeries.add(second, (double) heapUsage.getCommitted() / MEGABYTE);
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java
new file mode 100644
index 0000000..3e4566f
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/PopulationFitnessView.java
@@ -0,0 +1,228 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BorderLayout;
+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 org.jfree.chart.ChartFactory;
+import org.jfree.chart.ChartPanel;
+import org.jfree.chart.JFreeChart;
+import org.jfree.chart.axis.NumberAxis;
+import org.jfree.chart.axis.ValueAxis;
+import org.jfree.chart.plot.PlotOrientation;
+import org.jfree.data.xy.XYSeries;
+import org.jfree.data.xy.XYSeriesCollection;
+import org.uncommons.watchmaker.framework.PopulationData;
+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.
+ * @author Daniel Dyer
+ */
+class PopulationFitnessView extends JPanel implements IslandEvolutionObserver<Object>
+{
+ 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;
+
+
+ PopulationFitnessView(boolean islands)
+ {
+ super(new BorderLayout());
+ meanSeries = new XYSeries(islands ? "Global Mean Fitness" : "Population Mean Fitness");
+ dataSet.addSeries(bestSeries);
+ dataSet.addSeries(meanSeries);
+ chart = ChartFactory.createXYLineChart(islands ? "Global Population Fitness" : "Population Fitness",
+ islands ? "Epochs" : "Generations",
+ "Fitness",
+ dataSet,
+ PlotOrientation.VERTICAL,
+ true, // Legend.
+ false, // Tooltips.
+ false);
+ this.domainAxis = chart.getXYPlot().getDomainAxis();
+ this.rangeAxis = chart.getXYPlot().getRangeAxis();
+ domainAxis.setStandardTickUnits(NumberAxis.createIntegerTickUnits());
+ domainAxis.setLowerMargin(0);
+ domainAxis.setUpperMargin(0.05);
+ domainAxis.setRangeWithMargins(0, SHOW_FIXED_GENERATIONS);
+ rangeAxis.setRange(minY, maxY);
+ 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
+ add(chartPanel, BorderLayout.CENTER);
+ add(createControls(islands), BorderLayout.SOUTH);
+ }
+
+
+ /**
+ * Creates the GUI controls for toggling graph display options.
+ * @return A component that can be added to the main panel.
+ */
+ private JComponent createControls(boolean islands)
+ {
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.RIGHT));
+
+ allDataButton.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent ev)
+ {
+ updateDomainAxisRange();
+ }
+ });
+ String text = "Last " + SHOW_FIXED_GENERATIONS + (islands ? " Epochs" : " Generations");
+ JRadioButton recentDataButton = new JRadioButton(text, true);
+ ButtonGroup buttonGroup = new ButtonGroup();
+ buttonGroup.add(allDataButton);
+ buttonGroup.add(recentDataButton);
+
+ controls.add(allDataButton);
+ controls.add(recentDataButton);
+
+ final JCheckBox meanCheckBox = new JCheckBox("Show Mean", true);
+ meanCheckBox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent itemEvent)
+ {
+ if (itemEvent.getStateChange() == ItemEvent.SELECTED)
+ {
+ dataSet.addSeries(meanSeries);
+ }
+ else
+ {
+ dataSet.removeSeries(meanSeries);
+ }
+ }
+ });
+ controls.add(meanCheckBox);
+
+ invertCheckBox.addItemListener(new ItemListener()
+ {
+ public void itemStateChanged(ItemEvent itemEvent)
+ {
+ rangeAxis.setInverted(invertCheckBox.isSelected());
+ }
+ });
+ controls.add(invertCheckBox);
+
+ return controls;
+ }
+
+
+ /**
+ * If "all data" is selected, set the range of the domain axis to include all
+ * values. Otherwise set it to show the most recent 200 generations.
+ */
+ private void updateDomainAxisRange()
+ {
+ int count = dataSet.getSeries(0).getItemCount();
+ if (count < SHOW_FIXED_GENERATIONS)
+ {
+ domainAxis.setRangeWithMargins(0, SHOW_FIXED_GENERATIONS);
+ }
+ else if (allDataButton.isSelected())
+ {
+ domainAxis.setRangeWithMargins(0, Math.max(SHOW_FIXED_GENERATIONS, count));
+ }
+ else
+ {
+ domainAxis.setRangeWithMargins(count - SHOW_FIXED_GENERATIONS, count);
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void populationUpdate(final PopulationData<?> populationData)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ chart.setNotify(false); // Avoid triggering a redraw for every change we make in this method.
+ if (populationData.getGenerationNumber() == 0)
+ {
+ if (!populationData.isNaturalFitness())
+ {
+ invertCheckBox.setSelected(true);
+ }
+ // 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);
+
+ // 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());
+ if (high > maxY)
+ {
+ maxY = high;
+ rangeAxis.setRange(minY, maxY);
+ }
+ if (low < minY)
+ {
+ minY = low;
+ rangeAxis.setRange(minY, maxY);
+ }
+
+ updateDomainAxisRange();
+ chart.setNotify(true); // Redraw all at once now.
+ }
+ });
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends Object> populationData)
+ {
+ // Do nothing.
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java
new file mode 100644
index 0000000..51c6651
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBar.java
@@ -0,0 +1,165 @@
+//=============================================================================
+// 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.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 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.
+ * @author Daniel Dyer
+ */
+public class StatusBar extends Box implements IslandEvolutionObserver<Object>
+{
+ private final JLabel generationsLabel = new JLabel("N/A", JLabel.RIGHT);
+ 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;
+
+
+ /**
+ * Creates a status bar configured for non-island evolution.
+ */
+ public StatusBar()
+ {
+ this(false);
+ }
+
+
+ /**
+ * @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}
+ */
+ public StatusBar(boolean islands)
+ {
+ super(BoxLayout.X_AXIS);
+ add(new JLabel("Population: "));
+ add(populationLabel);
+ add(createHorizontalStrut(15));
+ add(new JLabel("Elitism: "));
+ add(elitismLabel);
+ add(createHorizontalStrut(15));
+ add(new JLabel(islands ? "Epochs: " : "Generations: "));
+ add(generationsLabel);
+ add(createHorizontalStrut(15));
+ add(new JLabel("Elapsed Time: "));
+ add(timeLabel);
+ setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
+
+ // Set component names for easy look-up from tests.
+ populationLabel.setName("Population");
+ elitismLabel.setName("Elitism");
+ generationsLabel.setName("Generations");
+ timeLabel.setName("Time");
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void populationUpdate(final PopulationData<?> populationData)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ if (populationData.getGenerationNumber() == 0)
+ {
+ int islandSize = islandPopulationSize.get();
+ if (islandSize > 0)
+ {
+ int islandCount = populationData.getPopulationSize() / islandSize;
+ populationLabel.setText(islandCount + "x" + islandSize);
+ elitismLabel.setText(islandCount + "x" + populationData.getEliteCount());
+ }
+ else
+ {
+ populationLabel.setText(String.valueOf(populationData.getPopulationSize()));
+ elitismLabel.setText(String.valueOf(populationData.getEliteCount()));
+ }
+ }
+ generationsLabel.setText(String.valueOf(populationData.getGenerationNumber() + 1));
+ elapsedTime = populationData.getElapsedTime();
+ epochTime = 0;
+ timeLabel.setText(formatTime(elapsedTime));
+ }
+ });
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public void islandPopulationUpdate(int islandIndex,
+ final PopulationData<? extends Object> populationData)
+ {
+ islandPopulationSize.compareAndSet(-1, populationData.getPopulationSize());
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ public void run()
+ {
+ // Only update the label if the time has advanced. Sometimes, due to threading
+ // variations, later updates have shorter elapsed times.
+ if (populationData.getElapsedTime() > epochTime)
+ {
+ epochTime = populationData.getElapsedTime();
+ timeLabel.setText(formatTime(elapsedTime + epochTime));
+ }
+ }
+ });
+ }
+
+
+ private String formatTime(long time)
+ {
+ long seconds = time / 1000;
+ long minutes = seconds / 60;
+ seconds %= 60;
+ long hours = minutes / 60;
+ minutes %= 60;
+ StringBuilder buffer = new StringBuilder();
+ if (hours < 10)
+ {
+ buffer.append('0');
+ }
+ buffer.append(hours);
+ buffer.append(':');
+ if (minutes < 10)
+ {
+ buffer.append('0');
+ }
+ buffer.append(minutes);
+ buffer.append(':');
+ if (seconds < 10)
+ {
+ buffer.append('0');
+ }
+ buffer.append(seconds);
+ return buffer.toString();
+ }
+}
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/package-info.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/package-info.java
new file mode 100644
index 0000000..b975985
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/evolutionmonitor/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * This package provides an experimental evolution monitor Swing component.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.swing.evolutionmonitor;
diff --git a/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/package-info.java b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/package-info.java
new file mode 100644
index 0000000..d8dfc18
--- /dev/null
+++ b/watchmaker/swing/src/java/main/org/uncommons/watchmaker/swing/package-info.java
@@ -0,0 +1,23 @@
+//=============================================================================
+// 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.
+//=============================================================================
+/**
+ * This package provides a number of classes to simplify the development of
+ * Swing GUIs for evolutionary programs. As well as common controls for
+ * modifying evolution parameters, it includes a Swing implementation of the
+ * {@link org.uncommons.watchmaker.framework.interactive.Console} interface
+ * for interactive evolutionary algorithms.
+ */
+package org.uncommons.watchmaker.swing;
diff --git a/watchmaker/swing/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java b/watchmaker/swing/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java
new file mode 100644
index 0000000..3c45f5d
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/swing/SwingBackgroundTaskTest.java
@@ -0,0 +1,100 @@
+//=============================================================================
+// 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.swing;
+
+import javax.swing.SwingUtilities;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link SwingBackgroundTask}. Ensures code is
+ * executed on correct threads.
+ * @author Daniel Dyer
+ */
+public class SwingBackgroundTaskTest
+{
+ private boolean taskExecuted;
+ private boolean taskOnEDT;
+ private boolean postProcessingExecuted;
+ private boolean postProcessingOnEDT;
+ private boolean exceptionHandled;
+
+ @Test
+ public void testExecutionThreads() throws InterruptedException
+ {
+ SwingBackgroundTask<Object> testTask = new SwingBackgroundTask<Object>()
+ {
+ @Override
+ protected Object performTask()
+ {
+ taskExecuted = true;
+ taskOnEDT = SwingUtilities.isEventDispatchThread();
+ return null;
+ }
+
+ @Override
+ protected void postProcessing(Object result)
+ {
+ super.postProcessing(result);
+ postProcessingExecuted = true;
+ postProcessingOnEDT = SwingUtilities.isEventDispatchThread();
+ }
+ };
+ testTask.execute();
+ testTask.waitForCompletion();
+ assert taskExecuted : "Task was not executed.";
+ assert postProcessingExecuted : "Post-processing was not executed.";
+ assert !taskOnEDT : "Task was executed on EDT.";
+ assert postProcessingOnEDT : "Post-processing was not executed on EDT.";
+ }
+
+
+ /**
+ * Exceptions in the {@link SwingBackgroundTask#performTask()} method should
+ * not be swallowed, they must be passed to the
+ * {@link SwingBackgroundTask#onError(Throwable)} method.
+ */
+ @Test
+ public void testExceptionInTask() throws InterruptedException
+ {
+ SwingBackgroundTask<Object> testTask = new SwingBackgroundTask<Object>()
+ {
+ @Override
+ protected Object performTask()
+ {
+ throw new UnsupportedOperationException("Task failed.");
+ }
+
+
+ @Override
+ protected void onError(Throwable throwable)
+ {
+ // Make sure we've been passed the right exception.
+ if (throwable.getClass().equals(UnsupportedOperationException.class))
+ {
+ exceptionHandled = true;
+ }
+ else
+ {
+ Reporter.log("Wrong exception class: " + throwable.getClass());
+ }
+ }
+ };
+ testTask.execute();
+ testTask.waitForCompletion();
+ assert exceptionHandled : "Exception was not handled.";
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/AbortControlTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/AbortControlTest.java
new file mode 100644
index 0000000..340ba97
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/AbortControlTest.java
@@ -0,0 +1,41 @@
+//=============================================================================
+// 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.swing;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for evolution abort control.
+ * @author Daniel Dyer
+ */
+public class AbortControlTest
+{
+ /**
+ * Make sure that clicking the button causes the termination condition
+ * to be satisfied.
+ */
+ @Test
+ public void testAbort()
+ {
+ AbortControl control = new AbortControl();
+ assert !control.getTerminationCondition().shouldTerminate(null) : "Abort condition should be false.";
+ control.getControl().doClick(); // Should fire an event that changes the condition.
+ assert control.getTerminationCondition().shouldTerminate(null) : "Abort condition should be true.";
+ // Finally, ensure that reset works as expected.
+ control.reset();
+ assert !control.getTerminationCondition().shouldTerminate(null) : "Abort condition should be false.";
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/NumericParameterControlTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/NumericParameterControlTest.java
new file mode 100644
index 0000000..e69b3ad
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/NumericParameterControlTest.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// 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.swing;
+
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.NumberGenerator;
+
+/**
+ * Unit test for the numeric parameter GUI control.
+ * @author Daniel Dyer
+ */
+public class NumericParameterControlTest
+{
+ /**
+ * Make sure that the control works with integers.
+ */
+ @Test
+ public void testIntegerValues()
+ {
+ NumericParameterControl<Integer> control = new NumericParameterControl<Integer>(50, 100, 1, 60);
+ NumberGenerator<Integer> generator = control.getNumberGenerator();
+
+ // Check the initial output of the generator.
+ int initialValue = generator.nextValue();
+ assert initialValue == 60 : "Initial value should be 60, is " + initialValue;
+
+ // Modify the position of the slider and check that the generator value changes.
+ control.getControl().setValue(87);
+ int adjustedValue = generator.nextValue();
+ assert adjustedValue == 87 : "Adjusted value should be 87, is " + adjustedValue;
+
+ // Reset the control and check that the output is reverted to the default.
+ control.reset();
+ int resetValue = generator.nextValue();
+ assert resetValue == 60 : "Reset value should be 60, is " + resetValue;
+ }
+
+
+ /**
+ * Make sure that the control works with doubles.
+ */
+ @Test
+ public void testRealValues()
+ {
+ NumericParameterControl<Double> control = new NumericParameterControl<Double>(0d, 1d, 0.01d, 0.4d);
+ NumberGenerator<Double> generator = control.getNumberGenerator();
+
+ // Check the initial output of the generator.
+ double initialValue = generator.nextValue();
+ assert initialValue == 0.4d : "Initial value should be 0.4, is " + initialValue;
+
+ // Modify the position of the slider and check that the generator value changes.
+ control.getControl().setValue(0.73d);
+ double adjustedValue = generator.nextValue();
+ assert adjustedValue == 0.73d : "Adjusted value should be 0.73, is " + adjustedValue;
+
+ // Reset the control and check that the output is reverted to the default.
+ control.reset();
+ double resetValue = generator.nextValue();
+ assert resetValue == 0.4d : "Reset value should be 0.4, is " + resetValue;
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ObjectSwingRendererTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ObjectSwingRendererTest.java
new file mode 100644
index 0000000..a77bd3d
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ObjectSwingRendererTest.java
@@ -0,0 +1,38 @@
+//=============================================================================
+// 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.swing;
+
+import java.math.BigDecimal;
+import javax.swing.JComponent;
+import javax.swing.text.JTextComponent;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.interactive.Renderer;
+
+/**
+ * Unit test for the {@link ObjectSwingRenderer} class.
+ * @author Daniel Dyer
+ */
+public class ObjectSwingRendererTest
+{
+ @Test
+ public void testRendering()
+ {
+ Renderer<Object, JComponent> renderer = new ObjectSwingRenderer();
+ JTextComponent textComponent = (JTextComponent) renderer.render(BigDecimal.TEN);
+ assert textComponent.getText().equals("10") : "Wrong text rendered.";
+ assert !textComponent.isEditable() : "Text component should not be editable.";
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ProbabilityParameterControlTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ProbabilityParameterControlTest.java
new file mode 100644
index 0000000..1da22ea
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/ProbabilityParameterControlTest.java
@@ -0,0 +1,163 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import javax.swing.JSlider;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Unit test for the {@link ProbabilityParameterControl} component.
+ * @author Daniel Dyer
+ */
+public class ProbabilityParameterControlTest
+{
+ private Robot robot;
+
+ @BeforeMethod(groups = "display-required")
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod(groups = "display-required")
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test
+ public void testDefaultValue()
+ {
+ Probability defaultValue = new Probability(0.75d);
+ ProbabilityParameterControl control = new ProbabilityParameterControl(defaultValue);
+ assert control.getNumberGenerator().nextValue().equals(defaultValue) : "Wrong initial value.";
+ }
+
+
+ /**
+ * Initial value must not be less than the minimum.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDefaultValueTooLow()
+ {
+ new ProbabilityParameterControl(Probability.EVENS,
+ Probability.ONE,
+ 2,
+ new Probability(0.45d)); // Should throw an IllegalArgumentException.
+ }
+
+
+ /**
+ * Initial value must not be less than the minimum.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDefaultValueTooHigh()
+ {
+ new ProbabilityParameterControl(Probability.ZERO,
+ Probability.EVENS,
+ 2,
+ new Probability(0.55d)); // Should throw an IllegalArgumentException.
+ }
+
+
+ /**
+ * Minimum must be less than maximum.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testMinimumHigherThanMaximum()
+ {
+ new ProbabilityParameterControl(new Probability(0.7d),
+ new Probability(0.6d),
+ 2,
+ new Probability(0.7d)); // Should throw an IllegalArgumentException.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidDecimalPlaces()
+ {
+ new ProbabilityParameterControl(Probability.ZERO,
+ Probability.ONE,
+ 0, // Invalid, should trigger IllegalArgumentException.
+ Probability.EVENS);
+ }
+
+
+ @Test(dependsOnMethods = "testDefaultValue",
+ groups = "display-required")
+ public void testSlider()
+ {
+ Probability defaultValue = new Probability(0.75d);
+ ProbabilityParameterControl control = new ProbabilityParameterControl(defaultValue);
+
+ JFrame frame = new JFrame();
+ frame.add(control.getControl(), BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(300, 50);
+ frame.validate();
+ frame.setVisible(true);
+
+ JSlider slider = frameFixture.slider().component();
+ assert slider.getValue() == 75 : "Wrong slider position: " + slider.getValue();
+ String displayedValue = frameFixture.label().text();
+ assert displayedValue.equals("0.75") : "Wrong value displayed: " + displayedValue;
+
+
+ frameFixture.slider().slideTo(80); // 80 ticks is a probability of 0.8.
+ robot.waitForIdle();
+ double probability = control.getNumberGenerator().nextValue().doubleValue();
+ assert probability == 0.8 : "Wrong probability: " + probability;
+ displayedValue = frameFixture.label().text();
+ assert displayedValue.equals("0.80") : "Wrong value displayed: " + displayedValue;
+ }
+
+
+ @Test(dependsOnMethods = "testSlider",
+ groups = "display-required")
+ public void testReset()
+ {
+ Probability defaultValue = new Probability(0.75d);
+ ProbabilityParameterControl control = new ProbabilityParameterControl(defaultValue);
+
+ JFrame frame = new JFrame();
+ frame.add(control.getControl(), BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(300, 50);
+ frame.validate();
+ frame.setVisible(true);
+
+ JSlider slider = frameFixture.slider().component();
+ frameFixture.slider().slideTo(80); // 80 ticks is a probability of 0.8.
+
+ control.reset();
+ assert control.getNumberGenerator().nextValue().equals(defaultValue) : "NumberGenerator reset failed.";
+ assert slider.getValue() == 75 : "JSlider reset failed.";
+
+ String displayedValue = frameFixture.label().text();
+ assert displayedValue.equals("0.75") : "Wrong value displayed: " + displayedValue;
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SelectionStrategyControlTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SelectionStrategyControlTest.java
new file mode 100644
index 0000000..bd83fe6
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SelectionStrategyControlTest.java
@@ -0,0 +1,105 @@
+//=============================================================================
+// 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.swing;
+
+import java.util.LinkedList;
+import java.util.List;
+import javax.swing.JComboBox;
+import org.testng.annotations.Test;
+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.TruncationSelection;
+
+/**
+ * Unit test for the {@link SelectionStrategyControl}.
+ * @author Daniel Dyer
+ */
+public class SelectionStrategyControlTest
+{
+ @Test
+ public void testInitialisation()
+ {
+ SelectionStrategy<Object> rank = new RankSelection();
+ SelectionStrategy<Object> roulette = new RouletteWheelSelection();
+ List<SelectionStrategy<? super Object>> strategies = new LinkedList<SelectionStrategy<? super Object>>();
+ strategies.add(rank);
+ strategies.add(roulette);
+ SelectionStrategyControl<?> control = new SelectionStrategyControl<Object>(strategies);
+ JComboBox component = control.getControl();
+ assert component.getItemCount() == 2 : "Combobox should contain 2 entries, is " + component.getItemCount();
+ assert component.getItemAt(0) == rank : "First item should be rank selection.";
+ assert component.getItemAt(1) == roulette : "Second item should be roulette wheel selection.";
+ }
+
+
+ @Test
+ public void testChangeSelection()
+ {
+ SelectionStrategy<Object> quarter = new TruncationSelection(0.25);
+ SelectionStrategy<Object> half = new TruncationSelection(0.5);
+ List<SelectionStrategy<? super Object>> strategies = new LinkedList<SelectionStrategy<? super Object>>();
+ strategies.add(quarter);
+ strategies.add(half);
+ SelectionStrategyControl<Object> control = new SelectionStrategyControl<Object>(strategies);
+
+ List<EvaluatedCandidate<String>> population = new LinkedList<EvaluatedCandidate<String>>();
+ population.add(new EvaluatedCandidate<String>("DDD", 4));
+ population.add(new EvaluatedCandidate<String>("CCC", 3));
+ population.add(new EvaluatedCandidate<String>("BBB", 2));
+ population.add(new EvaluatedCandidate<String>("AAA", 1));
+
+ // Using the first selection strategy, only the fittest 25% of candidates should be selected from.
+ List<String> selection = control.getSelectionStrategy().select(population, true, 2, null);
+ assert selection.get(0).equals("DDD") : "Wrong candidate selected: " + selection.get(0);
+ assert selection.get(1).equals("DDD") : "Wrong candidate selected: " + selection.get(1);
+
+ JComboBox component = control.getControl();
+ component.setSelectedIndex(1); // Switch to 50% truncation.
+
+ // Using the second selection strategy, only the fittest 50% of candidates should be selected from.
+ selection = control.getSelectionStrategy().select(population, true, 2, null);
+ assert selection.contains("CCC") : "Candidate CCC missing from selection.";
+ assert selection.contains("DDD") : "Candidate DDD missing from selection.";
+ }
+
+
+ @Test(dependsOnMethods = "testChangeSelection")
+ public void testReset()
+ {
+ SelectionStrategy<Object> quarter = new TruncationSelection(0.25);
+ SelectionStrategy<Object> half = new TruncationSelection(0.5);
+ List<SelectionStrategy<? super Object>> strategies = new LinkedList<SelectionStrategy<? super Object>>();
+ strategies.add(quarter);
+ strategies.add(half);
+ SelectionStrategyControl<Object> control = new SelectionStrategyControl<Object>(strategies);
+
+ control.getControl().setSelectedIndex(1); // Not the first strategy.
+ control.reset(); // Reset to the first strategy.
+
+ List<EvaluatedCandidate<String>> population = new LinkedList<EvaluatedCandidate<String>>();
+ population.add(new EvaluatedCandidate<String>("DDD", 4));
+ population.add(new EvaluatedCandidate<String>("CCC", 3));
+ population.add(new EvaluatedCandidate<String>("BBB", 2));
+ population.add(new EvaluatedCandidate<String>("AAA", 1));
+
+ // Using the first selection strategy, only the fittest 25% of candidates should be selected from.
+ List<String> selection = control.getSelectionStrategy().select(population, true, 2, null);
+ assert selection.get(0).equals("DDD") : "Wrong candidate selected: " + selection.get(0);
+ assert selection.get(1).equals("DDD") : "Wrong candidate selected: " + selection.get(1);
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SwingConsoleTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SwingConsoleTest.java
new file mode 100644
index 0000000..56f4f41
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/SwingConsoleTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// 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.swing;
+
+import java.awt.BorderLayout;
+import java.util.Arrays;
+import java.util.List;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link SwingConsole} class.
+ * @author Daniel Dyer
+ */
+public class SwingConsoleTest
+{
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testUserSelection() throws InterruptedException
+ {
+ Robot robot = BasicRobot.robotWithNewAwtHierarchy();
+ final SwingConsole swingConsole = new SwingConsole();
+ JFrame frame = new JFrame();
+ frame.add(swingConsole, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(300, 100);
+ frame.validate();
+ frame.setVisible(true);
+
+ final List<JLabel> labels = Arrays.asList(new JLabel("Zero"),
+ new JLabel("One"),
+ new JLabel("Two"));
+
+ final int[] selection = new int[1];
+ new Thread(new Runnable()
+ {
+ public void run()
+ {
+ // This method blocks so we can't run it on the test thread.
+ selection[0] = swingConsole.select(labels);
+ }
+ }).start();
+ Thread.sleep(250); // TO DO: Come up with a proper solution to this race condition.
+ frameFixture.button("Selection-1").click();
+
+ assert selection[0] == 1
+ : "Second item (index 1) should have been selected, selection index was " + selection[0];
+
+ robot.cleanUp();
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitorTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitorTest.java
new file mode 100644
index 0000000..c3fb9e8
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/EvolutionMonitorTest.java
@@ -0,0 +1,107 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.Dialog;
+import java.awt.Frame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.core.matcher.DialogMatcher;
+import org.fest.swing.core.matcher.FrameMatcher;
+import org.fest.swing.fixture.DialogFixture;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+/**
+ * Basic unit test for the {@link EvolutionMonitor} component.
+ * @author Daniel Dyer
+ */
+public class EvolutionMonitorTest
+{
+ private Robot robot;
+
+ @BeforeMethod
+ public void prepare()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testShowInFrame()
+ {
+ EvolutionMonitor<String> monitor = new EvolutionMonitor<String>();
+ monitor.showInFrame("MonitorFrame", false);
+ robot.waitForIdle();
+ // There ought to be a visible frame containing the evolution monitor.
+ Frame frame = robot.finder().find(FrameMatcher.withTitle("MonitorFrame").andShowing());
+ assert monitor.getGUIComponent().isShowing() : "Evolution monitor should be showing.";
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frameFixture.close();
+ robot.waitForIdle();
+ assert !monitor.getGUIComponent().isShowing() : "Evolution monitor should not be showing.";
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testShowInDialog()
+ {
+ EvolutionMonitor<String> monitor = new EvolutionMonitor<String>();
+ monitor.showInDialog(null, "MonitorDialog", false);
+ robot.waitForIdle();
+ // There ought to be a visible dialog containing the evolution monitor.
+ Dialog dialog = robot.finder().find(DialogMatcher.withTitle("MonitorDialog").andShowing());
+ assert monitor.getGUIComponent().isShowing() : "Evolution monitor should be showing.";
+ DialogFixture dialogFixture = new DialogFixture(robot, dialog);
+ dialogFixture.close();
+ robot.waitForIdle();
+ assert !monitor.getGUIComponent().isShowing() : "Evolution monitor should not be showing.";
+ }
+
+
+ /**
+ * If the evolution monitor is already displayed in a window, a subsequent call to one of
+ * the show methods should result in that window being replaced.
+ */
+ @Test(dependsOnMethods = {"testShowInFrame", "testShowInDialog"},
+ groups = "display-required") // Will fail if run in a headless environment.
+ public void testShowInFrameThenShowInDialog()
+ {
+ EvolutionMonitor<String> monitor = new EvolutionMonitor<String>(true);
+ monitor.showInFrame("MonitorFrame", false);
+ robot.waitForIdle();
+ // There ought to be a visible frame containing the evolution monitor.
+ Frame frame = robot.finder().find(FrameMatcher.withTitle("MonitorFrame").andShowing());
+
+ monitor.showInDialog(null, "MonitorDialog", false);
+ robot.waitForIdle();
+ // There ought to be a visible dialog containing the evolution monitor.
+ robot.finder().find(DialogMatcher.withTitle("MonitorDialog").andShowing());
+ assert monitor.getGUIComponent().isShowing() : "Evolution monitor should be showing.";
+
+ assert !frame.isShowing() : "Frame should have been replaced by dialog.";
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java
new file mode 100644
index 0000000..4411423
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/FittestCandidateViewTest.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BorderLayout;
+import java.math.BigDecimal;
+import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.text.JTextComponent;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+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()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required")
+ public void testUpdate()
+ {
+ Renderer<Object, JComponent> renderer = new ObjectSwingRenderer();
+ FittestCandidateView<BigDecimal> view = new FittestCandidateView<BigDecimal>(renderer);
+ JFrame frame = new JFrame();
+ frame.add(view, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(300, 300);
+ frame.validate();
+ frame.setVisible(true);
+
+ view.populationUpdate(new PopulationData<BigDecimal>(BigDecimal.TEN, 10, 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;
+
+ // Check rendered candidate.
+ frameFixture.textBox().requireNotEditable();
+ String text = frameFixture.textBox().component().getText();
+ 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.
+ */
+ @Test(groups = "display-required",
+ dependsOnMethods = "testUpdate")
+ public void testUpdateSameCandidate()
+ {
+ Renderer<Object, JComponent> renderer = new ObjectSwingRenderer();
+ FittestCandidateView<BigDecimal> view = new FittestCandidateView<BigDecimal>(renderer);
+ JFrame frame = new JFrame();
+ frame.add(view, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(300, 300);
+ frame.validate();
+ frame.setVisible(true);
+
+ PopulationData<BigDecimal> data1 = new PopulationData<BigDecimal>(BigDecimal.TEN, 10, 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<BigDecimal> data2 = new PopulationData<BigDecimal>(BigDecimal.TEN, 10, 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.";
+ }
+}
diff --git a/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java
new file mode 100644
index 0000000..8dc416b
--- /dev/null
+++ b/watchmaker/swing/src/java/test/org/uncommons/watchmaker/swing/evolutionmonitor/StatusBarTest.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// 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.swing.evolutionmonitor;
+
+import java.awt.BorderLayout;
+import javax.swing.JFrame;
+import org.fest.swing.core.BasicRobot;
+import org.fest.swing.core.Robot;
+import org.fest.swing.fixture.FrameFixture;
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+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()
+ {
+ robot = BasicRobot.robotWithNewAwtHierarchy();
+ }
+
+
+ @AfterMethod
+ public void cleanUp()
+ {
+ robot.cleanUp();
+ robot = null;
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testFieldUpdates()
+ {
+ StatusBar statusBar = new StatusBar();
+ JFrame frame = new JFrame();
+ frame.add(statusBar, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(400, 30);
+ 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.";
+
+ statusBar.populationUpdate(new PopulationData<Object>(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.";
+ // 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.";
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testFieldUpdatesForIslandMode()
+ {
+ StatusBar statusBar = new StatusBar(true);
+ JFrame frame = new JFrame();
+ frame.add(statusBar, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(400, 30);
+ 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.";
+
+ statusBar.islandPopulationUpdate(0, new PopulationData<Object>(new Object(), 10, 8, 2, true, 10, 1, 0, 36610000));
+ statusBar.populationUpdate(new PopulationData<Object>(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.";
+ // 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.";
+ }
+
+
+ @Test(groups = "display-required") // Will fail if run in a headless environment.
+ public void testTimeFormat()
+ {
+ StatusBar statusBar = new StatusBar();
+ JFrame frame = new JFrame();
+ frame.add(statusBar, BorderLayout.CENTER);
+ FrameFixture frameFixture = new FrameFixture(robot, frame);
+ frame.setSize(400, 30);
+ frame.validate();
+ frameFixture.show();
+
+ // Previous test checks two-digit field values, this test checks that single-digit
+ // values and zeros are correctly padded.
+ statusBar.populationUpdate(new PopulationData<Object>(new Object(), 10, 8, 2, true, 10, 1, 0, 1000));
+ assert frameFixture.label("Time").text().equals("00:00:01");
+ }
+
+}
diff --git a/watchmaker/swing/swing.iml b/watchmaker/swing/swing.iml
new file mode 100644
index 0000000..09d2997
--- /dev/null
+++ b/watchmaker/swing/swing.iml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java/main" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/java/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="framework" />
+ <orderEntry type="library" name="Maven: org.uncommons.maths:uncommons-maths:1.2.2" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jcommon:1.0.12" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jfreechart:1.0.13" level="project" />
+ <orderEntry type="library" name="Maven: com.google.collections:google-collections:1.0" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.testng:testng:6.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: junit:junit:3.8.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.beanshell:bsh:2.0b4" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: com.beust:jcommander:1.12" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.yaml:snakeyaml:1.6" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-swing:1.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-assert:1.2" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-util:1.1.3" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.easytesting:fest-reflect:1.2" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: net.jcip:jcip-annotations:1.0" level="project" />
+ </component>
+</module>
+