[mod][fix] modified the sim to not pass the entire shared memory
- [mod] this change should be faster at the cost of memory - each underlying call only gets the required data instead of everything - [mod] renamed Group param to better reflect its purpose - [mod] changed Time to incorporate the total duration as well - [fix] fixed shared memory mapping to match simulation
This commit is contained in:
@@ -194,7 +194,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(Some(truthObject(1).asInstanceOf[Double]))
|
||||
Map[Time, Truth](Time(start) -> truth)
|
||||
Map[Time, Truth](Time(start, config.duration) -> truth)
|
||||
}
|
||||
case truthObject: Map[_, _] => {
|
||||
val truth: Truth = Truth(new BroadcastObject())
|
||||
@@ -206,10 +206,10 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
|
||||
|
||||
val output: mutable.Map[Time, Truth] = mutable.Map()
|
||||
timeLineVector.map(tuple => {
|
||||
output(Time(tuple._1)) = truth
|
||||
output(Time(tuple._1, config.duration)) = truth
|
||||
if (!tuple._2.isEmpty) {
|
||||
endTruth.set(None)
|
||||
output(Time(tuple._2.get)) = endTruth
|
||||
output(Time(tuple._2.get, config.duration)) = endTruth
|
||||
}
|
||||
})
|
||||
output.toMap
|
||||
|
||||
@@ -1,56 +1,82 @@
|
||||
package scim.components
|
||||
|
||||
import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, GroundTruth, Group, Sensor, Time}
|
||||
import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, GroundTruth, Group, Sensor, SensorCount, SensorResult, Time, Truth}
|
||||
import scim.lib.simulation.SimulationBehaviour
|
||||
|
||||
class Simulation(config: Configuration) {
|
||||
private val sharedMemory = config.sharedMemory
|
||||
private val sharedConfig = sharedMemory.config
|
||||
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 duration = sharedConfig.duration
|
||||
val timeVector = Vector.tabulate(duration)(t => Time(t, duration))
|
||||
def simTime(time: Time): Unit = {
|
||||
val prevTruths = if(time.start) sharedMemory.groundTruth(time) else sharedMemory.groundTruth(time.prev)
|
||||
val resultTruths = sharedMemory.groundTruth(time)
|
||||
val sensorResults = sharedMemory.sensor(time)
|
||||
simCells(time, prevTruths, resultTruths, sensorResults)
|
||||
}
|
||||
timeVector.foreach(simTime)
|
||||
}
|
||||
|
||||
private def simCells(time: Time, prevTruths: Map[Cell, Map[Group, Truth]], resultTruths: Map[Cell, Map[Group, Truth]], sensorResults: Map[Cell, Map[Group, Vector[SensorResult]]]) : Unit = {
|
||||
// Instead of using foreach, we could map and have the result returned here and write it into the shared memory
|
||||
// This would possibly result in less writes into the shared memory and allows just to return their result
|
||||
// the previous ground truth result would have to be passed into the map function, this approach
|
||||
// would fit better with functional programming than the current
|
||||
def simCell(input: (Cell, Map[Group, List[(Group, Sensor, Option[GroundTruth])]])): Unit ={
|
||||
val cell = input._1
|
||||
val groups = input._2
|
||||
simGroups(time, cell, groups, prevTruths(cell), resultTruths(cell), sensorResults(cell))
|
||||
}
|
||||
if(sharedConfig.parCell != 0 && cells.size >= sharedConfig.parCell ) {
|
||||
val cellList = cells.par
|
||||
cellList.tasksupport = sharedMemory.config.cellThreadPool
|
||||
timeVector.foreach(time => cellList.foreach(cell => simGroups(time, cell._1, cell._2)))
|
||||
cellList.tasksupport = sharedConfig.cellThreadPool
|
||||
cellList.foreach(simCell)
|
||||
} else {
|
||||
timeVector.foreach(time => cells.foreach(cell => simGroups(time, cell._1, cell._2)))
|
||||
cells.foreach(simCell)
|
||||
}
|
||||
}
|
||||
|
||||
private def simGroups(time: Time, cell: Cell, groups: Map[Group, List[(Group, Sensor, Option[GroundTruth])]]): Unit = {
|
||||
private def simGroups(time: Time, cell: Cell, groups: Map[Group, List[(Group, Sensor, Option[GroundTruth])]], prevTruths: Map[Group, Truth], resultTruths: Map[Group, Truth], resultSensors: Map[Group, Vector[SensorResult]]): 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 ) {
|
||||
// :
|
||||
// only run ground truth in list, have sensors in their respective groups
|
||||
// requires groups to be split into two elements: sensor and ground truth
|
||||
// Tuple2(Map[Group, list[(Group, Option[GroundTruth])]], Map[Group, Sensor])
|
||||
def simGroup(group: (Group, List[(Group, Sensor, Option[GroundTruth])])): Unit = {
|
||||
group._2.foreach(element => simulate(time, cell, prevTruths(group._1), resultTruths(group._1), resultSensors(group._1), element))
|
||||
}
|
||||
if(sharedConfig.parGroup != 0 && cells.size >= sharedConfig.parGroup ) {
|
||||
val groupList = groups.par
|
||||
groupList.tasksupport = sharedMemory.config.groupThreadPool
|
||||
groupList.foreach(group => group._2.foreach(element => simulate(time, cell, element)))
|
||||
groupList.tasksupport = sharedConfig.groupThreadPool
|
||||
groupList.foreach(simGroup)
|
||||
} else {
|
||||
groups.foreach(group => group._2.foreach(element => simulate(time, cell, element)))
|
||||
groups.foreach(simGroup)
|
||||
}
|
||||
}
|
||||
|
||||
private def simulate(time: Time, cell: Cell, element: (Group, Sensor, Option[GroundTruth])): Unit = {
|
||||
private def simulate(time: Time, cell: Cell, prevTruth: Truth, resultTruth: Truth, resultSensors: Vector[SensorResult], 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)
|
||||
groundTruth.get.simulate(cell, time, prevTruth, resultTruth)
|
||||
} 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)
|
||||
val v: Vector[Option[(SimulationBehaviour, Option[FunctionArgs])]] = Vector.fill(time.duration)(None)
|
||||
GroundTruth(group, Some(Behaviour(BehaviourVector(v)))).simulate(cell, time, prevTruth, resultTruth)
|
||||
}
|
||||
// simulate the sensor
|
||||
sensor.simulate(cell, group, time, sharedMemory)
|
||||
sensor.simulate(time, resultTruth, resultSensors)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,19 +7,31 @@ 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) }
|
||||
// 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){
|
||||
def prev: Time = { Time(time - 1, duration) }
|
||||
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)
|
||||
case class SharedMemory( sensor: Map[Time, Map[Cell, Map[Group, Vector[SensorResult]]]],
|
||||
groundTruth: Map[Time, Map[Cell, Map[Group, Truth]]],
|
||||
config: SimulationConfig, cache: Boolean = true) {
|
||||
// This could be spatially optimized to have a function to invoke a new simulation tick
|
||||
// a template of Map[SensorID, SensorResult] would have to be kept and deep copied when
|
||||
// another tick is simulated. this won't work for ground truth, as it may already be defined
|
||||
// through the configuration. it can however be deleted once it is no longer required, tho this seems
|
||||
// to be rather unbeneficial, as the space was already preallocated. Sensors however could be replaced
|
||||
// or deleted, depending if a buffer or list is used.
|
||||
|
||||
def getTruth(time: Time, cell: Cell, group: Group): Truth = {
|
||||
groundTruth(time)(cell)(group)
|
||||
}
|
||||
def getResult(time: Time): Map[SensorID, SensorResult] = {
|
||||
def getResult(time: Time, cell: Cell, group: Group): Vector[SensorResult] = {
|
||||
sensor(time)(cell)(group)
|
||||
}
|
||||
def getAllResults(time: Time): Map[Cell, Map[Group, Vector[SensorResult]]] = {
|
||||
sensor(time)
|
||||
}
|
||||
}
|
||||
@@ -48,29 +60,26 @@ 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))
|
||||
// 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){
|
||||
case class Group(name: String, sensorParent: Option[String] = None){
|
||||
// the sensorParent serves as indicator for the parser that a ground truth is not required
|
||||
// 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 = {
|
||||
var parent: Option[String] = sensorParent // have to make this mutable to reassign it during parsing
|
||||
def getParentOrGroup: Group = {
|
||||
if(parent.isEmpty) {
|
||||
this
|
||||
} else {
|
||||
Group(parent.get)
|
||||
Group(parent.get, None)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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,
|
||||
@@ -90,29 +99,29 @@ case class Behaviour(behaviour: BehaviourVector){
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
case class GroundTruth(group: Group, behaviour: Option[Behaviour]) {
|
||||
def simulate(cell: Cell, time: Time, prevTruth: Truth, result: Truth): Unit = {
|
||||
// don't need a truth, if the sensor uses another, but this should not happen
|
||||
// as the parser should not create a ground truth in this case
|
||||
if (group.sensorParent.isEmpty) {
|
||||
// only calculate a new ground truth if a behaviour is defined
|
||||
if(!behaviour.isEmpty) {
|
||||
val prevTruth = sharedMemory.getTruth(cell, time.prev, group)
|
||||
if (!behaviour.isEmpty) {
|
||||
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 ) {
|
||||
case class Sensor(group: Group, cell: Cell, count: SensorCount, behaviour: Behaviour, parSensor: Int, sensorThreadPool: ForkJoinTaskSupport) {
|
||||
def simulate(time: Time, truth: Truth, results: Vector[SensorResult]): Unit = {
|
||||
if(parSensor != 0 && parSensor <= count.count) {
|
||||
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))))
|
||||
sensorList.tasksupport = sensorThreadPool
|
||||
sensorList.foreach(i => results(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))))
|
||||
count.getVector.foreach(i => results(i).set((group, cell, behaviour.run(time, cell, truth))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user