All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.unibo.scafi.simulation.Simulation.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2016-2019, Roberto Casadei, Mirko Viroli, and contributors.
 * See the LICENSE file distributed with this work for additional information regarding copyright ownership.
*/

package it.unibo.scafi.simulation

import java.time.Instant
import java.time.temporal.{ChronoField, ChronoUnit, TemporalField}
import java.util.concurrent.TimeUnit
import it.unibo.scafi.config.GridSettings
import it.unibo.scafi.platform.SimulationPlatform
import it.unibo.utils.observer.{SimpleSource, Source}

import scala.collection.immutable.{Queue, Map => IMap}
import scala.collection.mutable.{ArrayBuffer => MArray, Map => MMap}
import scala.concurrent.duration.{DurationInt, FiniteDuration}
import scala.util.Random

/**
 *
 *         This trait defines a component that extends a Platform and
 *         requires to be "attached" to an Engine.
 *         It defines a trait with a simulator skeleton along with two settings
 *         for it, created by a factory.
 *
 */

trait Simulation extends SimulationPlatform { self: SimulationPlatform.PlatformDependency =>

  override type NETWORK = Network with SimulatorOps

  def simulatorFactory: SimulatorFactory = new BasicSimulatorFactory

  trait SimulatorOps {
    self: Network =>
    def context(id: ID): CONTEXT

    def addSensor[A](name: CNAME, value: A): Unit

    def chgSensorValue[A](name: CNAME, ids: Set[ID], value: A): Unit

    def clearExports(): Unit

    def exec(node: EXECUTION, exp: => Any, id: ID): Unit

    def execMany(node: EXECUTION, exp: => Any, size: Int, action: (Network, Int) => Unit): Seq[ID]

    def executeMany(node: EXECUTION, size: Int, action: (Network, Int) => Unit): Seq[ID]

    def execInOrderAndReturn(node: EXECUTION, exp: => Any, firingSeq: Seq[ID]): NETWORK

    def exec(ap: CONTEXT=>EXPORT): (ID,EXPORT)

    def valueMap[T](): Map[ID, T] = this.exports().mapValues(_.get.root().asInstanceOf[T]).toMap
  }

  case class Seeds(configSeed: Long = System.currentTimeMillis(),
                   simulationSeed: Long = System.currentTimeMillis(),
                   randomSensorSeed: Long = System.currentTimeMillis())

  trait SimulatorFactory {
    lazy val CONFIG_SEED: Long = System.currentTimeMillis()
    lazy val SIM_SEED: Long = System.currentTimeMillis()
    lazy val RANDOM_SENSOR_SEED: Long = System.currentTimeMillis()

    def basicSimulator(idArray: MArray[ID] = MArray(),
                       nbrMap: MMap[ID, Set[ID]] = MMap(),
                       lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap(),
                       nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap()): NETWORK

    def simulator(idArray: MArray[ID] = MArray(),
                  nbrMap: MMap[ID, Set[ID]] = MMap(),
                  localSensors: PartialFunction[CNAME, PartialFunction[ID, Any]] = Map.empty,
                  nbrSensors: PartialFunction[CNAME, PartialFunction[(ID,ID), Any]] = Map.empty
                 ): NETWORK

    def gridLike(gsettings: GridSettings,
                 rng: Double,
                 lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap(),
                 nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap(),
                 seeds: Seeds = Seeds(CONFIG_SEED, SIM_SEED, RANDOM_SENSOR_SEED)): NETWORK
  }

  class BasicSimulatorFactory extends SimulatorFactory with StandardSpatialSensorNames {
    protected val lId = linearID

    def basicSimulator(
                        idArray: MArray[ID] = MArray(),
                        nbrMap: MMap[ID, Set[ID]] = MMap(),
                        lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap(),
                        nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap()
                        ): NETWORK =
      NetworkSimulator(idArray, nbrMap, lsnsMap, nsnsMap, NetworkSimulator.defaultRepr(_), SIM_SEED, RANDOM_SENSOR_SEED)

    def simulator(idArray: MArray[ID] = MArray(),
                  nbrMap: MMap[ID, Set[ID]] = MMap(),
                  localSensors: PartialFunction[CNAME, PartialFunction[ID, Any]] = Map.empty,
                  nbrSensors: PartialFunction[CNAME, PartialFunction[(ID,ID), Any]] = Map.empty
                 ): NETWORK =
      new NetworkSimulator(SIM_SEED, RANDOM_SENSOR_SEED, idArray, localSensors, nbrSensors, nbrMap, NetworkSimulator.defaultRepr(_))

