From 112683ea424090a2662ec852154016f5fadb3daf Mon Sep 17 00:00:00 2001 From: Pascal Date: Wed, 29 Jan 2020 02:31:50 -0600 Subject: [PATCH] [add][mod] added output files and modified config - added OutputFile class to library - [mod] use file from arg as config if exists - [mod] added readme text --- README.md | 4 +- docs/wiki/config/config_preview.yml | 83 ++- src/main/scala/scim/Main.scala | 7 +- .../scala/scim/components/Behaviour.scala | 428 +++++------ src/main/scala/scim/components/Output.scala | 29 +- src/main/scala/scim/components/Parser.scala | 694 +++++++++--------- .../scala/scim/datastruct/Configuration.scala | 2 +- src/main/scala/scim/lib/Library.scala | 26 + 8 files changed, 667 insertions(+), 606 deletions(-) diff --git a/README.md b/README.md index d30017e..5a8dc83 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# untitled +# Sensor Simulation + +Sensor simulation that focuses on generating high volumes of sensor output. The output can be used to simulate faulty and correct behaviour, based on a ground truth. To remove the requirement of collecting and compose ground truth values for specific scenarios, the simulation also allows for a ground truth to be automatically generated during the simulation. The simulation is fully controllable and creates reproducible outputs with a given configuration. ## Wiki diff --git a/docs/wiki/config/config_preview.yml b/docs/wiki/config/config_preview.yml index 322484c..a47df63 100644 --- a/docs/wiki/config/config_preview.yml +++ b/docs/wiki/config/config_preview.yml @@ -1,6 +1,6 @@ simulation: duration: 24 - tickfactor: 10 + tickfactor: 1 runs: 1 parCell: 0 parCellThreads: 5 @@ -19,10 +19,7 @@ sensors: - [1, 1, 5] - [2, 2, 5] - [3, 3, 5] - blocks: - - start: [0, 4] - end: [6, 6] - count: 210 + - [4, 4, 5] - group: faultyTemp parent: temperature @@ -36,39 +33,47 @@ sensors: cells: - [3, 3, 5] -# all cells are declared to output the gt -# of baseline for each into the console + # all cells are declared to output the gt + # of baseline for each into the console - group: baseline blocks: - - start: [0, 0] - end: [6, 6] - count: 0 + - [0, 0, 0] + - [1, 1, 0] + - [2, 2, 0] + - [3, 3, 0] + - [4, 4, 0] groundtruths: - group: baseline truth: - - value: 10.0 + - value: 7.0 timeline: - start: 0 - end: 7 - - [18, 24] - - [7, 10] - - [12, 16.0] + end: 2 + - [19, 22] + - [2, 7.0] + - [22, 7.0] delta: - name: fluctuate - args: {range: 0.0, center: [3, 3], distOffset: -0.5, time:[0, 12, 18]} + args: {range: 0.0, center: [3, 3], distOffset: -0.5, time:[0, 19, 22]} timeline: - - [0, 7] - - [12, 16] - - [18, 24] - - [7, change, {range: 0.0, target: 16.0, targetTime: 12, center: [3, 3], distOffset: -0.5, time:[7]}] - - [16, change, {range: 0.0, target: 10.0, targetTime: 18, center: [3, 3], distOffset: -0.5, time:[]}] + - [0, 2] + - [3, 5] + - [8, 9] + - [19, 22] + - [23, 24] + - [2, change, {range: 0.0, target: 6.0, targetTime: 3, center: [3, 3], distOffset: -0.5, time:[2]}] + - [5, change, {range: 0.0, target: 4.0, targetTime: 7, center: [3, 3], distOffset: -0.5, time:[]}] + - [7, change, {range: 0.0, target: 5.0, targetTime: 8, center: [3, 3], distOffset: -0.5, time:[]}] + - [9, change, {range: 0.0, target: 9.0, targetTime: 17, center: [3, 3], distOffset: -0.5, time:[]}] + - [17, change, {range: 0.0, target: 7.0, targetTime: 19, center: [3, 3], distOffset: -0.5, time:[]}] + - [22, change, {range: 0.0, target: 6.0, targetTime: 23, center: [3, 3], distOffset: -0.5, time:[22]}] - group: temperature parent: baseline delta: - name: fluctuate - args: {range: 1.0} + args: {range: 0.3} timeline: - [0, 7] - [12, 16] @@ -97,23 +102,23 @@ behaviours: - group: faultyTemp functions: - - name: measureTemp - args: - range: [-50.0, 150.0] - resolution: 0.1 - accuracy: 1.0 - offset: 10.0 - timeline: - - [0, 12] - - [12, noOutput, {}] - - name: measureTemp - args: - range: [-50.0, 150.0] - resolution: 0.1 - accuracy: 20.0 - offset: 0.0 - timeline: - - [15, 24] + - name: measureTemp + args: + range: [-50.0, 150.0] + resolution: 0.1 + accuracy: 1.0 + offset: 10.0 + timeline: + - [0, 12] + - [12, noOutput, {}] + - name: measureTemp + args: + range: [-50.0, 150.0] + resolution: 0.1 + accuracy: 20.0 + offset: 0.0 + timeline: + - [15, 24] - group: humidity functions: diff --git a/src/main/scala/scim/Main.scala b/src/main/scala/scim/Main.scala index 935662a..bda32ac 100644 --- a/src/main/scala/scim/Main.scala +++ b/src/main/scala/scim/Main.scala @@ -8,11 +8,12 @@ import scim.lib.simInterfaces.SimulationBehaviour object Main { // o7 def main(args: Array[String]): Unit = { - startSimulation() + val file = if(args.length > 0) args(0) else "docs/wiki/config/config_due.yml" + startSimulation(file) } - def startSimulation(): Unit = { - val config = new Parser().parse("docs/wiki/config/config_preview.yml", scim.components.behaviour.behaviours) + def startSimulation(file: String): Unit = { + val config = new Parser().parse(file, scim.components.behaviour.behaviours) val sharedMemory = config.sharedMemory val output = new Output(sharedMemory) val simulator = new Simulation(config) diff --git a/src/main/scala/scim/components/Behaviour.scala b/src/main/scala/scim/components/Behaviour.scala index 0f5adf7..66fbd3c 100644 --- a/src/main/scala/scim/components/Behaviour.scala +++ b/src/main/scala/scim/components/Behaviour.scala @@ -1,214 +1,214 @@ -package scim.components - -import scim.datastruct.{Cell, FunctionArgs, SimulationBehaviours, Time} -import scim.lib.numberUtil._ -import scim.lib.mapUtil._ -import scim.lib.behaviourUtil._ -import scim.lib.simInterfaces.SimulationBehaviour - -import scala.util.Random - -package object behaviour { - - val behaviours = SimulationBehaviours(Map[String, SimulationBehaviour]( - "measureGeneric" -> measureGeneric, - "measureTemp" -> measureTemp, - "measureHumid" -> measureHumid, - "noOutput" -> noOutput, - "randomOutput" -> randomOutput, - "measureTempMod" -> measureTempMod, - "fluctuate" -> fluctuate, - "change" -> change, - "humidFromTemp" -> humidFromTemp - )) - - def measureGeneric: SimulationBehaviour = { - sensorFactory(Vector(0, 1), 0.1, 0.1, 0.0) - } - - def measureTemp: SimulationBehaviour = { - sensorFactory(Vector(-50, 150), 0.1, 1.0, 0.0) - } - - def measureHumid: SimulationBehaviour = { - sensorFactory(Vector(5, 100), 1.0, 2.0, 0.0) - } - - def measureTempMod: SimulationBehaviour = { - sensorModifierFactory("accuracy", Vector(-10, 10), 5, 15.0, 20.0, measureTemp) - } - - def noOutput: SimulationBehaviour = { - (_: Cell, _: Time, _: Option[Double], _: Option[FunctionArgs]) => { None } - } - - def randomOutput: SimulationBehaviour = { - randomOutputFactory(Vector(-50, 50)) - } - - def randomOutputFactory(defaultRange: Vector[Double]) : SimulationBehaviour = { - (_: Cell, _: Time, _: Option[Double], args: Option[FunctionArgs]) => { - val range: Vector[Double] = args.getParam("range", defaultRange) - val rng = new Random() - val start = range(0).intValue() - val end = range(1).intValue() - val rngRange = start + rng.nextInt((end - start) + 1) - Some((rngRange.doubleValue() + rng.nextDouble() * (if (rng.nextBoolean()) -1 else 1)).range(range(0), range(1))) - } - } - - def sensorModifierFactory(defaultMod: String, modRange: Vector[Double], modResolution: Double, modAccuracy: Double, modOffset: Double, - behaviour: SimulationBehaviour): SimulationBehaviour = { - def modifyArgs(args: Option[FunctionArgs]): Option[FunctionArgs] = { - val modifier: Option[Map[String, Any]] = args.getParam("modifier") - if (!modifier.isEmpty) { - val modType = modifier.get.getKeyOrElse[String]("type", defaultMod) - // TODO: use interface to replace args - val newArgs = modType match { - case "range" => args.get.args + ("range" -> modifier.get.getKeyOrElse[Vector[Double]]("value", modRange)) - case "resolution" => args.get.args + ("resolution" -> modifier.get.getKeyOrElse[Double]("value", modResolution)) - case "accuracy" => args.get.args + ("accuracy" -> modifier.get.getKeyOrElse[Double]("value", modAccuracy)) - case "offset" => args.get.args + ("offset" -> modifier.get.getKeyOrElse[Double]("value", modOffset)) - } - Some(FunctionArgs(newArgs)) - } else { - None - } - } - - val modifierWrapper: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { - val modifier: Option[Map[String, Any]] = args.getParam("modifier") - if (!modifier.isEmpty) { - val x = modifier.get.getKey[Int]("x") - val y = modifier.get.getKey[Int]("y") - val timeMod = modifier.get.getKey[Int]("time") - (timeMod, x, y) match { - case (timeFault, None, None) if (!timeFault.isEmpty && (timeFault.get * time.tickfactor.getOrElse(1)) == time.time) => behaviour(cell, time, inTruth, modifyArgs(args)) - case (None, xVal, yVal) if (!xVal.isEmpty && !yVal.isEmpty && Cell(xVal.get, yVal.get) == cell) => behaviour(cell, time, inTruth, modifyArgs(args)) - case (timeFault, xVal, yVal) if (!timeFault.isEmpty && (timeFault.get * time.tickfactor.getOrElse(1)) == time.time && !xVal.isEmpty && !yVal.isEmpty && Cell(xVal.get, yVal.get) == cell) => - behaviour(cell, time, inTruth, modifyArgs(args)) - case _ => behaviour(cell, time, inTruth, args) - } - } else { - behaviour(cell, time, inTruth, args) - } - } - - modifierWrapper - } - - def sensorFactory(defaultRange: Vector[Double], defaultResolution: Double, defaultAccuracy: Double, defaultOffset: Double, - altFunction: Option[SimulationBehaviour] = None, - preFunction: Option[SimulationBehaviour] = None, - postFunction: Option[SimulationBehaviour] = None): SimulationBehaviour = { - - val genericSensor: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { - // can use altFunction to entirely disable normal behaviour (including pre/postFunction) - // useful when trying to use different params based on cell/time - val altResult = if (!altFunction.isEmpty) altFunction.get(cell, time, inTruth, args) else None - if (!altResult.isEmpty) { - altResult - } else { - // preFunction can modify ground truth - // can e.g. be used to return None or modify truth based on cell/time - val preResult = if (!preFunction.isEmpty) preFunction.get(cell, time, inTruth, args) else inTruth - if (preResult.isEmpty) { - None - } else { - val truth = preResult.get - val range: Vector[Double] = args.getParam("range", defaultRange) // using a vector to match yml parsing - val resolution: Double = args.getParam("resolution", defaultResolution) - val accuracy: Double = args.getParam("accuracy", defaultAccuracy) - val offset: Double = args.getParam("offset", defaultOffset) - val rng = new Random() - val rngAcc = rng.nextDouble() * accuracy * (if (rng.nextBoolean()) -1 else 1) - val result: Option[Double] = Some((truth + rngAcc + offset).resolution(resolution).range(range(0), range(1))) - // postFunction can modify result - // can e.g. be used to modify result based on cell, time and result itself - // (e.g. return garbage when result is above threshold to simulate failure above threshold) - if (!postFunction.isEmpty) postFunction.get(cell, time, result, args) else result - } - } - } - - genericSensor - } - - def sensorFactoryBehaviour(default: SimulationBehaviour, - altFunction: Option[SimulationBehaviour] = None, - preFunction: Option[SimulationBehaviour] = None, - postFunction: Option[SimulationBehaviour] = None): SimulationBehaviour = { - - val genericSensor: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { - val altResult = if (!altFunction.isEmpty) altFunction.get(cell, time, inTruth, args) else None - if (!altResult.isEmpty) { - altResult - } else { - val preResult = if (!preFunction.isEmpty) preFunction.get(cell, time, inTruth, args) else inTruth - if (preResult.isEmpty) { - None - } else { - val result = default(cell, time, inTruth, args) - if (!postFunction.isEmpty) postFunction.get(cell, time, result, args) else result - } - } - } - - genericSensor - } - - def fluctuate (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { - if(inTruth.isEmpty) return None - val range: Double = args.getParam("range", 1.0) - val center: Vector[Int] = args.getParam("center", Vector(0, 0)) - val distOffset: Option[Double] = args.getParam[Double]("distOffset") - val offsetTimes: Option[Vector[Int]] = args.getParam[Vector[Int]]("time") - val offset = if(!distOffset.isEmpty && offsetTimes.get.contains(time.getNormalized().getOrElse(-1))) { - val centerCell = Cell(center(0), center(1)) - centerCell.distance(cell) * distOffset.get - } else { 0 } - - val rng = new Random() - val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1) - - Some(inTruth.get + rngRange + offset) - - } - - def change (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { - if(inTruth.isEmpty) return None - val range: Double = args.getParam("range", 1.0) - val target: Double = args.getParam("target", inTruth.get + 5) - val targetTime: Int = args.getParam("targetTime", time.getNormalized + 1) * time.tickfactor.getOrElse(1) + 1 - val center: Vector[Int] = args.getParam("center", Vector(0, 0)) - val distOffset: Option[Double] = args.getParam[Double]("distOffset") - val offsetTimes: Option[Vector[Int]] = args.getParam[Vector[Int]]("time") - // always calculate offset for target - val offset = if(!distOffset.isEmpty) { - val centerCell = Cell(center(0), center(1)) - centerCell.distance(cell) * distOffset.get - } else { 0 } - - // only change truth on offsetTimes - val truth = if(!offsetTimes.isEmpty && offsetTimes.get.contains(time.getNormalized().getOrElse(-1))) inTruth.get + offset else inTruth.get - - val rng = new Random() - val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1) - val step: Double = if(time.time < targetTime) { - (target + offset - truth) / (targetTime - time.time).toDouble - } else { 0 } - - Some(truth + rngRange + step) - } - - def humidFromTemp (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { - if(inTruth.isEmpty) return None - val dewpoint: Double = args.getParam("dewpoint", 12.0) - val temp: Double = inTruth.get - - // source: https://bmcnoldy.rsmas.miami.edu/Humidity.html - val rh = 100*(Math.exp((17.625*dewpoint)/(243.04+dewpoint))/Math.exp((17.625*temp)/(243.04+temp))) - Some(rh) - } - -} +package scim.components + +import scim.datastruct.{Cell, FunctionArgs, SimulationBehaviours, Time} +import scim.lib.numberUtil._ +import scim.lib.mapUtil._ +import scim.lib.behaviourUtil._ +import scim.lib.simInterfaces.SimulationBehaviour + +import scala.util.Random + +package object behaviour { + + val behaviours = SimulationBehaviours(Map[String, SimulationBehaviour]( + "measureGeneric" -> measureGeneric, + "measureTemp" -> measureTemp, + "measureHumid" -> measureHumid, + "noOutput" -> noOutput, + "randomOutput" -> randomOutput, + "measureTempMod" -> measureTempMod, + "fluctuate" -> fluctuate, + "change" -> change, + "humidFromTemp" -> humidFromTemp + )) + + def measureGeneric: SimulationBehaviour = { + sensorFactory(Vector(0, 1), 0.1, 0.1, 0.0) + } + + def measureTemp: SimulationBehaviour = { + sensorFactory(Vector(-50, 150), 0.1, 1.0, 0.0) + } + + def measureHumid: SimulationBehaviour = { + sensorFactory(Vector(5, 100), 1.0, 2.0, 0.0) + } + + def measureTempMod: SimulationBehaviour = { + sensorModifierFactory("accuracy", Vector(-10, 10), 5, 15.0, 20.0, measureTemp) + } + + def noOutput: SimulationBehaviour = { + (_: Cell, _: Time, _: Option[Double], _: Option[FunctionArgs]) => { None } + } + + def randomOutput: SimulationBehaviour = { + randomOutputFactory(Vector(-50, 50)) + } + + def randomOutputFactory(defaultRange: Vector[Double]) : SimulationBehaviour = { + (_: Cell, _: Time, _: Option[Double], args: Option[FunctionArgs]) => { + val range: Vector[Double] = args.getParam("range", defaultRange) + val rng = new Random() + val start = range(0).intValue() + val end = range(1).intValue() + val rngRange = start + rng.nextInt((end - start) + 1) + Some((rngRange.doubleValue() + rng.nextDouble() * (if (rng.nextBoolean()) -1 else 1)).range(range(0), range(1))) + } + } + + def sensorModifierFactory(defaultMod: String, modRange: Vector[Double], modResolution: Double, modAccuracy: Double, modOffset: Double, + behaviour: SimulationBehaviour): SimulationBehaviour = { + def modifyArgs(args: Option[FunctionArgs]): Option[FunctionArgs] = { + val modifier: Option[Map[String, Any]] = args.getParam("modifier") + if (!modifier.isEmpty) { + val modType = modifier.get.getKeyOrElse[String]("type", defaultMod) + // TODO: use interface to replace args + val newArgs = modType match { + case "range" => args.get.args + ("range" -> modifier.get.getKeyOrElse[Vector[Double]]("value", modRange)) + case "resolution" => args.get.args + ("resolution" -> modifier.get.getKeyOrElse[Double]("value", modResolution)) + case "accuracy" => args.get.args + ("accuracy" -> modifier.get.getKeyOrElse[Double]("value", modAccuracy)) + case "offset" => args.get.args + ("offset" -> modifier.get.getKeyOrElse[Double]("value", modOffset)) + } + Some(FunctionArgs(newArgs)) + } else { + None + } + } + + val modifierWrapper: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { + val modifier: Option[Map[String, Any]] = args.getParam("modifier") + if (!modifier.isEmpty) { + val x = modifier.get.getKey[Int]("x") + val y = modifier.get.getKey[Int]("y") + val timeMod = modifier.get.getKey[Int]("time") + (timeMod, x, y) match { + case (timeFault, None, None) if (!timeFault.isEmpty && (timeFault.get * time.tickfactor.getOrElse(1)) == time.time) => behaviour(cell, time, inTruth, modifyArgs(args)) + case (None, xVal, yVal) if (!xVal.isEmpty && !yVal.isEmpty && Cell(xVal.get, yVal.get) == cell) => behaviour(cell, time, inTruth, modifyArgs(args)) + case (timeFault, xVal, yVal) if (!timeFault.isEmpty && (timeFault.get * time.tickfactor.getOrElse(1)) == time.time && !xVal.isEmpty && !yVal.isEmpty && Cell(xVal.get, yVal.get) == cell) => + behaviour(cell, time, inTruth, modifyArgs(args)) + case _ => behaviour(cell, time, inTruth, args) + } + } else { + behaviour(cell, time, inTruth, args) + } + } + + modifierWrapper + } + + def sensorFactory(defaultRange: Vector[Double], defaultResolution: Double, defaultAccuracy: Double, defaultOffset: Double, + altFunction: Option[SimulationBehaviour] = None, + preFunction: Option[SimulationBehaviour] = None, + postFunction: Option[SimulationBehaviour] = None): SimulationBehaviour = { + + val genericSensor: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { + // can use altFunction to entirely disable normal behaviour (including pre/postFunction) + // useful when trying to use different params based on cell/time + val altResult = if (!altFunction.isEmpty) altFunction.get(cell, time, inTruth, args) else None + if (!altResult.isEmpty) { + altResult + } else { + // preFunction can modify ground truth + // can e.g. be used to return None or modify truth based on cell/time + val preResult = if (!preFunction.isEmpty) preFunction.get(cell, time, inTruth, args) else inTruth + if (preResult.isEmpty) { + None + } else { + val truth = preResult.get + val range: Vector[Double] = args.getParam("range", defaultRange) // using a vector to match yml parsing + val resolution: Double = args.getParam("resolution", defaultResolution) + val accuracy: Double = args.getParam("accuracy", defaultAccuracy) + val offset: Double = args.getParam("offset", defaultOffset) + val rng = new Random() + val rngAcc = rng.nextDouble() * accuracy * (if (rng.nextBoolean()) -1 else 1) + val result: Option[Double] = Some((truth + rngAcc + offset).resolution(resolution).range(range(0), range(1))) + // postFunction can modify result + // can e.g. be used to modify result based on cell, time and result itself + // (e.g. return garbage when result is above threshold to simulate failure above threshold) + if (!postFunction.isEmpty) postFunction.get(cell, time, result, args) else result + } + } + } + + genericSensor + } + + def sensorFactoryBehaviour(default: SimulationBehaviour, + altFunction: Option[SimulationBehaviour] = None, + preFunction: Option[SimulationBehaviour] = None, + postFunction: Option[SimulationBehaviour] = None): SimulationBehaviour = { + + val genericSensor: SimulationBehaviour = (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]) => { + val altResult = if (!altFunction.isEmpty) altFunction.get(cell, time, inTruth, args) else None + if (!altResult.isEmpty) { + altResult + } else { + val preResult = if (!preFunction.isEmpty) preFunction.get(cell, time, inTruth, args) else inTruth + if (preResult.isEmpty) { + None + } else { + val result = default(cell, time, inTruth, args) + if (!postFunction.isEmpty) postFunction.get(cell, time, result, args) else result + } + } + } + + genericSensor + } + + def fluctuate (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { + if(inTruth.isEmpty) return None + val range: Double = args.getParam("range", 0.0) + val center: Vector[Int] = args.getParam("center", Vector(0, 0)) + val distOffset: Option[Double] = args.getParam[Double]("distOffset") + val offsetTimes: Option[Vector[Int]] = args.getParam[Vector[Int]]("time") + val offset = if(!distOffset.isEmpty && offsetTimes.get.contains(time.getNormalized().getOrElse(-1))) { + val centerCell = Cell(center(0), center(1)) + centerCell.distance(cell) * distOffset.get + } else { 0 } + + val rng = new Random() + val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1) + + Some(inTruth.get + rngRange + offset) + + } + + def change (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { + if(inTruth.isEmpty) return None + val range: Double = args.getParam("range", 0.0) + val target: Double = args.getParam("target", inTruth.get + 5) + val targetTime: Int = args.getParam("targetTime", time.getNormalized + 1) * time.tickfactor.getOrElse(1) + 1 + val center: Vector[Int] = args.getParam("center", Vector(0, 0)) + val distOffset: Option[Double] = args.getParam[Double]("distOffset") + val offsetTimes: Option[Vector[Int]] = args.getParam[Vector[Int]]("time") + // always calculate offset for target + val offset = if(!distOffset.isEmpty) { + val centerCell = Cell(center(0), center(1)) + centerCell.distance(cell) * distOffset.get + } else { 0 } + + // only change truth on offsetTimes + val truth = if(!offsetTimes.isEmpty && offsetTimes.get.contains(time.getNormalized().getOrElse(-1))) inTruth.get + offset else inTruth.get + + val rng = new Random() + val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1) + val step: Double = if(time.time < targetTime) { + (target + offset - truth) / (targetTime - time.time).toDouble + } else { 0 } + + Some(truth + rngRange + step) + } + + def humidFromTemp (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = { + if(inTruth.isEmpty) return None + val dewpoint: Double = args.getParam("dewpoint", 12.0) + val temp: Double = inTruth.get + + // source: https://bmcnoldy.rsmas.miami.edu/Humidity.html + val rh = 100*(Math.exp((17.625*dewpoint)/(243.04+dewpoint))/Math.exp((17.625*temp)/(243.04+temp))) + Some(rh) + } + +} diff --git a/src/main/scala/scim/components/Output.scala b/src/main/scala/scim/components/Output.scala index f2caf47..b611f51 100644 --- a/src/main/scala/scim/components/Output.scala +++ b/src/main/scala/scim/components/Output.scala @@ -2,16 +2,27 @@ package scim.components import scim.datastruct.{Cell, Group, SensorResult, SharedMemory, Time, Truth} import scim.lib.consoleUtil.Color._ +import scim.lib.io._ class Output(sharedMemory: SharedMemory, printOutput: Boolean = true) extends Runnable { + val outputFileBaseline = OutputFile("baseline.txt") + val outputFileTemperature = OutputFile("temperature.txt") + val outputFileFaultyTemp = OutputFile("faultyTemp.txt") + val outputFileSensor = OutputFile("sensor_temp.txt") + val outputFileFaultySensor = OutputFile("sensor_fault.txt") def run(): Unit = { val duration = sharedMemory.config.getDuration def outputHelper(time: Time): Unit = { val simOutput = sharedMemory.subscribe(time) outputData(time, simOutput._1, simOutput._2) } - + //outputFile.buffer("group,time,x,y,gt\n") Vector.tabulate(duration)(t => Time(t, duration)).foreach(outputHelper) + outputFileBaseline.write() + outputFileTemperature.write() + outputFileFaultyTemp.write() + outputFileSensor.write() + outputFileFaultySensor.write() } def outputData(time: Time, sensors: Map[Cell, Map[Group, Vector[SensorResult]]], groundTruths: Map[Cell, Map[Group, Truth]]): Unit = { @@ -28,6 +39,22 @@ class Output(sharedMemory: SharedMemory, printOutput: Boolean = true) extends Ru groups._2.foreach(res => print(res.get._3.getOrElse("None") + (if(res.eq(lastElement.get)) "" else " | "))) if(groundTruths.contains(cell) && groundTruths(cell).contains(group)) print(GREEN("\n Ground Truth: ") + groundTruths(cell)(group).peak().getOrElse("None")) print("\n") + + val x = cell.x; val y = cell.y + val groupName = group.name + val groupParent = group.parent.getOrElse(""); + val value = groundTruths(cell)(group).peak().getOrElse(None).getOrElse(None) + val sResult = if(groups._2.size > 0) groups._2(0).get._3.getOrElse(None) else None + if(groupName == "baseline") { + outputFileBaseline.buffer(time.time + "," + x + "," + y + "," + value.toString + "\n") + } else if(groupName == "temperature") { + outputFileTemperature.buffer(time.time + "," + x + "," + y + "," + value.toString + "\n") + outputFileSensor.buffer(time.time + "," + x + "," + y + "," + sResult.toString + "\n") + } else if(groupName == "faultyTemp") { + outputFileFaultyTemp.buffer(time.time + "," + x + "," + y + "," + value.toString + "\n") + outputFileFaultySensor.buffer(time.time + "," + x + "," + y + "," + sResult.toString + "\n") + } + }) }) } diff --git a/src/main/scala/scim/components/Parser.scala b/src/main/scala/scim/components/Parser.scala index 62c64c7..39d93c1 100644 --- a/src/main/scala/scim/components/Parser.scala +++ b/src/main/scala/scim/components/Parser.scala @@ -1,348 +1,348 @@ -package scim.components - -import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, GroundTruth, Group, Sensor, SensorCount, SensorResult, SharedMemory, SimulationBehaviours, SimulationConfig, Time, Truth} -import scim.lib.debugUtil.{DebugType, debugf} -import scim.lib.mapUtil._ -import scim.lib.simInterfaces.SimulationBehaviour -import scim.lib.yml -import scim.lib.parserUtil._ -import scala.collection.mutable - -class Parser() { - def parse(file: String, simulationBehaviours: SimulationBehaviours): Configuration = { - val parser = new SimulationConfigParser(yml.parse(file), simulationBehaviours) - parser.parse() - } - -} - - -// when creating (Group, Sensor, Option[GroundTruth]), keep ref in Map[Group, Sensor, Option[GroundTruth]] for all, to find -// root group via group.parent (while !isempty get next) (maybe tree instead) - -// for cells s-> create Map[Group, Map[Cell, Count]] -// for gt g-> create Map[Group, GroundTruth] -// for behaviour b-> create Map[Group, Behaviour] -// sensors = s.map(kv => (kv._1, kv._2.map(kv2 => (kv2._1, SENSOR, TRUTH))) -// sensors.foreach(kv => kv._1 - -class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: SimulationBehaviours) { - private val config: SimulationConfig = parseSimulationConfig(input("simulation").asInstanceOf[Map[String, Any]]) - private val groups = mutable.Map[String, Group]().empty - private val sensorList = mutable.Map[Cell, mutable.Map[Group, SensorCount]]().empty - private val behaviourListSensor = mutable.Map[Group, Behaviour]().empty - private val behaviourListGroundTruth = mutable.Map[Group, Behaviour]().empty - private val groundTruthValues = mutable.Map[Time, mutable.Map[Group, Truth]]() // FIXME: are not parsed correctly - // this will throw if the map doesn't contain what it should - @throws(classOf[Exception]) - def parse(): Configuration = { - - val sensors = input("sensors").asInstanceOf[Vector[Map[String, Any]]] - val groundTruths = input("groundtruths").asInstanceOf[Vector[Map[String, Any]]] - val behaviours = input("behaviours").asInstanceOf[Vector[Map[String, Any]]] - - sensors.foreach(sensor => parseSensor(sensor)) - behaviours.foreach(behaviour => parseBehaviour(behaviour)) - groundTruths.foreach(groundTruth => parseGroundTruth(groundTruth)) - // TODO: insert dummy sensors if none exists at cells of the parent group where this group exists - // TODO: when inserting time based values, only overwrite None, dont overwrite Some() with None! - // e.g. truth was declared at tick 9 with value 11, but later again with None at tick 9, DONT OVERWRITE - // e.g. truth was declared at tick 9 with value none, but later again with value 9, OVERWRITE - - //populate configuration - val configurationMapping = sensorList.map(cell => cell._1 -> cellMapper(cell._1, cell._2.toMap)).toMap - - // construct shared memory sensor - val sharedMemorySensor = Vector.tabulate(config.getDuration)(t => (Time(t, config.getDuration), sensorList.map(kv => kv._1 -> kv._2.map(kv2 => kv2._1 -> Vector.tabulate(kv2._2.count)(_ => SensorResult())).toMap).toMap)) - - def truthHelper(cell: Cell, list: Map[Group, List[(Group, Sensor, Option[GroundTruth])]]): (Cell, Map[Group, Truth]) = { - val groupList: mutable.Map[Group, Truth] = mutable.Map() - list.foreach(kv => kv._2.foreach(e => groupList(e._1) = Truth())) - cell -> groupList.toMap - } - - val sharedMemoryTruth = Vector.tabulate(config.getDuration)(t => (Time(t, config.getDuration), configurationMapping.map(a => truthHelper(a._1, a._2)))) - - val sharedMemorySensorMap = traversableToMap[Time, Map[Cell, Map[Group, Vector[SensorResult]]]](sharedMemorySensor) - - val sharedMemoryTruthMap = traversableToMap[Time, Map[Cell, Map[Group, Truth]]](sharedMemoryTruth) - - val sharedMemory = SharedMemory(sharedMemorySensorMap, sharedMemoryTruthMap, groundTruthValues.map(kv => kv._1 -> kv._2.toMap).toMap, config, groups.toMap) - - Configuration(configurationMapping, sharedMemory) - } - - private def parseSimulationConfig(configInput: Map[String, Any]): SimulationConfig = { - val config = configInput.asInstanceOf[Map[String, Int]] - val duration = config("duration") - val runs = config("runs") - val tickfactor = if(config.contains("tickfactor")) config("tickfactor") else 1 - val parCell = if(config.contains("parCell")) config("parCell") else 0 - val parCellThreads = if(config.contains("parCellThreads")) config("parCellThreads") else 5 - val parGroup = if(config.contains("parGroup")) config("parGroup") else 0 - val parGroupThreads = if(config.contains("parGroupThreads")) config("parGroupThreads") else 5 - val parSensor = if(config.contains("parSensor")) config("parSensor") else 0 - val parSensorThreads = if(config.contains("parSensorThreads")) config("parSensorThreads") else 5 - SimulationConfig(duration, runs, tickfactor, parCell, parCellThreads, parGroup, parGroupThreads, parSensor, parSensorThreads) - } - - private def parseSensor(sensor: Map[String, Any]): Unit = { - val groupString = sensor("group").asInstanceOf[String] - val parentString: Option[String] = if(sensor.contains("parent")) Some(sensor("parent").asInstanceOf[String]) else None - val cellsParsed: Map[Cell, SensorCount] = if(sensor.contains("cells")) parseCells(sensor("cells").asInstanceOf[Vector[Any]]) else Map() - val blocksParsed: Map[Cell, SensorCount] = if(sensor.contains("blocks")) parseBlocks(sensor("blocks").asInstanceOf[Vector[Any]]) else Map() - val cells = mergeMaps[Cell, SensorCount](blocksParsed, cellsParsed, (a: SensorCount, b: SensorCount) => a.add(b), SensorCount(0)) - val group = Group(groupString, parentString) - if(!groups.contains(groupString)) { - groups(groupString) = group - cells.foreach(kv => { - val cell = kv._1 - val count = kv._2 - if(sensorList.contains(cell)) { - if(sensorList(cell).contains(group)) { - // just add sensor count if one was already defined - sensorList(cell)(group) = sensorList(cell)(group).add(count) - } else { - sensorList(cell)(group) = count - } - } else { - sensorList(cell) = mutable.Map(group -> count) - } - }) - } else { - debugf(DebugType.WARNING, "Duplicate Sensors in Group [%s]. Ignoring Sensor: %s", groupString, sensor.toString()) - } - } - - private def parseCells(cells: Vector[Any]): Map[Cell, SensorCount] = { - val parseCell: Any => (Cell, SensorCount) = (inCell: Any) => inCell match { - case cell: Vector[_] => val cellVector = cell.asInstanceOf[Vector[Int]] - (Cell(cellVector(0), cellVector(1)), SensorCount(cellVector(2))) - case cell: Map[_, _] => val cellMap = cell.asInstanceOf[Map[String, Int]] - (Cell(cellMap("x"), cellMap("y")), SensorCount(cellMap("count"))) - case _ => throw new Exception("Invalid Cell: " + inCell.toString) - } - // traversableToMap iterates over the vector while parseCell returns a key value - // pair for each element which than gets added to the returned map - traversableToMap[Cell, SensorCount](cells, parseCell) - } - - private def parseBlocks(blocks: Vector[Any]): Map[Cell, SensorCount] = { - val parseBlock: Any => Map[Cell, SensorCount] = (inBlock: Any) => inBlock match { - case block: Map[_, _] => { - val blockMap = block.asInstanceOf[Map[String, Any]] - val start = blockMap("start").asInstanceOf[Vector[Int]] - val end = blockMap("end").asInstanceOf[Vector[Int]] - val count = blockMap("count").asInstanceOf[Int] - val blockCells = Cell(start(0), start(1)).block(Cell(end(0), end(1))) - val countPerCell: Int = count / blockCells.length - var countRest: Int = count % blockCells.length - - val result: Vector[(Cell, SensorCount)] = blockCells.map(cell => { - val sensorCount = SensorCount(countPerCell + (if(countRest > 0) 1 else 0)) - if(countRest > 0) countRest -= 1 - cell -> sensorCount - }) - result.toMap - } - case _ => throw new Exception("Invalid Block: " + inBlock.toString) - } - mergeMaps[Cell, SensorCount](blocks.map(parseBlock), (a: SensorCount, b: SensorCount) => a.add(b), SensorCount(0)) - } - - // don't add tickfacor here, this is done in parseTruth with config.time() - private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = { - timeLineElement match { - case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]] - (elementVector(0), if(elementVector.size > 1) Some(elementVector(1)) else None) - case timeLineElement: Map[_, _] => val elementMap = timeLineElement.asInstanceOf[Map[String, Int]] - (elementMap("start"), if(elementMap.contains("end")) Some(elementMap("end")) else None) - } - } - - private def parseFunction(functions: Vector[Any]): Behaviour = { - val parseFunctionHelper = (function: Any) => { - var funcPointer: Option[(SimulationBehaviour, Option[FunctionArgs])] = None - function match { - case function: Vector[_] => { - val start: Int = function(0).asInstanceOf[Int] * config.tickfactor - // val end: Option[Int] = None // Not used - val name: String = function(1).asInstanceOf[String] - val args: Option[FunctionArgs] = if (function.size > 2) Some(FunctionArgs(function(2).asInstanceOf[Map[String, Any]])) else None - if (!simulationBehaviours.get(name).isEmpty) funcPointer = Some((simulationBehaviours.get(name).get, args)) - Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]](start -> funcPointer) // using a map here since the second case can produces multiple keys - } - case function: Map[_, _] => { - val functionMap = function.asInstanceOf[Map[String, Any]] - val timeline: Vector[Any] = functionMap("timeline").asInstanceOf[Vector[Any]] - val timeLineVector: Vector[(Int, Option[Int])] = timeline.map(e => parseTimeLineElement(e)) - val name: String = functionMap("name").asInstanceOf[String] - val args: Option[FunctionArgs] = if (functionMap.contains("args")) Some(FunctionArgs(functionMap("args").asInstanceOf[Map[String, Any]])) else None - if (!simulationBehaviours.get(name).isEmpty) funcPointer = Some((simulationBehaviours.get(name).get, args)) - val output: mutable.Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]] = mutable.Map() - timeLineVector.map(tuple => { - output(tuple._1 * config.tickfactor) = funcPointer - if (!tuple._2.isEmpty && !output.contains(tuple._2.get)) output(tuple._2.get * config.tickfactor) = None // only set None when it doesnt exist yet - }) - output.toMap - } - case _ => throw new Exception("Invalid Function: " + function.toString) - } - } - val parsedFunction: mutable.Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]] = mutable.Map().empty - // TODO: This will currently overwrite already assigned values; output warning - val a = functions.map(function => parseFunctionHelper(function)) - a.foreach(e => e.foreach(kv => { - if(!kv._2.isEmpty || !parsedFunction.contains(kv._1)) // only set None when it doesnt exist yet - parsedFunction(kv._1) = kv._2 - } )) - val timeVector: mutable.ArraySeq[Option[(SimulationBehaviour, Option[FunctionArgs])]] = new mutable.ArraySeq[Option[(SimulationBehaviour, Option[FunctionArgs])]](config.getDuration).map(_ => None) - val it = parsedFunction.toList.sortBy(_._1).iterator - var current = if(it.hasNext) Some(it.next) else None - while(!current.isEmpty) { - val next = if(it.hasNext) Some(it.next) else None - for(time <- current.get._1 to (if(!next.isEmpty && next.get._1 < config.getDuration) next.get._1 else config.getDuration - 1)) { - timeVector(time) = current.get._2 - } - current = next - } - Behaviour(BehaviourVector(timeVector.toVector)) - } - - private def parseBehaviour(behaviourMap: Map[String, Any]): Unit = { - val groupString = behaviourMap("group").asInstanceOf[String] - // only continue parsing if sensors in this group exist - if(groups.contains(groupString)) { - val group: Group = groups(groupString) - if(!behaviourListSensor.contains(group)) { - behaviourListSensor(group) = parseFunction(behaviourMap("functions").asInstanceOf[Vector[Any]]) - } else { - debugf(DebugType.WARNING, "Duplicate Sensor Behaviour in Group [%s]. Ignoring Behaviour: %s", groupString, behaviourMap.toString()) - } - } else { - debugf(DebugType.WARNING, "Unused Sensor Behaviour, no sensor for group [%s] exists. Ignoring Behaviour: %s", groupString, behaviourMap.toString()) - } - } - - private def parseGroundTruth(groundTruthMap: Map[String, Any]): Unit = { - val groupString = groundTruthMap("group").asInstanceOf[String] - // only continue parsing if sensors in this group exist - if(groups.contains(groupString)) { - val group: Group = groups(groupString) - if(group.parent.isEmpty) { - if(!behaviourListGroundTruth.contains(group)) { - group.parent = if(groundTruthMap.contains("parent")) Some(groundTruthMap("parent").asInstanceOf[String]) else None - behaviourListGroundTruth(group) = parseFunction(groundTruthMap("delta").asInstanceOf[Vector[Any]]) - } else { - debugf(DebugType.WARNING, "Duplicate Ground Truth in Group [%s]. Ignoring Ground Truth: %s", groupString, groundTruthMap.toString()) - } - if(groundTruthMap.contains("truth")){ - parseTruth(groundTruthMap("truth").asInstanceOf[Vector[Any]]).map(kv => { - if(groundTruthValues.contains(kv._1)) { - groundTruthValues(kv._1)(group) = kv._2 - } else { - groundTruthValues(kv._1) = mutable.Map[Group, Truth](group -> kv._2) - } - }) - } else if(group.parent.isEmpty) { - debugf(DebugType.WARNING, "Sensor Group [%s] has no parent and no ground truth values", groupString) - } - } else { - debugf(DebugType.WARNING, "Unused Ground Truth in Group [%s] because the Sensor has Parent [%s]. Ignoring Ground Truth: %s", groupString, group.parent.get, groundTruthMap.toString()) - } - } else { - debugf(DebugType.WARNING, "Unused Ground Truth, no sensor for group [%s] exists. Ignoring Ground Truth: %s", groupString, groundTruthMap.toString()) - } - } - - private def getParentRoot(group: Group, groups: Map[String, Group], seen: Vector[String] = Vector()): Option[Group] = { - if(seen.contains(group.name)) { - debugf(DebugType.WARNING, "Circular dependency of groups: [%s]", seen.toString()) - None - } else if(!groups.contains(group.name)) { // this should not happen and be caught earlier - debugf(DebugType.WARNING, "Group [%s] ignored, it is not part of the sensor groups [%s]", group.toString, groups.toString()) - None - } else { - if(group.parent.isEmpty) Some(group) else getParentRoot(groups(group.parent.get), groups, seen ++ Vector(group.name)) - } - } - - private def parseTruth(truthElements: Vector[Any]): Map[Time, Truth] = { - val parseTruthHelper = (truthElement: Any) => { - truthElement match { - case truthObject: Vector[_] => { - val truth: Truth = Truth() - val start: Int = truthObject(0).asInstanceOf[Int] - // val end: Option[Int] = None // Not used - truth.set(parseDouble(truthObject(1))) - Map[Time, Truth](config.time(start) -> truth) - } - case truthObject: Map[_, _] => { - val truth: Truth = Truth() - lazy val endTruth: Truth = Truth() - val truthObjectMap = truthObject.asInstanceOf[Map[String, Any]] - val timeline: Vector[Any] = truthObjectMap("timeline").asInstanceOf[Vector[Any]] - val timeLineVector: Vector[(Int, Option[Int])] = timeline.map(e => parseTimeLineElement(e)) - truth.set(parseDouble(truthObjectMap("value"))) - - val output: mutable.Map[Time, Truth] = mutable.Map() - timeLineVector.map(tuple => { - output(config.time(tuple._1)) = truth - if (!tuple._2.isEmpty) { - endTruth.set(None) - output(config.time(tuple._2.get)) = endTruth - } - }) - output.toMap - } - case _ => throw new Exception("Invalid Truth: " + truthElement.toString) - } - } - val parsedTruth: mutable.Map[Time,Truth] = mutable.Map().empty - // TODO: This will currently overwrite already assigned values; output warning - truthElements.map(truth => parseTruthHelper(truth)).foreach(e => e.foreach(kv => parsedTruth(kv._1) = kv._2)) - parsedTruth.toMap - } - - def cellMapper(cell: Cell, groups: Map[Group, SensorCount]): Map[Group, List[(Group, Sensor, Option[GroundTruth])]] = { - val groupMap: mutable.Map[Group, List[(Group, Sensor, Option[GroundTruth])]] = mutable.Map() - val groupDone: mutable.HashSet[Group] = mutable.HashSet() - - def groupMapper(group: Group, count: SensorCount): List[(Group, Sensor, Option[GroundTruth])] = { - if(groupDone.contains(group)) { - // if we've already seen the group, get the list so we can add a depended group - val root = getParentRoot(group, this.groups.toMap) // get root if already added - if(!root.isEmpty) { - groupMap(root.get) - } else { List() } // circular dependency, just return empty list - } else { - if(!behaviourListSensor.contains(group)) { - debugf(DebugType.WARNING, "Group [%s] does not have a behaviour, using default", group.toString()) - behaviourListSensor(group) = Behaviour(BehaviourVector(Vector.tabulate(config.getDuration)(_ => None))) - } - if(group.parent.isEmpty) { - groupDone.add(group) - List((group, Sensor(group, cell, count, behaviourListSensor(group), config.parSensor, config.sensorThreadPool), - if(behaviourListGroundTruth.contains(group)) Some(GroundTruth(group, Some(behaviourListGroundTruth(group)))) else None)) - } else { - groupDone.add(group) - val parentGroup = this.groups(group.parent.get) // this should really exist at this point - val parentCount = if(groups.contains(parentGroup)) groups(parentGroup) else SensorCount(0) // insert dummy parent group if not in cell - groupMapper(parentGroup, parentCount) ++ List((group, Sensor(group, cell, count, behaviourListSensor(group), config.parSensor, config.sensorThreadPool), - if(behaviourListGroundTruth.contains(group)) Some(GroundTruth(group, Some(behaviourListGroundTruth(group)))) else None)) - } - } - } - - groups.foreach(kv => { - val group = kv._1 - val count = kv._2 - val root = getParentRoot(group, this.groups.toMap) - if(!groupDone.contains(group) && !root.isEmpty) { - groupMap(root.get) = groupMapper(group, count) - } - }) - groupMap.toMap - } - +package scim.components + +import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, GroundTruth, Group, Sensor, SensorCount, SensorResult, SharedMemory, SimulationBehaviours, SimulationConfig, Time, Truth} +import scim.lib.debugUtil.{DebugType, debugf} +import scim.lib.mapUtil._ +import scim.lib.simInterfaces.SimulationBehaviour +import scim.lib.yml +import scim.lib.parserUtil._ +import scala.collection.mutable + +class Parser() { + def parse(file: String, simulationBehaviours: SimulationBehaviours): Configuration = { + val parser = new SimulationConfigParser(yml.parse(file), simulationBehaviours) + parser.parse() + } + +} + + +// when creating (Group, Sensor, Option[GroundTruth]), keep ref in Map[Group, Sensor, Option[GroundTruth]] for all, to find +// root group via group.parent (while !isempty get next) (maybe tree instead) + +// for cells s-> create Map[Group, Map[Cell, Count]] +// for gt g-> create Map[Group, GroundTruth] +// for behaviour b-> create Map[Group, Behaviour] +// sensors = s.map(kv => (kv._1, kv._2.map(kv2 => (kv2._1, SENSOR, TRUTH))) +// sensors.foreach(kv => kv._1 + +class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: SimulationBehaviours) { + private val config: SimulationConfig = parseSimulationConfig(input("simulation").asInstanceOf[Map[String, Any]]) + private val groups = mutable.Map[String, Group]().empty + private val sensorList = mutable.Map[Cell, mutable.Map[Group, SensorCount]]().empty + private val behaviourListSensor = mutable.Map[Group, Behaviour]().empty + private val behaviourListGroundTruth = mutable.Map[Group, Behaviour]().empty + private val groundTruthValues = mutable.Map[Time, mutable.Map[Group, Truth]]() // FIXME: are not parsed correctly + // this will throw if the map doesn't contain what it should + @throws(classOf[Exception]) + def parse(): Configuration = { + + val sensors = input("sensors").asInstanceOf[Vector[Map[String, Any]]] + val groundTruths = input("groundtruths").asInstanceOf[Vector[Map[String, Any]]] + val behaviours = input("behaviours").asInstanceOf[Vector[Map[String, Any]]] + + sensors.foreach(sensor => parseSensor(sensor)) + behaviours.foreach(behaviour => parseBehaviour(behaviour)) + groundTruths.foreach(groundTruth => parseGroundTruth(groundTruth)) + // TODO: insert dummy sensors if none exists at cells of the parent group where this group exists + // TODO: when inserting time based values, only overwrite None, dont overwrite Some() with None! + // e.g. truth was declared at tick 9 with value 11, but later again with None at tick 9, DONT OVERWRITE + // e.g. truth was declared at tick 9 with value none, but later again with value 9, OVERWRITE + + //populate configuration + val configurationMapping = sensorList.map(cell => cell._1 -> cellMapper(cell._1, cell._2.toMap)).toMap + + // construct shared memory sensor + val sharedMemorySensor = Vector.tabulate(config.getDuration)(t => (Time(t, config.getDuration), sensorList.map(kv => kv._1 -> kv._2.map(kv2 => kv2._1 -> Vector.tabulate(kv2._2.count)(_ => SensorResult())).toMap).toMap)) + + def truthHelper(cell: Cell, list: Map[Group, List[(Group, Sensor, Option[GroundTruth])]]): (Cell, Map[Group, Truth]) = { + val groupList: mutable.Map[Group, Truth] = mutable.Map() + list.foreach(kv => kv._2.foreach(e => groupList(e._1) = Truth())) + cell -> groupList.toMap + } + + val sharedMemoryTruth = Vector.tabulate(config.getDuration)(t => (Time(t, config.getDuration), configurationMapping.map(a => truthHelper(a._1, a._2)))) + + val sharedMemorySensorMap = traversableToMap[Time, Map[Cell, Map[Group, Vector[SensorResult]]]](sharedMemorySensor) + + val sharedMemoryTruthMap = traversableToMap[Time, Map[Cell, Map[Group, Truth]]](sharedMemoryTruth) + + val sharedMemory = SharedMemory(sharedMemorySensorMap, sharedMemoryTruthMap, groundTruthValues.map(kv => kv._1 -> kv._2.toMap).toMap, config, groups.toMap) + + Configuration(configurationMapping, sharedMemory) + } + + private def parseSimulationConfig(configInput: Map[String, Any]): SimulationConfig = { + val config = configInput.asInstanceOf[Map[String, Int]] + val duration = config("duration") + val runs = config("runs") + val tickfactor = if(config.contains("tickfactor")) config("tickfactor") else 1 + val parCell = if(config.contains("parCell")) config("parCell") else 0 + val parCellThreads = if(config.contains("parCellThreads")) config("parCellThreads") else 5 + val parGroup = if(config.contains("parGroup")) config("parGroup") else 0 + val parGroupThreads = if(config.contains("parGroupThreads")) config("parGroupThreads") else 5 + val parSensor = if(config.contains("parSensor")) config("parSensor") else 0 + val parSensorThreads = if(config.contains("parSensorThreads")) config("parSensorThreads") else 5 + SimulationConfig(duration, runs, tickfactor, parCell, parCellThreads, parGroup, parGroupThreads, parSensor, parSensorThreads) + } + + private def parseSensor(sensor: Map[String, Any]): Unit = { + val groupString = sensor("group").asInstanceOf[String] + val parentString: Option[String] = if(sensor.contains("parent")) Some(sensor("parent").asInstanceOf[String]) else None + val cellsParsed: Map[Cell, SensorCount] = if(sensor.contains("cells")) parseCells(sensor("cells").asInstanceOf[Vector[Any]]) else Map() + val blocksParsed: Map[Cell, SensorCount] = if(sensor.contains("blocks")) parseBlocks(sensor("blocks").asInstanceOf[Vector[Any]]) else Map() + val cells = mergeMaps[Cell, SensorCount](blocksParsed, cellsParsed, (a: SensorCount, b: SensorCount) => a.add(b), SensorCount(0)) + val group = Group(groupString, parentString) + if(!groups.contains(groupString)) { + groups(groupString) = group + cells.foreach(kv => { + val cell = kv._1 + val count = kv._2 + if(sensorList.contains(cell)) { + if(sensorList(cell).contains(group)) { + // just add sensor count if one was already defined + sensorList(cell)(group) = sensorList(cell)(group).add(count) + } else { + sensorList(cell)(group) = count + } + } else { + sensorList(cell) = mutable.Map(group -> count) + } + }) + } else { + debugf(DebugType.WARNING, "Duplicate Sensors in Group [%s]. Ignoring Sensor: %s", groupString, sensor.toString()) + } + } + + private def parseCells(cells: Vector[Any]): Map[Cell, SensorCount] = { + val parseCell: Any => (Cell, SensorCount) = (inCell: Any) => inCell match { + case cell: Vector[_] => val cellVector = cell.asInstanceOf[Vector[Int]] + (Cell(cellVector(0), cellVector(1)), SensorCount(cellVector(2))) + case cell: Map[_, _] => val cellMap = cell.asInstanceOf[Map[String, Int]] + (Cell(cellMap("x"), cellMap("y")), SensorCount(cellMap("count"))) + case _ => throw new Exception("Invalid Cell: " + inCell.toString) + } + // traversableToMap iterates over the vector while parseCell returns a key value + // pair for each element which than gets added to the returned map + traversableToMap[Cell, SensorCount](cells, parseCell) + } + + private def parseBlocks(blocks: Vector[Any]): Map[Cell, SensorCount] = { + val parseBlock: Any => Map[Cell, SensorCount] = (inBlock: Any) => inBlock match { + case block: Map[_, _] => { + val blockMap = block.asInstanceOf[Map[String, Any]] + val start = blockMap("start").asInstanceOf[Vector[Int]] + val end = blockMap("end").asInstanceOf[Vector[Int]] + val count = blockMap("count").asInstanceOf[Int] + val blockCells = Cell(start(0), start(1)).block(Cell(end(0), end(1))) + val countPerCell: Int = count / blockCells.length + var countRest: Int = count % blockCells.length + // TODO: filter out empty cells and return early if countPerCell == 0 + val result: Vector[(Cell, SensorCount)] = blockCells.map(cell => { + val sensorCount = SensorCount(countPerCell + (if(countRest > 0) 1 else 0)) + if(countRest > 0) countRest -= 1 + cell -> sensorCount + }) + result.toMap + } + case _ => throw new Exception("Invalid Block: " + inBlock.toString) + } + mergeMaps[Cell, SensorCount](blocks.map(parseBlock), (a: SensorCount, b: SensorCount) => a.add(b), SensorCount(0)) + } + + // don't add tickfacor here, this is done in parseTruth with config.time() and parseFunction with config.tickfactor + private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = { + timeLineElement match { + case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]] + (elementVector(0), if(elementVector.size > 1) Some(elementVector(1)) else None) + case timeLineElement: Map[_, _] => val elementMap = timeLineElement.asInstanceOf[Map[String, Int]] + (elementMap("start"), if(elementMap.contains("end")) Some(elementMap("end")) else None) + } + } + + private def parseFunction(functions: Vector[Any]): Behaviour = { + val parseFunctionHelper = (function: Any) => { + var funcPointer: Option[(SimulationBehaviour, Option[FunctionArgs])] = None + function match { + case function: Vector[_] => { + val start: Int = function(0).asInstanceOf[Int] * config.tickfactor + // val end: Option[Int] = None // Not used + val name: String = function(1).asInstanceOf[String] + val args: Option[FunctionArgs] = if (function.size > 2) Some(FunctionArgs(function(2).asInstanceOf[Map[String, Any]])) else None + if (!simulationBehaviours.get(name).isEmpty) funcPointer = Some((simulationBehaviours.get(name).get, args)) + Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]](start -> funcPointer) // using a map here since the second case can produces multiple keys + } + case function: Map[_, _] => { + val functionMap = function.asInstanceOf[Map[String, Any]] + val timeline: Vector[Any] = functionMap("timeline").asInstanceOf[Vector[Any]] + val timeLineVector: Vector[(Int, Option[Int])] = timeline.map(e => parseTimeLineElement(e)) + val name: String = functionMap("name").asInstanceOf[String] + val args: Option[FunctionArgs] = if (functionMap.contains("args")) Some(FunctionArgs(functionMap("args").asInstanceOf[Map[String, Any]])) else None + if (!simulationBehaviours.get(name).isEmpty) funcPointer = Some((simulationBehaviours.get(name).get, args)) + val output: mutable.Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]] = mutable.Map() + timeLineVector.map(tuple => { + output(tuple._1 * config.tickfactor) = funcPointer + if (!tuple._2.isEmpty && !output.contains(tuple._2.get)) output(tuple._2.get * config.tickfactor) = None // only set None when it doesnt exist yet + }) + output.toMap + } + case _ => throw new Exception("Invalid Function: " + function.toString) + } + } + val parsedFunction: mutable.Map[Int, Option[(SimulationBehaviour, Option[FunctionArgs])]] = mutable.Map().empty + // TODO: This will currently overwrite already assigned values; output warning + val a = functions.map(function => parseFunctionHelper(function)) + a.foreach(e => e.foreach(kv => { + if(!kv._2.isEmpty || !parsedFunction.contains(kv._1)) // only set None when it doesnt exist yet + parsedFunction(kv._1) = kv._2 + } )) + val timeVector: mutable.ArraySeq[Option[(SimulationBehaviour, Option[FunctionArgs])]] = new mutable.ArraySeq[Option[(SimulationBehaviour, Option[FunctionArgs])]](config.getDuration).map(_ => None) + val it = parsedFunction.toList.sortBy(_._1).iterator + var current = if(it.hasNext) Some(it.next) else None + while(!current.isEmpty) { + val next = if(it.hasNext) Some(it.next) else None + for(time <- current.get._1 to (if(!next.isEmpty && next.get._1 < config.getDuration) next.get._1 else config.getDuration - 1)) { + timeVector(time) = current.get._2 + } + current = next + } + Behaviour(BehaviourVector(timeVector.toVector)) + } + + private def parseBehaviour(behaviourMap: Map[String, Any]): Unit = { + val groupString = behaviourMap("group").asInstanceOf[String] + // only continue parsing if sensors in this group exist + if(groups.contains(groupString)) { + val group: Group = groups(groupString) + if(!behaviourListSensor.contains(group)) { + behaviourListSensor(group) = parseFunction(behaviourMap("functions").asInstanceOf[Vector[Any]]) + } else { + debugf(DebugType.WARNING, "Duplicate Sensor Behaviour in Group [%s]. Ignoring Behaviour: %s", groupString, behaviourMap.toString()) + } + } else { + debugf(DebugType.WARNING, "Unused Sensor Behaviour, no sensor for group [%s] exists. Ignoring Behaviour: %s", groupString, behaviourMap.toString()) + } + } + + private def parseGroundTruth(groundTruthMap: Map[String, Any]): Unit = { + val groupString = groundTruthMap("group").asInstanceOf[String] + // only continue parsing if sensors in this group exist + if(groups.contains(groupString)) { + val group: Group = groups(groupString) + if(group.parent.isEmpty) { + if(!behaviourListGroundTruth.contains(group)) { + group.parent = if(groundTruthMap.contains("parent")) Some(groundTruthMap("parent").asInstanceOf[String]) else None + behaviourListGroundTruth(group) = parseFunction(groundTruthMap("delta").asInstanceOf[Vector[Any]]) + } else { + debugf(DebugType.WARNING, "Duplicate Ground Truth in Group [%s]. Ignoring Ground Truth: %s", groupString, groundTruthMap.toString()) + } + if(groundTruthMap.contains("truth")){ + parseTruth(groundTruthMap("truth").asInstanceOf[Vector[Any]]).map(kv => { + if(groundTruthValues.contains(kv._1)) { + groundTruthValues(kv._1)(group) = kv._2 + } else { + groundTruthValues(kv._1) = mutable.Map[Group, Truth](group -> kv._2) + } + }) + } else if(group.parent.isEmpty) { + debugf(DebugType.WARNING, "Sensor Group [%s] has no parent and no ground truth values", groupString) + } + } else { + debugf(DebugType.WARNING, "Unused Ground Truth in Group [%s] because the Sensor has Parent [%s]. Ignoring Ground Truth: %s", groupString, group.parent.get, groundTruthMap.toString()) + } + } else { + debugf(DebugType.WARNING, "Unused Ground Truth, no sensor for group [%s] exists. Ignoring Ground Truth: %s", groupString, groundTruthMap.toString()) + } + } + + private def getParentRoot(group: Group, groups: Map[String, Group], seen: Vector[String] = Vector()): Option[Group] = { + if(seen.contains(group.name)) { + debugf(DebugType.WARNING, "Circular dependency of groups: [%s]", seen.toString()) + None + } else if(!groups.contains(group.name)) { // this should not happen and be caught earlier + debugf(DebugType.WARNING, "Group [%s] ignored, it is not part of the sensor groups [%s]", group.toString, groups.toString()) + None + } else { + if(group.parent.isEmpty) Some(group) else getParentRoot(groups(group.parent.get), groups, seen ++ Vector(group.name)) + } + } + + private def parseTruth(truthElements: Vector[Any]): Map[Time, Truth] = { + val parseTruthHelper = (truthElement: Any) => { + truthElement match { + case truthObject: Vector[_] => { + val truth: Truth = Truth() + val start: Int = truthObject(0).asInstanceOf[Int] + // val end: Option[Int] = None // Not used + truth.set(parseDouble(truthObject(1))) + Map[Time, Truth](config.time(start) -> truth) + } + case truthObject: Map[_, _] => { + val truth: Truth = Truth() + lazy val endTruth: Truth = Truth() + val truthObjectMap = truthObject.asInstanceOf[Map[String, Any]] + val timeline: Vector[Any] = truthObjectMap("timeline").asInstanceOf[Vector[Any]] + val timeLineVector: Vector[(Int, Option[Int])] = timeline.map(e => parseTimeLineElement(e)) + truth.set(parseDouble(truthObjectMap("value"))) + + val output: mutable.Map[Time, Truth] = mutable.Map() + timeLineVector.map(tuple => { + output(config.time(tuple._1)) = truth + if (!tuple._2.isEmpty) { + endTruth.set(None) + output(config.time(tuple._2.get)) = endTruth + } + }) + output.toMap + } + case _ => throw new Exception("Invalid Truth: " + truthElement.toString) + } + } + val parsedTruth: mutable.Map[Time,Truth] = mutable.Map().empty + // TODO: This will currently overwrite already assigned values; output warning + truthElements.map(truth => parseTruthHelper(truth)).foreach(e => e.foreach(kv => parsedTruth(kv._1) = kv._2)) + parsedTruth.toMap + } + + def cellMapper(cell: Cell, groups: Map[Group, SensorCount]): Map[Group, List[(Group, Sensor, Option[GroundTruth])]] = { + val groupMap: mutable.Map[Group, List[(Group, Sensor, Option[GroundTruth])]] = mutable.Map() + val groupDone: mutable.HashSet[Group] = mutable.HashSet() + + def groupMapper(group: Group, count: SensorCount): List[(Group, Sensor, Option[GroundTruth])] = { + if(groupDone.contains(group)) { + // if we've already seen the group, get the list so we can add a depended group + val root = getParentRoot(group, this.groups.toMap) // get root if already added + if(!root.isEmpty) { + groupMap(root.get) + } else { List() } // circular dependency, just return empty list + } else { + if(!behaviourListSensor.contains(group)) { + debugf(DebugType.WARNING, "Group [%s] does not have a behaviour, using default", group.toString()) + behaviourListSensor(group) = Behaviour(BehaviourVector(Vector.tabulate(config.getDuration)(_ => None))) + } + if(group.parent.isEmpty) { + groupDone.add(group) + List((group, Sensor(group, cell, count, behaviourListSensor(group), config.parSensor, config.sensorThreadPool), + if(behaviourListGroundTruth.contains(group)) Some(GroundTruth(group, Some(behaviourListGroundTruth(group)))) else None)) + } else { + groupDone.add(group) + val parentGroup = this.groups(group.parent.get) // this should really exist at this point + val parentCount = if(groups.contains(parentGroup)) groups(parentGroup) else SensorCount(0) // insert dummy parent group if not in cell + groupMapper(parentGroup, parentCount) ++ List((group, Sensor(group, cell, count, behaviourListSensor(group), config.parSensor, config.sensorThreadPool), + if(behaviourListGroundTruth.contains(group)) Some(GroundTruth(group, Some(behaviourListGroundTruth(group)))) else None)) + } + } + } + + groups.foreach(kv => { + val group = kv._1 + val count = kv._2 + val root = getParentRoot(group, this.groups.toMap) + if(!groupDone.contains(group) && !root.isEmpty) { + groupMap(root.get) = groupMapper(group, count) + } + }) + groupMap.toMap + } + } \ No newline at end of file diff --git a/src/main/scala/scim/datastruct/Configuration.scala b/src/main/scala/scim/datastruct/Configuration.scala index ee4cc95..9d8193f 100644 --- a/src/main/scala/scim/datastruct/Configuration.scala +++ b/src/main/scala/scim/datastruct/Configuration.scala @@ -163,7 +163,7 @@ case class FunctionArgs(args: Map[String, Any]) { } case class BehaviourVector(function: Vector[Option[(SimulationBehaviour, Option[FunctionArgs])]], - defaultFunc: SimulationBehaviour = (_: Cell, _: Time, v: Option[Double], _: Option[FunctionArgs]) => v, + defaultFunc: SimulationBehaviour = (_: Cell, _: Time, _: Option[Double], _: Option[FunctionArgs]) => None, defaultArgs: Option[FunctionArgs] = None) { def get(t: Time): (SimulationBehaviour, Option[FunctionArgs]) = { function(t.time).getOrElse((defaultFunc, defaultArgs)) diff --git a/src/main/scala/scim/lib/Library.scala b/src/main/scala/scim/lib/Library.scala index c93d26c..994ee96 100644 --- a/src/main/scala/scim/lib/Library.scala +++ b/src/main/scala/scim/lib/Library.scala @@ -438,4 +438,30 @@ package object parserUtil { case _ => None } } +} + +package object io { + import java.io._ + case class OutputFile(fileName: String = "output.txt", newFile: Boolean = true) { + val file = new File(fileName) + val buffer: mutable.StringBuilder = new mutable.StringBuilder() + + if(newFile) { + val fw = new FileWriter(file) + fw.write("") + fw.close() + } + + def buffer(text: String): Unit = { + buffer.append(text) + } + + def write(): Unit = { + val fwAppend = new FileWriter(file, true) + fwAppend.write(buffer.toString()) + fwAppend.close() + buffer.clear() + } + + } } \ No newline at end of file