Compare commits

..

5 Commits

Author SHA1 Message Date
112683ea42 [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
2020-01-29 03:43:51 -06:00
8f47875cd7 [add] added missing block parsing
- added mergeMaps function to library
 - added block function to Cell class
2020-01-29 01:19:11 -06:00
05a3a88f83 [fix] fixed behaviour timeline map parsing
- added missing config.tickfactor when a map is used
2020-01-29 01:19:11 -06:00
a5984f037e [fix] fixed change behaviour time and truth issues
- targettime + 1 to reach the target at the configured time
 - output now correctly uses recalculated truth
2020-01-29 01:19:11 -06:00
95bc1363f9 [add] added missing tickfactor parsing
- override Time compares to only use Time.time
 - added Option[Int] tickfactor to Time class
 - behaviours need to be selfaware and use Time.tickfactor
 - addded tickfactor to simulation time vector
 - added parser tickfactor parsing
 - modified output to use tickfactor aware duration
2020-01-29 01:19:11 -06:00
10 changed files with 720 additions and 586 deletions

View File

@@ -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

View File

@@ -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
@@ -39,37 +36,44 @@ sensors:
# all cells are declared to output the gt
# of baseline for each into the console
- group: baseline
cells:
blocks:
- [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]

View File

@@ -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)

View File

@@ -83,9 +83,9 @@ package object behaviour {
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 (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.time && !xVal.isEmpty && !yVal.isEmpty && Cell(xVal.get, yVal.get) == cell) =>
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)
}
@@ -159,11 +159,11 @@ package object behaviour {
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 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.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 }
@@ -177,9 +177,9 @@ package object behaviour {
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 range: Double = args.getParam("range", 0.0)
val target: Double = args.getParam("target", inTruth.get + 5)
val targetTime: Int = args.getParam("targetTime", time.time + 1)
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")
@@ -190,7 +190,7 @@ package object behaviour {
} else { 0 }
// only change truth on offsetTimes
val truth = if(!offsetTimes.isEmpty && offsetTimes.get.contains(time.time)) inTruth.get + offset else inTruth.get
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)
@@ -198,7 +198,7 @@ package object behaviour {
(target + offset - truth) / (targetTime - time.time).toDouble
} else { 0 }
Some(inTruth.get + rngRange + step)
Some(truth + rngRange + step)
}
def humidFromTemp (cell: Cell, time: Time, inTruth: Option[Double], args: Option[FunctionArgs]): Option[Double] = {

View File

@@ -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.duration
val duration = sharedMemory.config.getDuration
def outputHelper(time: Time): Unit = {
val simOutput = sharedMemory.subscribe(time)
outputData(time, simOutput._1, simOutput._2)
}
Vector.tabulate(sharedMemory.config.duration)(t => Time(t, duration)).foreach(outputHelper)
//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")
}
})
})
}

View File

