[mod][mod] modified function to return closest keys in map into object

- two variants, one with binary search, the other with iterate
 - [mod] modified debug to closer mirror linux kernel debug
 - [mod] just added a few comments in parser and simulation
This commit is contained in:
2020-01-29 01:19:11 -06:00
parent cea02cc076
commit b640ca4a65
3 changed files with 151 additions and 43 deletions

View File

@@ -3,7 +3,7 @@ package scim.components
import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, Group, SensorCount, SharedMemory, SimulationBehaviours, SimulationConfig, Time, Truth} import scim.datastruct.{Behaviour, BehaviourVector, Cell, Configuration, FunctionArgs, Group, SensorCount, SharedMemory, SimulationBehaviours, SimulationConfig, Time, Truth}
import scim.lib.ConcurrentBroadcast.BroadcastObject import scim.lib.ConcurrentBroadcast.BroadcastObject
import scim.lib.debugUtil.{DebugType, debugf} import scim.lib.debugUtil.{DebugType, debugf}
import scim.lib.mapUtil.traversableToMap import scim.lib.mapUtil._
import scim.lib.simulation.SimulationBehaviour import scim.lib.simulation.SimulationBehaviour
import scim.lib.yml import scim.lib.yml
@@ -47,6 +47,9 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
behaviours.foreach(behaviour => parseBehaviour(behaviour)) behaviours.foreach(behaviour => parseBehaviour(behaviour))
groundTruths.foreach(groundTruth => parseGroundTruth(groundTruth)) groundTruths.foreach(groundTruth => parseGroundTruth(groundTruth))
// TODO: insert dummy sensors if none exists at cells of the parent group where this group exists // TODO: insert dummy sensors if none exists at cells of the parent group where this group exists
// TODO: when inserting time based values, only overwrite None, dont overwrite Some() with None!
// e.g. truth was declared at tick 9 with value 11, but later again with None at tick 9, DONT OVERWRITE
// e.g. truth was declared at tick 9 with value none, but later again with value 9, OVERWRITE
Unit Unit
} }
@@ -78,16 +81,19 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu
} }
private def parseCells(cells: Vector[Any]): Map[Cell, SensorCount] = { private def parseCells(cells: Vector[Any]): Map[Cell, SensorCount] = {
val parseCell = (cell: Any) => cell match { val parseCell: (Any) => (Cell, SensorCount) = (cell: Any) => cell match {
case cell: Vector[_] => val cellVector = cell.asInstanceOf[Vector[Int]] case cell: Vector[_] => val cellVector = cell.asInstanceOf[Vector[Int]]
(Cell(cellVector(0), cellVector(1)), SensorCount(cellVector(2))) (Cell(cellVector(0), cellVector(1)), SensorCount(cellVector(2)))
case cell: Map[_, _] => val cellMap = cell.asInstanceOf[Map[String, Int]] case cell: Map[_, _] => val cellMap = cell.asInstanceOf[Map[String, Int]]
(Cell(cellMap("x"), cellMap("y")), SensorCount(cellMap("count"))) (Cell(cellMap("x"), cellMap("y")), SensorCount(cellMap("count")))
case _ => throw new Exception("Invalid Cell: " + cell.toString) case _ => throw new Exception("Invalid Cell: " + cell.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) traversableToMap[Cell, SensorCount](cells, parseCell)
} }
// TODO: multiply time by tickfactor
private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = { private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = {
timeLineElement match { timeLineElement match {
case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]] case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]]

View File

@@ -26,7 +26,8 @@ class Simulation(config: Configuration) {
// Instead of using foreach, we could map and have the result returned here and write it into the shared memory // 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 // 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 // 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 // would fit better with functional programming than the current a tuple (Sensor Result, Truth Result)
// TODO: return (Map[Cell, Map[Group, Vector[SensorResult]]], Map[Cell, Map[Group, Truth]]) from subsequent functions
def simCell(input: (Cell, Map[Group, List[(Group, Sensor, Option[GroundTruth])]])): Unit ={ def simCell(input: (Cell, Map[Group, List[(Group, Sensor, Option[GroundTruth])]])): Unit ={
val cell = input._1 val cell = input._1
val groups = input._2 val groups = input._2

View File

@@ -59,9 +59,9 @@ package object evalUtil {
// FIXME: obviously need to assign something and not null // FIXME: obviously need to assign something and not null
def memSize(obj: Any): Long = { def memSize(obj: Any): Long = {
// import java.lang.instrument.Instrumentation // import java.lang.instrument.Instrumentation
// val instrumentation: Instrumentation = null // val instrumentation: Instrumentation = null
// instrumentation.getObjectSize(obj) // instrumentation.getObjectSize(obj)
0 0
} }
} }
@@ -70,21 +70,32 @@ package object debugUtil {
sealed case class DebugType(name: String, level: Int) sealed case class DebugType(name: String, level: Int)
object DebugType { object DebugType {
object ERROR extends DebugType("ERROR", 0) // Comments based on linux kernel debugging (https://elinux.org/Debugging_by_printing)
object WARNING extends DebugType("WARNING", 1) object ERROR extends DebugType("ERROR", 0) // Error occurred, simulation crashed
object MESSAGE extends DebugType("MESSAGE", 2) object WARNING extends DebugType("WARNING", 1) // A warning, meaning nothing serious by itself but might indicate problems
val values = Seq(ERROR, WARNING, MESSAGE) object NOTICE extends DebugType("NOTICE", 2) // Nothing serious, but notably nevertheless.
object INFO extends DebugType("INFO", 3) // Informational message e.g. startup information at simulation start
object DEBUG extends DebugType("DEBUG", 4) // Debug messages
val values = Seq(ERROR, WARNING, NOTICE, INFO, DEBUG)
} }
val level: Int = DebugType.ERROR.level val level: Int = DebugType.DEBUG.level
def currentMethodName: String = Thread.currentThread.getStackTrace()(2).getMethodName def currentMethodName: String = Thread.currentThread.getStackTrace()(2).getMethodName
def currentMethodCallerName: String = Thread.currentThread.getStackTrace()(3).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: _*) private def debugPrefix(dbg: DebugType): String = {
dbg.name + " [" + Thread.currentThread.getStackTrace()(5).getMethodName + "]: "
}
// TODO: implement logging to file
def debug(dbg: DebugType, x: Any): Unit = if(dbg.level <= level) println(debugPrefix(dbg) + x)
// FIXME: should try catch this, printf is finicky
// "When you are a responsible developer and create an error logging system for production"
// *facepalm*
// "and it crashes production..."
def debugf(dbg: DebugType, text: String, xs: Any*): Unit = if(dbg.level <= level) printf(debugPrefix(dbg) + text + "\n", xs: _*)
} }
package object convertUtil { package object convertUtil {
@@ -164,51 +175,141 @@ package object mapUtil {
ListMap(map.toSeq.sortBy(_._1):_*).toVector(map.size - 1) ListMap(map.toSeq.sortBy(_._1):_*).toVector(map.size - 1)
} }
sealed trait KeyPosition
object KeyPosition {
object LEFT extends KeyPosition()
object START extends KeyPosition()
object AT extends KeyPosition()
object BETWEEN extends KeyPosition()
object END extends KeyPosition()
object RIGHT extends KeyPosition()
val values = Seq(LEFT, START, AT, BETWEEN, END, RIGHT)
}
// this is just a binary search that returns the closest match // this is just a binary search that returns the closest match
private def getClosestKeysHelper[A, B](key: A, start: Int, end: Int, vector: Vector[(A, B)])(implicit ordering:Ordering[A]): ((A, B), Option[(A, B)]) = { // as this is private we dont check for sane input (start < end, vector.size >= 2)
private def getIndexOrPrev[A, B](key: A, start: Int, end: Int, vector: Vector[(A, B)])(implicit ordering:Ordering[A]): (KeyPosition, Int) = {
val mid = start + (end - start) / 2 val mid = start + (end - start) / 2
val foundKey = vector(mid)._1 val foundKey = vector(mid)._1
// dont check for equality but order // dont check for equality but order, if key found, check if its the beginning
if(ordering.compare(key, foundKey) == 0) return (vector(mid), Some(vector(mid + 1))) if(ordering.compare(key, foundKey) == 0) if(mid == 0) return (KeyPosition.START, 0) else return (KeyPosition.AT, mid)
// special case for 2 elements left // special case for 2 elements left
if((end - start) == 1) { if(mid == start) {
if(ordering.compare(key, vector(end)._1) >= 0) { if(ordering.compare(key, vector(end)._1) == 0) {
(vector(end), None) // key is at the end
// end == vector.size - 1 should not be required as we wouldnt be here otherwise
(KeyPosition.END, end)
} else if(ordering.compare(key, vector(end)._1) > 0) {
// key beyond the end
(KeyPosition.RIGHT, end)
} else if(ordering.compare(key, vector(start)._1) > 0) {
// key is between start and end
(KeyPosition.BETWEEN, start)
} else { } else {
// use getFirstElement to check if this is the beginning // key is before start
(vector(start), Some(vector(end))) // start == 0 should not be required as we wouldnt be here otherwise
} (KeyPosition.LEFT, start)
} // at (start) is caught earlier due to mid == start
} else { } else {
if (ordering.compare(key, foundKey) > 0) { if (ordering.compare(key, foundKey) > 0) {
getClosestKeysHelper(key, mid, end, vector) getIndexOrPrev(key, mid, end, vector)
} else { } else {
getClosestKeysHelper(key, start, mid, vector) getIndexOrPrev(key, start, mid, vector)
} }
} }
} }
def getClosestKeys[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): ((A, B), Option[(A, B)]) = { def getClosestKeyAndNext[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): (Option[(A, B)], Option[(A, B)]) = {
val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector
getClosestKeysHelper(key, 0, vectorMap.size - 1, vectorMap) if(vectorMap.size <= 1) if(vectorMap.size == 0) return (None, None) else return (Some(vectorMap(0)._1, vectorMap(0)._2), None)
getIndexOrPrev(key, 0, vectorMap.size - 1, vectorMap) match {
case (KeyPosition.LEFT, _) => (None, Some(vectorMap(0)))
case (KeyPosition.START, _) =>(Some(vectorMap(0)), Some(vectorMap(1)))
case (KeyPosition.AT, i) =>(Some(vectorMap(i)), Some(vectorMap(i + 1)))
case (KeyPosition.BETWEEN, i) =>(Some(vectorMap(i)), Some(vectorMap(i + 1)))
case (KeyPosition.END, i) => (Some(vectorMap(i)), None)
case (KeyPosition.RIGHT, i) =>(Some(vectorMap(i)), None)
}
} }
def getKeyOrNext[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): (A, B) = { def getPrevKeyAndClosest[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): (Option[(A, B)], Option[(A, B)]) = {
if(map.contains(key)) return (key, map(key))
val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector
val result = getClosestKeysHelper(key, 0, vectorMap.size - 1, vectorMap) if(vectorMap.size <= 1) if(vectorMap.size == 0) return (None, None) else return (None, Some(vectorMap(0)._1, vectorMap(0)._2))
if(ordering.compare(key, result._1._1)<= 0) { getIndexOrPrev(key, 0, vectorMap.size - 1, vectorMap) match {
result._1 case (KeyPosition.LEFT, _) => (None, Some(vectorMap(0)))
case (KeyPosition.START, _) =>(None, Some(vectorMap(0)))
case (KeyPosition.AT, i) =>(Some(vectorMap(i - 1)), Some(vectorMap(i)))
case (KeyPosition.BETWEEN, i) =>(Some(vectorMap(i)), Some(vectorMap(i + 1)))
case (KeyPosition.END, i) => (Some(vectorMap(i - 1)), Some(vectorMap(i)))
case (KeyPosition.RIGHT, i) =>(Some(vectorMap(i)), None)
}
}
class KeyFinder[A, B](map: Map[A, B])(implicit ordering:Ordering[A]) {
if(map.size == 0) throw new Exception("Empty Map")
val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector // FIXME: seems to hang for size > 10k (tested only in IDE)
var iterator = vectorMap.iterator
var currentKV = if(iterator.hasNext) Some(iterator.next()) else None
var nextKV = if(iterator.hasNext) Some(iterator.next()) else None
val firstElement = currentKV
private def reset(): Unit = {
iterator = vectorMap.iterator
currentKV = if(iterator.hasNext) Some(iterator.next()) else None
nextKV = if(iterator.hasNext) Some(iterator.next()) else None
}
private def shift(): Unit = {
currentKV = nextKV
nextKV = if(iterator.hasNext) Some(iterator.next()) else None
}
// returns value of key in map if it exists or the previous one. If key is smaller then the key of the first
// element, the first value is returned. This functions is optimized for random key calls.
// For sequentially incrementing key calls use getValueOrPrevIterate.
def getValueOrPrev(key: A): B = {
// quick simple checks first
if(map.contains(key)) return map(key)
if(currentKV.isEmpty) throw new Exception("Empty Map")// only happens when size == 0
if(vectorMap.size == 1) return vectorMap(0)._2
val result = getIndexOrPrev(key, 0, vectorMap.size - 1, vectorMap)
vectorMap(result._2)._2
}
// returns value of key in map if it exists or the previous one. If key is smaller then the key of the first
// element, the first value is returned. This functions is optimized for sequentially incrementing key calls.
// For random access calls use getValueOrPrev.
// Note: probably need rather large map to make this valuable vs getValueOrPrev
def getValueOrPrevIterate(key: A): B = {
// quick simple checks first
if(map.contains(key)) return map(key)
if(currentKV.isEmpty) throw new Exception("Empty Map")// only happens when size == 0
if(vectorMap.size == 1) return vectorMap(0)._2
val currentKey = currentKV.get._1
if(ordering.compare(key, currentKey) >= 0) {
// TODO: find out if scala actually does not check the second condition if first one fails and always in that order
while(!nextKV.isEmpty && ordering.compare(key, nextKV.get._1) >= 0) {
// when trying to do this with recursion (shift than call this function again) resulted in a non reproducible stack overflow
shift()
}
currentKV.get._2
} else { } else {
result._2.getOrElse(result._1) // currentKV.isEmpty catches size == 0, vectormap has at least 1 element here
if(ordering.compare(key, vectorMap(0)._1) < 0) {
vectorMap(0)._2
} else {
// out of order call to function, have to reset
scim.lib.debugUtil.debugf(debugUtil.DebugType.DEBUG, "Function called out of order, Key: %s is greater then current Key: %s", key.toString, currentKey.toString)
reset()
getValueOrPrevIterate(key)
}
}
} }
} }
def getKeyOrPrev[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): (A, B) = {
if(map.contains(key)) return (key, map(key))
val vectorMap = ListMap(map.toSeq.sortBy(_._1):_*).toVector
val result = getClosestKeysHelper(key, 0, vectorMap.size - 1, vectorMap)
result._1
}
} }
package object yml { package object yml {