diff --git a/src/main/scala/scim/Main.scala b/src/main/scala/scim/Main.scala index 5708717..4f9f541 100644 --- a/src/main/scala/scim/Main.scala +++ b/src/main/scala/scim/Main.scala @@ -21,7 +21,7 @@ object Main { } def startSimulation(): Unit = { - val config = new Parser().parse("docs/wiki/config/config.yml", simBehavirous) + val config = new Parser().parse("docs/wiki/config/config_preview.yml", 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 new file mode 100644 index 0000000..0746325 --- /dev/null +++ b/src/main/scala/scim/components/Behaviour.scala @@ -0,0 +1,126 @@ +package scim.components + +import scim.datastruct.{Cell, FunctionArgs, SimulationBehaviours, Time} +import scim.lib.numberUtil._ +import scim.lib.behaviourUtil._ +import scim.lib.simInterfaces.SimulationBehaviour + +import scala.util.Random + +package object behaviour { + + val behaviours = SimulationBehaviours(Map[String, SimulationBehaviour]( + "measureTemp" -> measureTemp, + "measureHumid" -> measureHumid, + "noOutput" -> noOutput, + "fluctuate" -> fluctuate, + "change" -> change, + "humidFromTemp" -> humidFromTemp + )) + + def measureTemp: SimulationBehaviour = { + sensorFactory(Vector(-50, 150), 0.1, 1.0, 0.0) + } + + def measureHumid: SimulationBehaviour = { + sensorFactory(Vector(0, 100), 1.0, 2.0, 0.0) + } + + def noOutput: SimulationBehaviour = { + (_: Cell, _: Time, _: Option[Double], _: Option[FunctionArgs]) => { None } + } + + def sensorFactory(defaultRange: Vector[Double], defaultResolution: Double, defaultAccuracy: Double, defaultOffset: Double, + preFunction: Option[SimulationBehaviour] = None, + postFunction: Option[SimulationBehaviour] = None, + altFunction: 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 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: Double = args.getParam("distOffset", 0) + val offsetTimes: Vector[Int] = args.getParam("time", Vector(-1)) + val offset = if(distOffset != 0 && offsetTimes.contains(time.time)) { + val centerCell = Cell(center(0), center(1)) + centerCell.distance(cell) * distOffset + } else { 0 } + + val rng = new Random() + val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1) + + //if(time.time == 0 || time.time == 11) println("\nXXX: " + cell + " :: " + inTruth.get + " :OFF: " + offset + " :RES: " + (inTruth.get + rngRange + offset)) + + 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.time + 1) + val center: Vector[Int] = args.getParam("center", Vector(0, 0)) + val distOffset: Double = args.getParam("distOffset", 0) + val offsetTimes: Vector[Int] = args.getParam("time", Vector(-1)) + // always calculate offset for target + val offset = if(distOffset != 0) { + val centerCell = Cell(center(0), center(1)) + centerCell.distance(cell) * distOffset + } else { 0 } + + // only change truth on offsetTimes + val truth = if(offsetTimes.contains(time.time)) 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(inTruth.get + 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) + } + +}