[add] multiple commits merged
# Conflicts: # docs/wiki/config/config.yml # src/main/scala/scim/Main.scala # src/main/scala/scim/archive/Elements.scala # src/main/scala/scim/archive/GroundTruth.scala # src/main/scala/scim/datastruct/Configuration.scala # src/main/scala/scim/lib/Concurrency.scala
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
## Simulation
|
## Simulation
|
||||||
|
|
||||||
* `duration`:`int` - number of simulation ticks, after which everything will be reset to the initial configuration
|
* `duration`:`int` - number of simulation ticks, after which everything will be reset to the initial configuration
|
||||||
* `tickfactor`:`int` - used to artificially add ticks by repeating the result of a simulation tick by the given value
|
* `tickfactor`:`int` - used to artificially add ticks by repeating the calculation of a simulation tick by the given value
|
||||||
* `loop`:`bool` - loop the simulation
|
* `loop`:`bool` - loop the simulation
|
||||||
* `loopcount`:`int` - amount of loops, `0` for infinite
|
* `loopcount`:`int` - amount of loops, `0` for infinite
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ Elements:
|
|||||||
* `end`:`int` _[optional]_ - last simulation tick before this function is inactive
|
* `end`:`int` _[optional]_ - last simulation tick before this function is inactive
|
||||||
Alternative condensed of `timeline` notation as `array`: `[start, end]`
|
Alternative condensed of `timeline` notation as `array`: `[start, end]`
|
||||||
Alternative condensed of `value` notation as `array`: `[start, value or None, ...]`
|
Alternative condensed of `value` notation as `array`: `[start, value or None, ...]`
|
||||||
* `delta`:`array` _[optional]_ - function to calculate `value` of the next simulation tick, only called if no value is defined for the next tick. Result is saved as new `value` and passed again to the function for the next tick. If no function is given, the `value` remains
|
* `delta`:`array` _[optional]_ - function to calculate `value` of the next simulation tick, overwrites any existing value. Result is saved as new `value` and passed again to the function for the next tick. If no function is given, the `value` remains
|
||||||
* `name`:`String` - name of the function. Needs to exist at compile time in a map: `["name" -> (value: double, parentValue: double, args: ...) => double]`
|
* `name`:`String` - name of the function. Needs to exist at compile time in a map: `["name" -> (value: double, parentValue: double, args: ...) => double]`
|
||||||
* `args`:`array` - arguments passed to the function in addition to the `value`
|
* `args`:`array` - arguments passed to the function in addition to the `value`
|
||||||
* `timeline`:`array` - sets simulation tick when this function is active and turns inactive.
|
* `timeline`:`array` - sets simulation tick when this function is active and turns inactive.
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
simulation:
|
simulation:
|
||||||
duration: 500
|
duration: 500
|
||||||
tickfactor: 10
|
tickfactor: 10
|
||||||
loop: true
|
runs: 20
|
||||||
loopcount: 20
|
parCell: 0
|
||||||
|
parCellThreads: 5
|
||||||
|
parGroup: 0
|
||||||
|
parGroupThreads: 5
|
||||||
|
parSensor: 0
|
||||||
|
parSensorThreads: 5
|
||||||
|
|
||||||
|
|
||||||
sensors:
|
sensors:
|
||||||
- group: temperature
|
- group: temperature
|
||||||
@@ -16,27 +22,27 @@ sensors:
|
|||||||
end: [3, 3]
|
end: [3, 3]
|
||||||
count: 30
|
count: 30
|
||||||
|
|
||||||
groundtruth:
|
- group: faultyTemp
|
||||||
|
parent: temperature
|
||||||
|
cells:
|
||||||
|
- [1, 2, 10]
|
||||||
|
|
||||||
|
groundtruths:
|
||||||
- group: temperature
|
- group: temperature
|
||||||
truth:
|
truth:
|
||||||
- value: 11
|
- value: 11.0
|
||||||
timeline:
|
timeline:
|
||||||
- start: 0
|
- start: 0
|
||||||
end: 120
|
end: 120
|
||||||
- [240, 360]
|
- [240, 360]
|
||||||
delta:
|
delta:
|
||||||
- name: factor
|
- name: factor
|
||||||
args:
|
args: {factor: 1.1}
|
||||||
- 1.1
|
|
||||||
timeline:
|
timeline:
|
||||||
- start: 60
|
- start: 20
|
||||||
end: 120
|
end: 95
|
||||||
cells:
|
- [170, randomValue, {arg: true}]
|
||||||
- [0, 2, 10]
|
|
||||||
blocks:
|
|
||||||
- start: [8, 1]
|
|
||||||
end: [3, 3]
|
|
||||||
count: 50
|
|
||||||
- group: humidity
|
- group: humidity
|
||||||
parent: temperature
|
parent: temperature
|
||||||
truth:
|
truth:
|
||||||
@@ -44,27 +50,18 @@ groundtruth:
|
|||||||
- [30, 0.6]
|
- [30, 0.6]
|
||||||
- [50, None]
|
- [50, None]
|
||||||
delta:
|
delta:
|
||||||
- [10, None, []]
|
- [10, None]
|
||||||
- [30, addValuePercent, [0.03, 0.8]]
|
- [30, addValuePercent, {jitterA: 0.03, jitterB: 0.8}]
|
||||||
- [50, humidFromTemp, []]
|
- [50, humidFromTemp]
|
||||||
blocks:
|
|
||||||
- start: [15, 15]
|
|
||||||
end: [17, 17]
|
|
||||||
percent: 0.8
|
|
||||||
|
|
||||||
behaviour:
|
behaviours:
|
||||||
- group: temperature
|
- group: temperature
|
||||||
sharedObject: objectTemp
|
sharedObject: objectTemp
|
||||||
functions:
|
functions:
|
||||||
- name: randomValue
|
- name: randomValue
|
||||||
args:
|
args: {a: 40, b: 80}
|
||||||
- 40
|
timeline:
|
||||||
- 80
|
- start: 44
|
||||||
duration:
|
end: 166
|
||||||
- start: 60
|
- [100, normalValue, {v: 2}]
|
||||||
- [100, normalValue, [2]]
|
- [120, addValuePercent, {}]
|
||||||
- [120, None, []]
|
|
||||||
blocks:
|
|
||||||
- start: [5,6]
|
|
||||||
end: [8,9]
|
|
||||||
percent: 1.0
|
|
||||||
@@ -4,6 +4,7 @@ package lib
|
|||||||
// java imports
|
// java imports
|
||||||
import java.io.{File, FileInputStream}
|
import java.io.{File, FileInputStream}
|
||||||
|
|
||||||
|
|
||||||
// scala imports
|
// scala imports
|
||||||
import scala.collection.convert.wrapAll._
|
import scala.collection.convert.wrapAll._
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
@@ -13,13 +14,13 @@ import scala.collection.JavaConverters._
|
|||||||
import org.yaml.snakeyaml.Yaml
|
import org.yaml.snakeyaml.Yaml
|
||||||
|
|
||||||
package object printUtil {
|
package object printUtil {
|
||||||
def indentCollection(s: String) : String = {
|
def indentCollection(s: String): String = {
|
||||||
var indent = 0
|
var indent = 0
|
||||||
var skip = false
|
var skip = false
|
||||||
var bracket = false
|
var bracket = false
|
||||||
var output = ""
|
var output = ""
|
||||||
|
|
||||||
def closureIndentCollection(c: Char) : Unit = {
|
def closureIndentCollection(c: Char): Unit = {
|
||||||
if(c == '(') {
|
if(c == '(') {
|
||||||
indent += 1
|
indent += 1
|
||||||
output += "(\n" + "\t" * indent
|
output += "(\n" + "\t" * indent
|
||||||
@@ -44,8 +45,39 @@ package object printUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package object evalUtil {
|
||||||
|
def time[A](name: String, f: => A): A = {
|
||||||
|
val t0 = System.nanoTime()
|
||||||
|
val result = f
|
||||||
|
val t1 = System.nanoTime()
|
||||||
|
println(name + " Elapsed time: " + (t1 - t0) + "ns")
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package object debugUtil {
|
||||||
|
sealed case class DebugType(name: String, level: Int)
|
||||||
|
|
||||||
|
object DebugType {
|
||||||
|
object ERROR extends DebugType("ERROR", 0)
|
||||||
|
object WARNING extends DebugType("WARNING", 1)
|
||||||
|
object MESSAGE extends DebugType("MESSAGE", 2)
|
||||||
|
val values = Seq(ERROR, WARNING, MESSAGE)
|
||||||
|
}
|
||||||
|
val level: Int = DebugType.ERROR.level
|
||||||
|
|
||||||
|
def currentMethodName: String = Thread.currentThread.getStackTrace()(2).getMethodName
|
||||||
|
def currentMethodCallerName: String = Thread.currentThread.getStackTrace()(3).getMethodName
|
||||||
|
private def debugPrefix(dbg: DebugType): String = {
|
||||||
|
dbg.name + " [" + Thread.currentThread.getStackTrace()(4).getMethodName + "]: "
|
||||||
|
}
|
||||||
|
def debug(dbg: DebugType, x: Any): Unit = if(level <= dbg.level) print(debugPrefix(dbg) + x)
|
||||||
|
|
||||||
|
def debugf(dbg: DebugType, text: String, xs: Any*): Unit = if(level <= dbg.level) printf(debugPrefix(dbg) + text, xs: _*)
|
||||||
|
}
|
||||||
|
|
||||||
package object convertUtil {
|
package object convertUtil {
|
||||||
def javaArrayListToSeq(javaArray: Any) : Option[Seq[Any]] = {
|
def javaArrayListToSeq(javaArray: Any): Option[Seq[Any]] = {
|
||||||
javaArray match {
|
javaArray match {
|
||||||
/* While LinearSeq(List) for array.length == 2 is faster than than IndexedSeq(Vector) when accessed via tail/head
|
/* While LinearSeq(List) for array.length == 2 is faster than than IndexedSeq(Vector) when accessed via tail/head
|
||||||
* this would convert a lot of elements to List that are accessed via foreach and not tail/head lookup
|
* this would convert a lot of elements to List that are accessed via foreach and not tail/head lookup
|
||||||
@@ -61,13 +93,19 @@ package object convertUtil {
|
|||||||
|
|
||||||
package object mapUtil {
|
package object mapUtil {
|
||||||
|
|
||||||
def javaHashMapToScalaMap[K, V](javaMap: java.util.LinkedHashMap[_, _]) : Map[K, V] = {
|
def traversableToMap[A, B](traversable: Traversable[Any], getTuple: (Any) => (A, B)): 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
|
||||||
|
// traversable.map(e => getKey(e) -> getValue(e)).toMap
|
||||||
|
traversable.foldLeft(Map[A, B]()) { (m, e) => m + getTuple(e) }
|
||||||
|
}
|
||||||
|
def javaHashMapToScalaMap[K, V](javaMap: java.util.LinkedHashMap[_, _]): Map[K, V] = {
|
||||||
mapAsScalaMap(javaMap).toMap.asInstanceOf[Map[K, V]]
|
mapAsScalaMap(javaMap).toMap.asInstanceOf[Map[K, V]]
|
||||||
}
|
}
|
||||||
|
|
||||||
def anyToNestedMap[K](map: Any, depthLimit: Int = 100) : Map[K, Any] = {
|
def anyToNestedMap[K](map: Any, depthLimit: Int = 100): Map[K, Any] = {
|
||||||
|
|
||||||
def closureAnyToNestedMap(nestedValue: Any, depth: Int = 0) : Any = {
|
def closureAnyToNestedMap(nestedValue: Any, depth: Int = 0): Any = {
|
||||||
if(depth > depthLimit) return nestedValue // crude way to avoid a circular map
|
if(depth > depthLimit) return nestedValue // crude way to avoid a circular map
|
||||||
anyToMap[K, Any](nestedValue) match {
|
anyToMap[K, Any](nestedValue) match {
|
||||||
case Some(nestedMap) => nestedMap.map(kv => kv._1 -> closureAnyToNestedMap(kv._2, depth + 1))
|
case Some(nestedMap) => nestedMap.map(kv => kv._1 -> closureAnyToNestedMap(kv._2, depth + 1))
|
||||||
@@ -85,7 +123,7 @@ package object mapUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def anyToMutableMap[K, V](mapAny: Any) : Option[mutable.Map[K, V]] = {
|
def anyToMutableMap[K, V](mapAny: Any): Option[mutable.Map[K, V]] = {
|
||||||
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
||||||
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
||||||
mapAny match {
|
mapAny match {
|
||||||
@@ -96,7 +134,7 @@ package object mapUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def anyToMap[K, V](mapAny: Any) : Option[Map[K, V]] = {
|
def anyToMap[K, V](mapAny: Any): Option[Map[K, V]] = {
|
||||||
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
// TODO: use TypeTag to work around type erasure to check for T[K, V]
|
||||||
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
// http://squidarth.com/scala/types/2019/01/11/type-erasure-scala.html
|
||||||
mapAny match {
|
mapAny match {
|
||||||
@@ -112,9 +150,14 @@ package object mapUtil {
|
|||||||
package object yml {
|
package object yml {
|
||||||
val yaml = new Yaml
|
val yaml = new Yaml
|
||||||
// TODO: handle errors (invalid file/format)
|
// TODO: handle errors (invalid file/format)
|
||||||
def parse(file:String) : Map[String, Any] = {
|
def parse(file:String): Map[String, Any] = {
|
||||||
val fis = new FileInputStream(new File(file))
|
val fis = new FileInputStream(new File(file))
|
||||||
mapUtil.anyToNestedMap[String](yaml.load(fis))
|
mapUtil.anyToNestedMap[String](yaml.load(fis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
package object simulation {
|
||||||
|
import scim.datastruct._
|
||||||
|
// maybe change to by-name parameters? have to check performance gain
|
||||||
|
type SimulationBehaviour = (Cell, Time, Option[Double], Option[FunctionArgs]) => Option[Double]
|
||||||
|
}
|
||||||
@@ -1,12 +1,48 @@
|
|||||||
package scim
|
package scim
|
||||||
|
|
||||||
|
import scim.components.{Parser, SimulationConfigParser, Output, Simulation}
|
||||||
|
import scim.datastruct.{Cell, FunctionArgs, SharedMemory, SimulationBehaviours, Time}
|
||||||
import scim.lib._
|
import scim.lib._
|
||||||
|
import scim.lib.simulation.SimulationBehaviour
|
||||||
|
//package scim.components._
|
||||||
object Main {
|
object Main {
|
||||||
|
|
||||||
|
val func: (Cell, Time, Option[Double], Option[FunctionArgs]) => Option[Double] = (cell, time, truth, args) => { truth }
|
||||||
|
val behaviours: Map[String, SimulationBehaviour] = Map[String, SimulationBehaviour](
|
||||||
|
"factor" -> func,
|
||||||
|
"None" -> func, "addValuePercent" -> func,
|
||||||
|
"humidFromTemp" -> func,
|
||||||
|
"normalValue" -> func,
|
||||||
|
"randomValue" -> func)
|
||||||
|
val simBehavirous = SimulationBehaviours(behaviours)
|
||||||
|
|
||||||
def main(args: Array[String]): Unit = {
|
def main(args: Array[String]): Unit = {
|
||||||
val config = "docs/wiki/config/config.yml"
|
val config = "docs/wiki/config/config.yml"
|
||||||
val list: Map[String, Any] = yml.parse(config)
|
val list: Map[String, Any] = yml.parse(config)
|
||||||
|
|
||||||
println(printUtil.indentCollection(list.toString()).toString)
|
new SimulationConfigParser(list, simBehavirous).parse()
|
||||||
|
// println(printUtil.indentCollection(list.toString()).toString)
|
||||||
|
//new Test2().run()
|
||||||
|
val parlist: Map[String, Int] = Map("a" -> 1, "b" -> 1, "c" -> 1, "d" -> 1)
|
||||||
|
val v: Vector[Int] = Vector(1,2,3,4,5,6,7)
|
||||||
|
|
||||||
|
//lib.evalUtil.time("par", parlist.par.map({_ => Thread.sleep(10); 2}))
|
||||||
|
//lib.evalUtil.time("ser", parlist.map({_ => Thread.sleep(10); 2}))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
def startSimulation(): Unit = {
|
||||||
|
val config = new Parser().parse("docs/wiki/config/config.yml", simBehavirous).get
|
||||||
|
val sharedMemory = config.sharedMemory
|
||||||
|
val output = new Output(sharedMemory)
|
||||||
|
val outputThread = new Thread(output)
|
||||||
|
val simulator = new Simulation(config)
|
||||||
|
|
||||||
|
for(_ <- 0 to sharedMemory.config.runs) {
|
||||||
|
outputThread.start()
|
||||||
|
simulator.start()
|
||||||
|
outputThread.join()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
19
src/main/scala/scim/archive/Elements.scala
Normal file
19
src/main/scala/scim/archive/Elements.scala
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package scim.archive
|
||||||
|
|
||||||
|
// child elements level 2
|
||||||
|
case class Coordinate(x: Int, y: Int)
|
||||||
|
case class FunctionArgs(args: Map[String, Any])
|
||||||
|
case class FunctionId(id: Int, name: String)
|
||||||
|
case class TimeLine(start: Int, end: Option[Int])
|
||||||
|
|
||||||
|
// child elements level 1
|
||||||
|
case class Group(id: Int, name: String)
|
||||||
|
case class Block(start: Coordinate, end: Coordinate, count: Int)
|
||||||
|
case class Truth(value: Option[Double], timeline: TimeLine)
|
||||||
|
case class Function(function: FunctionId, args: Option[FunctionArgs], timeLine: TimeLine)
|
||||||
|
|
||||||
|
// root elements
|
||||||
|
case class Simulation(duration: Int, tickfactor: Int, loop: Boolean, loopcount: Int)
|
||||||
|
case class Sensor(group: Group, blocks: Vector[Block])
|
||||||
|
case class GroundTruth(group: Group, parent: Option[GroundTruth], truths: Vector[Truth], deltas: Option[Vector[Function]])
|
||||||
|
case class Behaviour(group: Group, parent: Option[Behaviour], functions: Option[Vector[Function]])
|
||||||
27
src/main/scala/scim/archive/GroundTruth.scala
Normal file
27
src/main/scala/scim/archive/GroundTruth.scala
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package scim.archive
|
||||||
|
|
||||||
|
case class Handler(config: Configuration, data: Data)
|
||||||
|
case class BufferHandler() {
|
||||||
|
|
||||||
|
}
|
||||||
|
case class Data() {
|
||||||
|
//val buffer = Map[Position, (BufferHandler, ArraySeq[(Time, Truth)])]()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//object SimBuffer {
|
||||||
|
// def apply[A](v: A*): SimBuffer[A] = new SimBuffer(v)
|
||||||
|
// def apply[A](): SimBuffer[A] = new SimBuffer[A]()
|
||||||
|
//}
|
||||||
|
|
||||||
|
//class SimBuffer[A] (buffer: ArraySeq[A], handler: BufferHandler) {
|
||||||
|
// var bufferd: Int = 1
|
||||||
|
// def get(time: Time): A = {
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
case class Configuration()
|
||||||
|
|
||||||
|
case class GroundTruth(handler: Handler, config: Configuration, data: Data)
|
||||||
|
|
||||||
|
case class GroundTruths(groundTruths: Map[Group, GroundTruth])
|
||||||
21
src/main/scala/scim/archive/Simulation.scala
Normal file
21
src/main/scala/scim/archive/Simulation.scala
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package scim.datastruct
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
//case class Coordinate(x: Int, y: Int)
|
||||||
|
//case class World(size: Int) // implements trait def getPosition(coord: Coordinate): Position; def getCoordinate(pos: Position): Coordinate
|
||||||
|
//case class Position(pos: Int)
|
||||||
|
//case class Time(tick: Int)
|
||||||
|
//case class Truth(truth: Option[Double])
|
||||||
|
//case class FunctionArgs(args: Map[String, Any])
|
||||||
|
//case class Function(function: (Truth, Position, Time, FunctionArgs) => Option[Double], args: FunctionArgs)
|
||||||
|
//case class Group(id: Int, name: String)
|
||||||
|
//
|
||||||
|
//case class GroundTruths(truth: Map[Group, mutable.ArraySeq[(Time, Truth)]]) // implements trait def getTruth(group: Group, pos: Position): Option[Double]
|
||||||
|
//case class Result(truth: Map[Group, List[(Time, Truth)]]) // implements trait def getTruth(group: Group, pos: Position): Option[Double]
|
||||||
|
//case class Behaviours(function: Map[Group, List[(Time, Option[Function])]]) // implement trait def run(truth: Truth, pos: Position, time: Time): Option[Double]
|
||||||
|
//case class Sensor(group: Group, count: Int)
|
||||||
|
//case class Cells(cells: Map[Position, Vector[Sensor]])
|
||||||
|
//case class Sensors(sensors: Map[Group, Vector[(Position, Int)]]) // implement trait def sim(time: Time): Option[Double]
|
||||||
|
//case class Simulation(groundTruths: GroundTruths, behaviours: Behaviours, cells: Cells)
|
||||||
|
|
||||||
13
src/main/scala/scim/components/Output.scala
Normal file
13
src/main/scala/scim/components/Output.scala
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package scim.components
|
||||||
|
|
||||||
|
import scim.datastruct.SharedMemory
|
||||||
|
|
||||||
|
class Output(sharedMemory: SharedMemory) extends Runnable {
|
||||||
|
def run(): Unit = {
|
||||||
|
outputData()
|
||||||
|
}
|
||||||
|
|
||||||
|
def outputData(): Unit = {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
226
src/main/scala/scim/components/Parser.scala
Normal file
226
src/main/scala/scim/components/Parser.scala
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package scim.components
|
||||||
|
|
||||||
|
import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, Group, SensorCount, SharedMemory, SimulationBehaviours, SimulationConfig, Time, Truth}
|
||||||
|
import scim.lib.ConcurrentBroadcast.BroadcastObject
|
||||||
|
import scim.lib.debugUtil.{DebugType, debugf}
|
||||||
|
import scim.lib.mapUtil.traversableToMap
|
||||||
|
import scim.lib.simulation.SimulationBehaviour
|
||||||
|
import scim.lib.yml
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
class Parser() {
|
||||||
|
def parse(file: String, simulationBehaviours: SimulationBehaviours): Option[Configuration] = {
|
||||||
|
val parser = new SimulationConfigParser(yml.parse(file), simulationBehaviours)
|
||||||
|
parser.parse()
|
||||||
|
None // FIXME: this should return something
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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[Group, Map[Cell, 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(): Unit = {
|
||||||
|
|
||||||
|
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
|
||||||
|
Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cells: Map[Cell, SensorCount] = parseCells(sensor("cells").asInstanceOf[Vector[Any]])
|
||||||
|
val group = Group(groupString, parentString)
|
||||||
|
if(!groups.contains(groupString)) {
|
||||||
|
groups(groupString) = group
|
||||||
|
sensorList(group) = cells
|
||||||
|
} 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 = (cell: Any) => cell 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)
|
||||||
|
}
|
||||||
|
traversableToMap[Cell, SensorCount](cells, parseCell)
|
||||||
|
}
|
||||||
|
|
||||||
|
private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = {
|
||||||
|
timeLineElement match {
|
||||||
|
case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]]
|
||||||
|
(elementVector(1), if(timeLineElement.contains(2)) Some(elementVector(2)) 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]
|
||||||
|
// val end: Option[Int] = None // Not used
|
||||||
|
val name: String = function(1).asInstanceOf[String]
|
||||||
|
val args: Option[FunctionArgs] = if (function.contains(3)) 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)
|
||||||
|
}
|
||||||
|
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) = funcPointer
|
||||||
|
if (!tuple._2.isEmpty) output(tuple._2.get) = None
|
||||||
|
})
|
||||||
|
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
|
||||||
|
functions.map(function => parseFunctionHelper(function)).foreach(e => e.foreach(kv => 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 it = parsedFunction.toList.sortBy(_._1).iterator
|
||||||
|
println(parsedFunction.toList.sortBy(_._1))
|
||||||
|
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 else config.duration - 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())
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
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 parseTruth(truthElements: Vector[Any]): Map[Time, Truth] = {
|
||||||
|
val parseTruthHelper = (truthElement: Any) => {
|
||||||
|
truthElement match {
|
||||||
|
case truthObject: Vector[_] => {
|
||||||
|
val truth: Truth = Truth(new BroadcastObject())
|
||||||
|
println(truthElement)
|
||||||
|
val start: Int = truthObject(0).asInstanceOf[Int]
|
||||||
|
// val end: Option[Int] = None // Not used
|
||||||
|
truth.set(Some(truthObject(1).asInstanceOf[Double]))
|
||||||
|
Map[Time, Truth](Time(start) -> truth)
|
||||||
|
}
|
||||||
|
case truthObject: Map[_, _] => {
|
||||||
|
val truth: Truth = Truth(new BroadcastObject())
|
||||||
|
lazy val endTruth: Truth = Truth(new BroadcastObject())
|
||||||
|
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(Some(truthObjectMap("value").asInstanceOf[Double]))
|
||||||
|
|
||||||
|
val output: mutable.Map[Time, Truth] = mutable.Map()
|
||||||
|
timeLineVector.map(tuple => {
|
||||||
|
output(Time(tuple._1)) = truth
|
||||||
|
if (!tuple._2.isEmpty) {
|
||||||
|
endTruth.set(None)
|
||||||
|
output(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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
56
src/main/scala/scim/components/Simulation.scala
Normal file
56
src/main/scala/scim/components/Simulation.scala
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package scim.components
|
||||||
|
|
||||||
|
import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, GroundTruth, Group, Sensor, Time}
|
||||||
|
import scim.lib.simulation.SimulationBehaviour
|
||||||
|
|
||||||
|
class Simulation(config: Configuration) {
|
||||||
|
private val sharedMemory = config.sharedMemory
|
||||||
|
private val cells = config.cells
|
||||||
|
val timeVector = Vector.tabulate(sharedMemory.config.duration)(t => Time(t))
|
||||||
|
|
||||||
|
def start(): Unit = {
|
||||||
|
if(sharedMemory.config.parCell != 0 && cells.size >= sharedMemory.config.parCell ) {
|
||||||
|
val cellList = cells.par
|
||||||
|
cellList.tasksupport = sharedMemory.config.cellThreadPool
|
||||||
|
timeVector.foreach(time => cellList.foreach(cell => simGroups(time, cell._1, cell._2)))
|
||||||
|
} else {
|
||||||
|
timeVector.foreach(time => cells.foreach(cell => simGroups(time, cell._1, cell._2)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def simGroups(time: Time, cell: Cell, groups: Map[Group, List[(Group, Sensor, Option[GroundTruth])]]): Unit = {
|
||||||
|
// TODO: possible optimisation (see text below)
|
||||||
|
// this can be further optimised as a child is only dependent on its parents ground truth and could run
|
||||||
|
// at the same time as the parent in a different thread. This would allow the child to start running as soon
|
||||||
|
// as the ground truth is calculated in the parent and doesn't have have to wait for the sensors to finish.
|
||||||
|
// Any subsequent children can start in the same manner, when their parent is done with the ground truth.
|
||||||
|
// --
|
||||||
|
// other possibility:
|
||||||
|
// run simulateGT foreach first, then run simulateSensor foreach (dont know if there is much benefit tho)
|
||||||
|
if(sharedMemory.config.parGroup != 0 && cells.size >= sharedMemory.config.parGroup ) {
|
||||||
|
val groupList = groups.par
|
||||||
|
groupList.tasksupport = sharedMemory.config.groupThreadPool
|
||||||
|
groupList.foreach(group => group._2.foreach(element => simulate(time, cell, element)))
|
||||||
|
} else {
|
||||||
|
groups.foreach(group => group._2.foreach(element => simulate(time, cell, element)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private def simulate(time: Time, cell: Cell, element: (Group, Sensor, Option[GroundTruth])): Unit = {
|
||||||
|
val group = element._1
|
||||||
|
val sensor = element._2
|
||||||
|
val groundTruth = element._3
|
||||||
|
// simulate the ground truth if required
|
||||||
|
if(!groundTruth.isEmpty) {
|
||||||
|
groundTruth.get.simulate(cell,group, time, sharedMemory)
|
||||||
|
} else if (group.parent.isEmpty) {
|
||||||
|
// calc default ground truth if none was defined and no parent given which is None
|
||||||
|
// this ensures a value when the sensor accesses the gt and prevents a deadlock
|
||||||
|
val v: Vector[Option[(SimulationBehaviour, Option[FunctionArgs])]] = Vector.fill(sharedMemory.config.duration)(None)
|
||||||
|
GroundTruth(Some(Behaviour(BehaviourVector(v)))).simulate(cell, group, time, sharedMemory)
|
||||||
|
}
|
||||||
|
// simulate the sensor
|
||||||
|
sensor.simulate(cell, group, time, sharedMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
132
src/main/scala/scim/datastruct/Configuration.scala
Normal file
132
src/main/scala/scim/datastruct/Configuration.scala
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
package scim.datastruct
|
||||||
|
|
||||||
|
import scim.lib.ConcurrentBroadcast.BroadcastObject
|
||||||
|
import scim.lib.simulation._
|
||||||
|
import scala.collection.parallel.ForkJoinTaskSupport
|
||||||
|
|
||||||
|
// configuration
|
||||||
|
//case class Configuration(simulation: Simulation, sensors: Vector[Sensor], groundTruths: Vector[GroundTruth], behaviours: Vector[Behaviour])
|
||||||
|
|
||||||
|
case class Time(time: Int){
|
||||||
|
def prev: Time = { Time(time - 1) }
|
||||||
|
def start: Boolean = { time == 0 }
|
||||||
|
def get: Int = { time }
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SharedMemory( sensor: Map[Time, Map[SensorID, SensorResult]],
|
||||||
|
groundTruth: Map[Cell, Map[Time, Map[Group, Truth]]],
|
||||||
|
config: SimulationConfig) {
|
||||||
|
def getTruth(cell: Cell, time: Time, group: Group): Truth = {
|
||||||
|
groundTruth(cell)(time)(group)
|
||||||
|
}
|
||||||
|
def getResult(time: Time): Map[SensorID, SensorResult] = {
|
||||||
|
sensor(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class BroadcastHandler[A](broadcastObject: BroadcastObject[A]) {
|
||||||
|
def get: A = {
|
||||||
|
broadcastObject.watch()
|
||||||
|
}
|
||||||
|
def set(newValue: A): Unit = {
|
||||||
|
broadcastObject.broadcast(newValue)
|
||||||
|
}
|
||||||
|
def exists: Boolean = {
|
||||||
|
!broadcastObject.peak().isEmpty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case class SimulationBehaviours(functions: Map[String, SimulationBehaviour]) {
|
||||||
|
def get(name: String): Option[SimulationBehaviour] = {
|
||||||
|
if(functions.contains(name)) Some(functions(name)) else None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class SimulationConfig(duration: Int, runs: Int, tickfactor: Int = 1,
|
||||||
|
parCell: Int = 0, parCellThreads: Int = 5,
|
||||||
|
parGroup: Int = 0, parGroupThreads: Int = 5,
|
||||||
|
parSensor: Int = 0, parSensorThreads: Int = 5) {
|
||||||
|
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))
|
||||||
|
// TODO: give truth their own threadpool and parallize the calculation
|
||||||
|
}
|
||||||
|
case class SensorResult(result: BroadcastObject[(Group, Cell, Option[Double])]) extends BroadcastHandler(result)
|
||||||
|
case class SensorID(group: Group, Id: Int)
|
||||||
|
case class Duration(simDuration: Int)
|
||||||
|
case class TickFactor(factor: Int) // processed when parsing configuration, artifically place timings
|
||||||
|
case class Cell(x: Int, y: Int)
|
||||||
|
case class Group(name: String, parentIn: Option[String] = None){
|
||||||
|
// FIXME: use parent as get function and parent.set as setter to make var private
|
||||||
|
var parent: Option[String] = parentIn // have to make this mutable to reassign it during parsing
|
||||||
|
def getParentOrThis: Group = {
|
||||||
|
if(parent.isEmpty) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
Group(parent.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
case class Truth(value: BroadcastObject[Option[Double]]) extends BroadcastHandler(value)
|
||||||
|
case class FunctionArgs(args: Map[String, Any])
|
||||||
|
// passing the group and therefore the behaviour during the simulation adds additional processing costs during runtime.
|
||||||
|
// Benefit would be fewer sensor objects.
|
||||||
|
|
||||||
|
case class BehaviourVector(function: Vector[Option[(SimulationBehaviour, Option[FunctionArgs])]],
|
||||||
|
defaultFunc: SimulationBehaviour = (_: Cell, _: Time, v: Option[Double], _: Option[FunctionArgs]) => v,
|
||||||
|
defaultArgs: Option[FunctionArgs] = None){
|
||||||
|
def get(t: Time): (SimulationBehaviour, Option[FunctionArgs]) = {
|
||||||
|
function(t.time).getOrElse((defaultFunc, defaultArgs))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Behaviour(behaviour: BehaviourVector){
|
||||||
|
def run(time: Time, cell: Cell, truth: Truth): Option[Double] = {
|
||||||
|
val functionTuple = behaviour.get(time)
|
||||||
|
val function = functionTuple._1
|
||||||
|
val args = functionTuple._2
|
||||||
|
// TODO: add try catch, to prevent crash in case the args are wrong
|
||||||
|
function(cell, time, truth.get, args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class GroundTruth(behaviour: Option[Behaviour]) {
|
||||||
|
def simulate(cell: Cell, group: Group, time: Time, sharedMemory: SharedMemory): Unit = {
|
||||||
|
val result = sharedMemory.getTruth(cell, time, group.getParentOrThis)
|
||||||
|
// only calculate a new ground truth if a behaviour is defined
|
||||||
|
if(!behaviour.isEmpty) {
|
||||||
|
val prevTruth = sharedMemory.getTruth(cell, time.prev, group)
|
||||||
|
result.set(behaviour.get.run(time, cell, prevTruth))
|
||||||
|
} else {
|
||||||
|
result.set(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case class Sensor(behaviour: Behaviour, count: SensorCount) {
|
||||||
|
def simulate(cell: Cell, group: Group, time: Time, sharedMemory: SharedMemory): Unit = {
|
||||||
|
val truth = sharedMemory.getTruth(cell, time, group.getParentOrThis)
|
||||||
|
val result = sharedMemory.getResult(time)
|
||||||
|
if(sharedMemory.config.parSensor != 0 && count.count >= sharedMemory.config.parSensor ) {
|
||||||
|
val sensorList = count.getVector.par
|
||||||
|
sensorList.tasksupport = sharedMemory.config.sensorThreadPool
|
||||||
|
sensorList.foreach(i => result(SensorID(group, i)).set((group, cell, behaviour.run(time, cell, truth))))
|
||||||
|
} else {
|
||||||
|
count.getVector.foreach(i => result(SensorID(group, i)).set((group, cell, behaviour.run(time, cell, truth))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate 1 full tick at a time
|
||||||
|
// a group may depend on others within a cell
|
||||||
|
// a cell does not depend on others within a group
|
||||||
|
// a sensor and ground truth are unique within a group in a cell
|
||||||
|
case class SensorCount(count: Int) {
|
||||||
|
def getVector: Vector[Int] = {
|
||||||
|
Vector.tabulate(count)(e => e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class Configuration(cells: Map[Cell, Map[Group, List[(Group, Sensor, Option[GroundTruth])]]], sharedMemory: SharedMemory)
|
||||||
|
|
||||||
229
src/main/scala/scim/lib/Concurrency.scala
Normal file
229
src/main/scala/scim/lib/Concurrency.scala
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
package scim.lib
|
||||||
|
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.locks.{Condition, Lock, ReentrantReadWriteLock}
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
object ScalaLocks {
|
||||||
|
|
||||||
|
class ScalaReadLock(lock: Lock) {
|
||||||
|
def apply[A](read: => A): A = {
|
||||||
|
lock.lock()
|
||||||
|
try read finally {
|
||||||
|
lock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScalaWriteLock(lock: Lock) {
|
||||||
|
def apply[A](write: () => A): Unit = {
|
||||||
|
lock.lock()
|
||||||
|
try write() finally {
|
||||||
|
lock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScalaReadWriteLock(lock: Lock) {
|
||||||
|
def apply[A](readWrite: () => A): A = {
|
||||||
|
lock.lock()
|
||||||
|
try readWrite() finally {
|
||||||
|
lock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScalaConditionalReadLock(readLock: ReentrantReadWriteLock.ReadLock, writeLock: ReentrantReadWriteLock.WriteLock, signal: Condition) {
|
||||||
|
private val scalaWriteLock = new ScalaWriteLock(writeLock)
|
||||||
|
|
||||||
|
// condition and read should not change any mutable state
|
||||||
|
def apply[A](condition: => Boolean, read: => A): A = {
|
||||||
|
readLock.lock() // block writer
|
||||||
|
if(!condition) {
|
||||||
|
readLock.unlock() // unblock writer
|
||||||
|
// acquire write lock, await signal and release it
|
||||||
|
scalaWriteLock (() => {
|
||||||
|
while(!condition) {
|
||||||
|
signal.await()
|
||||||
|
}
|
||||||
|
// block the writer before releasing write lock
|
||||||
|
readLock.lock()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
try read finally {
|
||||||
|
readLock.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object ConcurrentBroadcast {
|
||||||
|
|
||||||
|
class BroadcastObject[A]() {
|
||||||
|
|
||||||
|
import ScalaLocks._
|
||||||
|
|
||||||
|
private val javaLock = new ReentrantReadWriteLock()
|
||||||
|
private val javaReadLock = javaLock.readLock()
|
||||||
|
private val javaWriteLock = javaLock.writeLock()
|
||||||
|
|
||||||
|
private val signal = javaWriteLock.newCondition()
|
||||||
|
private val conditionalReadLock = new ScalaConditionalReadLock(javaReadLock, javaWriteLock, signal)
|
||||||
|
private val readWriteLock = new ScalaReadWriteLock(javaWriteLock)
|
||||||
|
private val writeLock = new ScalaWriteLock(javaWriteLock)
|
||||||
|
private val readLock = new ScalaReadLock(javaReadLock)
|
||||||
|
|
||||||
|
private var value: Option[A] = None
|
||||||
|
|
||||||
|
// NON-BLOCKING: check if value has been set, write new value and return if it was overwritten
|
||||||
|
def broadcast(newValue: A): Unit = {
|
||||||
|
writeLock(() => {
|
||||||
|
value = Some(newValue)
|
||||||
|
signal.signalAll()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NON-BLOCKING: check if value has been set, write new value and return if it was overwritten
|
||||||
|
def broadcastOverwrite(newValue: A): Boolean = {
|
||||||
|
readWriteLock(() => {
|
||||||
|
val result = value.isEmpty
|
||||||
|
value = Some(newValue)
|
||||||
|
signal.signalAll()
|
||||||
|
result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BLOCKING: watch value, block until it is set, then return it
|
||||||
|
def watch(): A = {
|
||||||
|
conditionalReadLock(!value.isEmpty, value.get)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NON-BLOCKING: returns value as Option
|
||||||
|
def peak(): Option[A] = {
|
||||||
|
readLock {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Broadcast[A](stream: mutable.ArraySeq[BroadcastObject[A]]) extends Runnable {
|
||||||
|
override def run(): Unit = {
|
||||||
|
access(stream)
|
||||||
|
}
|
||||||
|
def access(stream: mutable.ArraySeq[BroadcastObject[A]])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait is just used to rename the function (maybe a bit redundant)
|
||||||
|
trait Watcher[A] extends Broadcast[A] {
|
||||||
|
override def access(stream: mutable.ArraySeq[BroadcastObject[A]]): Unit = { watch(stream) }
|
||||||
|
def watch(stream: mutable.ArraySeq[BroadcastObject[A]])
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Broadcaster[A] extends Broadcast[A] {
|
||||||
|
override def access(stream: mutable.ArraySeq[BroadcastObject[A]]): Unit = { broadcast(stream) }
|
||||||
|
def broadcast(stream: mutable.ArraySeq[BroadcastObject[A]])
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Test2 {
|
||||||
|
import scim.lib.ConcurrentBroadcast._
|
||||||
|
|
||||||
|
private val stream: mutable.ArraySeq[BroadcastObject[Int]] = new mutable.ArraySeq[Int](5).map(_ => new BroadcastObject[Int])
|
||||||
|
|
||||||
|
class Producer(stream: mutable.ArraySeq[BroadcastObject[Int]]) extends Broadcast[Int](stream) with Broadcaster[Int] {
|
||||||
|
def a(): Unit = {
|
||||||
|
stream.foreach(element => {element.broadcast(1); Thread.sleep(1000)})
|
||||||
|
}
|
||||||
|
|
||||||
|
override def broadcast(stream: mutable.ArraySeq[BroadcastObject[Int]]): Unit = {
|
||||||
|
val f: (Int, Int) => Int = (x, y) => x + y
|
||||||
|
var previous: Int = 0
|
||||||
|
stream.foreach(element => {
|
||||||
|
previous = f(previous, 2)
|
||||||
|
element.broadcast(previous)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Watcher(id: Int, stream: mutable.ArraySeq[BroadcastObject[Int]]) extends Runnable {
|
||||||
|
def run(): Unit = {
|
||||||
|
stream.foreach(element => { Thread.sleep(id); println(id + ": " + element.watch() + " - " + Calendar.getInstance().getTime()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def run(): Unit = {
|
||||||
|
val threadPool = Executors.newFixedThreadPool(4)
|
||||||
|
val producer = new Producer(stream)
|
||||||
|
val watcher = new Watcher(0,stream)
|
||||||
|
val watcher2 = new Watcher(3000,stream)
|
||||||
|
|
||||||
|
val p = new Thread(producer)
|
||||||
|
|
||||||
|
val t = new Thread(watcher)
|
||||||
|
val t2 = new Thread(watcher2)
|
||||||
|
println("start producer")
|
||||||
|
p.start()
|
||||||
|
println("start watcher")
|
||||||
|
t.start()
|
||||||
|
t2.start()
|
||||||
|
println("join producer")
|
||||||
|
p.join()
|
||||||
|
println("join watcher")
|
||||||
|
t.join()
|
||||||
|
t2.join()
|
||||||
|
println("done")
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
import scim.lib.ConcurrentBroadcast.BroadcastObject
|
||||||
|
|
||||||
|
private val stream: mutable.ArraySeq[BroadcastObject[Int]] = new mutable.ArraySeq[Int](5).map(_ => new BroadcastObject[Int])
|
||||||
|
|
||||||
|
class Producer(stream: mutable.ArraySeq[BroadcastObject[Int]]) extends Runnable {
|
||||||
|
def run(): Unit = {
|
||||||
|
stream.foreach(element => {element.broadcast(1); Thread.sleep(1000)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Watcher(id: Int, stream: mutable.ArraySeq[BroadcastObject[Int]]) extends Runnable {
|
||||||
|
def run(): Unit = {
|
||||||
|
stream.foreach(element => { Thread.sleep(id); println(id + ": " + element.watch() + " - " + Calendar.getInstance().getTime()) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def run(): Unit = {
|
||||||
|
val threadPool = Executors.newFixedThreadPool(4)
|
||||||
|
val producer = new Producer(stream)
|
||||||
|
val watcher = new Watcher(0,stream)
|
||||||
|
val watcher2 = new Watcher(3000,stream)
|
||||||
|
|
||||||
|
val p = new Thread(producer)
|
||||||
|
|
||||||
|
val t = new Thread(watcher)
|
||||||
|
val t2 = new Thread(watcher2)
|
||||||
|
println("start producer")
|
||||||
|
p.start()
|
||||||
|
println("start watcher")
|
||||||
|
t.start()
|
||||||
|
t2.start()
|
||||||
|
println("join producer")
|
||||||
|
p.join()
|
||||||
|
println("join watcher")
|
||||||
|
t.join()
|
||||||
|
t2.join()
|
||||||
|
println("done")
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user