Commit 48ef46d2 authored by Romain Reuillon's avatar Romain Reuillon
Browse files

[Plugin] feat: first implementation of ABC

parent 15cc085d
......@@ -223,7 +223,7 @@ lazy val squants =
) settings(settings: _*)
lazy val mgoVersion = "3.25"
lazy val mgoVersion = "3.26-SNAPSHOT"
lazy val mgo = OsgiProject(dir, "mgo", exports = Seq("mgo.*", "freestyle.*"), imports = Seq("!better.*", "!javax.xml.*", "!scala.meta.*", "!sun.misc.*", "*"), privatePackages = Seq("!scala.*", "!monocle.*", "!org.apache.commons.math3.*", "!cats.*", "!squants.*", "!scalaz.*", "*")) settings(
libraryDependencies += "fr.iscpif" %% "mgo" % mgoVersion,
......
......@@ -452,14 +452,14 @@ lazy val modifierHook = OsgiProject(pluginDir, "org.openmole.plugin.hook.modifie
/* Method */
def allMethod = Seq(evolution, directSampling, sensitivity)
def allMethod = Seq(evolution, directSampling, sensitivity, abc)
lazy val evolution = OsgiProject(pluginDir, "org.openmole.plugin.method.evolution", imports = Seq("*")) dependsOn(
openmoleDSL, csvTool, toolsTask, pattern, collectionDomain % "test", boundsDomain % "test"
) settings(libraryDependencies += Libraries.mgo, libraryDependencies += Libraries.shapeless) settings (pluginSettings: _*)
//lazy val abc = OsgiProject(pluginDir, "org.openmole.plugin.method.abc", imports = Seq("*")) dependsOn(openmoleDSL, fileHook, tools) settings
// (libraryDependencies += Libraries.scalabc) settings (pluginSettings: _*)
lazy val abc = OsgiProject(pluginDir, "org.openmole.plugin.method.abc", imports = Seq("*")) dependsOn(openmoleDSL, csvTool, toolsTask, pattern) settings
(libraryDependencies += Libraries.mgo, libraryDependencies += Libraries.shapeless) settings (pluginSettings: _*)
lazy val directSampling = OsgiProject(pluginDir, "org.openmole.plugin.method.directsampling", imports = Seq("*")) dependsOn(openmoleDSL, distributionDomain, pattern, modifierDomain) settings (pluginSettings: _*)
......
......@@ -504,10 +504,11 @@ package composition {
def and(t2: DSL) = new &(t1, t2)
def outputs = {
implicit def scope = DefinitionScope.Internal("outptus")
def outputs: Seq[Val[_]] = outputs(false)
def outputs(explore: Boolean): Seq[Val[_]] = {
implicit def scope = DefinitionScope.Internal("outputs")
val last = EmptyTask()
val p: Puzzle = dslToPuzzle(t1 -- last)
val p: Puzzle = if (!explore) dslToPuzzle(t1 -- last) else dslToPuzzle(t1 -< last)
val mole = p.toMole
val slot = p.slots.toSeq.find(_.capsule.task == last).head
TypeUtil.receivedTypes(mole, p.sources, p.hooks)(slot) toSeq
......
/*
* Copyright (C) 15/01/14 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import org.openmole.core.context.Val
trait ABC {
def targetPrototypes: Seq[Val[Double]]
def priorPrototypes: Seq[Val[Double]]
}
/*
* Copyright (C) 2015 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import org.openmole.core.pluginmanager._
......@@ -22,6 +5,7 @@ import org.openmole.core.preference.ConfigurationInfo
import org.osgi.framework.BundleContext
class Activator extends PluginInfoActivator {
override def stop(context: BundleContext): Unit = {
PluginInfo.unregister(this)
ConfigurationInfo.unregister(this)
......@@ -32,10 +16,7 @@ class Activator extends PluginInfoActivator {
val keyWords: Vector[KeyWord] =
Vector(
Hook(objectName[SaveABCHook]),
Task(objectName[LenormandAnalyseTask]),
Sampling(objectName[LenormandSampling]),
Pattern(objectName[Lenormand])
Pattern("ABC")
)
PluginInfo.register(this, Vector(this.getClass.getPackage), keyWords = keyWords)
......@@ -44,4 +25,4 @@ class Activator extends PluginInfoActivator {
ConfigurationInfo.list()
)
}
}
}
\ No newline at end of file
/*
* Copyright (C) 2014 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import fr.iscpif.scalabc._
import org.openmole.core.context.Val
object Lenormand {
def apply(
priors: Seq[((Val[Double], (Double, Double)))],
targets: Seq[(Val[Double], Double)],
simulations: Int,
minimumProportionOfAccepted: Double = 0.05,
alpha: Double = 0.5
) = {
val (_priors, _simulations, _alpha, _targets, _minimumProportionOfAccepted) = (priors, simulations, alpha, targets, minimumProportionOfAccepted)
new algorithm.Lenormand with sampling.JabotMover with distance.DefaultDistance with ABC {
val targetPrototypes = _targets.unzip._1
val priorPrototypes = _priors.unzip._1
override val minimumProportionOfAccepted = _minimumProportionOfAccepted
override val alpha = _alpha
val summaryStatsTarget = targets.unzip._2
val simulations = _simulations
val priors = _priors.unzip._2.map { case (min, max) prior.Uniform(min, max) }
}
}
}
/*
* Copyright (C) 15/01/14 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import fr.iscpif.scalabc._
import org.openmole.core.context.{ Context, Val, Variable }
import org.openmole.core.workflow.dsl._
import org.openmole.core.workflow.task._
object LenormandAnalyseTask {
def apply(
lenormand: algorithm.Lenormand with ABC,
state: Val[algorithm.Lenormand#STATE],
terminated: Val[Boolean],
iteration: Val[Int],
accepted: Val[Double]
) =
ClosureTask("LenormandAnalyseTask") { (context, _, _)
val thetasValue: Seq[Seq[Double]] = lenormand.priorPrototypes.map { p context(p.toArray).toSeq }.transpose
val summaryStatsValue: Seq[Seq[Double]] = lenormand.targetPrototypes.map { p context(p.toArray).toSeq }.transpose
val stateValue: algorithm.Lenormand#STATE = context(state)
val nextState = lenormand.analyse(stateValue, thetasValue, summaryStatsValue)
Context(
Variable(state, nextState),
Variable(terminated, lenormand.finished(nextState)),
Variable(iteration, nextState.iteration),
Variable(accepted, nextState.proportionOfAccepted)
)
} set (
inputs += state,
inputs += (lenormand.priorPrototypes: _*),
outputs += (lenormand.targetPrototypes: _*),
outputs += (state, terminated, iteration, accepted)
)
}
/*
* Copyright (C) 15/01/14 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import fr.iscpif.scalabc.algorithm._
import org.openmole.core.context.{ Val, Variable }
import org.openmole.core.expansion.FromContext
import org.openmole.core.workflow.sampling._
object LenormandSampling {
def apply(
lenormand: Lenormand with ABC,
state: Val[Lenormand#STATE]
) = {
val (_lenormand, _state) = (lenormand, state)
new LenormandSampling {
val lenormand = _lenormand
def state = _state
}
}
}
abstract class LenormandSampling extends Sampling {
val lenormand: Lenormand with ABC
def state: Val[Lenormand#STATE]
def prototypes = lenormand.priorPrototypes
override def inputs = Seq(state)
override def apply() = FromContext.apply { p
import p._
lenormand.sample(context(state))(random()).map {
sampled
(lenormand.priorPrototypes zip sampled).map {
case (v, s) Variable(v, s)
}
}.toIterator
}
}
package org.openmole.plugin.method.abc
import mgo.abc.MonAPMC
import org.openmole.core.context.Variable
import org.openmole.core.dsl.{ OptionalArgument, _ }
import org.openmole.core.workflow.builder.DefinitionScope
import org.openmole.core.workflow.task.FromContextTask
object PostStepTask {
def apply(
n: Int,
nAlpha: Int,
prior: Seq[ABCPrior],
observed: Seq[ABCObserved],
state: Val[MonAPMC.MonState],
stepState: Val[MonAPMC.StepState],
minAcceptedRatio: Double,
termination: OptionalArgument[Int],
stop: Val[Boolean],
step: Val[Int])(implicit name: sourcecode.Name, definitionScope: DefinitionScope) =
FromContextTask("postStepTask") { p
import p._
val priorBounds = prior.map(pr (pr.low.from(context), pr.high.from(context)))
val volume = priorBounds.map { case (min, max) math.abs(max - min) }.reduceLeft(_ * _)
def density(point: Array[Double]) = {
val inside = (priorBounds zip point).forall { case ((min, max), p) p >= min && p <= max }
if (inside) 1.0 / volume else 0.0
}
val xs = observed.toArray.map(o context(o.v.array)).transpose
val s = MonAPMC.postStep(n, nAlpha, density, observed.map(_.observed).toArray, context(stepState), xs)(random())
val stopValue = MonAPMC.stop(minAcceptedRatio, s) || termination.option.map(_ <= context(step)).getOrElse(false)
context + Variable(state, s) + Variable(stop, stopValue) + Variable(step, context(step) + 1)
} set (
inputs += stepState,
inputs += (observed.map(_.v.array): _*),
outputs += (state, stop),
(inputs, outputs) += step
)
}
package org.openmole.plugin.method.abc
import org.openmole.core.workflow.task.FromContextTask
import org.openmole.core.dsl._
import mgo.abc._
import org.openmole.core.context.Variable
import org.openmole.core.expansion.FromContext
import org.openmole.core.tools.math._
import org.openmole.core.workflow.builder.DefinitionScope
import scala.util.Random
object PreStepTask {
def apply(n: Int, nAlpha: Int, prior: Seq[ABCPrior], state: Val[MonAPMC.MonState], stepState: Val[MonAPMC.StepState], step: Val[Int])(implicit name: sourcecode.Name, definitionScope: DefinitionScope) =
FromContextTask("preStepTask") { p
import p._
val priorBounds = prior.map(pr (pr.low.from(context), pr.high.from(context)))
def priorSampler(bound: (Double, Double))(rng: util.Random): Double = rng.nextDouble.scale(bound._1, bound._2)
val priorSamplers = (rng: Random) priorBounds.toArray.map(b priorSampler(b)(rng))
val s = context(state)
val (ns, matrix: Array[Array[Double]]) = MonAPMC.preStep(n, nAlpha, priorSamplers, s)(random())
val samples = (prior.map(_.v) zip matrix.toVector.transpose).map { case (v, samples) Variable(v.array, samples.toArray) }
context ++ samples + Variable(stepState, ns)
} set (
(inputs, outputs) += step,
inputs += state,
exploredOutputs ++= prior.map(_.v.array),
outputs += stepState,
state := MonAPMC.Empty(),
step := 0
)
}
/*
* Copyright (C) 2014 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method.abc
import org.openmole.core.expansion.{ ExpandedString, FromContext }
import org.openmole.core.workflow.dsl._
import org.openmole.plugin.hook.file._
object SaveABCHook {
def apply(puzzle: ABCPuzzle, dir: FromContext[File]) = {
val fileName = dir / ExpandedString("abc${" + puzzle.iteration.name + "}.csv")
val prototypes = Seq(puzzle.iteration) ++ puzzle.algorithm.priorPrototypes.map(_.toArray) ++ puzzle.algorithm.targetPrototypes.map(_.toArray)
AppendToCSVFileHook(fileName, prototypes: _*)
}
}
/*
* Copyright (C) 16/01/14 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.openmole.plugin.method
import fr.iscpif.scalabc.algorithm.Lenormand
import org.openmole.core.context.Val
import org.openmole.core.expansion.{ Condition, FromContext }
import org.openmole.core.workflow.dsl._
import org.openmole.core.workflow.mole._
import org.openmole.core.workflow.puzzle._
import org.openmole.core.workflow.task._
import org.openmole.core.workflow.transition._
import org.openmole.core.context.{ Context, Namespace }
import org.openmole.core.dsl._
import org.openmole.core.expansion._
import org.openmole.plugin.tool.pattern._
import mgo.abc._
import org.openmole.core.workflow.builder.DefinitionScope
import org.openmole.core.expansion.Condition
package object abc {
trait ABCPuzzle {
def iteration: Val[Int]
def algorithm: ABC
}
def abc(
algorithm: Lenormand with ABC,
model: Puzzle
) = {
val methodName = "abc"
val acceptedPrototype = Val[Double](methodName + "Accepted")
val iterationPrototype = Val[Int](methodName + "Iteration")
val statePrototype = Val[Lenormand#STATE](methodName + "State")
val terminatedPrototype = Val[Boolean](methodName + "Terminated")
val preModel = StrainerCapsule(EmptyTask() set (name := methodName + "PreModel"))
val postModel = Slot(StrainerCapsule(EmptyTask() set (name := methodName + "PostModel")))
val last = StrainerCapsule(EmptyTask() set (name := methodName + "Last"))
val sampling = LenormandSampling(algorithm, statePrototype)
val explorationTask = ExplorationTask(sampling) set (
name := methodName + "Exploration",
statePrototype := FromContext(_ algorithm.initialState),
outputs += statePrototype
)
val exploration = StrainerCapsule(explorationTask)
val analyseTask =
LenormandAnalyseTask(
algorithm,
statePrototype,
terminatedPrototype,
iterationPrototype,
acceptedPrototype
val abcNamespace = Namespace("abc")
case class ABCPrior(v: Val[Double], low: FromContext[Double], high: FromContext[Double])
case class ABCObserved(v: Val[Double], observed: Double)
def ABC(
evaluation: DSL,
prior: Seq[ABCPrior],
observed: Seq[ABCObserved],
sample: Int,
generated: Int,
minAcceptedRatio: Double = 0.01,
termination: OptionalArgument[Int] = None,
scope: DefinitionScope = "abc") = {
implicit def defScope = scope
val state = Val[MonAPMC.MonState]("state", abcNamespace)
val stepState = Val[MonAPMC.StepState]("stepState", abcNamespace)
val step = Val[Int]("step", abcNamespace)
val stop = Val[Boolean]
val n = sample + generated
val nAlpha = sample
val preStepTask = PreStepTask(n, nAlpha, prior, state, stepState, step)
val postStepTask = PostStepTask(n, nAlpha, prior, observed, state, stepState, minAcceptedRatio, termination, stop, step)
val mapReduce =
MapReduce(
sampler = preStepTask,
evaluation = evaluation,
aggregation = postStepTask,
scope = scope
)
val analyse = Slot(StrainerCapsule(analyseTask))
val terminated: Condition = terminatedPrototype
val modelVariables = algorithm.priorPrototypes ++ algorithm.targetPrototypes
val puzzle =
(exploration -< (preModel filter Block(statePrototype)) -- model -- postModel >- analyse -- (last when terminated)) &
(exploration -- (analyse filter Block(modelVariables: _*))) &
(preModel -- postModel) &
(exploration oo (model.firstSlot, filter = Block(modelVariables: _*))) &
(analyse -- (exploration when !terminated filter Block(modelVariables: _*)))
val _algorithm = algorithm
val loop =
While(
evaluation = mapReduce,
condition = !(stop: Condition)
)
new Puzzle(puzzle) with ABCPuzzle {
val output = analyse
val state = statePrototype
val accepted = acceptedPrototype
val iteration = iterationPrototype
val algorithm = _algorithm
}
DSLContainer(loop, output = Some(postStepTask), delegate = mapReduce.delegate)
}
}
package org.openmole.plugin.method.abc
import org.scalatest.{FlatSpec, Matchers}
import org.openmole.core.dsl._
import org.openmole.core.workflow.test.TestTask
class ABCSpec extends FlatSpec with Matchers {
import org.openmole.core.workflow.test.Stubs._
"abc" should "run" in {