diff --git a/src/main/scala/scim/components/Behaviour.scala b/src/main/scala/scim/components/Behaviour.scala index 0746325..d2a40f8 100644 --- a/src/main/scala/scim/components/Behaviour.scala +++ b/src/main/scala/scim/components/Behaviour.scala @@ -2,6 +2,7 @@ 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 @@ -10,30 +11,96 @@ import scala.util.Random package object behaviour { val behaviours = SimulationBehaviours(Map[String, SimulationBehaviour]( + "measureGeneric" -> measureGeneric, "measureTemp" -> measureTemp, "measureHumid" -> measureHumid, "noOutput" -> noOutput, + "randomOutput" -> randomOutput, + "measureTempFault" -> measureTempFault, "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(0, 100), 1.0, 2.0, 0.0) + sensorFactory(Vector(5, 100), 1.0, 2.0, 0.0) + } + + def measureTempFault: 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.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.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, - altFunction: Option[SimulationBehaviour] = None): SimulationBehaviour = { + 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) @@ -67,22 +134,43 @@ package object behaviour { 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: Double = args.getParam("distOffset", 0) - val offsetTimes: Vector[Int] = args.getParam("time", Vector(-1)) - val offset = if(distOffset != 0 && offsetTimes.contains(time.time)) { + 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.time)) { val centerCell = Cell(center(0), center(1)) - centerCell.distance(cell) * distOffset + centerCell.distance(cell) * distOffset.get } 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) } @@ -93,16 +181,16 @@ package object behaviour { 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)) + 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 != 0) { + val offset = if(!distOffset.isEmpty) { val centerCell = Cell(center(0), center(1)) - centerCell.distance(cell) * distOffset + centerCell.distance(cell) * distOffset.get } else { 0 } // only change truth on offsetTimes - val truth = if(offsetTimes.contains(time.time)) inTruth.get + offset else inTruth.get + val truth = if(!offsetTimes.isEmpty && offsetTimes.get.contains(time.time)) inTruth.get + offset else inTruth.get val rng = new Random() val rngRange = rng.nextDouble() * range * (if (rng.nextBoolean()) -1 else 1)