summaryrefslogtreecommitdiffstats
path: root/watchmaker/examples
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/examples
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/examples')
-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
127 files changed, 12083 insertions, 0 deletions
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);
+ }
+}