├── .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 | --------------------------------------------------------------------------------