![JAR search and dependency download from the Maven repository](/logo.png)
sims.GradientsDemo.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2016-2017, Roberto Casadei, Mirko Viroli, and contributors.
* See the LICENCE.txt file distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package sims
import it.unibo.scafi.incarnations.BasicSimulationIncarnation._
import it.unibo.scafi.simulation.gui.{Launcher, Settings}
import java.time.{LocalDateTime, ZoneOffset}
import java.time.temporal.ChronoUnit
import it.unibo.scafi.space.Point3D
import sims.DoubleUtils.Precision
import scala.concurrent.duration.FiniteDuration
object GradientsDemo extends Launcher {
// Configuring simulation
Settings.Sim_ProgramClass = "sims.GradientWithObstacle" // starting class, via Reflection
Settings.ShowConfigPanel = false // show a configuration panel at startup
Settings.Sim_NbrRadius = 0.15 // neighbourhood radius
Settings.Sim_NumNodes = 40 // number of nodes
Settings.ConfigurationSeed = 0
launch()
}
class GradientWithObstacle extends AggregateProgram with SensorDefinitions with Gradients {
def main = g2(sense1, sense2)
def g1(isSrc: Boolean, isObstacle: Boolean): Double = mux(isObstacle){
() => aggregate { Double.PositiveInfinity }
}{
() => aggregate { classic(isSrc) }
}()
def g2(isSrc: Boolean, isObstacle: Boolean): Double = branch(isObstacle){
Double.PositiveInfinity
}{
classic(isSrc)
}
}
class SteeringProgram extends AggregateProgram with SensorDefinitions {
def main = steering(sense1)
def steering(source: Boolean): Point3D = {
val g = classic(source)
val p = currentPosition()
val q = minHoodPLoc((g, mid, p))(nbr{ (g, mid, p) })._3
Point3D(q.x - p.x, q.y - p.y, q.z - p.z)
}
def classic(source: Boolean): Double = rep(Double.PositiveInfinity){ distance =>
mux(source){ 0.0 }{
minHoodPlus(nbr{distance} + nbrRange)
}
}
def minHoodPLoc[A](default: A)(expr: => A)(implicit poglb: Ordering[A]): A = {
import scala.math.Ordered.orderingToOrdered
val ordering = implicitly[Ordering[A]]
foldhoodPlus[A](default)((x, y) => if(x <= y) x else y){expr}
}
implicit def tupleOrd[A:Ordering, B:Ordering, C]: Ordering[(A,B,C)] = new Ordering[(A,B,C)] {
import scala.math.Ordered.orderingToOrdered
override def compare(x: (A, B, C), y: (A, B, C)): Int = (x._1,x._2).compareTo((y._1,y._2))
}
}
class ShortestPathProgram extends AggregateProgram with Gradients with SensorDefinitions {
def main = {
val g = classic(sense1)
ShortestPath(sense2, g)
}
}
class CheckSpeed extends AggregateProgram
with Gradients with BlockG with SensorDefinitions with GenericUtils with StateManagement {
implicit val deftime = new Builtins.Defaultable[LocalDateTime] {
override def default: LocalDateTime = LocalDateTime.now()
}
override def main(): Any = {
val communicationRadius = 0.2 * 100
val frequency = 1000000
val meanTimeToReach = meanCounter(
ChronoUnit.MILLIS.between(G_v2(sense1, currentTime(), (x: LocalDateTime)=>x, nbrRange()), currentTime()),
frequency).toLong
val distance = G[Double](sense1, 0, _ + nbrRange(), nbrRange())
val speed = distance / meanTimeToReach
val meanFireInterval = meanCounter(deltaTime().toMillis, frequency)
val expectedSpeed = communicationRadius / meanFireInterval
//val estSinglePathSpeed = communicationRadius / (3 * meanFireInterval)
f"${meanTimeToReach}ms; ${distance}%.1f; $speed%.3f; $expectedSpeed%.3f"
}
}
class GradientComparison extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = f"${gradientBIS(sense1)}%.1f|${crf(sense1)}%.1f|${classic(sense1)}%.1f"
}
class BISGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = gradientBIS(sense1)
}
class SVDGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = gradientSVD(sense1)
}
class FlexGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = flex(sense1)
}
class CrfGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = crf(sense1)
}
class BasicGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = gradient(sense1)
}
class ClassicGradient extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = classic(sense1)
}
class ClassicGradientWithG extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = classicWithG(sense1)
}
class ClassicGradientWithGv2 extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = classicWithGv2(sense1)
}
class ClassicGradientWithUnboundedG extends AggregateProgram with Gradients with SensorDefinitions {
override def main() = classicWithUnboundedG(sense1)
}
class DistanceBetween extends AggregateProgram with SensorDefinitions with BlockG {
def isSource: Boolean = sense1
def isTarget: Boolean = sense2
override def main(): Any = distanceBetween(isSource, isTarget)
}
object DoubleUtils {
case class Precision(p:Double)
implicit class DoubleWithAlmostEquals(val d:Double) extends AnyVal {
def ~=(d2:Double)(implicit p:Precision) = (d - d2).abs < p.p
}
}
trait Gradients extends BlockG
with FieldUtils
with TimeUtils
with StateManagement
with GenericUtils { self: AggregateProgram with SensorDefinitions with StandardSensors =>
def ShortestPath(source: Boolean, gradient: Double): Boolean =
rep(false)(
path => mux(source){
true
} {
foldhood(false)(_||_){
nbr(path) & (gradient == nbr(minHood(nbr(gradient))))
}
}
)
/*###########################
############ CRF ############
#############################*/
def crf(source: Boolean, raisingSpeed: Double = 5): Double = rep((Double.PositiveInfinity, 0.0)){ case (g, speed) =>
mux(source){ (0.0, 0.0) }{
implicit def durationToDouble(fd: FiniteDuration): Double = fd.toMillis.toDouble / 1000.0
case class Constraint(nbr: ID, gradient: Double, nbrDistance: Double)
val constraints = foldhoodPlus[List[Constraint]](List.empty)(_ ++ _){
val (nbrg, d) = (nbr{g}, nbrRange)
mux(nbrg + d + speed * (nbrLag()) <= g){ List(Constraint(nbr{mid()}, nbrg, d)) }{ List() }
}
if(constraints.isEmpty){
(g + raisingSpeed * deltaTime(), raisingSpeed)
} else {
(constraints.map(c => c.gradient + c.nbrDistance).min, 0.0)
}
}
}._1
/*############################
############ FLEX ############
##############################*/
/**
* Idea: a device should change its estimate only for significant errors.
* Useful when devices far from the source need only coarse estimates.
* Flex gradient provides tunable trade-off between precision and communication cost.
*
* @param source Source fields of devices from which the gradient is calculated
* @param epsilon Parameter expressing tolerance wrt changes
* @param delta Distortion into the distance measure, such that neighbor distance is
* never considered to be less than delta * communicationRadius.
* @param communicationRadius
* @return
*/
def flex(source: Boolean,
epsilon: Double = 0.5,
delta: Double = 1.0,
communicationRadius: Double = 1.0
): Double =
rep(Double.PositiveInfinity){ g =>
def distance = Math.max(nbrRange(), delta * communicationRadius)
import BoundedTypeClasses._; import Builtins.Bounded._ // for min/maximizing over tuples
val maxLocalSlope: (Double,ID,Double,Double) = ??? // TODO: typeclass resolution for tuple (Double,ID,Double,Double) broke
/*maxHood {
((g - nbr{g})/distance, nbr{mid}, nbr{g}, nbrRange())
}*/
val constraint = minHoodPlus{ (nbr{g} + distance) }
mux(source){ 0.0 }{
if(Math.max(communicationRadius, 2*constraint) < g) {
constraint
}
else if(maxLocalSlope._1 > 1 + epsilon) {
maxLocalSlope._3 + (1 + epsilon)*Math.max(delta * communicationRadius, maxLocalSlope._4)
}
else if(maxLocalSlope._1 < 1 - epsilon){
maxLocalSlope._3 + (1 - epsilon)*Math.max(delta * communicationRadius, maxLocalSlope._4)
} else {
g
}
}
}
/*#################################
############ UTILITIES ############
#################################*/
def timeLastChange(expr: => Double): FiniteDuration = {
import scala.concurrent.duration.DurationLong
(-rep(timestamp(), expr){ case (tLastChange, lastVal) =>
val newValue = expr
import DoubleUtils._; implicit val prec = Precision(0.001)
(if(newValue ~= lastVal) tLastChange else timestamp(), newValue)
}._1 + timestamp()).millis
}
def damping(oldVal: Double, newVal: Double, delta: Double, factor: Double): Double = {
if (oldVal > factor * newVal || newVal > factor * oldVal) {
newVal
} else {
val sign = if(oldVal < newVal) 0.5 else -0.5
val diff = Math.abs(newVal - oldVal)
if(diff > delta) newVal - delta*sign else oldVal
}
}
/*#################################
############ CLASSIC ############
#################################*/
def classic(source: Boolean): Double = rep(Double.PositiveInfinity){ distance =>
mux(source){ 0.0 }{
// NB: must be minHoodPlus (i.e., not the minHood which includes the device itself)
// otherwise a source which stops being a source will continue to count as 0 because of self-messages.
minHoodPlus(nbr{distance} + nbrRange)
}
}
def classicWithG(source: Boolean): Double = G(source, if(source) 0.0 else Double.PositiveInfinity, (_:Double) + nbrRange, nbrRange)
def classicWithGv2(source: Boolean): Double = {
implicit val defValue = Builtins.Defaultable.apply(Double.PositiveInfinity)
G_v2(source, 0.0, (_:Double) + nbrRange, nbrRange)
}
def classicWithUnboundedG(source: Boolean): Double =
unboundedG3[Double](source, if(source) 0.0 else Double.PositiveInfinity, (_:Double) + nbrRange, nbrRange)
def classicWithUnboundedG2(source: Boolean): Double =
unboundedG[Double](source, if(source) 0.0 else Double.PositiveInfinity, (_:Double) + nbrRange, nbrRange, Math.min(_:Double, _:Double))
def G_v2[V : Builtins.Defaultable](source: Boolean, field: V, acc: V => V, metric: => Double): V =
rep((Double.MaxValue, field)) { case (dist, value) =>
mux(source) {
(0.0, field)
} {
import Builtins.Bounded.tupleOnFirstBounded
minHoodPlus { (nbr {dist} + metric, acc(nbr {value})) }
}
}._2
def unboundedG[V](source: Boolean, field: V, acc: V=>V, metric: => Double)
(implicit idOrd: Ordering[ID]): V = {
rep(Double.PositiveInfinity, field) { case (dist, value) =>
mux(source) {
(0.0, field)
} {
val res = foldhoodPlus((Double.PositiveInfinity, field, mid)) { case (d1 @ (g1: Double, v1: V, id1: ID), d2 @ (g2: Double, v2: V, id2: ID)) =>
import DoubleUtils.DoubleWithAlmostEquals
implicit val prec = Precision(0.00001)
if(g1 ~= g2){
if(idOrd.lteq(id1, id2)) d1 else d2
}
else if (g1 < g2){ d1 }
else { d2 }
} { (nbr{ dist } + metric, acc(nbr{ value }), nbr{ mid }) }
(res._1, res._2)
}
}._2
}
def unboundedG2[V](source: Boolean, field: V, acc: V=>V, metric: => Double)
(implicit idOrd: Ordering[ID]): V = {
rep(Double.PositiveInfinity, field) { case (dist, value) =>
mux(source) {
(0.0, field)
} {
val res = foldhoodPlus((Double.PositiveInfinity, mid, field)) { case (d1 @ (g1: Double, id1: ID, v1: V), d2 @ (g2: Double, id2: ID, v2: V)) =>
import scala.math.Ordered.orderingToOrdered
if((g1,id1) <= (g2,id2)) d1 else d2
} { (nbr{ dist } + metric, nbr{ mid }, acc(nbr{ value })) }
(res._1, res._3)
}
}._2
}
def minHoodPLoc[A](default: A)(expr: => A)(implicit poglb: Ordering[A]): A = {
import scala.math.Ordered.orderingToOrdered
val ordering = implicitly[Ordering[A]]
foldhoodPlus[A](default)((x, y) => if(x <= y) x else y){expr}
}
implicit def tupleOrd[A:Ordering, B:Ordering, C]: Ordering[(A,B,C)] = new Ordering[(A,B,C)] {
import scala.math.Ordered.orderingToOrdered
override def compare(x: (A, B, C), y: (A, B, C)): Int = (x._1,x._2).compareTo((y._1,y._2))
}
def unboundedG3[V](source: Boolean, field: V, acc: V=>V, metric: => Double)
(implicit idOrd: Ordering[ID]): V = {
rep(Double.PositiveInfinity, mid, field) { case (dist, _, value) =>
mux(source) {
(0.0, mid, field)
} {
minHoodPLoc((Double.PositiveInfinity, mid, field)){ (nbr{ dist } + metric, nbr{ mid }, acc(nbr{ value })) }
}
}._3
}
def unboundedG[V](source: Boolean, field: V, acc: V=>V, metric: => Double, aggr: (V,V) => V): V = {
rep(Double.PositiveInfinity, field) { case (dist, value) =>
mux(source) {
(0.0, field)
} {
foldhoodPlus((Double.PositiveInfinity, field)) { case (data1 @ (d1: Double, v1: V), data2 @ (d2: Double, v2: V)) =>
import DoubleUtils.DoubleWithAlmostEquals
implicit val prec = Precision(0.00001)
if(d1 ~= d2) ((d1+d2)/2, aggr(v1, v2))
else if (d1 < d2) data1 else data2
} { (nbr {dist} + metric, acc(nbr {value})) }
}
}._2
}
def gradient(source: Boolean): Double =
rep(Double.PositiveInfinity){ distance =>
mux(source) {
0.0
}{
foldhoodPlus(Double.PositiveInfinity)(Math.min(_,_)){ nbr{distance} + nbrRange }
}
}
/*############################
############ SVD ############
#############################*/
def gradientSVD(source: Boolean, metric: => Double = nbrRange(), lagMetric: => Double = nbrLag().toMillis): Double = {
val defaultDist = if(source) 0.0 else Double.PositiveInfinity
val loc = (defaultDist, defaultDist, mid(), false)
// REP tuple: (spatial distance estimate, temporal distance estimate, source ID, obsolete value detected flag)
rep[(Double,Double,Int,Boolean)](loc) {
case old @ (spaceDistEst, timeDistEst, sourceId, isObsolete) => {
// (1) Let's calculate new values for spaceDistEst and sourceId
import BoundedTypeClasses._; import Builtins.Bounded._
val (newSpaceDistEst: Double, newSourceId: Int) = (???.asInstanceOf[Double],???.asInstanceOf[Int]) // TODO: implicit resolution broke
/* minHood {
mux(nbr{isObsolete} && excludingSelf.anyHood { !nbr{isObsolete} })
{ // let's discard neighbours where 'obsolete' flag is true
// (unless 'obsolete' flag is true for all the neighbours)
(defaultDist, mid())
} {
// if info is not obsolete OR all nbrs have obsolete info
// let's use classic gradient calculation
(nbr{spaceDistEst} + metric, nbr{sourceId})
}
}*/
// (2) The most recent timeDistEst for the newSourceId is retrieved
// by minimising nbrs' values for timeDistEst + their relative time distance
// (we only consider neighbours that have same value for 'sourceId')
val newTimeDistEst = minHood{
mux(nbr{sourceId} != newSourceId){
// let's discard neighbours with a sourceId different than newSourceId
defaultDist
} {
nbr { timeDistEst } + lagMetric
}
}
// (3) Let's compute if the newly produced info is to be considered obsolete
val loop = newSourceId == mid() && newSpaceDistEst < defaultDist
val newObsolete =
detect(timestamp() - newTimeDistEst) || // (i) if the time when currently used info started
// from sourceId is too old to be reliable
loop || // or, (ii) if the device's value happens to be calculated from itself,
excludingSelf.anyHood { // or, (iii) if any (not temporally farther) nbr with same sourceId than
// the device's one has already been claimed obsolete
nbr{isObsolete} && nbr{sourceId} == newSourceId && nbr{timeDistEst}+lagMetric < newTimeDistEst + 0.0001
}
List[(Double,Double,Int,Boolean)]((newSpaceDistEst, newTimeDistEst, newSourceId, newObsolete), loc).min
}
}._1 // Selects estimated distance
}
/**
* At the heart of SVD algorithm. This function is responsible to kick-start the reconfiguration process.
* @param time
* @return
*/
def detect(time: Double): Boolean = {
// Let's keep track into repCount of how much time is elapsed since the first time
// the current info (originated from the source in time 'time') reached the current device
val repCount = rep(0.0) { old =>
if(Math.abs(time - delay(time)) < 0.0001) { old + deltaTime().toMillis } else { 0.0 }
}
val obsolete = repCount > rep[(Double, Double, Double)](2, 8, 16) { case (avg, sqa, bound) =>
// Estimate of the average peak value for repCount, obtained by exponentially filtering
// with a factor 0.1 the peak values of repCount
val newAvg = 0.9 * avg + 0.1 * delay(repCount)
// Estimate of the average square of repCount peak values
val newSqa = 0.9 * sqa + 0.1 * Math.pow(delay(repCount), 2)
// Standard deviation
val stdev = Math.sqrt(newSqa - Math.pow(newAvg, 2))
// New bound
val newBound = newAvg + 7*stdev
(newAvg, newSqa, newBound)
}._3
obsolete
}
/*############################
############ BIS ############
#############################*/
def gradientBIS(source: Boolean): Double = {
val avgFireInterval = meanCounter(deltaTime().toMillis, 1000000)
val speed = 1.0 / avgFireInterval
val commRadius = 0.2
rep((Double.PositiveInfinity, Double.PositiveInfinity)){ case (spatialDist: Double, tempDist: Double) =>
mux(source){ (0.0, 0.0) }{
minHoodPlus {
val newEstimate = Math.max(nbr{spatialDist} + nbrRange(), speed * nbr{tempDist} - commRadius)
(newEstimate, nbr{tempDist} + nbrLag.toMillis/1000.0)
}
}
}._1
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy