Commit 0124c972 authored by Mathieu Leclaire's avatar Mathieu Leclaire
Browse files

Add Matthias vizualization work

parent 3f216f85
......@@ -11,7 +11,7 @@ val laminarVersion = "0.12.2"
val scaladgetVersion = "1.9.0"
val scalajsDomVersion = "1.1.0"
val scalatagsVersion = "0.9.4"
val plotlyVersion = "1.5.6"
val plotlyVersion = "1.5.8-SNAPSHOT"
val openmoleVersion = "14.0-SNAPSHOT"
lazy val shared = project.in(file("shared")) settings(
......
......@@ -22,21 +22,27 @@ object App {
val method = Var("")
val experiment = Var("")
val convergencePlot = Var(div())
val paretoPlot = Var(div())
lazy val d = div(
h3(child.text <-- experiment.signal),
h4(child.text <-- method.signal.map{m=> s"with the method $m"}),
child <-- convergencePlot.signal
div(child <-- convergencePlot.signal),
div(child <-- paretoPlot.signal)
)
Post[shared.Api].convergence().call().foreach {c=>
convergencePlot.set(NSGA2.convergence(c))
convergencePlot.set(Plot.convergence(c))
}
Post[shared.Api].metadata().call().foreach {m=>
setMetadata(m.OMRData)
}
Post[shared.Api].lastGeneration().call().foreach{lg=>
lg.map{gen=> paretoPlot.set(Plot.pareto(gen))}.getOrElse(div("Nothing to be ploted"))
}
def setMetadata(omrData: OMROutputFormat.OMRData) = {
method.set(omrData.method)
experiment.set(s"${omrData.fileName} - ${omrData.version}")
......
......@@ -4,12 +4,16 @@ import org.openmole.plotlyjs._
import org.openmole.plotlyjs.all._
import scala.scalajs.js.JSConverters.{iterableOnceConvertible2JSRichIterableOnce, _}
import org.openmole.plugin.method.evolution.data.AnalysisData
import org.openmole.plotlyjs.PlotlyImplicits._
import com.raquo.laminar.api.L._
import plotlyjs.demo.homemade.api.Data.{Input, Outcome, Output}
import plotlyjs.demo.homemade.api.Pareto.{Minimization, ParetoDisplay, ParetoObjective}
import plotlyjs.demo.homemade.pareto.Pareto
import scala.scalajs._
object NSGA2 {
object Plot {
def convergence(convergence: org.openmole.plugin.method.evolution.data.AnalysisData.Convergence) = {
val plotDiv = div()
......@@ -23,8 +27,8 @@ object NSGA2 {
val data = linechart.lines
val (generations, hypervolume) = convergence match {
case st: org.openmole.plugin.method.evolution.data.AnalysisData.StochasticNSGA2.Convergence => st.generations.map{gs => (gs.generation.toString, gs.hypervolume.getOrElse(0.0))}.unzip
case n: org.openmole.plugin.method.evolution.data.AnalysisData.NSGA2.Convergence=> n.generations.map{gs => (gs.generation.toString, gs.hypervolume.getOrElse(0.0))}.unzip
case st: AnalysisData.StochasticNSGA2.Convergence => st.generations.map{gs => (gs.generation.toString, gs.hypervolume.getOrElse(0.0))}.unzip
case n: AnalysisData.NSGA2.Convergence=> n.generations.map{gs => (gs.generation.toString, gs.hypervolume.getOrElse(0.0))}.unzip
}
val dataRef = data
......@@ -41,4 +45,32 @@ object NSGA2 {
plotDiv
}
def pareto(paretoGeneration: AnalysisData.Generation) = {
val (objective, generation, genome) = paretoGeneration match {
case st: AnalysisData.StochasticNSGA2.Generation=> (st.objective, st.generation, st.genome)
case n: AnalysisData.NSGA2.Generation=> (n.objective, n.generation, n.genome)
}
println("OBJ")
objective.foreach{println}
// println("GENOME")
// genome.foreach{println}
// println("PARETO " + objective.head)
objective match {
case sto: Vector[AnalysisData.StochasticNSGA2.Objective]=>
val outcomes = genome.transpose.zip(sto.map{_.objectives})
println("STOCHASTIC ... " + sto)
Pareto.plot(
Seq("o1","o2","o3").map{o=> ParetoObjective(o, Minimization)},
outcomes.map{case(i,o)=>
println("I size " + i.size + " // " + i)
println("O size " + o.size + " // " + o)
Outcome(i.map{ii=> Input("??",ii.toDouble)}, o.map{oo=> Output("???",oo.toDouble)})},
ParetoDisplay(800, showPath = true)
)
case n: AnalysisData.NSGA2.Objective=> div("stochastic")
}
}
}
\ No newline at end of file
package plotlyjs.demo.homemade.api
object Data {
case class Input(name: String, value: Double)
case class Output(name: String, value: Double)
case class Outcome(inputs: Seq[Input], outputs: Seq[Output], samples: Option[Int] = None)
}
package plotlyjs.demo.homemade.api
import com.raquo.laminar.nodes.ReactiveHtmlElement
import org.scalajs.dom.html.Div
import plotlyjs.demo.homemade.api.Data.Outcome
import plotlyjs.demo.homemade.pse.PSE.plotAPI
object PSE {
case class PSEDimension(name: String, bounds: Seq[Double])
case class PSEDisplay(size: Int)
def pse(dimensions: Seq[PSEDimension], outcomes: Seq[Outcome], pseDisplay: PSEDisplay): ReactiveHtmlElement[Div] = plotAPI(dimensions, outcomes, pseDisplay)
}
package plotlyjs.demo.homemade.api
import com.raquo.laminar.nodes.ReactiveHtmlElement
import org.scalajs.dom.html.Div
import plotlyjs.demo.homemade.api.Data.Outcome
import plotlyjs.demo.homemade.pareto.Pareto.plotAPI
object Pareto {
trait OptimizationType
object Maximization extends OptimizationType
object Minimization extends OptimizationType
case class ParetoObjective(name: String, optimizationType: OptimizationType)
case class ParetoDisplay(size: Int, showPath: Boolean = false)
def pareto(objectives: Seq[ParetoObjective], outcomes: Seq[Outcome], paretoDisplay: ParetoDisplay): ReactiveHtmlElement[Div] = plotAPI(objectives, outcomes, paretoDisplay)
}
This diff is collapsed.
package plotlyjs.demo.homemade.pareto
import plotlyjs.demo.homemade.api.Pareto.{Maximization, Minimization, OptimizationType}
import plotlyjs.demo.homemade.pareto.PointPlotter._
import plotlyjs.demo.homemade.utils.Vectors._
case class PointPlotter(optimizationTypes: Seq[OptimizationType], outputs: Seq[Vector], betterPlot: BetterPlot) {
val plotOutputs: Seq[Vector] = {
val orientations = optimizationTypes.map((_, betterPlot)).map {
case (Minimization, BetterPlot.IsLower) | (Maximization, BetterPlot.IsHigher) => 1.0
case (Minimization, BetterPlot.IsHigher) | (Maximization, BetterPlot.IsLower) => -1.0
}
val orientedOutputs = outputs.map(_.mul(orientations))
val min = orientedOutputs.transpose.map(_.min)
val max = orientedOutputs.transpose.map(_.max)
orientedOutputs.map(_.zipWithIndex.map { case (c, i) => (c - min(i))/(max(i) - min(i)) })
}
}
object PointPlotter {
class BetterPlot
object BetterPlot {
object IsLower extends BetterPlot //TODO delete
object IsHigher extends BetterPlot
}
}
package plotlyjs.demo.homemade.pareto
import plotlyjs.demo.homemade.pareto.SnowflakeBasis.cartesianFromPolar
import plotlyjs.demo.homemade.utils.Basis
import plotlyjs.demo.homemade.utils.Vectors._
import scala.math.{atan2, cos, sin}
class SnowflakeBasis(dimension: Int) extends Basis {
override val size: Int = dimension
override def basisVector(i: Int): Vector = {
if (dimension == 2) {
i match {
case 0 => cartesianFromPolar(Seq(1, 0))
case 1 => cartesianFromPolar(Seq(1, 90))
}
} else {
cartesianFromPolar(Seq(1, 360 * i / dimension))
}
}
}
object SnowflakeBasis {
class PolarVector(vector: Vector) extends Vector {
override def apply(i: Int): Double = vector.apply(i)
override def length: Int = vector.length
override def iterator: Iterator[Double] = vector.iterator
val radius: Double = vector(0)
val angle: Double = vector(1)
}
def polarFromCartesian(vector: Vector): PolarVector = {
val radius = vector.norm
val x = vector(0)
val y = vector(1)
val angle = atan2(y, x).toDegrees
new PolarVector(Seq(radius, angle))
}
class CartesianVector(vector: Vector) extends Vector {
override def apply(i: Int): Double = vector.apply(i)
override def length: Int = vector.length
override def iterator: Iterator[Double] = vector.iterator
val x: Double = vector(0)
val y: Double = vector(1)
}
def cartesianFromPolar(vector: Vector): CartesianVector = {
val radius = vector(0)
val theta = vector(1).toRadians
val x = radius * cos(theta)
val y = radius * sin(theta)
new CartesianVector(Seq(x, y))
}
}
package plotlyjs.demo.homemade.pse
import plotlyjs.demo.homemade.utils.Basis
import plotlyjs.demo.homemade.utils.Vectors._
case class MultiScaleBasis(sourceDimension: Int, subdivisions: Seq[Int], destinationDimension: Int, allowStretch: Boolean = false) extends Basis {
val remainder: Int = sourceDimension % destinationDimension
val stretchable: Boolean = remainder != 0
val stretched: Boolean = allowStretch && stretchable
def axisIndex(i: Int): Int = {
i % destinationDimension
}
def scaleIndex(i: Int): Int = {
i / destinationDimension
}
def axis(i: Int): Int = {
axisIndex(i)
}
def scale(i: Int): Double = {
var iSubScale = i
var scaleFactor = 1
var valid = true
while(valid) {
iSubScale -= destinationDimension
if(iSubScale >=0) {
scaleFactor *= (subdivisions(iSubScale) + 1)
} else {
valid = false
}
}
scaleFactor
}
val maxScaleIndex: Int = scaleIndex(sourceDimension - 1)
override val size: Int = sourceDimension
override def basisVector(i: Int): Vector = {
(0.0 at destinationDimension).replace(axis(i), 1.0) * scale(i)
}
override def transform(vector: Vector): Vector = {
if ((destinationDimension until sourceDimension).map(vector(_).isWhole).reduceOption(_ && _).getOrElse(true)) {
super.transform(vector)
} else {
throw new IllegalArgumentException(s"Coordinates from index $destinationDimension until $sourceDimension must be whole.")
}
}
def totalSize(i: Int): Double = {
val maxParallel = sourceDimension - 1 - ((sourceDimension - 1 - i) % destinationDimension)
(basisVector(maxParallel) * subdivisions(i)).norm
}
def size(destinationAxis: Int): Double = {
for (i <- 0 until sourceDimension) {
if (axis(i) == destinationAxis) {
return totalSize(i)
}
}
-1
}
}
This diff is collapsed.
package plotlyjs.demo.homemade.utils
import plotlyjs.demo.homemade.utils.Vectors._
trait Basis {
val size: Int
def basisVector(i: Int): Vector
def component(vector: Vector, i: Int): Vector = {
vector(i) * basisVector(i)
}
def transform(vector: Vector): Vector = {
(0 until size).map(component(vector, _)).reduce(_ + _)
}
}
package plotlyjs.demo.homemade.utils
import plotlyjs.demo.homemade.utils.Vectors._
import scala.language.implicitConversions
import scala.math._
object IntVectors {
type IntVector = Seq[Int]
implicit class ImplicitIntVector(intVector: IntVector) {
def vector: Vector = intVector.map(_.toDouble)
def intVectorToString: String = "(" + intVector.mkString(", ") + ")"
}
implicit def implicitToVector(i: IntVector): Vector = i.map(_.toDouble)
implicit def toIntVector(v: Vector): IntVector = v.map(rint(_).toInt)
def vectorIndices(sizes: Seq[Int]): Iterable[IntVector] = {
if(sizes.isEmpty) {
new Iterable[IntVector] {
override def iterator: Iterator[IntVector] = new Iterator[IntVector] {
override def hasNext: Boolean = false
override def next(): IntVector = ???
}
}
} else {
new Iterable[IntVector] {
override def iterator: Iterator[IntVector] = new Iterator[IntVector] {
private val dimension = sizes.size
private val totalCount = sizes.product
private val pointGenerator = sizes.map(_ - 1).toArray//Array.fill[Int](dimension)(_size - 1)
private var count = 0
override def hasNext: Boolean = count < totalCount
override def next(): IntVector = {
pointGenerator(0) += 1
for(i <- 0 until dimension if pointGenerator(i) == sizes(i)) {
pointGenerator(i) = 0
if(i + 1 < dimension) pointGenerator(i + 1) += 1
}
count = count + 1
pointGenerator
}
}
}.view
}
}
def positiveNCube(dimension: Int, p: Int): Iterable[IntVector] = {
if(dimension == 0) {
new Iterable[IntVector] {
override def iterator: Iterator[IntVector] = new Iterator[IntVector] {
override def hasNext: Boolean = false
override def next(): IntVector = ???
}
}
} else {
new Iterable[IntVector] {
override def iterator: Iterator[IntVector] = new Iterator[IntVector] {
private val pointGenerator = Array.fill[Int](dimension)(p - 1)
private var count = 0
override def hasNext: Boolean = count < pow(p, dimension)
override def next(): IntVector = {
pointGenerator(0) += 1
for (i <- 0 until dimension if pointGenerator(i) == p) {
pointGenerator(i) = 0
if (i + 1 < dimension) pointGenerator(i + 1) += 1
}
count = count + 1
pointGenerator.toSeq
}
}
}.view
}
}
def centeredNCube(dimension: Int, radius: Int): Iterable[IntVector] = {
positiveNCube(dimension, 2 * radius + 1).map(_.vector.sub(radius.toDouble))
}
}
package plotlyjs.demo.homemade.utils
import plotlyjs.demo.homemade.utils.Vectors._
object ParetoFrontGenerator {
def random(dimension: Int, size: Int): Seq[Vector] = {
var front = Seq[Vector]()
def compareToFront(v: Vector): Double = ParetoFrontGenerator.compareToFront(front, v)
def stepTowardFront(s: Double, v: Vector): Vector = {
v + front.flatMap(_.zip(v).map({ case (_c, c) => _c - c })).filter(math.signum(_) == s).minBy(math.abs)
}
def frontBounds(v: Vector): (Vector, Vector) = {
val comparison = compareToFront(v)
if (comparison == 0) {
(stepTowardFront(-1, v), stepTowardFront(+1, v))
} else {
val direction = -comparison
var vPrevious = v
var vNext = v
while (compareToFront((vPrevious + vNext) / 2) != 0) {
vPrevious = vNext
vNext = stepTowardFront(direction, vPrevious)
}
if (direction > 0) (vPrevious, vNext) else (vNext, vPrevious)
}
}
front = front ++ (0 until dimension).map((0 at dimension).replace(_, 1))
for (_ <- 1 to size) {
val (v1, v2) = frontBounds((() => math.random()) at dimension)
val epsilon = 10E-4
val alpha = epsilon + math.random() * (1 - epsilon)
val v = (1 - alpha) * v1 + alpha * v2
front = front :+ v
}
front = front.drop(dimension)
assert(front.map(compareToFront(_) == 0).reduce(_ && _))
front
}
def compare(v1: Vector, v2: Vector): Double = {
(v1 - v2).map(math.signum).filterNot(_ == 0).reduceOption((s1, s2) => if (s1 == s2) s1 else 0).getOrElse(0)
}
def compareToFront(front: Seq[Vector], v: Vector): Double = {
front.map(compare(v, _)).filterNot(_ == 0).headOption.getOrElse(0)
}
}
package plotlyjs.demo.homemade.utils
import com.raquo.laminar.nodes.ReactiveHtmlElement
import org.openmole.plotlyjs.{PlotData, Plotly}
import org.scalajs.dom.html
import plotlyjs.demo.homemade.utils.Utils.ExtraTraceManager.ExtraTracesRef
import plotlyjs.demo.homemade.utils.Vectors._
import scala.math.{ceil, random}
import scala.scalajs.js.JSConverters._
object Utils {
def printCode[T](sc: sourcecode.Text[T])(implicit line: sourcecode.Line, file: sourcecode.File): T = {
println(file.value + ":" + line.value + " " + sc.source + " = " + sc.value)
sc.value
}
def randomizeDimensions(seq: Seq[Vector]): Seq[Vector] = {
seq.headOption.map(head => {
val dimension = head.dimension
val mulVector = (() => ceil(10 * random)) at dimension
val addVector = (() => 10 * random - 5) at dimension
seq
.map(_.mul(mulVector))
.map(_.add(addVector))
}).getOrElse(Seq[Vector]())
}
class SkipOnBusy {
private var busy = false
def skipOnBusy(name: String, f: () => Unit): Unit = {
if(!busy) {
busy = true
//println(name + "...")
f()
//println(name + ".")
busy = false
} else {
//println(name + " skipped")
}
}
}
class ExtraTraceManager(plotDiv: ReactiveHtmlElement[html.Div], initialTraceCount: Int) {
private var extraSize = 0
private var refs = Seq[ExtraTracesRef]()
def addTraces(plotDataSeq: Seq[PlotData]): ExtraTracesRef = {
refs = refs :+ new ExtraTracesRef(extraSize, extraSize + plotDataSeq.size)
Plotly.addTraces(plotDiv.ref, plotDataSeq.map(Option(_).orUndefined).toJSArray)
extraSize = refs.last.to
refs.last
}
def deleteTraces(ref: ExtraTracesRef): Unit = {
val size = ref.size
Plotly.deleteTraces(
plotDiv.ref,
(ref.from until ref.to)
.map(_ + initialTraceCount)
.map(_.toDouble)
.toJSArray
)
extraSize -= size
refs = refs.filterNot(_ == ref)
refs
.filter(ref.to <= _.from)
.foreach { nextRef =>
nextRef.from -= size
nextRef.to -= size
}
}
def deleteAllTraces(): Unit = {
Plotly.deleteTraces(