@@ -53,7 +53,7 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
val configurationMapping = sensorList.map(cell => cell._1 -> cellMapper(cell._1, cell._2.toMap)).toMap
// construct shared memory sensor
val sharedMemorySensor = Vector.tabulate(config.duration)(t => (Time(t, config.duration), sensorList.map(kv => kv._1 -> kv._2.map(kv2 => kv2._1 -> Vector.tabulate(kv2._2.count)(_ => SensorResult())).toMap).toMap))
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()
@@ -61,7 +61,7 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
cell -> groupList.toMap
}
val sharedMemoryTruth = Vector.tabulate(config.duration)(t => (Time(t, config.duration), configurationMapping.map(a => truthHelper(a._1, a._2))))
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)
@@ -89,7 +89,9 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
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 cells: Map[Cell, SensorCount] = parseCells(sensor("cells").asInstanceOf[Vector[Any]])
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
@@ -113,19 +115,42 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
}
private def parseCells(cells: Vector[Any]): Map[Cell, SensorCount] = {
val parseCell: Any => (Cell, SensorCount) = (cell: Any) => cell match {
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: " + cell.toString)
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)
}
// TODO: multiply time by tickfactor
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]]
@@ -140,7 +165,7 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
var funcPointer: Option[(SimulationBehaviour, Option[FunctionArgs])] = None
function match {
case function: Vector[_] => {
val start: Int = function(0).asInstanceOf[Int]
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
@@ -156,8 +181,8 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
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) = funcPointer
if (!tuple._2.isEmpty && !output.contains(tuple._2.get)) output(tuple._2.get) = None // only set None when it doesnt exist yet
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
}
@@ -171,12 +196,12 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
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.duration).map(_ => None)
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.duration) next.get._1 else config.duration - 1)) {
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
@@ -250,7 +275,7 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
val start: Int = truthObject(0).asInstanceOf[Int]
// val end: Option[Int] = None // Not used
truth.set(parseDouble(truthObject(1)))
Map[Time, Truth](Time(start, config.duration) -> truth)
Map[Time, Truth](config.time(start) -> truth)
}
case truthObject: Map[_, _] => {
val truth: Truth = Truth()
@@ -262,10 +287,10 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
val output: mutable.Map[Time, Truth] = mutable.Map()
timeLineVector.map(tuple => {
output(Time(tuple._1, config.duration)) = truth
output(config.time(tuple._1)) = truth
if (!tuple._2.isEmpty) {
endTruth.set(None)
output(Time(tuple._2.get, config.duration)) = endTruth
output(config.time(tuple._2.get)) = endTruth
}
})
output.toMap
@@ -293,7 +318,7 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
} 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.duration)(_ => None)))
behaviourListSensor(group) = Behaviour(BehaviourVector(Vector.tabulate(config.getDuration)(_ => None)))
}
if(group.parent.isEmpty) {
groupDone.add(group)

View File

@@ -15,7 +15,7 @@ class Simulation(config: Configuration) {
def start(): Unit = {
val duration = config.getDuration
val timeVector = Vector.tabulate(duration)(t => Time(t, duration))
val timeVector = Vector.tabulate(duration)(t => Time(t, duration, Some(config.getTickFactor)))
def simTime(time: Time): Unit = {
// FIXME: really shouldn't have to .map(kv => kv._1 -> kv._2) to get an immutable map, in fact it shouldn't even be mutable in the first place...
val prevTruths: scala.collection.immutable.Map[Cell, Map[Group, Truth]] = if(time.start) sharedMemory.groundTruth(time).map(kv => kv._1 -> kv._2) else sharedMemory.groundTruth(time.prev).map(kv => kv._1 -> kv._2)

View File

@@ -12,15 +12,31 @@ import scala.util.control.NonFatal
// Note: case classes implement equality and compare their values and not the memory address
// It only compares vals from the constructor
case class Time(time: Int, duration: Int) extends Ordered[Time]{
def prev: Time = { Time(time - 1, duration) }
case class Time(time: Int, duration: Int, tickfactor: Option[Int] = None) extends Ordered[Time]{
def prev: Time = Time(time - 1, duration, tickfactor)
def prevTickfactor = Time(time - 1 * tickfactor.getOrElse(1), duration, tickfactor)
def next: Time = Time(time + 1, duration, tickfactor)
def nextTickfactor = Time(time + 1 * tickfactor.getOrElse(1), duration, tickfactor)
def start: Boolean = { time == 0 }
def get: Int = { time }
def getNormalized: Int = time / tickfactor.getOrElse(1)
def getNormalized(collision: Option[Int] = None): Option[Int] = {
if((time % tickfactor.getOrElse(1) )== 0) Some(time / tickfactor.getOrElse(1)) else collision
}
def newTime(t: Int, normalized: Boolean = true): Time = if(normalized) Time(t * tickfactor.getOrElse(1), duration, tickfactor) else Time(t, duration, tickfactor)
override def equals(obj: Any): Boolean = {
obj match {
case that: Time => time.equals(that.time)
case that: Int => time.equals(that * tickfactor.getOrElse(1))
case _ => false
}
}
override def compare(that: Time): Int = {
time.compare(that.time)
}
override def hashCode: Int = time.hashCode()
}
// while it would seem convinient to just throw the sensors/behaviours as value into these
@@ -81,6 +97,11 @@ case class SimulationConfig(duration: Int, runs: Int, tickfactor: Int = 1,
val cellThreadPool: ForkJoinTaskSupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(parCellThreads))
val groupThreadPool: ForkJoinTaskSupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(parGroupThreads))
val sensorThreadPool: ForkJoinTaskSupport = new ForkJoinTaskSupport(new scala.concurrent.forkjoin.ForkJoinPool(parSensorThreads))
def time(t: Int, useTickfactor: Boolean = true): Time = {
if (useTickfactor) Time(t * tickfactor, getDuration * tickfactor, Some(tickfactor))
else Time(t, getDuration * tickfactor, Some(tickfactor))
}
def getDuration: Int = duration * tickfactor
}
case class SensorResult() extends Result[(Group, Cell, Option[Double])]
/* rough size estimation
@@ -105,6 +126,18 @@ case class Cell(x: Int, y: Int) {
def distance(otherCell: Cell): Double = {
Math.sqrt(Math.pow(x - otherCell.x, 2) + Math.pow(y - otherCell.y, 2))
}
def block(otherCell: Cell): Vector[Cell] = {
val bottomLeft = Cell(math.min(x, otherCell.x), math.min(y, otherCell.y))
val topRight = Cell(math.max(x, otherCell.x), math.max(y, otherCell.y))
// add + 1 as topright is included in the block
val xCount = topRight.x - bottomLeft.x + 1
val yCount = topRight.y - bottomLeft.y + 1
Vector.tabulate(yCount)(yCoord => {
Vector.tabulate(xCount)(xCoord => {
Cell(xCoord + bottomLeft.x, yCoord + bottomLeft.y)
})
}).flatten
}
}
case class Group(name: String, sensorParent: Option[String] = None){
// the sensorParent serves as indicator for the parser that a ground truth is not required
@@ -130,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))
@@ -195,7 +228,7 @@ case class SensorCount(count: Int) {
// NOTE: The Group is actually obsolete in the Tuple3 as Sensor and GroundTruth hold theirs (unless removed from their case class)
case class Configuration(cells: Map[Cell, Map[Group, List[(Group, Sensor, Option[GroundTruth])]]], sharedMemory: SharedMemory) extends SimConfig {
def getDuration: Int = sharedMemory.config.duration
def getDuration: Int = sharedMemory.config.duration * sharedMemory.config.tickfactor
def getRuns: Int = sharedMemory.config.runs
def getTickFactor: Int = sharedMemory.config.tickfactor
def getCellThreadConfig: (Int, ForkJoinTaskSupport) = (sharedMemory.config.parCell, sharedMemory.config.cellThreadPool)

View File

@@ -34,6 +34,10 @@ package object configInterfaces {
def getDuration: Int
def getRuns: Int
def getTickFactor: Int
def newTime(t: Int, tickfactor: Boolean = true): Time = {
if (tickfactor) Time(t * getTickFactor, getDuration * getTickFactor, Some(getTickFactor))
else Time(t, getDuration * getTickFactor, Some(getTickFactor))
}
def getCellThreadConfig: (Int, ForkJoinTaskSupport)
def getGroupThreadConfig: (Int, ForkJoinTaskSupport)
def getSensorThreadConfig: (Int, ForkJoinTaskSupport)

View File

@@ -152,6 +152,18 @@ package object mapUtil {
}
}
def mergeMaps[A, B](mapA: Map[A, B], mapB: Map[A, B], add: (B, B) => B, default: B): Map[A, B] = {
mapA ++ mapB.map(e => e._1 -> add(e._2, mapA.getOrElse(e._1, default)))
}
def mergeMaps[A, B](vector: Vector[Map[A, B]], add: (B, B) => B, default: B): Map[A, B] = {
if(vector.length > 1) {
mergeMaps(vector.head, mergeMaps(vector.tail, add, default), add, default)
} else {
vector.head
}
}
def traversableToMap[A, B](traversable: Traversable[Any], getTuple: (Any) => (A, B) = (e: Any) => e.asInstanceOf[(A, B)]._1 -> e.asInstanceOf[(A, B)]._2): Map[A, B] = {
// Traversable[Tuple[A, B]] can be converted via .map
// .foldLeft used in favor of .map().toMap as the latter needs two traversals
@@ -427,3 +439,29 @@ package object parserUtil {
}
}
}
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()
}
}
}