    def gridLike(gsettings: GridSettings,
                 rng: Double,
                 lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap(),
                 nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap(),
                 seeds: Seeds = Seeds(CONFIG_SEED, SIM_SEED, RANDOM_SENSOR_SEED)): NETWORK = {
      val GridSettings(rows, cols, stepx, stepy, tolerance, offsx, offsy, mapPos) = gsettings
      val configRandom = new Random(seeds.configSeed)

      def rnd(): Double = configRandom.nextDouble() * 2 * tolerance - tolerance

      def dist(a: (Double, Double), b: (Double, Double)): Double =
        Math.sqrt((a._1 - b._1) * (a._1 - b._1) + (a._2 - b._2) * (a._2 - b._2))

      import Array._
      val grid = ofDim[(Double, Double)](rows, cols)
      for (i <- 0 until rows;
           j <- 0 until cols) {
        grid(i)(j) = mapPos(i, j, offsx + i.toDouble * stepx + rnd(), offsy + j.toDouble * stepy + rnd())
      }
      val idArray = MArray() ++= (0 until rows * cols) map lId.fromNum

      val nbrMap = MMap() ++= idArray.map(lId.toNum(_)).map { i =>
        (lId.fromNum(i), idArray.filter { j =>
          dist(grid(i % rows)(i / rows), grid(lId.toNum(j) % rows)(lId.toNum(j) / rows)) < rng && j != lId.fromNum(i)
        }.toSet)
      }
      def nbsExportsInGridFor(i: ID) = MMap[ID, Any]((nbrMap(i) + i).toList.map(
        j => (j -> dist(grid(lId.toNum(i).toInt % rows)(lId.toNum(i) / rows), grid(lId.toNum(j) % rows)(lId.toNum(j) / rows)))
      ): _*)
      nsnsMap += (NBR_RANGE -> MMap(idArray.toList.map(i => i -> nbsExportsInGridFor(i)): _*))

      NetworkSimulator(
        idArray, nbrMap, lsnsMap, nsnsMap, NetworkSimulator.gridRepr(rows)(_),
        seeds.simulationSeed, seeds.randomSensorSeed)
    }

    /*
    override def random(n: Int,
                        fromx: Double,
                        tox: Double,
                        fromy: Double,
                        toy: Double,
                        eps: Double,
                        rng: Double,
                        seed: Long,
                        lsnsMap: MMap[LSNS, MMap[ID, Any]] = MMap(),
                        nsnsMap: MMap[NSNS, MMap[ID, MMap[ID, Any]]] = MMap()): Network with SimulatorOps = {
      val rand = new Random(seed)
      def r(min:Double, max:Double) = min+rand.nextInt(max.toInt-min.toInt)
      val devs = for(ids <- (0 until n);
                     x = r(fromx,tox);
                     y = r(fromy,toy)) yield(ids,x,y)
      val ids = MArray(devs.map(_._1):_*)
      import math._
      val nbrMap = devs.map(d => (d._1, devs.filter(other =>
        sqrt(pow(d._2 - other._2, 2) + pow(d._3 - other._3, 2)) < rng
      ).map(_._1))).toMap
      new NetworkSimulator(ids, nbrMap, lsnsMap, nsnsMap, NetworkSimulator.GridRepr(n))
    }
    */
  }

