├── .gitignore
├── README.md
├── build.sbt
├── mgo
└── src
│ └── main
│ └── scala
│ └── mgo
│ ├── abc
│ ├── APMC.scala
│ ├── MonAPMC.scala
│ └── package.scala
│ ├── evolution
│ ├── algorithm
│ │ ├── CMAES.scala
│ │ ├── EMPPSE.scala
│ │ ├── HDOSE.scala
│ │ ├── HyperNEAT.scala
│ │ ├── NEAT.scala
│ │ ├── NSGA2.scala
│ │ ├── NSGA3.scala
│ │ ├── NichedNSGA2.scala
│ │ ├── NoisyHDOSE.scala
│ │ ├── NoisyNSGA2.scala
│ │ ├── NoisyNSGA3.scala
│ │ ├── NoisyNichedNSGA2.scala
│ │ ├── NoisyOSE.scala
│ │ ├── NoisyPSE.scala
│ │ ├── NoisyProfile.scala
│ │ ├── OSE.scala
│ │ ├── PSE.scala
│ │ ├── Profile.scala
│ │ └── package.scala
│ ├── breeding.scala
│ ├── cmaes
│ │ ├── CMAESArchive.scala
│ │ └── CMAESBreeding.scala
│ ├── contexts.scala
│ ├── diversity.scala
│ ├── dominance.scala
│ ├── elitism.scala
│ ├── neat
│ │ ├── NEATAnyTopology.scala
│ │ ├── NEATArchive.scala
│ │ ├── NEATBreedingContext.scala
│ │ ├── NEATCrossover.scala
│ │ ├── NEATFeedforwardTopology.scala
│ │ ├── NEATGenome.scala
│ │ ├── NEATGenomesAlign.scala
│ │ ├── NEATInitialGenome.scala
│ │ ├── NEATMinimalGenomeConnectedIO.scala
│ │ ├── NEATMinimalGenomeUnconnected.scala
│ │ ├── NEATMutation.scala
│ │ ├── NEATProblem.scala
│ │ ├── NEATSpeciesFitnessSharing.scala
│ │ ├── NEATSpeciesRescaledFitnessSharing.scala
│ │ └── README
│ ├── niche.scala
│ ├── package.scala
│ ├── ranking.scala
│ └── stop.scala
│ ├── test
│ ├── TestAggregated.scala
│ ├── TestCMAES.scala
│ ├── TestCrowding.scala
│ ├── TestFunctionSMSEMOEA.scala
│ ├── TestHDOSE.scala
│ ├── TestHyperNEATBoxes.scala
│ ├── TestMap.scala
│ ├── TestMonAPMC.scala
│ ├── TestMonoidParallel.scala
│ ├── TestNEATVarSize.scala
│ ├── TestNEATXOR.scala
│ ├── TestNSGA3.scala
│ ├── TestNSGAII.scala
│ ├── TestNeuralNetworksPerformance.scala
│ ├── TestNichedNSGA2.scala
│ ├── TestOSE.scala
│ ├── TestOptimumDiversity.scala
│ ├── TestPSE.scala
│ ├── TestProfile.scala
│ ├── TestSMSEMOEA.scala
│ └── package.scala
│ └── tools
│ ├── Breeze.scala
│ ├── CanBeNaN.scala
│ ├── HierarchicalRanking.scala
│ ├── ImplementEqualMethod.scala
│ ├── KDTree.scala
│ ├── Lazy.scala
│ ├── LinearAlgebra.scala
│ ├── NeighborMatrix.scala
│ ├── Neighbours.scala
│ ├── RejectionSampler.scala
│ ├── Stats.scala
│ ├── benchmark
│ └── ManyObjective.scala
│ ├── clustering
│ ├── Cluster.scala
│ ├── EMGMM.scala
│ ├── KMeans.scala
│ ├── RandomCentroids.scala
│ └── WDFEMGMM.scala
│ ├── execution
│ ├── ExposedEval.scala
│ ├── MonoidParallel.scala
│ └── Sequential.scala
│ ├── metric
│ ├── ClosedCrowdingDistance.scala
│ ├── CrowdingDistance.scala
│ ├── HyperVolumeApprox.scala
│ ├── Hypervolume.scala
│ └── KNearestNeighboursAverageDistance.scala
│ ├── network
│ ├── DenseTopology.scala
│ ├── DirectedEdges.scala
│ ├── Network.scala
│ ├── SparseTopology.scala
│ └── UndirectedEdges.scala
│ ├── neuralnetwork
│ └── NeuralNetwork.scala
│ └── package.scala
├── project
├── build.properties
└── plugin.sbt
└── version.sbt
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | target/*
3 | .idea*/*
4 | build.xml
5 | nbactions.xml
6 | /nbproject/
7 | pom.xml
8 | *.iml
9 | *.sublime*
10 | *.DS_Store
11 | tags
12 | .bsp
13 | .bloop/
14 | .metals/
15 | .vscode/
16 | project/.bloop/
17 | project/metals.sbt
18 | project/project/
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | MGO
2 | ===
3 |
4 | MGO is a purely functionnal scala library based for evolutionary / genetic algorithms:
5 | * enforcing immutability,
6 | * exposes a modular and extensible architecture,
7 | * implements state of the art algorithms,
8 | * handles noisy (stochastic) fitness functions,
9 | * implements auto-adaptatative algortihms,
10 | * implements algorithms with distributed computing in mind for integration with [OpenMOLE](http://openmole.org).
11 |
12 | MGO implements NGSAII, NSGA3, CP (Calibration Profile), PSE (Pattern Search Experiment), OSE (Antecedant research), Niched Evolution, ABC (Bayesian Calibration).
13 |
14 | Licence
15 | -------
16 |
17 | MGO is licenced under the GNU GPLv3 software licence.
18 |
19 | Example
20 | -------
21 |
22 | Define a problem, for instance the multi-modal multi-objective ZDT4 benchmark:
23 |
24 | ```scala
25 |
26 | object zdt4 {
27 |
28 | def continuous(size: Int) = Vector.fill(size)(C(0.0, 5.0))
29 |
30 | def compute(genome: Vector[Double], d: Vector[Int]): Vector[Double] = {
31 | val genomeSize = genome.size
32 |
33 | def g(x: Seq[Double]) = 1 + 10 * (genomeSize - 1) + x.map { i => pow(i, 2) - 10 * cos(4 * Pi * i) }.sum
34 |
35 | def f(x: Seq[Double]) = {
36 | val gx = g(x)
37 | gx * (1 - sqrt(genome(0) / gx))
38 | }
39 |
40 | Vector(genome(0), f(genome.tail))
41 | }
42 |
43 | }
44 |
45 | ```
46 |
47 | Define the optimisation algorithm, for instance NSGAII:
48 |
49 | ```scala
50 |
51 | import mgo.evolution._
52 | import mgo.evolution.algorithm._
53 |
54 | // For zdt4
55 | import mgo.test._
56 |
57 | val nsga2 =
58 | NSGA2(
59 | mu = 100,
60 | lambda = 100,
61 | fitness = zdt4.compute,
62 | continuous = zdt4.continuous(10))
63 |
64 | ```
65 |
66 | Run the optimisation:
67 |
68 | ```scala
69 |
70 | def evolution =
71 | nsga2.
72 | until(afterGeneration(1000)).
73 | trace((s, is) => println(s.generation))
74 |
75 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
76 |
77 | println(NSGA2.result(nsga2, finalPopulation).mkString("\n"))
78 |
79 | ```
80 |
81 | Noisy fitness functions
82 | -----------------------
83 |
84 | All algorithm in MGO have version to compute on noisy fitness function. MGO handle noisy fitness functions by resampling
85 | only the most promising individuals. It uses an aggregation function to aggregate the multiple sample when needed.
86 |
87 | For instance a version of NSGA2 for noisy fitness functions may be used has follow:
88 |
89 | ```scala
90 | import mgo._
91 | import algorithm.noisynsga2._
92 | import context.implicits._
93 |
94 | object sphere {
95 | def scale(s: Vector[Double]): Vector[Double] = s.map(_.scale(-2, 2))
96 | def compute(i: Vector[Double]): Double = i.map(x => x * x).sum
97 | }
98 |
99 | object noisySphere {
100 | def scale(s: Vector[Double]): Vector[Double] = sphere.scale(s)
101 | def compute(rng: util.Random, v: Vector[Double]) =
102 | sphere.compute(v) + rng.nextGaussian() * 0.5 * math.sqrt(sphere.compute(v))
103 | }
104 |
105 | def aggregation(history: Vector[Vector[Double]]) = history.transpose.map { o => o.sum / o.size }
106 |
107 | val nsga2 =
108 | NoisyNSGA2(
109 | mu = 100,
110 | lambda = 100,
111 | fitness = (rng, v) => Vector(noisySphere.compute(rng, v)),
112 | aggregation = aggregation,
113 | genomeSize = 2)
114 |
115 | val (finalState, finalPopulation) =
116 | run(nsga2).
117 | until(afterGeneration(1000)).
118 | trace((s, is) => println(s.generation)).
119 | eval(new util.Random(42))
120 |
121 | println(result(finalPopulation, aggregation, noisySphere.scale).mkString("\n"))
122 | ```
123 |
124 | Diversity only
125 | --------------
126 |
127 | MGO proposes the PSE alorithm that aim a creating diverse solution instead of optimsing a function. The paper about this
128 | algorithm can be found [here](http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0138212).
129 |
130 | ```scala
131 | import mgo._
132 | import algorithm.pse._
133 | import context.implicits._
134 |
135 | val pse = PSE(
136 | lambda = 10,
137 | phenotype = zdt4.compute,
138 | pattern =
139 | boundedGrid(
140 | lowBound = Vector(0.0, 0.0),
141 | highBound = Vector(1.0, 200.0),
142 | definition = Vector(10, 10)),
143 | genomeSize = 10)
144 |
145 | val (finalState, finalPopulation) =
146 | run(pse).
147 | until(afterGeneration(1000)).
148 | trace((s, is) => println(s.generation)).
149 | eval(new util.Random(42))
150 |
151 | println(result(finalPopulation, zdt4.scale).mkString("\n"))
152 | ```
153 |
154 | This program explores all the different combination of values that can be produced by the multi-objective function of ZDT4.
155 |
156 | For more examples, have a look at the main/scala/fr/iscpif/mgo/test directory in the repository.
157 |
158 | Mixed optimisation and diversity
159 | --------------------------------
160 |
161 | The calibration profile algorthim compute the best fitness function for a set of niches. This algorithm is explained [here](http://jasss.soc.surrey.ac.uk/18/1/12.html).
162 |
163 | In MGO you can compute profiles of a 10 dimensional hyper-sphere function using the following:
164 |
165 | ```scala
166 |
167 | import algorithm.profile._
168 | import context.implicits._
169 |
170 | //Profile the first dimension of the genome
171 | val algo = Profile(
172 | lambda = 100,
173 | fitness = sphere.compute,
174 | niche = genomeProfile(x = 0, nX = 10),
175 | genomeSize = 10)
176 |
177 | val (finalState, finalPopulation) =
178 | run(algo).
179 | until(afterGeneration(1000)).
180 | trace((s, is) => println(s.generation)).
181 | eval(new util.Random(42))
182 |
183 | println(result(finalPopulation, sphere.scale).mkString("\n"))
184 | ```
185 |
186 | Noisy profiles
187 | --------------
188 |
189 | All algorithms in MGO have a pendant for noisy fitness function. Here is an example of a profile computation for a sphere
190 | function with noise.
191 |
192 | ```scala
193 | import algorithm.noisyprofile._
194 | import context.implicits._
195 |
196 | def aggregation(history: Vector[Double]) = history.sum / history.size
197 | def niche = genomeProfile(x = 0, nX = 10)
198 |
199 | val algo = NoisyProfile(
200 | muByNiche = 20,
201 | lambda = 100,
202 | fitness = noisySphere.compute,
203 | aggregation = aggregation,
204 | niche = niche,
205 | genomeSize = 5)
206 |
207 | val (finalState, finalPopulation) =
208 | run(algo).
209 | until(afterGeneration(1000)).
210 | trace((s, is) => println(s.generation)).
211 | eval(new util.Random(42))
212 |
213 | println(result(finalPopulation, aggregation, noisySphere.scale, niche).mkString("\n"))
214 |
215 | ```
216 |
217 | Distributed computing
218 | ---------------------
219 |
220 | Algorithms implemented in MGO are also avialiable in the workflow plateform for distributed computing [OpenMOLE](http://openmole.org).
221 |
222 | SBT dependency
223 | ----------------
224 | ```scala
225 | libraryDependencies += "fr.iscpif" %% "mgo" % "2.45"
226 | ```
227 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 |
2 | name := "mgo"
3 | ThisBuild / organization := "org.openmole"
4 | ThisBuild / scalaVersion := "3.7.0"
5 | //ThisBuild / crossScalaVersions := Seq("3.6.2")
6 |
7 | val monocleVersion = "3.2.0"
8 |
9 | lazy val settings: Seq[Setting[_]] = Seq(
10 | //addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.10"),
11 | resolvers += Resolver.sonatypeRepo("public"),
12 | resolvers += Resolver.sonatypeRepo("staging"),
13 | resolvers += Resolver.sonatypeRepo("snapshots"),
14 | javacOptions ++= Seq("-source", "11", "-target", "11"),
15 | scalacOptions ++= Seq("-Xtarget:11", "-language:higherKinds"),
16 | scalacOptions ++= Seq("-language:postfixOps", "-source:3.7")
17 | // scalacOptions ++= (
18 | // if (priorTo2_13(scalaVersion.value)) Nil else Seq("-Ymacro-annotations", "-language:postfixOps")
19 | // ),
20 | // libraryDependencies ++=
21 | // (if (priorTo2_13(scalaVersion.value))
22 | // Seq(
23 | // compilerPlugin(("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.patch))
24 | // )
25 | // else Nil)
26 | //libraryDependencies += "org.scala-lang.modules" %% "scala-collection-compat" % "2.1.6"
27 | )
28 |
29 | lazy val mgo = Project(id = "mgo", base = file("mgo")) settings(settings: _*) settings (
30 | // macro paradise doesn't work with scaladoc
31 | //Compile / sources in (Compile, doc) := Nil,
32 | libraryDependencies += "org.apache.commons" % "commons-math3" % "3.6.1",
33 |
34 | libraryDependencies += "dev.optics" %% "monocle-core" % monocleVersion,
35 | libraryDependencies += "dev.optics" %% "monocle-macro" % monocleVersion,
36 | // libraryDependencies += "com.github.julien-truffaut" %% "monocle-generic" % monocleVersion,
37 | // libraryDependencies += "com.github.julien-truffaut" %% "monocle-macro" % monocleVersion,
38 |
39 | libraryDependencies += "org.typelevel" %% "squants" % "1.8.3", //cross(CrossVersion.for2_13Use3),
40 |
41 | // libraryDependencies ++= (if(scala2(scalaVersion.value)) Seq("org.typelevel" %% "squants" % "1.6.0") else Seq()),
42 |
43 | //libraryDependencies += "org.typelevel" %% "cats-core" % "2.1.0",
44 | libraryDependencies += "com.github.pathikrit" %% "better-files" % "3.9.2",
45 | libraryDependencies ++= Seq(
46 | "org.scalanlp" %% "breeze" % "2.1.0"
47 | //"org.scalanlp" %% "breeze-natives" % breezeVersion
48 | ),
49 | excludeDependencies += ExclusionRule(organization = "org.typelevel", name = "cats-kernel_2.13"),
50 | //libraryDependencies += "com.edwardraff" % "JSAT" % "0.0.9",
51 | Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-verbosity", "1")
52 | )
53 |
54 |
55 | /* Publish */
56 |
57 | ThisBuild / publishMavenStyle := true
58 | ThisBuild / Test / publishArtifact := false
59 | publishArtifact := false
60 |
61 | ThisBuild / licenses := Seq("GPLv3" -> url("http://www.gnu.org/licenses/"))
62 | ThisBuild / homepage := Some(url("https://github.com/openmole/mgo"))
63 | ThisBuild / scmInfo := Some(ScmInfo(url("https://github.com/openmole/mgo.git"), "scm:git:git@github.com:openmole/mgo.git"))
64 |
65 | ThisBuild / publishTo := {
66 | val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
67 | if (isSnapshot.value) Some("central-snapshots" at centralSnapshots)
68 | else localStaging.value
69 | }
70 |
71 | ThisBuild / developers := List(
72 | Developer(
73 | id = "romainreuillon",
74 | name = "Romain Reuillon",
75 | email = "",
76 | url = url("https://github.com/romainreuillon/")
77 | )
78 | )
79 |
80 | releasePublishArtifactsAction := PgpKeys.publishSigned.value
81 | releaseVersionBump := sbtrelease.Version.Bump.Minor
82 | releaseTagComment := s"Releasing ${(ThisBuild / version).value}"
83 | releaseCommitMessage := s"Bump version to ${(ThisBuild / version).value}"
84 | publishConfiguration := publishConfiguration.value.withOverwrite(true)
85 |
86 | import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._
87 |
88 | releaseProcess := Seq[ReleaseStep](
89 | checkSnapshotDependencies,
90 | inquireVersions,
91 | runClean,
92 | runTest,
93 | setReleaseVersion,
94 | tagRelease,
95 | releaseStepCommandAndRemaining("publishSigned"),
96 | releaseStepCommand("sonaRelease"),
97 | setNextVersion,
98 | commitNextVersion,
99 | pushChanges
100 | )
101 |
102 |
103 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/abc/package.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 05/03/2019 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo
19 |
20 | /**
21 | * Approximate Bayesian computation (ABC) methods are used to draw samples
22 | * approximating a posterior distribution p(θ|y) ∝ p(y|θ) p(θ) when the value
23 | * of the likelihood p(y|θ) is unavailable but one can sample according
24 | * to the likelihood, typically via simulation.
25 | */
26 |
27 | package object abc {
28 | type Matrix = Array[Array[Double]]
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/CMAES.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.evolution.algorithm
19 |
20 | //import mgo.evolution._
21 |
22 | /*trait CMAES <: Evolution
23 | with KeepOffspringElitism
24 | with GAGenomeWithRandomValue
25 | with MaxAggregation
26 | with CMAESBreeding
27 | with CMAESArchive
28 | with ClampedGenome*/
29 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/HyperNEAT.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 08/07/2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.evolution.algorithm
19 |
20 | //import mgo.evolution._
21 | //import mgo.evolution.breed.NEATBreedingContext
22 | //import mgo.evolution.crossover.NEATCrossover
23 | //import mgo.evolution.mutation.NEATMutation
24 | //import mgo.evolution.problem.NEATProblem
25 | //import mgo.selection.NEATMating
26 | //import mgo.tools.neuralnetwork.ActivationFunction
27 | //
28 | //import mgo.evolution.genome.NEATGenome._
29 | //
30 | //import util.Random
31 |
32 | /**
33 | * Differences with NEAT:
34 | * - Nodes carry activation functions
35 | * - Neural nets are created from the evolved nets
36 | */
37 | /*trait HyperNEAT <: NEATProblem with GeneticBreeding with NEATBreedingContext with NEATMating with NEATCrossover with NEATMutation with NEATElitism with NEATArchive with NoPhenotype with Cloning {
38 |
39 | type ACTIVF = String
40 | val cppnActivationFunctions: Seq[ACTIVF]
41 |
42 | type NODEDATA = ACTIVF
43 |
44 | def pickActivationFunction(implicit rng: Random): ACTIVF = cppnActivationFunctions(rng.nextInt(cppnActivationFunctions.length))
45 |
46 | def pickNewHiddenNode(level: Double)(implicit rng: Random): HiddenNode =
47 | HiddenNode(
48 | pickActivationFunction,
49 | level)
50 |
51 | def newInputNode: InputNode = InputNode("lin")
52 | def newBiasNode: BiasNode = BiasNode("lin")
53 | def newOutputNode: OutputNode = OutputNode("lin")
54 | }*/
55 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/NEAT.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 21/05/2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | /**
19 | * - Init Pop minimally
20 | * - Evaluate
21 | * - Loop
22 | * - Selection
23 | * - Crossover
24 | * - Mutation
25 | * - Evaluation
26 | * - Elitism
27 | *
28 | * # Genome
29 | *
30 | * List of connection gene
31 | *
32 | * # Connection gene
33 | *
34 | * - in-node
35 | * - out-node
36 | * - weight
37 | * - enable bit
38 | * - innovation number
39 | *
40 | * # Selection
41 | *
42 | * - every species is assigned a number of offsprings = (species fitness / sum of species fitnesses) * population size
43 | * - parents are picked at random in each species (only the fittest individuals of each species were selected at the elitism stage)
44 | *
45 | * # Crossover
46 | *
47 | * - align the 2 parent genomes according to their innovation number
48 | * - genes that match no other gene in the other genome are disjoint if they occur within the range of the the other genome innovation numbers
49 | * - they are excess if they occur outside this range.
50 | * - a new offspring is composed by chosing each matching genes randomly from either parent.
51 | * - excess or disjoint genes from the fittest parent are included.
52 | *
53 | * # Mutation
54 | *
55 | * - weight mutation (gaussian)
56 | * - structural mutation
57 | * -- add connection (weight = 0?)
58 | * -- add node: split existing connection
59 | * --- existing connection is disabled
60 | * --- create 2 new connection
61 | * ---- connection leading into the new node has weight 1
62 | * ---- connection leading out of the new onde has weight equal to the weight of the old connection.
63 | *
64 | * Adding a mutation increments the global innovation number.
65 | *
66 | * Check for identical mutations in current generation (problem en cas de steady state, pop size = 1).
67 | *
68 | * # Evaluation
69 | *
70 | * (species attribution can happen at the breeding stage)
71 | * - the index of species represents each species by a random genome of the corresponding species of the past generation.
72 | * - each genome is attributed to a species in the index of species if its delta is < delta_t
73 | * - delta = c1 * E / N + c2 * D / N + c3*avg(W)
74 | * - when a genome does not belong to any species, a new species is created with it as its representative
75 | *
76 | * (When updating the archive)
77 | * - the index of species is updated by selecting a random genome in each species
78 | *
79 | * - the fitness of each genome is computed (the corresponding network must be created and evaluated)
80 | * - each individual is reattributed a fitness equal to the average fitness of the species.
81 | *
82 | * In the orginal NEAT, the fitness sharing function is designed so that higher fitness is better. In MGO, fitness is minimized. Must adapt the fitness sharing function.
83 | *
84 | * # Elitism
85 | *
86 | * - keep only the 20% fittest individuals of each species.
87 | *
88 | * - if the fitness of the entire population does not improve for more than 20 generations, only the two best species are allowed to reproduce.
89 | *
90 | * # Speciation
91 | *
92 | * - there is an index of species, represented by a random genome of the past generation.
93 | *
94 | * Dynamic thresholding
95 | *
96 | *
97 | *
98 | * # Networks
99 | *
100 | * - create a network that can be queried from a list of connection genes (genome)
101 | *
102 | *
103 | * ## querying
104 | *
105 | * How do I ensure that a network stabilizes before taking its output(s) for a classification problem?
106 | *
107 | * The proper (and quite nice) way to do it is to check every hidden node and output node from one
108 | * timestep to the next, and see if nothing has changed, or at least not changed within some delta.
109 | * Once this criterion is met, the output must be stable.
110 | *
111 | * Note that output may not always stabilize in some cases. Also, for continuous control problems,
112 | * do not check for stabilization as the network never "settles" but rather continuously reacts to a
113 | * changing environment. Generally, stabilization is used in classification problems, or in board games. "
114 | *
115 | * # Testing
116 | *
117 | * see "How should I test my own version of NEAT to make sure it works?"
118 | * http://www.cs.ucf.edu/~kstanley/neat.html
119 | *
120 | */
121 |
122 | package mgo.evolution.algorithm
123 |
124 | //import mgo.evolution._
125 | //import mgo.evolution.breed.NEATBreedingContext
126 | //import mgo.evolution.crossover.NEATCrossover
127 | //import mgo.evolution.mutation.NEATMutation
128 | //import mgo.evolution.problem.NEATProblem
129 | //import mgo.evolution.genome.NEATGenome._
130 | //import mgo.selection.NEATMating
131 | //import util.Random
132 |
133 | /**
134 | * Differences with the original neat:
135 | * - can start with an unconnected genome
136 | * - can choose between using species hint or not
137 | * - can mutate weights to 0 to enforce sparsity
138 | * - On ne normalise pas la distance entre génomes par le génome le plus grand, et on prend la somme des différences des poids plutôt que la moyenne
139 | */
140 |
141 | /*trait NEAT <: NEATProblem with GeneticBreeding with NEATBreedingContext with NEATMating with NEATCrossover with NEATMutation with Cloning with NEATElitism with NEATArchive with NoPhenotype {
142 | type NODEDATA = Unit
143 | def pickNewHiddenNode(level: Double)(implicit rng: Random): HiddenNode = HiddenNode((), level)
144 |
145 | def newInputNode: InputNode = InputNode(())
146 | def newBiasNode: BiasNode = BiasNode(())
147 | def newOutputNode: OutputNode = OutputNode(())
148 | }*/
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/NichedNSGA2.scala:
--------------------------------------------------------------------------------
1 | //package mgo.evolution.algorithm
2 | //
3 | //import scala.language.higherKinds
4 | //import mgo.evolution._
5 | //import mgo.ranking._
6 | //import mgo.evolution.breeding._
7 | //import mgo.evolution.elitism._
8 | //import mgo.evolution.contexts._
9 | //import tools._
10 | //import cats._
11 | //import cats.data._
12 | //import cats.implicits._
13 | //import GenomeVectorDouble._
14 | //import freedsl.dsl
15 | //import freedsl.tool._
16 | //import monocle._
17 | //import monocle.macros._
18 | //
19 | //object nichednsga2 extends niche.Imports {
20 | //
21 | // import algorithm.nsga2._
22 | //
23 | // // @Lenses case class Genome[N](niche: N, values: Array[Double], operator: Option[Int])
24 | // @Lenses case class Individual[P](genome: nsga2.Genome, phenotype: P, fitness: Array[Double], age: Long)
25 | // //
26 | // def buildIndividual[P](g: Genome, p: P, f: Vector[Double]) = Individual(g, p, f.toArray, 0)
27 | // // def buildGenome[N](niche: N, values: Vector[Double], operator: Option[Int]) = Genome[N](niche, values.toArray, operator)
28 | // //
29 | // //
30 | // //
31 | // def vectorFitness[P] = Individual.fitness[P] composeLens arrayToVectorLens
32 | // // def vectorValues = Genome.values composeLens arrayToVectorLens
33 | // //
34 | // // def initialGenomes[M[_]: cats.Monad: Random, N](lambda: Int, genomeSize: Int, generateNiche: M[N]): M[Vector[Genome[N]]] =
35 | // // for {
36 | // // vectors <- GenomeVectorDouble.randomGenomes[M](lambda, genomeSize)
37 | // // niches <- generateNiche.repeat(lambda)
38 | // // genomes = (vectors zip niches).map { case (v, n) => Genome[N](n, v.toArray, None) }
39 | // // } yield genomes
40 | //
41 | // def adaptiveBreeding[M[_]: Generation: Random: cats.Monad, P](lambda: Int, operatorExploration: Double): Breeding[M, Individual[P], Genome] =
42 | // nsga2Operations.adaptiveBreeding[M, Individual[P], Genome](
43 | // vectorFitness.get, Individual.genome[P].get, vectorValues.get, Genome.operator.get, buildGenome)(lambda, operatorExploration)
44 | //
45 | // def expression[P](phenotype: Vector[Double] => P, fitness: P => Vector[Double]): Expression[Genome, Individual[P]] =
46 | // nichedNSGA2Operations.expression[Genome, Individual[P], P](vectorValues.get, buildIndividual)(phenotype, fitness)
47 | //
48 | // def elitism[M[_]: cats.Monad: Random: Generation, N, P](niche: Individual[P] => N, mu: Int): Elitism[M, Individual[P]] =
49 | // nichedNSGA2Operations.elitism[M, Individual[P], N](
50 | // vectorFitness.get,
51 | // (Individual.genome composeLens vectorValues).get,
52 | // Individual.age)(niche, mu)
53 | //
54 | // def result[P](population: Vector[Individual[P]], scaling: Vector[Double] => Vector[Double]) =
55 | // population.map { i => (scaling(i.genome.values.toVector), i.phenotype, i.fitness.toVector) }
56 | //
57 | // // def state[M[_]: Monad: StartTime: Random: Generation] = mgo.evolution.algorithm.state[M, Unit](())
58 | //
59 | // object NichedNSGA2 {
60 | //
61 | // def run[T](rng: util.Random)(f: contexts.run.Implicits => T): T = contexts.run(rng)(f)
62 | // def run[T](state: EvolutionState[Unit])(f: contexts.run.Implicits => T): T = contexts.run(state)(f)
63 | //
64 | // implicit def isAlgorithm[M[_]: Generation: Random: cats.Monad: StartTime, N, P]: Algorithm[NichedNSGA2[N, P], M, Individual[P], Genome, EvolutionState[Unit]] =
65 | // new Algorithm[NichedNSGA2[N, P], M, Individual[P], Genome, EvolutionState[Unit]] {
66 | //
67 | // override def initialPopulation(t: NichedNSGA2[N, P]) =
68 | // deterministicInitialPopulation[M, Genome, Individual[P]](
69 | // nsga2.initialGenomes[M](t.lambda, t.genomeSize),
70 | // expression(t.phenotype, t.fitness))
71 | //
72 | // override def step(t: NichedNSGA2[N, P]) =
73 | // nsga2Operations.step[M, Individual[P], Genome](
74 | // adaptiveBreeding[M, P](t.lambda, t.operatorExploration),
75 | // expression(t.phenotype, t.fitness),
76 | // nichednsga2.elitism((Individual.phenotype[P].asGetter composeGetter Getter(t.niche)).get, t.mu))
77 | //
78 | // override def state = nsga2.state[M]
79 | // }
80 | //
81 | // }
82 | //
83 | // case class NichedNSGA2[N, P](
84 | // niche: P => N,
85 | // mu: Int,
86 | // lambda: Int,
87 | // phenotype: Vector[Double] => P,
88 | // fitness: P => Vector[Double],
89 | // genomeSize: Int,
90 | // operatorExploration: Double = 0.1)
91 | //
92 | //}
93 | //
94 | //object nichedNSGA2Operations {
95 | //
96 | // def elitism[S, I, N](
97 | // fitness: I => Vector[Double],
98 | // values: I => Vector[Double],
99 | // age: monocle.Lens[I, Long])(niche: I => N, mu: Int): Elitism[S, I] =
100 | // (s, population, rng) => {
101 | // def nicheElitism(population: Vector[I]) = NSGA2Operations.elitism[S, I](fitness, values, age)(mu).apply(population)
102 | // byNiche(population, nicheElitism, niche)
103 | // }
104 | //
105 | // def expression[G, I, P](
106 | // values: G => Vector[Double],
107 | // build: (G, P, Vector[Double]) => I)(phenotype: Vector[Double] => P, fitness: P => Vector[Double]): Expression[G, I] =
108 | // (g: G) => {
109 | // val phenotypeValue = phenotype(values(g))
110 | // build(g, phenotypeValue, fitness(phenotypeValue))
111 | // }
112 | //
113 | //}
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/NoisyNSGA3.scala:
--------------------------------------------------------------------------------
1 | package mgo.evolution.algorithm
2 |
3 | //import cats.implicits._
4 | import mgo.evolution._
5 | import mgo.evolution.algorithm.GenomeVectorDouble._
6 | import mgo.evolution.breeding._
7 | import mgo.evolution.elitism._
8 |
9 | import monocle._
10 | import monocle.syntax.all._
11 |
12 | object NoisyNSGA3 {
13 |
14 | import CDGenome._
15 | import NoisyIndividual._
16 |
17 | type NSGA3State = EvolutionState[Unit]
18 |
19 | case class Result[P](continuous: Vector[Double], discrete: Vector[Int], fitness: Vector[Double], replications: Int, individual: Individual[P])
20 |
21 | def result[P: Manifest](population: Vector[Individual[P]], aggregation: Vector[P] => Vector[Double], continuous: Vector[C], discrete: Vector[D], keepAll: Boolean): Vector[Result[P]] = {
22 | val individuals = if (keepAll) population else keepFirstFront(population, fitness(aggregation))
23 |
24 | individuals.map: i =>
25 | val (c, d, f, r) = NoisyIndividual.aggregate(i, aggregation, continuous, discrete)
26 | Result(c, d, f, r, i)
27 |
28 | }
29 |
30 | def result[P: Manifest](nsga3: NoisyNSGA3[P], population: Vector[Individual[P]]): Vector[Result[P]] =
31 | result[P](population, nsga3.aggregation, nsga3.continuous, nsga3.discrete, keepAll = false)
32 |
33 | def fitness[P: Manifest](aggregation: Vector[P] => Vector[Double]): Individual[P] => Vector[Double] =
34 | NoisyNSGA3Operations.aggregated[Individual[P], P](
35 | vectorPhenotype[P].get,
36 | aggregation,
37 | i => i.focus(_.phenotypeHistory).get.length.toDouble)(_)
38 |
39 | def initialGenomes(populationSize: Int, continuous: Vector[C], discrete: Vector[D], reject: Option[Genome => Boolean], rng: scala.util.Random): Vector[Genome] =
40 | CDGenome.initialGenomes(populationSize, continuous, discrete, reject, rng)
41 |
42 | def adaptiveBreeding[S, P: Manifest](
43 | operatorExploration: Double,
44 | cloneProbability: Double,
45 | continuous: Vector[C],
46 | discrete: Vector[D],
47 | aggregation: Vector[P] => Vector[Double],
48 | reject: Option[Genome => Boolean],
49 | lambda: Int = -1): Breeding[S, Individual[P], Genome] =
50 | NoisyNSGA3Operations.adaptiveBreeding[S, Individual[P], Genome, P](
51 | fitness(aggregation),
52 | Focus[Individual[P]](_.genome).get,
53 | continuousValues(continuous).get,
54 | continuousOperator.get,
55 | discreteValues(discrete).get,
56 | discreteOperator.get,
57 | discrete,
58 | buildGenome(discrete),
59 | reject,
60 | operatorExploration,
61 | cloneProbability)
62 |
63 | def expression[P: Manifest](phenotype: (util.Random, IArray[Double], IArray[Int]) => P, continuous: Vector[C], discrete: Vector[D]) =
64 | NoisyIndividual.expression[P](phenotype, continuous, discrete)
65 |
66 | def elitism[S, P: Manifest](mu: Int, references: NSGA3Operations.ReferencePoints, historySize: Int, aggregation: Vector[P] => Vector[Double], components: Vector[C], discrete: Vector[D]): Elitism[S, Individual[P]] = {
67 | def individualValues(i: Individual[P]) = scaledValues(components, discrete)(i.genome)
68 |
69 | NoisyNSGA3Operations.elitism[S, Individual[P]](
70 | fitness[P](aggregation),
71 | individualValues,
72 | mergeHistories(individualValues, vectorPhenotype[P], Focus[Individual[P]](_.historyAge), historySize),
73 | mu,
74 | references)
75 | }
76 |
77 | def reject[P](t: NoisyNSGA3[P]): Option[Genome => Boolean] = NSGA3.reject(t.reject, t.continuous, t.discrete)
78 |
79 | implicit def isAlgorithm[P: Manifest]: Algorithm[NoisyNSGA3[P], Individual[P], Genome, NSGA3State] =
80 | new Algorithm[NoisyNSGA3[P], Individual[P], Genome, NSGA3State] {
81 | override def initialState(t: NoisyNSGA3[P], rng: scala.util.Random) = EvolutionState(s = ())
82 |
83 | override def initialPopulation(t: NoisyNSGA3[P], rng: scala.util.Random, parallel: Algorithm.ParallelContext): Vector[Individual[P]] =
84 | noisy.initialPopulation[Genome, Individual[P]](
85 | NoisyNSGA3.initialGenomes(t.popSize, t.continuous, t.discrete, reject(t), rng),
86 | NoisyNSGA3.expression[P](t.fitness, t.continuous, t.discrete),
87 | rng,
88 | parallel)
89 |
90 | override def step(t: NoisyNSGA3[P]) =
91 | noisy.step[NSGA3State, Individual[P], Genome](
92 | NoisyNSGA3.adaptiveBreeding[NSGA3State, P](t.operatorExploration, t.cloneProbability, t.continuous, t.discrete, t.aggregation, reject(t)),
93 | NoisyNSGA3.expression(t.fitness, t.continuous, t.discrete),
94 | NoisyNSGA3.elitism[NSGA3State, P](
95 | t.popSize, t.referencePoints,
96 | t.historySize,
97 | t.aggregation,
98 | t.continuous,
99 | t.discrete),
100 | Focus[NSGA3State](_.generation),
101 | Focus[NSGA3State](_.evaluated))
102 |
103 | }
104 |
105 | }
106 |
107 | case class NoisyNSGA3[P](
108 | popSize: Int,
109 | referencePoints: NSGA3Operations.ReferencePoints,
110 | fitness: (util.Random, IArray[Double], IArray[Int]) => P,
111 | aggregation: Vector[P] => Vector[Double],
112 | continuous: Vector[C] = Vector.empty,
113 | discrete: Vector[D] = Vector.empty,
114 | historySize: Int = 100,
115 | cloneProbability: Double = 0.2,
116 | operatorExploration: Double = 0.1,
117 | reject: Option[(IArray[Double], IArray[Int]) => Boolean] = None)
118 |
119 | object NoisyNSGA3Operations {
120 |
121 | def aggregated[I, P](fitness: I => Vector[P], aggregation: Vector[P] => Vector[Double], accuracy: I => Double)(i: I): Vector[Double] = {
122 | aggregation(fitness(i)) ++ Vector(1.0 / accuracy(i))
123 | }
124 |
125 | def adaptiveBreeding[S, I, G, P](
126 | fitness: I => Vector[Double],
127 | genome: I => G,
128 | continuousValues: G => IArray[Double],
129 | continuousOperator: G => Option[Int],
130 | discreteValues: G => IArray[Int],
131 | discreteOperator: G => Option[Int],
132 | discrete: Vector[D],
133 | buildGenome: (IArray[Double], Option[Int], IArray[Int], Option[Int]) => G,
134 | reject: Option[G => Boolean],
135 | operatorExploration: Double,
136 | cloneProbability: Double,
137 | lambda: Int = -1): Breeding[S, I, G] = (s, population, rng) =>
138 | // same as deterministic, but eventually adding clones
139 | val breededGenomes = NSGA3Operations.adaptiveBreeding(fitness, genome, continuousValues, continuousOperator, discreteValues, discreteOperator, discrete, buildGenome, reject, operatorExploration, lambda)(s, population, rng)
140 | clonesReplace(cloneProbability, population, genome, randomSelection[S, I])(s, breededGenomes, rng)
141 |
142 | def elitism[S, I](
143 | fitness: I => Vector[Double],
144 | values: I => (IArray[Double], IArray[Int]),
145 | mergeHistories: (Vector[I], Vector[I]) => Vector[I],
146 | mu: Int,
147 | references: NSGA3Operations.ReferencePoints): Elitism[S, I] =
148 | (s, population, candidates, rng) =>
149 | val mergedHistories = mergeHistories(population, candidates)
150 | val filtered: Vector[I] = filterNaN(mergedHistories, fitness)
151 | (s, NSGA3Operations.eliteWithReference[S, I](filtered, fitness, references, mu)(rng))
152 |
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/algorithm/NoisyNichedNSGA2.scala:
--------------------------------------------------------------------------------
1 | //package mgo.evolution.algorithm
2 | //
3 | //import scala.language.higherKinds
4 | //import mgo.evolution._
5 | //import mgo.ranking._
6 | //import mgo.evolution.breeding._
7 | //import mgo.evolution.elitism._
8 | //import mgo.evolution.contexts._
9 | //import tools._
10 | //import cats._
11 | //import cats.data._
12 | //import cats.implicits._
13 | //import GenomeVectorDouble._
14 | //import freedsl.dsl
15 | //import freedsl.tool._
16 | //import monocle.macros._
17 | //
18 | //object noisyNichedNSGA2 extends niche.Imports {
19 | //
20 | // import algorithm.noisynsga2._
21 | //
22 | // def elitism[M[_]: cats.Monad: Random: Generation, N](mu: Int, historySize: Int, aggregation: Vector[Vector[Double]] => Vector[Double], niche: Individual => N): Elitism[M, Individual] =
23 | // noisyNichedNSGA2Operations.elitism[M, Individual, N](
24 | // vectorFitness,
25 | // aggregation,
26 | // (Individual.genome composeLens vectorValues).get,
27 | // Individual.age,
28 | // Individual.historyAge)(niche, mu, historySize)
29 | //
30 | // def result(population: Vector[Individual], aggregation: Vector[Vector[Double]] => Vector[Double], scaling: Vector[Double] => Vector[Double]) =
31 | // noisynsga2.result(population, aggregation, scaling)
32 | //
33 | // object NoisyNSGA2 {
34 | //
35 | // def run[T](rng: util.Random)(f: contexts.run.Implicits => T): T = contexts.run(rng)(f)
36 | // def run[T](state: EvolutionState[Unit])(f: contexts.run.Implicits => T): T = contexts.run(state)(f)
37 | //
38 | // implicit def isAlgorithm[M[_]: Generation: Random: cats.Monad: StartTime, N]: Algorithm[NoisyNichedNSGA2[N], M, Individual, Genome, EvolutionState[Unit]] = new Algorithm[NoisyNichedNSGA2[N], M, Individual, Genome, EvolutionState[Unit]] {
39 | // def initialPopulation(t: NoisyNichedNSGA2[N]) =
40 | // stochasticInitialPopulation[M, Genome, Individual](
41 | // noisynsga2.initialGenomes[M](t.lambda, t.genomeSize),
42 | // noisynsga2.expression(t.fitness))
43 | //
44 | // def step(t: NoisyNichedNSGA2[N]): Kleisli[M, Vector[Individual], Vector[Individual]] =
45 | // noisynsga2Operations.step[M, Individual, Genome](
46 | // noisynsga2.adaptiveBreeding[M](t.lambda, t.operatorExploration, t.cloneProbability, t.aggregation),
47 | // noisynsga2.expression(t.fitness),
48 | // elitism[M, N](t.mu, t.historySize, t.aggregation, t.niche))
49 | //
50 | // def state = noisynsga2.state[M]
51 | // }
52 | // }
53 | //
54 | // case class NoisyNichedNSGA2[N](
55 | // niche: Individual => N,
56 | // mu: Int,
57 | // lambda: Int,
58 | // fitness: (util.Random, Vector[Double]) => Vector[Double],
59 | // aggregation: Vector[Vector[Double]] => Vector[Double],
60 | // genomeSize: Int,
61 | // historySize: Int = 100,
62 | // cloneProbability: Double = 0.2,
63 | // operatorExploration: Double = 0.1)
64 | //
65 | //}
66 | //
67 | //object noisyNichedNSGA2Operations {
68 | //
69 | // def elitism[M[_]: cats.Monad: Random: Generation, I, N](
70 | // history: monocle.Lens[I, Vector[Vector[Double]]],
71 | // aggregation: Vector[Vector[Double]] => Vector[Double],
72 | // values: I => Vector[Double],
73 | // age: monocle.Lens[I, Long],
74 | // historyAge: monocle.Lens[I, Long])(niche: I => N, mu: Int, historySize: Int): Elitism[M, I] = Elitism[M, I] { population =>
75 | //
76 | // def nicheElitism(population: Vector[I]) =
77 | // noisynsga2Operations.elitism[M, I](
78 | // history,
79 | // aggregation,
80 | // values,
81 | // age,
82 | // historyAge)(mu, historySize).apply(population)
83 | //
84 | // byNiche(population, nicheElitism, niche)
85 | // }
86 | //
87 | //}
88 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/cmaes/CMAESBreeding.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2014 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.breed
19 | //
20 | //import mgo.evolution._
21 | //import org.apache.commons.math3.linear.Array2DRowRealMatrix
22 | //
23 | //import scalaz._
24 | //import Scalaz._
25 | //
26 | //import monocle.syntax._
27 | //
28 | //import scala.util.Random
29 | //
30 | //trait CMAESBreeding <: Breeding with GA with CMAESArchive {
31 | // def breed(population: Population[G, P, F], a: A)(implicit rng: Random): Seq[G] = {
32 | // // Generate lambda offspring
33 | // val arz = randn1(genomeSize, lambda)
34 | //
35 | // (0 until lambda).map {
36 | // k =>
37 | // val v = a.xmean.add(a.BD.multiply(arz.getColumnMatrix(k)).scalarMultiply(a.sigma))
38 | // assert(!v.getColumn(0).exists(_.isNaN), a.C.getData.map(_.mkString(",")).mkString("\n"))
39 | // (randomGenome &|-> values set v.getColumn(0)) &|-> randomValues set arz.getColumn(k)
40 | // }
41 | //
42 | // // final RealMatrix arx = zeros(dimension, lambda);
43 | // // final double[] fitness = new double[lambda];
44 | // // final ValuePenaltyPair[] valuePenaltyPairs = new ValuePenaltyPair[lambda];
45 | // // // generate random offspring
46 | // // for (int k = 0; k < lambda; k++) {
47 | // // RealMatrix arxk = null;
48 | // // for (int i = 0; i < checkFeasableCount + 1; i++) {
49 | // // if (diagonalOnly <= 0) {
50 | // // arxk = xmean.add(BD.multiply(arz.getColumnMatrix(k))
51 | // // .scalarMultiply(sigma)); // m + sig * Normal(0,C)
52 | // // } else {
53 | // // arxk = xmean.add(times(diagD,arz.getColumnMatrix(k))
54 | // // .scalarMultiply(sigma));
55 | // // }
56 | // // if (i >= checkFeasableCount ||
57 | // // fitfun.isFeasible(arxk.getColumn(0))) {
58 | // // break;
59 | // // }
60 | // // // regenerate random arguments for row
61 | // // arz.setColumn(k, randn(dimension));
62 | // // }
63 | // // copyColumn(arxk, 0, arx, k);
64 | // // try {
65 | // // valuePenaltyPairs[k] = fitfun.value(arx.getColumn(k)); // compute fitness
66 | // // } catch (TooManyEvaluationsException e) {
67 | // // break generationLoop;
68 | // // }
69 | // // }
70 | // //
71 | // // // Compute fitnesses by adding value and penalty after scaling by value range.
72 | // // double valueRange = valueRange(valuePenaltyPairs);
73 | // // for (int iValue=0;iValue rng.nextGaussian
86 | // }
87 | // new Array2DRowRealMatrix(d, false)
88 | // }
89 | //}
90 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/contexts.scala:
--------------------------------------------------------------------------------
1 | //package mgo.evolution
2 | //
3 | //import java.util.UUID
4 | //
5 | //import cats._
6 | //import cats.implicits._
7 | //import freestyle.tagless._
8 | //import mgo.evolution.algorithm.EvolutionState
9 | //import squants.Time
10 | //import mgo.tagtools._
11 | //
12 | //object contexts {
13 | //
14 | // case class GenerationInterpreter(g: Long) extends Generation.Handler[Evaluated] {
15 | // var generation = g
16 | // def get() = result(generation)
17 | // def increment() = result(generation += 1)
18 | // }
19 | //
20 | // @tagless trait Generation {
21 | // def get(): FS[Long]
22 | // def increment(): FS[Unit]
23 | // }
24 | //
25 | // case class StartTimeInterpreter(startTime: Long) extends StartTime.Handler[Evaluated] {
26 | // def get() = result(startTime)
27 | // }
28 | //
29 | // @tagless trait StartTime {
30 | // def get(): FS[Long]
31 | // }
32 | //
33 | // @tagless trait HitMap {
34 | // def get(): FS[Map[Vector[Int], Int]]
35 | // def set(map: Map[Vector[Int], Int]): FS[Unit]
36 | // }
37 | //
38 | // case class HitMapInterpreter(var map: Map[Vector[Int], Int]) extends HitMap.Handler[Evaluated] {
39 | // def get() = result(map)
40 | // def set(m: Map[Vector[Int], Int]) = result(map = m)
41 | // }
42 | //
43 | // // implicit def convert[M[_]](implicit vhm: VectorHitMap[M]) = new HitMap[M, Vector[Int]] {
44 | // // def get() = vhm.get()
45 | // // def set(map: Map[Vector[Int], Int]) = vhm.set(map)
46 | // // }
47 | //
48 | // trait Archive[M[_], I] {
49 | // def put(i: Seq[I]): M[Unit]
50 | // def get(): M[Vector[I]]
51 | // }
52 | //
53 | // @tagless trait ReachMap {
54 | // def reached(c: Vector[Int]): FS[Boolean]
55 | // def setReached(c: Seq[Vector[Int]]): FS[Unit]
56 | // def get(): FS[Seq[Vector[Int]]]
57 | // }
58 | //
59 | // case class ReachMapInterpreter(map: collection.mutable.HashSet[Vector[Int]]) extends ReachMap.Handler[Evaluated] {
60 | // def reached(c: Vector[Int]) = result(map.contains(c))
61 | // def setReached(c: Seq[Vector[Int]]) = result(map ++= c)
62 | // def get() = result(map.toVector)
63 | // }
64 | //
65 | // import freestyle.tagless._
66 | //
67 | // @tagless trait Random {
68 | // def nextDouble(): FS[Double]
69 | // def nextInt(n: Int): FS[Int]
70 | // def nextBoolean(): FS[Boolean]
71 | // def shuffle[A](s: Vector[A]): FS[Vector[A]]
72 | // def use[T](f: util.Random => T): FS[T]
73 | // }
74 | //
75 | // object RandomInterpreter {
76 | // def apply(seed: Long): RandomInterpreter = new RandomInterpreter(new util.Random(seed))
77 | // def apply(random: util.Random): RandomInterpreter = new RandomInterpreter(random)
78 | // }
79 | //
80 | // class RandomInterpreter(val random: util.Random) extends Random.Handler[mgo.tagtools.Evaluated] {
81 | // def nextDouble() = mgo.tagtools.guard(random.nextDouble())
82 | // def nextInt(n: Int) = mgo.tagtools.guard(random.nextInt(n))
83 | // def nextBoolean() = mgo.tagtools.guard(random.nextBoolean())
84 | // def shuffle[A](s: Vector[A]) = mgo.tagtools.guard(random.shuffle(s))
85 | // def use[T](f: util.Random => T) = mgo.tagtools.guard(f(random))
86 | // }
87 | //
88 | // def multinomial[M[_]: Monad, T](elements: Vector[(T, Double)])(implicit randomM: Random[M]) = {
89 | // def roulette(weights: List[(T, Double)], selected: Double): M[T] =
90 | // weights match {
91 | // case Nil => randomElement[M, (T, Double)](elements).map(_._1)
92 | // case (i, p) :: t =>
93 | // if (selected <= p) i.pure[M]
94 | // else roulette(t, selected - p)
95 | // }
96 | // randomM.nextDouble.flatMap(d => roulette(elements.toList, d))
97 | // }
98 | //
99 | // def randomElement[M[_]: Functor, T](v: Vector[T])(implicit randomM: Random[M]) =
100 | // randomM.nextInt(v.size).map(v.apply)
101 | //
102 | // implicit class RandomDecorator[M[_]](randomM: Random[M]) {
103 | // private implicit def implicitRandomM = randomM
104 | // def multinomial[T](v: Vector[(T, Double)])(implicit monad: Monad[M]) = contexts.multinomial[M, T](v)
105 | // def randomElement[T](v: Vector[T])(implicit functor: Functor[M]) = contexts.randomElement[M, T](v)
106 | // }
107 | //
108 | // @tagless trait IO {
109 | // def apply[A](f: () => A): FS[A]
110 | // }
111 | //
112 | // case class IOInterpreter() extends IO.Handler[mgo.tagtools.Evaluated] {
113 | // def apply[A](f: () => A) = mgo.tagtools.guard(f())
114 | // }
115 | //
116 | // @tagless trait System {
117 | // def randomUUID(): FS[UUID]
118 | // def sleep(duration: Time): FS[Unit]
119 | // def currentTime(): FS[Long]
120 | // }
121 | //
122 | // case class SystemInterpreter() extends System.Handler[mgo.tagtools.Evaluated] {
123 | // def randomUUID() = mgo.tagtools.guard(UUID.randomUUID())
124 | // def sleep(d: Time) = mgo.tagtools.guard(Thread.sleep(d.millis))
125 | // def currentTime() = mgo.tagtools.guard(java.lang.System.currentTimeMillis())
126 | // }
127 | //
128 | // object run {
129 | // object Implicits {
130 | // def apply[S](state: EvolutionState[S]): Implicits =
131 | // Implicits()(GenerationInterpreter(state.generation), RandomInterpreter(state.random), StartTimeInterpreter(state.startTime), IOInterpreter(), SystemInterpreter())
132 | //
133 | // }
134 | // case class Implicits(implicit generationInterpreter: GenerationInterpreter, randomInterpreter: RandomInterpreter, startTimeInterpreter: StartTimeInterpreter, iOInterpreter: IOInterpreter, systemInterpreter: SystemInterpreter)
135 | //
136 | // def apply[T](rng: util.Random)(f: Implicits => T): T = {
137 | // val state = EvolutionState[Unit](random = rng, s = ())
138 | // apply(state)(f)
139 | // }
140 | //
141 | // def apply[T, S](state: EvolutionState[S])(f: Implicits => T): T = f(Implicits(state))
142 | // }
143 | //
144 | //}
145 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/diversity.scala:
--------------------------------------------------------------------------------
1 | package mgo.evolution
2 |
3 | import cats._
4 | import cats.data._
5 | import cats.implicits._
6 |
7 | import mgo.tools.metric._
8 |
9 | /**
10 | * Layer of the cake that compute a diversity metric for a set of values
11 | */
12 | object diversity:
13 |
14 | /** Compute the diversity metric of the values */
15 | type Diversity[M[_], I] = Kleisli[M, Vector[I], Vector[Later[Double]]]
16 | object Diversity:
17 | def apply[M[_]: cats.Monad, I](f: Vector[I] => M[Vector[Later[Double]]]): Diversity[M, I] = Kleisli[M, Vector[I], Vector[Later[Double]]](f)
18 |
19 | /* def closedCrowdingDistance(implicit mg: Fitness[Seq[Double]]) = new Diversity {
20 | override def apply(values: Pop) =
21 | State.state { ClosedCrowdingDistance(values.map(e => mg(e))) }
22 | }*/
23 |
24 | def crowdingDistance[I](population: Vector[I], fitness: I => Vector[Double]): Vector[Double] = CrowdingDistance(population.map(fitness))
25 |
26 | def hypervolumeContribution[M[_]: cats.Monad, I](referencePoint: Vector[Double], fitness: I => Vector[Double]): Diversity[M, I] =
27 | Diversity((values: Vector[I]) => Hypervolume.contributions(values.map(e => fitness(e)), referencePoint).pure[M])
28 |
29 | def KNearestNeighbours[M[_], I](k: Int, fitness: I => Vector[Double])(implicit MM: cats.Monad[M]): Diversity[M, I] =
30 | Diversity((values: Vector[I]) => KNearestNeighboursAverageDistance(values.map(e => fitness(e)), k).pure[M])
31 |
32 |
33 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/dominance.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | package mgo.evolution
18 |
19 | object dominance:
20 | /**
21 | * Dominance type between 2 solution
22 | */
23 | trait Dominance:
24 | def isDominated(p1: Seq[Double], p2: Seq[Double]): Boolean
25 |
26 | /**
27 | * A point dominates another if the other is not better on any objective
28 | */
29 | lazy val nonStrictDominance: Dominance =
30 | (p1: Seq[Double], p2: Seq[Double]) =>
31 | val dominated =
32 | import scala.util.boundary
33 | var equal = true
34 | boundary[Boolean]:
35 | for (g1, g2) <- p1 lazyZip p2
36 | do
37 | if g1 < g2 then boundary.break(false)
38 | if g1 != g2 then equal = false
39 |
40 | !equal
41 |
42 | dominated
43 |
44 |
45 | /**
46 | * A point dominates another if all its objective are better
47 | */
48 | lazy val strictDominance: Dominance = new Dominance:
49 | override def isDominated(p1: Seq[Double], p2: Seq[Double]): Boolean =
50 | (p1 zip p2).forall { case (g1, g2) => g2 < g1 }
51 |
52 | /**
53 | * A point is dominated if all its objectif are above another point in a range
54 | * of epsilon A.G. Hernández-Díaz, L.V. Santana-Quintero, C.A.C. Coello, and
55 | * J.M. Luque, "Pareto-adaptive epsilon-dominance",
56 | * presented at Evolutionary Computation, 2007, pp.493-517.
57 | */
58 | def nonStrictEpsilonDominance(epsilons: Seq[Double]): Dominance = new Dominance:
59 | override def isDominated(p1: Seq[Double], p2: Seq[Double]): Boolean =
60 | !(p1 zip p2 zip epsilons).exists {
61 | case (((g1, g2), e)) => g2 > e + g1
62 | }
63 |
64 | def strictEpsilonDominance(epsilons: Seq[Double]): Dominance = new Dominance:
65 | override def isDominated(p1: Seq[Double], p2: Seq[Double]): Boolean =
66 | (p1 zip p2 zip epsilons).forall:
67 | case (((g1, g2), e)) => g1 > g2 + e
68 |
69 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/elitism.scala:
--------------------------------------------------------------------------------
1 | package mgo.evolution
2 |
3 | import cats.Order
4 | import cats.implicits._
5 | import mgo.evolution.algorithm.HitMap
6 | import mgo.evolution.diversity.crowdingDistance
7 | import mgo.tools._
8 |
9 | object elitism:
10 |
11 | type Elitism[S, I] = (S, Vector[I], Vector[I], scala.util.Random) => (S, Vector[I])
12 |
13 | // object Elitism {
14 | // def apply[M[_]: cats.Monad, I](f: (Vector[I], Vector[I]) => M[Vector[I]]): Elitism[M, I] = Kleisli[M, (Vector[I], Vector[I]), Vector[I]](Function.tupled(f))
15 | // }
16 |
17 | // def minimiseO[M[_]: Applicative, I, F](f: I => F, mu: Int)(implicit MM: cats.Monad[M], FO: Order[F]): Elitism[M, I] =
18 | // Elitism[M, I](individuals => individuals.sorted(FO.contramap[I](f).toOrdering).take(mu).pure[M])
19 | //
20 | // def maximiseO[M[_]: Applicative, I, F](f: I => F, mu: Int)(implicit MM: cats.Monad[M], FO: Order[F]): Elitism[M, I] =
21 | // Elitism(individuals => individuals.sorted(Order.reverse(FO.contramap[I](f)).toOrdering).take(mu).pure[M])
22 | //
23 | // /** Returns n individuals randomly. */
24 | // def randomO[M[_]: cats.Monad, I](n: Int)(implicit randomM: Random[M]): Elitism[M, I] =
25 | // Elitism(
26 | // individuals => Vector.fill(n)(randomM.randomElement(individuals)).sequence)
27 |
28 | def maximiseO[I, F](f: I => F, mu: Int)(implicit FO: Order[F]): Vector[I] => Vector[I] = (individuals: Vector[I]) =>
29 | individuals.sorted(Order.reverse(FO.contramap[I](f)).toOrdering).take(mu)
30 |
31 | // def randomO[M[_]: cats.Applicative, I](n: Int)(implicit randomM: Random[M]) =
32 | // (individuals: Vector[I]) => Vector.fill(n)(randomM.randomElement(individuals)).sequence
33 |
34 | // def incrementGeneration[M[_]: Generation] = Generation[M].increment
35 |
36 | def addHits[I](cell: I => Vector[Int], population: Vector[I], hitmap: HitMap): HitMap =
37 | def hits(map: HitMap, c: Vector[Int]) = map.updated(c, map.getOrElse(c, 0) + 1)
38 | population.foldLeft(hitmap)((m, i) => hits(m, cell(i)))
39 |
40 | /** Returns the mu individuals with the highest ranks. */
41 | def keepHighestRanked[I, K: Order as KO](population: Vector[I], ranks: Vector[K], mu: Int): Vector[I] =
42 | if population.size < mu
43 | then population
44 | else
45 | val sortedBestToWorst = (population zip ranks).sortBy { _._2 }(using Order.reverse(KO).toOrdering).map { _._1 }
46 | sortedBestToWorst.take(mu)
47 |
48 | def nicheElitism[I, N](population: Vector[I], keep: Vector[I] => Vector[I], niche: I => N): Vector[I] =
49 | val niches = population.groupBy(niche).toVector
50 | niches.flatMap { case (_, individuals) => keep(individuals) }
51 |
52 | def keepFirstFront[I](population: Vector[I], fitness: I => Vector[Double]): Vector[I] =
53 | if population.isEmpty
54 | then population
55 | else
56 | val dominating = ranking.numberOfDominating(fitness, population)
57 | val minDominating = dominating.map(_.value).min
58 | (population zip dominating).filter((_, d) => d.value == minDominating).map(_._1)
59 |
60 | def keepOnFirstFront[I](population: Vector[I], fitness: I => Vector[Double], mu: Int, random: scala.util.Random): Vector[I] =
61 | val first = keepFirstFront(population, fitness)
62 | val crowding = crowdingDistance(first, fitness)
63 | val res = keepHighestRanked(first, crowding, mu)
64 | res
65 |
66 | //type UncloneStrategy[M[_], I] = Vector[I] => M[I]
67 |
68 | /**** Clone strategies ****/
69 |
70 | // def applyCloneStrategy[M[_]: cats.Monad, I, G](getGenome: I => G, cloneStrategy: UncloneStrategy[M, I]): Elitism[M, I] = {
71 | // import mgo.tools._
72 | // def unclone(clones: Vector[I]) =
73 | // if (clones.size == 1) clones.head.pure[M]
74 | // else cloneStrategy(clones)
75 | //
76 | // Elitism(_.groupByOrdered(getGenome).valuesIterator.map(_.toVector).toVector.traverse(unclone))
77 | // }
78 | //
79 | // def keepOldest[M[_]: cats.Monad, I](age: I => Long): UncloneStrategy[M, I] =
80 | // (clones: Vector[I]) => clones.maxBy(age).pure[M]
81 | //
82 | // def keepFirst[M[_]: cats.Monad, I]: UncloneStrategy[M, I] =
83 | // (clones: Vector[I]) => clones.head.pure[M]
84 |
85 | def keepRandomElementInNiches[I, N: ImplementEqualMethod as eqm](niche: I => N, random: scala.util.Random): Vector[I] => Vector[I] = individuals =>
86 | val indivsByNiche = individuals.groupByOrdered(niche)
87 | indivsByNiche.values.toVector.map(_.toVector).flatMap: individuals =>
88 | if individuals.isEmpty
89 | then individuals
90 | else Vector(individuals(random.nextInt(individuals.size)))
91 |
92 | def keepNiches[I, N: ImplementEqualMethod as eqm](niche: I => N, keep: Vector[I] => Vector[I]): Vector[I] => Vector[I] =
93 | (individuals: Vector[I]) =>
94 | val indivsByNiche = individuals.groupByOrdered(niche)
95 | indivsByNiche.values.toVector.map(_.toVector).flatMap(keep.apply)
96 |
97 | def keepFirst[G: ImplementEqualMethod as eqm, I](genome: I => G)(population: Vector[I], newIndividuals: Vector[I]): Vector[I] =
98 | val filteredClone =
99 | val existingGenomes = population.map(genome).map(eqm.apply).toSet
100 | newIndividuals.filter: i =>
101 | !existingGenomes.contains(eqm(genome(i)))
102 |
103 | population ++ filteredClone
104 |
105 | def mergeHistories[G: ImplementEqualMethod, I, P](genome: I => G, history: monocle.Lens[I, Vector[P]], historyAge: monocle.Lens[I, Long], historySize: Int): (Vector[I], Vector[I]) => Vector[I] =
106 | (population: Vector[I], newIndividuals: Vector[I]) =>
107 | val mergedClones =
108 | val indexedNI = newIndividuals.groupByOrdered(genome)
109 |
110 | for
111 | i <- population
112 | clones = indexedNI.getOrElse(ImplementEqualMethod(genome(i)), List())
113 | yield
114 | val additionalHistory = clones.flatMap(history.get)
115 | history.modify(h => (h ++ additionalHistory).takeRight(historySize)) andThen
116 | historyAge.modify(_ + additionalHistory.size) apply i
117 |
118 | val filteredClone =
119 | val filter = population.map(genome andThen ImplementEqualMethod.apply).toSet
120 | newIndividuals.filter(i => !filter.contains(ImplementEqualMethod(genome(i))))
121 |
122 | mergedClones ++ filteredClone
123 |
124 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATAnyTopology.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.breed
19 | //
20 | //import mgo.evolution._
21 | //import util.Random
22 | //import mgo.evolution.genome.RandomGenome
23 | //import mgo.evolution.genome.NEATGenome
24 | //import mgo.evolution.archive.NEATArchive
25 | //import collection.immutable.IntMap
26 | //
27 | //trait NEATAnyTopology extends NEATGenome {
28 | //
29 | // def mutationAddLinkBiasProb: Double
30 | //
31 | // def pickNodesAddLink(genome: G)(implicit rng: Random): Option[(Int, Int)] = {
32 | // val connections =
33 | // IntMap[Seq[Int]](
34 | // genome.connectionGenes.map { cg => cg.inNode -> cg.outNode }
35 | // .groupBy { (_: (Int, Int))._1 }
36 | // .mapValues { (_: Seq[(Int, Int)]).map { _._2 } }.toSeq: _*)
37 | //
38 | // val pair: Option[(Int, Int)] =
39 | // (if (rng.nextDouble() < mutationAddLinkBiasProb)
40 | // rng.shuffle(biasNodesIndices.iterator)
41 | // else
42 | // rng.shuffle(genome.nodes.keysIterator))
43 | // .flatMap { u => rng.shuffle(genome.nodes.keysIterator).map { v => (u, v) } }
44 | // .find {
45 | // case (u: Int, v: Int) =>
46 | // !connections(u).contains(v)
47 | // }
48 | //
49 | // pair
50 | // }
51 | //}
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATArchive.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | //package mgo.evolution.archive
19 |
20 | //import mgo.evolution._
21 | //import mgo.evolution.genome.NEATGenome
22 | //
23 | //import scala.util.Random
24 | //import scala.collection.immutable.Queue
25 | //import collection.immutable.IntMap
26 | //import math._
27 |
28 | /*trait NEATArchive extends Archive with NEATGenome with DoubleFitness {
29 |
30 | case class Archive(
31 | // to maintain a record of innovation throughout generations would require to make the whole Evolution stateful
32 | // so that innovations created at the breeding stage can be added. Let's just record the innovations for the
33 | // current generation at the breeding stage only (like in Stanley's original paper).
34 | //recordOfInnovations: Seq[NEATGenome.Innovation],
35 | indexOfSpecies: IntMap[G],
36 | lastEntirePopulationFitnesses: Queue[Double],
37 | speciesCompatibilityThreshold: List[Double])
38 |
39 | type A = Archive
40 |
41 | def numberSpeciesTarget: Int
42 | def speciesCompatibilityThreshold: Double
43 | def speciesCompatibilityMod: Double
44 | def speciesCompatibilityMin: Double
45 |
46 | def initialArchive(implicit rng: Random): A =
47 | Archive(
48 | //0,
49 | //0,
50 | //Vector[NEATGenome.Innovation](),
51 | IntMap[G](),
52 | Queue[Double](),
53 | List[Double](speciesCompatibilityThreshold))
54 |
55 | def archive(a: A, oldIndividuals: Population[G, P, F], offsprings: Population[G, P, F])(implicit rng: Random): A = {
56 | val indivsBySpecies: IntMap[Seq[G]] = IntMap.empty ++ offsprings.toIndividuals.map { _.genome }.groupBy { g => g.species }
57 | val newios: IntMap[G] =
58 | indivsBySpecies.map { case (sp, indivs) => (sp, indivs(rng.nextInt(indivs.length))) }
59 | val numberOfSpecies = newios.size
60 | val lastsct = a.speciesCompatibilityThreshold.head
61 | val newsct =
62 | max(
63 | speciesCompatibilityMin,
64 | if (numberOfSpecies < numberSpeciesTarget)
65 | lastsct - speciesCompatibilityMod
66 | else lastsct + speciesCompatibilityMod)
67 | Archive(
68 | //globalInnovationNumber = offsprings.content.flatMap { _.genome.connectionGenes }.map { _.innovation.number }.max,
69 | /* recordOfInnovation contains the unique innovations of offsprings*/
70 | //recordOfInnovations = offsprings.content.flatMap { _.genome.connectionGenes }.map { _.innovation }.distinct,
71 | /** The index of species represents each species by a random genome of the corresponding species of the past generation. */
72 | indexOfSpecies = newios,
73 | lastEntirePopulationFitnesses =
74 | a.lastEntirePopulationFitnesses.enqueue(offsprings.content.map {
75 | _.fitness
76 | }.sum / offsprings.content.size),
77 | newsct :: a.speciesCompatibilityThreshold
78 | )
79 | }
80 | }*/
81 |
82 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATBreedingContext.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 16/09/2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.evolution.breed
18 | //
19 | //import mgo.evolution.archive.NEATArchive
20 | //import mgo.evolution.genome.NEATGenome.LinkInnovation
21 | //import mgo.{ Population, Individual }
22 | //import mgo.evolution.genome.{ NEATGenome }
23 | //
24 | //import scala.collection.immutable.IntMap
25 | //import scala.math._
26 | //import scala.util.Random
27 | //import scalaz._
28 | //import Scalaz._
29 | //
30 | //trait NEATBreedingContext extends BreedingContext with NEATGenome with NEATArchive {
31 | //
32 | // type BreedingState = (Int, //global innovation number
33 | // Int, //global node number
34 | // Seq[Innovation], //record of innovations
35 | // IntMap[G]) //index of species
36 | //
37 | // type BreedingContext[Z] = State[BreedingState, Z]
38 | //
39 | // override implicit def monadBreedingContext: cats.Monad[BreedingContext] = new cats.Monad[BreedingContext] {
40 | // def bind[A, B](fa: BreedingContext[A])(f: (A) ⇒ BreedingContext[B]): BreedingContext[B] = State({ s1 =>
41 | // val (s2, a) = fa.run(s1)
42 | // val (s3, b) = f(a).run(s2)
43 | // (s3, b)
44 | // })
45 | //
46 | // def point[A](a: ⇒ A): BreedingContext[A] = State(s => (s, a))
47 | // }
48 | //
49 | // /** extract the content from the breeding monad */
50 | // override def unwrapBreedingContext[Z](x: BreedingContext[Z], population: Population[G, P, F], archive: A): Z = {
51 | //
52 | // // First, compute the initial state from the population and archive
53 | //
54 | // val (globalInnovationNumber, globalNodeNumber) = population.foldLeft((0, 0)) {
55 | // case (acc, indiv) =>
56 | // val (gin, gnn) = acc
57 | // val newgin = if (indiv.genome.connectionGenes.isEmpty) 0 else max(gin, indiv.genome.connectionGenes.map {
58 | // _.innovation
59 | // }.max)
60 | // val newgnn = max(gnn, indiv.genome.lastNodeId)
61 | // (newgin, newgnn)
62 | // }
63 | //
64 | // val initialState = (globalInnovationNumber, globalNodeNumber, Seq.empty, archive.indexOfSpecies)
65 | //
66 | // x.run(initialState)._2
67 | // }
68 | //
69 | //}
70 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATCrossover.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 17/09/2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.evolution.crossover
18 | //
19 | //import mgo.fitness.DoubleFitness
20 | //import mgo.{ Individual, Population }
21 | //import mgo.evolution.genome.NEATGenomesAlign
22 | //import mgo.evolution.genome.NEATGenome
23 | //import mgo.evolution.genome.NEATGenome.Genome
24 | //
25 | //import scalaz._
26 | //import Scalaz._
27 | //
28 | //import scala.collection.immutable.IntMap
29 | //import scala.util.Random
30 | //
31 | //trait NEATCrossover <: Crossover with NEATGenomesAlign with NEATGenome with DoubleFitness {
32 | //
33 | // def crossoverInheritDisabledProb: Double
34 | //
35 | // override def crossover(indivs: Seq[Individual[G, P, F]], population: Population[G, P, F], archive: A)(implicit rng: Random): BreedingContext[Vector[G]] = {
36 | // val Vector(i1, i2) = indivs
37 | //
38 | // // - genes that match no other gene in the other genome are disjoint if they occur within the range of the the other genome innovation numbers
39 | // // - they are excess if they occur outside this range.
40 | // val aligned = alignGenomes(i1.genome.connectionGenes, i2.genome.connectionGenes)
41 | //
42 | // // - a new offspring is composed by chosing each matching genes randomly from either parent.
43 | // // - excess or disjoint genes from the fittest parent are included.
44 | // // - if the parents fitnesses are the same, inherit from the smallest genome or randomly
45 | // val fittest = if (i1.fitness == i2.fitness) 0 else if (i1.fitness > i2.fitness) 1 else 2
46 | //
47 | // val newconnectiongenes =
48 | // aligned.foldLeft(Seq[ConnectionGene]()) { (acc, pair) =>
49 | // pair match {
50 | // case (None, Some(cg2), _) =>
51 | // if (fittest == 1) acc
52 | // else if (fittest == 2) acc :+ cg2
53 | // else if (rng.nextBoolean()) acc else acc :+ cg2
54 | //
55 | // case (Some(cg1), None, _) =>
56 | // if (fittest == 1) acc :+ cg1
57 | // else if (fittest == 2) acc
58 | // else if (rng.nextBoolean()) acc :+ cg1 else acc
59 | //
60 | // case (Some(cg1), Some(cg2), _) =>
61 | // val inherit =
62 | // if (fittest == 1) cg1
63 | // else if (fittest == 2) cg2
64 | // else if (rng.nextBoolean()) cg1 else cg2
65 | //
66 | // // If one gene is disabled, the offspring gene is more likely to be disabled
67 | // val disable =
68 | // if (!(cg1.enabled && cg2.enabled)) rng.nextDouble() < crossoverInheritDisabledProb
69 | // else false
70 | //
71 | // if (disable) acc :+ inherit.copy(enabled = false)
72 | // else acc :+ inherit
73 | //
74 | // case (None, None, _) => acc
75 | // }
76 | // }.toVector
77 | //
78 | // // Include at least one node from each parent. Also include one for each input, output, and bias node that are not connected.
79 | // val newnodesidx =
80 | // (newconnectiongenes.flatMap { cg => Seq(cg.inNode, cg.outNode) } ++
81 | // (inputNodesIndices ++ biasNodesIndices ++ outputNodesIndices)
82 | // ).toSet
83 | // val newnodes = IntMap(newnodesidx.toSeq.map { i =>
84 | // i -> {
85 | // if (!i1.genome.nodes.contains(i)) i2.genome.nodes(i)
86 | // else if (!i2.genome.nodes.contains(i)) i1.genome.nodes(i)
87 | // else if (rng.nextBoolean()) i1.genome.nodes(i) else i2.genome.nodes(i)
88 | // }
89 | // }: _*)
90 | //
91 | // if (newconnectiongenes.exists(cg1 => {
92 | // if (newnodes(cg1.inNode).level >= newnodes(cg1.outNode).level) {
93 | // println(cg1)
94 | // true
95 | // } else false
96 | // })) throw new RuntimeException(s"Backwards!! $newconnectiongenes $newnodes\n$i1\n$i2")
97 | // val result = Genome(
98 | // connectionGenes = newconnectiongenes,
99 | // nodes = newnodes,
100 | // // Give the offspring its parents species. Species will be reattributed at the postBreeding
101 | // // stage anyway. This will just be used as a hint to speed up species attribution
102 | // species = i1.genome.species,
103 | // lastNodeId = newnodes.lastKey)
104 | //
105 | // Vector(result).point[BreedingContext]
106 | // }
107 | //}
108 | //
109 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATFeedforwardTopology.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.breed
19 | //
20 | //import mgo.evolution._
21 | //import util.Random
22 | //import mgo.evolution.genome.RandomGenome
23 | //import mgo.evolution.genome.NEATGenome
24 | //import mgo.evolution.archive.NEATArchive
25 | //import collection.immutable.IntMap
26 | //
27 | //trait NEATFeedforwardTopology extends NEATGenome {
28 | //
29 | // def mutationAddLinkBiasProb: Double
30 | //
31 | // def pickNodesAddLink(genome: G)(implicit rng: Random): Option[(Int, Int)] = {
32 | // val connections =
33 | // IntMap[Seq[Int]](
34 | // genome.connectionGenes.map { cg => cg.inNode -> cg.outNode }
35 | // .groupBy { (_: (Int, Int))._1 }
36 | // .mapValues { (_: Seq[(Int, Int)]).map { _._2 } }.toSeq: _*)
37 | // // Have a chance to force the in node to be a bias node
38 | // val pair: Option[(Int, Int)] =
39 | // (if (rng.nextDouble() < mutationAddLinkBiasProb)
40 | // rng.shuffle(biasNodesIndices.iterator)
41 | // else
42 | // rng.shuffle(genome.nodes.keysIterator))
43 | // .flatMap { u => rng.shuffle(genome.nodes.keysIterator).map { v => (u, v) } }
44 | // .find {
45 | // case (u: Int, v: Int) =>
46 | // (!(connections.contains(u) && connections(u).contains(v))) &&
47 | // (genome.nodes(u).level < genome.nodes(v).level)
48 | // }
49 | //
50 | // pair
51 | // }
52 | //}
53 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATGenome.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.genome
19 | //
20 | //import scala.util.Random
21 | //import monocle.Lens
22 | //import mgo.evolution._
23 | //import mgo.evolution.breed._
24 | //import collection.immutable.IntMap
25 | //
26 | //object NEATGenome {
27 | //
28 | // case class Genome[+NODEDATA](
29 | // connectionGenes: Seq[ConnectionGene],
30 | // nodes: IntMap[Node[NODEDATA]],
31 | // species: Int,
32 | // lastNodeId: Int) {
33 | //
34 | // override def toString: String = s"Species $species; ${connectionGenes.toString}; $nodes"
35 | //
36 | // }
37 | //
38 | // case class ConnectionGene(
39 | // inNode: Int,
40 | // outNode: Int,
41 | // weight: Double,
42 | // enabled: Boolean,
43 | // innovation: Int) {
44 | // override def toString: String = f"$inNode%d${if (enabled) "-" else "X"}%s>$outNode%d($weight%.2f);$innovation%s"
45 | // }
46 | //
47 | // sealed trait Node[+NODEDATA] { val level: Double; val data: NODEDATA }
48 | // case class InputNode[+NODEDATA](data: NODEDATA, level: Double = 0) extends Node[NODEDATA]
49 | // case class OutputNode[+NODEDATA](data: NODEDATA, level: Double = 1) extends Node[NODEDATA]
50 | // case class HiddenNode[+NODEDATA](data: NODEDATA, level: Double) extends Node[NODEDATA]
51 | // case class BiasNode[+NODEDATA](data: NODEDATA, level: Double = 0) extends Node[NODEDATA]
52 | //
53 | // sealed trait Innovation
54 | // case class NodeInnovation[+NODEDATA](innovnum1: Int, innovnum2: Int, newnodeId: Int, newnode: Node[NODEDATA], inNode: Int, outNode: Int) extends Innovation
55 | // case class LinkInnovation(innovnum: Int, inNode: Int, outNode: Int) extends Innovation
56 | //
57 | //}
58 | //
59 | ///**
60 | // * Genome for NEAT
61 | // */
62 | //trait NEATGenome extends G {
63 | // type NODEDATA
64 | // type G = NEATGenome.Genome[NODEDATA]
65 | // type Node = NEATGenome.Node[NODEDATA]
66 | // type InputNode = NEATGenome.InputNode[NODEDATA]
67 | // type OutputNode = NEATGenome.OutputNode[NODEDATA]
68 | // type HiddenNode = NEATGenome.HiddenNode[NODEDATA]
69 | // type BiasNode = NEATGenome.BiasNode[NODEDATA]
70 | // type Innovation = NEATGenome.Innovation
71 | // type NodeInnovation = NEATGenome.NodeInnovation[NODEDATA]
72 | // type LinkInnovation = NEATGenome.LinkInnovation
73 | // type ConnectionGene = NEATGenome.ConnectionGene
74 | //
75 | // def inputNodes: Int
76 | // def outputNodes: Int
77 | // def biasNodes: Int
78 | //
79 | // def inputNodesIndices: Range = 0 until inputNodes
80 | // def biasNodesIndices: Range = inputNodes until inputNodes + biasNodes
81 | // def outputNodesIndices: Range = inputNodes + biasNodes until inputNodes + biasNodes + outputNodes
82 | //
83 | //}
84 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATGenomesAlign.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 18/09/2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.evolution.genome
18 | //
19 | //import mgo.evolution.genome.NEATGenome.ConnectionGene
20 | //
21 | //import scala.annotation.tailrec
22 | //
23 | //trait NEATGenomesAlign {
24 | //
25 | // /**Enum object for alignment state*/
26 | // object AlignmentInfo extends Enumeration {
27 | // type AlignmentInfo = Value
28 | // val Aligned, Excess, Disjoint = Value
29 | // }
30 | //
31 | // import AlignmentInfo._
32 | //
33 | // /**
34 | // * Returns a list of aligned connection genes. The first two elements of each tuple give aligned genes, (or None for unmatching genes) and the third is set to 0 when the genes are
35 | // * aligned, 1 for an excess genes, and 2 for disjoint genes
36 | // */
37 | // @tailrec final def alignGenomes(
38 | // cg1: Seq[ConnectionGene],
39 | // cg2: Seq[ConnectionGene],
40 | // acc: List[(Option[ConnectionGene], Option[ConnectionGene], AlignmentInfo)] = List.empty): List[(Option[ConnectionGene], Option[ConnectionGene], AlignmentInfo)] = {
41 | // if (cg1.isEmpty && cg2.isEmpty)
42 | // acc
43 | // else if (cg1.isEmpty)
44 | // alignGenomes(Seq.empty, cg2.tail,
45 | // (None, Some(cg2.head), Disjoint) :: acc)
46 | // else if (cg2.isEmpty)
47 | // alignGenomes(cg1.tail, Seq.empty,
48 | // (Some(cg1.head), None, Disjoint) :: acc)
49 | // else if (cg1.head.innovation == cg2.head.innovation)
50 | // alignGenomes(cg1.tail, cg2.tail,
51 | // (Some(cg1.head), Some(cg2.head), Aligned) :: acc)
52 | // else if (cg1.head.innovation < cg2.head.innovation)
53 | // alignGenomes(cg1.tail, cg2,
54 | // (Some(cg1.head), None, Excess) :: acc)
55 | // else
56 | // alignGenomes(cg1, cg2.tail,
57 | // (None, Some(cg2.head), Excess) :: acc)
58 | // }
59 | //}
60 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATInitialGenome.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 17/09/2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.evolution.genome
18 | //
19 | //import mgo.evolution.breed.NEATBreedingContext
20 | //import mgo.evolution.genome.NEATGenome.{ ConnectionGene, Genome }
21 | //
22 | //import scala.collection.immutable.IntMap
23 | //import scala.math._
24 | //import scala.util.Random
25 | //import scalaz.State
26 | //
27 | //trait NEATInitialGenome <: InitialGenome with NEATGenome with NEATBreedingContext {
28 | //
29 | // /**
30 | // *
31 | // * The introduction of an initial genome should harmonize the breeding state and the new genome by:
32 | // * - setting the global innovation number to the highest genome's innovation number, if it's higher than the current global innovation number,
33 | // * - same with the global node number,
34 | // * - setting the genome's species to one found in the index of species if any, and add the genome to the index otherwise.
35 | // */
36 | // def initialGenome(implicit rng: Random): BreedingContext[G] = {
37 | // val g: G = minimalGenome(rng)
38 | //
39 | // State(s => {
40 | // val (gin, gnn, roi, ios) = s
41 | //
42 | // val newgin =
43 | // if (g.connectionGenes.isEmpty) gin
44 | // else
45 | // max(
46 | // gin,
47 | // g.connectionGenes.map { _.innovation }.max
48 | // )
49 | //
50 | // val newgnn = max(
51 | // gnn,
52 | // g.lastNodeId
53 | // )
54 | //
55 | // // This initial minimal genome is always species 0. So just add it to the intmap if not there
56 | // val newios = if (ios.contains(0)) ios else ios + ((0, g))
57 | //
58 | // ((newgin, newgnn, roi, newios), g)
59 | //
60 | // }
61 | // )
62 | // }
63 | //
64 | // def minimalGenome(implicit rng: Random): G
65 | //}
66 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATMinimalGenomeConnectedIO.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.genome
19 | //
20 | //import mgo.evolution.breed.NEATBreedingContext
21 | //
22 | //import collection.immutable.IntMap
23 | //import util.Random
24 | //import math._
25 | //
26 | //import scalaz._
27 | //import Scalaz._
28 | //
29 | //import NEATGenome._
30 | //
31 | //trait NEATMinimalGenomeConnectedIO <: NEATInitialGenome {
32 | //
33 | // override def minimalGenome(implicit rng: Random): G =
34 | // Genome(
35 | // connectionGenes =
36 | // inputNodesIndices.flatMap { u =>
37 | // outputNodesIndices.map {
38 | // (u, _)
39 | // }
40 | // }
41 | // .zipWithIndex.map {
42 | // case ((u, v), i) =>
43 | // ConnectionGene(
44 | // inNode = u,
45 | // outNode = v,
46 | // weight = max(min(mutationWeightHardMax, rng.nextGaussian() * mutationWeightSigma), mutationWeightHardMin),
47 | // enabled = true,
48 | // innovation = i)
49 | // },
50 | // nodes =
51 | // IntMap(
52 | // inputNodesIndices.map {
53 | // _ -> newInputNode
54 | // }
55 | // ++ biasNodesIndices.map {
56 | // _ -> newBiasNode
57 | // }
58 | // ++ outputNodesIndices.map {
59 | // _ -> newOutputNode
60 | // }.toSeq: _*),
61 | // species = 0,
62 | // lastNodeId = inputNodes + biasNodes + outputNodes - 1
63 | // )
64 | //
65 | // def newInputNode: InputNode
66 | // def newBiasNode: BiasNode
67 | // def newOutputNode: OutputNode
68 | //
69 | // def mutationWeightHardMin: Double
70 | // def mutationWeightHardMax: Double
71 | // def mutationWeightSigma: Double
72 | //
73 | //}
74 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATMinimalGenomeUnconnected.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.genome
19 | //
20 | //import collection.immutable.IntMap
21 | //import scala.util.Random
22 | //
23 | //import scalaz._
24 | //import Scalaz._
25 | //
26 | //import NEATGenome._
27 | //
28 | //trait NEATMinimalGenomeUnconnected <: NEATInitialGenome {
29 | //
30 | // override def minimalGenome(implicit rng: Random): G = mg
31 | //
32 | // lazy val mg: G =
33 | // Genome(
34 | // connectionGenes = Seq[ConnectionGene](),
35 | // nodes =
36 | // IntMap(
37 | // inputNodesIndices.map { _ -> newInputNode }
38 | // ++ biasNodesIndices.map { _ -> newBiasNode }
39 | // ++ outputNodesIndices.map { _ -> newOutputNode }.toSeq: _*),
40 | // species = 0,
41 | // lastNodeId = inputNodes + biasNodes + outputNodes - 1
42 | // )
43 | //
44 | // def newInputNode: InputNode
45 | // def newBiasNode: BiasNode
46 | // def newOutputNode: OutputNode
47 | //}
48 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATProblem.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.evolution.problem
19 | //
20 | //import mgo.evolution._
21 | //import scala.util.Random
22 | //import monocle.syntax._
23 | //import mgo.tools.neuralnetwork._
24 | //import collection.immutable.IntMap
25 | //
26 | //import mgo.tools.time
27 | //
28 | ///**
29 | // * Cake to define a problem for a genetic algorithm
30 | // */
31 | //trait NEATProblem extends Problem with NoPhenotype with NEATGenome with DoubleFitness {
32 | //
33 | // type NN // <: NeuralNetwork[Any, Double, Double]
34 | //
35 | // def evaluate(phenotype: P, rng: Random): F = evaluateNet(createNet(phenotype)(rng))(rng)
36 | //
37 | // def createNet(phenotype: P)(implicit rng: Random): NN = {
38 | // // nodes indices in phenotypes may not be contiguous. Translate into contiguous indices
39 | // val nodes = phenotype.nodes.toVector.sortBy { case (index, node) => index }
40 | // val nodesMap = IntMap.empty ++ nodes.map { case (index, _) => index }.toSeq.sorted.zipWithIndex
41 | // createNet(
42 | // nodes.map { case (_, node) => node },
43 | // (inputNodesIndices ++ biasNodesIndices).map { nodesMap(_) },
44 | // outputNodesIndices.map { nodesMap(_) },
45 | // phenotype.connectionGenes.filter { _.enabled }.map { cg => (nodesMap(cg.inNode), nodesMap(cg.outNode), cg.weight) }
46 | // )
47 | // }
48 | //
49 | // def createNet(
50 | // _nodes: IndexedSeq[Node],
51 | // _inputnodes: IndexedSeq[Int],
52 | // _outputnodes: IndexedSeq[Int],
53 | // _edges: Seq[(Int, Int, Double)])(implicit rng: Random): NN
54 | //
55 | // def evaluateNet(n: NN)(implicit rng: Random): Double
56 | //}
57 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATSpeciesFitnessSharing.scala:
--------------------------------------------------------------------------------
1 | //package mgo.evolution.breed
2 | //
3 | //import mgo.evolution._
4 | //
5 | //import scala.collection.immutable.Map
6 | //import scala.math._
7 | //
8 | ///**
9 | // * Created by guillaume on 01/07/2015.
10 | // */
11 | //trait NEATSpeciesFitnessSharing extends NEATGenome with DoubleFitness with P {
12 | //
13 | // def speciesOffsprings(
14 | // indivsBySpecies: Map[Int, Seq[Individual[G, P, F]]],
15 | // totalOffsprings: Int): Vector[(Int, Int)] = {
16 | // val speciesFitnesses: Vector[(Int, Double)] = indivsBySpecies.map {
17 | // case (sp, indivs) => (sp, indivs.map {
18 | // _.fitness
19 | // }.sum / indivs.size)
20 | // }.toVector
21 | //
22 | // val sumOfSpeciesFitnesses: Double = speciesFitnesses.map {
23 | // _._2
24 | // }.sum
25 | //
26 | // /** If the sum of species fitnesses is 0, take an equal number of offsprings for each (fitnesses should never be negative */
27 | // val result: Vector[(Int, Double)] = if (sumOfSpeciesFitnesses <= 0.0) {
28 | // val numberOfSpecies = indivsBySpecies.size.toDouble
29 | // indivsBySpecies.keysIterator.map { sp => (sp, totalOffsprings / numberOfSpecies) }.toVector
30 | // } else
31 | // speciesFitnesses.map { case (sp, f) => (sp, (f / sumOfSpeciesFitnesses) * totalOffsprings) }
32 | //
33 | // val resultFloored: Vector[(Int, Double, Int)] = result.map { case (sp, nb) => (sp, nb, nb.toInt) }
34 | //
35 | // /* Rounding errors can result in fewer offsprings than totalOffsprings. To correct the number of offsprings, sort
36 | // the species by the number of missed offsprings (double value - floored value) and give one to the one that misses the most. */
37 | // val missingOffsprings = totalOffsprings - result.foldLeft(0) { case (acc, (sp, nb)) => acc + nb.toInt }
38 | //
39 | // val corrected = (0 until missingOffsprings).foldLeft(resultFloored) {
40 | // case (acc, _) =>
41 | // val sortedByNeed: Vector[(Int, Double, Int)] = acc.sortBy { case (sp, nb, nbf) => -(nb - nbf) }
42 | // val (sp, nb, nbf): (Int, Double, Int) = sortedByNeed(0)
43 | // sortedByNeed.updated(0, (sp, nb, nbf + 1))
44 | // }.map { case (sp, nb, nbf) => (sp, nbf) }
45 | //
46 | // corrected
47 | // }
48 | //
49 | //}
50 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/NEATSpeciesRescaledFitnessSharing.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 15/07/2015 Guillaume Chérel
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.evolution.breed
18 | //
19 | //import mgo.evolution._
20 | //
21 | //import scala.collection.immutable.Map
22 | //import scala.math._
23 | //
24 | //trait NEATSpeciesRescaledFitnessSharing extends NEATGenome with DoubleFitness with P {
25 | //
26 | // def speciesOffsprings(
27 | // indivsBySpecies: Map[Int, Seq[Individual[G, P, F]]],
28 | // totalOffsprings: Int): Vector[(Int, Int)] = {
29 | //
30 | // val speciesAvgFitnesses: Vector[(Int, Double)] = indivsBySpecies.map {
31 | // case (sp, indivs) => (sp, indivs.map {
32 | // _.fitness
33 | // }.sum / indivs.size)
34 | // }.toVector
35 | //
36 | // val lowestSpeciesFitness = speciesAvgFitnesses.minBy {
37 | // _._2
38 | // }._2
39 | //
40 | // val rescaledSpeciesFitnesses =
41 | // speciesAvgFitnesses.map { case (sp, f) => (sp, f - lowestSpeciesFitness) }
42 | //
43 | // val sumOfSpeciesFitnesses: Double = rescaledSpeciesFitnesses.map {
44 | // _._2
45 | // }.sum
46 | //
47 | // /** If the sum of species fitnesses is 0, take an equal number of offsprings for each (fitnesses should never be negative */
48 | // val result: Vector[(Int, Double)] = if (sumOfSpeciesFitnesses <= 0.0) {
49 | // val numberOfSpecies = indivsBySpecies.size.toDouble
50 | // indivsBySpecies.keysIterator.map { sp => (sp, totalOffsprings / numberOfSpecies) }.toVector
51 | // } else
52 | // rescaledSpeciesFitnesses.map { case (sp, f) => (sp, f * totalOffsprings / sumOfSpeciesFitnesses) }
53 | //
54 | // val resultFloored: Vector[(Int, Double, Int)] = result.map { case (sp, nb) => (sp, nb, nb.toInt) }
55 | //
56 | // /* Rounding errors can result in fewer offsprings than totalOffsprings. To correct the number of offsprings, sort
57 | // the species by the number of missed offsprings (double value - floored value) and give one to the one that misses the most. */
58 | // val missingOffsprings = totalOffsprings - result.foldLeft(0) { case (acc, (sp, nb)) => acc + nb.toInt }
59 | //
60 | // val corrected = (0 until missingOffsprings).foldLeft(resultFloored) {
61 | // case (acc, _) =>
62 | // val sortedByNeed: Vector[(Int, Double, Int)] = acc.sortBy { case (sp, nb, nbf) => -(nb - nbf) }
63 | // val (sp, nb, nbf): (Int, Double, Int) = sortedByNeed(0)
64 | // sortedByNeed.updated(0, (sp, nb, nbf + 1))
65 | // }.map { case (sp, nb, nbf) => (sp, nbf) }
66 | //
67 | // corrected
68 | // }
69 | //}
70 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/neat/README:
--------------------------------------------------------------------------------
1 | Keep these files until NEAT and HyperNEAT are coded again in the new MGO.
2 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/niche.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | package mgo.evolution
18 |
19 | import java.lang.Math._
20 | import monocle._
21 | import mgo.tools._
22 |
23 | object niche {
24 | type Niche[-I, +T] = (I => T)
25 |
26 | def grid(gridSize: Seq[Double])(value: Vector[Double]): Vector[Int] =
27 | (value zip gridSize).map {
28 | case (x, g) => (x / g).toInt
29 | }
30 |
31 | def boundedGrid(lowBound: Vector[Double], highBound: Vector[Double], definition: Vector[Int])(value: Vector[Double]): Vector[Int] =
32 | (value zip definition zip lowBound zip highBound).map {
33 | case (((x, d), lb), hb) =>
34 | val step = (hb - lb) / d
35 | val p = ((x - lb) / step).floor.toInt
36 | max(0, min(d, p))
37 | }
38 |
39 | def irregularGrid[A: scala.math.Ordering](axes: Vector[Vector[A]])(values: Vector[A]): Vector[Int] =
40 | axes zip values map { case (axe, v) => findInterval(axe.sorted, v) }
41 |
42 | def continuousProfile[G](values: G => Vector[Double], x: Int, nX: Int): Niche[G, Int] =
43 | (genome: G) => {
44 | val niche = (values(genome)(x) * nX).toInt
45 | if (niche == nX) niche - 1 else niche
46 | }
47 |
48 | def boundedContinuousProfile[I](values: I => Vector[Double], x: Int, nX: Int, min: Double, max: Double): Niche[I, Int] =
49 | (i: I) =>
50 | values(i)(x) match {
51 | case v if v < min => -1
52 | case v if v > max => nX
53 | case v =>
54 | val bounded = changeScale(v, min, max, 0, 1)
55 | val niche = (bounded * nX).toInt
56 | if (niche == nX) niche - 1 else niche
57 | }
58 |
59 | def gridContinuousProfile[I](values: I => Vector[Double], x: Int, intervals: Vector[Double]): Niche[I, Int] =
60 | (i: I) => findInterval(intervals, values(i)(x))
61 |
62 | def discreteProfile[G](values: G => Vector[Int], x: Int): Niche[G, Int] =
63 | (genome: G) => values(genome)(x)
64 |
65 | def sequenceNiches[G, T](niches: Vector[Niche[G, T]]): Niche[G, Vector[T]] = { (g: G) => niches.map(_(g)) }
66 |
67 | def mapGenomePlotter[G](x: Int, nX: Int, y: Int, nY: Int)(implicit values: Lens[G, Seq[Double]]): Niche[G, (Int, Int)] =
68 | (genome: G) => {
69 | val (nicheX, nicheY) = ((values.get(genome)(x) * nX).toInt, (values.get(genome)(y) * nY).toInt)
70 | (if (nicheX == nX) nicheX - 1 else nicheX, if (nicheY == nY) nicheY - 1 else nicheY)
71 | }
72 | }
73 |
74 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/package.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 reuillon, Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.evolution
19 |
20 | import cats.implicits._
21 | import mgo.evolution.algorithm._
22 | import mgo.evolution.stop._
23 | import mgo.tools.execution._
24 | import org.apache.commons.math3.random._
25 |
26 | import monocle._
27 | import monocle.syntax.all._
28 |
29 | import scala.language.higherKinds
30 |
31 | /*------------- Running the EA ------------------*/
32 |
33 | type Trace[S, I] = (S, Vector[I]) => Unit
34 |
35 | case class RunAlgorithm[T, I, G, S](
36 | t: T,
37 | algo: Algorithm[T, I, G, S],
38 | stopCondition: Option[StopCondition[S, I]] = None,
39 | traceOperation: Option[Trace[S, I]] = None):
40 |
41 | def evolution(rng: scala.util.Random, parallel: Algorithm.ParallelContext): (S, Vector[I]) =
42 | val initialPop = algo.initialPopulation(t, rng, parallel)
43 | val initialState = algo.initialState(t, rng)
44 | val step = algo.step(t)
45 |
46 | def evolve(s: S, pop: Vector[I]): (S, Vector[I]) =
47 | traceOperation.foreach(_(s, pop))
48 | if (stopCondition.getOrElse(never)(s, pop))
49 | then (s, pop)
50 | else
51 | val (s2, p2) = step(s, pop, rng, parallel)
52 | evolve(s2, p2)
53 |
54 | evolve(initialState, initialPop)
55 |
56 | def until(stopCondition: StopCondition[S, I]): RunAlgorithm[T, I, G, S] = copy(stopCondition = Some(stopCondition))
57 |
58 | def trace(f: (S, Vector[I]) => Unit): RunAlgorithm[T, I, G, S] = copy(traceOperation = Some(f))
59 |
60 | def eval(rng: scala.util.Random, parallel: Boolean = false): (S, Vector[I]) =
61 | val context = if parallel then Algorithm.parallel else Algorithm.Sequential
62 | evolution(rng, context)
63 | // def eval(rng: Random)(implicit monadM: cats.Monad[M]) = algo.run(evolution, algo.initialState(t, rng))
64 |
65 |
66 | implicit def toAlgorithm[T, I, G, S](t: T)(implicit algo: Algorithm[T, I, G, S]): RunAlgorithm[T, I, G, S] = RunAlgorithm(t, algo)
67 |
68 | /** ** Stop conditions ****/
69 |
70 | def anyReaches[M[_]: cats.Monad, I](goalReached: I => Boolean)(population: Vector[I]): Vector[I] => M[Boolean] =
71 | (population: Vector[I]) => population.exists(goalReached).pure[M]
72 |
73 | def afterGeneration[I, S](g: Long): StopCondition[EvolutionState[S], I] = stop.afterGeneration[EvolutionState[S], I](g, Focus[EvolutionState[S]](_.generation))
74 |
75 | def newRNG(seed: Long) = new util.Random(new RandomAdaptor(new SynchronizedRandomGenerator(new Well44497a(seed))))
76 |
77 | def changeScale(v: Double, fromMin: Double, fromMax: Double, toMin: Double, toMax: Double): Double =
78 | val factor = (toMax - toMin) / (fromMax - fromMin)
79 | factor * (v - fromMin) + toMin
80 |
81 | implicit class Double2Scalable(d: Double):
82 | def scale(min: Double, max: Double): Double = changeScale(d, 0, 1, min, max)
83 | def scale(s: C): Double = scale(s.low, s.high)
84 | //def unscale(min: Double, max: Double) = changeScale(d, min, max, 0, 1)
85 |
86 | def arrayToVectorIso[A: Manifest]: Iso[IArray[A], Vector[A]] = monocle.Iso[IArray[A], Vector[A]](_.toVector)(v => IArray.from(v))
87 | def array2ToVectorLens[A: Manifest]: Iso[Array[Array[A]], Vector[Vector[A]]] = monocle.Iso[Array[Array[A]], Vector[Vector[A]]](_.toVector.map(_.toVector))(v => v.map(_.toArray).toArray)
88 | def intToUnsignedIntOption: Iso[Int, Option[Int]] = monocle.Iso[Int, Option[Int]](i => if (i < 0) None else Some(i))(v => v.getOrElse(-1))
89 | def byteToUnsignedIntOption: Iso[Byte, Option[Int]] = monocle.Iso[Byte, Option[Int]](i => if i < 0 then None else Some(i))(v => v.getOrElse(-1).toByte)
90 |
91 | def iArrayTupleToVector(p: (IArray[Double], IArray[Int])) = (Vector.from(p._1), Vector.from(p._2))
92 |
93 | case class C(low: Double, high: Double)
94 |
95 | object D:
96 | object IntegerPrecision:
97 | extension (t: IntegerPrecision)
98 | def size =
99 | t match
100 | case Byte => 1
101 | case Short => 2
102 | case Int => 4
103 |
104 | enum IntegerPrecision:
105 | case Byte, Short, Int
106 |
107 | val byteRange = Byte.MaxValue.toInt - Byte.MinValue
108 | val shortRange = Short.MaxValue.toInt - Short.MinValue
109 |
110 | def precision(low: Int, high: Int) =
111 | val interval = Math.abs(high.toLong - low)
112 | if interval <= byteRange
113 | then IntegerPrecision.Byte
114 | else if interval <= shortRange
115 | then IntegerPrecision.Short
116 | else IntegerPrecision.Int
117 |
118 | def apply(low: Int, high: Int) =
119 | val l = Math.min(low, high)
120 | val h = Math.max(low, high)
121 | val p = precision(l, h)
122 | new D(l, h, p)
123 |
124 | case class D(low: Int, high: Int, precision: D.IntegerPrecision)
125 |
126 |
127 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/ranking.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | package mgo.evolution
18 |
19 | import cats._
20 | import cats.data._
21 | import cats.implicits._
22 | import mgo.evolution.algorithm.HitMap
23 | import mgo.evolution.diversity._
24 | import mgo.evolution.dominance._
25 | import mgo.evolution.niche._
26 | import mgo.tools._
27 |
28 | import scala.language.higherKinds
29 |
30 | object ranking {
31 |
32 | /**
33 | * Compute the ranks of the individuals in the same order
34 | */
35 | type Ranking[M[_], I] = Kleisli[M, Vector[I], Vector[Later[Int]]]
36 |
37 | object Ranking {
38 | def apply[M[_]: cats.Monad, I](f: Vector[I] => M[Vector[Later[Int]]]): Ranking[M, I] = Kleisli(f)
39 | }
40 |
41 | def monoObjectiveRanking[M[_]: cats.Monad, I](fitness: I => Double): Ranking[M, I] =
42 | Ranking((values: Vector[I]) => {
43 |
44 | val byFitness = values.map(fitness).zipWithIndex.sortBy { case (v, _) => v }
45 | def ranks(fitnesses: List[Double], lastValue: Double = Double.NegativeInfinity, rank: Int = 0, rs: List[Int] = List()): List[Int] =
46 | fitnesses match {
47 | case h :: t =>
48 | if (h > lastValue) ranks(t, h, rank + 1, rank :: rs)
49 | else ranks(t, h, rank, rank :: rs)
50 | case Nil => rs.reverse
51 | }
52 |
53 | val ranksValue = ranks(byFitness.unzip._1.toList)
54 |
55 | (ranksValue zip byFitness.unzip._2).sortBy { case (_, r) => r }.unzip._1.toVector.map(r => Later(r))
56 | }.pure[M])
57 |
58 | // def hyperVolumeRanking[M[_]: cats.Monad, I](referencePoint: Vector[Double], fitness: I => Vector[Double]): Ranking[M, I] =
59 | // Ranking((values: Vector[I]) =>
60 | // HierarchicalRanking.downRank(Hypervolume.contributions(values.map(e => fitness(e)), referencePoint)).pure[M])
61 | //
62 | // def hierarchicalRanking[M[_]: cats.Monad, I](fitness: I => Vector[Double]): Ranking[M, I] =
63 | // Ranking((values: Vector[I]) =>
64 | // HierarchicalRanking.upRank(values.map(v => fitness(v))).pure[M])
65 |
66 | def numberOfDominating[I](fitness: I => Vector[Double], values: Vector[I], dominance: Dominance = nonStrictDominance): Vector[Later[Int]] =
67 | val fitnesses = values.map(i => fitness(i))
68 | def ranks =
69 | fitnesses.zipWithIndex.map: (v1, index1) =>
70 | def containsNaN = v1.exists(_.isNaN)
71 | def otherIndividuals = fitnesses.zipWithIndex.filter((_, index2) => index1 != index2)
72 | def numberOfDominatingIndividual = otherIndividuals.count((v2, _) => dominance.isDominated(v1, v2))
73 | Later:
74 | if containsNaN
75 | then Int.MaxValue
76 | else numberOfDominatingIndividual
77 |
78 | ranks
79 |
80 | // def profileRanking[M[_]: cats.Monad, I](niche: Niche[I, Int], fitness: I => Double): Ranking[M, I] =
81 | // Ranking((population: Vector[I]) => {
82 | // val (points, indexes) =
83 | // population.map {
84 | // i => (niche(i).toDouble, fitness(i))
85 | // }.zipWithIndex.sortBy(_._1._1).unzip
86 | //
87 | // def signedSurface(p1: Point2D, p2: Point2D, p3: Point2D) = {
88 | // val surface = mgo.tools.surface(p1, p2, p3)
89 | // if (isUpper(p1, p3, p2)) -surface else surface
90 | // }
91 | //
92 | // val contributions =
93 | // points match {
94 | // case Seq() => Seq.empty
95 | // case Seq(x) => Seq(1.0)
96 | // case s =>
97 | // val first = s(0)
98 | // val second = s(1)
99 | // val zero = (first.x - (second.x - first.x), second.y)
100 | //
101 | // val leftSurface = signedSurface(zero, first, second)
102 | //
103 | // val preLast = s(s.length - 2)
104 | // val last = s(s.length - 1)
105 | // val postLast = (last.x + (last.x - preLast.x), preLast.y)
106 | //
107 | // val rightSurface = signedSurface(preLast, last, postLast)
108 | //
109 | // val middlePoints = s.sliding(3).filter(_.size == 3).map {
110 | // s => signedSurface(s(0), s(1), s(2))
111 | // }
112 | //
113 | // val surfaces = (Seq(leftSurface) ++ middlePoints ++ Seq(rightSurface)).zip(indexes).sortBy(_._2).map(_._1)
114 | // val smallest = surfaces.min
115 | // surfaces.map(s => s - smallest)
116 | // }
117 | //
118 | // HierarchicalRanking.downRank(contributions.toVector)
119 | // }.pure[M])
120 |
121 | //TODO: Lazy ne sert à rien ici. On pourrait redefinir le type Ranking en Ranking[M,I,K] avec K est de typeclass Order,
122 | def hitCountRanking[S, I](s: S, population: Vector[I], cell: I => Vector[Int], hitmap: monocle.Lens[S, HitMap]): Vector[Int] =
123 | def hitCount(cell: Vector[Int]): Int = hitmap.get(s).getOrElse(cell, 0)
124 | population.map { i => hitCount(cell(i)) }
125 |
126 | /**** Generic functions on rankings ****/
127 |
128 | // def reversedRanking[M[_]: cats.Monad, I](ranking: Ranking[M, I]): Ranking[M, I] =
129 | // Ranking((population: Vector[I]) => ranking(population).map { ranks => ranks.map { rank => rank.map(x => -x) } })
130 |
131 | def paretoRanking[I](population: Vector[I], fitness: I => Vector[Double], dominance: Dominance = nonStrictDominance): Vector[Eval[Int]] =
132 | numberOfDominating(fitness, population, dominance).map(_.map(x => -x))
133 |
134 | //TODO: the following functions don't produce rankings and don't belong here.
135 | def rankAndDiversity[M[_]: cats.Monad, I](ranking: Ranking[M, I], diversity: Diversity[M, I]): Kleisli[M, Vector[I], Vector[(Later[Int], Later[Double])]] =
136 | Kleisli((population: Vector[I]) =>
137 | for
138 | r <- ranking(population)
139 | d <- diversity(population)
140 | yield r zip d)
141 |
142 | def paretoRankingMinAndCrowdingDiversity[I](population: Vector[I], fitness: I => Vector[Double]): Vector[(Eval[Int], Double)] =
143 | paretoRanking(population, fitness) zip crowdingDistance(population, fitness)
144 |
145 | def worstParetoRanking: (Later[Int], Double) = (Later(Int.MinValue), Double.NegativeInfinity)
146 |
147 | def rank[M[_]: cats.Monad, I, K](ranking: Kleisli[M, Vector[I], Vector[K]]): Kleisli[M, Vector[I], Vector[(I, K)]] = Kleisli[M, Vector[I], Vector[(I, K)]] { is =>
148 | for {
149 | rs <- ranking.run(is)
150 | } yield is zip rs
151 | }
152 |
153 | }
154 |
155 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/evolution/stop.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by reuillon on 07/01/16.
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | *
17 | */
18 |
19 | package mgo.evolution
20 |
21 | import cats.data._
22 | import cats.implicits._
23 | import squants.time._
24 |
25 | object stop {
26 |
27 | type StopCondition[S, I] = (S, Vector[I]) => Boolean
28 |
29 | def afterEvaluated[S, I](e: Long, evaluated: monocle.Lens[S, Long]): StopCondition[S, I] =
30 | (s, is) => evaluated.get(s) >= e
31 |
32 | def afterGeneration[S, I](g: Long, generation: monocle.Lens[S, Long]): StopCondition[S, I] =
33 | (s, is) => generation.get(s) >= g
34 |
35 | def afterDuration[S, I](d: Time, start: monocle.Lens[S, Long]): StopCondition[S, I] =
36 | (s, is) => {
37 | val now = java.lang.System.currentTimeMillis()
38 | val st = start.get(s)
39 | (st + d.toMilliseconds) <= now
40 | }
41 |
42 | def never[S, I]: StopCondition[S, I] = (s, i) => false
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestAggregated.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2012 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.test
19 | //
20 | //import mgo.evolution._
21 | //import tools.Math._
22 | //
23 | //import scala.util.Random
24 | //import scalax.io.Resource
25 | //
26 | //object TestAggregated extends App {
27 | //
28 | // implicit val rng = newRNG(42)
29 | //
30 | // //val mutationRes = Resource.fromFile("/tmp/mutation.csv")
31 | // //val crossoverRes = Resource.fromFile("/tmp/crossover.csv")
32 | //
33 | // val m =
34 | // new Rastrigin with AggregatedOptimisation with DiversityAggregatedElitism {
35 | // def mu = 200
36 | // def lambda = 1
37 | // def genomeSize = 10
38 | // def steps = 100000
39 | //
40 | // override type STATE = None.type
41 | // override def terminated(population: Population[G, P, F], terminationState: STATE)(implicit rng: Random): (Boolean, STATE) = {
42 | // (population.map(i => aggregate(i.fitness)).min <= 0.0, None)
43 | // }
44 | //
45 | // override def initialState: STATE = None
46 | // }
47 | //
48 | // /* m.evolve.untilConverged {
49 | // i =>
50 | // println(i.population.map(i => m.aggregate(i.fitness)).min)
51 | //
52 | // val cstats = m.crossoverStats(i.population).toMap.withDefaultValue(0.0)
53 | // crossoverRes.append((0 until m.crossovers.size).map(cstats).mkString(",") + "\n")
54 | //
55 | // val mstats = m.mutationStats(i.population).toMap.withDefaultValue(0.0)
56 | // mutationRes.append((0 until m.mutations.size).map(mstats).mkString(",") + "\n")
57 | // }*/
58 | //
59 | // m.evolve.untilConverged {
60 | // i => println(i.generation + " " + i.population.map(i => m.aggregate(i.fitness)).min)
61 | // }
62 | // /*println(average((0 until 100).par.map(i => m.evolve.untilConverged { i => println(i.generation) }.generation.toDouble).seq))*/
63 | //
64 | // /*val res =
65 | // m.evolve.untilConverged {
66 | // s =>
67 | // println(s.generation + " " + s.population.map(i => m.aggregate(i.fitness)).min)
68 | // }.population.toIndividuals
69 | //
70 | // val output = Resource.fromFile("/tmp/res.csv")
71 | // for {
72 | // r <- res
73 | // } {
74 | // def line = m.scale(m.values.get(r.genome)) ++ m.fitness(r)
75 | // output.append(line.mkString(",") + "\n")
76 | // }*/
77 | //
78 | //}
79 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestCMAES.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2014 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.test
19 | //
20 | //import mgo.evolution._
21 | //
22 | //import scala.util.Random
23 | //import scalax.io
24 | //
25 | //object TestCMAES extends App {
26 | //
27 | // val m = new Rastrigin with CMAES with CounterTermination {
28 | // /** Number of steps before the algorithm stops */
29 | // override def steps: Int = 1000
30 | // override def genomeSize: Int = 5
31 | //
32 | // def lambda = 200
33 | //
34 | // //To be fair with other methods
35 | // override def guess(implicit rng: Random) = Seq.fill[Double](genomeSize)(rng.nextDouble)
36 | // }
37 | //
38 | // implicit val rng = new Random(46)
39 | //
40 | // val res =
41 | // m.evolve.untilConverged {
42 | // s =>
43 | // println(s.generation + " " + s.population.size + " " + s.population.toIndividuals.map(i => m.aggregate(m.fitness(i))).min)
44 | // }.population
45 | //
46 | // val output = io.Resource.fromFile("/tmp/res.csv")
47 | // for {
48 | // r <- res.toIndividuals
49 | // } {
50 | // def line = m.scale(m.values.get(r.genome)) ++ m.fitness(r)
51 | // output.append(line.mkString(",") + "\n")
52 | // }
53 | //
54 | //}
55 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestFunctionSMSEMOEA.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2012 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.test
19 | //
20 | //import mgo.evolution._
21 | //import util.Random
22 | //import scalax.io.Resource
23 | //
24 | //object TestFunctionSMSEMOEA extends App {
25 | //
26 | // implicit val rng = new Random
27 | //
28 | // val smsemoea =
29 | // new ZDT4 with SMSEMOEA with HyperVolumeStabilityTermination {
30 | // def windowSize = 100
31 | // def deviationEpsilon = 0.001
32 | // def mu = 200
33 | // def lambda = 200
34 | // def genomeSize = 10
35 | // def referencePoint = IndexedSeq(1000.0, 1000.0)
36 | // }
37 | //
38 | // val res =
39 | // smsemoea.evolve.untilConverged {
40 | // s =>
41 | // println(s.generation + " " + s.terminationState)
42 | // }.population
43 | //
44 | // val output = Resource.fromFile("/tmp/res.csv")
45 | // for {
46 | // r <- res.toIndividuals
47 | // } {
48 | // def line = smsemoea.scale(smsemoea.values.get(r.genome)) ++ smsemoea.fitness(r)
49 | // output.append(line.mkString(",") + "\n")
50 | // }
51 | //
52 | //}
53 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestHDOSE.scala:
--------------------------------------------------------------------------------
1 | package mgo.test
2 |
3 | import mgo.evolution._
4 | import better.files._
5 |
6 | object RastriginHDOSE extends App:
7 | import algorithm._
8 | import niche._
9 | import OSE._
10 |
11 | def dimensions = 30
12 |
13 | val hdose: HDOSE = HDOSE(
14 | mu = 100,
15 | lambda = 100,
16 | fitness = (x, _) => IArray(rastrigin.compute(x)),
17 | limit = Vector(10.0),
18 | archiveSize = 10,
19 | continuous = rastrigin.continuous(dimensions))
20 |
21 | val (finalState, finalPopulation) =
22 | hdose.
23 | until(afterGeneration(5000)).
24 | trace { (s, is) => println("" + s.generation + " " + s.s.archive.size + " " + s.s.distance) }.
25 | eval(new util.Random(42))
26 |
27 | File("/tmp/hdose.csv") write HDOSE.result(hdose, finalState, finalPopulation).map( r => (r.continuous ++ r.fitness).mkString(",")).mkString("\n")
28 |
29 |
30 | object NoisyRastriginHDOSE extends App:
31 |
32 | import algorithm._
33 | import niche._
34 | import NoisyOSE._
35 |
36 | def dimensions = 3
37 |
38 | val hdose = NoisyHDOSE(
39 | mu = 100,
40 | lambda = 100,
41 | fitness = (rng, x, _) => Vector(rastrigin.compute(x) + rng.nextGaussian() * 0.25),
42 | aggregation = Aggregation.average,
43 | limit = Vector(10.0),
44 | archiveSize = 10,
45 | historySize = 5,
46 | // origin =
47 | // (c, _) =>
48 | // boundedGrid(
49 | // lowBound = Vector.fill(dimensions)(-10.0),
50 | // highBound = Vector.fill(dimensions)(10.0),
51 | // definition = Vector.fill(dimensions)(100))(c),
52 | continuous = rastrigin.continuous(dimensions))
53 |
54 | val (finalState, finalPopulation) =
55 | hdose.
56 | until(afterGeneration(2000)).
57 | trace { (s, is) => println("" + s.generation + " " + s.s._1.size) }.
58 | eval(new util.Random(42))
59 |
60 | File("/tmp/hdose.csv") write NoisyHDOSE.result(hdose, finalState, finalPopulation).map(_.continuous.mkString(",")).mkString("\n")
61 |
62 | //
63 | ///**
64 | // * Benchmark function used in
65 | // * Sambridge, M. (2001). Finding acceptable models in nonlinear inverse problems using a neighbourhood algorithm. Inverse Problems, 17(3), 387.
66 | // * in the paper: f0 = c*d*((e+g)*h + l) ; g not defined -> use f0 = c*d*(e*h + l)
67 | // *
68 | // * Neighborhood algorithm (with confidence intervals in second paper)
69 | // * Sambridge, M. (1999). Geophysical inversion with a neighbourhood algorithm—I. Searching a parameter space. Geophysical journal international, 138(2), 479-494.
70 | // * Sambridge, M. (1999). Geophysical inversion with a neighbourhood algorithm—II. Appraising the ensemble. Geophysical Journal International, 138(3), 727-746.
71 | // *
72 | // */
73 | //object Sambridge2001OSE extends App {
74 | // import algorithm._
75 | // import niche._
76 | // import OSE._
77 | //
78 | // def dimensions = 5
79 | // def continuous(size: Int): Vector[C] = Vector.fill(size)(C(-2.0, 2.0))
80 | //
81 | // def f(x: Vector[Double]): Double = {
82 | // val (x1, x2, x3, x4, x5) = (x(0), x(1), x(2), x(3), x(4))
83 | // val a = 2.5 * math.pow(x1 + 0.2, 2.0) + 1.25 * x2 * x2
84 | // val b = 5.0 * math.pow(x1 - 0.6, 2.0) + math.pow(x2 + 0.15, 2.0)
85 | // val c = 0.01 * (x1 * x1 + math.pow(x2 - 1.1, 2.0))
86 | // val d = math.pow(x1 - 1.0, 2.0) + 10.0 * math.pow(x2 - 1.0, 2.0)
87 | // val e = 5 * (50 * math.pow(x2 - x1 * x1, 2.0) + math.pow(1 - x1, 2.0))
88 | // val h = math.pow(x1 + 0.7, 2.0)
89 | // val l = 5 * math.pow(x2 - 0.5, 2.0)
90 | // val f0loc = c * d * (e * h + l)
91 | // a * b * math.log(1 + f0loc) + x3 * x3 + x4 * x4 + x5 * x5
92 | // }
93 | //
94 | // val ose: OSE = OSE(
95 | // mu = 100,
96 | // lambda = 100,
97 | // fitness = (x, _) => Vector(f(x)),
98 | // limit = Vector(0.01),
99 | // origin =
100 | // (c, _) =>
101 | // boundedGrid(
102 | // lowBound = Vector.fill(dimensions)(-2.0),
103 | // highBound = Vector.fill(dimensions)(2.0),
104 | // definition = Vector.fill(dimensions)(100))(c),
105 | // continuous = continuous(dimensions))
106 | //
107 | // val (finalState, finalPopulation) =
108 | // ose.
109 | // until(afterGeneration(5000)).
110 | // trace { (s, is) => println("" + s.generation + " " + s.s._1.length) }.
111 | // eval(new util.Random(42))
112 | //
113 | // File("./test/ose.csv") write OSE.result(ose, finalState, finalPopulation).map(i => (i.continuous ++ Vector(f(i.continuous))).mkString(",")).mkString("\n")
114 | //}
115 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestMap.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 20/11/12 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU Affero General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.test
19 | //
20 | //import mgo.evolution._
21 | //import util.Random
22 | //import java.io._
23 | //
24 | //object TestMap extends App {
25 | //
26 | // val m =
27 | // new Rastrigin with Map with MapGenomePlotter with CounterTermination {
28 | // def genomeSize: Int = 6
29 | // def lambda: Int = 200
30 | // def steps = 500
31 | // def x: Int = 0
32 | // def y: Int = 1
33 | // def nX: Int = 100
34 | // def nY: Int = 100
35 | // }
36 | //
37 | // implicit val rng = new Random
38 | //
39 | // val res = m.evolve.untilConverged(s => println(s.generation)).population.toIndividuals
40 | //
41 | // val writer = new FileWriter(new File("/tmp/matrix.csv"))
42 | // for {
43 | // i <- res
44 | // (x, y) = m.plot(i)
45 | // v = m.aggregate(i.fitness)
46 | // if !v.isPosInfinity
47 | // } writer.write("" + x + "," + y + "," + v + "\n")
48 | // writer.close
49 | //
50 | //}
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestMonoidParallel.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.test
19 |
20 | import mgo.tools.execution.MonoidParallel
21 | import org.apache.commons.math3.random.Well1024a
22 |
23 | import scala.concurrent.ExecutionContext
24 | import scala.concurrent.ExecutionContextExecutor
25 |
26 | object SumMonoidParallel extends App {
27 |
28 | implicit val rng: Well1024a = new Well1024a()
29 | implicit val ec: ExecutionContextExecutor = ExecutionContext.global
30 |
31 | val maxSum = 10
32 |
33 | val empty = 0
34 | val append: (Int, Int) => Int = { (a: Int, b: Int) => a + b }
35 | val split: Int => (Int, Int) = { (a: Int) => (a, 0) }
36 | val step: Int => Int = (_: Int) + 1
37 | val stop: Int => Boolean = (_: Int) >= maxSum
38 |
39 | {
40 | val parallel = 1
41 | val stepSize = 1
42 | val res = MonoidParallel[Int](empty, append, split, step, parallel, stepSize, stop).scan
43 | println(res)
44 | }
45 |
46 | {
47 | val parallel = 1
48 | val stepSize = 2
49 | val res = MonoidParallel[Int](empty, append, split, step, parallel, stepSize, stop).scan
50 | println(res)
51 | }
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestNSGA3.scala:
--------------------------------------------------------------------------------
1 | package mgo.test
2 |
3 | import java.io.{ BufferedWriter, File, FileWriter }
4 |
5 | import mgo.evolution._
6 | import mgo.evolution.algorithm.CDGenome.DeterministicIndividual.Individual
7 | import mgo.evolution.algorithm.CDGenome.{ Genome, NoisyIndividual }
8 | import mgo.evolution.algorithm.NSGA3Operations
9 | import mgo.tools.benchmark.ManyObjective
10 |
11 | /**
12 | * For test manyobjective: coco-biobj http://numbbo.github.io/coco-doc/bbob-biobj/functions/ (check mgo-benchmark)
13 | * Chek Cheng, R., Li, M., Tian, Y., Zhang, X., Yang, S., Jin, Y., & Yao, X. (2017). A benchmark test suite for evolutionary many-objective optimization. Complex & Intelligent Systems, 3(1), 67-81.
14 | */
15 | object ReferencePoints extends App {
16 |
17 | val divisions = 4
18 | val dimension = 3
19 |
20 | println("Computing simplex reference points with " + divisions + " divisions in dimension " + dimension)
21 | val start: Long = System.currentTimeMillis()
22 | val points: Vector[Vector[Double]] = NSGA3Operations.simplexRefPoints(divisions, dimension)
23 | println("n = " + dimension + " ; p = " + divisions + " ; t = " + (System.currentTimeMillis() - start))
24 | println(points)
25 |
26 | }
27 |
28 | object ManyObjectiveFunctions extends App {
29 |
30 | /*
31 | val xt = Vector.fill(10)(0.5)
32 | def f(x: Double, y: Double): Vector[Double] = ManyObjective.maf1(12)(Vector(x, y) ++ xt)
33 |
34 | println(f(0, 0))
35 | println(f(1, 1))
36 | println(f(1, 0))
37 | */
38 | val rng = util.Random
39 | val pop1: Vector[Vector[Double]] = Vector(Vector(rng.nextDouble, rng.nextDouble))
40 | def fitness(x: Vector[Double]): Vector[Double] = x
41 | //println(NSGA3Operations.successiveFronts(pop1, fitness))
42 | //println(NSGA3Operations.successiveFronts(pop1 ++ pop1, fitness))
43 | //println(NSGA3Operations.successiveFronts(pop1 ++ pop1 ++ pop1, fitness))
44 |
45 | def pop(n: Int): Vector[Vector[Double]] = Vector.fill(n, 2)(rng.nextDouble)
46 | //println(pop(5))
47 | (0 until 10).foreach { i =>
48 | println("" + i + ": " + NSGA3Operations.successiveFronts(pop(i), fitness))
49 | }
50 |
51 | }
52 |
53 | object FunctionNSGA3 extends App {
54 |
55 | import algorithm._
56 |
57 | //def fitness(cont: Vector[Double], discr: Vector[Int]): Vector[Double] = Vector(rastrigin.compute(cont), rastrigin.compute(cont.map { _ + 0.5 }))
58 | // ! dimension must be correct with reference points
59 | def fitness(cont: Vector[Double], discr: Vector[Int]): Vector[Double] = ManyObjective.maf1(12)(cont)
60 |
61 | //val ref = NSGA3Operations.ReferencePoints(40, 2)
62 | val ref: NSGA3Operations.ReferencePoints = NSGA3Operations.ReferencePoints(50, 3)
63 |
64 | //val genome = rastrigin.continuous(4)
65 | val genome: Vector[C] = Vector.fill(13)(C(0.0, 1.0))
66 |
67 | def write(gen: Long, pop: Vector[Individual[Vector[Double]]]): Unit = {
68 | val w = new BufferedWriter(new FileWriter(new File("test/pop" + gen + ".csv")))
69 | w.write(pop.map(i => fitness(i.genome.continuousValues.toVector, Vector.empty)).map(_.mkString(";")).mkString("\n"))
70 | w.close()
71 | }
72 |
73 | val nsga3: NSGA3 = NSGA3(
74 | popSize = 1000,
75 | referencePoints = ref,
76 | fitness = fitness,
77 | continuous = genome)
78 |
79 | def evolution: RunAlgorithm[NSGA3, Individual[Vector[Double]], Genome, EvolutionState[Unit]] =
80 | nsga3.until(afterGeneration(1000)).
81 | trace { (s, individuals) =>
82 | println("\n====================\ngen: " + s.generation)
83 | write(s.generation, individuals)
84 | }
85 |
86 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
87 |
88 | val res: Vector[NSGA3.Result[Vector[Double]]] = NSGA3.result(nsga3, finalPopulation)
89 | println(res.mkString("\n"))
90 | val fitnesses: Array[Array[Double]] = res.map(_.fitness.toArray).toArray
91 | val w = new BufferedWriter(new FileWriter(new File("test/functionNSGA3.csv")))
92 | w.write(fitnesses.map(_.mkString(";")).mkString("\n"))
93 | w.close()
94 | val wr = new BufferedWriter(new FileWriter(new File("test/reference.csv")))
95 | wr.write(ref.references.map(_.mkString(";")).mkString("\n"))
96 | wr.close()
97 | }
98 |
99 | object TestNoisyNSGA3 extends App {
100 |
101 | import algorithm._
102 |
103 | def fitness(rng: util.Random, cont: Vector[Double], discr: Vector[Int]): Vector[Double] = {
104 | val res = ManyObjective.maf1(12)(cont).map(_ + 0.1 * rng.nextGaussian())
105 | //println(res.size)
106 | res
107 | }
108 | // ! for noisy, ref points must be dim + 1
109 | val ref: NSGA3Operations.ReferencePoints = NSGA3Operations.ReferencePoints(50, 4)
110 | val genome: Vector[C] = Vector.fill(13)(C(0.0, 1.0))
111 |
112 | val nsga3: NoisyNSGA3[Vector[Double]] = NoisyNSGA3[Vector[Double]](
113 | popSize = 100,
114 | referencePoints = ref,
115 | fitness = fitness,
116 | // ! take average across repetitions and not within each ! => add assert that dim ref = dim extended obj?
117 | aggregation = pop => pop.transpose.map(x => x.sum / x.length),
118 | continuous = genome)
119 |
120 | def evolution: RunAlgorithm[NoisyNSGA3[Vector[Double]], NoisyIndividual.Individual[Vector[Double]], Genome, NoisyNSGA3.NSGA3State] =
121 | nsga3.until(afterGeneration(10)).
122 | trace { (s, individuals) =>
123 | println("\n====================\ngen: " + s.generation)
124 | }
125 |
126 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
127 |
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestNSGAII.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 Guillaume Chérel, Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.test
19 |
20 | import mgo.evolution._
21 |
22 | object SphereNSGAII extends App {
23 |
24 | import algorithm._
25 |
26 | val nsga2: NSGA2 = NSGA2(
27 | mu = 100,
28 | lambda = 100,
29 | fitness = (v, _) => Vector(sphere.compute(v)),
30 | continuous = sphere.genome(6))
31 |
32 | def evolution: RunAlgorithm[NSGA2, CDGenome.DeterministicIndividual.Individual[Vector[Double]], CDGenome.Genome, EvolutionState[Unit]] =
33 | nsga2.
34 | until(afterGeneration(1000)).
35 | trace((s, is) => println(s.generation))
36 |
37 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
38 |
39 | println(NSGA2.result(nsga2, finalPopulation).mkString("\n"))
40 |
41 | }
42 |
43 | object DiscreteNSGAII extends App {
44 |
45 | import algorithm._
46 |
47 | val nsga2: NSGA2 = NSGA2(
48 | mu = 100,
49 | lambda = 100,
50 | fitness = discreteSphere.compute,
51 | continuous = discreteSphere.continuous(6),
52 | discrete = discreteSphere.discrete(3))
53 |
54 | def evolution: RunAlgorithm[NSGA2, CDGenome.DeterministicIndividual.Individual[Vector[Double]], CDGenome.Genome, EvolutionState[Unit]] =
55 | nsga2.
56 | until(afterGeneration(1000)).
57 | trace((s, is) => println(s.generation))
58 |
59 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
60 |
61 | println(NSGA2.result(nsga2, finalPopulation).mkString("\n"))
62 |
63 | }
64 |
65 | object NoisySphereNSGAII extends App {
66 |
67 | import algorithm._
68 |
69 | val nsga2: NoisyNSGA2[Vector[Double]] =
70 | NoisyNSGA2(
71 | mu = 100,
72 | lambda = 100,
73 | fitness = noisyDiscreteSphere.compute,
74 | aggregation = Aggregation.average,
75 | continuous = noisyDiscreteSphere.continuous(2),
76 | discrete = noisyDiscreteSphere.discrete(2))
77 |
78 | def evolution: RunAlgorithm[NoisyNSGA2[Vector[Double]], CDGenome.NoisyIndividual.Individual[Vector[Double]], CDGenome.Genome, NoisyNSGA2.NSGA2State] =
79 | nsga2.
80 | until(afterGeneration(1000)).
81 | trace((s, is) => println(s.generation))
82 |
83 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
84 |
85 | println(NoisyNSGA2.result(nsga2, finalPopulation).mkString("\n"))
86 |
87 | }
88 |
89 | object ZDT4NSGAII extends App {
90 |
91 | import algorithm._
92 |
93 | val nsga2: NSGA2 =
94 | NSGA2(
95 | mu = 100,
96 | lambda = 100,
97 | fitness = zdt4.compute,
98 | continuous = zdt4.continuous(10))
99 |
100 | def evolution: RunAlgorithm[NSGA2, CDGenome.DeterministicIndividual.Individual[Vector[Double]], CDGenome.Genome, EvolutionState[Unit]] =
101 | nsga2.
102 | until(afterGeneration(1000)).
103 | trace((s, is) => println(s.generation))
104 |
105 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
106 |
107 | println(NSGA2.result(nsga2, finalPopulation).mkString("\n"))
108 |
109 | }
110 |
111 | object RastriginNSGAII extends App {
112 |
113 | import algorithm._
114 |
115 | val nsga2: NSGA2 = NSGA2(
116 | mu = 100,
117 | lambda = 100,
118 | fitness = (x, _) => Vector(rastrigin.compute(x)),
119 | continuous = rastrigin.continuous(2))
120 |
121 | def evolution: RunAlgorithm[NSGA2, CDGenome.DeterministicIndividual.Individual[Vector[Double]], CDGenome.Genome, EvolutionState[Unit]] =
122 | nsga2.
123 | until(afterGeneration(1000)).
124 | trace { (s, is) => println(s.generation) }
125 |
126 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
127 |
128 | println(NSGA2.result(nsga2, finalPopulation).mkString("\n"))
129 | }
130 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestNichedNSGA2.scala:
--------------------------------------------------------------------------------
1 | package mgo.test
2 |
3 | import mgo.evolution.*
4 |
5 | object NichedNSGAII extends App {
6 |
7 | import algorithm.*
8 |
9 | case class Phenotype(diversity: Double, optimisation: Double)
10 |
11 | val nsga2: Profile[Seq[Int]] = Profile(
12 | lambda = 100,
13 | fitness = discreteSphere.compute,
14 | continuous = discreteSphere.continuous(6),
15 | discrete = discreteSphere.discrete(3),
16 | niche = i => CDGenome.discreteValues(discreteSphere.discrete(3)).get(i.genome).take(2).toSeq)
17 |
18 | def evolution: RunAlgorithm[Profile[Seq[Int]], CDGenome.DeterministicIndividual.Individual[Vector[Double]], CDGenome.Genome, Profile.ProfileState] =
19 | nsga2.
20 | until(afterGeneration(100)).
21 | trace((s, is) => println(s.generation))
22 |
23 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
24 |
25 | println(Profile.result(nsga2, finalPopulation).mkString("\n"))
26 |
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestOSE.scala:
--------------------------------------------------------------------------------
1 | package mgo.test
2 |
3 | import mgo.evolution._
4 | import better.files._
5 |
6 | object RastriginOSE extends App {
7 | import algorithm._
8 | import niche._
9 | import OSE._
10 |
11 | def dimensions = 3
12 |
13 | val ose: OSE = OSE(
14 | mu = 100,
15 | lambda = 100,
16 | fitness = (x, _) => Vector(rastrigin.compute(x)),
17 | limit = Vector(10.0),
18 | origin =
19 | (c, _) =>
20 | boundedGrid(
21 | lowBound = Vector.fill(dimensions)(-10.0),
22 | highBound = Vector.fill(dimensions)(10.0),
23 | definition = Vector.fill(dimensions)(100))(c),
24 | continuous = rastrigin.continuous(dimensions))
25 |
26 | val (finalState, finalPopulation) =
27 | ose.
28 | until(afterGeneration(5000)).
29 | trace { (s, is) => println("" + s.generation + " " + s.s._1.size) }.
30 | eval(new util.Random(42))
31 |
32 | File("/tmp/ose.csv") write OSE.result(ose, finalState, finalPopulation).map(_.continuous.mkString(",")).mkString("\n")
33 | }
34 |
35 | object NoisyRastriginOSE extends App {
36 |
37 | import algorithm._
38 | import niche._
39 | import NoisyOSE._
40 |
41 | def dimensions = 3
42 |
43 | val ose: NoisyOSE[Vector[Double]] = NoisyOSE(
44 | mu = 100,
45 | lambda = 100,
46 | fitness = (rng, x, _) => Vector(rastrigin.compute(x) + rng.nextGaussian() * 0.25),
47 | aggregation = Aggregation.average,
48 | limit = Vector(10.0),
49 | origin =
50 | (c, _) =>
51 | boundedGrid(
52 | lowBound = Vector.fill(dimensions)(-10.0),
53 | highBound = Vector.fill(dimensions)(10.0),
54 | definition = Vector.fill(dimensions)(100))(c),
55 | continuous = rastrigin.continuous(dimensions))
56 |
57 | val (finalState, finalPopulation) =
58 | ose.
59 | until(afterGeneration(50000)).
60 | trace { (s, is) => println("" + s.generation + " " + s.s._1.size) }.
61 | eval(new util.Random(42))
62 |
63 | File("/tmp/ose.csv") write NoisyOSE.result(ose, finalState, finalPopulation).map(_.continuous.mkString(",")).mkString("\n")
64 | }
65 |
66 | /**
67 | * Benchmark function used in
68 | * Sambridge, M. (2001). Finding acceptable models in nonlinear inverse problems using a neighbourhood algorithm. Inverse Problems, 17(3), 387.
69 | * in the paper: f0 = c*d*((e+g)*h + l) ; g not defined -> use f0 = c*d*(e*h + l)
70 | *
71 | * Neighborhood algorithm (with confidence intervals in second paper)
72 | * Sambridge, M. (1999). Geophysical inversion with a neighbourhood algorithm—I. Searching a parameter space. Geophysical journal international, 138(2), 479-494.
73 | * Sambridge, M. (1999). Geophysical inversion with a neighbourhood algorithm—II. Appraising the ensemble. Geophysical Journal International, 138(3), 727-746.
74 | *
75 | */
76 | object Sambridge2001OSE extends App {
77 | import algorithm._
78 | import niche._
79 | import OSE._
80 |
81 | def dimensions = 5
82 | def continuous(size: Int): Vector[C] = Vector.fill(size)(C(-2.0, 2.0))
83 |
84 | def f(x: Vector[Double]): Double = {
85 | val (x1, x2, x3, x4, x5) = (x(0), x(1), x(2), x(3), x(4))
86 | val a = 2.5 * math.pow(x1 + 0.2, 2.0) + 1.25 * x2 * x2
87 | val b = 5.0 * math.pow(x1 - 0.6, 2.0) + math.pow(x2 + 0.15, 2.0)
88 | val c = 0.01 * (x1 * x1 + math.pow(x2 - 1.1, 2.0))
89 | val d = math.pow(x1 - 1.0, 2.0) + 10.0 * math.pow(x2 - 1.0, 2.0)
90 | val e = 5 * (50 * math.pow(x2 - x1 * x1, 2.0) + math.pow(1 - x1, 2.0))
91 | val h = math.pow(x1 + 0.7, 2.0)
92 | val l = 5 * math.pow(x2 - 0.5, 2.0)
93 | val f0loc = c * d * (e * h + l)
94 | a * b * math.log(1 + f0loc) + x3 * x3 + x4 * x4 + x5 * x5
95 | }
96 |
97 | val ose: OSE = OSE(
98 | mu = 100,
99 | lambda = 100,
100 | fitness = (x, _) => Vector(f(x)),
101 | limit = Vector(0.01),
102 | origin =
103 | (c, _) =>
104 | boundedGrid(
105 | lowBound = Vector.fill(dimensions)(-2.0),
106 | highBound = Vector.fill(dimensions)(2.0),
107 | definition = Vector.fill(dimensions)(100))(c),
108 | continuous = continuous(dimensions))
109 |
110 | val (finalState, finalPopulation) =
111 | ose.
112 | until(afterGeneration(5000)).
113 | trace { (s, is) => println("" + s.generation + " " + s.s._1.length) }.
114 | eval(new util.Random(42))
115 |
116 | File("./test/ose.csv") write OSE.result(ose, finalState, finalPopulation).map(i => (i.continuous ++ Vector(f(i.continuous))).mkString(",")).mkString("\n")
117 | }
118 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestOptimumDiversity.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 13/11/13 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU Affero General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.test
19 | //
20 | //import mgo.evolution._
21 | //import monocle.syntax._
22 | //import scala.util.Random
23 | //import scalax.io.Resource
24 | //
25 | //object TestOptimumDiversity extends App {
26 | //
27 | // /* trait OptimumDiversity <: Evolution
28 | // with BinaryTournamentSelection
29 | // with TournamentOnRankAndDiversity
30 | // with NonDominatedElitism
31 | // with DynamicApplicationGA
32 | // with DiversityRanking
33 | // with FitnessCrowdingDiversity
34 | // with CeilDiversityRanking
35 | // with MaxAggregation
36 | // with NonStrictDominance
37 | // with NoArchive
38 | // with CloneRemoval
39 | // with GeneticBreeding
40 | // with MGFitness
41 | // with ClampedGenome*/
42 | //
43 | // /*trait OptimumDiversity <: NoArchive
44 | // with BestRankedNicheElitism
45 | // with ParetoRanking
46 | // with GAGenotypeGridNiche
47 | // with MG
48 | // with FitnessCrowdingDiversity
49 | // with GeneticBreeding
50 | // with BinaryTournamentSelection
51 | // with TournamentOnRankAndDiversity
52 | // with DynamicApplicationGA
53 | // with NonStrictDominance
54 | // with ClampedGenome
55 | // with ProportionalNumberOfRound
56 | //
57 | // val m = new Rastrigin with OptimumDiversity with CounterTermination {
58 | // def genomeSize: Int = 2
59 | // def lambda: Int = 200
60 | // //def mu: Int = 200
61 | // def steps = 1000
62 | // def gridSize = Seq(0.25, 0.25)
63 | // }
64 | //
65 | // implicit val rng = new Random
66 | //
67 | // m.evolve.untilConverged { s =>
68 | // val output = Resource.fromFile(s"/tmp/novelty/novelty${s.generation}.csv")
69 | // s.population.foreach {
70 | // i => output.append((m.scale(i.genome &|-> m.values get)).mkString(",") + "," + i.fitness.mkString(",") + "\n")
71 | // }
72 | // }*/
73 | //
74 | // /*val m = new ZDT4 with OptimumDiversity with CounterTermination {
75 | // def genomeSize: Int = 2
76 | // def lambda: Int = 200
77 | // def steps = 400
78 | // def gridSize = Seq(0.1, 0.5, 0.5)
79 | // }
80 | //
81 | // implicit val rng = new Random
82 | //
83 | // m.evolve.untilConverged {
84 | // s =>
85 | // val output = Resource.fromFile(s"/tmp/novelty/novelty${s.generation}.csv")
86 | // s.population.foreach {
87 | // i => output.append((m.scale(i.genome &|-> m.values get)).mkString(",") + "," + i.fitness.mkString(",") + "\n")
88 | // }
89 | // //println(s.individuals.map(_.fitness))
90 | //
91 | // //import Ordering.Implicits._
92 | // //println(s.individuals.map(m.niche).sorted.mkString("\n"))
93 | //
94 | // //println(s.individuals.map(i => m.niche(i).mkString(",")).mkString("\n"))
95 | //
96 | // //println(s.individuals.size)
97 | // println(s.generation)
98 | // }*/
99 | //
100 | //}
101 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestPSE.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) Guillaume Chérel 06/05/14
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.test
19 |
20 | import mgo.evolution._
21 | import niche._
22 |
23 | object ZDT4PSE extends App {
24 |
25 | import algorithm._
26 | import algorithm.PSE._
27 |
28 | val pse = PSE(
29 | lambda = 10,
30 | phenotype = (c, d) => zdt4.compute(c, d),
31 | pattern =
32 | boundedGrid(
33 | lowBound = Vector(0.0, 0.0),
34 | highBound = Vector(1.0, 200.0),
35 | definition = Vector(10, 10)),
36 | continuous = zdt4.continuous(10))
37 |
38 | def evolution =
39 | pse.
40 | until(afterGeneration(1000)).
41 | trace((s, is) => println(s.generation))
42 |
43 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
44 |
45 | println(result(pse, finalPopulation).mkString("\n"))
46 | }
47 |
48 | object ZDT4NoisyPSE extends App {
49 |
50 | import algorithm._
51 | import algorithm.NoisyPSE._
52 |
53 | val pse: NoisyPSE[Vector[Double]] = NoisyPSE(
54 | lambda = 10,
55 | phenotype = (_, c, d) => zdt4.compute(c, d),
56 | pattern =
57 | boundedGrid(
58 | lowBound = Vector(0.0, 0.0),
59 | highBound = Vector(1.0, 200.0),
60 | definition = Vector(10, 10)),
61 | continuous = zdt4.continuous(10),
62 | aggregation = Aggregation.average)
63 |
64 | def evolution: RunAlgorithm[NoisyPSE[Vector[Double]], Individual[Vector[Double]], CDGenome.Genome, PSEState] =
65 | pse.
66 | until(afterGeneration(1000)).
67 | trace((s, is) => println(s.generation))
68 |
69 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
70 |
71 | println(result(pse, finalPopulation).mkString("\n"))
72 |
73 | }
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestProfile.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 08/01/13 Guillaume Chérel, Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.test
19 |
20 | import mgo.evolution._
21 |
22 | object SphereProfile extends App {
23 |
24 | import algorithm._
25 |
26 | //Profile the first dimension of the genome
27 | val algo: Profile[Int] = Profile(
28 | lambda = 100,
29 | fitness = discreteSphere.compute,
30 | niche = Profile.continuousProfile(discreteSphere.continuous(2), x = 0, nX = 10),
31 | continuous = discreteSphere.continuous(2),
32 | discrete = discreteSphere.discrete(2))
33 |
34 | def evolution =
35 | algo.
36 | until(afterGeneration(1000)).
37 | trace((s, is) => println(s.generation))
38 |
39 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
40 |
41 | println(
42 | Profile.result(algo, finalPopulation).map {
43 | r => (r.continuous ++ r.discrete ++ r.fitness).mkString(",")
44 | }.mkString("\n"))
45 |
46 | }
47 |
48 | object NoisySphereProfile extends App {
49 |
50 | import algorithm._
51 |
52 | def aggregation(history: Vector[Vector[Double]]): Vector[Double] = history.transpose.map(h => h.sum / h.size)
53 |
54 | val algo: NoisyProfile[Int, Vector[Double]] = NoisyProfile(
55 | muByNiche = 20,
56 | lambda = 100,
57 | fitness = noisyDiscreteSphere.compute,
58 | aggregation = aggregation,
59 | niche = NoisyProfile.continuousProfile(noisyDiscreteSphere.continuous(2), x = 0, nX = 10),
60 | continuous = noisyDiscreteSphere.continuous(2),
61 | discrete = noisyDiscreteSphere.discrete(2))
62 |
63 | def evolution =
64 | algo.
65 | until(afterGeneration(1000)).
66 | trace((s, is) => println(s.generation))
67 |
68 | val (finalState, finalPopulation) = evolution.eval(new util.Random(42))
69 |
70 | println:
71 | NoisyProfile.result(algo, finalPopulation).map:
72 | r => (r.continuous ++ r.discrete ++ r.fitness ++ Vector(r.replications)).mkString(",")
73 | .mkString("\n")
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/TestSMSEMOEA.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 2015 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //package mgo.test
18 | //
19 | //import mgo.evolution._
20 | //import mgo.evolution.algorithm._
21 | //
22 | //import scala.util.Random
23 | //import scalaz._
24 | //
25 | //object TestSMSEMOEAStochastic extends App {
26 | // def average(s: Seq[Double]) = s.sum / s.size
27 | //
28 | // val nsgaII = new SMSEMOEA {
29 | // def referencePoint = Seq(1000.0, 10)
30 | // def mu = 100
31 | // def fitness = Fitness(i => Seq(average(i.phenotype), 1.0 / i.phenotype.size))
32 | // override val cloneStrategy = queue(100)
33 | // type P = History[Double]
34 | // }
35 | //
36 | // import nsgaII._
37 | //
38 | // def dimensions = 10
39 | // def function = rastrigin
40 | //
41 | // def problem(g: G) = State { rng: Random =>
42 | // val scaled = function.scale(g.genomeValue)
43 | // val eval = function.compute(scaled)
44 | // (rng, eval + (rng.nextGaussian() * 0.5 * math.sqrt(eval)))
45 | // }
46 | //
47 | // val evo = evolution(nsgaII, 100)(randomGenome(dimensions), problem, afterGeneration(1000))
48 | //
49 | // import scala.Ordering.Implicits._
50 | // val (s, res) = evo.run(47)
51 | //
52 | // val oldest = res.minBy(i => Seq(-i.phenotype.size, average(i.phenotype)))
53 | //
54 | // println(res.count(_.phenotype.size == oldest.phenotype.size))
55 | // println(res.groupBy(_.genome.fromOperation).toList.map { case (k, v) => k -> v.size }.sortBy(_._1))
56 | // println(function.scale(oldest.genomeValue) + " " + average(oldest.phenotype) + " " + oldest.phenotype.size + " " + oldest.born)
57 | //
58 | //}
59 | //
60 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/test/package.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | package mgo
18 |
19 | import math._
20 | import mgo.evolution._
21 | import scala.util.Random
22 |
23 | package object test {
24 |
25 | given intConv: Conversion[IArray[Int], Vector[Int]] = _.toVector
26 | given doubleConv: Conversion[IArray[Double], Vector[Double]] = _.toVector
27 |
28 | def twoVarInter(x1: Double, x2: Double): Double = x1 + x2 + x1 * x2
29 |
30 | def average(s: Seq[Double]): Double = s.sum / s.size
31 |
32 | object sphere {
33 | def compute(i: Vector[Double]): Double = i.map(x => x * x).sum
34 | def genome(size: Int): Vector[C] = Vector.fill(size)(C(-2, 2))
35 | }
36 |
37 | object discreteSphere {
38 | def compute(x: Vector[Double], i: Vector[Int]): Vector[Double] = Vector(x.map(x => x * x).sum + i.map(math.abs).sum)
39 | def continuous(size: Int): Vector[C] = Vector.fill(size)(C(-2, 2))
40 | def discrete(size: Int): Vector[D] = Vector.fill(size)(D(-2, 2))
41 | }
42 |
43 | object noisyDiscreteSphere {
44 | def compute(rng: Random, v: Vector[Double], i: Vector[Int]): Vector[Double] = {
45 | val res = v.map(x => x * x).sum + i.map(math.abs).sum
46 | val noise = rng.nextGaussian() * 0.5 * res
47 | Vector(res + noise)
48 | }
49 | def continuous(size: Int): Vector[C] = Vector.fill(size)(C(-2, 2))
50 | def discrete(size: Int): Vector[D] = Vector.fill(size)(D(-2, 2))
51 | }
52 |
53 | object rastrigin {
54 | def continuous(size: Int): Vector[C] = Vector.fill(size)(C(-5.12, 5.12))
55 | def compute(i: Vector[Double]): Double =
56 | 10 * i.size + i.map(x => (x * x) - 10 * math.cos(2 * Pi * x)).sum
57 | }
58 |
59 | def himmelblau(x: Double, y: Double): Double = {
60 | def z(x: Double, y: Double) =
61 | pow(pow(x, 2) + y - 11, 2) + pow(x + pow(y, 2) - 7, 2)
62 |
63 | z(x.scale(-4.5, 4.5), y.scale(-4.5, 4.5))
64 | }
65 |
66 | def griewangk(g: Vector[Double]): Double = {
67 | val values = g.map(_.scale(-600, 600))
68 | 1.0 + values.map(x => math.pow(x, 2.0) / 4000).sum - values.zipWithIndex.map { case (x, i) => x / math.sqrt(i + 1.0) }.map(math.cos).reduce(_ * _)
69 | }
70 |
71 | def rosenbrock(x: Double, y: Double): Double = {
72 | val sx = x.scale(-2048.0, 2048.0)
73 | val sy = y.scale(-2048.0, 2048.0)
74 | pow(1 - sx, 2) + 100 * pow(sy - pow(sx, 2), 2)
75 | }
76 |
77 | def saltelliB(x: Vector[Double], omega: Vector[Double]): Double =
78 | (0 to (x.size - 1)).map(i => x(i) * omega(i)).sum
79 |
80 | // Simple MG Function created by Schaffer for 1985 VEGA paper
81 | def schaffer(x: Double): Seq[Double] = {
82 | val sx = x.scale(-100000.0, 100000.0)
83 | Seq(pow(sx, 2), pow(x - 2, 2))
84 | }
85 |
86 | object zdt4 {
87 |
88 | def continuous(size: Int): Vector[C] = Vector.fill(size)(C(0.0, 5.0))
89 | def discrete = Vector.empty
90 |
91 | def compute(genome: Vector[Double], d: Vector[Int]): Vector[Double] = {
92 | val genomeSize = genome.size
93 |
94 | def g(x: Seq[Double]) = 1 + 10 * (genomeSize - 1) + x.map { i => pow(i, 2) - 10 * cos(4 * Pi * i) }.sum
95 |
96 | def f(x: Seq[Double]) = {
97 | val gx = g(x)
98 | gx * (1 - sqrt(genome(0) / gx))
99 | }
100 |
101 | Vector(genome(0), f(genome.tail))
102 | }
103 |
104 | }
105 |
106 | object dropWave {
107 | def dropWave(x1: Double, x2: Double): Double = {
108 | val dist = pow(x1, 2) + pow(x2, 2)
109 | val top = 1 + cos(12 * sqrt(dist))
110 | val bottom = 0.5 * dist + 2
111 | -1 * top / bottom
112 | }
113 |
114 | def scale(s: Vector[Double]): Vector[Double] = s.map(_.scale(-5.12, 5.12))
115 |
116 | def compute(genome: Vector[Double]): Double = {
117 | val s = scale(genome)
118 | dropWave(s(0), s(1))
119 | }
120 | }
121 |
122 | //def sourceOf[T](v: sourcecode.Text[T]) = (v.value, v.source)
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/Breeze.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools
2 |
3 | import breeze.linalg.{ DenseMatrix, DenseVector }
4 |
5 | /*
6 | * Copyright (C) 2021 Romain Reuillon
7 | *
8 | * This program is free software: you can redistribute it and/or modify
9 | * it under the terms of the GNU Affero General Public License as published by
10 | * the Free Software Foundation, either version 3 of the License, or
11 | * (at your option) any later version.
12 | *
13 | * This program is distributed in the hope that it will be useful,
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 | * GNU Affero General Public License for more details.
17 | *
18 | * You should have received a copy of the GNU Affero General Public License
19 | * along with this program. If not, see .
20 | */
21 |
22 | object Breeze {
23 | def arrayToDenseMatrix(rows: Int, cols: Int, array: Array[Array[Double]]): DenseMatrix[Double] =
24 | // we need to transpose the array first because of breeze column first representation of matrices
25 | DenseMatrix.create(rows, cols, array.transpose.flatten)
26 |
27 | def arrayToDenseMatrix(array: Array[Array[Double]]) = {
28 | assert(!array.isEmpty)
29 | DenseMatrix.create(rows = array.length, cols = array.head.length, array.flatten)
30 | }
31 |
32 | def arrayToDenseVector(array: Array[Double]) = DenseVector(array: _*)
33 |
34 | def matrixToArray(m: DenseMatrix[Double]): Array[Array[Double]] =
35 | Array.tabulate(m.rows, m.cols)((i, j) => m(i, j))
36 |
37 | def matrixToArray(m: DenseMatrix[Double], w: DenseVector[Int]): Array[Array[Double]] =
38 | matrixToArray(m).zipWithIndex.flatMap { case (v, i) => Array.fill(w(i))(v) }
39 |
40 | def vectorToArray(m: DenseVector[Double]): Array[Double] = m.toArray
41 | }
42 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/CanBeNaN.scala:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Romain Reuillon on 08/01/16.
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | *
17 | */
18 | package mgo.tools
19 |
20 | object CanBeNaN:
21 | given CanBeNaN[Double] = _.isNaN
22 | given [T](using cbn: CanBeNaN[T]): CanBeNaN[Vector[T]] = _.exists(cbn.isNaN)
23 |
24 |
25 | trait CanBeNaN[T]:
26 | def isNaN(t: T): Boolean
27 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/HierarchicalRanking.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 24/03/13 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | object HierarchicalRanking {
21 |
22 | def downRank[T](values: Vector[T])(implicit ordering: Ordering[T]): Vector[Int] = upRank(values)(ordering.reverse)
23 |
24 | def upRank[T](values: Vector[T])(implicit ordering: Ordering[T]): Vector[Int] =
25 | values.
26 | zipWithIndex.
27 | sortBy { case (v, _) => v }.
28 | map { case (_, originalOrder) => originalOrder }.
29 | zipWithIndex.
30 | sortBy { case (originalOrder, _) => originalOrder }.
31 | map(_._2)
32 |
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/ImplementEqualMethod.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools
2 |
3 | /*
4 | * Copyright (C) 2025 Romain Reuillon
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | object ImplementEqualMethod:
21 | given ImplementEqualMethod[Int] = identity
22 | given ImplementEqualMethod[Double] = identity
23 |
24 | given [T: ImplementEqualMethod as imeq]: ImplementEqualMethod[IArray[T]] = a => IArray.genericWrapArray(a.map(imeq.apply))
25 | given [T: ImplementEqualMethod as imeq]: ImplementEqualMethod[Vector[T]] = v => v.map(imeq.apply)
26 | given [T1: ImplementEqualMethod as eq1, T2: ImplementEqualMethod as eq2]: ImplementEqualMethod[(T1, T2)] = t => (eq1(t._1), eq2(t._2))
27 |
28 | opaque type EQM[T] = Any
29 |
30 | def apply[T: ImplementEqualMethod as eq](t: T) = eq(t)
31 |
32 | trait ImplementEqualMethod[T]:
33 | def apply(t: T): ImplementEqualMethod.EQM[T]
34 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/KDTree.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) Guillaume Chérel 2/05/14
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | /**
21 | * KD-Tree algorithm from https://en.wikipedia.org/wiki/Kd-tree 5-5-2014
22 | *
23 | */
24 |
25 | trait KDTree {
26 | def node: Seq[Double]
27 | def left: KDTree
28 | def right: KDTree
29 |
30 | def distance(p1: Seq[Double], p2: Seq[Double]): Double =
31 | math.sqrt((p1 zip p2).map {
32 | case (x, y) => math.pow(x - y, 2)
33 | }.sum)
34 |
35 | def nearest(query: Seq[Double], depth: Int = 0): Seq[Double] = {
36 | val axis = depth % node.size
37 | val (naturalDirection, unnaturalDirection) = if (query(axis) < node(axis)) (left, right) else (right, left)
38 |
39 | val curBest =
40 | naturalDirection match {
41 | case EmptyTree => node
42 | case child => {
43 | val childBest = child.nearest(query, depth + 1)
44 | if (distance(query, node) < distance(query, childBest)) node else childBest
45 | }
46 | }
47 | val distCurBest = distance(query, curBest)
48 | val best =
49 | if (distCurBest <= (query(axis) - node(axis)).abs) curBest
50 | else unnaturalDirection match {
51 | case EmptyTree => curBest
52 | case child => {
53 | val childBest = child.nearest(query, depth + 1)
54 | if (distance(query, curBest) <= distance(query, childBest)) curBest else childBest
55 | }
56 | }
57 | best
58 | }
59 |
60 | def knearest(k: Int, query: Seq[Double], depth: Int = 0): Seq[Seq[Double]] = {
61 | val axis = depth % node.size
62 | val (naturalDirection, unnaturalDirection) = if (query(axis) < node(axis)) (left, right) else (right, left)
63 |
64 | val curBest =
65 | naturalDirection match {
66 | case EmptyTree => Vector(node)
67 | case child => {
68 | val childBest = child.knearest(k, query, depth + 1)
69 | insertInKNearest(childBest, node, query, k)
70 | }
71 | }
72 | val couldBeNearer: Boolean = curBest exists (distance(_, query) > (query(axis) - node(axis)).abs)
73 | val best =
74 | if (!couldBeNearer && curBest.size >= k) curBest
75 | else unnaturalDirection match {
76 | case EmptyTree => curBest
77 | case child => {
78 | val childBest = child.knearest(k, query, depth + 1)
79 |
80 | childBest.foldLeft(curBest)((kn, n) => insertInKNearest(kn, n, query, k))
81 | }
82 | }
83 | best
84 | }
85 |
86 | def insertInKNearest(l: Seq[Seq[Double]], e: Seq[Double], query: Seq[Double], k: Int): Seq[Seq[Double]] =
87 | (e +: l).sortWith(distance(_, query) < distance(_, query)).take(k)
88 |
89 | // def insertSortedWith(l: Seq[Seq[Double]], e: Seq[Double], lt: (Seq[Double], Seq[Double]) => Boolean): Seq[Seq[Double]] = {
90 | // if (l.size == 0) Vector(e)
91 | // else if (lt(e, l(0))) e +: l
92 | // else l(0) +: insertSortedWith(l.drop(1), e, lt)
93 | // }
94 |
95 | def toSeq: Seq[Seq[Double]] = (left.toSeq :+ node) ++ right.toSeq
96 |
97 | override def toString: String = s"Node($node, $left, $right)"
98 | }
99 |
100 | object EmptyTree extends KDTree {
101 | def node = Vector.empty
102 | def left: EmptyTree.type = this
103 | def right: EmptyTree.type = this
104 |
105 | override def nearest(query: Seq[Double], depth: Int = 0): Seq[Double] = Vector[Double]()
106 | override def knearest(k: Int, query: Seq[Double], depth: Int = 0): Seq[Seq[Double]] = Vector[Vector[Double]]()
107 | override def toString: String = "EmptyTree"
108 | override def toSeq: Seq[Seq[Double]] = Vector(node)
109 | }
110 |
111 | object KDTree {
112 | /**
113 | * @param pointList A size N sequence of size K sequences, representing N points in K dimensions
114 | * @return
115 | */
116 | def apply(pointList: Seq[Seq[Double]]): KDTree = {
117 | if (pointList.size == 0) EmptyTree
118 | else if (pointList(0).size == 0) EmptyTree
119 | else {
120 | val tPointList = transpose(pointList)
121 | val sortedDims = tPointList map (argSort)
122 | build(tPointList, sortedDims, 0)
123 | }
124 | }
125 |
126 | def transpose(pointList: Seq[Seq[Double]]): Seq[Seq[Double]] = {
127 | val k = pointList(0).size
128 | (0 until k).map(ki => pointList.map(_(ki)))
129 | }
130 |
131 | def build(pointList: Seq[Seq[Double]], sortedDims: Seq[Vector[Int]], depth: Int): KDTree = {
132 | val nDims = pointList.size
133 | val axis = depth % nDims
134 | val (medInt, medVal) = findLeftMostMedian(pointList(axis), sortedDims(axis))
135 | //println(s"medInt: $medInt medVal: $medVal")
136 | val split: Seq[(Vector[Int], Vector[Int])] = sortedDims.map(splitIdArrays(pointList(axis), _, medInt))
137 | //println("pointList:"+pointList)
138 | //println("sortedDims:"+sortedDims)
139 | //println("split:"+split)
140 | new KDTree {
141 | val node: Seq[Double] = pointList.map(_(medInt))
142 | val left: KDTree = if (split(0)._1.size > 0) build(pointList, split.map(_._1), depth + 1) else EmptyTree
143 | val right: KDTree = if (split(0)._2.size > 0) build(pointList, split.map(_._2), depth + 1) else EmptyTree
144 | }
145 | }
146 |
147 | /**
148 | * split the sorted indices according to threshold, such that elements in res._1 refer to points < threshold, and res._2
149 | * to points >= threshold. The element splitPoint excluded from the result
150 | */
151 | def splitIdArrays(points: Seq[Double], sortedIndices: Vector[Int], splitPoint: Int): (Vector[Int], Vector[Int]) =
152 | sortedIndices.foldLeft((Vector[Int](), Vector[Int]())) {
153 | case (split, i) =>
154 | if (points(i) < points(splitPoint)) (split._1 :+ i, split._2)
155 | else if (i == splitPoint) split
156 | else (split._1, split._2 :+ i)
157 | }
158 |
159 | def findLeftMostMedian(points: Seq[Double], sortedIndices: Vector[Int]): (Int, Double) = {
160 | var medInd = sortedIndices.size / 2
161 | while ((medInd > 0) && (points(sortedIndices(medInd - 1)) == points(sortedIndices(medInd)))) { medInd -= 1 }
162 | (sortedIndices(medInd), points(sortedIndices(medInd)))
163 | }
164 |
165 | /* return the indices of the given array referencing the arrays elements in increasing order
166 | */
167 | def argSort(a: Seq[Double]): Vector[Int] = (a zip a.indices).sortBy(_._1).map(_._2).toVector
168 | }
169 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/Lazy.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools
2 |
3 | /*
4 | * Copyright (C) 2021 Romain Reuillon
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | class Lazy[T](t: => T) {
21 | lazy val value: T = t
22 | }
23 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/LinearAlgebra.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 12/03/2019 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | import org.apache.commons.math3.linear.MatrixUtils
21 | import org.apache.commons.math3.linear.RealMatrix
22 |
23 | object LinearAlgebra {
24 |
25 | def functorVectorVectorDoubleToRealMatrix(f: Vector[Vector[Double]] => Vector[Vector[Double]])(thetas: RealMatrix): RealMatrix =
26 | MatrixUtils.createRealMatrix(
27 | f(thetas.getData.map { _.toVector }.toVector).map { _.toArray }.toArray)
28 |
29 | def functorVectorVectorDoubleToMatrix(f: Vector[Vector[Double]] => Vector[Vector[Double]])(thetas: Array[Array[Double]]): Array[Array[Double]] =
30 | f(thetas.map { _.toVector }.toVector).map { _.toArray }.toArray
31 | }
32 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/NeighborMatrix.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 14/11/12 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | import Function._
21 |
22 | object NeighborMatrix {
23 |
24 | def empty[S]: NeighborMatrix[S] =
25 | new NeighborMatrix[S] {
26 | def maxX = 0
27 | def maxY = 0
28 | def matrix(x: Int, y: Int) = None
29 | }
30 |
31 | def apply[S](elements: (Int, Int) => Option[S], mX: Int, mY: Int): NeighborMatrix[S] =
32 | new NeighborMatrix[S] {
33 | def maxX = mX
34 | def maxY = mY
35 | def matrix(x: Int, y: Int) = elements(x, y)
36 | }
37 |
38 | def apply[S](elements: Iterable[S], index: S => (Int, Int)): NeighborMatrix[S] = {
39 | val matrix = elements.map(e => index(e) -> e).toMap
40 | val maxX = matrix.keys.map(_._1).max + 1
41 | val maxY = matrix.keys.map(_._2).max + 1
42 | apply[S](untupled(matrix.get _), maxX, maxY)
43 | }
44 |
45 | }
46 |
47 | trait NeighborMatrix[T] {
48 |
49 | def matrix(x: Int, y: Int): Option[T]
50 | def maxX: Int
51 | def maxY: Int
52 |
53 | def knn(x: Int, y: Int, n: Int): List[(Int, Int)] =
54 | growUntilEnough(x, y, n).sortBy { case (x1, y1) => distance(x, y, x1, y1) }.take(n)
55 |
56 | def distance(x1: Int, y1: Int, x2: Int, y2: Int): Double = java.lang.Math.hypot(x2 - x1, y2 - y1)
57 |
58 | def isIn(x: Int, y: Int): Boolean = {
59 | def isIn(c: Int, maxC: Int) = c >= 0 && c <= maxC
60 | isIn(x, maxX) && isIn(y, maxY)
61 | }
62 |
63 | lazy val maxRange: Int = java.lang.Math.max(maxX, maxY)
64 |
65 | //TODO reuse previously found neighbours
66 | def growUntilEnough(x: Int, y: Int, n: Int, range: Int = 1): List[(Int, Int)] = {
67 | val included = (extrema(x, y, range) ::: square(x, y, range).toList).filter { case (x1, y1) => matrix(x1, y1).isDefined }
68 | if (included.size >= n || range > maxRange) included
69 | else growUntilEnough(x, y, n, range + 1)
70 | }
71 |
72 | def extrema(x: Int, y: Int, range: Int): List[(Int, Int)] =
73 | for {
74 | dx <- List(x - range - 1, x + range + 1)
75 | dy <- List(y - range - 1, y + range + 1)
76 | if isIn(dx, dy)
77 | } yield (dx, dy)
78 |
79 | def square(x: Int, y: Int, range: Int): IndexedSeq[(Int, Int)] =
80 | for {
81 | dx <- (x - range) to (x + range)
82 | dy <- (y - range) to (y + range)
83 | if !(dx == x && dy == y)
84 | if isIn(dx, dy)
85 | } yield (dx, dy)
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/Neighbours.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 13/11/13 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | trait Neighbours {
21 |
22 | def neighbours[A](distance: (A, A) => Double)(a: A, all: Seq[A], nb: Int): Seq[A] = {
23 | val distances = all.map(distance(a, _))
24 | (all zip distances).sortBy(_._2).take(nb).map(_._1)
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/RejectionSampler.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools
2 |
3 | /*
4 | * Copyright (C) 2021 Romain Reuillon
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Affero General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU Affero General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU Affero General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | object RejectionSampler {
21 |
22 | /**
23 | * Monte Carlo estimation of the success rate of the predicate.
24 | *
25 | * @param test
26 | * @param pass
27 | */
28 | case class State(test: Long = 0L, pass: Long = 0L) {
29 | def inverseProbability() = test.toDouble / pass
30 | }
31 |
32 | def success(state: State) = State(state.test + 1, state.pass + 1)
33 | def fail(state: State) = State(state.test + 1, state.pass)
34 | def noSuccess(state: State) = state.pass == 0
35 |
36 | }
37 |
38 | import RejectionSampler._
39 | import scala.annotation.tailrec
40 | /**
41 | * Rejection sampler with a predicate and a state.
42 | *
43 | * @param dist
44 | * @param patternFunction
45 | * @param accept
46 | */
47 | class RejectionSampler(_sample: () => (Vector[Double], Lazy[Double]), val accept: Vector[Double] => Boolean) {
48 |
49 | def warmup(n: Int, state: State = State()): State =
50 | if (n > 0) {
51 | val (x, _) = _sample()
52 | if (!accept(x)) warmup(n - 1, fail(state))
53 | else warmup(n - 1, success(state))
54 | } else state
55 |
56 | def sample(state: State = State()): (State, (Vector[Double], Double)) = {
57 | @tailrec def sample0(state: State): (State, (Vector[Double], Double)) = {
58 | val (x, density) = _sample()
59 | if (!accept(x)) {
60 | // if the sample is rejected, resample and keep the failure in the state
61 | sample0(fail(state))
62 | } else {
63 | val newState = success(state)
64 | // if the sample is accepted, return the state, the sample pattern and the adjusted density
65 | (newState, (x, density.value / newState.inverseProbability()))
66 | }
67 | }
68 |
69 | sample0(state)
70 | }
71 |
72 | @tailrec final def sampleVector(n: Int, state: State = State(), res: List[(Vector[Double], Double)] = List()): (State, Vector[(Vector[Double], Double)]) = {
73 | if (n > 0) {
74 | val (newState, newSample) = sample(state)
75 | sampleVector(n - 1, newState, newSample :: res)
76 | } else (state, res.reverse.toVector)
77 | }
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/Stats.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2018-02-22 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU Affero General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU Affero General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools
19 |
20 | import org.apache.commons.math3.distribution.EnumeratedIntegerDistribution
21 | import org.apache.commons.math3.distribution.MultivariateNormalDistribution
22 | import org.apache.commons.math3.distribution.NormalDistribution
23 | import org.apache.commons.math3.linear.LUDecomposition
24 | import org.apache.commons.math3.linear.MatrixUtils
25 | import org.apache.commons.math3.linear.RealMatrix
26 | import org.apache.commons.math3.linear.RealVector
27 | import org.apache.commons.math3.random.RandomGenerator
28 | import org.apache.commons.math3.stat.correlation.Covariance
29 | import java.lang.Math._
30 | import scala.util.Random
31 | import scala.collection.Searching.search
32 |
33 | object stats {
34 |
35 | def weightedCovariance(data: RealMatrix, weights: Array[Double]): RealMatrix = {
36 | val n = data.getRowDimension()
37 | val weightsSum = weights.sum
38 | val weightsSumSquared = pow(weightsSum, 2)
39 | val weightsSquaredSum = weights.map { pow(_, 2) }.sum
40 | val dataMean = MatrixUtils.createRealVector(
41 | data.transpose().operate(weights)).mapDivide(weightsSum)
42 | val dataCenteredWeighted = (0 to n - 1).map { i =>
43 | data.getRowVector(i).subtract(dataMean).mapMultiply(sqrt(weights(i)))
44 | .toArray
45 | }.toArray
46 | val cov = new Covariance(dataCenteredWeighted, false)
47 | .getCovarianceMatrix()
48 | .scalarMultiply(n.toDouble / weightsSum)
49 | // Compute the unbiased weighted variance
50 | .scalarMultiply(weightsSumSquared /
51 | (weightsSumSquared - weightsSquaredSum))
52 | cov
53 | }
54 |
55 | def weightedSample[T](n: Int, data: Vector[T], weights: Vector[Double])(implicit rng: Random): Vector[T] = {
56 | val cumul = weights.drop(1).scanLeft(weights(0)) {
57 | case (acc, x) => acc + x
58 | }
59 | val randoms = Vector.fill(n)(() => rng.nextDouble()).map(_())
60 | randoms.map { r => data(cumul.search(r).insertionPoint) }
61 | }
62 | }
63 |
64 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/clustering/Cluster.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 30/04/13 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU Affero General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.tools.clustering
19 | //
20 | //trait Cluster {
21 | // def centroid: Seq[Double]
22 | // def points: Seq[Seq[Double]]
23 | //}
24 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/clustering/EMGMM.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools.clustering
2 |
3 | import better.files.File
4 | import org.apache.commons.math3.distribution.{ MixtureMultivariateNormalDistribution, MultivariateNormalDistribution }
5 |
6 | import scala.annotation.tailrec
7 | import scala.util.Random
8 |
9 | /**
10 | * EM-GMM implementation.
11 | * Inspired by the work of Maël Fabien: https://github.com/maelfabien/EM_GMM_HMM
12 | */
13 | object EMGMM {
14 |
15 | /**
16 | * Full covariance Gaussian Mixture Model, trained using Expectation Maximization.
17 | *
18 | * @param x data points
19 | * @param columns number of data columns
20 | */
21 | def initializeAndFit(
22 | components: Int,
23 | iterations: Int,
24 | tolerance: Double,
25 | x: Array[Array[Double]],
26 | columns: Int,
27 | random: Random): (GMM, Seq[Double]) = {
28 | // initialize parameters
29 | // chose Random means in data points
30 | val means = random.shuffle(x.indices.toArray[Int]).take(components).map(c => x(c)).toArray
31 | // set equal weights to all components
32 | val weights = Array.fill(components)(1.0 / components)
33 | // compute covariances
34 | val covariances = Array.fill(components)(cov(x, columns))
35 | val (gmm, logLikelihoodTrace) =
36 | fit(
37 | x = x,
38 | means = means,
39 | covariances = covariances,
40 | weights = weights,
41 | components = components,
42 | iterations = iterations,
43 | tolerance = tolerance,
44 | trace = IndexedSeq())
45 |
46 | (gmm, logLikelihoodTrace)
47 | }
48 |
49 | def toDistribution(gmm: GMM, random: Random): MixtureMultivariateNormalDistribution = {
50 | import org.apache.commons.math3.distribution._
51 | import org.apache.commons.math3.util._
52 |
53 | import scala.jdk.CollectionConverters._
54 |
55 | def dist = (gmm.means zip gmm.covariances).map { case (m, c) => new MultivariateNormalDistribution(m, c) }
56 | def pairs = (dist zip gmm.weights).map { case (d, w) => new Pair(java.lang.Double.valueOf(w), d) }.toList
57 |
58 | new MixtureMultivariateNormalDistribution(mgo.tools.apacheRandom(random), pairs.asJava)
59 | }
60 |
61 | @tailrec
62 | def fit(
63 | x: Array[Array[Double]],
64 | means: Array[Array[Double]],
65 | covariances: Array[Array[Array[Double]]],
66 | weights: Array[Double],
67 | components: Int,
68 | iterations: Int,
69 | tolerance: Double,
70 | logLikelihood: Double = 0.0,
71 | trace: Seq[Double] = Seq()): (GMM, Seq[Double]) = {
72 | def gmm =
73 | GMM(
74 | means = means,
75 | covariances = covariances,
76 | weights = weights)
77 |
78 | iterations match {
79 | case 0 => (gmm, trace)
80 | case i =>
81 | val (updatedLogLikelihood, resp) = eStep(x, means, covariances, weights)
82 | val (updatedWeights, updatedMeans, updatedCovariances) = mStep(x, resp, components)
83 | if (math.abs(updatedLogLikelihood - logLikelihood) <= tolerance) (gmm, trace :+ updatedLogLikelihood)
84 | else fit(
85 | x = x,
86 | means = updatedMeans,
87 | covariances = updatedCovariances,
88 | weights = updatedWeights,
89 | logLikelihood = updatedLogLikelihood,
90 | components = components,
91 | iterations = i - 1,
92 | tolerance = tolerance,
93 | trace = trace :+ updatedLogLikelihood)
94 | }
95 | }
96 |
97 | /**
98 | * Estimate a covariance matrix, given data.
99 | * @param x data points
100 | * @param columns number of columns of the points
101 | */
102 | def cov(x: Array[Array[Double]], columns: Int): Array[Array[Double]] = {
103 | val mean = Array.tabulate(columns) { i => x.map(_(i)).sum / x.length }
104 | val m = x.map(_.zipWithIndex.map(v => v._1 - mean(v._2)))
105 | val p = m.map(v => v.map(x => v.map(_ * x)))
106 | Array.tabulate(columns, columns)((i, j) => p.map(_(i)(j)).sum / (x.length - 1))
107 | }
108 |
109 | /**
110 | * E-step: compute responsibilities,
111 | * update resp matrix so that resp[j, k] is the responsibility of cluster k for data point j,
112 | * to compute likelihood of seeing data point j given cluster k.
113 | *
114 | * @param x data points
115 | * @param means means of the components (clusters)
116 | * @param covariances covariances of the components (clusters)
117 | * @param weights weights of the components (clusters)
118 | */
119 | def eStep(x: Array[Array[Double]], means: Array[Array[Double]],
120 | covariances: Array[Array[Array[Double]]], weights: Array[Double]): (Double, Array[Array[Double]]) = {
121 | // resp matrix
122 | val resp = compute_log_likelihood(x, means, covariances, weights)
123 | val sum = resp.map(_.sum)
124 | val log_likelihood = sum.map(math.log).sum
125 | val updatedResp = resp.zip(sum).map { case (v, div) => v.map(_ / div) }
126 | (log_likelihood, updatedResp)
127 | }
128 |
129 | /**
130 | * Compute the log likelihood (used for e step).
131 | * @param x data points
132 | * @param means means of the components (clusters)
133 | * @param covariances covariances of the components (clusters)
134 | * @param weights weights of the components (clusters)
135 | */
136 | def compute_log_likelihood(x: Array[Array[Double]], means: Array[Array[Double]], covariances: Array[Array[Array[Double]]], weights: Array[Double]): Array[Array[Double]] = {
137 | weights.zipWithIndex.map { case (prior, k) => x.map(x => new MultivariateNormalDistribution(means(k), covariances(k)).density(x) * prior) }.transpose
138 | }
139 |
140 | /**
141 | * M-step, update parameters.
142 | * @param X data points
143 | */
144 | def mStep(X: Array[Array[Double]], resp: Array[Array[Double]], components: Int): (Array[Double], Array[Array[Double]], Array[Array[Array[Double]]]) = {
145 | // sum the columns to get total responsibility assigned to each cluster, N^{soft}
146 | val resp_weights = Array.tabulate(components)(i => resp.map(_(i)).sum)
147 | // normalized weights
148 | val weights = resp_weights.map(_ / X.length)
149 | // means
150 | val weighted_sum = dot(resp.transpose, X)
151 | val means = weighted_sum.zip(resp_weights).map { case (array, w) => array.map(_ / w) }
152 | // covariance
153 | val resp_t = resp.transpose
154 | val covariances = Array.tabulate(components) { k =>
155 | val diff = X.map(x => x.indices.map(i => x(i) - means(k)(i)).toArray).transpose
156 | val resp_k = resp_t(k)
157 | val w_sum = dot(diff.map { l => l.zip(resp_k).map { case (a, b) => a * b } }, diff.transpose)
158 | w_sum.map(_.map(_ / resp_weights(k)))
159 | }
160 | (weights, means, covariances)
161 | }
162 |
163 | /**
164 | * 2d matrix dot product.
165 | * @param A matrix A
166 | * @param B matrix B
167 | */
168 | def dot(A: Array[Array[Double]], B: Array[Array[Double]]): Array[Array[Double]] = {
169 | Array.tabulate(A.length)(i => B.indices.map(j => B(j).map(_ * A(i)(j))).transpose.map(_.sum).toArray)
170 | }
171 |
172 | def dilate(gmm: GMM, f: Double): GMM =
173 | gmm.copy(covariances = gmm.covariances.map(_.map(_.map(_ * f))))
174 |
175 | }
176 |
177 | //object GMM {
178 | //
179 | // import ppse.tool.Display._
180 | //
181 | // def toString(gmm: GMM): String = {
182 | // s"${arrayToString(gmm.weights)},${arrayToString(gmm.means)},${arrayToString(gmm.covariances)}"
183 | // }
184 | //
185 | //}
186 |
187 | case class GMM(
188 | means: Array[Array[Double]],
189 | covariances: Array[Array[Array[Double]]],
190 | weights: Array[Double]) {
191 | def components = means.size
192 | }
193 |
194 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/clustering/KMeans.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 30/04/13 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU Affero General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.tools.clustering
19 | //
20 | //import math._
21 | //import scala.util.Random
22 | //import scala.collection.mutable.ListBuffer
23 | //import mgo.tools.distance.Distance
24 | //
25 | //trait KMeans <: Distance {
26 | //
27 | // def updateClusters(p: Seq[Cluster])(implicit rng: Random): Seq[Cluster] = {
28 | // val ps = p.flatMap(_.points)
29 | // val cs = p.map(_.centroid)
30 | // createClusters(ps, cs)
31 | // }
32 | //
33 | // def createClusters(points: Seq[Seq[Double]], centroids: Seq[Seq[Double]])(implicit rng: Random): Seq[Cluster] = {
34 | // val assignations =
35 | // for (p <- points) yield {
36 | // val centroid = centroids.minBy(c => distance(p, c))
37 | // centroid -> p
38 | // }
39 | //
40 | // def ensureNumberOfCentroids(as: Seq[(Seq[Double], Seq[Double])]): Map[Seq[Double], Seq[Seq[Double]]] = {
41 | // val map = as.groupBy(_._1).map {
42 | // case (c, p) => c -> p.unzip._2
43 | // }
44 | // val missing = centroids.size - map.keys.size
45 | // if (missing <= 0) map
46 | // else {
47 | // val shuffled = rng.shuffle(as)
48 | // val reassing = shuffled.take(missing).map {
49 | // case (_, p) => p -> p
50 | // }
51 | // ensureNumberOfCentroids(reassing.toList ::: shuffled.drop(missing).toList)
52 | // }
53 | // }
54 | //
55 | // ensureNumberOfCentroids(assignations).toList.map {
56 | // case (_, ps) =>
57 | // val c = centroid(ps)
58 | // new Cluster {
59 | // def centroid = c
60 | // def points = ps
61 | // }
62 | // }
63 | // }
64 | //
65 | // def centroid(p: Seq[Seq[Double]]) = p.transpose.map(c => c.sum / c.size)
66 | //
67 | // def initialCentroids(p: Seq[Seq[Double]])(implicit rng: Random): Seq[Seq[Double]]
68 | //
69 | // def compute(p: Seq[Seq[Double]])(implicit rng: Random): Seq[Cluster] = {
70 | // def step(clusters: Seq[Cluster]): Seq[Cluster] = {
71 | // val newClusters = updateClusters(clusters)
72 | // if (stop(clusters, newClusters)) newClusters
73 | // else step(updateClusters(clusters))
74 | // }
75 | //
76 | // step(createClusters(p, initialCentroids(p)))
77 | // }
78 | //
79 | // def stop(old: Seq[Cluster], current: Seq[Cluster]) = {
80 | // import Ordering.Implicits._
81 | // old.map(_.centroid).sorted == current.map(_.centroid).sorted
82 | // }
83 | //
84 | //}
85 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/clustering/RandomCentroids.scala:
--------------------------------------------------------------------------------
1 | ///*
2 | // * Copyright (C) 30/04/13 Romain Reuillon
3 | // *
4 | // * This program is free software: you can redistribute it and/or modify
5 | // * it under the terms of the GNU Affero General Public License as published by
6 | // * the Free Software Foundation, either version 3 of the License, or
7 | // * (at your option) any later version.
8 | // *
9 | // * This program is distributed in the hope that it will be useful,
10 | // * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // * GNU Affero General Public License for more details.
13 | // *
14 | // * You should have received a copy of the GNU General Public License
15 | // * along with this program. If not, see .
16 | // */
17 | //
18 | //package mgo.tools.clustering
19 | //
20 | //import scala.util.Random
21 | //
22 | //trait RandomCentroids {
23 | // def nb: Int
24 | // def initialCentroids(p: Seq[Seq[Double]])(implicit rng: Random) = rng.shuffle(p).take(nb)
25 | //}
26 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/execution/ExposedEval.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2019 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 | package mgo.tools.execution
18 |
19 | import scala.annotation.tailrec
20 | import scala.util.Random
21 |
22 | /**
23 | * An datastructure describing an computation which at some points delegates
24 | * some work to the user. The user receives an intermediate state
25 | * (type SI) and a value of type X by calling pre.
26 | * The value needs to be transformed into another of type
27 | * Y that is fed back to the computation, along with the intermediate
28 | * state with the functions post. e.g. pseudo-code to
29 | * run the whole computation:
30 | *
31 | * (si, x) = pre()
32 | * y = f(x)
33 | * result = post(si, y)
34 | *
35 | */
36 | case class ExposedEval[X, U, S, V, Y](
37 | pre: X => (S, U),
38 | post: (S, V) => Y) {
39 |
40 | def run(f: U => V)(s: X): Y = {
41 | val (pass, x) = pre(s)
42 | val ys = f(x)
43 | post(pass, ys)
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/execution/MonoidParallel.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 29/01/2018 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.execution
19 |
20 | import org.apache.commons.math3.random.RandomGenerator
21 | import scala.annotation.tailrec
22 | import scala.concurrent.ExecutionContext
23 | import scala.concurrent.Future
24 | import scala.util.{ Try, Success, Failure }
25 |
26 | import mgo.abc._
27 |
28 | /**
29 | * @param empty The monoid identity element, such that: append(empty, s) == s, append(s, empty) == s
30 | * @param append The monoid binary operator to combine 2 states
31 | * @param split Split the current state. The second member of the tuple is to be sent as input to the next step. The first is to become the new current state. This function can compute new states or simply duplicate its input, untouched, e.g. split(s) == (s, s).
32 | */
33 | case class MonoidParallel[S](
34 | empty: S,
35 | append: (S, S) => S,
36 | split: S => (S, S),
37 | step: S => S,
38 | parallel: Int,
39 | stepSize: Int,
40 | stop: S => Boolean) {
41 |
42 | def run(
43 | implicit
44 | ec: ExecutionContext): Try[S] = {
45 |
46 | @tailrec
47 | def go(curS: S, running: Vector[Future[S]]): Try[S] = {
48 | waitForNextT(running) match {
49 | case Success((res, left)) => {
50 | val newS = append(curS, res)
51 | if (stop(newS)) { new Success(newS) }
52 | else {
53 | val (newS_1, newS_2) = split(newS)
54 | go(newS_1, left :+ Future(fullStep(step, stepSize, newS_2)))
55 | }
56 | }
57 | case Failure(e) => Failure(e)
58 | }
59 | }
60 |
61 | go(empty, init(empty, parallel).map { i => Future(i) })
62 | }
63 |
64 | def scan(
65 | implicit
66 | ec: ExecutionContext): Vector[S] = {
67 |
68 | @tailrec
69 | def go(curS: S, running: Vector[Future[S]], acc: Vector[S]): Vector[S] = {
70 | waitForNext(running) match {
71 | case (res, remaining) => {
72 | val newS = append(curS, res)
73 | if (stop(newS)) { acc :+ newS }
74 | else {
75 | val (newS_1, newS_2) = split(newS)
76 | go(newS_1, remaining :+ Future(fullStep(step, stepSize, newS_2)), acc :+ newS_1)
77 | }
78 | }
79 | }
80 | }
81 |
82 | go(empty, init(empty, parallel).map { i => Future(i) }, Vector.empty)
83 | }
84 |
85 | def init(start: S, n: Int): Vector[S] = {
86 | if (n <= 0) Vector.empty
87 | else if (n == 1) Vector(start)
88 | else {
89 | val (s1, s2) = split(start)
90 | (s2 +: init(s1, n - 1))
91 | }
92 | }
93 |
94 | @tailrec
95 | final def fullStep(step: S => S, stepSize: Int, s: S): S = {
96 | if (stepSize <= 0) s
97 | else fullStep(step, stepSize - 1, step(s))
98 | }
99 |
100 | @tailrec
101 | final def waitForNextT(running: Vector[Future[S]]): Try[(S, Vector[Future[S]])] = {
102 | val r = running.head
103 | val rs = running.tail
104 | r.value match {
105 | case None => waitForNextT(rs :+ r)
106 | case Some(Failure(error)) =>
107 | new Failure(new Throwable("Error in a running job: " ++ error.toString))
108 | case Some(Success(a)) => new Success((a, rs))
109 | }
110 | }
111 |
112 | @tailrec
113 | final def waitForNext(running: Vector[Future[S]]): (S, Vector[Future[S]]) = {
114 | val r = running.head
115 | val rs = running.tail
116 | r.value match {
117 | case None => waitForNext(rs :+ r)
118 | case Some(ta) => (ta.get, rs)
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/execution/Sequential.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 29/01/2018 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.execution
19 |
20 | import org.apache.commons.math3.random.RandomGenerator
21 | import scala.annotation.tailrec
22 | import scala.concurrent.ExecutionContext
23 | import scala.concurrent.Future
24 | import scala.util.{ Try, Success, Failure }
25 |
26 | import mgo.abc._
27 |
28 | case class Sequential[S](
29 | init: () => S,
30 | step: S => S,
31 | stop: S => Boolean) {
32 |
33 | def run: S = {
34 |
35 | @tailrec
36 | def go(s: S): S =
37 | if (stop(s)) s
38 | else go(step(s))
39 |
40 | go(init())
41 | }
42 |
43 | def scan: Vector[S] = {
44 | @tailrec
45 | def go(s: S, acc: Vector[S]): Vector[S] =
46 | if (stop(s)) { acc :+ s }
47 | else {
48 | val nextS = step(s)
49 | go(nextS, acc :+ s)
50 | }
51 |
52 | go(init(), Vector.empty)
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/metric/ClosedCrowdingDistance.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 03/03/2014 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.metric
19 |
20 | import cats._
21 |
22 | /**
23 | * Modification of the crowding distance to avoid infinite values for the first and last
24 | * point in each domain. The crowding for the second and last element of each domain is instead
25 | * the distance between the first and second (or last and second but last).
26 | *
27 | * Crowding distance computation see Deb, K., Agrawal, S., Pratap, A. & Meyarivan, T.
28 | * A fast elitist non-dominated sorting genetic algorithm for multi-objective
29 | * optimization: NSGA-II. Lecture notes in computer science 1917, 849–858 (2000).
30 | */
31 | object ClosedCrowdingDistance {
32 |
33 | /**
34 | * Compute the closed crowding distance
35 | *
36 | * @param data the set of point
37 | * @return the crowding distance of each point in the same order as the input
38 | * sequence
39 | */
40 | def apply(data: Seq[Seq[Double]]): Seq[Later[Double]] = {
41 | if (data.size < 2) data.map(d => Later(Double.PositiveInfinity))
42 | else {
43 | class CrowdingInfo(val d: Seq[Double], var crowding: Double = 0.0)
44 |
45 | val crowding = data.map(new CrowdingInfo(_))
46 |
47 | // for each objective
48 | for (curDim <- 0 until data.head.size) {
49 |
50 | val curCrowding = crowding.sortBy(_.d(curDim))
51 |
52 | val firstCrowdingInfo = curCrowding.head
53 | val secondCrowdingInfo = curCrowding(1)
54 | val secondButLastCrowdingInfo = curCrowding(curCrowding.size - 2)
55 | val lastCrowdingInfo = curCrowding.last
56 |
57 | val first = firstCrowdingInfo.d
58 | val second = secondCrowdingInfo.d
59 | val secondButLast = secondButLastCrowdingInfo.d
60 | val last = lastCrowdingInfo.d
61 |
62 | val min = first(curDim)
63 | val max = last(curDim)
64 |
65 | val maxMinusMin = max - min
66 |
67 | firstCrowdingInfo.crowding += (second(curDim) - first(curDim)) * 2 / maxMinusMin
68 | lastCrowdingInfo.crowding += (last(curDim) - secondButLast(curDim)) * 2 / maxMinusMin
69 |
70 | val itOpod = curCrowding.iterator
71 | var ptMinus1 = itOpod.next
72 | var pt = itOpod.next
73 |
74 | while (itOpod.hasNext) {
75 | val ptPlus1 = itOpod.next
76 | pt.crowding += (ptPlus1.d(curDim) - ptMinus1.d(curDim)) / maxMinusMin
77 | ptMinus1 = pt
78 | pt = ptPlus1
79 | }
80 | }
81 |
82 | crowding.map(c => Later(c.crowding))
83 | }
84 | }
85 |
86 | /**
87 | * Compute the closed crowding distance of the i-th point in the data points
88 | *
89 | * @param point the point
90 | * @param data the set of point
91 | * @return the closed crowding distance of the point relative to the data
92 | */
93 |
94 | def of(i: Int, data: Seq[Seq[Double]]): Later[Double] = {
95 | if (data.size < 2) Later(Double.PositiveInfinity)
96 | else {
97 | var crowdingOfI: Double = 0.0
98 |
99 | // for each objective
100 | for (curDim <- 0 until data.head.size) {
101 |
102 | val curSortedIdx = data.indices.sortBy(data(_)(curDim))
103 |
104 | val first = data(curSortedIdx.head)
105 | val last = data(curSortedIdx.last)
106 |
107 | val min = first(curDim)
108 | val max = last(curDim)
109 |
110 | val maxMinusMin = max - min
111 |
112 | if (i == curSortedIdx.head) crowdingOfI += (data(curSortedIdx(1))(curDim) - first(curDim)) * 2 / maxMinusMin
113 | else if (i == curSortedIdx.last) crowdingOfI += (last(curDim) - data(curSortedIdx(curSortedIdx.size - 2))(curDim)) * 2 / maxMinusMin
114 | else {
115 | var j = 1
116 | while (curSortedIdx(j) != i) {
117 | j += 1
118 | }
119 | val ptMinus1 = data(curSortedIdx(j - 1))
120 | val ptPlus1 = data(curSortedIdx(j + 1))
121 | crowdingOfI += (ptPlus1(curDim) - ptMinus1(curDim)) / maxMinusMin
122 | }
123 | }
124 |
125 | Later(crowdingOfI)
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/metric/CrowdingDistance.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 Romain Reuillon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.metric
19 |
20 | import cats.implicits._
21 |
22 | /**
23 | * Crowding distance computation see Deb, K., Agrawal, S., Pratap, A. & Meyarivan, T.
24 | * A fast elitist non-dominated sorting genetic algorithm for multi-objective
25 | * optimization: NSGA-II. Lecture notes in computer science 1917, 849–858 (2000).
26 | */
27 | object CrowdingDistance:
28 |
29 | /**
30 | * Compute the crowding distance
31 | *
32 | * @param data the set of point
33 | * @return the crowding distance of each point in the same order as the input
34 | * sequence
35 | */
36 | def computeCrowdingDistance(data: Seq[Vector[Double]]): Seq[Double] =
37 | case class Individual(objectives: Vector[Double], var distance: Double = 0.0)
38 |
39 | val n = data.length
40 | if n == 0
41 | then Seq.empty
42 | else
43 | val front = data.map(Individual(_, 0.0))
44 | val numObjectives = front.head.objectives.length
45 |
46 | for
47 | m <- 0 until numObjectives
48 | do
49 | val sorted = front.sortBy(_.objectives(m))
50 |
51 | sorted.head.distance = Double.PositiveInfinity
52 | sorted.last.distance = Double.PositiveInfinity
53 |
54 | val minObj = sorted.head.objectives(m)
55 | val maxObj = sorted.last.objectives(m)
56 | val range = maxObj - minObj
57 |
58 | if range != 0.0
59 | then
60 | for (i <- 1 until n - 1)
61 | do
62 | val prev = sorted(i - 1).objectives(m)
63 | val next = sorted(i + 1).objectives(m)
64 | sorted(i).distance += (next - prev) / range
65 |
66 | front.map(_.distance)
67 |
68 | def apply(data: Vector[Vector[Double]]): Vector[Double] =
69 | val distinctData = data.distinct
70 | val crowding = (distinctData zip computeCrowdingDistance(distinctData)).toMap
71 | data.map(crowding)
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/metric/HyperVolumeApprox.scala:
--------------------------------------------------------------------------------
1 | package mgo.tools.metric
2 |
3 | /**
4 | *
5 | * Bader, J., & Zitzler, E. (2011). HypE: An algorithm for fast hypervolume-based many-objective optimization. Evolutionary computation, 19(1), 45-76.
6 | *
7 | * other indic (entropy?)
8 | * Santos, T., & Xavier, S. (2018). A Convergence Indicator for Multi-Objective Optimisation Algorithms. TEMA (São Carlos), 19(3), 437-448.
9 | *
10 | * Greenhalgh, D., & Marshall, S. (2000). Convergence criteria for genetic algorithms. SIAM Journal on Computing, 30(1), 269-282.
11 | */
12 | object HyperVolumeApprox {
13 |
14 | }
15 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/metric/KNearestNeighboursAverageDistance.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 09/05/2014 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.metric
19 |
20 | import cats._
21 | import mgo.tools.KDTree
22 |
23 | /**
24 | * Distance to the K Nearest Neighbours using the KD-Tree algorithm
25 | *
26 | */
27 |
28 | object KNearestNeighboursAverageDistance {
29 |
30 | def apply(values: Vector[Seq[Double]], k: Int): Vector[Later[Double]] = {
31 | val tree = KDTree(values)
32 |
33 | values.map {
34 | v =>
35 | Later {
36 | val neighbours = tree.knearest(k, v)
37 | neighbours.foldLeft(0: Double) { case (sum, cur) => sum + tree.distance(cur.toSeq, v) } / neighbours.size
38 | }
39 | }
40 | }
41 | }
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/network/DenseTopology.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.network
19 |
20 | trait DenseTopology[E] {
21 | def in(u: Int): Vector[(Int, E)] = (matrix.zipWithIndex) map { case (row, v) => (v, row(u)) }
22 | def out(u: Int): Vector[(Int, E)] = (matrix(u).zipWithIndex) map { case (d, v) => (v, d) }
23 | def edge(u: Int, v: Int): Option[E] = Some(matrix(u)(v))
24 | def iteredges: Iterator[(Int, Int, E)] =
25 | matrix.iterator.zipWithIndex.flatMap {
26 | case (row, u) =>
27 | row.iterator.zipWithIndex.map { case (e, v) => (u, v, e) }
28 | }
29 |
30 | def matrix: Vector[Vector[E]]
31 | }
32 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/network/DirectedEdges.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.network
19 |
20 | trait DirectedEdges[E] {
21 | def outedges(u: Int): Vector[(Int, Int, E)] = out(u) map { case (v, d) => (u, v, d) }
22 | def inedges(u: Int): Vector[(Int, Int, E)] = in(u) map { case (v, d) => (v, u, d) }
23 | def outneighbours(u: Int): Vector[Int] = out(u) map { _._1 }
24 | def inneighbours(u: Int): Vector[Int] = in(u) map { _._1 }
25 |
26 | val dotGraphType: String = "digraph"
27 | val dotEdgeOperator: String = "->"
28 |
29 | def in(u: Int): Vector[(Int, E)]
30 | def out(u: Int): Vector[(Int, E)]
31 | }
32 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/network/Network.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.network
19 |
20 | import collection.JavaConverters._
21 |
22 | /**
23 | * N = Node data type
24 | * E = Edge data type
25 | */
26 | trait Network[N, E] {
27 | def node(u: Int): N = nodes(u)
28 | def iternodes: Iterator[(Int, N)] = nodes.indices.iterator zip nodes.iterator
29 |
30 | def nodes: Vector[N]
31 | def edge(u: Int, v: Int): Option[E]
32 | def iteredges: Iterator[(Int, Int, E)]
33 |
34 | /** Either digraph or graph */
35 | def dotGraphType: String
36 |
37 | // def toDot(
38 | // graphId: String,
39 | // nodeAttr: N => Seq[(String, String)],
40 | // edgeAttr: E => Seq[(String, String)],
41 | // additionalStatements: String): String =
42 | // s"""$dotGraphType $graphId {
43 | // |${additionalStatements.lines.map { " " ++ _ }.iterator().asScala.mkString { "\n" }}
44 | // |${toDotNodes(nodeAttr).lines.map { " " ++ _ }.iterator().asScala.mkString { "\n" }}
45 | // |${toDotEdges(edgeAttr).lines.map { " " ++ _ }.iterator().asScala.mkString { "\n" }}
46 | // |}""".stripMargin
47 |
48 | def toDotNodes(nodeAttr: N => Seq[(String, String)]): String =
49 | iternodes.map {
50 | case (i, n) => s"""$i [ ${
51 | nodeAttr(n).map {
52 | case (k, v) => s"$k = $v"
53 | }.mkString(", ")
54 | } ]"""
55 | }.mkString("\n")
56 |
57 | /** Either -> or -- */
58 | def dotEdgeOperator: String
59 |
60 | def toDotEdges(edgeAttr: E => Seq[(String, String)]): String =
61 | iteredges.map {
62 | case (u, v, e) => s"""$u $dotEdgeOperator $v [ ${
63 | edgeAttr(e).map {
64 | case (k, v) => s"$k = $v"
65 | }.mkString(", ")
66 | } ]"""
67 | }.mkString("\n")
68 |
69 | // def toJSONNodeLink: String =
70 | // s"""{
71 | // "nodes":${toJSONNodes.lines.map { " " ++ _ }.iterator().asScala.mkString { "\n" }},
72 | // "links":${toJSONLinks.lines.map { " " ++ _ }.iterator().asScala.mkString { "\n" }}
73 | //}"""
74 |
75 | def toJSONNodes: String =
76 | "[\n" ++
77 | iternodes.map { case (i, n) => s""" {"id":$i, "data":"$n"}""" }.mkString(",\n") ++
78 | "\n]"
79 |
80 | def toJSONLinks: String =
81 | "[\n" ++
82 | iteredges.map { case (u, v, e) => s""" {"source":$u, "target": $v, "data": $e}""" }.mkString(",\n") ++
83 | "\n]"
84 |
85 | }
86 |
87 | object Network {
88 | def directedSparse[N, E](
89 | _nodes: IndexedSeq[N],
90 | _edges: Traversable[(Int, Int, E)]): Network[N, E] with DirectedEdges[E] with SparseTopology[E] =
91 | new Network[N, E] with DirectedEdges[E] with SparseTopology[E] {
92 | val nodes = _nodes.toVector
93 | val mapin = SparseTopology.mapinFrom(_edges)
94 | val mapout = SparseTopology.mapoutFrom(_edges)
95 | }
96 |
97 | def directedSparse[E](
98 | nbOfNodes: Int,
99 | _edges: Traversable[(Int, Int, E)]): Network[Unit, E] with DirectedEdges[E] with SparseTopology[E] =
100 | new Network[Unit, E] with DirectedEdges[E] with SparseTopology[E] {
101 | val nodes: Vector[Unit] = Vector.fill(nbOfNodes)((): Unit)
102 | val mapin = SparseTopology.mapinFrom(_edges)
103 | val mapout = SparseTopology.mapoutFrom(_edges)
104 | }
105 |
106 | def directedDense[N, E](
107 | _nodes: IndexedSeq[N],
108 | _edges: Vector[Vector[E]]): Network[N, E] with DirectedEdges[E] with DenseTopology[E] =
109 | new Network[N, E] with DirectedEdges[E] with DenseTopology[E] {
110 | val nodes = _nodes.toVector
111 | val matrix = _edges
112 | }
113 | }
114 |
115 |
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/network/SparseTopology.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.network
19 |
20 | import collection.immutable.IntMap
21 |
22 | trait SparseTopology[E] {
23 | def in(u: Int): Vector[(Int, E)] = mapin.getOrElse(u, Vector.empty).toVector
24 | def out(u: Int): Vector[(Int, E)] = mapout.getOrElse(u, Vector.empty).toVector
25 | def edge(u: Int, v: Int): Option[E] = if ((mapout contains u) && (mapout(u) contains v)) Some(mapout(u)(v)) else None
26 | def iteredges: Iterator[(Int, Int, E)] =
27 | if (mapout.isEmpty)
28 | Iterator.empty
29 | else
30 | mapout.iterator.flatMap {
31 | case (node1, mapoutnode1) =>
32 | mapoutnode1.iterator.map { case (node2, e) => (node1, node2, e) }
33 | }
34 |
35 | def mapin: IntMap[IntMap[E]] //mapin(u) edges leading into u
36 | def mapout: IntMap[IntMap[E]] //mapout(u) edges leading out of u
37 | }
38 |
39 | object SparseTopology {
40 |
41 | def mapinFrom[E](s: Traversable[(Int, Int, E)]): IntMap[IntMap[E]] = {
42 | IntMap.empty[IntMap[E]] ++ (s.groupBy { _._2 }.map {
43 | case (outnode, edges) =>
44 | (outnode -> innodesAndEdges(edges))
45 | })
46 | }
47 |
48 | def mapoutFrom[E](s: Traversable[(Int, Int, E)]): IntMap[IntMap[E]] = {
49 | IntMap.empty[IntMap[E]] ++ (s.groupBy { _._1 }.map {
50 | case (innode, edges) =>
51 | (innode -> outnodesAndEdges(edges))
52 | })
53 | }
54 |
55 | def innodesAndEdges[E](s: Traversable[(Int, Int, E)]): IntMap[E] = IntMap.empty[E] ++ (s map { e => e._1 -> e._3 })
56 | def outnodesAndEdges[E](s: Traversable[(Int, Int, E)]): IntMap[E] = IntMap.empty[E] ++ (s map { e => e._2 -> e._3 })
57 |
58 | }
--------------------------------------------------------------------------------
/mgo/src/main/scala/mgo/tools/network/UndirectedEdges.scala:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2015 Guillaume Chérel
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU Affero General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | */
17 |
18 | package mgo.tools.network
19 |
20 | trait UndirectedEdges[E] {
21 | def edges(u: Int): Vector[(Int, Int, E)] = out(u) map { case (v, d) => (u, v, d) }
22 | def neighbours(u: Int): Vector[Int] = out(u) map { _._1 }
23 |
24 | val dotGraphType: String = "graph"
25 | val dotEdgeOperator: String = "--"
26 |
27 | def out(u: Int): Vector[(Int, E)]
28 | }
29 |
30 | object UndirectedEdges {
31 | /** Takes a sequence of edges and returns a sequence by adding a (n2,n1,e) for all (n1,n2,e) */
32 | def makeSymetric[E](s: Seq[(Int, Int, E)]): Vector[(Int, Int, E)] = (s.toSet ++ s.map { case (n1, n2, e) => (n2, n1, e) }).toVector
33 | }
34 |
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 |
2 | sbt.version=1.11.0
3 |
4 |
--------------------------------------------------------------------------------
/project/plugin.sbt:
--------------------------------------------------------------------------------
1 | addSbtPlugin("com.github.sbt" % "sbt-release" % "1.4.0")
2 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")
3 |
4 |
--------------------------------------------------------------------------------
/version.sbt:
--------------------------------------------------------------------------------
1 | ThisBuild / version := "3.66-SNAPSHOT"
2 |
--------------------------------------------------------------------------------