diff --git a/src/main/scala/scim/components/Parser.scala b/src/main/scala/scim/components/Parser.scala index cd31857..de5ba4e 100644 --- a/src/main/scala/scim/components/Parser.scala +++ b/src/main/scala/scim/components/Parser.scala @@ -3,7 +3,7 @@ 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.mapUtil._ import scim.lib.simulation.SimulationBehaviour import scim.lib.yml @@ -47,6 +47,9 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu 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 + // 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 } @@ -78,16 +81,19 @@ class SimulationConfigParser(input: Map[String, Any], simulationBehaviours: Simu } 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]] (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 iterates over the vector while parseCell returns a key value + // pair for each element which than gets added to the returned map traversableToMap[Cell, SensorCount](cells, parseCell) } + // TODO: multiply time by tickfactor private def parseTimeLineElement(timeLineElement: Any): (Int, Option[Int]) = { timeLineElement match { case timeLineElement: Vector[_] => val elementVector = timeLineElement.asInstanceOf[Vector[Int]] diff --git a/src/main/scala/scim/components/Simulation.scala b/src/main/scala/scim/components/Simulation.scala index 4132d08..5db1163 100644 --- a/src/main/scala/scim/components/Simulation.scala +++ b/src/main/scala/scim/components/Simulation.scala @@ -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 // 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 + // 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 ={ val cell = input._1 val groups = input._2 diff --git a/src/main/scala/scim/lib/Library.scala b/src/main/scala/scim/lib/Library.scala index 2a1c359..bbe78b0 100644 --- a/src/main/scala/scim/lib/Library.scala +++ b/src/main/scala/scim/lib/Library.scala @@ -59,9 +59,9 @@ package object evalUtil { // FIXME: obviously need to assign something and not null def memSize(obj: Any): Long = { -// import java.lang.instrument.Instrumentation -// val instrumentation: Instrumentation = null -// instrumentation.getObjectSize(obj) + // import java.lang.instrument.Instrumentation + // val instrumentation: Instrumentation = null + // instrumentation.getObjectSize(obj) 0 } } @@ -70,21 +70,32 @@ 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) + // Comments based on linux kernel debugging (https://elinux.org/Debugging_by_printing) + object ERROR extends DebugType("ERROR", 0) // Error occurred, simulation crashed + object WARNING extends DebugType("WARNING", 1) // A warning, meaning nothing serious by itself but might indicate problems + 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 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 { @@ -164,51 +175,141 @@ package object mapUtil { 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 - 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 foundKey = vector(mid)._1 - // dont check for equality but order - if(ordering.compare(key, foundKey) == 0) return (vector(mid), Some(vector(mid + 1))) + // dont check for equality but order, if key found, check if its the beginning + if(ordering.compare(key, foundKey) == 0) if(mid == 0) return (KeyPosition.START, 0) else return (KeyPosition.AT, mid) // special case for 2 elements left - if((end - start) == 1) { - if(ordering.compare(key, vector(end)._1) >= 0) { - (vector(end), None) + if(mid == start) { + if(ordering.compare(key, vector(end)._1) == 0) { + // 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 { - // use getFirstElement to check if this is the beginning - (vector(start), Some(vector(end))) - } + // key is before start + // 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 { if (ordering.compare(key, foundKey) > 0) { - getClosestKeysHelper(key, mid, end, vector) + getIndexOrPrev(key, mid, end, vector) } 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 - getClosestKeysHelper(key, 0, vectorMap.size - 1, vectorMap) - } - - def getKeyOrNext[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) - if(ordering.compare(key, result._1._1)<= 0) { - result._1 - } else { - result._2.getOrElse(result._1) + 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 getKeyOrPrev[A, B](key: A, map: Map[A, B])(implicit ordering:Ordering[A]): (A, B) = { - if(map.contains(key)) return (key, map(key)) + def getPrevKeyAndClosest[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 result = getClosestKeysHelper(key, 0, vectorMap.size - 1, vectorMap) - result._1 + if(vectorMap.size <= 1) if(vectorMap.size == 0) return (None, None) else return (None, Some(vectorMap(0)._1, vectorMap(0)._2)) + getIndexOrPrev(key, 0, vectorMap.size - 1, vectorMap) match { + 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 { + // 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) + } + } + } + } + } package object yml {