  class NetworkSimulator(val simulationSeed: Long = 0L,
                         val randomSensorSeed: Long = 0L,
                         val idArray: MArray[ID] = MArray(),
                         val localSensors: PartialFunction[CNAME, PartialFunction[ID, Any]] = Map.empty,
                         val nbrSensors: PartialFunction[CNAME, PartialFunction[(ID,ID), Any]] = Map.empty,
                         val nbrMap: MMap[ID, Set[ID]] = MMap(),
                         val toStr: NetworkSimulator => String = NetworkSimulator.defaultRepr,
                         val timeTick: FiniteDuration = 1.millis
                         ) extends Network with SimulatorOps with SimpleSource {
    self: NETWORK =>
    override type O = SimulationObserver[ID,CNAME]
    protected val eMap: MMap[ID,EXPORT] = MMap()
    protected var globalClock: Instant = Instant.ofEpochMilli(0)
    protected var lastRound: Map[ID,Instant] = Map()
    protected val simulationRandom = new Random(simulationSeed)
    protected val randomSensor = new Random(randomSensorSeed)

    val lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap()
    val nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap()

    // *****************
    // Network interface
    // *****************

    val ids = idArray.toSet

    def neighbourhood(id: ID): Set[ID] = nbrMap.getOrElse(id, Set())

    def sensorState(filter: (CNAME,ID) => Boolean = (s,n) => true): Map[CNAME, collection.Map[ID,Any]] =
      lsnsMap.toMap

    def neighbouringSensorState(filter: (CNAME,ID,ID) => Boolean = (s,n,nbr) => true): collection.Map[CNAME, collection.Map[ID, collection.Map[ID, Any]]] =
      nsnsMap.toMap

    def localSensor[A](name: CNAME)(id: ID): A =
      lsnsMap.get(name).flatMap(_.get(id)).getOrElse(localSensors(name)(id).asInstanceOf[A]).asInstanceOf[A]

    def nbrSensor[A](name: CNAME)(id: ID)(idn: ID): A =
      nsnsMap.get(name).flatMap(_.get(id)).flatMap(_.get(idn)).getOrElse(nbrSensors(name)(id, idn).asInstanceOf[A]).asInstanceOf[A]

    def getExport(id: ID): Option[EXPORT] = eMap.get(id)

    def exports(): IMap[ID, Option[EXPORT]] = ids.map(id => (id, getExport(id))).toMap

    protected var sensors: Map[CNAME,Any] = Map[CNAME,Any]()

    // **********************
    // SimulatorOps interface
    // **********************

    def getSensor(name: CNAME): Option[Any] = sensors.get(name)

    def addSensor[A](name: CNAME, value: A): Unit = {
      this.sensors += name -> value
      lsnsMap += (name -> MMap(idArray.map((_: ID) -> value).toSeq: _*))
    }

    def chgSensorValue[A](name: CNAME, ids: Set[ID], value: A): Unit = ids.foreach { id => lsnsMap(name) += id -> value }

    override def clearExports(): Unit = eMap.clear()

    private def getExports(id: ID): Iterable[(ID,EXPORT)] =
      (neighbourhood(id) + id).intersect(eMap.keySet).toList.map{x => { x -> eMap(x)}}

    class SimulatorContextImpl(id: ID)
      extends ContextImpl(
        selfId = id,
        exports = getExports(id),
        localSensor = IMap(),
        nbrSensor = IMap())
    with StandardTemporalSensorNames with StandardPlatformSensorNames {
      import NetworkSimulator.Optionable
      def localSensorRetrieve[T](lsns: CNAME, id: ID): Option[T] =
        lsnsMap.get(lsns).flatMap(_.get(id)).orElse(Some(localSensors(lsns)(id))).map (_.asInstanceOf[T] )

      def nbrSensorRetrieve[T](nsns: CNAME, id: ID, nbr: ID): Option[T] =
        nsnsMap.get(nsns).flatMap(_.get(id)).flatMap(_.get(nbr)).orElse(Some(nbrSensors(nsns)(id, nbr))).map(_.asInstanceOf[T])

      override def sense[T](lsns: CNAME): Option[T] = lsns match {
        case _ if lsnsMap.get(lsns).flatMap(_.get(id)).isDefined => lsnsMap(lsns).get(id).map(_.asInstanceOf[T])
        case LSNS_RANDOM => randomSensor.some[T]
        case LSNS_TIME => globalClock.some[T]
        case LSNS_TIMESTAMP => globalClock.toEpochMilli.some[T]
        case LSNS_DELTA_TIME => FiniteDuration(
          lastRound.get(id).map(t => ChronoUnit.NANOS.between(t, globalClock)).getOrElse(0L),
          TimeUnit.NANOSECONDS).some[T]
        case _ => this.localSensorRetrieve(lsns, id)
      }

      override def nbrSense[T](nsns: CNAME)(nbr: ID): Option[T] = nsns match {
        case NBR_LAG => lastRound.get(nbr).map(nbrLast =>
          FiniteDuration(ChronoUnit.NANOS.between(nbrLast, globalClock), TimeUnit.NANOSECONDS)
        ).getOrElse(FiniteDuration(0L, TimeUnit.NANOSECONDS)).some[T]
        case NBR_DELAY => lastRound.get(nbr).map(nbrLast =>
          FiniteDuration(
            ChronoUnit.NANOS.between(
              nbrLast.plusNanos(
                lastRound.get(id).map(t => ChronoUnit.NANOS.between(t, globalClock)).getOrElse(0L)),
              globalClock),
          TimeUnit.NANOSECONDS
        )).getOrElse(FiniteDuration(0L, TimeUnit.NANOSECONDS)).some[T]
        case _ => nbrSensorRetrieve(nsns, id, nbr)
      }
    }

    def context(id: ID): CONTEXT = new SimulatorContextImpl(id)

    def exec(node: EXECUTION, exp: => Any, id: ID): Unit = {
      progress(node, exp, id)
      sequentialWorldClock(id)
    }

    /**
     * @param node The local execution node
     * @param exp The expression to be run
     * @param size The number of executions to be performed
     * @param action An optional action launched after each execution
     */
    def execMany(node: EXECUTION, exp: => Any, size: Int, action: (Network, Int) => Unit = (n, i) => {}): Seq[ID] = {
      var executedNodes = Seq[ID]()
      for (i <- 0 until size) {
        val nextIdToRun = idArray(simulationRandom.nextInt(idArray.size))
        executedNodes :+= nextIdToRun
        exec(node, exp, nextIdToRun)
        action(this, i)
      }
      executedNodes
    }

    def executeMany(node: EXECUTION, size: Int, action: (Network, Int) => Unit = (n, i) => {}): Seq[ID] = {
      execMany(node, node.main(), size, action)
    }

    def exec(ap: CONTEXT=>EXPORT): (ID,EXPORT) = {
      val idToRun = idArray(simulationRandom.nextInt(idArray.size))
      val c = context(idToRun)
      val (_,exp) = idToRun -> ap(c)
      eMap += idToRun -> exp
      sequentialWorldClock(idToRun)
      idToRun -> exp
    }

    override def execInOrderAndReturn(node: EXECUTION,
                                      exp: => Any,
                                      firingSeq: Seq[ID]): Network with SimulatorOps = {
      firingSeq.foreach(id => exec(node, exp, id))
      this
    }

    override def toString: String = toStr(this)
    /* evaluate the current expression in the given node identified by the id */
    protected def progress(node: EXECUTION, exp: => Any, id: ID): Unit = {
      val c = context(id)
      eMap += (id -> node.round(c, exp))
    }
    /* update the time as the events can be sequential */
    protected def sequentialWorldClock(id: ID) = {
      updateLocalClock(id)
      tick()
    }
    /* update the world global clock by the static timeTick */
    protected def tick(): Unit = globalClock = globalClock.plusMillis(timeTick.toMillis)
    /* update the local node clock to make possible the delta tick computation */
    protected def updateLocalClock(id: ID): Unit = lastRound += id -> globalClock
  }
  object NetworkSimulator extends Serializable {
    def apply(_idArray: MArray[ID] = MArray(),
              _nbrsMap: MMap[ID, Set[ID]] = MMap(),
              _lsnsMap: MMap[CNAME, MMap[ID, Any]] = MMap(),
              _nsnsMap: MMap[CNAME, MMap[ID, MMap[ID, Any]]] = MMap(),
              _toStr: NetworkSimulator => String = NetworkSimulator.defaultRepr,
              _simulationSeed: Long,
              _randomSensorSeed: Long
             ): NETWORK = {
      new NetworkSimulator (
        _simulationSeed,
        _randomSensorSeed,
        _idArray,
        Map.empty : PartialFunction[CNAME,PartialFunction[ID,Any]],
        Map.empty : PartialFunction[CNAME,PartialFunction[(ID,ID),Any]],
        _nbrsMap,
        _toStr
      ) {
        this.lsnsMap ++= _lsnsMap
        this.nsnsMap ++= _nsnsMap
      }
    }

    implicit class Optionable[T](obj: T) {
      def some[U]: Option[U] = Option[U](obj.asInstanceOf[U])
    }

    def defaultRepr(net: NetworkSimulator): String = {
      net.idArray.map {
        i => net.getExport(i).map { e => e.root().toString }.getOrElse("_")
      }.mkString("", "\t", "")
    }

    def gridRepr(numCols: Int)(net: NetworkSimulator): String = {
      net.idArray.map {
        i => net.getExport(i).map { e => e.root().toString }.getOrElse("_")
      }.zipWithIndex
        .map(z => (if (z._2 % numCols == 0) "\n" else "") + z._1)
        .mkString("", "\t", "")
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy