summaryrefslogtreecommitdiffstats
path: root/watchmaker/framework
diff options
context:
space:
mode:
Diffstat (limited to 'watchmaker/framework')
-rw-r--r--watchmaker/framework/framework.iml25
-rw-r--r--watchmaker/framework/nb-configuration.xml61
-rw-r--r--watchmaker/framework/pom.xml52
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java97
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java64
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java38
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java33
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java81
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java81
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java60
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java179
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java341
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java97
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java72
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java109
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java163
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java57
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java115
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java129
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java63
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java112
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java78
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java55
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java132
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java48
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java165
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java49
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java147
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java32
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java76
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java53
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java57
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java58
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java62
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java21
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java177
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java73
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java21
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java53
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java375
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java38
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java36
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java67
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java68
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java22
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java184
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java128
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java100
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java70
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java44
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java131
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java129
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java87
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java68
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java164
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java105
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java136
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java96
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java119
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java132
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java95
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java22
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java113
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java118
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java114
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java95
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java115
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java117
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java20
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java52
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java49
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java113
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java63
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java70
-rw-r--r--watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java19
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java137
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java36
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java56
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java56
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java35
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java198
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java94
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java102
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java68
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java48
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java169
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java37
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java138
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java93
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java125
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java126
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java112
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java30
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java146
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java143
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java55
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java67
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java70
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java72
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java82
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java69
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java80
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java45
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java126
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java64
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java58
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java75
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java69
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java81
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java77
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java92
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java91
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java54
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java94
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java99
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java124
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java91
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java90
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java97
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java52
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java52
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java97
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java49
-rw-r--r--watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java39
137 files changed, 11859 insertions, 0 deletions
diff --git a/watchmaker/framework/framework.iml b/watchmaker/framework/framework.iml
new file mode 100644
index 0000000..82c64ca
--- /dev/null
+++ b/watchmaker/framework/framework.iml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_6" inherit-compiler-output="false">
+ <output url="file://$MODULE_DIR$/target/classes" />
+ <output-test url="file://$MODULE_DIR$/target/test-classes" />
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java/main" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/src/java/test" isTestSource="true" />
+ <excludeFolder url="file://$MODULE_DIR$/target" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="Maven: org.uncommons.maths:uncommons-maths:1.2.2" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jcommon:1.0.12" level="project" />
+ <orderEntry type="library" name="Maven: jfree:jfreechart:1.0.8" level="project" />
+ <orderEntry type="library" name="Maven: com.google.collections:google-collections:1.0" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.testng:testng:6.2.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: junit:junit:3.8.1" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.beanshell:bsh:2.0b4" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: com.beust:jcommander:1.12" level="project" />
+ <orderEntry type="library" scope="TEST" name="Maven: org.yaml:snakeyaml:1.6" level="project" />
+ </component>
+</module>
+
diff --git a/watchmaker/framework/nb-configuration.xml b/watchmaker/framework/nb-configuration.xml
new file mode 100644
index 0000000..ae35717
--- /dev/null
+++ b/watchmaker/framework/nb-configuration.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>words</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>0</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.blankLinesAfterClassHeader>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantIfBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantForBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaceBeforeColon>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTryResources>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapTernaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssignOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodCallArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapChainedMethodCalls>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapArrayInit>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapBinaryOps>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeWhileOnNewLine>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>100</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.generateParagraphTagOnBlankLines>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>false</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.otherBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>NEW_LINE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.methodDeclBracePlacement>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>LEAVE_ALONE</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.redundantDoWhileBraces>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>WRAP_IF_LONG</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList>
+ </properties>
+</project-shared-configuration>
diff --git a/watchmaker/framework/pom.xml b/watchmaker/framework/pom.xml
new file mode 100644
index 0000000..51ecd7b
--- /dev/null
+++ b/watchmaker/framework/pom.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--===========================================================================
+ Copyright 2006-2010 Daniel W. Dyer
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ ==========================================================================-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.uncommons.watchmaker</groupId>
+ <artifactId>watchmaker</artifactId>
+ <version>0.7.2</version>
+ </parent>
+ <artifactId>watchmaker-framework</artifactId>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.uncommons.maths</groupId>
+ <artifactId>uncommons-maths</artifactId>
+ <version>1.2.2</version>
+ </dependency>
+ <dependency>
+ <groupId>com.google.collections</groupId>
+ <artifactId>google-collections</artifactId>
+ <version>1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.testng</groupId>
+ <artifactId>testng</artifactId>
+ <version>6.2.1</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <sourceDirectory>src/java/main</sourceDirectory>
+ <testSourceDirectory>src/java/test</testSourceDirectory>
+ </build>
+</project>
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java
new file mode 100644
index 0000000..ed35b70
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/ConfigurableThreadFactory.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.concurrent;
+
+import java.util.concurrent.ThreadFactory;
+import org.uncommons.util.id.IDSource;
+import org.uncommons.util.id.IntSequenceIDSource;
+import org.uncommons.util.id.StringPrefixIDSource;
+
+/**
+ * Thread factory that creates threads for use by a
+ * {@link java.util.concurrent.ThreadPoolExecutor}. The factory can be
+ * configured to customise the names, priority and daemon status of created
+ * threads.
+ * @author Daniel Dyer
+ */
+public class ConfigurableThreadFactory implements ThreadFactory
+{
+ /**
+ * A default exception handler that simply logs the stack trace of the exception.
+ */
+ private static final Thread.UncaughtExceptionHandler DEFAULT_EXCEPTION_HANDLER
+ = new Thread.UncaughtExceptionHandler()
+ {
+ public void uncaughtException(Thread thread, Throwable throwable)
+ {
+ // Log any exceptions thrown.
+ throwable.printStackTrace();
+ }
+ };
+
+
+ private final IDSource<String> nameGenerator;
+ private final int priority;
+ private final boolean daemon;
+ private final Thread.UncaughtExceptionHandler uncaughtExceptionHandler;
+
+ /**
+ * @param namePrefix The String prefix used to assign identifiers to created threads.
+ * @param priority The initial priority for created threads.
+ * @param daemon Whether or not created threads should be daemon threads or user threads.
+ * The JVM exits when the only threads running are all daemon threads.
+ */
+ public ConfigurableThreadFactory(String namePrefix,
+ int priority,
+ boolean daemon)
+ {
+ this(namePrefix, priority, daemon, DEFAULT_EXCEPTION_HANDLER);
+ }
+
+
+ /**
+ * @param namePrefix The String prefix used to assign identifiers to created threads.
+ * @param priority The initial priority for created threads.
+ * @param daemon Whether or not created threads should be daemon threads or user threads.
+ * The JVM exits when the only threads running are all daemon threads.
+ * @param uncaughtExceptionHandler A strategy for dealing with uncaught exceptions.
+ */
+ public ConfigurableThreadFactory(String namePrefix,
+ int priority,
+ boolean daemon,
+ Thread.UncaughtExceptionHandler uncaughtExceptionHandler)
+ {
+ this.nameGenerator = new StringPrefixIDSource(namePrefix + '-', new IntSequenceIDSource());
+ this.priority = priority;
+ this.daemon = daemon;
+ this.uncaughtExceptionHandler = uncaughtExceptionHandler;
+ }
+
+
+ /**
+ * Creates a new thread configured according to this factory's parameters.
+ * @param runnable The runnable to be executed by the new thread.
+ * @return The created thread.
+ */
+ public Thread newThread(Runnable runnable)
+ {
+ Thread thread = new Thread(runnable, nameGenerator.nextID());
+ thread.setPriority(priority);
+ thread.setDaemon(daemon);
+ thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
+ return thread;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java
new file mode 100644
index 0000000..9ff32d1
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/concurrent/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Concurrency utility classes.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.concurrent;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java
new file mode 100644
index 0000000..91635a9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/CompositeIDSource.java
@@ -0,0 +1,64 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * <p>Thread-safe source for partitioned unique IDs. A single instance of this class
+ * represents a single 'partition' in the space of possible IDs. By creating
+ * multiple instances with different discriminators, multiple entities may generate
+ * globally unique IDs independently.</p>
+ * <p>Any given instance of this class may generate a maximum of 2^31 unique values
+ * (the most significant 4 bytes are fixed and the least significant 4 bytes vary
+ * in sequence).</p>
+ * @author Daniel Dyer
+ */
+public final class CompositeIDSource implements IDSource<Long>
+{
+ private final Lock lock = new ReentrantLock();
+ private final long top32bits;
+ private final IDSource<Integer> sequence = new IntSequenceIDSource();
+
+
+ /**
+ * @param topPart The most significant 32 bits to use for the 64-bit IDs generated
+ * by this source. All IDs generated by this source will have the same top 4 bytes.
+ */
+ public CompositeIDSource(int topPart)
+ {
+ top32bits = ((long) topPart) << 32;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Long nextID()
+ {
+ lock.lock();
+ try
+ {
+ // Top part is value provided in constructor, lower 32 bits are from the sequence.
+ return (top32bits + sequence.nextID());
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java
new file mode 100644
index 0000000..ae5d642
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSource.java
@@ -0,0 +1,38 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import java.io.Serializable;
+
+/**
+ * Defines operations for classes that generate unique identifiers. Generated IDs must
+ * be of a {@link java.io.Serializable} type. The strategy used will vary between
+ * implementations. It may be a straightforward sequence or a more complex, less predictable
+ * algorithm.
+ * @param <T> The type of ID returned by this source.
+ * @author Daniel Dyer
+ */
+public interface IDSource<T extends Serializable>
+{
+ /**
+ * Implementing classes are responsible for synchronization if concurrent invocations
+ * of this method are required.
+ * @return The next ID.
+ * @throws IDSourceExhaustedException If this ID source cannot generate any more
+ * unique IDs.
+ */
+ T nextID();
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java
new file mode 100644
index 0000000..fbf7d3f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IDSourceExhaustedException.java
@@ -0,0 +1,33 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+/**
+ * Unchecked exception thrown if an {@link IDSource} runs out of unique IDs.
+ * @author Daniel Dyer
+ */
+public class IDSourceExhaustedException extends RuntimeException
+{
+ public IDSourceExhaustedException(String string)
+ {
+ super(string);
+ }
+
+ public IDSourceExhaustedException(String string, Throwable throwable)
+ {
+ super(string, throwable);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java
new file mode 100644
index 0000000..bf6c3cb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/IntSequenceIDSource.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Thread-safe source for unique IDs. This particular implementation restricts
+ * values to those positive integer values that can be represented by the int data type.
+ * Provides sequenced 32-bit IDs.
+ * @author Daniel Dyer
+ */
+public final class IntSequenceIDSource implements IDSource<Integer>
+{
+ private static final long SECONDS_IN_HOUR = 3600L;
+
+ private final Lock lock = new ReentrantLock();
+ private final long startTime;
+ private int lastID = -1;
+
+
+ /**
+ * @param firstValue The value at which to start the sequence (must
+ * be non-negative).
+ */
+ public IntSequenceIDSource(int firstValue)
+ {
+ if (firstValue < 0)
+ {
+ throw new IllegalArgumentException("Initial value must be non-negative.");
+ }
+ lastID = firstValue - 1;
+ startTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * Creates a sequence that starts at zero.
+ */
+ public IntSequenceIDSource()
+ {
+ this(0);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Integer nextID()
+ {
+ lock.lock();
+ try
+ {
+ if (lastID == Integer.MAX_VALUE)
+ {
+ long hours = (System.currentTimeMillis() - startTime) / SECONDS_IN_HOUR;
+ throw new IDSourceExhaustedException("32-bit ID source exhausted after " + hours + " hours.");
+ }
+ ++lastID;
+ return lastID;
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java
new file mode 100644
index 0000000..3ea85a9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/LongSequenceIDSource.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Thread-safe source for unique IDs. This particular implementation restricts
+ * values to those positive integer values that can be represented by the long data type.
+ * Provides sequenced 64-bit IDs.
+ * @author Daniel Dyer
+ */
+public final class LongSequenceIDSource implements IDSource<Long>
+{
+ private static final int SECONDS_IN_DAY = 86400;
+
+ private final Lock lock = new ReentrantLock();
+ private final long startTime;
+ private long lastID = -1;
+
+
+ /**
+ * @param firstValue The value at which to start the sequence (must
+ * be non-negative).
+ */
+ public LongSequenceIDSource(long firstValue)
+ {
+ if (firstValue < 0)
+ {
+ throw new IllegalArgumentException("Initial value must be non-negative.");
+ }
+ lastID = firstValue - 1;
+ startTime = System.currentTimeMillis();
+ }
+
+
+ /**
+ * Creates a sequence that starts at zero.
+ */
+ public LongSequenceIDSource()
+ {
+ this(0);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public Long nextID()
+ {
+ lock.lock();
+ try
+ {
+ if (lastID == Long.MAX_VALUE)
+ {
+ long days = (System.currentTimeMillis() - startTime) / SECONDS_IN_DAY;
+ throw new IDSourceExhaustedException("64-bit ID source exhausted after " + days + " days.");
+ }
+ ++lastID;
+ return lastID;
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java
new file mode 100644
index 0000000..c4d683c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/StringPrefixIDSource.java
@@ -0,0 +1,60 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Thread-safe ID source that wraps another source of IDs and adds a fixed String
+ * prefix to each ID generated.
+ * @author Daniel Dyer
+ */
+public class StringPrefixIDSource implements IDSource<String>
+{
+ private final Lock lock = new ReentrantLock();
+ private final String prefix;
+ private final IDSource<?> source;
+
+ /**
+ * @param prefix A fixed String that is attached to the front of each ID.
+ * @param source The source of IDs to which the prefix is added.
+ */
+ public StringPrefixIDSource(String prefix, IDSource<?> source)
+ {
+ this.prefix = prefix;
+ this.source = source;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public String nextID()
+ {
+ lock.lock();
+ try
+ {
+ StringBuilder output = new StringBuilder(prefix);
+ output.append(source.nextID());
+ return output.toString();
+ }
+ finally
+ {
+ lock.unlock();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java
new file mode 100644
index 0000000..323b7d3
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/id/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Strategies for providing unique identifiers of different types.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.id;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java
new file mode 100644
index 0000000..e104798
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/ReflectionUtils.java
@@ -0,0 +1,179 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Helper methods to simplify code that uses reflection. These methods handle the
+ * checked exceptions and throw only unchecked exceptions. They are useful for dealing
+ * with reflection when we know that there is no chance of a checked exception. We can
+ * use this class and avoid all of the boiler-plate exception handling.
+ * @author Daniel Dyer
+ */
+public final class ReflectionUtils
+{
+ private ReflectionUtils()
+ {
+ // Prevents instantiation.
+ }
+
+
+ /**
+ * Invokes the specified method without throwing any checked exceptions.
+ * This is only valid for methods that are not declared to throw any checked
+ * exceptions. Any unchecked exceptions thrown by the specified method will be
+ * re-thrown (in their original form, not wrapped in an InvocationTargetException
+ * as would be the case for a normal reflective invocation).
+ * @param method The method to invoke. Both the method and its class must have
+ * been declared public and non-abstract, otherwise they will be inaccessible.
+ * @param target The object on which to invoke the method.
+ * @param arguments The method arguments.
+ * @param <T> The return type of the method. The compiler can usually infer the
+ * correct type.
+ * @return The result of invoking the method, or null if the method is void.
+ */
+ public static <T> T invokeUnchecked(Method method, Object target, Object... arguments)
+ {
+ try
+ {
+ @SuppressWarnings("unchecked")
+ T result = (T) method.invoke(target, arguments);
+ return result;
+ }
+ catch (IllegalAccessException ex)
+ {
+ // This cannot happen if the method is public.
+ throw new IllegalArgumentException("Method " + method.getName() + " is not publicly accessible.", ex);
+ }
+ catch (InvocationTargetException ex)
+ {
+ // If the method is not declared to throw any checked exceptions,
+ // the worst that can happen is a RuntimeException or an Error (we can,
+ // and should, re-throw both).
+ if (ex.getCause() instanceof Error)
+ {
+ throw (Error) ex.getCause();
+ }
+ else
+ {
+ throw (RuntimeException) ex.getCause();
+ }
+ }
+ }
+
+
+ /**
+ * Invokes the specified constructor without throwing any checked exceptions.
+ * This is only valid for constructors that are not declared to throw any checked
+ * exceptions. Any unchecked exceptions thrown by the specified constructor will be
+ * re-thrown (in their original form, not wrapped in an InvocationTargetException
+ * as would be the case for a normal reflective invocation).
+ * @param constructor The constructor to invoke. Both the constructor and its
+ * class must have been declared public, and the class must not be abstract,
+ * otherwise they will be inaccessible.
+ * @param arguments The method arguments.
+ * @param <T> The return type of the method. The compiler can usually infer the
+ * correct type.
+ * @return The object created by invoking the specified constructor with the specified
+ * arguments.
+ */
+ public static <T> T invokeUnchecked(Constructor<T> constructor, Object... arguments)
+ {
+ try
+ {
+ return constructor.newInstance(arguments);
+ }
+ catch (IllegalAccessException ex)
+ {
+ // This cannot happen if the constructor is public.
+ throw new IllegalArgumentException("Constructor is not publicly accessible.", ex);
+ }
+ catch (InstantiationException ex)
+ {
+ // This can only happen if the constructor belongs to an
+ // abstract class.
+ throw new IllegalArgumentException("Constructor is part of an abstract class.", ex);
+ }
+ catch (InvocationTargetException ex)
+ {
+ // If the method is not declared to throw any checked exceptions,
+ // the worst that can happen is a RuntimeException or an Error (we can,
+ // and should, re-throw both).
+ if (ex.getCause() instanceof Error)
+ {
+ throw (Error) ex.getCause();
+ }
+ else
+ {
+ throw (RuntimeException) ex.getCause();
+ }
+ }
+ }
+
+
+ /**
+ * Looks up a method that is explicitly identified. This method should only
+ * be used for methods that definitely exist. It does not throw the checked
+ * NoSuchMethodException. If the method does not exist, it will instead fail
+ * with an unchecked IllegalArgumentException.
+ * @param aClass The class in which the method exists.
+ * @param name The name of the method.
+ * @param paramTypes The types of the method's parameters.
+ * @return The identified method.
+ */
+ public static Method findKnownMethod(Class<?> aClass,
+ String name,
+ Class<?>... paramTypes)
+ {
+ try
+ {
+ return aClass.getMethod(name, paramTypes);
+ }
+ catch (NoSuchMethodException ex)
+ {
+ // This cannot happen if the method is correctly identified.
+ throw new IllegalArgumentException("Method " + name + " does not exist in class " + aClass.getName(), ex);
+ }
+ }
+
+
+ /**
+ * Looks up a constructor that is explicitly identified. This method should only
+ * be used for constructors that definitely exist. It does not throw the checked
+ * NoSuchMethodException. If the constructor does not exist, it will instead fail
+ * with an unchecked IllegalArgumentException.
+ * @param <T> The type of object that the constructor creates.
+ * @param aClass The class in which the constructor exists.
+ * @param paramTypes The types of the constructor's parameters.
+ * @return The identified constructor.
+ */
+ public static <T> Constructor<T> findKnownConstructor(Class<T> aClass,
+ Class<?>... paramTypes)
+ {
+ try
+ {
+ return aClass.getConstructor(paramTypes);
+ }
+ catch (NoSuchMethodException ex)
+ {
+ // This cannot happen if the method is correctly identified.
+ throw new IllegalArgumentException("Specified constructor does not exist in class " + aClass.getName(), ex);
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java
new file mode 100644
index 0000000..253ac44
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/util/reflection/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Utilities for simplifying code that uses reflection.
+ * @author Daniel Dyer
+ */
+package org.uncommons.util.reflection;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
new file mode 100644
index 0000000..486f10d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/AbstractEvolutionEngine.java
@@ -0,0 +1,341 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * Base class for {@link EvolutionEngine} implementations.
+ * @param <T> The type of entity evolved by the evolution engine.
+ * @author Daniel Dyer
+ * @see CandidateFactory
+ * @see FitnessEvaluator
+ */
+public abstract class AbstractEvolutionEngine<T> implements EvolutionEngine<T>
+{
+ // A single multi-threaded worker is shared among multiple evolution engine instances.
+ private static FitnessEvaluationWorker concurrentWorker = null;
+
+ private final Set<EvolutionObserver<? super T>> observers = new CopyOnWriteArraySet<EvolutionObserver<? super T>>();
+
+ private final Random rng;
+ private final CandidateFactory<T> candidateFactory;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+
+ private volatile boolean singleThreaded = false;
+
+ private List<TerminationCondition> satisfiedTerminationConditions;
+
+
+ /**
+ * Creates a new evolution engine by specifying the various components required by
+ * an evolutionary algorithm.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ protected AbstractEvolutionEngine(CandidateFactory<T> candidateFactory,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ Random rng)
+ {
+ this.candidateFactory = candidateFactory;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.rng = rng;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolve(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ seedCandidates,
+ conditions).get(0).getCandidate();
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions)
+ {
+ return evolvePopulation(populationSize,
+ eliteCount,
+ Collections.<T>emptySet(),
+ conditions);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions)
+ {
+ if (eliteCount < 0 || eliteCount >= populationSize)
+ {
+ throw new IllegalArgumentException("Elite count must be non-negative and less than population size.");
+ }
+ if (conditions.length == 0)
+ {
+ throw new IllegalArgumentException("At least one TerminationCondition must be specified.");
+ }
+
+ satisfiedTerminationConditions = null;
+ int currentGenerationIndex = 0;
+ long startTime = System.currentTimeMillis();
+
+ List<T> population = candidateFactory.generateInitialPopulation(populationSize,
+ seedCandidates,
+ rng);
+
+ // Calculate the fitness scores for each member of the initial population.
+ List<EvaluatedCandidate<T>> evaluatedPopulation = evaluatePopulation(population);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ PopulationData<T> data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+
+ List<TerminationCondition> satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ while (satisfiedConditions == null)
+ {
+ ++currentGenerationIndex;
+ evaluatedPopulation = nextEvolutionStep(evaluatedPopulation, eliteCount, rng);
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ data = EvolutionUtils.getPopulationData(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ eliteCount,
+ currentGenerationIndex,
+ startTime);
+ // Notify observers of the state of the population.
+ notifyPopulationChange(data);
+ satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ }
+ this.satisfiedTerminationConditions = satisfiedConditions;
+ return evaluatedPopulation;
+ }
+
+
+ /**
+ * This method performs a single step/iteration of the evolutionary process.
+ * @param evaluatedPopulation The population at the beginning of the process.
+ * @param eliteCount The number of the fittest individuals that must be preserved.
+ * @param rng A source of randomness.
+ * @return The updated population after the evolutionary process has proceeded
+ * by one step/iteration.
+ */
+ protected abstract List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng);
+
+
+ /**
+ * Takes a population, assigns a fitness score to each member and returns
+ * the members with their scores attached, sorted in descending order of
+ * fitness (descending order of fitness score for natural scores, ascending
+ * order of scores for non-natural scores).
+ * @param population The population to evaluate (each candidate is assigned
+ * a fitness score).
+ * @return The evaluated population (a list of candidates with attached fitness
+ * scores).
+ */
+ protected List<EvaluatedCandidate<T>> evaluatePopulation(List<T> population)
+ {
+ List<EvaluatedCandidate<T>> evaluatedPopulation = new ArrayList<EvaluatedCandidate<T>>(population.size());
+
+ if (singleThreaded) // Do fitness evaluations on the request thread.
+ {
+ for (T candidate : population)
+ {
+ evaluatedPopulation.add(new EvaluatedCandidate<T>(candidate,
+ fitnessEvaluator.getFitness(candidate, population)));
+ }
+ }
+ else
+ {
+ // Divide the required number of fitness evaluations equally among the
+ // available processors and coordinate the threads so that we do not
+ // proceed until all threads have finished processing.
+ try
+ {
+ List<T> unmodifiablePopulation = Collections.unmodifiableList(population);
+ List<Future<EvaluatedCandidate<T>>> results = new ArrayList<Future<EvaluatedCandidate<T>>>(population.size());
+ // Submit tasks for execution and wait until all threads have finished fitness evaluations.
+ for (T candidate : population)
+ {
+ results.add(getSharedWorker().submit(new FitnessEvalutationTask<T>(fitnessEvaluator,
+ candidate,
+ unmodifiablePopulation)));
+ }
+ for (Future<EvaluatedCandidate<T>> result : results)
+ {
+ evaluatedPopulation.add(result.get());
+ }
+ }
+ catch (ExecutionException ex)
+ {
+ throw new IllegalStateException("Fitness evaluation task execution failed.", ex);
+ }
+ catch (InterruptedException ex)
+ {
+ // Restore the interrupted status, allows methods further up the call-stack
+ // to abort processing if appropriate.
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ return evaluatedPopulation;
+ }
+
+
+
+ /**
+ * <p>Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the evolution engine. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.</p>
+ *
+ * <p>If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.</p>
+ *
+ * <p>If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.</p>
+ *
+ * @throws IllegalStateException If this method is invoked on an evolution engine before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ public List<TerminationCondition> getSatisfiedTerminationConditions()
+ {
+ if (satisfiedTerminationConditions == null)
+ {
+ throw new IllegalStateException("EvolutionEngine has not terminated.");
+ }
+ else
+ {
+ return Collections.unmodifiableList(satisfiedTerminationConditions);
+ }
+ }
+
+
+ /**
+ * Adds a listener to receive status updates on the evolution progress.
+ * Updates are dispatched synchronously on the request thread. Observers should
+ * complete their processing and return in a timely manner to avoid holding up
+ * the evolution.
+ * @param observer An evolution observer call-back.
+ * @see #removeEvolutionObserver(EvolutionObserver)
+ */
+ public void addEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.add(observer);
+ }
+
+
+ /**
+ * Removes an evolution progress listener.
+ * @param observer An evolution observer call-back.
+ * @see #addEvolutionObserver(EvolutionObserver)
+ */
+ public void removeEvolutionObserver(EvolutionObserver<? super T> observer)
+ {
+ observers.remove(observer);
+ }
+
+
+ /**
+ * Send the population data to all registered observers.
+ * @param data Information about the current state of the population.
+ */
+ private void notifyPopulationChange(PopulationData<T> data)
+ {
+ for (EvolutionObserver<? super T> observer : observers)
+ {
+ observer.populationUpdate(data);
+ }
+ }
+
+
+ /**
+ * By default, fitness evaluations are performed on separate threads (as many as there are
+ * available cores/processors). Use this method to force evaluation to occur synchronously
+ * on the request thread. This is useful in restricted environments where programs are not
+ * permitted to start or control threads. It might also lead to better performance for
+ * programs that have extremely lightweight/trivial fitness evaluations.
+ * @param singleThreaded If true, fitness evaluations will be performed synchronously on the
+ * request thread. If false, fitness evaluations will be performed by worker threads.
+ */
+ public void setSingleThreaded(boolean singleThreaded)
+ {
+ this.singleThreaded = singleThreaded;
+ }
+
+
+ /**
+ * Lazily create the multi-threaded worker for fitness evaluations.
+ */
+ private static synchronized FitnessEvaluationWorker getSharedWorker()
+ {
+ if (concurrentWorker == null)
+ {
+ concurrentWorker = new FitnessEvaluationWorker();
+ }
+ return concurrentWorker;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java
new file mode 100644
index 0000000..ed3d8d2
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CachingFitnessEvaluator.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import com.google.common.collect.MapMaker;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * <p>A wrapper that provides caching for {@link FitnessEvaluator} implementations. The
+ * results of fitness evaluations are stored in a cache so that if the same candidate
+ * is evaluated twice, the expense of the fitness calculation can be avoided the second
+ * time. The cache uses weak references in order to avoid memory leakage.</p>
+ *
+ * <p>Caching of fitness values can be a useful optimisation in situations where the
+ * fitness evaluation is expensive and there is a possibility that some candidates
+ * will survive from generation to generation unmodified. Programs that use elitism
+ * are one example of candidates surviving unmodified. Another scenario is when the
+ * configured evolutionary operator does not always modify every candidate in the
+ * population for every generation.</p>
+ *
+ * <p>Unmodified candidates are identified by reference equality. This is a valid
+ * assumption since evolutionary operators are required to return distinct objects,
+ * except when the candidate is unaffected by the evolution, as per the contract of the
+ * {@link EvolutionaryOperator} interface. In other words, the Watchmaker Framework
+ * treats candidate representations as immutable even when that is not strictly the case.</p>
+ *
+ * <p>Caching of fitness scores is provided as an option rather than as the default
+ * Watchmaker Framework behaviour because caching is only valid when fitness evaluations
+ * are <em>isolated</em> and repeatable. An isolated fitness evaluation is one where the
+ * result depends only upon the candidate being evaluated. This is not the case when
+ * candidates are evaluated against the other members of the population. So unless the
+ * fitness evaluator ignores the second parameter to the
+ * {@link #getFitness(Object, List)} method, caching must not be used.</p>
+ * @param <T> The type of evolvable entity that can be evaluated.
+ *
+ * @author Daniel Dyer
+ */
+public class CachingFitnessEvaluator<T> implements FitnessEvaluator<T>
+{
+ private final FitnessEvaluator<T> delegate;
+
+ // This field is marked as transient, even though the class is not Serializable, because
+ // Terracotta will respect the fact it is transient and not try to share it.
+ private final transient ConcurrentMap<T, Double> cache = new MapMaker().weakKeys().makeMap();
+
+
+ /**
+ * Creates a caching fitness evaluator that wraps the specified evaluator.
+ * @param delegate The fitness evaluator that performs the actual calculations.
+ */
+ public CachingFitnessEvaluator(FitnessEvaluator<T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p>This implementation performs a cache look-up every time it is invoked. If the
+ * fitness evaluator has already calculated the fitness score for the specified
+ * candidate that score is returned without delegating to the wrapped evaluator.</p>
+ */
+ public double getFitness(T candidate, List<? extends T> population)
+ {
+ Double fitness = cache.get(candidate);
+ if (fitness == null)
+ {
+ fitness = delegate.getFitness(candidate, population);
+ cache.put(candidate, fitness);
+ }
+ return fitness;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean isNatural()
+ {
+ return delegate.isNatural();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java
new file mode 100644
index 0000000..e20c552
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/CandidateFactory.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Creates new populations of candidates. For most implementations it
+ * will be easiest just to extend {@link org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory} and
+ * implement the method to generate a single random candidate.
+ * @param <T> The type of evolvable entity created by the factory.
+ * @author Daniel Dyer
+ */
+public interface CandidateFactory<T>
+{
+ /**
+ * Creates an initial population of candidates. If more control is required
+ * over the composition of the initial population, consider the overloaded
+ * {@link #generateInitialPopulation(int,Collection,Random)} method.
+ * @param populationSize The number of candidates to create.
+ * @param rng The random number generator to use when creating the initial
+ * candidates.
+ * @return An initial population of candidate solutions.
+ */
+ List<T> generateInitialPopulation(int populationSize,
+ Random rng);
+
+ /**
+ * Sometimes it is desirable to seed the initial population with some
+ * known good candidates, or partial solutions, in order to provide some
+ * hints for the evolution process. This method generates an initial
+ * population, seeded with some initial candidates. If the number of seed
+ * candidates is less than the required population size, the factory should
+ * generate additional candidates to fill the remaining spaces in the
+ * population.
+ * @param populationSize The size of the initial population.
+ * @param seedCandidates Candidates to seed the population with. Number
+ * of candidates must be no bigger than the population size.
+ * @param rng The random number generator to use when creating additional
+ * candidates to fill the population when the number of seed candidates is
+ * insufficient. This can be null if and only if the number of seed
+ * candidates provided is sufficient to fully populate the initial population.
+ * @return An initial population of candidate solutions, including the
+ * specified seed candidates.
+ */
+ List<T> generateInitialPopulation(int populationSize,
+ Collection<T> seedCandidates,
+ Random rng);
+
+ /**
+ * Randomly create a single candidate solution.
+ * @param rng The random number generator to use when creating the random
+ * candidate.
+ * @return A randomly-initialised candidate.
+ */
+ T generateRandomCandidate(Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java
new file mode 100644
index 0000000..904ebd8
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvaluatedCandidate.java
@@ -0,0 +1,109 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Immutable wrapper class for associating a candidate solution with its
+ * fitness score.
+ * @author Daniel Dyer.
+ * @param <T> The candidate type.
+ */
+public final class EvaluatedCandidate<T> implements Comparable<EvaluatedCandidate<T>>
+{
+ private final T candidate;
+ private final double fitness;
+
+
+ /**
+ * @param candidate The evolved candidate.
+ * @param fitness The candidates fitness score.
+ */
+ public EvaluatedCandidate(T candidate, double fitness)
+ {
+ if (fitness < 0)
+ {
+ throw new IllegalArgumentException("Fitness score must be greater than or equal to zero.");
+ }
+ this.candidate = candidate;
+ this.fitness = fitness;
+ }
+
+
+ /**
+ * @return The evolved candidate solution.
+ */
+ public T getCandidate()
+ {
+ return candidate;
+ }
+
+
+ /**
+ * @return The fitness score for the associated candidate.
+ */
+ public double getFitness()
+ {
+ return fitness;
+ }
+
+
+ /**
+ * Compares this candidate's fitness score with that of the specified
+ * candidate.
+ * @param evaluatedCandidate The candidate to compare scores with.
+ * @return -1, 0 or 1 if this candidate's score is less than, equal to,
+ * or greater than that of the specified candidate. The comparison applies
+ * to the raw numerical score and does not consider whether that score is
+ * a natural fitness score or not.
+ */
+ public int compareTo(EvaluatedCandidate<T> evaluatedCandidate)
+ {
+ return Double.compare(fitness, evaluatedCandidate.getFitness());
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #compareTo(EvaluatedCandidate)}.
+ * @param o The object to check for equality.
+ * @return true If this object is logically equivalent to {code o}.
+ */
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass())
+ {
+ return false;
+ }
+ final EvaluatedCandidate<?> that = (EvaluatedCandidate<?>) o;
+ return Double.compare(that.getFitness(), fitness) == 0;
+ }
+
+
+ /**
+ * Over-ridden to be consistent with {@link #equals(Object)}.
+ * @return This object's hash code.
+ */
+ @Override
+ public int hashCode()
+ {
+ final long temp = fitness == 0.0d ? 0L : Double.doubleToLongBits(fitness);
+ return (int) (temp ^ (temp >>> 32));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java
new file mode 100644
index 0000000..4f53a69
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionEngine.java
@@ -0,0 +1,163 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Operations for classes that provide an evolution implementation.
+ * @param <T> The type of entity evolved by the evolution engine.
+ * @author Daniel Dyer
+ */
+public interface EvolutionEngine<T>
+{
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return the fittest candidate from the final generation. To return the
+ * entire population rather than just the fittest candidate, use the
+ * {@link #evolvePopulation(int, int, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ */
+ T evolve(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return the fittest candidate from the final generation. To return the
+ * entire population rather than just the fittest candidate, use the
+ * {@link #evolvePopulation(int, int, Collection, TerminationCondition[])}
+ * method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param seedCandidates A set of candidates to seed the population with. The size of
+ * this collection must be no greater than the specified population size.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int,int,TerminationCondition[])
+ */
+ T evolve(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return all of the candidates from the final generation. To return just the
+ * fittest candidate rather than the entire population, use the
+ * {@link #evolve(int, int, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ * @see #evolvePopulation(int, int, Collection, TerminationCondition[])
+ */
+ List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Execute the evolutionary algorithm until one of the termination conditions is met,
+ * then return all of the candidates from the final generation. To return just the
+ * fittest candidate rather than the entire population, use the
+ * {@link #evolve(int, int, Collection, TerminationCondition[])} method instead.
+ * @param populationSize The number of candidate solutions present in the population
+ * at any point in time.
+ * @param eliteCount The number of candidates preserved via elitism. In elitism, a
+ * sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param seedCandidates A set of candidates to seed the population with. The size of
+ * this collection must be no greater than the specified population size.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process.
+ * @see #evolve(int, int, Collection, TerminationCondition[])
+ * @see #evolvePopulation(int, int, Collection, TerminationCondition[])
+ */
+ List<EvaluatedCandidate<T>> evolvePopulation(int populationSize,
+ int eliteCount,
+ Collection<T> seedCandidates,
+ TerminationCondition... conditions);
+
+
+ /**
+ * Adds a listener to receive status updates on the evolution progress.
+ * @param observer An evolution observer call-back.
+ * @see #removeEvolutionObserver(EvolutionObserver)
+ */
+ void addEvolutionObserver(EvolutionObserver<? super T> observer);
+
+
+ /**
+ * Removes an evolution progress listener.
+ * @param observer An evolution observer call-back.
+ * @see #addEvolutionObserver(EvolutionObserver)
+ */
+ void removeEvolutionObserver(EvolutionObserver<? super T> observer);
+
+
+ /**
+ * Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the evolution engine. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.
+ *
+ * If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.
+ *
+ * If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.
+ *
+ * @throws IllegalStateException If this method is invoked on an evolution engine before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ List<TerminationCondition> getSatisfiedTerminationConditions();
+} \ No newline at end of file
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java
new file mode 100644
index 0000000..fe3a165
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionObserver.java
@@ -0,0 +1,57 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * <p>Call-back interface so that programs can monitor the state of a
+ * long-running evolutionary algorithm.</p>
+ * <p>Depending on the parameters of the evolutionary program, an observer may
+ * be invoked dozens or hundreds of times a second, especially when the population
+ * size is small as this leads to shorter generations. The processing performed by an
+ * evolution observer should be reasonably short-lived so as to avoid slowing down
+ * the evolution.</p>
+ * <p><strong>Using an EvolutionObserver to update a Swing GUI:</strong>
+ * Evolution updates are dispatched on the request thread. To adhere to
+ * Swing threading rules you must use {@link javax.swing.SwingUtilities#invokeLater(Runnable)}
+ * or {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} to perform any updates to Swing
+ * components.</p>
+ * <p>Be aware that if there are too many Swing updates queued for asynchronous
+ * execution with {@link javax.swing.SwingUtilities#invokeLater(Runnable)}, due to a high
+ * number of generations per second, then the GUI will become sluggish and
+ * unresponsive.
+ * This situation can be mitigated by minimising the amount of work done by
+ * the evolution observer and/or by not updating the GUI every time the observer is
+ * notified.</p>
+ * <p>The unresponsive GUI problem does not occur when using
+ * {@link javax.swing.SwingUtilities#invokeAndWait(Runnable)} because updates are
+ * executed synchronously. The downside is that evolution threads are stalled/idle until
+ * Swing has finished performing the updates. This won't make much difference on a single
+ * core machine but will impact throughput on multi-core machines.</p>
+ * @param <T> The type of entity that exists in the evolving population
+ * that is being observed. This type can be bound to a super-type of the
+ * actual population type so as to allow a non-specific observer that can
+ * be re-used for different population types.
+ * @author Daniel Dyer
+ */
+public interface EvolutionObserver<T>
+{
+ /**
+ * Invoked when the state of the population has changed (typically
+ * at the end of a generation).
+ * @param data Statistics about the state of the current generation.
+ */
+ void populationUpdate(PopulationData<? extends T> data);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java
new file mode 100644
index 0000000..c639b2c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionStrategyEngine.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * <p>General purpose engine for implementing Evolution Strategies. Both (μ+λ) and (μ,λ)
+ * strategies are supported (choose which to use by setting the boolean constructor parameter).</p>
+ *
+ * <p>Though this implementation accepts the {@code eliteCount} argument for each of its evolve
+ * methods in common with other {@link EvolutionEngine} implementations, it has no effect for
+ * evolution strategies. Elitism is implicit in a (μ+λ) ES and undesirable for a (μ,λ) ES.</p>
+
+ * @param <T> The type of entity that is to be evolved.
+ * @see GenerationalEvolutionEngine
+ * @see SteadyStateEvolutionEngine
+ * @author Daniel Dyer
+ */
+public class EvolutionStrategyEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final boolean plusSelection;
+ private final int offspringMultiplier;
+
+
+ /**
+ * Creates a new engine for an evolution strategy.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param plusSelection If true this object implements a (μ+λ) evolution strategy rather
+ * than (μ,λ). With plus-selection the parents are eligible for survival. With
+ * comma-selection only the offspring survive.
+ * @param offspringMultiplier How many offspring to create for each member of the parent
+ * population. This parameter effectively defines a multiplier for μ that gives λ.
+ * We define λ in this indirect way because we don't know the value of μ until
+ * it is passed as an argument to one of the evolve methods.
+ * For a 1+1 ES this parameter would be set to one. For other evolution strategies
+ * a higher value might be better. Eiben & Smith suggest 7 as a good value.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public EvolutionStrategyEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ boolean plusSelection,
+ int offspringMultiplier,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.evolutionScheme = evolutionScheme;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.plusSelection = plusSelection;
+ this.offspringMultiplier = offspringMultiplier;
+ }
+
+
+ /**
+ * This method performs a single step/iteration of the evolutionary process.
+ * @param evaluatedPopulation The population at the beginning of the process.
+ * @param eliteCount Ignored by evolution strategies. Elitism is implicit in a (μ+λ)
+ * ES and undesirable for a (μ,λ) ES.
+ * @param rng A source of randomness.
+ * @return The updated population after the evolution strategy has advanced.
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng)
+ {
+ // Elite count is ignored. If it's non-zero it doesn't really matter, but if assertions are
+ // enabled we will flag it as wrong.
+ assert eliteCount == 0 : "Explicit elitism is not supported for an ES, eliteCount should be 0.";
+
+ // Select candidates that will be operated on to create the offspring.
+ int offspringCount = offspringMultiplier * evaluatedPopulation.size();
+ List<T> parents = new ArrayList<T>(offspringCount);
+ for (int i = 0; i < offspringCount; i++)
+ {
+ parents.add(evaluatedPopulation.get(rng.nextInt(evaluatedPopulation.size())).getCandidate());
+ }
+
+ // Then evolve the parents.
+ List<T> offspring = evolutionScheme.apply(parents, rng);
+
+ List<EvaluatedCandidate<T>> evaluatedOffspring = evaluatePopulation(offspring);
+ if (plusSelection) // Plus-selection means parents are considered for survival as well as offspring.
+ {
+ evaluatedOffspring.addAll(evaluatedPopulation);
+ }
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedOffspring, fitnessEvaluator.isNatural());
+ // Retain the fittest of the candidates that are eligible for survival.
+ return evaluatedOffspring.subList(0, evaluatedPopulation.size());
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java
new file mode 100644
index 0000000..2838e31
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionUtils.java
@@ -0,0 +1,129 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.uncommons.maths.statistics.DataSet;
+
+/**
+ * Utility methods used by different evolution implementations. This class exists to
+ * avoid duplication of this logic among multiple evolution implementations.
+ * @author Daniel Dyer
+ */
+public final class EvolutionUtils
+{
+ private EvolutionUtils()
+ {
+ // Prevents instantiation of utility class.
+ }
+
+
+ /**
+ * Given data about the current population and a set of termination conditions, determines
+ * whether or not the evolution should continue.
+ * @param data The current state of the population.
+ * @param conditions One or more termination conditions. The evolution should not continue if
+ * any of these is satisfied.
+ * @param <T> The type of entity that is being evolved.
+ * @return A list of satisfied termination conditions if the evolution has reached some
+ * pre-specified state, an empty list if the evolution should stop because of a thread
+ * interruption, or null if the evolution should continue.
+ */
+ public static <T> List<TerminationCondition> shouldContinue(PopulationData<T> data,
+ TerminationCondition... conditions)
+ {
+ // If the thread has been interrupted, we should abort and return whatever
+ // result we currently have.
+ if (Thread.currentThread().isInterrupted())
+ {
+ return Collections.emptyList();
+ }
+ // Otherwise check the termination conditions for the evolution.
+ List<TerminationCondition> satisfiedConditions = new LinkedList<TerminationCondition>();
+ for (TerminationCondition condition : conditions)
+ {
+ if (condition.shouldTerminate(data))
+ {
+ satisfiedConditions.add(condition);
+ }
+ }
+ return satisfiedConditions.isEmpty() ? null : satisfiedConditions;
+ }
+
+
+ /**
+ * Sorts an evaluated population in descending order of fitness
+ * (descending order of fitness score for natural scores, ascending
+ * order of scores for non-natural scores).
+ *
+ * @param evaluatedPopulation The population to be sorted (in-place).
+ * @param naturalFitness True if higher fitness scores mean fitter individuals, false otherwise.
+ * @param <T> The type of entity that is being evolved.
+ */
+ public static <T> void sortEvaluatedPopulation(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ boolean naturalFitness)
+ {
+ // Sort candidates in descending order according to fitness.
+ if (naturalFitness) // Descending values for natural fitness.
+ {
+ Collections.sort(evaluatedPopulation, Collections.reverseOrder());
+ }
+ else // Ascending values for non-natural fitness.
+ {
+ Collections.sort(evaluatedPopulation);
+ }
+ }
+
+
+
+ /**
+ * Gets data about the current population, including the fittest candidate
+ * and statistics about the population as a whole.
+ *
+ * @param evaluatedPopulation Population of candidate solutions with their
+ * associated fitness scores.
+ * @param naturalFitness True if higher fitness scores mean fitter individuals, false otherwise.
+ * @param eliteCount The number of candidates preserved via elitism.
+ * @param iterationNumber The zero-based index of the current generation/epoch.
+ * @param startTime The time at which the evolution began, expressed as a number of milliseconds since
+ * 00:00 on 1st January 1970.
+ * @param <T> The type of entity that is being evolved.
+ * @return Statistics about the current generation of evolved individuals.
+ */
+ public static <T> PopulationData<T> getPopulationData(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ boolean naturalFitness,
+ int eliteCount,
+ int iterationNumber,
+ long startTime)
+ {
+ DataSet stats = new DataSet(evaluatedPopulation.size());
+ for (EvaluatedCandidate<T> candidate : evaluatedPopulation)
+ {
+ stats.addValue(candidate.getFitness());
+ }
+ return new PopulationData<T>(evaluatedPopulation.get(0).getCandidate(),
+ evaluatedPopulation.get(0).getFitness(),
+ stats.getArithmeticMean(),
+ stats.getStandardDeviation(),
+ naturalFitness,
+ stats.getSize(),
+ eliteCount,
+ iterationNumber,
+ System.currentTimeMillis() - startTime);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java
new file mode 100644
index 0000000..e733f06
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/EvolutionaryOperator.java
@@ -0,0 +1,63 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * <p>An evolutionary operator is a function that takes a population of
+ * candidates as an argument and returns a new population that is the
+ * result of applying a transformation to the original population.</p>
+ * <p><strong>An implementation of this class must not modify any of
+ * the selected candidate objects passed in.</strong> Doing so will
+ * affect the correct operation of the {@link EvolutionEngine}. Instead
+ * the operator should create and return new candidate objects. The
+ * operator is not required to create copies of unmodified individuals
+ * (for efficiency these may be returned directly).</p>
+ * @param <T> The type of evolvable entity that this operator accepts.
+ * @author Daniel Dyer
+ */
+public interface EvolutionaryOperator<T>
+{
+ /**
+ * <p>Apply the operation to each entry in the list of selected
+ * candidates. It is important to note that this method operates on
+ * the list of candidates returned by the selection strategy and not
+ * on the current population. Each entry in the list (not each
+ * individual - the list may contain the same individual more than
+ * once) must be operated on exactly once.</p>
+ *
+ * <p>Implementing classes should not assume any particular ordering
+ * (or lack of ordering) for the selection. If ordering or
+ * shuffling is required, it should be performed by the implementing
+ * class. The implementation should not re-order the list provided
+ * but instead should make a copy of the list and re-order that.
+ * The ordering of the selection should be totally irrelevant for
+ * operators that process each candidate in isolation, such as mutation.
+ * It should only be an issue for operators, such as cross-over, that
+ * deal with multiple candidates in a single operation.</p>
+ * <p><strong>The operator must not modify any of the candidates passed
+ * in</strong>. Instead it should return a list that contains evolved
+ * copies of those candidates (umodified candidates can be included in
+ * the results without having to be copied).</p>
+ * @param selectedCandidates The individuals to evolve.
+ * @param rng A source of randomness for stochastic operators (most
+ * operators will be stochastic).
+ * @return The evolved individuals.
+ */
+ List<T> apply(List<T> selectedCandidates, Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java
new file mode 100644
index 0000000..9c6b410
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluationWorker.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import org.uncommons.util.concurrent.ConfigurableThreadFactory;
+import org.uncommons.util.id.IDSource;
+import org.uncommons.util.id.IntSequenceIDSource;
+import org.uncommons.util.id.StringPrefixIDSource;
+
+/**
+ * This is the class that actually runs the fitness evaluation tasks created by a
+ * {@link EvolutionEngine}. This responsibility is abstracted away from
+ * the evolution engine to permit the possibility of creating multiple instances
+ * across several machines, all fed by a single shared work queue, using Terracotta
+ * (http://www.terracotta.org) or similar.
+ * @author Daniel Dyer
+ */
+public class FitnessEvaluationWorker
+{
+ // Provide each worker instance with a unique name with which to prefix its threads.
+ private static final IDSource<String> WORKER_ID_SOURCE = new StringPrefixIDSource("FitnessEvaluationWorker",
+ new IntSequenceIDSource());
+
+ /**
+ * Share this field to use Terracotta to distribute fitness evaluations.
+ */
+ private final LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>();
+
+
+ /**
+ * Thread pool that performs concurrent fitness evaluations.
+ */
+ private final ThreadPoolExecutor executor;
+
+
+ /**
+ * Creates a FitnessEvaluationWorker that uses daemon threads.
+ */
+ FitnessEvaluationWorker()
+ {
+ this(true);
+ }
+
+
+ /**
+ * @param daemonWorkerThreads If true, any worker threads created will be daemon threads.
+ */
+ private FitnessEvaluationWorker(boolean daemonWorkerThreads)
+ {
+ ConfigurableThreadFactory threadFactory = new ConfigurableThreadFactory(WORKER_ID_SOURCE.nextID(),
+ Thread.NORM_PRIORITY,
+ daemonWorkerThreads);
+ this.executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),
+ Runtime.getRuntime().availableProcessors(),
+ 60,
+ TimeUnit.SECONDS,
+ workQueue,
+ threadFactory);
+ executor.prestartAllCoreThreads();
+ }
+
+
+ public <T> Future<EvaluatedCandidate<T>> submit(FitnessEvalutationTask<T> task)
+ {
+ return executor.submit(task);
+ }
+
+
+ /**
+ * Entry-point for running this class standalone, as an additional node for fitness evaluations.
+ * If this method is invoked without using Terracotta (or similar) to share the work queue, the
+ * program will do nothing.
+ * @param args Program arguments, should be empty.
+ */
+ public static void main(String[] args)
+ {
+ // The program will not exit immediately upon completion of the main method because
+ // the worker is configured to use non-daemon threads that keep the JVM alive.
+ new FitnessEvaluationWorker(false);
+ }
+
+
+ /**
+ * A FitnessWorker cannot be garbage-collected if its thread pool has not been shutdown.
+ * This method, invoked on garabage collection (or maybe not at all), shuts down the thread
+ * pool so that the threads can be released.
+ * @throws Throwable Any exception or error that occurs during finalisation.
+ */
+ @Override
+ protected void finalize() throws Throwable
+ {
+ executor.shutdown();
+ super.finalize();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java
new file mode 100644
index 0000000..9a7c791
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvaluator.java
@@ -0,0 +1,78 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * Calculates the fitness score of a given candidate of the appropriate type.
+ * Fitness evaluations may be executed concurrently and therefore any access
+ * to mutable shared state should be properly synchronised.
+ * @param <T> The type of evolvable entity that can be evaluated.
+ * @author Daniel Dyer
+ */
+public interface FitnessEvaluator<T>
+{
+ /**
+ * Calculates a fitness score for the given candidate. Whether
+ * a higher score indicates a fitter candidate or not depends on
+ * whether the fitness scores are natural (see {@link #isNatural}).
+ * This method must always return a value greater than or equal to
+ * zero. Framework behaviour is undefined for negative fitness scores.
+ * @param candidate The candidate solution to calculate fitness for.
+ * @param population The entire population. This will include the
+ * specified candidate. This is provided for fitness evaluators that
+ * evaluate individuals in the context of the population that they are
+ * part of (e.g. a program that evolves game-playing strategies may wish
+ * to play each strategy against each of the others). This parameter
+ * can be ignored by simple fitness evaluators. When iterating
+ * over the population, a simple reference equality check (==) can be
+ * used to identify which member of the population is the specified
+ * candidate.
+ * @return The fitness score for the specified candidate. Must always be
+ * a non-negative value regardless of natural or non-natural evaluation is
+ * being used.
+ */
+ double getFitness(T candidate,
+ List<? extends T> population);
+
+ /**
+ * <p>Specifies whether this evaluator generates <i>natural</i> fitness
+ * scores or not.</p>
+ * <p>Natural fitness scores are those in which the fittest
+ * individual in a population has the highest fitness value. In this
+ * case the algorithm is attempting to maximise fitness scores.
+ * There need not be a specified maximum possible value.</p>
+ * <p>In contrast, <i>non-natural</i> fitness evaluation results in fitter
+ * individuals being assigned lower scores than weaker individuals.
+ * In the case of non-natural fitness, the algorithm is attempting to
+ * minimise fitness scores.</p>
+ * <p>An example of a situation in which non-natural fitness scores are
+ * preferable is when the fitness corresponds to a cost and the algorithm
+ * is attempting to minimise that cost.</p>
+ * <p>The terminology of <i>natural</i> and <i>non-natural</i> fitness scores
+ * is introduced by the Watchmaker Framework to describe the two types of fitness
+ * scoring that exist within the framework. It does not correspond to either
+ * <i>standardised fitness</i> or <i>normalised fitness</i> in the EA
+ * literature. Standardised fitness evaluation generates non-natural
+ * scores with a score of zero corresponding to the best possible fitness.
+ * Normalised fitness evaluation is similar to standardised fitness but
+ * with the scores adjusted to fall within the range 0 - 1.</p>
+ * @return True if a high fitness score means a fitter candidate
+ * or false if a low fitness score means a fitter candidate.
+ */
+ boolean isNatural();
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java
new file mode 100644
index 0000000..6f3f827
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/FitnessEvalutationTask.java
@@ -0,0 +1,55 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+
+/**
+ * Callable task for performing parallel fitness evaluations.
+ * @param <T> The type of entity for which fitness is calculated.
+ * @author Daniel Dyer
+ */
+class FitnessEvalutationTask<T> implements Callable<EvaluatedCandidate<T>>
+{
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final T candidate;
+ private final List<T> population;
+
+ /**
+ * Creates a task for performing fitness evaluations.
+ * @param fitnessEvaluator The fitness function used to determine candidate fitness.
+ * @param candidate The candidate to evaluate.
+ * @param population The entire current population. This will include all
+ * of the candidates to evaluate along with any other individuals that are
+ * not being evaluated by this task.
+ */
+ FitnessEvalutationTask(FitnessEvaluator<? super T> fitnessEvaluator,
+ T candidate,
+ List<T> population)
+ {
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.candidate = candidate;
+ this.population = population;
+ }
+
+
+ public EvaluatedCandidate<T> call()
+ {
+ return new EvaluatedCandidate<T>(candidate,
+ fitnessEvaluator.getFitness(candidate, population));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java
new file mode 100644
index 0000000..3e43915
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/GenerationalEvolutionEngine.java
@@ -0,0 +1,132 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.interactive.InteractiveSelection;
+
+/**
+ * <p>This class implements a general-purpose generational evolutionary algorithm.
+ * It supports optional concurrent fitness evaluations to take full advantage of
+ * multi-processor, multi-core and hyper-threaded machines.</p>
+ *
+ * <p>If multi-threading is enabled, evolution (mutation, cross-over, etc.) occurs
+ * on the request thread but fitness evaluations are delegated to a pool of worker
+ * threads. All of the host's available processing units are used (i.e. on a quad-core
+ * machine there will be four fitness evaluation worker threads).</p>
+ *
+ * <p>If multi-threading is disabled, all work is performed synchronously on the
+ * request thread. This strategy is suitable for restricted/managed environments where
+ * it is not permitted for applications to manage their own threads. If there are no
+ * restrictions on concurrency, applications should enable multi-threading for improved
+ * performance.</p>
+ *
+ * @param <T> The type of entity that is to be evolved.
+ * @see SteadyStateEvolutionEngine
+ * @see EvolutionStrategyEngine
+ * @author Daniel Dyer
+ */
+public class GenerationalEvolutionEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final SelectionStrategy<? super T> selectionStrategy;
+
+ /**
+ * Creates a new evolution engine by specifying the various components required by
+ * a generational evolutionary algorithm.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param fitnessEvaluator A function for assigning fitness scores to candidate
+ * solutions.
+ * @param selectionStrategy A strategy for selecting which candidates survive to
+ * be evolved.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public GenerationalEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.evolutionScheme = evolutionScheme;
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.selectionStrategy = selectionStrategy;
+ }
+
+
+ /**
+ * Creates a new evolution engine for an interactive evolutionary algorithm. It
+ * is not necessary to specify a fitness evaluator for interactive evolution.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The combination of evolutionary operators used to evolve
+ * the population at each generation.
+ * @param selectionStrategy Interactive selection strategy configured with appropriate
+ * console.
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public GenerationalEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ InteractiveSelection<T> selectionStrategy,
+ Random rng)
+ {
+ this(candidateFactory,
+ evolutionScheme,
+ new NullFitnessEvaluator(), // No fitness evaluations to perform.
+ selectionStrategy,
+ rng);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng)
+ {
+ List<T> population = new ArrayList<T>(evaluatedPopulation.size());
+
+ // First perform any elitist selection.
+ List<T> elite = new ArrayList<T>(eliteCount);
+ Iterator<EvaluatedCandidate<T>> iterator = evaluatedPopulation.iterator();
+ while (elite.size() < eliteCount)
+ {
+ elite.add(iterator.next().getCandidate());
+ }
+ // Then select candidates that will be operated on to create the evolved
+ // portion of the next generation.
+ population.addAll(selectionStrategy.select(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ evaluatedPopulation.size() - eliteCount,
+ rng));
+ // Then evolve the population.
+ population = evolutionScheme.apply(population, rng);
+ // When the evolution is finished, add the elite to the population.
+ population.addAll(elite);
+ return evaluatePopulation(population);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java
new file mode 100644
index 0000000..0fe0d3a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/NullFitnessEvaluator.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * Fitness evaluation is not required for interactive selection, so this stub
+ * implementation is used to satisfy the framework requirements.
+ * @author Daniel Dyer
+ */
+class NullFitnessEvaluator implements FitnessEvaluator<Object>
+{
+ /**
+ * Returns a score of zero, regardless of the candidate being evaluated.
+ * @param candidate The individual to evaluate.
+ * @param population {@inheritDoc}
+ * @return Zero.
+ */
+ public double getFitness(Object candidate,
+ List<?> population)
+ {
+ return 0;
+ }
+
+ /**
+ * Always returns true. However, the return value of this method is
+ * irrelevant since no meaningful fitness scores are produced.
+ * @return True.
+ */
+ public boolean isNatural()
+ {
+ return true;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java
new file mode 100644
index 0000000..a04f5bb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/PopulationData.java
@@ -0,0 +1,165 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Immutable data object containing statistics about the state of
+ * an evolved population and a reference to the fittest candidate
+ * solution in the population.
+ * @param <T> The type of evolved entity present in the population
+ * that this data describes.
+ * @see EvolutionObserver
+ * @author Daniel Dyer
+ */
+public final class PopulationData<T>
+{
+ private final T bestCandidate;
+ private final double bestCandidateFitness;
+ private final double meanFitness;
+ private final double fitnessStandardDeviation;
+ private final boolean naturalFitness;
+ private final int populationSize;
+ private final int eliteCount;
+ private final int generationNumber;
+ private final long elapsedTime;
+
+ /**
+ * @param bestCandidate The fittest candidate present in the population.
+ * @param bestCandidateFitness The fitness score for the fittest candidate
+ * in the population.
+ * @param meanFitness The arithmetic mean of fitness scores for each member
+ * of the population.
+ * @param fitnessStandardDeviation A measure of the variation in fitness
+ * scores.
+ * @param naturalFitness True if higher fitness scores are better, false
+ * otherwise.
+ * @param populationSize The number of individuals in the population.
+ * @param eliteCount The number of candidates preserved via elitism.
+ * @param generationNumber The (zero-based) number of the last generation
+ * that was processed.
+ * @param elapsedTime The number of milliseconds since the start of the
+ */
+ public PopulationData(T bestCandidate,
+ double bestCandidateFitness,
+ double meanFitness,
+ double fitnessStandardDeviation,
+ boolean naturalFitness,
+ int populationSize,
+ int eliteCount,
+ int generationNumber,
+ long elapsedTime)
+ {
+ this.bestCandidate = bestCandidate;
+ this.bestCandidateFitness = bestCandidateFitness;
+ this.meanFitness = meanFitness;
+ this.fitnessStandardDeviation = fitnessStandardDeviation;
+ this.naturalFitness = naturalFitness;
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.generationNumber = generationNumber;
+ this.elapsedTime = elapsedTime;
+ }
+
+
+ /**
+ * @return The fittest candidate present in the population.
+ * @see #getBestCandidateFitness()
+ */
+ public T getBestCandidate()
+ {
+ return bestCandidate;
+ }
+
+
+ /**
+ * @return The fitness score of the fittest candidate.
+ * @see #getBestCandidateFitness()
+ */
+ public double getBestCandidateFitness()
+ {
+ return bestCandidateFitness;
+ }
+
+
+ /**
+ * Returns the average fitness score of population members.
+ * @return The arithmetic mean fitness of individual candidates.
+ */
+ public double getMeanFitness()
+ {
+ return meanFitness;
+ }
+
+
+ /**
+ * Returns a statistical measure of variation in fitness scores within
+ * the population.
+ * @return Population standard deviation for fitness scores.
+ */
+ public double getFitnessStandardDeviation()
+ {
+ return fitnessStandardDeviation;
+ }
+
+
+ /**
+ * Indicates whether the fitness scores are natural or non-natural.
+ * @return True if higher fitness scores indicate fitter individuals, false
+ * otherwise.
+ */
+ public boolean isNaturalFitness()
+ {
+ return naturalFitness;
+ }
+
+
+ /**
+ * @return The number of individuals in the current population.
+ */
+ public int getPopulationSize()
+ {
+ return populationSize;
+ }
+
+
+ /**
+ * @return The number of candidates preserved via elitism.
+ */
+ public int getEliteCount()
+ {
+ return eliteCount;
+ }
+
+
+ /**
+ * @return The number of this generation (zero-based).
+ */
+ public int getGenerationNumber()
+ {
+ return generationNumber;
+ }
+
+
+ /**
+ * Returns the amount of time (in milliseconds) since the
+ * start of the evolutionary algorithm's execution.
+ * @return How long (in milliseconds) the algorithm has been running.
+ */
+ public long getElapsedTime()
+ {
+ return elapsedTime;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java
new file mode 100644
index 0000000..a91f0fa
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SelectionStrategy.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Strategy interface for "natural" selection.
+ * @param <T> The type of evolved entity that we are selecting.
+ * @author Daniel Dyer
+ */
+public interface SelectionStrategy<T>
+{
+ /**
+ * <p>Select the specified number of candidates from the population.
+ * Implementations may assume that the population is sorted in descending
+ * order according to fitness (so the fittest individual is the first item
+ * in the list).</p>
+ * <p>It is an error to call this method with an empty or null population.</p>
+ * @param <S> The type of evolved entity that we are selecting, a sub-type of T.
+ * @param population The population from which to select.
+ * @param naturalFitnessScores Whether higher fitness values represent fitter
+ * individuals or not.
+ * @param selectionSize The number of individual selections to make (not necessarily
+ * the number of distinct candidates to select, since the same individual may
+ * potentially be selected more than once).
+ * @param rng Source of randomness for stochastic selection strategies.
+ * @return A list containing the selected candidates. Some individual canidates may
+ * potentially have been selected multiple times.
+ */
+ <S extends T> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java
new file mode 100644
index 0000000..3321db8
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngine.java
@@ -0,0 +1,147 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * An implementation of steady-state evolution, which is a type of evolutionary algorithm
+ * where a population is changed incrementally, with one individual evolved at a time. This
+ * differs from {@link GenerationalEvolutionEngine} in which the entire population is evolved in
+ * parallel.
+ *
+ * @param <T> The type of entity that is to be evolved.
+ * @see GenerationalEvolutionEngine
+ * @see EvolutionStrategyEngine
+ * @author Daniel Dyer
+ */
+public class SteadyStateEvolutionEngine<T> extends AbstractEvolutionEngine<T>
+{
+ private final EvolutionaryOperator<T> evolutionScheme;
+ private final FitnessEvaluator<? super T> fitnessEvaluator;
+ private final SelectionStrategy<? super T> selectionStrategy;
+ private final int selectionSize;
+ private final boolean forceSingleCandidateUpdate;
+
+ /**
+ * Create a steady-state evolution strategy in which one or more (usually just one) evolved
+ * offspring replace randomly-chosen individuals.
+ * @param candidateFactory Factory used to create the initial population that is
+ * iteratively evolved.
+ * @param evolutionScheme The evolutionary operator that modifies the population. The
+ * number of candidates used as input is controlled by the {@code selectionSize} parameter.
+ * The number of candidates that will be outputted depends on the implementation. Typically
+ * it will be the same as the input size, but this is not necessary. In fact, for steady-state
+ * evolution, it is typical that the output size is always 1, regardless of the input size, so
+ * that only one member of the population is replaced at a time. To acheive this using cross-over
+ * requires a cross-over implementation that returns only one offspring, rather than the normal
+ * two.
+ * @param fitnessEvaluator The fitness function.
+ * @param selectionStrategy The strategy for selecting which candidate(s) will be
+ * the parent(s) when evolving individuals.
+ * @param selectionSize How many parent candidates are required by the evolution scheme.
+ * This controls how many individuals will be provided to the evolutionary operator at
+ * each iteration. If you are just using mutation, this will typically be 1. For
+ * cross-over, two separate parents are required, so this must be set to 2.
+ * @param forceSingleCandidateUpdate Some evolutionary operators, specifically cross-over
+ * operators, generate more than one evolved individual. A true steady-state algorithm will
+ * only replace one individual at a time. Setting this parameter to true forces the evolution
+ * to discard any additional generated offspring so that for each iteration of the algorithm
+ * there is only one updated individual. This allows cross-over operators that were designed
+ * for generational evolutionary algorithms to be reused for steady-state evolution. A more
+ * efficient, but less straightforward, alternative would be to implement a steady-state-specific
+ * cross-over operator that returns only a single evolved individual. Setting this parameter to
+ * false permits multiple candidates to be replaced per iteration, depending on the specifics of
+ * the evolutionary operator(s).
+ * @param rng The source of randomness used by all stochastic processes (including
+ * evolutionary operators and selection strategies).
+ */
+ public SteadyStateEvolutionEngine(CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ int selectionSize,
+ boolean forceSingleCandidateUpdate,
+ Random rng)
+ {
+ super(candidateFactory, fitnessEvaluator, rng);
+ this.fitnessEvaluator = fitnessEvaluator;
+ this.evolutionScheme = evolutionScheme;
+ this.selectionStrategy = selectionStrategy;
+ this.selectionSize = selectionSize;
+ this.forceSingleCandidateUpdate = forceSingleCandidateUpdate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<EvaluatedCandidate<T>> nextEvolutionStep(List<EvaluatedCandidate<T>> evaluatedPopulation,
+ int eliteCount,
+ Random rng)
+ {
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedPopulation, fitnessEvaluator.isNatural());
+ List<T> selectedCandidates = selectionStrategy.select(evaluatedPopulation,
+ fitnessEvaluator.isNatural(),
+ selectionSize,
+ rng);
+ List<EvaluatedCandidate<T>> offspring = evaluatePopulation(evolutionScheme.apply(selectedCandidates, rng));
+
+ doReplacement(evaluatedPopulation, offspring, eliteCount, rng);
+
+ return evaluatedPopulation;
+ }
+
+
+ /**
+ * Add the offspring to the population, removing the same number of existing individuals to make
+ * space for them.
+ * This method randomly chooses which individuals should be replaced, but it can be over-ridden
+ * in sub-classes if alternative behaviour is required.
+ * @param existingPopulation The full popultation, sorted in descending order of fitness.
+ * @param newCandidates The (unsorted) newly-created individual(s) that should replace existing members
+ * of the population.
+ * @param eliteCount The number of the fittest individuals that should be exempt from being replaced.
+ * @param rng A source of randomness.
+ */
+ protected void doReplacement(List<EvaluatedCandidate<T>> existingPopulation,
+ List<EvaluatedCandidate<T>> newCandidates,
+ int eliteCount,
+ Random rng)
+ {
+ assert newCandidates.size() < existingPopulation.size() - eliteCount : "Too many new candidates for replacement.";
+ // If this is strictly steady-state (only one updated individual per iteration), then we can't keep multiple
+ // evolved individuals, so just pick one at random and use that.
+ if (newCandidates.size() > 1 && forceSingleCandidateUpdate)
+ {
+ // Replace a randomly selected individual, but not one of the "elite" individuals at the
+ // beginning of the sorted population.
+ existingPopulation.set(rng.nextInt(existingPopulation.size() - eliteCount) + eliteCount,
+ newCandidates.get(rng.nextInt(newCandidates.size())));
+ }
+ else
+ {
+ for (EvaluatedCandidate<T> candidate : newCandidates)
+ {
+ // Replace a randomly selected individual, but not one of the "elite" individuals at the
+ // beginning of the sorted population.
+ existingPopulation.set(rng.nextInt(existingPopulation.size() - eliteCount) + eliteCount, candidate);
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java
new file mode 100644
index 0000000..173cc1e
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/TerminationCondition.java
@@ -0,0 +1,32 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+/**
+ * Interface for implementing conditions used to terminate evolutionary algorithms.
+ * @author Daniel Dyer
+ */
+public interface TerminationCondition
+{
+ /**
+ * The condition is queried via this method to determine whether or not evolution
+ * should finish at the current point.
+ * @param populationData Information about the current state of evolution. This may
+ * be used to determine whether evolution should continue or not.
+ * @return true if evolution should be terminated, false otherwise.
+ */
+ boolean shouldTerminate(PopulationData<?> populationData);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java
new file mode 100644
index 0000000..5a03985
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/AbstractCandidateFactory.java
@@ -0,0 +1,76 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+
+/**
+ * Convenient base class for implementations of
+ * {@link org.uncommons.watchmaker.framework.CandidateFactory}.
+ * @param <T> The type of entity evolved by this engine.
+ * @author Daniel Dyer
+ */
+public abstract class AbstractCandidateFactory<T> implements CandidateFactory<T>
+{
+ /**
+ * Randomly, create an initial population of candidates. If some
+ * control is required over the composition of the initial population,
+ * consider the overloaded {@link #generateInitialPopulation(int,Collection,Random)}
+ * method.
+ * @param populationSize The number of candidates to randomly create.
+ * @param rng The random number generator to use when creating the random
+ * candidates.
+ * @return A randomly generated initial population of candidate solutions.
+ */
+ public List<T> generateInitialPopulation(int populationSize, Random rng)
+ {
+ List<T> population = new ArrayList<T>(populationSize);
+ for (int i = 0; i < populationSize; i++)
+ {
+ population.add(generateRandomCandidate(rng));
+ }
+ return Collections.unmodifiableList(population);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * If the number of seed candidates is less than the required population
+ * size, the remainder of the population will be generated randomly via
+ * the {@link #generateRandomCandidate(Random)} method.
+ */
+ public List<T> generateInitialPopulation(int populationSize,
+ Collection<T> seedCandidates,
+ Random rng)
+ {
+ if (seedCandidates.size() > populationSize)
+ {
+ throw new IllegalArgumentException("Too many seed candidates for specified population size.");
+ }
+ List<T> population = new ArrayList<T>(populationSize);
+ population.addAll(seedCandidates);
+ for (int i = seedCandidates.size(); i < populationSize; i++)
+ {
+ population.add(generateRandomCandidate(rng));
+ }
+ return Collections.unmodifiableList(population);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java
new file mode 100644
index 0000000..3dd4fc7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/BitStringFactory.java
@@ -0,0 +1,53 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+
+/**
+ * General purpose candidate factory for generating bit strings for
+ * genetic algorithms.
+ * @see BitString
+ * @author Daniel Dyer
+ */
+public class BitStringFactory extends AbstractCandidateFactory<BitString>
+{
+ private final int length;
+
+
+ /**
+ * @param length The length of all bit strings created by this
+ * factory.
+ */
+ public BitStringFactory(int length)
+ {
+ this.length = length;
+ }
+
+
+ /**
+ * Generates a random bit string, with a uniform distribution of
+ * ones and zeroes.
+ * @param rng The source of randomness for setting the bits.
+ * @return A random bit string of the length configured for this
+ * factory.
+ */
+ public BitString generateRandomCandidate(Random rng)
+ {
+ return new BitString(length, rng);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java
new file mode 100644
index 0000000..406b899
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ListPermutationFactory.java
@@ -0,0 +1,57 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Generates random candidates from a set of elements. Each candidate is a random
+ * permutation of the full set of elements.
+ * @param <T> The component type of the lists created by this factory.
+ * @author Daniel Dyer
+ */
+public class ListPermutationFactory<T> extends AbstractCandidateFactory<List<T>>
+{
+ private final List<T> elements;
+
+ /**
+ * Creates a factory that creates lists that contain each of the specified
+ * elements exactly once. The ordering of those elements within generated
+ * lists is random.
+ * @param elements The elements to permute.
+ */
+ public ListPermutationFactory(List<T> elements)
+ {
+ this.elements = elements;
+ }
+
+
+ /**
+ * Generates a random permutation from the configured elements.
+ * @param rng A source of randomness used to generate the random
+ * permutation.
+ * @return A random permutation.
+ */
+ public List<T> generateRandomCandidate(Random rng)
+ {
+ List<T> candidate = new ArrayList<T>(elements);
+ Collections.shuffle(candidate, rng);
+ return candidate;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java
new file mode 100644
index 0000000..ab62b59
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactory.java
@@ -0,0 +1,58 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Generates random candidates from a set of elements. Each candidate is a random
+ * permutation of the full set of elements.
+ * @author Daniel Dyer
+ * @param <T> The element type of the arrays created.
+ */
+public class ObjectArrayPermutationFactory<T> extends AbstractCandidateFactory<T[]>
+{
+ private final T[] elements;
+
+ /**
+ * Creates a factory that creates arrays that contain each of the specified
+ * elements exactly once. The ordering of those elements within generated
+ * arrays is random.
+ * @param elements The elements to permute.
+ */
+ public ObjectArrayPermutationFactory(T[] elements)
+ {
+ this.elements = elements.clone();
+ }
+
+
+ /**
+ * Generates a random permutation from the configured elements.
+ * @param rng A source of randomness used to generate the random
+ * permutation.
+ * @return A random permutation.
+ */
+ public T[] generateRandomCandidate(Random rng)
+ {
+ T[] candidate = elements.clone();
+ List<T> list = Arrays.asList(candidate);
+ Collections.shuffle(list, rng);
+ return list.toArray(candidate);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java
new file mode 100644
index 0000000..364e9fd
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/StringFactory.java
@@ -0,0 +1,62 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+
+/**
+ * General-purpose candidate factory for EAs that use a fixed-length String encoding.
+ * Generates random strings of a fixed length from a given alphabet.
+ * @author Daniel Dyer
+ */
+public class StringFactory extends AbstractCandidateFactory<String>
+{
+ private final char[] alphabet;
+ private final int stringLength;
+
+ /**
+ * @param alphabet The set of characters that can legally occur within a
+ * string generated by this factory.
+ * @param stringLength The fixed length of all strings generated by this
+ * factory.
+ */
+ public StringFactory(char[] alphabet,
+ int stringLength)
+ {
+ this.alphabet = alphabet.clone();
+ this.stringLength = stringLength;
+ }
+
+
+ /**
+ * Generates a random string of a pre-configured length. Each character
+ * is randomly selected from the pre-configured alphabet. The same
+ * character may appear multiple times and some characters may not appear
+ * at all.
+ * @param rng A source of randomness used to select characters to make up
+ * the string.
+ * @return A randomly generated string.
+ */
+ public String generateRandomCandidate(Random rng)
+ {
+ char[] chars = new char[stringLength];
+ for (int i = 0; i < stringLength; i++)
+ {
+ chars[i] = alphabet[rng.nextInt(alphabet.length)];
+ }
+ return new String(chars);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java
new file mode 100644
index 0000000..7a91d34
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/factories/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Provides convenient general-purpose {@link org.uncommons.watchmaker.framework.CandidateFactory}
+ * implementations for common candidate representations such as strings, lists and arrays.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.factories;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java
new file mode 100644
index 0000000..80c106b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Console.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.List;
+
+/**
+ * A console provides users with a mechanism for interacting with an
+ * evolutionary algorithm.
+ * @param <T> The type of entity that can be presented by this console.
+ * Evolutionary algorithms that evolve a different type can work with
+ * a console via a {@link Renderer} that performs the necessary conversions.
+ * @author Daniel Dyer
+ */
+public interface Console<T>
+{
+ /**
+ * @param renderedEntities A list of the suitably transformed entities
+ * that will be presented to the user for selection.
+ * @return The index of the selected entity.
+ */
+ int select(List<? extends T> renderedEntities);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java
new file mode 100644
index 0000000..36a7883
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/InteractiveSelection.java
@@ -0,0 +1,177 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.util.reflection.ReflectionUtils;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Special selection strategy used for interactive evolutionary algorithms.
+ * @param <T> The type of evolved entity that can be selected by this class.
+ * @author Daniel Dyer
+ */
+public class InteractiveSelection<T> implements SelectionStrategy<T>
+{
+ private final Console<?> console;
+ private final Renderer<T, ?> renderer;
+ private final int groupSize;
+ private final int maxSelectionsPerGeneration;
+
+
+ /**
+ * @param <R> The type of object that can be displayed by the specified
+ * console. The specified renderer must be able to map evolved entities
+ * into objects of this type.
+ * @param console The user interface (graphical, textual or other) used
+ * to present a selection choice to the user.
+ * @param renderer A renderer used to map the evolved entities to objects
+ * that can be processed by the supplied console.
+ * @param groupSize The number of candidates to present to the user at
+ * once (the user selects one from this number).
+ * @param maxSelectionsPerGeneration The maximum number of selections that
+ * the user will be asked to make for each generation of the evolutionary
+ * algorithm. If this number is lower than the required selection size,
+ * the user's selections will be repeated to make up the shortfall. The
+ * purpose of this setting is two-fold. Firstly it minimises user fatigue.
+ * Secondly, it can be used to increase selection pressure. In the extreme
+ * case, a setting of 1 will ensure that members of the subsequent generation
+ * are all descended from a single parent.
+ */
+ public <R> InteractiveSelection(Console<R> console,
+ Renderer<T, R> renderer,
+ int groupSize,
+ int maxSelectionsPerGeneration)
+ {
+ if (groupSize < 2)
+ {
+ throw new IllegalArgumentException("Group size must be at least 2.");
+ }
+ if (maxSelectionsPerGeneration < 1)
+ {
+ throw new IllegalArgumentException("Maximum selections must be 1 or more.");
+ }
+ this.console = console;
+ this.renderer = renderer;
+ this.groupSize = groupSize;
+ this.maxSelectionsPerGeneration = maxSelectionsPerGeneration;
+ }
+
+
+ /**
+ * @param console The user interface (graphical, textual or other) used
+ * to present a selection choice to the user.
+ * @param groupSize The number of candidates to present to the user at
+ * once (the user selects one from this number).
+ * @param maxSelectionsPerGeneration The maximum number of selections that
+ * the user will be asked to make for each generation of the evolutionary
+ * algorithm. If this number is lower than the required selection size,
+ * the user's selections will be repeated to make up the shortfall. The
+ * purpose of this setting is two-fold. Firstly it minimises user fatigue.
+ * Secondly, it can be used to increase selection pressure. In the extreme
+ * case, a setting of 1 will ensure that members of the subsequent generation
+ * are all descended from a single parent.
+ */
+ public InteractiveSelection(Console<T> console,
+ int groupSize,
+ int maxSelectionsPerGeneration)
+ {
+ this(console, new NoOpRenderer<T>(), groupSize, maxSelectionsPerGeneration);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S extends T> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ if (population.size() < groupSize)
+ {
+ throw new IllegalArgumentException("Population is too small for selection group size of " + groupSize);
+ }
+
+ int selectionCount = Math.min(selectionSize, maxSelectionsPerGeneration);
+ List<S> selection = new ArrayList<S>(selectionCount);
+ for (int i = 0; i < selectionCount; i++)
+ {
+ // Pick candidates at random (without replacement).
+ List<S> group = new ArrayList<S>(groupSize);
+ List<EvaluatedCandidate<S>> candidates = new ArrayList<EvaluatedCandidate<S>>(population);
+ Collections.shuffle(candidates);
+ for (int j = 0; j < groupSize; j++)
+ {
+ group.add(candidates.get(j).getCandidate());
+ }
+ // Get the user to pick which one should survive to reproduce.
+ selection.add(select(group));
+ }
+
+ // If the selection is not big enough, extend it by randomly duplicating some
+ // of the selections.
+ if (selectionCount < selectionSize)
+ {
+ List<S> extendedSelection = new ArrayList<S>(selectionSize);
+ extendedSelection.addAll(selection);
+ for (int i = 0; i < selectionSize - selectionCount; i++)
+ {
+ extendedSelection.add(selection.get(selectionCount == 1 ? 0 : rng.nextInt(selectionCount)));
+ }
+ return extendedSelection;
+ }
+ else
+ {
+ return selection;
+ }
+ }
+
+
+ private <S extends T> S select(List<S> candidates)
+ {
+ List<Object> renderedCandidates = new ArrayList<Object>(candidates.size());
+ for (S candidate : candidates)
+ {
+ renderedCandidates.add(renderer.render(candidate));
+ }
+ Method consoleSelectMethod = ReflectionUtils.findKnownMethod(Console.class,
+ "select",
+ List.class);
+ Integer selection = ReflectionUtils.invokeUnchecked(consoleSelectMethod,
+ console,
+ renderedCandidates);
+ return candidates.get(selection);
+ }
+
+
+ /**
+ * Renderer that does nothing. Used when the console already supports the
+ * evolved type.
+ */
+ private static final class NoOpRenderer<T> implements Renderer<T, T>
+ {
+ public T render(T entity)
+ {
+ return entity;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java
new file mode 100644
index 0000000..9360763
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/Renderer.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+/**
+ * Maps objects of one type to objects of a different type. For example,
+ * this class could be used to render dates as Strings or to render arrays
+ * as GUI list components.
+ * @param <T> The input type for the renderer.
+ * @param <S> The output type for the renderer.
+ * @author Daniel Dyer
+ */
+public interface Renderer<T, S>
+{
+ /**
+ * Renders an object of one type as an instance of another. For example,
+ * if the generic types of this renderer are Date and String, this method
+ * would return a String representation of a Date.
+ * @param entity An object to render as a different type.
+ * @return A rendering of the parameter.
+ */
+ S render(T entity);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java
new file mode 100644
index 0000000..b8f669a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/RendererAdapter.java
@@ -0,0 +1,73 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.lang.reflect.Method;
+import org.uncommons.util.reflection.ReflectionUtils;
+
+/**
+ * Adapter class for chaining together two renderers in series to provide
+ * flexibility. For example, if we have a Long -> Date renderer that turns
+ * a number of milliseconds since epoch into a Java date, and a Date -> String
+ * renderer that converts a Java date into its String representation in a
+ * particular locale, we can combine the two to create a Long -> String renderer
+ * without having to write a separate implementation of the {@link Renderer}
+ * interface.
+ * @param <T> The input type for the renderer.
+ * @param <S> The output type for the renderer.
+ * @author Daniel Dyer
+ */
+public class RendererAdapter<T, S> implements Renderer<T, S>
+{
+ private final Renderer<T, ?> renderer1;
+ private final Renderer<?, S> renderer2;
+
+
+ /**
+ * Creates an adapter that feeds the output of renderer1 into renderer2.
+ * @param <R> The intermediate type when transforming objects of type T to
+ * objects of type S.
+ * @param renderer1 A renderer that will translate an object of the input type
+ * (T) into an object of the intermediate type (R).
+ * @param renderer2 A renderer that will translate an object of the intermediate type
+ * (R) into an object of the output type (S).
+ */
+ public <R> RendererAdapter(Renderer<T, ? extends R> renderer1,
+ Renderer<R, S> renderer2)
+ {
+ this.renderer1 = renderer1;
+ this.renderer2 = renderer2;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public S render(T entity)
+ {
+ // This reflection charade is necessary because we can't convince the
+ // compiler that the output of renderer1 is compatible with the input
+ // of renderer2 without exposing a redundant "intermediate" type parameter
+ // in the class definition. I don't what to do that, I'd rather have
+ // the ugliness encapsulated here than complicate code that uses this class.
+ Method renderMethod = ReflectionUtils.findKnownMethod(Renderer.class,
+ "render",
+ Object.class);
+ return ReflectionUtils.<S>invokeUnchecked(renderMethod,
+ renderer2,
+ renderer1.render(entity));
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java
new file mode 100644
index 0000000..516de90
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/interactive/package-info.java
@@ -0,0 +1,21 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Classes for implementing interactive evolutionary algorithms. In interactive
+ * evolutionary algorithms, user-guided selection is used instead of selection based
+ * on automated fitness evaluations.
+ */
+package org.uncommons.watchmaker.framework.interactive;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java
new file mode 100644
index 0000000..d4cd5d1
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Epoch.java
@@ -0,0 +1,53 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import java.util.concurrent.Callable;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.EvolutionEngine;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * @author Daniel Dyer
+ */
+class Epoch<T> implements Callable<List<EvaluatedCandidate<T>>>
+{
+ private final EvolutionEngine<T> island;
+ private final int populationSize;
+ private final int eliteCount;
+ private final List<T> seedCandidates;
+ private final TerminationCondition[] terminationConditions;
+
+ Epoch(EvolutionEngine<T> island,
+ int populationSize,
+ int eliteCount,
+ List<T> seedCandidates,
+ TerminationCondition... terminationConditions)
+ {
+ this.island = island;
+ this.populationSize = populationSize;
+ this.eliteCount = eliteCount;
+ this.seedCandidates = seedCandidates;
+ this.terminationConditions = terminationConditions;
+ }
+
+
+ public List<EvaluatedCandidate<T>> call() throws Exception
+ {
+ return island.evolvePopulation(populationSize, eliteCount, seedCandidates, terminationConditions);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java
new file mode 100644
index 0000000..300dce9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolution.java
@@ -0,0 +1,375 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.EvolutionEngine;
+import org.uncommons.watchmaker.framework.EvolutionObserver;
+import org.uncommons.watchmaker.framework.EvolutionUtils;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+import org.uncommons.watchmaker.framework.GenerationalEvolutionEngine;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * An implementation of island evolution in which multiple independent populations are evolved in
+ * parallel with periodic migration of individuals between islands.
+ * @param <T> The type of entity that is to be evolved.
+ * @author Daniel Dyer
+ */
+public class IslandEvolution<T>
+{
+ private final List<EvolutionEngine<T>> islands;
+ private final Migration migration;
+ private final boolean naturalFitness;
+ private final Random rng;
+
+ private final Set<IslandEvolutionObserver<? super T>> observers
+ = new CopyOnWriteArraySet<IslandEvolutionObserver<? super T>>();
+
+ private List<TerminationCondition> satisfiedTerminationConditions;
+
+
+ /**
+ * Create an island system with the specified number of identically-configured islands.
+ * If you want more fine-grained control over the configuration of each island, use the
+ * {@link #IslandEvolution(List, Migration, boolean, Random)} constructor, which accepts
+ * a list of pre-created islands (each is an instance of {@link EvolutionEngine}).
+ * @param islandCount The number of separate islands that will be part of the system.
+ * @param migration A migration strategy for moving individuals between islands at the
+ * end of an epoch.
+ * @param candidateFactory Generates the initial population for each island.
+ * @param evolutionScheme The evolutionary operator, or combination of evolutionary operators,
+ * used on each island.
+ * @param fitnessEvaluator The fitness function used on each island.
+ * @param selectionStrategy The selection strategy used on each island.
+ * @param rng A source of randomness, used by all islands.
+ * @see #IslandEvolution(List, Migration, boolean, Random)
+ */
+ public IslandEvolution(int islandCount,
+ Migration migration,
+ CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ this(createIslands(islandCount,
+ candidateFactory,
+ evolutionScheme,
+ fitnessEvaluator,
+ selectionStrategy,
+ rng),
+ migration,
+ fitnessEvaluator.isNatural(),
+ rng);
+ }
+
+
+ /**
+ * Create an island evolution system from a list of pre-configured islands. This constructor
+ * gives more control over the configuration of individual islands than the alternative constructor.
+ * The other constructor should be used where possible to avoid having to explicitly create each
+ * island.
+ * @param islands A list of pre-configured islands.
+ * @param migration A migration strategy for moving individuals between islands at the
+ * end of an epoch.
+ * @param naturalFitness If true, indicates that higher fitness values mean fitter
+ * individuals. If false, indicates that fitter individuals will have lower scores.
+ * @param rng A source of randomness, used by all islands.
+ * @see #IslandEvolution(int, Migration, CandidateFactory, EvolutionaryOperator, FitnessEvaluator,
+ * SelectionStrategy, Random)
+ */
+ public IslandEvolution(List<EvolutionEngine<T>> islands,
+ Migration migration,
+ boolean naturalFitness,
+ Random rng)
+ {
+ this.islands = islands;
+ this.migration = migration;
+ this.naturalFitness = naturalFitness;
+ this.rng = rng;
+
+ for (int i = 0; i < islands.size(); i++)
+ {
+ final int islandIndex = i;
+ EvolutionEngine<T> island = islands.get(islandIndex);
+ island.addEvolutionObserver(new EvolutionObserver<T>()
+ {
+ public void populationUpdate(PopulationData<? extends T> populationData)
+ {
+ for (IslandEvolutionObserver<? super T> islandObserver : observers)
+ {
+ islandObserver.islandPopulationUpdate(islandIndex, populationData);
+ }
+ }
+ });
+ }
+ }
+
+
+ /**
+ * Helper method used by the constructor to create the individual islands if they haven't
+ * been provided already (via the other constructor).
+ */
+ private static <T> List<EvolutionEngine<T>> createIslands(int islandCount,
+ CandidateFactory<T> candidateFactory,
+ EvolutionaryOperator<T> evolutionScheme,
+ FitnessEvaluator<? super T> fitnessEvaluator,
+ SelectionStrategy<? super T> selectionStrategy,
+ Random rng)
+ {
+ List<EvolutionEngine<T>> islands = new ArrayList<EvolutionEngine<T>>(islandCount);
+ for (int i = 0; i < islandCount; i++)
+ {
+ GenerationalEvolutionEngine<T> island = new GenerationalEvolutionEngine<T>(candidateFactory,
+ evolutionScheme,
+ fitnessEvaluator,
+ selectionStrategy,
+ rng);
+ island.setSingleThreaded(true); // Don't need fine-grained concurrency when each island is on a separate thread.
+ islands.add(island);
+ }
+ return islands;
+ }
+
+
+ /**
+ * <p>Start the evolutionary process on each island and return the fittest candidate so far at the point
+ * any of the termination conditions is satisfied.</p>
+ *
+ * <p><em>If you interrupt the request thread before this method returns, the
+ * method will return prematurely (with the best individual found so far).
+ * After returning in this way, the current thread's interrupted flag
+ * will be set. It is preferable to use an appropritate
+ * {@link org.uncommons.watchmaker.framework.TerminationCondition} rather than interrupting the evolution in
+ * this way.</em></p>
+ *
+ * @param populationSize The population size <em>for each island</em>. Therefore, if you have 5 islands,
+ * setting this parameter to 200 will result in 1000 individuals overall, 200 on each island.
+ * @param eliteCount The number of candidates preserved via elitism <em>on each island</em>. In elitism,
+ * a sub-set of the population with the best fitness scores are preserved unchanged in
+ * the subsequent generation. Candidate solutions that are preserved unchanged through
+ * elitism remain eligible for selection for breeding the remainder of the next generation.
+ * This value must be non-negative and less than the population size. A value of zero
+ * means that no elitism will be applied.
+ * @param epochLength The number of generations that make up an epoch. Islands evolve independently for
+ * this number of generations and then migration occurs at the end of the epoch and the next epoch starts.
+ * @param migrantCount The number of individuals that will be migrated from each island at the end of each
+ * epoch.
+ * @param conditions One or more conditions that may cause the evolution to terminate.
+ * @return The fittest solution found by the evolutionary process on any of the islands.
+ */
+ public T evolve(int populationSize,
+ int eliteCount,
+ int epochLength,
+ int migrantCount,
+ TerminationCondition... conditions)
+ {
+ ExecutorService threadPool = Executors.newFixedThreadPool(islands.size());
+ List<List<T>> islandPopulations = new ArrayList<List<T>>(islands.size());
+ List<EvaluatedCandidate<T>> evaluatedCombinedPopulation = new ArrayList<EvaluatedCandidate<T>>();
+
+ PopulationData<T> data = null;
+ List<TerminationCondition> satisfiedConditions = null;
+ int currentEpochIndex = 0;
+ long startTime = System.currentTimeMillis();
+ while (satisfiedConditions == null)
+ {
+ List<Callable<List<EvaluatedCandidate<T>>>> islandEpochs = createEpochTasks(populationSize,
+ eliteCount,
+ epochLength,
+ islandPopulations);
+ try
+ {
+ List<Future<List<EvaluatedCandidate<T>>>> futures = threadPool.invokeAll(islandEpochs);
+
+ evaluatedCombinedPopulation.clear();
+ List<List<EvaluatedCandidate<T>>> evaluatedPopulations
+ = new ArrayList<List<EvaluatedCandidate<T>>>(islands.size());
+ for (Future<List<EvaluatedCandidate<T>>> future : futures)
+ {
+ List<EvaluatedCandidate<T>> evaluatedIslandPopulation = future.get();
+ evaluatedCombinedPopulation.addAll(evaluatedIslandPopulation);
+ evaluatedPopulations.add(evaluatedIslandPopulation);
+ }
+
+ migration.migrate(evaluatedPopulations, migrantCount, rng);
+
+ EvolutionUtils.sortEvaluatedPopulation(evaluatedCombinedPopulation, naturalFitness);
+ data = EvolutionUtils.getPopulationData(evaluatedCombinedPopulation,
+ naturalFitness,
+ eliteCount,
+ currentEpochIndex,
+ startTime);
+ notifyPopulationChange(data);
+
+ islandPopulations.clear();
+ for (List<EvaluatedCandidate<T>> evaluatedPopulation : evaluatedPopulations)
+ {
+ islandPopulations.add(toCandidateList(evaluatedPopulation));
+ }
+ ++currentEpochIndex;
+ }
+ catch (InterruptedException ex)
+ {
+ Thread.currentThread().interrupt();
+ }
+ catch (ExecutionException ex)
+ {
+ throw new IllegalStateException(ex);
+ }
+ satisfiedConditions = EvolutionUtils.shouldContinue(data, conditions);
+ }
+ threadPool.shutdownNow();
+
+ this.satisfiedTerminationConditions = satisfiedConditions;
+ return evaluatedCombinedPopulation.get(0).getCandidate();
+ }
+
+
+ /**
+ * Create the concurrently-executed tasks that perform evolution on each island.
+ */
+ private List<Callable<List<EvaluatedCandidate<T>>>> createEpochTasks(int populationSize,
+ int eliteCount,
+ int epochLength,
+ List<List<T>> islandPopulations)
+ {
+ List<Callable<List<EvaluatedCandidate<T>>>> islandEpochs
+ = new ArrayList<Callable<List<EvaluatedCandidate<T>>>>(islands.size());
+ for (int i = 0; i < islands.size(); i++)
+ {
+ islandEpochs.add(new Epoch<T>(islands.get(i),
+ populationSize,
+ eliteCount,
+ islandPopulations.isEmpty() ? Collections.<T>emptyList() : islandPopulations.get(i),
+ new GenerationCount(epochLength)));
+ }
+ return islandEpochs;
+ }
+
+
+ /**
+ * Convert a list of {@link EvaluatedCandidate}s into a simple list of candidates.
+ * @param evaluatedCandidates The population of candidate objects to relieve of their
+ * evaluation wrappers.
+ * @param <T> The type of entity that is being evolved.
+ * @return The candidates, stripped of their fitness scores.
+ */
+ private static <T> List<T> toCandidateList(List<EvaluatedCandidate<T>> evaluatedCandidates)
+ {
+ List<T> candidates = new ArrayList<T>(evaluatedCandidates.size());
+ for (EvaluatedCandidate<T> evaluatedCandidate : evaluatedCandidates)
+ {
+ candidates.add(evaluatedCandidate.getCandidate());
+ }
+ return candidates;
+ }
+
+
+ /**
+ * <p>Returns a list of all {@link TerminationCondition}s that are satisfied by the current
+ * state of the island evolution. Usually this list will contain only one item, but it
+ * is possible that mutliple termination conditions will become satisfied at the same
+ * time. In this case the condition objects in the list will be in the same order that
+ * they were specified when passed to the engine.</p>
+ *
+ * <p>If the evolution has not yet terminated (either because it is still in progress or
+ * because it hasn't even been started) then an IllegalStateException will be thrown.</p>
+ *
+ * <p>If the evolution terminated because the request thread was interrupted before any
+ * termination conditions were satisfied then this method will return an empty list.</p>
+ *
+ * @throws IllegalStateException If this method is invoked on an island system before
+ * evolution is started or while it is still in progress.
+ *
+ * @return A list of statisfied conditions. The list is guaranteed to be non-null. The
+ * list may be empty because it is possible for evolution to terminate without any conditions
+ * being matched. The only situation in which this occurs is when the request thread is
+ * interrupted.
+ */
+ public List<TerminationCondition> getSatisfiedTerminationConditions()
+ {
+ if (satisfiedTerminationConditions == null)
+ {
+ throw new IllegalStateException("EvolutionEngine has not terminated.");
+ }
+ else
+ {
+ return Collections.unmodifiableList(satisfiedTerminationConditions);
+ }
+ }
+
+
+ /**
+ * <p>Adds an observer to the evolution. Observers will receives two types of updates:
+ * updates from each individual island at the end of each generation, and updates for
+ * the combined global population at the end of each epoch.</p>
+ *
+ * <p>Updates are dispatched synchronously on the request thread. Observers should
+ * complete their processing and return in a timely manner to avoid holding up
+ * the evolution.</p>
+ *
+ * @param observer The callback that will be notified at the end of each generation and epoch.
+ *
+ * @see #removeEvolutionObserver(IslandEvolutionObserver)
+ */
+ public void addEvolutionObserver(final IslandEvolutionObserver<? super T> observer)
+ {
+ observers.add(observer);
+ }
+
+
+ /**
+ * Remove the specified observer.
+ * @param observer The observer to remove (if it is registered).
+ *
+ * @see #addEvolutionObserver(IslandEvolutionObserver)
+ */
+ public void removeEvolutionObserver(final IslandEvolutionObserver<? super T> observer)
+ {
+ observers.remove(observer);
+ }
+
+
+ /**
+ * Send the population data to all registered observers.
+ * @param data Information about the current state of the population.
+ */
+ private void notifyPopulationChange(PopulationData<T> data)
+ {
+ for (IslandEvolutionObserver<? super T> observer : observers)
+ {
+ observer.populationUpdate(data);
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java
new file mode 100644
index 0000000..7613ff2
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/IslandEvolutionObserver.java
@@ -0,0 +1,38 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import org.uncommons.watchmaker.framework.EvolutionObserver;
+import org.uncommons.watchmaker.framework.PopulationData;
+
+/**
+ * A specialisation of {@link org.uncommons.watchmaker.framework.EvolutionObserver} that, as well as
+ * receiving global population updates (at the end of each epoch), can receive individual island
+ * population updates (at the end of each generation on each island).
+ * @param <T> The type of entity being evolved.
+ * @author Daniel Dyer
+ */
+public interface IslandEvolutionObserver<T> extends EvolutionObserver<T>
+{
+ /**
+ * Method called to notify the listener of the state of the population of an individual
+ * island. This will be called once for each generation on each island.
+ * @param islandIndex Identifies which individual island the data comes from.
+ * Indices start at zero and are sequential.
+ * @param data The latest data from the evolution on the specified island.
+ */
+ void islandPopulationUpdate(int islandIndex, PopulationData<? extends T> data);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java
new file mode 100644
index 0000000..e9b985f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/Migration.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Strategy interface for different ways of migrating individuals between islands
+ * in {@link IslandEvolution}.
+ * @author Daniel Dyer
+ */
+public interface Migration
+{
+ /**
+ * @param islandPopulations The populations of each island in the system.
+ * @param migrantCount The number of individuals to move from each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of the individual members of the island populations.
+ */
+ <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java
new file mode 100644
index 0000000..13e3bab
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RandomMigration.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Migrates a fixed number of candidates away from each island. Which individuals are migrated is determined
+ * randomly and which islands they move to is also random. This contrasts with the more ordered migration offered
+ * by {@link RingMigration}. If the migration count is greater than one, it is possible (probable) that migrants
+ * from the same island will be moved to different islands. It is also possible that when a migrant's destination is
+ * randomly chosen, it gets sent back to the island that it came from.
+ * @author Daniel Dyer
+ */
+public class RandomMigration implements Migration
+{
+ /**
+ * Migrates a fixed number of candidates away from each island. Which individuals are migrated is determined
+ * randomly and which islands they move to is also random. If the migration count is greater than one, it is
+ * possible (probable) that migrants from the same island will be moved to different islands. It is also possible
+ * that when a migrant's destination is randomly chosen, it gets sent back to the island that it came from.
+ * @param islandPopulations A list of the populations of each island.
+ * @param migrantCount The number of (randomly selected) individuals to be moved on from
+ * each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of entity being evolved.
+ */
+ public <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng)
+ {
+ List<EvaluatedCandidate<T>> migrants = new ArrayList<EvaluatedCandidate<T>>(migrantCount * islandPopulations.size());
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ Collections.shuffle(island, rng);
+ for (int i = 0; i < migrantCount; i++)
+ {
+ migrants.add(island.remove(island.size() - 1));
+ }
+ }
+ Collections.shuffle(migrants);
+ Iterator<EvaluatedCandidate<T>> iterator = migrants.iterator();
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ for (int i = 0; i < migrantCount; i++)
+ {
+ island.add(iterator.next());
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java
new file mode 100644
index 0000000..ca31cf9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/RingMigration.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+
+/**
+ * Migrates a fixed number of individuals from each island to the adjacent island.
+ * Operates as if the islands are arranged in a ring with migration occurring in a
+ * clockwise direction. The individuals to be migrated are chosen completely at random.
+ * @author Daniel Dyer
+ */
+public class RingMigration implements Migration
+{
+ /**
+ * Migrates a fixed number of individuals from each island to the adjacent island.
+ * Operates as if the islands are arranged in a ring with migration occurring in a
+ * clockwise direction. The individuals to be migrated are chosen completely at random.
+ * @param islandPopulations A list of the populations of each island.
+ * @param migrantCount The number of (randomly selected) individuals to be moved on from
+ * each island.
+ * @param rng A source of randomness.
+ * @param <T> The type of entity being evolved.
+ */
+ public <T> void migrate(List<List<EvaluatedCandidate<T>>> islandPopulations, int migrantCount, Random rng)
+ {
+ // The first batch of immigrants is from the last island to the first.
+ List<EvaluatedCandidate<T>> lastIsland = islandPopulations.get(islandPopulations.size() - 1);
+ Collections.shuffle(lastIsland, rng);
+ List<EvaluatedCandidate<T>> migrants = lastIsland.subList(lastIsland.size() - migrantCount, lastIsland.size());
+
+ for (List<EvaluatedCandidate<T>> island : islandPopulations)
+ {
+ // Migrants from the last island are immigrants for this island.
+ List<EvaluatedCandidate<T>> immigrants = migrants;
+ if (island != lastIsland) // We've already migrated individuals from the last island.
+ {
+ // Select the migrants that will move to the next island to make room for the immigrants here.
+ // Randomise the population so that there is no bias concerning which individuals are migrated.
+ Collections.shuffle(island, rng);
+ migrants = new ArrayList<EvaluatedCandidate<T>>(island.subList(island.size() - migrantCount, island.size()));
+ }
+ // Copy the immigrants over the last members of the population (those that are themselves
+ // migrating to the next island).
+ for (int i = 0; i < immigrants.size(); i++)
+ {
+ island.set(island.size() - migrantCount + i, immigrants.get(i));
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java
new file mode 100644
index 0000000..4509026
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/islands/package-info.java
@@ -0,0 +1,22 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * An implementation of island model evolution. Manages parallel evolution across
+ * multiple {@link org.uncommons.watchmaker.framework.EvolutionEngine}s (islands)
+ * with periodic migration between them.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.islands;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java
new file mode 100644
index 0000000..b0a9fba
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/AbstractCrossover.java
@@ -0,0 +1,184 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Generic base class for cross-over implementations. Supports all
+ * cross-over processes that operate on a pair of parent candidates.
+ * @param <T> The type of evolved candidates that are operated on by
+ * this cross-over implementation.
+ * @author Daniel Dyer
+ */
+public abstract class AbstractCrossover<T> implements EvolutionaryOperator<T>
+{
+ private final NumberGenerator<Integer> crossoverPointsVariable;
+ private final NumberGenerator<Probability> crossoverProbabilityVariable;
+
+ /**
+ * Sets up a fixed-point cross-over implementation. Cross-over is
+ * applied to all pairs of parents. To apply cross-over only to a
+ * proportion of parent pairs, use the {@link #AbstractCrossover(int, Probability)}
+ * constructor.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ protected AbstractCrossover(int crossoverPoints)
+ {
+ this(crossoverPoints, Probability.ONE);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a fixed number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbability} parameter.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population. Must be in the range
+ * {@literal 0 < crossoverProbability <= 1}
+ */
+ protected AbstractCrossover(int crossoverPoints,
+ Probability crossoverProbability)
+ {
+ this(new ConstantGenerator<Integer>(crossoverPoints),
+ new ConstantGenerator<Probability>(crossoverProbability));
+ if (crossoverPoints <= 0)
+ {
+ throw new IllegalArgumentException("Number of cross-over points must be positive.");
+ }
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to all pairs of parents. To apply cross-over
+ * only to a proportion of parent pairs, use the
+ * {@link #AbstractCrossover(NumberGenerator, NumberGenerator)} constructor.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ protected AbstractCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ this(crossoverPointsVariable,
+ new ConstantGenerator<Probability>(Probability.ONE));
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ protected AbstractCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ this.crossoverPointsVariable = crossoverPointsVariable;
+ this.crossoverProbabilityVariable = crossoverProbabilityVariable;
+ }
+
+
+ /**
+ * Applies the cross-over operation to the selected candidates. Pairs of
+ * candidates are chosen randomly and subjected to cross-over to produce
+ * a pair of offspring candidates.
+ * @param selectedCandidates The evolved individuals that have survived to
+ * be eligible to reproduce.
+ * @param rng A source of randomness used to determine the location of
+ * cross-over points.
+ * @return The combined set of evolved offspring generated by applying
+ * cross-over to the the selected candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ // Shuffle the collection before applying each operation so that the
+ // evolution is not influenced by any ordering artifacts from previous
+ // operations.
+ List<T> selectionClone = new ArrayList<T>(selectedCandidates);
+ Collections.shuffle(selectionClone, rng);
+
+ List<T> result = new ArrayList<T>(selectedCandidates.size());
+ Iterator<T> iterator = selectionClone.iterator();
+ while (iterator.hasNext())
+ {
+ T parent1 = iterator.next();
+ if (iterator.hasNext())
+ {
+ T parent2 = iterator.next();
+ // Randomly decide (according to the current cross-over probability)
+ // whether to perform cross-over for these 2 parents.
+ int crossoverPoints = crossoverProbabilityVariable.nextValue().nextEvent(rng)
+ ? crossoverPointsVariable.nextValue()
+ : 0;
+ if (crossoverPoints > 0)
+ {
+ result.addAll(mate(parent1, parent2, crossoverPoints, rng));
+ }
+ else
+ {
+ // If there is no cross-over to perform, just add the parents to the
+ // results unaltered.
+ result.add(parent1);
+ result.add(parent2);
+ }
+ }
+ else
+ {
+ // If we have an odd number of selected candidates, we can't pair up
+ // the last one so just leave it unmodified.
+ result.add(parent1);
+ }
+ }
+ return result;
+ }
+
+
+ /**
+ * Perform cross-over on a pair of parents to generate a pair of offspring.
+ * @param parent1 One of two individuals that provides the source material
+ * for generating offspring.
+ * @param parent2 One of two individuals that provides the source material
+ * for generating offspring.
+ * @param numberOfCrossoverPoints The number of cross-overs performed on the
+ * two parents.
+ * @param rng A source of randomness used to determine the location of
+ * cross-over points.
+ * @return A list containing two evolved offspring.
+ */
+ protected abstract List<T> mate(T parent1,
+ T parent2,
+ int numberOfCrossoverPoints,
+ Random rng);
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java
new file mode 100644
index 0000000..4f9bdd3
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringCrossover.java
@@ -0,0 +1,128 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * bit strings.
+ * @see BitString
+ * @author Daniel Dyer
+ */
+public class BitStringCrossover extends AbstractCrossover<BitString>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public BitStringCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public BitStringCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public BitStringCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public BitStringCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public BitStringCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<BitString> mate(BitString parent1,
+ BitString parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.getLength() != parent2.getLength())
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ BitString offspring1 = parent1.clone();
+ BitString offspring2 = parent2.clone();
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.getLength() - 1));
+ offspring1.swapSubstring(offspring2, 0, crossoverIndex);
+ }
+ List<BitString> result = new ArrayList<BitString>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java
new file mode 100644
index 0000000..fe849f9
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/BitStringMutation.java
@@ -0,0 +1,100 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation of individual bits in a {@link BitString} according to some
+ * probability.
+ * @see org.uncommons.maths.binary.BitString
+ * @author Daniel Dyer
+ */
+public class BitStringMutation implements EvolutionaryOperator<BitString>
+{
+ private final NumberGenerator<Probability> mutationProbability;
+ private final NumberGenerator<Integer> mutationCount;
+
+
+ /**
+ * Creates a mutation operator for bit strings with the specified probability that a given
+ * bit string will be mutated, with exactly one bit being flipped.
+ * @param mutationProbability The probability of a candidate being mutated.
+ */
+ public BitStringMutation(Probability mutationProbability)
+ {
+ this(new ConstantGenerator<Probability>(mutationProbability),
+ new ConstantGenerator<Integer>(1));
+ }
+
+
+ /**
+ * Creates a mutation operator for bit strings, with the probability that any
+ * given bit will be flipped governed by the specified number generator.
+ * @param mutationProbability The (possibly variable) probability of a candidate
+ * bit string being mutated at all.
+ * @param mutationCount The (possibly variable) number of bits that will be flipped
+ * on any candidate bit string that is selected for mutation.
+ */
+ public BitStringMutation(NumberGenerator<Probability> mutationProbability,
+ NumberGenerator<Integer> mutationCount)
+ {
+ this.mutationProbability = mutationProbability;
+ this.mutationCount = mutationCount;
+ }
+
+
+ public List<BitString> apply(List<BitString> selectedCandidates, Random rng)
+ {
+ List<BitString> mutatedPopulation = new ArrayList<BitString>(selectedCandidates.size());
+ for (BitString b : selectedCandidates)
+ {
+ mutatedPopulation.add(mutateBitString(b, rng));
+ }
+ return mutatedPopulation;
+ }
+
+
+ /**
+ * Mutate a single bit string. Zero or more bits may be flipped. The
+ * probability of any given bit being flipped is governed by the probability
+ * generator configured for this mutation operator.
+ * @param bitString The bit string to mutate.
+ * @param rng A source of randomness.
+ * @return The mutated bit string.
+ */
+ private BitString mutateBitString(BitString bitString, Random rng)
+ {
+ if (mutationProbability.nextValue().nextEvent(rng))
+ {
+ BitString mutatedBitString = bitString.clone();
+ int mutations = mutationCount.nextValue();
+ for (int i = 0; i < mutations; i++)
+ {
+ mutatedBitString.flipBit(rng.nextInt(mutatedBitString.getLength()));
+ }
+ return mutatedBitString;
+ }
+ return bitString;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java
new file mode 100644
index 0000000..c80647b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ByteArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive bytes.
+ * @author Daniel Dyer
+ */
+public class ByteArrayCrossover extends AbstractCrossover<byte[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ByteArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ByteArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ByteArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ByteArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public ByteArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<byte[]> mate(byte[] parent1,
+ byte[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ byte[] offspring1 = new byte[parent1.length];
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ byte[] offspring2 = new byte[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ byte[] temp = new byte[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<byte[]> result = new ArrayList<byte[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java
new file mode 100644
index 0000000..8e7dc66
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/CharArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive chars.
+ * @author Daniel Dyer
+ */
+public class CharArrayCrossover extends AbstractCrossover<char[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public CharArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public CharArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public CharArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public CharArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public CharArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<char[]> mate(char[] parent1,
+ char[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ char[] offspring1 = new char[parent1.length];
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ char[] offspring2 = new char[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ char[] temp = new char[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<char[]> result = new ArrayList<char[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java
new file mode 100644
index 0000000..459e6a7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive doubles.
+ * @author Daniel Dyer
+ */
+public class DoubleArrayCrossover extends AbstractCrossover<double[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public DoubleArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public DoubleArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public DoubleArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public DoubleArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public DoubleArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<double[]> mate(double[] parent1,
+ double[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ double[] offspring1 = new double[parent1.length];
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ double[] offspring2 = new double[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ double[] temp = new double[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<double[]> result = new ArrayList<double[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java
new file mode 100644
index 0000000..eafff64
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/EvolutionPipeline.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>A compound evolutionary operator that applies multiple operators (of the
+ * same type) in series.</p>
+ *
+ * <p>By combining EvolutionPipeline operators with {@link SplitEvolution} operators,
+ * elaborate evolutionary schemes can be constructed.</p>
+ *
+ * @param <T> The type of evolved candidate that this pipeline operates on.
+ * @author Daniel Dyer
+ */
+public class EvolutionPipeline<T> implements EvolutionaryOperator<T>
+{
+ private final List<EvolutionaryOperator<T>> pipeline;
+
+
+ /**
+ * Creates a pipeline consisting of the specified operators in
+ * the order that they are supplied.
+ * @param pipeline An ordered list of operators that make up the
+ * pipeline.
+ */
+ public EvolutionPipeline(List<EvolutionaryOperator<T>> pipeline)
+ {
+ if (pipeline.isEmpty())
+ {
+ throw new IllegalArgumentException("Pipeline must contain at least one operator.");
+ }
+ this.pipeline = new ArrayList<EvolutionaryOperator<T>>(pipeline);
+ }
+
+
+ /**
+ * Applies each operation in the pipeline in turn to the selection.
+ * @param selectedCandidates The candidates to subjected to evolution.
+ * @param rng A source of randomness used by all stochastic processes in
+ * the pipeline.
+ * @return A list of evolved candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ List<T> population = selectedCandidates;
+ for (EvolutionaryOperator<T> operator : pipeline)
+ {
+ population = operator.apply(population, rng);
+ }
+ return population;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java
new file mode 100644
index 0000000..ff31177
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IdentityOperator.java
@@ -0,0 +1,44 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Evolutionary operator that simply returns the selected candidates unaltered.
+ * This can be useful when combined with {@link SplitEvolution} so that a
+ * proportion of the selected candidates can be copied unaltered into the next
+ * generation while the remainder are evolved.
+ * @param <T> The type of evolvable entity that this operator is used with.
+ * @author Daniel Dyer
+ */
+public class IdentityOperator<T> implements EvolutionaryOperator<T>
+{
+ /**
+ * Returns the selected candidates unaltered.
+ * @param selectedCandidates The candidates to "evolve" (or do
+ * nothing to in this case).
+ * @param rng A source of randomness (not used).
+ * @return The unaltered candidates.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ return new ArrayList<T>(selectedCandidates);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java
new file mode 100644
index 0000000..f416ecd
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/IntArrayCrossover.java
@@ -0,0 +1,131 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of primitive ints.
+ * @author Daniel Dyer
+ */
+public class IntArrayCrossover extends AbstractCrossover<int[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public IntArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public IntArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public IntArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public IntArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public IntArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<int[]> mate(int[] parent1,
+ int[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ int[] offspring1 = new int[parent1.length];
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ int[] offspring2 = new int[parent2.length];
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ int[] temp = new int[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<int[]> result = new ArrayList<int[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java
new file mode 100644
index 0000000..698200d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListCrossover.java
@@ -0,0 +1,129 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Variable-point (fixed or random) cross-over for arbitrary lists.
+ * @param <T> The component type of the lists that are combined.
+ * @author Daniel Dyer
+ */
+public class ListCrossover<T> extends AbstractCrossover<List<T>>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ListCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ListCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ListCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<List<T>> mate(List<T> parent1,
+ List<T> parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ List<T> offspring1 = new ArrayList<T>(parent1); // Use a random-access list for performance.
+ List<T> offspring2 = new ArrayList<T>(parent2);
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int max = Math.min(parent1.size(), parent2.size());
+ if (max > 1) // Don't perform cross-over if there aren't at least 2 elements in each list.
+ {
+ int crossoverIndex = (1 + rng.nextInt(max - 1));
+ for (int j = 0; j < crossoverIndex; j++)
+ {
+ T temp = offspring1.get(j);
+ offspring1.set(j, offspring2.get(j));
+ offspring2.set(j, temp);
+ }
+ }
+ }
+ List<List<T>> result = new ArrayList<List<T>>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java
new file mode 100644
index 0000000..09e1602
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListInversion.java
@@ -0,0 +1,87 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * An evolutionary operator that randomly reverses a subsection of a list.
+ * @author Daniel Dyer
+ * @param <T> The type of entity being evolved.
+ */
+public class ListInversion<T> implements EvolutionaryOperator<List<T>>
+{
+ private final NumberGenerator<Probability> inversionProbability;
+
+
+ /**
+ * @param inversionProbability The probability that an individual list will have some
+ * subsection inverted.
+ */
+ public ListInversion(Probability inversionProbability)
+ {
+ this(new ConstantGenerator<Probability>(inversionProbability));
+ }
+
+
+ /**
+ * @param inversionProbability A variable that controls the probability that an
+ * individual list will have some subsection inverted.
+ */
+ public ListInversion(NumberGenerator<Probability> inversionProbability)
+ {
+ this.inversionProbability = inversionProbability;
+ }
+
+
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> result = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> candidate : selectedCandidates)
+ {
+ if (inversionProbability.nextValue().nextEvent(rng))
+ {
+ List<T> newCandidate = new ArrayList<T>(candidate);
+ int length = newCandidate.size();
+ int start = rng.nextInt(length);
+ int offset = 2 + rng.nextInt(length - 2); // Make sure segment length is at least 2.
+ int end = (start + offset) % length;
+ int segmentLength = end - start;
+ if (segmentLength < 0)
+ {
+ segmentLength += length;
+ }
+ for (int i = 0; i < segmentLength / 2; i++)
+ {
+ Collections.swap(newCandidate, (start + i) % length, (end - i + length) % length);
+ }
+ result.add(newCandidate);
+ }
+ else
+ {
+ result.add(candidate);
+ }
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java
new file mode 100644
index 0000000..cb75511
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOperator.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>A higher-order evolutionary operator that is applied to populations made
+ * up of lists. In such populations, each candidate solution is itself a list
+ * and this operator is applied to the list contents rather than the candidate.
+ * It is analogous to the map function in functional programming languages.</p>
+ *
+ * <p>For example, if the evolved population consists of candidates that are
+ * lists of strings, we could use a ListOperator to wrap an operator of type
+ * String and convert it to an operator that works with lists of Strings.</p>
+ *
+ * @param <T> The element type of the lists to be mutated.
+ * @author Daniel Dyer
+ */
+public class ListOperator <T> implements EvolutionaryOperator<List<T>>
+{
+ private final EvolutionaryOperator<T> delegate;
+
+ /**
+ * @param delegate The evolutionary operator that will be applied to each
+ * list candidate.
+ */
+ public ListOperator(EvolutionaryOperator<T> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * Applies the configured operator to each list candidate, operating on the elements
+ * that make up a candidate rather than on the list of candidates.
+ * candidates and returns the results.
+ * @param selectedCandidates A list of list candidates.
+ * @param rng A source of randomness.
+ * @return The result of applying the configured operator to each element
+ * in each list candidates.
+ */
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> output = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> item : selectedCandidates)
+ {
+ output.add(delegate.apply(item, rng));
+ }
+ return output;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java
new file mode 100644
index 0000000..5748e79
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderCrossover.java
@@ -0,0 +1,164 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Implements ordered cross-over between arbitrary lists. The algorithm is
+ * the Partially Mapped Cross-over (PMX) algorithm.
+ * @param <T> The component type of the lists that are combined.
+ * @author Daniel Dyer
+ */
+public class ListOrderCrossover<T> extends AbstractCrossover<List<T>>
+{
+ /**
+ * Creates a cross-over operator with a cross-over probability of 1.
+ */
+ public ListOrderCrossover()
+ {
+ this(Probability.ONE);
+ }
+
+
+ /**
+ * Creates a cross-over operator with the specified cross-over probability.
+ * @param crossoverProbability The probability that cross-over will be performed
+ * for any given pair.
+ */
+ public ListOrderCrossover(Probability crossoverProbability)
+ {
+ super(2, // Requires exactly two cross-over points.
+ crossoverProbability);
+ }
+
+
+ /**
+ * Creates a cross-over operator where cross-over may or may not be applied to a
+ * given pair of parents depending on the {@code crossoverProbability}.
+ * @param crossoverProbabilityVariable The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ListOrderCrossover(NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(new ConstantGenerator<Integer>(2), // Requires exactly two cross-over points.
+ crossoverProbabilityVariable);
+ }
+
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<List<T>> mate(List<T> parent1,
+ List<T> parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ assert numberOfCrossoverPoints == 2 : "Expected number of cross-over points to be 2.";
+
+ if (parent1.size() != parent2.size())
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+
+ List<T> offspring1 = new ArrayList<T>(parent1); // Use a random-access list for performance.
+ List<T> offspring2 = new ArrayList<T>(parent2);
+
+ int point1 = rng.nextInt(parent1.size());
+ int point2 = rng.nextInt(parent1.size());
+
+ int length = point2 - point1;
+ if (length < 0)
+ {
+ length += parent1.size();
+ }
+
+ Map<T, T> mapping1 = new HashMap<T, T>(length * 2); // Big enough map to avoid re-hashing.
+ Map<T, T> mapping2 = new HashMap<T, T>(length * 2);
+ for (int i = 0; i < length; i++)
+ {
+ int index = (i + point1) % parent1.size();
+ T item1 = offspring1.get(index);
+ T item2 = offspring2.get(index);
+ offspring1.set(index, item2);
+ offspring2.set(index, item1);
+ mapping1.put(item1, item2);
+ mapping2.put(item2, item1);
+ }
+
+ checkUnmappedElements(offspring1, mapping2, point1, point2);
+ checkUnmappedElements(offspring2, mapping1, point1, point2);
+
+ List<List<T>> result = new ArrayList<List<T>>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+
+
+ /**
+ * Checks elements that are outside of the partially mapped section to
+ * see if there are any duplicate items in the list. If there are, they
+ * are mapped appropriately.
+ */
+ private void checkUnmappedElements(List<T> offspring,
+ Map<T, T> mapping,
+ int mappingStart,
+ int mappingEnd)
+ {
+ for (int i = 0; i < offspring.size(); i++)
+ {
+ if (!isInsideMappedRegion(i, mappingStart, mappingEnd))
+ {
+ T mapped = offspring.get(i);
+ while (mapping.containsKey(mapped))
+ {
+ mapped = mapping.get(mapped);
+ }
+ offspring.set(i, mapped);
+ }
+ }
+ }
+
+
+ /**
+ * Checks whether a given list position is within the partially mapped
+ * region used for cross-over.
+ * @param position The list position to check.
+ * @param startPoint The starting index (inclusive) of the mapped region.
+ * @param endPoint The end index (exclusive) of the mapped region.
+ * @return True if the specified position is in the mapped region, false
+ * otherwise.
+ */
+ private boolean isInsideMappedRegion(int position,
+ int startPoint,
+ int endPoint)
+ {
+ boolean enclosed = (position < endPoint && position >= startPoint);
+ boolean wrapAround = (startPoint > endPoint && (position >= startPoint || position < endPoint));
+ return enclosed || wrapAround;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java
new file mode 100644
index 0000000..a218510
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ListOrderMutation.java
@@ -0,0 +1,105 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * A special mutation implementation that instead of changing the
+ * genes of the candidate, re-orders them. A single mutation involves
+ * swapping a random element in the list with the element immediately
+ * after it. This operation can either apply a fixed number of
+ * mutations to each candidate or it can draw values from a random
+ * sequence, typically a poisson distribution (see
+ * {@link org.uncommons.maths.random.PoissonGenerator}), to determine how
+ * many mutations to apply.
+ * @param <T> The component type of the lists that are mutated.
+ * @author Daniel Dyer
+ */
+public class ListOrderMutation<T> implements EvolutionaryOperator<List<T>>
+{
+ private final NumberGenerator<Integer> mutationCountVariable;
+ private final NumberGenerator<Integer> mutationAmountVariable;
+
+ /**
+ * Default is one mutation per candidate.
+ */
+ public ListOrderMutation()
+ {
+ this(1, 1);
+ }
+
+ /**
+ * @param mutationCount The constant number of mutations
+ * to apply to each individual in the population.
+ * @param mutationAmount The constant number of positions by
+ * which a list element will be displaced as a result of mutation.
+ */
+ public ListOrderMutation(int mutationCount, int mutationAmount)
+ {
+ this(new ConstantGenerator<Integer>(mutationCount),
+ new ConstantGenerator<Integer>(mutationAmount));
+ }
+
+
+ /**
+ * Typically the mutation count will be from a Poisson distribution.
+ * The mutation amount can be from any discrete probability distribution
+ * and can include negative values.
+ * @param mutationCount A random variable that provides a number
+ * of mutations that will be applied to each individual.
+ * @param mutationAmount A random variable that provides a number
+ * of positions by which to displace an element when mutating.
+ */
+ public ListOrderMutation(NumberGenerator<Integer> mutationCount,
+ NumberGenerator<Integer> mutationAmount)
+ {
+ this.mutationCountVariable = mutationCount;
+ this.mutationAmountVariable = mutationAmount;
+ }
+
+
+ public List<List<T>> apply(List<List<T>> selectedCandidates, Random rng)
+ {
+ List<List<T>> result = new ArrayList<List<T>>(selectedCandidates.size());
+ for (List<T> candidate : selectedCandidates)
+ {
+ List<T> newCandidate = new ArrayList<T>(candidate);
+ int mutationCount = Math.abs(mutationCountVariable.nextValue());
+ for (int i = 0; i < mutationCount; i++)
+ {
+ int fromIndex = rng.nextInt(newCandidate.size());
+ int mutationAmount = mutationAmountVariable.nextValue();
+ int toIndex = (fromIndex + mutationAmount) % newCandidate.size();
+ if (toIndex < 0)
+ {
+ toIndex += newCandidate.size();
+ }
+ // Swap the randomly selected element with the one that is the
+ // specified displacement distance away.
+ Collections.swap(newCandidate, fromIndex, toIndex);
+ }
+ result.add(newCandidate);
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java
new file mode 100644
index 0000000..176bfaa
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossover.java
@@ -0,0 +1,136 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Cross-over with a configurable number of points (fixed or random) for
+ * arrays of reference types.
+ * @param <T> The component type of the arrays that are being evolved.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayCrossover<T> extends AbstractCrossover<T[]>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public ObjectArrayCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public ObjectArrayCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public ObjectArrayCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public ObjectArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public ObjectArrayCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<T[]> mate(T[] parent1,
+ T[] parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length != parent2.length)
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ // Create the most specific-type arrays possible.
+ @SuppressWarnings("unchecked")
+ T[] offspring1 = (T[]) Array.newInstance(parent1.getClass().getComponentType(), parent1.length);
+ System.arraycopy(parent1, 0, offspring1, 0, parent1.length);
+ @SuppressWarnings("unchecked")
+ T[] offspring2 = (T[]) Array.newInstance(parent2.getClass().getComponentType(), parent2.length);
+ System.arraycopy(parent2, 0, offspring2, 0, parent2.length);
+ // Apply as many cross-overs as required.
+ Object[] temp = new Object[parent1.length];
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length - 1));
+ System.arraycopy(offspring1, 0, temp, 0, crossoverIndex);
+ System.arraycopy(offspring2, 0, offspring1, 0, crossoverIndex);
+ System.arraycopy(temp, 0, offspring2, 0, crossoverIndex);
+ }
+ List<T[]> result = new ArrayList<T[]>(2);
+ result.add(offspring1);
+ result.add(offspring2);
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java
new file mode 100644
index 0000000..94b8b5f
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/Replacement.java
@@ -0,0 +1,96 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * An evolutionary operator that replaces individuals with randomly-generated
+ * new individuals, according to some specified probability. The new individuals
+ * are not derived from the selected individuals, they are completely random. This
+ * operator provides a way to prevent stagnation by occassionally introducing
+ * new genetic material into the population.
+ * @param <T> The type of evolvable entity that this operator applies to.
+ * @author Daniel Dyer
+ */
+public class Replacement<T> implements EvolutionaryOperator<T>
+{
+ private final CandidateFactory<T> factory;
+ private final NumberGenerator<Probability> replacementProbability;
+
+
+ /**
+ * Creates a replacement operator that replaces individuals according to
+ * the specified probability. New individuals are obtained from the factory
+ * provided.
+ * @param factory A source of new individuals.
+ * @param replacementProbability The probability that any given individual will
+ * be replaced by a new individual. This should typically be quite low. If it is
+ * too high, it will undermine the evolutionary progress.
+ */
+ public Replacement(CandidateFactory<T> factory,
+ Probability replacementProbability)
+ {
+ this(factory, new ConstantGenerator<Probability>(replacementProbability));
+ }
+
+
+ /**
+ * Creates a replacement operator that replaces individuals according to
+ * a variable probability. New individuals are obtained from the factory
+ * provided.
+ * @param factory A source of new individuals.
+ * @param replacementProbability A {@link NumberGenerator} that provides
+ * a probability of replacement. The probablity may be constant, or it may change
+ * over time. The probability should typically be quite low. If it is too high,
+ * it will undermine the evolutionary progress.
+ */
+ public Replacement(CandidateFactory<T> factory,
+ NumberGenerator<Probability> replacementProbability)
+ {
+ this.factory = factory;
+ this.replacementProbability = replacementProbability;
+ }
+
+
+ /**
+ * Randomly replace zero or more of the selected candidates with new,
+ * independent individuals that are randomly created.
+ * @param selectedCandidates The selected candidates, some of these may be
+ * discarded and replaced with new individuals.
+ * @param rng A source of randomness.
+ * @return The remaining candidates after some (or none) have been replaced
+ * with new individuals.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ List<T> output = new ArrayList<T>(selectedCandidates.size());
+ for (T candidate : selectedCandidates)
+ {
+ output.add(replacementProbability.nextValue().nextEvent(rng)
+ ? factory.generateRandomCandidate(rng)
+ : candidate);
+ }
+ return output;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java
new file mode 100644
index 0000000..4c6d5fb
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/SplitEvolution.java
@@ -0,0 +1,119 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * <p>Compound evolutionary operator that allows the evolution of a population
+ * to be split into two separate streams. A percentage of the population
+ * will be evolved according to one specified operator and the remainder
+ * according to another operator. When both streams have been executed, the
+ * resulting offspring will be returned as a single combined population.</p>
+ *
+ * <p>This kind of separation is common in a genetic programming context where,
+ * for example, 10% of the population is mutated and the remaining 90%
+ * undergoes cross-over independently.</p>
+ *
+ * <p>To split evolution into more than two streams, multiple SplitEvolution operators
+ * can be combined. By combining SplitEvolution operators with
+ * {@link EvolutionPipeline} operators, elaborate evolutionary schemes can be
+ * constructed.</p>
+ *
+ * @param <T> The type of evolved entity dealt with by this operator.
+ * @author Daniel Dyer
+ */
+public class SplitEvolution<T> implements EvolutionaryOperator<T>
+{
+ private final EvolutionaryOperator<T> operator1;
+ private final EvolutionaryOperator<T> operator2;
+ private final NumberGenerator<Double> weightVariable;
+
+ /**
+ * @param operator1 The operator that will apply to the first part of the
+ * population (as determined by the {@code weight} parameter).
+ * @param operator2 The operator that will apply to the second part of the
+ * population (as determined by the {@code weight} parameter).
+ * @param weight The proportion (as a real number between zero and 1 exclusive)
+ * of the population that will be evolved by {@code operator1}. The
+ * remainder will be evolved by {@code operator2}.
+ */
+ public SplitEvolution(EvolutionaryOperator<T> operator1,
+ EvolutionaryOperator<T> operator2,
+ double weight)
+ {
+ this(operator1, operator2, new ConstantGenerator<Double>(weight));
+ if (weight <= 0 || weight >= 1)
+ {
+ throw new IllegalArgumentException("Split ratio must be greater than 0 and less than 1.");
+ }
+ }
+
+
+ /**
+ * @param operator1 The operator that will apply to the first part of the
+ * population (as determined by the {@code weightVariable} parameter).
+ * @param operator2 The operator that will apply to the second part of the
+ * population (as determined by the {@code weightVariable} parameter).
+ * @param weightVariable A random variable that provides the ratio for
+ * dividing the population between the two evolutionary streams. Must
+ * only generate values in the range {@literal 0 < ratio < 1}.
+ */
+ public SplitEvolution(EvolutionaryOperator<T> operator1,
+ EvolutionaryOperator<T> operator2,
+ NumberGenerator<Double> weightVariable)
+ {
+ this.operator1 = operator1;
+ this.operator2 = operator2;
+ this.weightVariable = weightVariable;
+ }
+
+
+ /**
+ * Applies one evolutionary operator to part of the population and another
+ * to the remainder. Returns a list combining the output of both. Which
+ * candidates are submitted to which stream is determined randomly.
+ * @param selectedCandidates A list of the candidates that survived to be
+ * eligible for evolution.
+ * @param rng A source of randomness passed to each of the two delegate
+ * evolutionary operators.
+ * @return The combined results from the two streams of evolution.
+ */
+ public List<T> apply(List<T> selectedCandidates, Random rng)
+ {
+ double ratio = weightVariable.nextValue();
+ int size = (int) Math.round(ratio * selectedCandidates.size());
+
+ // Shuffle the collection before applying each operation so that the
+ // split is not influenced by any ordering artifacts from previous
+ // operations.
+ List<T> selectionClone = new ArrayList<T>(selectedCandidates);
+ Collections.shuffle(selectionClone, rng);
+
+ List<T> list1 = selectionClone.subList(0, size);
+ List<T> list2 = selectionClone.subList(size, selectedCandidates.size());
+ List<T> result = new ArrayList<T>(selectedCandidates.size());
+ result.addAll(operator1.apply(list1, rng));
+ result.addAll(operator2.apply(list2, rng));
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java
new file mode 100644
index 0000000..e73119a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringCrossover.java
@@ -0,0 +1,132 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+
+/**
+ * Variable-point (fixed or random) cross-over for String candidates.
+ * This implementation assumes that all candidate Strings are the same
+ * length. If they are not, an exception will be thrown at runtime.
+ * @author Daniel Dyer
+ */
+public class StringCrossover extends AbstractCrossover<String>
+{
+ /**
+ * Default is single-point cross-over, applied to all parents.
+ */
+ public StringCrossover()
+ {
+ this(1);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ */
+ public StringCrossover(int crossoverPoints)
+ {
+ super(crossoverPoints);
+ }
+
+
+ /**
+ * Cross-over with a fixed number of cross-over points. Cross-over
+ * may or may not be applied to a given pair of parents depending on
+ * the {@code crossoverProbability}.
+ * @param crossoverPoints The constant number of cross-over points
+ * to use for all cross-over operations.
+ * @param crossoverProbability The probability that, once selected,
+ * a pair of parents will be subjected to cross-over rather than
+ * being copied, unchanged, into the output population.
+ */
+ public StringCrossover(int crossoverPoints, Probability crossoverProbability)
+ {
+ super(crossoverPoints, crossoverProbability);
+ }
+
+
+ /**
+ * Cross-over with a variable number of cross-over points.
+ * @param crossoverPointsVariable A random variable that provides a number
+ * of cross-over points for each cross-over operation.
+ */
+ public StringCrossover(NumberGenerator<Integer> crossoverPointsVariable)
+ {
+ super(crossoverPointsVariable);
+ }
+
+
+ /**
+ * Sets up a cross-over implementation that uses a variable number of cross-over
+ * points. Cross-over is applied to a proportion of selected parent pairs, with
+ * the remainder copied unchanged into the output population. The size of this
+ * evolved proportion is controlled by the {@code crossoverProbabilityVariable}
+ * parameter.
+ * @param crossoverPointsVariable A variable that provides a (possibly constant,
+ * possibly random) number of cross-over points for each cross-over operation.
+ * @param crossoverProbabilityVariable A variable that controls the probability
+ * that, once selected, a pair of parents will be subjected to cross-over rather
+ * than being copied, unchanged, into the output population.
+ */
+ public StringCrossover(NumberGenerator<Integer> crossoverPointsVariable,
+ NumberGenerator<Probability> crossoverProbabilityVariable)
+ {
+ super(crossoverPointsVariable, crossoverProbabilityVariable);
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected List<String> mate(String parent1,
+ String parent2,
+ int numberOfCrossoverPoints,
+ Random rng)
+ {
+ if (parent1.length() != parent2.length())
+ {
+ throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
+ }
+ StringBuilder offspring1 = new StringBuilder(parent1);
+ StringBuilder offspring2 = new StringBuilder(parent2);
+ // Apply as many cross-overs as required.
+ for (int i = 0; i < numberOfCrossoverPoints; i++)
+ {
+ // Cross-over index is always greater than zero and less than
+ // the length of the parent so that we always pick a point that
+ // will result in a meaningful cross-over.
+ int crossoverIndex = (1 + rng.nextInt(parent1.length() - 1));
+ for (int j = 0; j < crossoverIndex; j++)
+ {
+ char temp = offspring1.charAt(j);
+ offspring1.setCharAt(j, offspring2.charAt(j));
+ offspring2.setCharAt(j, temp);
+ }
+ }
+ List<String> result = new ArrayList<String>(2);
+ result.add(offspring1.toString());
+ result.add(offspring2.toString());
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java
new file mode 100644
index 0000000..6e2e485
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/StringMutation.java
@@ -0,0 +1,95 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Mutation of individual characters in a string according to some
+ * probability.
+ * @author Daniel Dyer
+ */
+public class StringMutation implements EvolutionaryOperator<String>
+{
+ private final char[] alphabet;
+ private final NumberGenerator<Probability> mutationProbability;
+
+ /**
+ * Creates a mutation operator that is applied with the given
+ * probability and draws its characters from the specified alphabet.
+ * @param alphabet The permitted values for each character in a string.
+ * @param mutationProbability The probability that a given character
+ * is changed.
+ */
+ public StringMutation(char[] alphabet, Probability mutationProbability)
+ {
+ this(alphabet, new ConstantGenerator<Probability>(mutationProbability));
+ }
+
+
+ /**
+ * Creates a mutation operator that is applied with the given
+ * probability and draws its characters from the specified alphabet.
+ * @param alphabet The permitted values for each character in a string.
+ * @param mutationProbability The (possibly variable) probability that a
+ * given character is changed.
+ */
+ public StringMutation(char[] alphabet,
+ NumberGenerator<Probability> mutationProbability)
+ {
+ this.alphabet = alphabet.clone();
+ this.mutationProbability = mutationProbability;
+ }
+
+
+ public List<String> apply(List<String> selectedCandidates, Random rng)
+ {
+ List<String> mutatedPopulation = new ArrayList<String>(selectedCandidates.size());
+ for (String s : selectedCandidates)
+ {
+ mutatedPopulation.add(mutateString(s, rng));
+ }
+ return mutatedPopulation;
+ }
+
+
+ /**
+ * Mutate a single string. Zero or more characters may be modified. The
+ * probability of any given character being modified is governed by the
+ * probability generator configured for this mutation operator.
+ * @param s The string to mutate.
+ * @param rng A source of randomness.
+ * @return The mutated string.
+ */
+ private String mutateString(String s, Random rng)
+ {
+ StringBuilder buffer = new StringBuilder(s);
+ for (int i = 0; i < buffer.length(); i++)
+ {
+ if (mutationProbability.nextValue().nextEvent(rng))
+ {
+ buffer.setCharAt(i, alphabet[rng.nextInt(alphabet.length)]);
+ }
+ }
+ return buffer.toString();
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java
new file mode 100644
index 0000000..ee33629
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/operators/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Provides several ready-to-use standard evolutionary operators for commonly
+ * used data types such as arrays, lists and strings.
+ */
+package org.uncommons.watchmaker.framework.operators;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java
new file mode 100644
index 0000000..4f3f095
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/package-info.java
@@ -0,0 +1,22 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * This package provides a framework for evolutionary computation. It defines generic
+ * interfaces for evolutionary operators, fitness functions and selection strategies.
+ * It also provides an all-purpose {@link org.uncommons.watchmaker.framework.EvolutionEngine}.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java
new file mode 100644
index 0000000..1b12b54
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RankSelection.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * <p>A selection strategy that is similar to fitness-proportionate selection
+ * except that is uses relative fitness rather than absolute fitness in order to
+ * determine the probability of selection for a given individual (i.e. the actual
+ * numerical fitness values are ignored and only the ordering of the sorted
+ * population is considered).</p>
+ * <p>Rank selection is implemented in terms of a mapping function ({@link
+ * #mapRankToScore(int, int)}) and delegation to a fitness-proportionate selector. The
+ * mapping function converts ranks into relative fitness scores that are used to
+ * drive the delegate selector.</p>
+ * @author Daniel Dyer
+ */
+public class RankSelection implements SelectionStrategy<Object>
+{
+ private final SelectionStrategy<Object> delegate;
+
+ /**
+ * Creates a default rank-based selector with a linear
+ * mapping function and selection frequencies that correspond
+ * to expected values.
+ */
+ public RankSelection()
+ {
+ this(new StochasticUniversalSampling());
+ }
+
+
+ /**
+ * Creates a rank-based selector with a linear mapping function and
+ * configurable delegate for performing the proportionate selection.
+ * @param delegate The proportionate selector that will be delegated
+ * to after converting rankings into relative fitness scores.
+ */
+ public RankSelection(SelectionStrategy<Object> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<EvaluatedCandidate<S>> rankedPopulation = new ArrayList<EvaluatedCandidate<S>>(population.size());
+ Iterator<EvaluatedCandidate<S>> iterator = population.iterator();
+ int index = -1;
+ while (iterator.hasNext())
+ {
+ S candidate = iterator.next().getCandidate();
+ rankedPopulation.add(new EvaluatedCandidate<S>(candidate,
+ mapRankToScore(++index,
+ population.size())));
+ }
+ return delegate.select(rankedPopulation, true, selectionSize, rng);
+ }
+
+
+ /**
+ * <p>Maps a population index to a relative pseudo-fitness score that can be used for
+ * fitness-proportionate selection. The general contract for the mapping function
+ * {@code f} is: {@code f(rank) >= f(rank + 1)} for all legal values of
+ * {@code rank}, assuming natural scores.</p>
+ * <p>The default mapping function is a simple linear transformation, but this
+ * can be over-ridden in sub-classes. Alternative implementations can be linear or
+ * non-linear and either natural or non-natural.</p>
+ * @param rank A zero-based index into the population
+ * {@code (0 <= rank < populationSize)}.
+ * @param populationSize The number of individuals in the population.
+ * @return {@code populationSize - rank}
+ */
+ protected double mapRankToScore(int rank, int populationSize)
+ {
+ return populationSize - rank;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Rank Selection";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java
new file mode 100644
index 0000000..a964f57
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/RouletteWheelSelection.java
@@ -0,0 +1,118 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * <p>Implements selection of <i>n</i> candidates from a population by selecting
+ * <i>n</i> candidates at random where the probability of each candidate getting
+ * selected is proportional to its fitness score. This is analogous to each
+ * candidate being assigned an area on a roulette wheel proportionate to its fitness
+ * and the wheel being spun <n>i</n> times. Candidates may be selected more than
+ * once.</p>
+ *
+ * <p>In some instances, particularly with small population sizes, the randomness
+ * of selection may result in excessively high occurrences of particular candidates.
+ * If this is a problem, {@link StochasticUniversalSampling} provides an alternative
+ * fitness-proportionate strategy for selection.</p>
+ *
+ * @author Daniel Dyer
+ */
+public class RouletteWheelSelection implements SelectionStrategy<Object>
+{
+ /**
+ * Selects the required number of candidates from the population with
+ * the probability of selecting any particular candidate being proportional
+ * to that candidate's fitness score. Selection is with replacement (the same
+ * candidate may be selected multiple times).
+ * @param <S> The type of the evolved objects in the population.
+ * @param population The candidates to select from.
+ * @param naturalFitnessScores True if higher fitness scores indicate fitter
+ * individuals, false if lower fitness scores indicate fitter individuals.
+ * @param selectionSize The number of selections to make.
+ * @param rng A source of randomness.
+ * @return The selected candidates.
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ // Record the cumulative fitness scores. It doesn't matter whether the
+ // population is sorted or not. We will use these cumulative scores to work out
+ // an index into the population. The cumulative array itself is implicitly
+ // sorted since each element must be greater than the previous one. The
+ // numerical difference between an element and the previous one is directly
+ // proportional to the probability of the corresponding candidate in the population
+ // being selected.
+ double[] cumulativeFitnesses = new double[population.size()];
+ cumulativeFitnesses[0] = getAdjustedFitness(population.get(0).getFitness(),
+ naturalFitnessScores);
+ for (int i = 1; i < population.size(); i++)
+ {
+ double fitness = getAdjustedFitness(population.get(i).getFitness(),
+ naturalFitnessScores);
+ cumulativeFitnesses[i] = cumulativeFitnesses[i - 1] + fitness;
+ }
+
+ List<S> selection = new ArrayList<S>(selectionSize);
+ for (int i = 0; i < selectionSize; i++)
+ {
+ double randomFitness = rng.nextDouble() * cumulativeFitnesses[cumulativeFitnesses.length - 1];
+ int index = Arrays.binarySearch(cumulativeFitnesses, randomFitness);
+ if (index < 0)
+ {
+ // Convert negative insertion point to array index.
+ index = Math.abs(index + 1);
+ }
+ selection.add(population.get(index).getCandidate());
+ }
+ return selection;
+ }
+
+
+ private double getAdjustedFitness(double rawFitness,
+ boolean naturalFitness)
+ {
+ if (naturalFitness)
+ {
+ return rawFitness;
+ }
+ else
+ {
+ // If standardised fitness is zero we have found the best possible
+ // solution. The evolutionary algorithm should not be continuing
+ // after finding it.
+ return rawFitness == 0 ? Double.POSITIVE_INFINITY : 1 / rawFitness;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Roulette Wheel Selection";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java
new file mode 100644
index 0000000..814b0b7
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/SigmaScaling.java
@@ -0,0 +1,114 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.statistics.DataSet;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * An alternative to straightforward fitness-proportionate selection such as that offered
+ * by {@link RouletteWheelSelection} and {@link StochasticUniversalSampling}. Uses the
+ * mean population fitness and fitness standard deviation to adjust individual fitness
+ * scores. Early on in an evolutionary algorithm this helps to avoid premature convergence
+ * caused by the dominance of one or two relatively fit candidates in a population of mostly
+ * unfit individuals. It also helps to amplify minor fitness differences in a more mature
+ * population where the rate of improvement has slowed.
+ * @author Daniel Dyer
+ */
+public class SigmaScaling implements SelectionStrategy<Object>
+{
+ private final SelectionStrategy<Object> delegate;
+
+ /**
+ * Creates a default sigma-scaled selection strategy.
+ */
+ public SigmaScaling()
+ {
+ this(new StochasticUniversalSampling());
+ }
+
+
+ /**
+ * Creates a sigma-scaled selection strategy that delegates to the specified selection
+ * strategy after adjusting individual fitness scores using sigma-scaling.
+ * @param delegate The proportionate selector that will be delegated
+ * to after fitness scores have been adjusted using sigma-scaling.
+ */
+ public SigmaScaling(SelectionStrategy<Object> delegate)
+ {
+ this.delegate = delegate;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ DataSet statistics = new DataSet(population.size());
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ statistics.addValue(candidate.getFitness());
+ }
+
+ List<EvaluatedCandidate<S>> scaledPopulation = new ArrayList<EvaluatedCandidate<S>>(population.size());
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ double scaledFitness = getSigmaScaledFitness(candidate.getFitness(),
+ statistics.getArithmeticMean(),
+ statistics.getStandardDeviation());
+ scaledPopulation.add(new EvaluatedCandidate<S>(candidate.getCandidate(),
+ scaledFitness));
+ }
+ return delegate.select(scaledPopulation, naturalFitnessScores, selectionSize, rng);
+ }
+
+
+ private double getSigmaScaledFitness(double candidateFitness,
+ double populationMeanFitness,
+ double fitnessStandardDeviation)
+ {
+ if (fitnessStandardDeviation == 0)
+ {
+ return 1;
+ }
+ else
+ {
+ double scaledFitness = 1 + (candidateFitness - populationMeanFitness) / (2 * fitnessStandardDeviation);
+ // Don't allow negative expected frequencies, use an arbitrary low but still positive
+ // frequency of 1 time in 10 for extremely unfit individuals (relative to the remainder
+ // of the population).
+ return scaledFitness > 0 ? scaledFitness : 0.1;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Sigma Scaling";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java
new file mode 100644
index 0000000..7e1bf9b
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/StochasticUniversalSampling.java
@@ -0,0 +1,95 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * An alternative to {@link RouletteWheelSelection}
+ * as a fitness-proportionate selection strategy. Ensures that the frequency of selection for
+ * each candidate is consistent with its expected frequency of selection.
+ * @author Daniel Dyer
+ */
+public class StochasticUniversalSampling implements SelectionStrategy<Object>
+{
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ // Calculate the sum of all fitness values.
+ double aggregateFitness = 0;
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ aggregateFitness += getAdjustedFitness(candidate.getFitness(),
+ naturalFitnessScores);
+ }
+
+ List<S> selection = new ArrayList<S>(selectionSize);
+ // Pick a random offset between 0 and 1 as the starting point for selection.
+ double startOffset = rng.nextDouble();
+ double cumulativeExpectation = 0;
+ int index = 0;
+ for (EvaluatedCandidate<S> candidate : population)
+ {
+ // Calculate the number of times this candidate is expected to
+ // be selected on average and add it to the cumulative total
+ // of expected frequencies.
+ cumulativeExpectation += getAdjustedFitness(candidate.getFitness(),
+ naturalFitnessScores) / aggregateFitness * selectionSize;
+
+ // If f is the expected frequency, the candidate will be selected at
+ // least as often as floor(f) and at most as often as ceil(f). The
+ // actual count depends on the random starting offset.
+ while (cumulativeExpectation > startOffset + index)
+ {
+ selection.add(candidate.getCandidate());
+ index++;
+ }
+ }
+ return selection;
+ }
+
+
+ private double getAdjustedFitness(double rawFitness, boolean naturalFitness)
+ {
+ if (naturalFitness)
+ {
+ return rawFitness;
+ }
+ else
+ {
+ // If standardised fitness is zero we have found the best possible
+ // solution. The evolutionary algorithm should not be continuing
+ // after finding it.
+ return rawFitness == 0 ? Double.POSITIVE_INFINITY : 1 / rawFitness;
+ }
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return "Stochastic Universal Sampling";
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java
new file mode 100644
index 0000000..78d8107
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TournamentSelection.java
@@ -0,0 +1,115 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Selection strategy that picks a pair of candidates at random and then
+ * selects the fitter of the two candidates with probability p, where p
+ * is the configured selection probability (therefore the probability of
+ * the less fit candidate being selected is 1 - p).
+ * @author Daniel Dyer
+ */
+public class TournamentSelection implements SelectionStrategy<Object>
+{
+ private final NumberGenerator<Probability> selectionProbability;
+
+ private String description = "Tournament Selection";
+
+ /**
+ * Creates a tournament selection strategy that is controlled by the
+ * variable selection probability provided by the specified
+ * {@link NumberGenerator}.
+ * @param selectionProbability A number generator that produces values in
+ * the range {@literal 0.5 < p < 1}. These values are used as the probability
+ * of the fittest candidate being selected in any given tournament.
+ */
+ public TournamentSelection(NumberGenerator<Probability> selectionProbability)
+ {
+ this.selectionProbability = selectionProbability;
+ }
+
+
+ /**
+ * Creates a tournament selection strategy with a fixed probability.
+ * @param selectionProbability The probability that the fitter of two randomly
+ * chosen candidates will be selected. Since this is a probability it must be
+ * between 0.0 and 1.0. This implementation adds the further restriction that
+ * the probability must be greater than 0.5 since any lower value would favour
+ * weaker candidates over strong ones, negating the "survival of the fittest"
+ * aspect of the evolutionary algorithm.
+ */
+ public TournamentSelection(Probability selectionProbability)
+ {
+ this(new ConstantGenerator<Probability>(selectionProbability));
+ if (selectionProbability.doubleValue() <= 0.5)
+ {
+ throw new IllegalArgumentException("Selection threshold must be greater than 0.5.");
+ }
+ this.description = "Tournament Selection (p = " + selectionProbability.toString() + ')';
+ }
+
+
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<S> selection = new ArrayList<S>(selectionSize);
+ for (int i = 0; i < selectionSize; i++)
+ {
+ // Pick two candidates at random.
+ EvaluatedCandidate<S> candidate1 = population.get(rng.nextInt(population.size()));
+ EvaluatedCandidate<S> candidate2 = population.get(rng.nextInt(population.size()));
+
+ // Use a random value to decide wether to select the fitter individual or the weaker one.
+ boolean selectFitter = selectionProbability.nextValue().nextEvent(rng);
+ if (selectFitter == naturalFitnessScores)
+ {
+ // Select the fitter candidate.
+ selection.add(candidate2.getFitness() > candidate1.getFitness()
+ ? candidate2.getCandidate()
+ : candidate1.getCandidate());
+ }
+ else
+ {
+ // Select the less fit candidate.
+ selection.add(candidate2.getFitness() > candidate1.getFitness()
+ ? candidate1.getCandidate()
+ : candidate2.getCandidate());
+ }
+ }
+ return selection;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return description;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java
new file mode 100644
index 0000000..594df97
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/TruncationSelection.java
@@ -0,0 +1,117 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.number.NumberGenerator;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Implements selection of <i>n</i> candidates from a population by simply
+ * selecting the <i>n</i> candidates with the highest fitness scores (the
+ * rest are discarded). A candidate is never selected more than once.
+ * @author Daniel Dyer
+ */
+public class TruncationSelection implements SelectionStrategy<Object>
+{
+ private static final DecimalFormat PERCENT_FORMAT = new DecimalFormat("#0.###%");
+ private final NumberGenerator<Double> selectionRatio;
+
+ private String description = "Truncation Selection";
+
+ /**
+ * Creates a truncation selection strategy that is controlled by the
+ * variable selection ratio provided by the specified
+ * {@link NumberGenerator}.
+ * @param selectionRatio A number generator that produces values in
+ * the range {@literal 0 < r < 1}. These values are used to determine
+ * the proportion of the population that is retained in any given selection.
+ */
+ public TruncationSelection(NumberGenerator<Double> selectionRatio)
+ {
+ this.selectionRatio = selectionRatio;
+ }
+
+
+ /**
+ * @param selectionRatio The proportion of the highest ranked candidates to
+ * select from the population. The value must be positive and less than 1.
+ */
+ public TruncationSelection(double selectionRatio)
+ {
+ this(new ConstantGenerator<Double>(selectionRatio));
+ if (selectionRatio <= 0 || selectionRatio >= 1)
+ {
+ throw new IllegalArgumentException("Selection ratio must be greater than 0 and less than 1.");
+ }
+ this.description = "Truncation Selection (" + PERCENT_FORMAT.format(selectionRatio) + ")";
+ }
+
+
+ /**
+ * Selects the fittest candidates. If the selectionRatio results in
+ * fewer selected candidates than required, then these candidates are
+ * selected multiple times to make up the shortfall.
+ * @param population The population of evolved and evaluated candidates
+ * from which to select.
+ * @param naturalFitnessScores Whether higher fitness values represent fitter
+ * individuals or not.
+ * @param selectionSize The number of candidates to select from the
+ * evolved population.
+ * @param rng A source of randomness (not used by this selection
+ * implementation since truncation selection is deterministic).
+ * @param <S> The type of evolved entity that is being selected.
+ * @return The selected candidates.
+ */
+ public <S> List<S> select(List<EvaluatedCandidate<S>> population,
+ boolean naturalFitnessScores,
+ int selectionSize,
+ Random rng)
+ {
+ List<S> selection = new ArrayList<S>(selectionSize);
+
+ double ratio = selectionRatio.nextValue();
+ assert ratio < 1 && ratio > 0 : "Selection ratio out-of-range: " + ratio;
+
+ int eligibleCount = (int) Math.round(ratio * population.size());
+ eligibleCount = eligibleCount > selectionSize ? selectionSize : eligibleCount;
+
+ do
+ {
+ int count = Math.min(eligibleCount, selectionSize - selection.size());
+ for (int i = 0; i < count; i++)
+ {
+ selection.add(population.get(i).getCandidate());
+ }
+ } while (selection.size() < selectionSize);
+ return selection;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString()
+ {
+ return description;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java
new file mode 100644
index 0000000..73f682a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/selection/package-info.java
@@ -0,0 +1,20 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Various selection strategies for use with evolutionary algorithms.
+ * @author Daniel Dyer
+ */
+package org.uncommons.watchmaker.framework.selection;
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java
new file mode 100644
index 0000000..bddea38
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/ElapsedTime.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Terminates evolution after a pre-determined period of time has elapsed.
+ * @author Daniel Dyer
+ */
+public class ElapsedTime implements TerminationCondition
+{
+ private final long maxDuration;
+
+ /**
+ * @param maxDuration The maximum period of time (in milliseconds) before
+ * evolution will be terminated.
+ */
+ public ElapsedTime(long maxDuration)
+ {
+ if (maxDuration <= 0)
+ {
+ throw new IllegalArgumentException("Duration must be positive.");
+ }
+ this.maxDuration = maxDuration;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * This implementation terminates evolution if the pre-configured maximum
+ * permitted time has elapsed.
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return populationData.getElapsedTime() >= maxDuration;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java
new file mode 100644
index 0000000..a0ec88c
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/GenerationCount.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Terminates evolution after a set number of generations have passed.
+ * @author Daniel Dyer
+ */
+public class GenerationCount implements TerminationCondition
+{
+ private final int generationCount;
+
+ /**
+ * @param generationCount The maximum number of generations that the
+ * evolutionary algorithm will permit before terminating.
+ */
+ public GenerationCount(int generationCount)
+ {
+ if (generationCount <= 0)
+ {
+ throw new IllegalArgumentException("Generation count must be positive.");
+ }
+ this.generationCount = generationCount;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return populationData.getGenerationNumber() + 1 >= generationCount;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java
new file mode 100644
index 0000000..b0bab4a
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/Stagnation.java
@@ -0,0 +1,113 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * A {@link TerminationCondition} that halts evolution if no improvement in fitness
+ * is observed within a specified number of generations.
+ * @author Daniel Dyer
+ */
+public class Stagnation implements TerminationCondition
+{
+ private final int generationLimit;
+ private final boolean naturalFitness;
+ private final boolean usePopulationAverage;
+
+ private double bestFitness;
+ private int fittestGeneration;
+
+ /**
+ * Creates a {@link TerminationCondition} that will halt evolution after the
+ * specified number of generations passes without any improvement in the population's
+ * fittest individual.
+ * @param generationLimit The number of generations without improvement that
+ * will lead to termination.
+ * @param naturalFitness True if higher fitness scores are better, false otherwise.
+ */
+ public Stagnation(int generationLimit,
+ boolean naturalFitness)
+ {
+ this(generationLimit, naturalFitness, false);
+ }
+
+
+ /**
+ * Creates a {@link TerminationCondition} that will halt evolution after the
+ * specified number of generations passes without any improvement in the population's
+ * fitness (either the fittest individual or the mean fitness of the entire population,
+ * depending on the final parameter).
+ * @param generationLimit The number of generations without improvement that
+ * will lead to termination.
+ * @param naturalFitness True if higher fitness scores are better, false otherwise.
+ * @param usePopulationAverage If true uses the mean fitness of the population as the
+ * criteria, otherwise uses the fittest individual.
+ */
+ public Stagnation(int generationLimit,
+ boolean naturalFitness,
+ boolean usePopulationAverage)
+ {
+ this.generationLimit = generationLimit;
+ this.naturalFitness = naturalFitness;
+ this.usePopulationAverage = usePopulationAverage;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ double fitness = getFitness(populationData);
+ if (populationData.getGenerationNumber() == 0 || hasFitnessImproved(fitness))
+ {
+ bestFitness = fitness;
+ fittestGeneration = populationData.getGenerationNumber();
+ }
+
+ return populationData.getGenerationNumber() - fittestGeneration >= generationLimit;
+ }
+
+
+ /**
+ * Determines the fitness of the current population (either best fitness or
+ * mean fitness depending on how the termination condition is configured).
+ * @param populationData Data about the current generation.
+ * @return The fitness measure used to decide whether the evolution has stagnated
+ * or not.
+ */
+ private double getFitness(PopulationData<?> populationData)
+ {
+ return usePopulationAverage
+ ? populationData.getMeanFitness()
+ : populationData.getBestCandidateFitness();
+ }
+
+
+ /**
+ * Determine whether the population fitness is better than the best seen so far.
+ * @param fitness The fitness of the current population (either best fitness or mean
+ * fitness depending on how the termination condition is configured).
+ * @return True if the fitness has improved in the current generation, false otherwise.
+ */
+ private boolean hasFitnessImproved(double fitness)
+ {
+ return (naturalFitness && fitness > bestFitness)
+ || (!naturalFitness && fitness < bestFitness);
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java
new file mode 100644
index 0000000..26c3298
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/TargetFitness.java
@@ -0,0 +1,63 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Terminates evolution once at least one candidate in the population has equalled
+ * or bettered a pre-determined fitness score.
+ * @author Daniel Dyer
+ */
+public class TargetFitness implements TerminationCondition
+{
+ private final double targetFitness;
+ private final boolean natural;
+
+ /**
+ * @param targetFitness The fitness score that must be achieved by at least
+ * one individual in the population in order for this condition to be satisfied.
+ * @param natural Whether fitness scores are natural or non-natural. If fitness
+ * is natural, the condition will be satisfied if any individual has a fitness
+ * that is greater than or equal to the target fitness. If fitness is non-natural,
+ * the condition will be satisfied in any individual has a fitness that is less
+ * than or equal to the target fitness.
+ * @see org.uncommons.watchmaker.framework.FitnessEvaluator
+ */
+ public TargetFitness(double targetFitness, boolean natural)
+ {
+ this.targetFitness = targetFitness;
+ this.natural = natural;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ if (natural)
+ {
+ // If we're using "natural" fitness scores, higher values are better.
+ return populationData.getBestCandidateFitness() >= targetFitness;
+ }
+ else
+ {
+ // If we're using "non-natural" fitness scores, lower values are better.
+ return populationData.getBestCandidateFitness() <= targetFitness;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java
new file mode 100644
index 0000000..dce1aab
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/UserAbort.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * {@link TerminationCondition} implementation that allows for user-initiated
+ * termination of an evolutionary algorithm. This condition can be used, for
+ * instance, to provide a button on a GUI that terminates execution. The
+ * application should retain a reference to the instance after passing it to
+ * the evolution engine and should invoke the {@link #abort()} method to make
+ * the evolution terminate at the end of the current generation.
+ * @see org.uncommons.watchmaker.swing.AbortControl
+ * @author Daniel Dyer
+ */
+public final class UserAbort implements TerminationCondition
+{
+ private volatile boolean aborted = false;
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean shouldTerminate(PopulationData<?> populationData)
+ {
+ return isAborted();
+ }
+
+
+ /**
+ * Aborts any evolutionary algorithms that monitor this termination condition
+ * instance.
+ */
+ public void abort()
+ {
+ aborted = true;
+ }
+
+
+ /**
+ * @return true if the {@link #abort()} method has been invoked, false otherwise.
+ */
+ public boolean isAborted()
+ {
+ return aborted;
+ }
+
+
+ /**
+ * Resets the abort condition to false so that it may be reused.
+ */
+ public void reset()
+ {
+ aborted = false;
+ }
+}
diff --git a/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java
new file mode 100644
index 0000000..e68cb9d
--- /dev/null
+++ b/watchmaker/framework/src/java/main/org/uncommons/watchmaker/framework/termination/package-info.java
@@ -0,0 +1,19 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+/**
+ * Configurable conditions for terminating evolutionary algorithms.
+ */
+package org.uncommons.watchmaker.framework.termination;
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java
new file mode 100644
index 0000000..8aaae2f
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/concurrent/ConfigurableThreadFactoryTest.java
@@ -0,0 +1,137 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.concurrent;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the general-purpose thread factory implementation.
+ * @author Daniel Dyer
+ */
+public class ConfigurableThreadFactoryTest
+{
+ @Test
+ public void testDaemonThreads()
+ {
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MIN_PRIORITY,
+ true);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ // Do nothing.
+ }
+ };
+ Thread thread1 = threadFactory.newThread(doNothing);
+ assert thread1.getName().startsWith("Test") : "Wrong thread name: " + thread1.getName();
+ assert thread1.getPriority() == Thread.MIN_PRIORITY : "Wrong priority: " + thread1.getPriority();
+ assert thread1.isDaemon() : "Thread should be a daemon.";
+
+ // Second thread should have a different name.
+ Thread thread2 = threadFactory.newThread(doNothing);
+ assert thread2.getName().startsWith("Test") : "Wrong thread name: " + thread2.getName();
+ assert !thread1.getName().equals(thread2.getName()) : "Thread names should be different.";
+ }
+
+
+ @Test
+ public void testNonDaemonThreads()
+ {
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ // Do nothing.
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ assert thread.getName().startsWith("Test") : "Wrong thread name: " + thread.getName();
+ assert thread.getPriority() == Thread.MAX_PRIORITY : "Wrong priority: " + thread.getPriority();
+ assert !thread.isDaemon() : "Thread should not be a daemon.";
+ }
+
+
+ @Test
+ public void testDefaultExceptionHandler() throws InterruptedException
+ {
+ // Intercept std. err.
+ ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(byteStream));
+
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ throw new IllegalStateException("This is a test.");
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ thread.start();
+ thread.join();
+
+ String output = byteStream.toString();
+ assert output.startsWith("java.lang.IllegalStateException") : "Exception handler failed to log exception.";
+ }
+
+
+ @Test
+ public void testCustomExceptionHandler() throws InterruptedException
+ {
+ ExceptionHandler exceptionHandler = new ExceptionHandler();
+ ThreadFactory threadFactory = new ConfigurableThreadFactory("Test",
+ Thread.MAX_PRIORITY,
+ false,
+ exceptionHandler);
+ Runnable doNothing = new Runnable()
+ {
+ public void run()
+ {
+ throw new IllegalStateException("This is a test.");
+ }
+ };
+ Thread thread = threadFactory.newThread(doNothing);
+ thread.start();
+ thread.join();
+ assert exceptionHandler.getExceptionCount() == 1 : "Exception not thrown.";
+ }
+
+
+ private static final class ExceptionHandler implements Thread.UncaughtExceptionHandler
+ {
+ private final AtomicInteger count = new AtomicInteger();
+
+ public void uncaughtException(Thread thread, Throwable throwable)
+ {
+ count.incrementAndGet();
+ }
+
+ public int getExceptionCount()
+ {
+ return count.get();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java
new file mode 100644
index 0000000..79946c7
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/CompositeIDSourceTest.java
@@ -0,0 +1,36 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for composite ID source.
+ * @author Daniel Dyer
+ */
+public class CompositeIDSourceTest
+{
+ @Test
+ public void testCombination()
+ {
+ int topPart = 15;
+ IDSource<Long> idSource = new CompositeIDSource(topPart);
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ assert 64424509440L == firstID : "First ID should be 2^36 - 2^32 (or 15 shifted left 32 places).";
+ assert secondID == firstID + 1 : "Second ID should be first ID plus 1.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java
new file mode 100644
index 0000000..e9ae567
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/IntSequenceIDSourceTest.java
@@ -0,0 +1,56 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for 32-bit ID sequence.
+ * @author Daniel Dyer
+ */
+public class IntSequenceIDSourceTest
+{
+ @Test
+ public void testSequence()
+ {
+ IntSequenceIDSource idSource = new IntSequenceIDSource();
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ long thirdID = idSource.nextID();
+ assert firstID == 0 : "First ID should be 0.";
+ assert secondID == firstID + 1 : "Second ID should be 1 more than first ID.";
+ assert thirdID == secondID + 1 : "Third ID should be 1 more than second ID.";
+ }
+
+
+ @Test(expectedExceptions = IDSourceExhaustedException.class)
+ public void testExhaustion()
+ {
+ IDSource<Integer> idSource = new IntSequenceIDSource(Integer.MAX_VALUE);
+ // Should be able to get one ID from this.
+ int id = idSource.nextID();
+ assert id == Integer.MAX_VALUE : "Incorrect initial value: " + id;
+ // But the next invocation should result in an exception.
+ idSource.nextID();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidInitialValue()
+ {
+ new IntSequenceIDSource(-1);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java
new file mode 100644
index 0000000..4cfe0e0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/LongSequenceIDSourceTest.java
@@ -0,0 +1,56 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for 64-bit ID sequence.
+ * @author Daniel Dyer
+ */
+public class LongSequenceIDSourceTest
+{
+ @Test
+ public void testSequence()
+ {
+ LongSequenceIDSource idSource = new LongSequenceIDSource();
+ long firstID = idSource.nextID();
+ long secondID = idSource.nextID();
+ long thirdID = idSource.nextID();
+ assert firstID == 0 : "First ID should be 0.";
+ assert secondID == firstID + 1 : "Second ID should be 1 more than first ID.";
+ assert thirdID == secondID + 1 : "Third ID should be 1 more than second ID.";
+ }
+
+
+ @Test(expectedExceptions = IDSourceExhaustedException.class)
+ public void testExhaustion()
+ {
+ IDSource<Long> idSource = new LongSequenceIDSource(Long.MAX_VALUE);
+ // Should be able to get one ID from this.
+ long id = idSource.nextID();
+ assert id == Long.MAX_VALUE : "Incorrect initial value: " + id;
+ // But the next invocation should result in an exception.
+ idSource.nextID();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidInitialValue()
+ {
+ new LongSequenceIDSource(-1);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java
new file mode 100644
index 0000000..546c78c
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/id/StringPrefixIDSourceTest.java
@@ -0,0 +1,35 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.id;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for prefixed ID source.
+ * @author Daniel Dyer
+ */
+public class StringPrefixIDSourceTest
+{
+ @Test
+ public void testPrefix()
+ {
+ IDSource<String> idSource = new StringPrefixIDSource("Watchmaker", new IntSequenceIDSource());
+ String id1 = idSource.nextID();
+ assert id1.equals("Watchmaker0") : "Wrong ID: " + id1;
+ String id2 = idSource.nextID();
+ assert id2.equals("Watchmaker1") : "Wrong ID: " + id2;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java b/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java
new file mode 100644
index 0000000..5d660e0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/util/reflection/ReflectionUtilsTest.java
@@ -0,0 +1,198 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.util.reflection;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for {@link ReflectionUtils}.
+ * @author Daniel Dyer
+ */
+public class ReflectionUtilsTest
+{
+ @Test
+ public void testFindMethod()
+ {
+ Method toString = ReflectionUtils.findKnownMethod(Object.class, "toString");
+ assert toString.getName().equals("toString") : "Wrong method returned: " + toString.getName();
+ // Make sure that the method returned is from the correct class and not some other class.
+ Class<?> declaringClass = toString.getDeclaringClass();
+ assert declaringClass.equals(Object.class) : "Method from wrong class returned: " + declaringClass.getName();
+ }
+
+
+ @Test(dependsOnMethods = "testFindMethod")
+ public void testSuccessfulInvocation()
+ {
+ Method toString = ReflectionUtils.findKnownMethod(Object.class, "toString");
+ String result = ReflectionUtils.invokeUnchecked(toString, "Hello");
+ assert result.equals("Hello") : "Wrong value returned by method.";
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoSuchMethod()
+ {
+ ReflectionUtils.findKnownMethod(Object.class, "noSuchMethod");
+ }
+
+
+ /**
+ * Invoking a method that throws a RuntimeException should result in that
+ * exception being thrown back to the caller. The RuntimeException should not
+ * be wrapped in an InvocationTargetException as would be the case for a
+ * normal reflective invocation.
+ */
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = ArithmeticException.class)
+ public void testMethodRuntimeExceptions()
+ {
+ Method divide = ReflectionUtils.findKnownMethod(BigDecimal.class, "divide", BigDecimal.class);
+ ReflectionUtils.invokeUnchecked(divide, BigDecimal.ONE, BigDecimal.ZERO); // Should throw ArithmeticException.
+ }
+
+
+ /**
+ * Invoking a method that throws an Error should result in that
+ * error being thrown back to the caller. The Error should not
+ * be wrapped in an InvocationTargetException as would be the case for a
+ * normal reflective invocation.
+ */
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = InternalError.class)
+ public void testErrors()
+ {
+ class ErrorTest
+ {
+ public void doError()
+ {
+ throw new InternalError();
+ }
+ }
+ Method error = ReflectionUtils.findKnownMethod(ErrorTest.class, "doError");
+ ReflectionUtils.invokeUnchecked(error, new ErrorTest()); // Should throw InternalError.
+ }
+
+
+ @Test
+ public void testFindConstructor()
+ {
+ Constructor<Object> defaultConstructor = ReflectionUtils.findKnownConstructor(Object.class);
+ // Make sure that the method returned is from the correct class and not some other class.
+ Class<?> declaringClass = defaultConstructor.getDeclaringClass();
+ assert declaringClass.equals(Object.class) : "Constructor from wrong class returned: " + declaringClass.getName();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoSuchConstructor()
+ {
+ ReflectionUtils.findKnownConstructor(Object.class, String.class);
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalStateException.class)
+ public void testExceptionInConstructor()
+ {
+ Constructor<ExceptionTest> constructor = ReflectionUtils.findKnownConstructor(ExceptionTest.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = InternalError.class)
+ public void testErrorInConstructor()
+ {
+ Constructor<ErrorTest> constructor = ReflectionUtils.findKnownConstructor(ErrorTest.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw error.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testConstructingAbstractClass()
+ {
+ Constructor<Abstract> constructor = ReflectionUtils.findKnownConstructor(Abstract.class);
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw an exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindConstructor",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testUnconstructable() throws NoSuchMethodException
+ {
+ Constructor<Unconstructable> constructor = Unconstructable.class.getDeclaredConstructor();
+ ReflectionUtils.invokeUnchecked(constructor); // Should throw an exception.
+ }
+
+
+ @Test(dependsOnMethods = "testFindMethod",
+ expectedExceptions = IllegalArgumentException.class)
+ public void testInaccessibleMethod() throws NoSuchMethodException
+ {
+ Method method = Inaccessible.class.getDeclaredMethod("inaccessibleMethod");
+ ReflectionUtils.invokeUnchecked(method, new Inaccessible()); // Should throw an exception.
+ }
+
+
+ private static class ExceptionTest
+ {
+ public ExceptionTest()
+ {
+ throw new IllegalStateException();
+ }
+ }
+
+
+ private static class ErrorTest
+ {
+ public ErrorTest()
+ {
+ throw new InternalError();
+ }
+ }
+
+
+ private abstract static class Abstract
+ {
+ public Abstract()
+ {
+ // Do nothing.
+ }
+ }
+
+
+ private static class Unconstructable
+ {
+ private Unconstructable()
+ {
+ // Do nothing.
+ }
+ }
+
+
+ private static class Inaccessible
+ {
+ private void inaccessibleMethod()
+ {
+ // Do nothing.
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java
new file mode 100644
index 0000000..f77bcff
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/CachingFitnessEvaluatorTest.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the {@link CachingFitnessEvaluator} wrapper.
+ * @author Daniel Dyer
+ */
+public class CachingFitnessEvaluatorTest
+{
+ @Test
+ public void testCacheMiss()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ double fitness = evaluator.getFitness("Test1", Collections.<String>emptyList());
+ assert fitness == 1 : "Wrong fitness: " + fitness;
+ // Different candidate so shouldn't return a cached value.
+ fitness = evaluator.getFitness("Test2", Collections.<String>emptyList());
+ assert fitness == 2 : "Wrong fitness: " + fitness;
+
+ }
+
+
+ @Test
+ public void testCacheHit()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ double fitness = evaluator.getFitness("Test", Collections.<String>emptyList());
+ assert fitness == 1 : "Wrong fitness: " + fitness;
+ fitness = evaluator.getFitness("Test", Collections.<String>emptyList());
+ // If the value is found in the cache it won't have changed. If it is recalculated, it will have.
+ assert fitness == 1 : "Expected cached value (1), got " + fitness;
+ }
+
+
+ @Test
+ public void testNatural()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(true));
+ assert evaluator.isNatural() : "Wrapper for natural scores should also be natural.";
+ }
+
+
+ @Test
+ public void testNonNatural()
+ {
+ FitnessEvaluator<String> evaluator = new CachingFitnessEvaluator<String>(new IncrementingEvaluator(false));
+ assert !evaluator.isNatural() : "Wrapper for non-natural scores should also be non-natural.";
+ }
+
+
+ /**
+ * This breaks the rules for the caching evaluator in that it is not repeatable
+ * (it returns different values when invoked multiple times for the same candidate),
+ * but it allows us to see whether we are getting a cached value or a new value.
+ */
+ private static final class IncrementingEvaluator implements FitnessEvaluator<String>
+ {
+ private final boolean natural;
+ private int count = 0;
+
+ IncrementingEvaluator(boolean natural)
+ {
+ this.natural = natural;
+ }
+
+ public double getFitness(String candidate, List<? extends String> population)
+ {
+ return ++count;
+ }
+
+ public boolean isNatural()
+ {
+ return natural;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java
new file mode 100644
index 0000000..c0d74d4
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvaluatedCandidateTest.java
@@ -0,0 +1,102 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import org.testng.annotations.Test;
+
+/**
+ * Unit test for the simple {@link EvaluatedCandidate} class. Ensures that
+ * the equals and hashCode methods function correctly.
+ * @author Daniel Dyer
+ */
+public class EvaluatedCandidateTest
+{
+ @Test
+ public void testEquality()
+ {
+ // Equality is determined only by fitness score, the actual candidate
+ // representation is irrelevant. These two candidates should be considered
+ // equal.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("BBBB", 5);
+
+ assert candidate1.equals(candidate1) : "Equality must be reflexive.";
+ assert candidate2.equals(candidate2) : "Equality must be reflexive.";
+
+ assert candidate1.hashCode() == candidate2.hashCode() : "Hash codes must be identical for equal objects.";
+ assert candidate1.compareTo(candidate2) == 0 : "compareTo() must be consistent with equals()";
+
+ assert candidate1.equals(candidate2) : "Candidates with equal fitness should be equal.";
+ assert candidate2.equals(candidate1) : "Equality must be symmetric.";
+ }
+
+
+ @Test
+ public void testNotEqual()
+ {
+ // Equality is determined only by fitness score, the actual candidate
+ // representation is irrelevant. These two candidates should be considered
+ // unequal.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("AAAA", 7);
+
+ assert !candidate1.equals(candidate2) : "Candidates with equal fitness should be equal.";
+ assert !candidate2.equals(candidate1) : "Equality must be symmetric.";
+
+ assert candidate1.compareTo(candidate2) != 0 : "compareTo() must be consistent with equals()";
+ }
+
+
+ @Test
+ public void testNullEquality()
+ {
+ EvaluatedCandidate<String> candidate = new EvaluatedCandidate<String>("AAAA", 5);
+ assert !candidate.equals(null) : "Object must not be considered equal to null reference.";
+ }
+
+
+ @Test
+ public void testDifferentClassEquality()
+ {
+ EvaluatedCandidate<String> candidate = new EvaluatedCandidate<String>("AAAA", 5);
+ assert !candidate.equals(new Object()) : "Object must not be equal to instances of other classes.";
+ }
+
+
+ /**
+ * Comparisons are based only on fitness score.
+ */
+ @Test(dependsOnMethods = "testEquality")
+ public void testComparisons()
+ {
+ // Only test greater than and less than comparisons here since we've already
+ // done equality.
+ EvaluatedCandidate<String> candidate1 = new EvaluatedCandidate<String>("AAAA", 5);
+ EvaluatedCandidate<String> candidate2 = new EvaluatedCandidate<String>("AAAA", 7);
+ assert candidate1.compareTo(candidate2) < 0 : "Incorrect ordering.";
+ assert candidate2.compareTo(candidate1) > 0 : "Incorrect ordering.";
+ }
+
+
+ /**
+ * Negative fitness scores are not supported. An informative exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNegativeFitness()
+ {
+ new EvaluatedCandidate<String>("ABC", -1); // Should throw an exception.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java
new file mode 100644
index 0000000..f07c354
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/EvolutionStrategyEngineTest.java
@@ -0,0 +1,68 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+
+/**
+ * Unit test for {@link EvolutionStrategyEngine} class.
+ * @author Daniel Dyer
+ */
+public class EvolutionStrategyEngineTest
+{
+ @Test
+ public void testOnePlusOneEvolutionStrategy()
+ {
+ EvolutionStrategyEngine<Integer> engine = new EvolutionStrategyEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(-1),
+ new IntegerEvaluator(),
+ true,
+ 1,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1));
+
+ List<EvaluatedCandidate<Integer>> evolvedPopulation
+ = engine.nextEvolutionStep(population, 0, FrameworkTestUtils.getRNG());
+ assert evolvedPopulation.size() == 1 : "Population size should be 1, is " + evolvedPopulation.size();
+ // The offspring is less fit than the parent (due to the -1 operator) so the parent should be retained.
+ assert evolvedPopulation.get(0).getCandidate() == 1 : "Wrong individual after evolution.";
+ }
+
+
+ @Test
+ public void testOneCommaOneEvolutionStrategy()
+ {
+ EvolutionStrategyEngine<Integer> engine = new EvolutionStrategyEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(-1),
+ new IntegerEvaluator(),
+ false,
+ 1,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1));
+
+ List<EvaluatedCandidate<Integer>> evolvedPopulation
+ = engine.nextEvolutionStep(population, 0, FrameworkTestUtils.getRNG());
+ assert evolvedPopulation.size() == 1 : "Population size should be 1, is " + evolvedPopulation.size();
+ // The offspring is less fit than the parent (due to the -1 operator) but the parent is not allowed to survive.
+ assert evolvedPopulation.get(0).getCandidate() == 0 : "Wrong individual after evolution.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java
new file mode 100644
index 0000000..316cd85
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/FrameworkTestUtils.java
@@ -0,0 +1,48 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Random;
+import org.uncommons.maths.random.XORShiftRNG;
+
+/**
+ * Utility methods for Watchmaker Framework unit tests. Provides
+ * access to shared resources used by tests.
+ * @author Daniel Dyer
+ */
+public final class FrameworkTestUtils
+{
+ private static final Random RNG = new XORShiftRNG();
+
+ private FrameworkTestUtils()
+ {
+ // Prevent instantiation.
+ }
+
+
+ /**
+ * Returns the singleton RNG shared by all tests. It might be preferable
+ * to have a separate RNG for each test (for true separation) but this
+ * causes problems. Seeding dozens of RNGs can exhaust the system's
+ * available entropy (the Uncommons Maths RNGs seed themselves from
+ * /dev/random by default).
+ * @return A random number generator.
+ */
+ public static Random getRNG()
+ {
+ return RNG;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java
new file mode 100644
index 0000000..6622229
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/GenerationalEvolutionEngineTest.java
@@ -0,0 +1,169 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.ElapsedTime;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * Unit test for the {@link GenerationalEvolutionEngine} class.
+ * @author Daniel Dyer
+ */
+public class GenerationalEvolutionEngineTest
+{
+ private EvolutionEngine<Integer> engine;
+
+
+ @BeforeMethod
+ public void prepareEngine()
+ {
+ this.engine = new GenerationalEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerZeroMaker(),
+ new IntegerEvaluator(),
+ new RouletteWheelSelection(),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ @Test
+ public void testElitism()
+ {
+ class ElitismObserver implements EvolutionObserver<Integer>
+ {
+ private PopulationData<? extends Integer> data;
+
+ public void populationUpdate(PopulationData<? extends Integer> data)
+ {
+ this.data = data;
+ }
+
+ public double getAverageFitness()
+ {
+ return data.getMeanFitness();
+ }
+ }
+ ElitismObserver observer = new ElitismObserver();
+ engine.addEvolutionObserver(observer);
+ List<Integer> elite = new ArrayList<Integer>(3);
+ // Add the following seed candidates, all better than any others that can possibly
+ // get into the population (since every other candidate will always be zero).
+ elite.add(7); // This candidate should be discarded by elitism.
+ elite.add(11);
+ elite.add(13);
+ engine.evolve(10,
+ 2,
+ elite,
+ new GenerationCount(2)); // Do at least 2 generations because the first is just the initial population.
+ // Then when we have run the evolution, if the elite canidates were preserved they will
+ // lift the average fitness above zero. The exact value of the expected average fitness
+ // is easy to calculate, it is the aggregate fitness divided by the population size.
+ assert observer.getAverageFitness() == 24d / 10 : "Elite candidates not preserved correctly: " + observer.getAverageFitness();
+ engine.removeEvolutionObserver(observer);
+ }
+
+
+ /**
+ * The number of candidates preserved by elitism must be less than the total
+ * population size.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEliteCountTooHigh()
+ {
+ engine.evolve(10, 10, new GenerationCount(10)); // Should throw exception because elite is too big.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEliteCountTooLow()
+ {
+ engine.evolvePopulation(10, -1, new GenerationCount(10)); // Should throw exception because elite is negative.
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNoTerminationConditions()
+ {
+ engine.evolve(10, 0); // Should throw exception because there are no termination conditions.
+ }
+
+
+ @Test
+ public void testInterrupt()
+ {
+ final long timeout = 1000L;
+ final Thread requestThread = Thread.currentThread();
+ engine.addEvolutionObserver(new EvolutionObserver<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ if (populationData.getElapsedTime() > timeout / 2)
+ {
+ requestThread.interrupt();
+ }
+ }
+ });
+ long startTime = System.currentTimeMillis();
+ engine.evolve(10, 0, new ElapsedTime(timeout));
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ assert Thread.interrupted() : "Thread was not interrupted before timeout.";
+ assert elapsedTime < timeout : "Engine did not respond to interrupt before timeout.";
+ assert engine.getSatisfiedTerminationConditions().isEmpty()
+ : "Interrupted engine should have no satisfied termination conditions.";
+ }
+
+
+ @Test
+ public void testGetSatisfiedTerminationConditions()
+ {
+ GenerationCount generationsCondition = new GenerationCount(1);
+ engine.evolve(10, 0, generationsCondition);
+ List<TerminationCondition> satisfiedConditions = engine.getSatisfiedTerminationConditions();
+ assert satisfiedConditions.size() == 1 : "Wrong number of conditions: " + satisfiedConditions.size();
+ assert satisfiedConditions.get(0) == generationsCondition : "Wrong condition returned.";
+ }
+
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testGetSatisfiedTerminationConditionsBeforeStart()
+ {
+ // Should throw an IllegalStateException because evolution hasn't started, let alone terminated.
+ engine.getSatisfiedTerminationConditions();
+ }
+
+
+ /**
+ * Trivial test operator that mutates all integers into zeroes.
+ */
+ private static final class IntegerZeroMaker implements EvolutionaryOperator<Integer>
+ {
+ public List<Integer> apply(List<Integer> selectedCandidates, Random rng)
+ {
+ List<Integer> result = new ArrayList<Integer>(selectedCandidates.size());
+ for (int i = 0; i < selectedCandidates.size(); i++)
+ {
+ result.add(0);
+ }
+ return result;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java
new file mode 100644
index 0000000..fc54bcb
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/IntegerEvaluator.java
@@ -0,0 +1,37 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.List;
+
+/**
+ * Trivial fitness evaluator for integers. Used by unit tests.
+ * @author Daniel Dyer
+ */
+final class IntegerEvaluator implements FitnessEvaluator<Integer>
+{
+
+ public double getFitness(Integer candidate,
+ List<? extends Integer> population)
+ {
+ return candidate;
+ }
+
+ public boolean isNatural()
+ {
+ return true;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java
new file mode 100644
index 0000000..6678269
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/SteadyStateEvolutionEngineTest.java
@@ -0,0 +1,138 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+
+/**
+ * Unit test for the {@link SteadyStateEvolutionEngine} class.
+ * @author Daniel Dyer
+ */
+public class SteadyStateEvolutionEngineTest
+{
+ /**
+ * A single iteration should update only a single candidate.
+ */
+ @Test
+ public void testIncrementalEvolution()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(5),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 1,
+ true,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0));
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 0,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ int unchangedCount = 0;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 1)
+ {
+ ++unchangedCount;
+ }
+ }
+ assert unchangedCount == 4 : "Should be 4 out of 5 candidates unchanged, is " + unchangedCount;
+ }
+
+
+ /**
+ * Even if the evolutionary operator generates multiple offspring, only a single individual should be
+ * replaced if the forceSingleUpdate flag is set.
+ */
+ @Test
+ public void testForcedSingleCandidateUpdate()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(5),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 2,
+ true, // Force single update.
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0),
+ new EvaluatedCandidate<Integer>(1, 0));
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 0,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ int unchangedCount = 0;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 1)
+ {
+ ++unchangedCount;
+ }
+ }
+ assert unchangedCount == 4 : "Should be 4 out of 5 candidates unchanged, is " + unchangedCount;
+ }
+
+
+
+ @Test
+ public void testElitism()
+ {
+ SteadyStateEvolutionEngine<Integer> steadyState = new SteadyStateEvolutionEngine<Integer>(new StubIntegerFactory(),
+ new IntegerAdjuster(10),
+ new NullFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ 1,
+ true,
+ FrameworkTestUtils.getRNG());
+ @SuppressWarnings("unchecked")
+ List<EvaluatedCandidate<Integer>> population = Arrays.asList(new EvaluatedCandidate<Integer>(1, 1),
+ new EvaluatedCandidate<Integer>(2, 2),
+ new EvaluatedCandidate<Integer>(3, 3),
+ new EvaluatedCandidate<Integer>(4, 4),
+ new EvaluatedCandidate<Integer>(5, 5));
+ // The fittest candidate should always be preserved.
+ for (int i = 0; i < 20; i++) // Once is not enough to be confident.
+ {
+ List<EvaluatedCandidate<Integer>> evaluatedPopulation = steadyState.nextEvolutionStep(population,
+ 1,
+ FrameworkTestUtils.getRNG());
+ assert evaluatedPopulation.size() == 5 : "Population size should be unchanged.";
+ boolean found = false;
+ for (EvaluatedCandidate<Integer> candidate : evaluatedPopulation)
+ {
+ if (candidate.getCandidate() == 5)
+ {
+ found = true;
+ break;
+ }
+ }
+ assert found : "Elite candidate should be preserved.";
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java
new file mode 100644
index 0000000..0e06412
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/BitStringFactoryTest.java
@@ -0,0 +1,93 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for bit string candidate factory.
+ * @author Daniel Dyer
+ */
+public class BitStringFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ List<BitString> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ validatePopulation(population);
+ }
+
+
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ BitString seed1 = new BitString("1111100000");
+ BitString seed2 = new BitString("1010101010");
+ List<BitString> seeds = new ArrayList<BitString>(2);
+ seeds.add(seed1);
+ seeds.add(seed2);
+ List<BitString> population = factory.generateInitialPopulation(populationSize,
+ seeds,
+ FrameworkTestUtils.getRNG());
+
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<BitString> factory = new BitStringFactory(candidateLength);
+ BitString candidate = new BitString(candidateLength);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList(candidate, candidate, candidate),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ private void validatePopulation(List<BitString> population)
+ {
+ // Make sure the correct number of candidates were generated.
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ // Make sure that each individual is the right length.
+ for (BitString bitString : population)
+ {
+ assert bitString.getLength() == candidateLength : "Bit string is wrong length: " + bitString.getLength();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java
new file mode 100644
index 0000000..b2df2e9
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ListPermutationFactoryTest.java
@@ -0,0 +1,125 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the list permutation candidate factory. Checks that it
+ * correctly generates populations of permutations.
+ * @author Daniel Dyer
+ */
+public class ListPermutationFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+ private final List<Integer> elements = new ArrayList<Integer>(candidateLength);
+ {
+ for (int i = 1; i <= candidateLength; i++)
+ {
+ elements.add(i);
+ }
+ }
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+ List<List<Integer>> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+
+ List<Integer> seed1 = new ArrayList<Integer>(elements);
+ List<Integer> seed2 = new ArrayList<Integer>(elements);
+ Collections.reverse(elements);
+ List<List<Integer>> seeds = new ArrayList<List<Integer>>(2);
+ seeds.add(seed1);
+ seeds.add(seed2);
+
+ List<List<Integer>> population = factory.generateInitialPopulation(populationSize,
+ seeds,
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<List<Integer>> factory = new ListPermutationFactory<Integer>(elements);
+
+ List<List<Integer>> seeds = new LinkedList<List<Integer>>();
+ seeds.add(elements);
+ seeds.add(elements);
+ seeds.add(elements);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2, seeds, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Make sure each candidate is valid (contains each element exactly once).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<List<Integer>> population)
+ {
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ for (List<Integer> candidate : population)
+ {
+ assert candidate.size() == candidateLength : "Wrong size candidate generated: " + candidate.size();
+ for (int i = 1; i < candidateLength; i++)
+ {
+ assert candidate.contains(i) : "Candidate is missing element " + i + ".";
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java
new file mode 100644
index 0000000..e4145c1
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/ObjectArrayPermutationFactoryTest.java
@@ -0,0 +1,126 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the array permutation candidate factory. Checks that it
+ * correctly generates populations of permutations.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayPermutationFactoryTest
+{
+ private final int candidateLength = 10;
+ private final int populationSize = 5;
+ private final Integer[] elements = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+ List<Integer[]> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+
+ Integer[] seed1 = elements.clone();
+ Integer[] seed2 = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
+
+ List<Integer[]> population = factory.generateInitialPopulation(populationSize,
+ Arrays.asList(seed1, seed2),
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<Integer[]> factory = new ObjectArrayPermutationFactory<Integer>(elements);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList(elements, elements, elements),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Make sure each candidate is valid (contains each element exactly once).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<Integer[]> population)
+ {
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+ for (Integer[] candidate : population)
+ {
+ assert candidate.length == candidateLength : "Wrong size candidate generated: " + candidate.length;
+ for (int i = 1; i < candidateLength; i++)
+ {
+ assert contains(candidate, i) : "Candidate is missing element " + i + ".";
+ }
+ }
+ }
+
+
+ /**
+ * Check whether the specified element occurs in the specified array.
+ */
+ private boolean contains(Integer[] array, int element)
+ {
+ for (int i : array)
+ {
+ if (i == element)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java
new file mode 100644
index 0000000..350a024
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StringFactoryTest.java
@@ -0,0 +1,112 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * @author Daniel Dyer
+ */
+public class StringFactoryTest
+{
+ private final int candidateLength = 8;
+ private final int populationSize = 5;
+
+ private final char[] alphabet = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'};
+
+
+ /**
+ * Generate a completely random population. Checks to make
+ * sure that the correct number of candidate solutions is
+ * generated and that each is valid.
+ */
+ @Test
+ public void testUnseededPopulation()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+ List<String> population = factory.generateInitialPopulation(populationSize, FrameworkTestUtils.getRNG());
+ assert population.size() == populationSize : "Wrong size population generated: " + population.size();
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * Generate a random population with some seed candidates. Checks to make
+ * sure that the correct number of candidate solutions is generated and that
+ * each is valid.
+ */
+ @Test
+ public void testSeededPopulation()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+
+ String seed1 = "cdefghij";
+ String seed2 = "bbbbbbbb";
+
+ List<String> population = factory.generateInitialPopulation(populationSize,
+ Arrays.asList(seed1, seed2),
+ FrameworkTestUtils.getRNG());
+ // Check that the seed candidates appear in the generated population.
+ assert population.contains(seed1) : "Population does not contain seed candidate 1.";
+ assert population.contains(seed2) : "Population does not contain seed candidate 2.";
+
+ validatePopulation(population);
+ }
+
+
+ /**
+ * It is an error if the number of seed candidates is greater than the
+ * population size. In this case an exception should be thrown. Not
+ * throwing an exception is wrong because it would permit undetected bugs
+ * in programs that use the factory.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testTooManySeedCandidates()
+ {
+ CandidateFactory<String> factory = new StringFactory(alphabet, candidateLength);
+ // The following call should cause an exception since the 3 seed candidates
+ // won't fit into a population of size 2.
+ factory.generateInitialPopulation(2,
+ Arrays.asList("abcdefgh", "ijklmnop", "qrstuvwx"),
+ FrameworkTestUtils.getRNG());
+ }
+
+
+
+
+ /**
+ * Make sure each candidate is valid (is the right length and contains only
+ * valid characters).
+ * @param population The population to be validated.
+ */
+ private void validatePopulation(List<String> population)
+ {
+ for (String candidate : population)
+ {
+ assert candidate.length() == candidateLength : "Wrong length candidate: " + candidate.length();
+ for (char c : candidate.toCharArray())
+ {
+ assert c >= 'a' && c <= 'j' : "Invalid character: " + c;
+ }
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java
new file mode 100644
index 0000000..ecfe422
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/factories/StubIntegerFactory.java
@@ -0,0 +1,30 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.factories;
+
+import java.util.Random;
+
+/**
+ * Stub candidate factory for tests. Always returns zero-valued integers.
+ * @author Daniel Dyer
+ */
+public final class StubIntegerFactory extends AbstractCandidateFactory<Integer>
+{
+ public Integer generateRandomCandidate(Random rng)
+ {
+ return 0;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java
new file mode 100644
index 0000000..a3f26b6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/InteractiveSelectionTest.java
@@ -0,0 +1,146 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for user-guided selection strategy.
+ * @author Daniel Dyer
+ */
+public class InteractiveSelectionTest
+{
+ @Test
+ public void testSingleSelectionPerGeneration()
+ {
+ final int groupSize = 2;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 1);
+ List<EvaluatedCandidate<Integer>> population = new ArrayList<EvaluatedCandidate<Integer>>(5);
+ population.add(new EvaluatedCandidate<Integer>(1, 0));
+ population.add(new EvaluatedCandidate<Integer>(2, 0));
+ population.add(new EvaluatedCandidate<Integer>(3, 0));
+ population.add(new EvaluatedCandidate<Integer>(4, 0));
+ population.add(new EvaluatedCandidate<Integer>(5, 0));
+
+ List<Integer> selection = strategy.select(population, true, 3, FrameworkTestUtils.getRNG());
+ assert selection.size() == 3 : "Incorrect selection size: " + selection.size();
+ assert console.getSelectionCount() == 1 : "Wrong number of user selections: " + console.getSelectionCount();
+ // All 3 selected individuals should be the same since the strategy doubles up
+ // selections when configured to restrict the number of user choices per generation.
+ assert selection.get(0).equals(selection.get(1)) : "Incorrect selection.";
+ assert selection.get(1).equals(selection.get(2)) : "Incorrect selection.";
+ assert selection.get(0).equals(selection.get(2)) : "Incorrect selection.";
+ }
+
+
+ @Test
+ public void testMultipleSelectionsPerGeneration()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 3);
+ List<EvaluatedCandidate<Integer>> population = new ArrayList<EvaluatedCandidate<Integer>>(5);
+ population.add(new EvaluatedCandidate<Integer>(1, 0));
+ population.add(new EvaluatedCandidate<Integer>(2, 0));
+ population.add(new EvaluatedCandidate<Integer>(3, 0));
+ population.add(new EvaluatedCandidate<Integer>(4, 0));
+ population.add(new EvaluatedCandidate<Integer>(5, 0));
+
+ List<Integer> selection = strategy.select(population, true, 3, FrameworkTestUtils.getRNG());
+ assert selection.size() == 3 : "Incorrect selection size.";
+ assert console.getSelectionCount() == 3 : "Wrong number of user selections: " + console.getSelectionCount();
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidMaxSelections()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ // This should throw an exception because max selections must be at least 1.
+ new InteractiveSelection<Integer>(console, groupSize, 0);
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testInvalidGroupSize()
+ {
+ final int groupSize = 1;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ // This should throw an exception because group size must be at least 2.
+ new InteractiveSelection<Integer>(console, groupSize, 1);
+ }
+
+
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testGroupSizeTooBigForPopulation()
+ {
+ final int groupSize = 5;
+ RandomConsole<Integer> console = new RandomConsole<Integer>(groupSize);
+ SelectionStrategy<Integer> strategy = new InteractiveSelection<Integer>(console,
+ groupSize,
+ 1);
+ List<EvaluatedCandidate<Integer>> population = new LinkedList<EvaluatedCandidate<Integer>>();
+ population.add(new EvaluatedCandidate<Integer>(1, 1.0));
+ population.add(new EvaluatedCandidate<Integer>(1, 2.0));
+ // This should fail because a population of 2 is not big enough with a
+ // group size of 5.
+ strategy.select(population, true, 2, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Automated test console implementation that simply selects an
+ * individual at random.
+ */
+ private final class RandomConsole<T> implements Console<T>
+ {
+ private final int expectedGroupSize;
+
+ /** Count how many times the select method is called. */
+ private int selectionCount = 0;
+
+ RandomConsole(int expectedGroupSize)
+ {
+ this.expectedGroupSize = expectedGroupSize;
+ }
+
+
+ public int select(List<? extends T> renderedEntities)
+ {
+ assert renderedEntities.size() == expectedGroupSize : "Wrong selection group size.";
+ ++selectionCount;
+ return FrameworkTestUtils.getRNG().nextInt(renderedEntities.size());
+ }
+
+
+ public int getSelectionCount()
+ {
+ return selectionCount;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java
new file mode 100644
index 0000000..717f65e
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/interactive/RendererAdapterTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.interactive;
+
+import java.util.Date;
+import org.testng.annotations.Test;
+
+/**
+ * Unit test to ensure that renderer chaining works correctly.
+ * @author Daniel Dyer
+ */
+public class RendererAdapterTest
+{
+ @Test
+ public void testChaining()
+ {
+ Renderer<Long, Date> longToDate = new TimestampToDateRenderer();
+ Renderer<Date, String> dateToString = new DateToStringRenderer();
+
+ long currentTime = System.currentTimeMillis();
+ Date date = longToDate.render(currentTime);
+ String expectedOutput = dateToString.render(date);
+
+ Renderer<Long, String> longToString = new RendererAdapter<Long, String>(longToDate,
+ dateToString);
+ String actualOutput = longToString.render(currentTime);
+ assert actualOutput.equals(expectedOutput) : "Actual/expected output mismatch: " + actualOutput;
+ }
+
+
+ /**
+ * Example renderer for converting a number of milliseconds since 00:00 on 1st
+ * January 1970 into a Java {@link Date} object.
+ */
+ private static final class TimestampToDateRenderer implements Renderer<Long, Date>
+ {
+ public Date render(Long timestamp)
+ {
+ return new Date(timestamp);
+ }
+ }
+
+
+ /**
+ * Example renderer for converting a Java {@link Date} object into a date String.
+ */
+ private static final class DateToStringRenderer implements Renderer<Date, String>
+ {
+ public String render(Date date)
+ {
+ return date.toString();
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java
new file mode 100644
index 0000000..98fc00d
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/IslandEvolutionTest.java
@@ -0,0 +1,143 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FitnessEvaluator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.factories.StubIntegerFactory;
+import org.uncommons.watchmaker.framework.operators.IntegerAdjuster;
+import org.uncommons.watchmaker.framework.selection.RouletteWheelSelection;
+import org.uncommons.watchmaker.framework.termination.ElapsedTime;
+import org.uncommons.watchmaker.framework.termination.GenerationCount;
+
+/**
+ * Unit test for the {@link IslandEvolution} class.
+ * @author Daniel Dyer
+ */
+public class IslandEvolutionTest
+{
+ /**
+ * This test makes sure that the evolution observer global method only gets invoked at
+ * the end of each epoch, and that the island method gets invoked for each generation on each
+ * island.
+ */
+ @Test
+ public void testListeners()
+ {
+ final int islandCount = 3;
+ final int epochCount = 2;
+ final int generationCount = 5;
+
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(islandCount,
+ new RingMigration(),
+ new StubIntegerFactory(),
+ new IntegerAdjuster(2),
+ new DummyFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ FrameworkTestUtils.getRNG());
+ final int[] observedEpochCount = new int[1];
+ final int[] observedGenerationCounts = new int[islandCount];
+
+ islandEvolution.addEvolutionObserver(new IslandEvolutionObserver<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ observedEpochCount[0]++;
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends Integer> populationData)
+ {
+ observedGenerationCounts[islandIndex]++;
+ }
+ });
+ islandEvolution.evolve(5, 0, 5, 0, new GenerationCount(2));
+ assert observedEpochCount[0] == 2 : "Listener should have been notified twice, was " + observedEpochCount[0];
+ for (int i = 0; i < islandCount; i++)
+ {
+ int expected = epochCount * generationCount;
+ assert observedGenerationCounts[i] == expected
+ : "Genertion count for island " + i + " should be " + expected + ", is " + observedGenerationCounts[i];
+ }
+ }
+
+
+ @Test
+ public void testInterrupt()
+ {
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(2,
+ new RingMigration(),
+ new StubIntegerFactory(),
+ new IntegerAdjuster(2),
+ new DummyFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ FrameworkTestUtils.getRNG());
+ final long timeout = 1000L;
+ final Thread requestThread = Thread.currentThread();
+ islandEvolution.addEvolutionObserver(new IslandEvolutionObserver<Integer>()
+ {
+ public void populationUpdate(PopulationData<? extends Integer> populationData)
+ {
+ if (populationData.getElapsedTime() > timeout / 2)
+ {
+ requestThread.interrupt();
+ }
+ }
+
+
+ public void islandPopulationUpdate(int islandIndex, PopulationData<? extends Integer> populationData){}
+ });
+ long startTime = System.currentTimeMillis();
+ islandEvolution.evolve(10, 0, 10, 0, new ElapsedTime(timeout));
+ long elapsedTime = System.currentTimeMillis() - startTime;
+ assert Thread.interrupted() : "Thread was not interrupted before timeout.";
+ assert elapsedTime < timeout : "Engine did not respond to interrupt before timeout.";
+ assert islandEvolution.getSatisfiedTerminationConditions().isEmpty()
+ : "Interrupted islands should have no satisfied termination conditions.";
+ }
+
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public void testGetSatisfiedTerminationConditionsBeforeStart()
+ {
+ IslandEvolution<Integer> islandEvolution = new IslandEvolution<Integer>(3,
+ new RingMigration(),
+ new StubIntegerFactory(),
+ new IntegerAdjuster(2),
+ new DummyFitnessEvaluator(),
+ new RouletteWheelSelection(),
+ FrameworkTestUtils.getRNG());
+ // Should throw an IllegalStateException because evolution has started, let alone terminated.
+ islandEvolution.getSatisfiedTerminationConditions();
+ }
+
+
+ private static class DummyFitnessEvaluator implements FitnessEvaluator<Integer>
+ {
+ public double getFitness(Integer candidate, List<? extends Integer> population)
+ {
+ return 0;
+ }
+
+ public boolean isNatural()
+ {
+ return true;
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java
new file mode 100644
index 0000000..3564a31
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/MigrationTestUtils.java
@@ -0,0 +1,55 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Utility methods used by unit tests for migration strategies.
+ * @author Daniel Dyer
+ */
+class MigrationTestUtils
+{
+ private MigrationTestUtils()
+ {
+ // Prevents instantiation.
+ }
+
+
+ public static <T> List<EvaluatedCandidate<T>> createTestPopulation(T... members)
+ {
+ List<EvaluatedCandidate<T>> population = new ArrayList<EvaluatedCandidate<T>>(members.length);
+ for (T member : members)
+ {
+ population.add(new EvaluatedCandidate<T>(member, 0));
+ }
+ return population;
+ }
+
+
+ public static void testPopulationContents(List<EvaluatedCandidate<String>> actualPopulation,
+ String... expectedPopulation)
+ {
+ assert actualPopulation.size() == expectedPopulation.length : "Wrong population size after migration.";
+ for (int i = 0; i < actualPopulation.size(); i++)
+ {
+ assert actualPopulation.get(i).getCandidate().equals(expectedPopulation[i]) : "Wrong value at index " + i;
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java
new file mode 100644
index 0000000..566f68b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RandomMigrationTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link RingMigration} class.
+ * @author Daniel Dyer
+ */
+public class RandomMigrationTest
+{
+ /**
+ * Make sure that nothing strange happens when there is no migration.
+ */
+ @Test
+ public void testZeroMigration()
+ {
+ Migration migration = new RandomMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 0, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3 : "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "B", "B", "B");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "C", "C", "C");
+ }
+
+
+ /**
+ * Make sure that nothing strange happens when the entire island is migrated.
+ */
+ @Test
+ public void testNonZeroMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 3, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3: "Wrong number of populations after migration.";
+ // Each population should still have 3 members (but it's not sure which members).
+ assert islandPopulations.get(0).size() == 3 : "Wrong population size.";
+ assert islandPopulations.get(1).size() == 3 : "Wrong population size.";
+ assert islandPopulations.get(2).size() == 3 : "Wrong population size.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java
new file mode 100644
index 0000000..ce86058
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/islands/RingMigrationTest.java
@@ -0,0 +1,67 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.islands;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link RingMigration} class.
+ * @author Daniel Dyer
+ */
+public class RingMigrationTest
+{
+ /**
+ * Make sure that nothing strange happens when there is no migration.
+ */
+ @Test
+ public void testZeroMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 0, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3 : "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "B", "B", "B");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "C", "C", "C");
+ }
+
+
+ /**
+ * Make sure that nothing strange happens when the entire island is migrated.
+ */
+ @Test
+ public void testFullMigration()
+ {
+ Migration migration = new RingMigration();
+ @SuppressWarnings("unchecked")
+ List<List<EvaluatedCandidate<String>>> islandPopulations = Arrays.asList(MigrationTestUtils.createTestPopulation("A", "A", "A"),
+ MigrationTestUtils.createTestPopulation("B", "B", "B"),
+ MigrationTestUtils.createTestPopulation("C", "C", "C"));
+ migration.migrate(islandPopulations, 3, FrameworkTestUtils.getRNG());
+ assert islandPopulations.size() == 3: "Wrong number of populations after migration.";
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(0), "C", "C", "C");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(1), "A", "A", "A");
+ MigrationTestUtils.testPopulationContents(islandPopulations.get(2), "B", "B", "B");
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java
new file mode 100644
index 0000000..58d4127
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringCrossoverTest.java
@@ -0,0 +1,70 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.watchmaker.framework.CandidateFactory;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.factories.BitStringFactory;
+
+/**
+ * Unit test for cross-over applied to bit strings.
+ * @author Daniel Dyer
+ */
+public class BitStringCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<BitString> operator = new BitStringCrossover();
+ CandidateFactory<BitString> factory = new BitStringFactory(50);
+ List<BitString> population = factory.generateInitialPopulation(2, FrameworkTestUtils.getRNG());
+ // Test to make sure that cross-over correctly preserves all genetic material
+ // originally present in the population and does not introduce anything new.
+ int totalSetBits = population.get(0).countSetBits() + population.get(1).countSetBits();
+ for (int i = 0; i < 50; i++) // Test several generations.
+ {
+ population = operator.apply(population, FrameworkTestUtils.getRNG());
+ int setBits = population.get(0).countSetBits() + population.get(1).countSetBits();
+ assert setBits == totalSetBits : "Total number of set bits in population changed during cross-over.";
+ }
+ }
+
+
+ /**
+ * The {@link BitStringCrossover} operator is only defined to work on populations
+ * containing Strings of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length Strings should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<BitString> crossover = new BitStringCrossover(new ConstantGenerator<Integer>(1));
+ List<BitString> population = new ArrayList<BitString>(2);
+ population.add(new BitString(32, FrameworkTestUtils.getRNG()));
+ population.add(new BitString(33, FrameworkTestUtils.getRNG()));
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java
new file mode 100644
index 0000000..ccc5d9b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/BitStringMutationTest.java
@@ -0,0 +1,72 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.binary.BitString;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for mutation of bit strings.
+ * @author Daniel Dyer
+ */
+public class BitStringMutationTest
+{
+ /**
+ * Ensures that mutation occurs correctly. Because of the random
+ * aspect we can't actually make many assertions. This just ensures
+ * that there are no unexpected exceptions and that the length of
+ * the bit strings remains as expected.
+ */
+ @Test
+ public void testRandomMutation()
+ {
+ EvolutionaryOperator<BitString> mutation = new BitStringMutation(Probability.EVENS);
+ BitString original = new BitString("111100101");
+ List<BitString> population = Arrays.asList(original);
+ for (int i = 0; i < 20; i++) // Perform several iterations to get different mutations.
+ {
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ BitString mutated = population.get(0);
+ assert mutated.getLength() == 9 : "Mutated bit string changed length.";
+ }
+ }
+
+
+ /**
+ * Ensures that mutation occurs correctly. Uses a probability of 1 to
+ * make the outcome predictable (all bits will be flipped).
+ */
+ @Test
+ public void testSingleBitMutation()
+ {
+ BitString original = new BitString("111100101");
+ EvolutionaryOperator<BitString> mutation = new BitStringMutation(Probability.ONE);
+ List<BitString> population = Arrays.asList(original);
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ BitString mutated = population.get(0);
+ assert !mutated.equals(original) : "Mutation should be different from original.";
+ assert mutated.getLength() == 9 : "Mutated bit string changed length.";
+ int set = mutated.countSetBits();
+ int unset = mutated.countUnsetBits();
+ assert set == 5 || set == 7 : "Mutated bit string has wrong number of 1s: " + set;
+ assert unset == 2 || unset == 4 : "Mutated bit string has wrong number of 0s: " + unset;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java
new file mode 100644
index 0000000..82a4b02
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ByteArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with byte arrays.
+ * @author Daniel Dyer
+ */
+public class ByteArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<byte[]> crossover = new ByteArrayCrossover();
+ List<byte[]> population = new ArrayList<byte[]>(4);
+ population.add(new byte[]{1, 2, 3, 4, 5});
+ population.add(new byte[]{6, 7, 8, 9, 10});
+ population.add(new byte[]{11, 12, 13, 14, 15});
+ population.add(new byte[]{16, 17, 18, 19, 20});
+ Set<Byte> values = new HashSet<Byte>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (byte[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (byte value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link ByteArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<byte[]> crossover = new ByteArrayCrossover(1, Probability.ONE);
+ List<byte[]> population = new ArrayList<byte[]>(2);
+ population.add(new byte[]{1, 2, 3, 4, 5});
+ population.add(new byte[]{2, 4, 8, 10, 12, 14, 16});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java
new file mode 100644
index 0000000..54e6c61
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/CharArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with character arrays.
+ * @author Daniel Dyer
+ */
+public class CharArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<char[]> crossover = new CharArrayCrossover();
+ List<char[]> population = new ArrayList<char[]>(4);
+ population.add(new char[]{'a', 'b', 'c', 'd', 'e'});
+ population.add(new char[]{'f', 'g', 'h', 'i', 'j'});
+ population.add(new char[]{'k', 'l', 'm', 'n', 'o'});
+ population.add(new char[]{'p', 'q', 'r', 's', 't'});
+ Set<Character> values = new HashSet<Character>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (char[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (char value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link CharArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<char[]> crossover = new CharArrayCrossover(1, Probability.ONE);
+ List<char[]> population = new ArrayList<char[]>(2);
+ population.add(new char[]{'a', 'b', 'c', 'd', 'e'});
+ population.add(new char[]{'f', 'g', 'h', 'i', 'j', 'k'});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java
new file mode 100644
index 0000000..9b46c61
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/DoubleArrayCrossoverTest.java
@@ -0,0 +1,82 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with floating point arrays.
+ * @author Daniel Dyer
+ */
+public class DoubleArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<double[]> crossover = new DoubleArrayCrossover(new ConstantGenerator<Integer>(1),
+ new ConstantGenerator<Probability>(Probability.ONE));
+ List<double[]> population = new ArrayList<double[]>(4);
+ population.add(new double[]{1.1d, 2.2d, 3.3d, 4.4d, 5.5d});
+ population.add(new double[]{6.6d, 7.7d, 8.8d, 9.9d, 10});
+ population.add(new double[]{11, 12, 13, 14, 15});
+ population.add(new double[]{16, 17, 18, 19, 20});
+ Set<Double> values = new HashSet<Double>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (double[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (double value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link DoubleArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<double[]> crossover = new DoubleArrayCrossover();
+ List<double[]> population = new ArrayList<double[]>(2);
+ population.add(new double[]{1.1d, 2.2d, 3.3d, 4.4d, 5.5d});
+ population.add(new double[]{6.6d, 7.7d, 8.8d});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java
new file mode 100644
index 0000000..469f6e6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/EvolutionPipelineTest.java
@@ -0,0 +1,69 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for compound, sequential evolutionary schemes.
+ * @author Daniel Dyer
+ */
+public class EvolutionPipelineTest
+{
+ /**
+ * Make sure that multiple operators in a pipeline are applied correctly
+ * to the population and validate the cumulative effects.
+ */
+ @Test
+ public void testCompoundEvolution()
+ {
+ List<Integer> population = new ArrayList<Integer>(10);
+ for (int i = 10; i <= 100; i += 10)
+ {
+ population.add(i);
+ }
+ // Increment 30% of the numbers and decrement the other 70%.
+ List<EvolutionaryOperator<Integer>> operators = new ArrayList<EvolutionaryOperator<Integer>>(2);
+ operators.add(new IntegerAdjuster(1));
+ operators.add(new IntegerAdjuster(3));
+ EvolutionPipeline<Integer> evolutionScheme = new EvolutionPipeline<Integer>(operators);
+ population = evolutionScheme.apply(population, FrameworkTestUtils.getRNG());
+ // Net result should be each candidate increased by 4.
+ int aggregate = 0;
+ for (Integer i : population)
+ {
+ aggregate += i;
+ assert (i % 10 == 4) : "Candidate should have increased by 4, is " + i;
+ }
+ assert aggregate == 590 : "Aggregate should be 590 after mutations, is " + aggregate;
+ }
+
+
+ /**
+ * An empty pipeline is not allowed. An exception should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testEmptyPipeline()
+ {
+ List<EvolutionaryOperator<String>> operators = Collections.emptyList();
+ new EvolutionPipeline<String>(operators); // Should throw an IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java
new file mode 100644
index 0000000..2ec9ef0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntArrayCrossoverTest.java
@@ -0,0 +1,80 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with integer arrays.
+ * @author Daniel Dyer
+ */
+public class IntArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<int[]> crossover = new IntArrayCrossover();
+ List<int[]> population = new ArrayList<int[]>(4);
+ population.add(new int[]{1, 2, 3, 4, 5});
+ population.add(new int[]{6, 7, 8, 9, 10});
+ population.add(new int[]{11, 12, 13, 14, 15});
+ population.add(new int[]{16, 17, 18, 19, 20});
+ Set<Integer> values = new HashSet<Integer>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (int[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (int value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link IntArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<int[]> crossover = new IntArrayCrossover(1, Probability.ONE);
+ List<int[]> population = new ArrayList<int[]>(2);
+ population.add(new int[]{1, 2, 3, 4, 5});
+ population.add(new int[]{2});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java
new file mode 100644
index 0000000..e90b4a9
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/IntegerAdjuster.java
@@ -0,0 +1,45 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+
+/**
+ * Trivial test operator that mutates all integers by adding a fixed offset.
+ * @author Daniel Dyer
+ */
+public final class IntegerAdjuster implements EvolutionaryOperator<Integer>
+{
+ private final int adjustment;
+
+ public IntegerAdjuster(int adjustment)
+ {
+ this.adjustment = adjustment;
+ }
+
+ public List<Integer> apply(List<Integer> selectedCandidates, Random rng)
+ {
+ List<Integer> result = new ArrayList<Integer>(selectedCandidates.size());
+ for (Integer i : selectedCandidates)
+ {
+ result.add(i + adjustment);
+ }
+ return result;
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java
new file mode 100644
index 0000000..3bfc297
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListCrossoverTest.java
@@ -0,0 +1,126 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with arbitrary lists.
+ * @author Daniel Dyer
+ */
+public class ListCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>();
+ List<List<Integer>> population = new ArrayList<List<Integer>>(4);
+ population.add(Arrays.asList(1, 2, 3, 4, 5));
+ population.add(Arrays.asList(6, 7, 8, 9, 10));
+ population.add(Arrays.asList(11, 12, 13, 14, 15));
+ population.add(Arrays.asList(16, 17, 18, 19, 20));
+ Set<Integer> values = new HashSet<Integer>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (List<Integer> individual : population)
+ {
+ assert individual.size() == 5 : "Invalid candidate length: " + individual.size();
+ values.addAll(individual);
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * When applied to lists of different lenghts, the {@link ListCrossover} operator
+ * should pick a cross-over point that exists in both lists. Therefore, the two
+ * offspring will be the lengths of the two parents.
+ */
+ @Test
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(1, Probability.ONE);
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
+ population.add(parent1);
+ List<Integer> parent2 = Arrays.asList(9, 10, 11);
+ population.add(parent2);
+
+ List<List<Integer>> offspring = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert offspring.size() == 2 : "Should be 2 offspring, is " + offspring.size();
+ // Should be 1 child of length 8 and one of length 3. Don't know which order though
+ // as parents are shuffled before cross-over is applied.
+ assert (offspring.get(0).size() == parent1.size() && offspring.get(1).size() == parent2.size())
+ || (offspring.get(0).size() == parent2.size() && offspring.get(1).size() == parent1.size())
+ : "Offspring are wrong lengths after cross-over.";
+ }
+
+
+ /**
+ * When the probability determines that no cross-over should be performed, the two parents
+ * should just be copied into the next generation unchanged.
+ */
+ @Test
+ public void testZeroProbability()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(new ConstantGenerator<Integer>(1),
+ new ConstantGenerator<Probability>(Probability.ZERO));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(4);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5);
+ List<Integer> parent2 = Arrays.asList(6, 7, 8, 9, 10);
+ population.add(parent1);
+ population.add(parent2);
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.contains(parent1) : "Parent should survive unaltered.";
+ assert population.contains(parent2) : "Parent should survive unaltered.";
+ }
+
+
+ /**
+ * If one or both of the parent lists has only one element, it can't participate in
+ * meaningful cross-over. In practice this situation is unlikely to occur (most
+ * programs won't be evolving single-element lists), but the operator should handle
+ * it gracefully.
+ */
+ @Test
+ public void testParentTooShort()
+ {
+ EvolutionaryOperator<List<Integer>> crossover = new ListCrossover<Integer>(new ConstantGenerator<Integer>(1));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ List<Integer> parent1 = Arrays.asList(1, 2, 3);
+ List<Integer> parent2 = Arrays.asList(4); // Too short for cross-over.
+ population.add(parent1);
+ population.add(parent2);
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.contains(parent1) : "Parent should survive unaltered.";
+ assert population.contains(parent2) : "Parent should survive unaltered.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java
new file mode 100644
index 0000000..0a5a6a7
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListInversionTest.java
@@ -0,0 +1,64 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link ListInversion} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ListInversionTest
+{
+ @Test
+ public void testZeroProbability()
+ {
+ EvolutionaryOperator<List<Integer>> inversion = new ListInversion<Integer>(Probability.ZERO);
+ @SuppressWarnings("unchecked")
+ List<List<Integer>> selection = Arrays.asList(Arrays.asList(1, 2, 3));
+ List<List<Integer>> evolvedSelection = inversion.apply(selection, FrameworkTestUtils.getRNG());
+ assert evolvedSelection.size() == 1 : "Wrong number of individuals after evolution: " + evolvedSelection.size();
+ assert evolvedSelection.get(0) == selection.get(0) : "Candidate should not have been modified.";
+ }
+
+
+ @Test
+ public void testInversion()
+ {
+ EvolutionaryOperator<List<Integer>> inversion = new ListInversion<Integer>(Probability.ONE);
+ @SuppressWarnings("unchecked")
+ List<List<Integer>> selection = Arrays.asList(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
+ for (int i = 0; i < 50; i++) // Try several times so that different random numbers are generated.
+ {
+ List<List<Integer>> evolvedSelection = inversion.apply(selection, FrameworkTestUtils.getRNG());
+
+ // After inversion, candidate should have same elements but not in the same order.
+ assert evolvedSelection.size() == 1 : "Wrong number of individuals after evolution: " + evolvedSelection.size();
+ assert evolvedSelection.get(0).size() == selection.get(0).size() : "Candidate length should be unchanged.";
+ assert !Arrays.deepEquals(evolvedSelection.get(0).toArray(), selection.get(0).toArray())
+ : "Candidate should have been modified.";
+ assert new HashSet<Integer>(evolvedSelection.get(0)).size() == 8
+ : "Evolved candidate should contain each element once.";
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java
new file mode 100644
index 0000000..4cd22f0
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOperatorTest.java
@@ -0,0 +1,58 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for the {@link ListOperator} high-order evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ListOperatorTest
+{
+ /**
+ * Make sure that the delegate operator is applied to each list in the
+ * population.
+ */
+ @Test
+ public void testApplication()
+ {
+ ListOperator<Integer> operator = new ListOperator<Integer>(new IntegerAdjuster(1));
+ List<List<Integer>> selection = new ArrayList<List<Integer>>(3);
+ selection.add(Arrays.asList(1, 2, 3));
+ selection.add(Arrays.asList(4, 5, 6));
+ selection.add(Arrays.asList(7, 8, 9));
+
+ List<List<Integer>> mutations = operator.apply(selection, FrameworkTestUtils.getRNG());
+ assert mutations.size() == 3 : "Wrong number of candidates after list operation: " + selection.size();
+
+ // Each element in each candidate list should have been incremented by the delegate operator.
+ for (int i = 0; i < selection.size(); i++)
+ {
+ List<Integer> original = selection.get(i);
+ List<Integer> mutation = mutations.get(i);
+ assert original.size() == mutation.size() : "Mutation size mismatch: " + mutation.size();
+ for (int j = 0; j < original.size(); j++)
+ {
+ assert mutation.get(j) == original.get(j) + 1 : "List value not mutated correctly.";
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java
new file mode 100644
index 0000000..22725b3
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderCrossoverTest.java
@@ -0,0 +1,75 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.number.ConstantGenerator;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test to validate the operation of the {@link ListOrderCrossover} operator.
+ * @author Daniel Dyer
+ */
+public class ListOrderCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<List<Integer>> operator = new ListOrderCrossover<Integer>();
+ List<Integer> parent1 = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
+ List<Integer> parent2 = Arrays.asList(3, 7, 5, 1, 6, 8, 2, 4);
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ population.add(parent1);
+ population.add(parent2);
+
+ for (int i = 0; i < 50; i++) // Do several cross-overs to check different cross-over points.
+ {
+ population = operator.apply(population, FrameworkTestUtils.getRNG());
+ for (List<Integer> offspring : population)
+ {
+ for (int j = 1; j <= 8; j++)
+ {
+ assert offspring.contains(j) : "Evolved candidate missing required element " + j;
+ }
+ }
+ }
+ }
+
+
+ /**
+ * The {@link ListOrderCrossover} operator is only defined to work on populations
+ * containing lists of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length lists should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<List<Integer>> crossover
+ = new ListOrderCrossover<Integer>(new ConstantGenerator<Probability>(Probability.ONE));
+ List<List<Integer>> population = new ArrayList<List<Integer>>(2);
+ population.add(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8));
+ population.add(Arrays.asList(9, 10, 11));
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java
new file mode 100644
index 0000000..6612d4b
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ListOrderMutationTest.java
@@ -0,0 +1,69 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.testng.Reporter;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test to validate the operation of the {@link ListOrderMutation} operator.
+ * @author Daniel Dyer
+ */
+public class ListOrderMutationTest
+{
+ @Test
+ public void testMutation()
+ {
+ ListOrderMutation<Character> operator = new ListOrderMutation<Character>();
+ List<Character> candidate = new ArrayList<Character>(5);
+ candidate.add('a');
+ candidate.add('b');
+ candidate.add('c');
+ candidate.add('d');
+ candidate.add('e');
+ List<List<Character>> population = new ArrayList<List<Character>>(1);
+ population.add(candidate);
+ List<List<Character>> mutatedPopulation = operator.apply(population, FrameworkTestUtils.getRNG());
+ assert mutatedPopulation.size() == population.size() : "Population size should be unchanged.";
+ List<Character> mutatedCandidate = mutatedPopulation.get(0);
+ Reporter.log("Original: " + Arrays.toString(candidate.toArray(new Character[candidate.size()])));
+ Reporter.log("Mutation: " + Arrays.toString(mutatedCandidate.toArray(new Character[mutatedCandidate.size()])));
+ assert mutatedCandidate.size() == candidate.size() : "Mutated candidate should be same length as original.";
+ // Mutated candidate should have same elements but in a different order.
+ int matchingPositions = 0;
+ for (int i = 0; i < candidate.size(); i++)
+ {
+ if (candidate.get(i).equals(mutatedCandidate.get(i)))
+ {
+ ++matchingPositions;
+ }
+ else
+ {
+ // If positions don't match, an adjacent character should be a match.
+ int nextPosition = (i + 1) % candidate.size();
+ int previousPosition = ((i - 1) + candidate.size()) % candidate.size();
+ boolean matchAdjacent = candidate.get(i).equals(mutatedCandidate.get(nextPosition))
+ ^ candidate.get(i).equals(mutatedCandidate.get(previousPosition));
+ assert matchAdjacent : "Mutated characters not in expected positions.";
+ }
+ }
+ assert matchingPositions == candidate.size() - 2 : "All but 2 positions should be unchanged.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java
new file mode 100644
index 0000000..04160d6
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ObjectArrayCrossoverTest.java
@@ -0,0 +1,81 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with arrays of objects.
+ * @author Daniel Dyer
+ */
+public class ObjectArrayCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<String[]> crossover = new ObjectArrayCrossover<String>();
+ List<String[]> population = new ArrayList<String[]>(4);
+ population.add(new String[]{"1", "2", "3", "4", "5"});
+ population.add(new String[]{"6", "7", "8", "9", "10"});
+ population.add(new String[]{"11", "12", "13", "14", "15"});
+ population.add(new String[]{"16", "17", "18", "19", "20"});
+ Set<String> values = new HashSet<String>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (String[] individual : population)
+ {
+ assert individual.length == 5 : "Invalid candidate length: " + individual.length;
+ for (String value : individual)
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link ObjectArrayCrossover} operator is only defined to work on populations
+ * containing arrays of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length arrays should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<String[]> crossover = new ObjectArrayCrossover<String>(1, Probability.ONE);
+ List<String[]> population = new ArrayList<String[]>(2);
+ population.add(new String[]{"1", "2", "3", "4", "5"});
+ population.add(new String[]{"6", "7", "8", "9", "10", "11", "12"});
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java
new file mode 100644
index 0000000..7a4ba59
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/ReplacementTest.java
@@ -0,0 +1,77 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.factories.AbstractCandidateFactory;
+
+/**
+ * Unit test for the {@link Replacement} evolutionary operator.
+ * @author Daniel Dyer
+ */
+public class ReplacementTest
+{
+ @Test
+ public void testReplacement()
+ {
+ IntegerFactory factory = new IntegerFactory();
+ List<Integer> candidates = Arrays.asList(10, 11, 12);
+ Replacement<Integer> replacement = new Replacement<Integer>(factory,
+ Probability.ONE);
+ // Numbers will be replaced with lower ones.
+ List<Integer> output = replacement.apply(candidates, FrameworkTestUtils.getRNG());
+ assert output.size() == candidates.size() : "Candidate list should be same size.";
+ assert !output.contains(10) : "Candidate should have been replaced.";
+ assert !output.contains(11) : "Candidate should have been replaced.";
+ assert !output.contains(12) : "Candidate should have been replaced.";
+ }
+
+
+ @Test
+ public void testZeroProbability()
+ {
+ IntegerFactory factory = new IntegerFactory();
+ List<Integer> candidates = Arrays.asList(10, 11, 12);
+ Replacement<Integer> replacement = new Replacement<Integer>(factory,
+ Probability.ZERO);
+ // Numbers will be replaced with lower ones.
+ List<Integer> output = replacement.apply(candidates, FrameworkTestUtils.getRNG());
+ assert output.size() == candidates.size() : "Candidate list should be same size.";
+ assert output.contains(10) : "Candidate should not have been replaced.";
+ assert output.contains(11) : "Candidate should not have been replaced.";
+ assert output.contains(12) : "Candidate should not have been replaced.";
+ }
+
+
+ /**
+ * Non-random factory, for test purposes.
+ */
+ protected static final class IntegerFactory extends AbstractCandidateFactory<Integer>
+ {
+ private int count = 0;
+
+ public Integer generateRandomCandidate(Random rng)
+ {
+ return ++count;
+ }
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java
new file mode 100644
index 0000000..f9773ca
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/SplitEvolutionTest.java
@@ -0,0 +1,92 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for forked evolutionary schemes.
+ * @author Daniel Dyer
+ */
+public class SplitEvolutionTest
+{
+ /**
+ * Make sure that the correct proportions are mutated correctly.
+ */
+ @Test
+ public void testSplit()
+ {
+ List<Integer> population = new ArrayList<Integer>(10);
+ for (int i = 10; i <= 100; i += 10)
+ {
+ population.add(i);
+ }
+ // Increment 30% of the numbers and decrement the other 70%.
+ SplitEvolution<Integer> evolutionScheme = new SplitEvolution<Integer>(new IntegerAdjuster(1),
+ new IntegerAdjuster(-1),
+ 0.3d);
+ population = evolutionScheme.apply(population, FrameworkTestUtils.getRNG());
+ int aggregate = 0;
+ int incrementedCount = 0;
+ int decrementedCount = 0;
+ for (Integer i : population)
+ {
+ aggregate += i;
+ if (i % 10 == 1)
+ {
+ ++incrementedCount;
+ }
+ else if (i % 10 == 9)
+ {
+ ++decrementedCount;
+ }
+ else
+ {
+ assert false : "Mutation failed.";
+ }
+ }
+ assert incrementedCount == 3 : "Should be 3 incremented candidates, is " + incrementedCount;
+ assert decrementedCount == 7 : "Should be 7 decremented candidates, is " + decrementedCount;
+ assert aggregate == 546 : "Aggregate should be 546 after mutation, is " + aggregate;
+ }
+
+
+ /**
+ * Make sure that the split cannot be set-up with a negative weight. If
+ * this is attempted, an IllegalArgumentException should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testNegativeWeight()
+ {
+ // Negative weight.
+ new SplitEvolution<Integer>(new IntegerAdjuster(1), new IdentityOperator<Integer>(), -0.01d);
+ }
+
+
+ /**
+ * Make sure that the split cannot be set-up with a weight greater than 1. If
+ * this is attempted, an IllegalArgumentException should be thrown.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testWeightTooHigh()
+ {
+ // Weight too high (must be less than or equal to one).
+ new SplitEvolution<Integer>(new IntegerAdjuster(1), new IdentityOperator<Integer>(), 1.01d);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java
new file mode 100644
index 0000000..3a74155
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringCrossoverTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvolutionaryOperator;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for cross-over with Strings.
+ * @author Daniel Dyer
+ */
+public class StringCrossoverTest
+{
+ @Test
+ public void testCrossover()
+ {
+ EvolutionaryOperator<String> crossover = new StringCrossover();
+ List<String> population = new ArrayList<String>(4);
+ population.add("abcde");
+ population.add("fghij");
+ population.add("klmno");
+ population.add("pqrst");
+ Set<Character> values = new HashSet<Character>(20);
+ for (int i = 0; i < 20; i++)
+ {
+ population = crossover.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 4 : "Population size changed after cross-over.";
+ for (String individual : population)
+ {
+ assert individual.length() == 5 : "Invalid candidate length: " + individual.length();
+ for (char value : individual.toCharArray())
+ {
+ values.add(value);
+ }
+ }
+ // All of the individual elements should still be present, just jumbled up
+ // between individuals.
+ assert values.size() == 20 : "Information lost during cross-over.";
+ values.clear();
+ }
+ }
+
+
+ /**
+ * The {@link StringCrossover} operator is only defined to work on populations
+ * containing Strings of equal lengths. Any attempt to apply the operation to
+ * populations that contain different length Strings should throw an exception.
+ * Not throwing an exception should be considered a bug since it could lead to
+ * hard to trace bugs elsewhere.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testDifferentLengthParents()
+ {
+ EvolutionaryOperator<String> crossover = new StringCrossover(1, Probability.ONE);
+ List<String> population = new ArrayList<String>(2);
+ population.add("abcde");
+ population.add("fghijklm");
+ // This should cause an exception since the parents are different lengths.
+ crossover.apply(population, FrameworkTestUtils.getRNG());
+ }
+
+
+ /**
+ * Number of cross-over points must be greater than zero otherwise the operator
+ * is a no-op.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroCrossoverPoints()
+ {
+ new StringCrossover(0, Probability.EVENS); // Should throw an IllegalArgumentException.
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java
new file mode 100644
index 0000000..7827452
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/operators/StringMutationTest.java
@@ -0,0 +1,54 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.operators;
+
+import java.util.Arrays;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+
+/**
+ * Unit test for string mutation operator.
+ * @author Daniel Dyer
+ */
+public class StringMutationTest
+{
+ private final char[] alphabet = {'a', 'b', 'c', 'd'};
+
+ @Test
+ public void testMutation()
+ {
+ StringMutation mutation = new StringMutation(alphabet, Probability.EVENS);
+ String individual1 = "abcd";
+ String individual2 = "abab";
+ String individual3 = "cccc";
+ List<String> population = Arrays.asList(individual1, individual2, individual3);
+ for (int i = 0; i < 20; i++) // Perform several iterations.
+ {
+ population = mutation.apply(population, FrameworkTestUtils.getRNG());
+ assert population.size() == 3 : "Population size changed after mutation: " + population.size();
+ for (String individual : population) // Check that each individual is still valid.
+ {
+ assert individual.length() == 4 : "Individual size changed after mutation: " + individual.length();
+ for (char c : individual.toCharArray())
+ {
+ assert c >= 'a' && c <= 'd' : "Mutation introduced invalid character: " + c;
+ }
+ }
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java
new file mode 100644
index 0000000..a2f140a
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RankSelectionTest.java
@@ -0,0 +1,94 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for rank-proportionate selection.
+ * @author Daniel Dyer
+ */
+public class RankSelectionTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RankSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 1 && steveCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RankSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 1 && garyCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java
new file mode 100644
index 0000000..b0b8282
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/RouletteWheelSelectionTest.java
@@ -0,0 +1,99 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for roulette selection strategy. We cannot easily test
+ * that the correct candidates are returned because of the random aspect
+ * of the selection, but we can at least make sure the right number of
+ * candidates are selected.
+ * @author Daniel Dyer
+ */
+public class RouletteWheelSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, true, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ /**
+ * Make sure that the code still functions for non-natural fitness scores even
+ * when one of them is a zero (a perfect score).
+ */
+ @Test
+ public void testNonNaturalFitnessPerfectSolution()
+ {
+ SelectionStrategy<Object> selector = new RouletteWheelSelection();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations to get different outcomes from the "roulette wheel".
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java
new file mode 100644
index 0000000..9d736d3
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/SigmaScalingTest.java
@@ -0,0 +1,124 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for the {@link SigmaScaling} selection strategy.
+ * @author Daniel Dyer
+ */
+public class SigmaScalingTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 1 && steveCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * If all fitness scores are equal, standard deviation is zero. Test that this case
+ * works correctly.
+ */
+ @Test
+ public void testNoVariance()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 4.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 4.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 4.0);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount == 1 : "Candidate selected wrong number of times (should be 1, was " + steveCount + ")";
+ assert johnCount == 1 : "Candidate selected wrong number of times (should be 1, was " + johnCount + ")";
+ assert maryCount == 1 : "Candidate selected wrong number of times (should be 1, was " + maryCount + ")";
+ assert garyCount == 1 : "Candidate selected wrong number of times (should be 1, was " + garyCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new SigmaScaling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 1 && garyCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2
+ : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java
new file mode 100644
index 0000000..6480bf8
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/StochasticUniversalSamplingTest.java
@@ -0,0 +1,91 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for fitness proportionate selection where observed selection
+ * frequencies correspond to expected frequencies.
+ * @author Daniel Dyer
+ */
+public class StochasticUniversalSamplingTest
+{
+ /**
+ * Test selection when fitness scoring is natural (higher is better).
+ */
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new StochasticUniversalSampling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ population.add(steve);
+ population.add(john);
+ population.add(mary);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ assert steveCount >= 2 && steveCount <= 3 : "Candidate selected wrong number of times (should be 2 or 3, was " + steveCount + ")";
+ assert johnCount >= 1 && johnCount <= 2 : "Candidate selected wrong number of times (should be 1 or 2, was " + johnCount + ")";
+ assert garyCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + garyCount + ")";
+ assert maryCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + maryCount + ")";
+ }
+
+
+ /**
+ * Test selection when fitness scoring is non-natural (lower is better).
+ */
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new StochasticUniversalSampling();
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 0.5);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 1.0);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 4.5);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(mary);
+ population.add(john);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 4, FrameworkTestUtils.getRNG());
+ assert selection.size() == 4 : "Selection size is " + selection.size() + ", should be 4.";
+ int garyCount = Collections.frequency(selection, gary.getCandidate());
+ int maryCount = Collections.frequency(selection, mary.getCandidate());
+ int johnCount = Collections.frequency(selection, john.getCandidate());
+ int steveCount = Collections.frequency(selection, steve.getCandidate());
+ assert garyCount >= 2 && garyCount <= 3 : "Candidate selected wrong number of times (should be 2 or 3, was " + garyCount + ")";
+ assert maryCount >= 1 && maryCount <= 2 : "Candidate selected wrong number of times (should be 1 or 2, was " + maryCount + ")";
+ assert johnCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + johnCount + ")";
+ assert steveCount <= 1 : "Candidate selected wrong number of times (should be 0 or 1, was " + steveCount + ")";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java
new file mode 100644
index 0000000..68f3b38
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TournamentSelectionTest.java
@@ -0,0 +1,90 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.maths.random.Probability;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.FrameworkTestUtils;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for tournament selection strategy. We cannot easily test
+ * that the correct candidates are returned because of the random aspect
+ * of the selection, but we can at least make sure the right number of
+ * candidates are selected.
+ * @author Daniel Dyer
+ */
+public class TournamentSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TournamentSelection(new Probability(0.7d));
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ for (int i = 0; i < 20; i++) // Run several iterations so that we get different tournament outcomes.
+ {
+ List<String> selection = selector.select(population, true, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TournamentSelection(new Probability(0.7d));
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ for (int i = 0; i < 20; i++) // Run several iterations so that we get different tournament outcomes.
+ {
+ List<String> selection = selector.select(population, false, 2, FrameworkTestUtils.getRNG());
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ }
+ }
+
+
+
+ /**
+ * The probability of selecting the fitter of two candidates must be greater than 0.5 to be
+ * useful (if it is not, there is no selection pressure, or the pressure is in favour of weaker
+ * candidates, which is counter-productive) . This test ensures that an appropriate exception
+ * is thrown if the probability is 0.5 or less. Not throwing an exception is an error because
+ * it permits undetected bugs in evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testProbabilityTooLow()
+ {
+ new TournamentSelection(Probability.EVENS);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java
new file mode 100644
index 0000000..3e33d06
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/selection/TruncationSelectionTest.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.selection;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.EvaluatedCandidate;
+import org.uncommons.watchmaker.framework.SelectionStrategy;
+
+/**
+ * Unit test for truncation selection strategy. Ensures the
+ * correct candidates are selected.
+ * @author Daniel Dyer
+ */
+public class TruncationSelectionTest
+{
+ @Test
+ public void testNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TruncationSelection(0.5d);
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Higher score is better.
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ population.add(steve);
+ population.add(mary);
+ population.add(john);
+ population.add(gary);
+ List<String> selection = selector.select(population, true, 2, null);
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ assert selection.contains(steve.getCandidate()) : "Best candidate not selected.";
+ assert selection.contains(mary.getCandidate()) : "Second best candidate not selected.";
+ }
+
+
+ @Test
+ public void testNonNaturalFitnessSelection()
+ {
+ SelectionStrategy<Object> selector = new TruncationSelection(0.5d);
+ List<EvaluatedCandidate<String>> population = new ArrayList<EvaluatedCandidate<String>>(4);
+ // Lower score is better.
+ EvaluatedCandidate<String> gary = new EvaluatedCandidate<String>("Gary", 6.2);
+ EvaluatedCandidate<String> john = new EvaluatedCandidate<String>("John", 8.4);
+ EvaluatedCandidate<String> mary = new EvaluatedCandidate<String>("Mary", 9.1);
+ EvaluatedCandidate<String> steve = new EvaluatedCandidate<String>("Steve", 10.0);
+ population.add(gary);
+ population.add(john);
+ population.add(mary);
+ population.add(steve);
+ List<String> selection = selector.select(population, false, 2, null);
+ assert selection.size() == 2 : "Selection size is " + selection.size() + ", should be 2.";
+ assert selection.contains(gary.getCandidate()) : "Best candidate not selected.";
+ assert selection.contains(john.getCandidate()) : "Second best candidate not selected.";
+ }
+
+
+ /**
+ * The selection ratio must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the ratio is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new TruncationSelection(0d);
+ }
+
+
+ /**
+ * The selection ratio must be less than 1 to be useful. This test
+ * ensures that an appropriate exception is thrown if the ratio is too high.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testRatioTooHigh()
+ {
+ new TruncationSelection(1d);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java
new file mode 100644
index 0000000..ea26876
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/ElapsedTimeTest.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Unit test for termination condition that checks the time taken so far by the
+ * evolutionary algorithm.
+ * @author Daniel Dyer
+ */
+public class ElapsedTimeTest
+{
+ @Test
+ public void testElapsedTimes()
+ {
+ TerminationCondition condition = new ElapsedTime(1000);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 0, 100);
+ assert !condition.shouldTerminate(data) : "Should not terminate before timeout.";
+ data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 0, 1000);
+ assert condition.shouldTerminate(data) : "Should terminate after timeout.";
+ }
+
+
+ /**
+ * The duration must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the duration is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new ElapsedTime(0L);
+ }
+
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java
new file mode 100644
index 0000000..64487a2
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/GenerationCountTest.java
@@ -0,0 +1,52 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Unit test for termination condition that checks the number of evolved generations.
+ * @author Daniel Dyer
+ */
+public class GenerationCountTest
+{
+ @Test
+ public void testGenerationCounts()
+ {
+ TerminationCondition condition = new GenerationCount(5);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 3, 100);
+ // Generation number 3 is the 4th generation (generation numbers are zero-based).
+ assert !condition.shouldTerminate(data) : "Should not terminate after 4th generation.";
+ data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 4, 100);
+ // Generation number 4 is the 5th generation (generation numbers are zero-based).
+ assert condition.shouldTerminate(data) : "Should terminate after 5th generation.";
+ }
+
+
+ /**
+ * The generation count must be greater than zero to be useful. This test
+ * ensures that an appropriate exception is thrown if the count is not positive.
+ * Not throwing an exception is an error because it permits undetected bugs in
+ * evolutionary programs.
+ */
+ @Test(expectedExceptions = IllegalArgumentException.class)
+ public void testZeroRatio()
+ {
+ new GenerationCount(0);
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java
new file mode 100644
index 0000000..6efe9fb
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/StagnationTest.java
@@ -0,0 +1,97 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Unit test for the {@link Stagnation} termination condition.
+ * @author Daniel Dyer
+ */
+public class StagnationTest
+{
+ @Test
+ public void testFittestCandidateStagnation()
+ {
+ TerminationCondition stagnation = new Stagnation(2, true);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 2, 1, 0.1, true, 10, 0, 0, 1);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(new Object(), 1.8, 1.2, 0.1, true, 10, 0, 1, 2);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1.5, 0.1, true, 10, 0, 2, 3);
+ assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement.";
+ }
+
+ @Test
+ public void testFittestCandidateStagnationNonNatural()
+ {
+ TerminationCondition stagnation = new Stagnation(2, false);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 2, 1.5, 0.1, true, 10, 0, 0, 1);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(new Object(), 2.2, 1.2, 0.1, true, 10, 0, 1, 2);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation.";
+ // Best doesn't improve even though mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1, 0.1, true, 10, 0, 2, 3);
+ assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement.";
+ }
+
+
+ @Test
+ public void testPopulationMeanStagnation()
+ {
+ TerminationCondition stagnation = new Stagnation(2, true, true);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 2, 1, 0.1, true, 10, 0, 0, 1);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1.2, 0.1, true, 10, 0, 1, 2);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1.5, 0.1, true, 10, 0, 2, 3);
+ // Best has stagnated but mean hasn't so shouldn't terminate.
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Now we let the mean stagnate...and let the best candidate get fitter...
+ data = new PopulationData<Object>(new Object(), 2.1, 1.5, 0.1, true, 10, 0, 3, 4);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation.";
+ data = new PopulationData<Object>(new Object(), 2.2, 1.5, 0.1, true, 10, 0, 4, 4);
+ assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement.";
+ }
+
+
+ @Test
+ public void testPopulationMeanStagnationNonNatural()
+ {
+ TerminationCondition stagnation = new Stagnation(2, false, true);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 2, 1.5, 0.1, true, 10, 0, 0, 1);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1.2, 0.1, true, 10, 0, 1, 2);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Best doesn't improve but mean does.
+ data = new PopulationData<Object>(new Object(), 2, 1, 0.1, true, 10, 0, 2, 3);
+ // Best has stagnated but mean hasn't so shouldn't terminate.
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 2 more generations.";
+ // Now we let the mean stagnate...and let the best candidate get fitter...
+ data = new PopulationData<Object>(new Object(), 2.1, 1.6, 0.1, true, 10, 0, 3, 4);
+ assert !stagnation.shouldTerminate(data) : "Stagnation should not be triggered for at least 1 more generation.";
+ data = new PopulationData<Object>(new Object(), 2.2, 1.5, 0.1, true, 10, 0, 4, 4);
+ assert stagnation.shouldTerminate(data) : "Stagnation should be triggered after 2 generations without improvement.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java
new file mode 100644
index 0000000..738f612
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/TargetFitnessTest.java
@@ -0,0 +1,49 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.PopulationData;
+import org.uncommons.watchmaker.framework.TerminationCondition;
+
+/**
+ * Unit test for termination condition that checks the best fitness attained so far
+ * against a pre-determined target.
+ * @author Daniel Dyer
+ */
+public class TargetFitnessTest
+{
+ @Test
+ public void testNaturalFitness()
+ {
+ TerminationCondition condition = new TargetFitness(10.0d, true);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 5.0d, 4.0d, 0, true, 2, 0, 0, 100);
+ assert !condition.shouldTerminate(data) : "Should not terminate before target fitness is reached.";
+ data = new PopulationData<Object>(new Object(), 10.0d, 8.0d, 0, true, 2, 0, 0, 100);
+ assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached.";
+ }
+
+
+ @Test
+ public void testNonNaturalFitness()
+ {
+ TerminationCondition condition = new TargetFitness(1.0d, false);
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 5.0d, 4.0d, 0, true, 2, 0, 0, 100);
+ assert !condition.shouldTerminate(data) : "Should not terminate before target fitness is reached.";
+ data = new PopulationData<Object>(new Object(), 1.0d, 3.1d, 0, true, 2, 0, 0, 100);
+ assert condition.shouldTerminate(data) : "Should terminate once target fitness is reached.";
+ }
+}
diff --git a/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java
new file mode 100644
index 0000000..34f8ce8
--- /dev/null
+++ b/watchmaker/framework/src/java/test/org/uncommons/watchmaker/framework/termination/UserAbortTest.java
@@ -0,0 +1,39 @@
+//=============================================================================
+// Copyright 2006-2010 Daniel W. Dyer
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//=============================================================================
+package org.uncommons.watchmaker.framework.termination;
+
+import org.testng.annotations.Test;
+import org.uncommons.watchmaker.framework.PopulationData;
+
+/**
+ * Unit test for termination condition that checks an abort flag set by the user.
+ * @author Daniel Dyer
+ */
+public class UserAbortTest
+{
+ @Test
+ public void testAbort()
+ {
+ UserAbort condition = new UserAbort();
+ // This population data should be irrelevant.
+ PopulationData<Object> data = new PopulationData<Object>(new Object(), 0, 0, 0, true, 2, 0, 0, 100);
+ assert !condition.shouldTerminate(data) : "Should not terminate without user abort.";
+ assert !condition.isAborted() : "Should not be aborted without user intervention.";
+ condition.abort();
+ assert condition.shouldTerminate(data) : "Should terminate after user abort.";
+ assert condition.isAborted() : "Should be aborted after user intervention.";
+ }
+}