[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:
2020-01-29 01:19:10 -06:00
parent bd22a441f0
commit fee44785d4
3 changed files with 90 additions and 55 deletions

View File

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

View File

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

View File

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