Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ _pool: &program
package it.unibo.scafi.test

import it.unibo.scafi.alchemist.device.sensors.AlchemistEnvironmentVariables
import it.unibo.scafi.language.AggregateFoundation
import it.unibo.scafi.language.fc.syntax.FieldCalculusSyntax
import it.unibo.scafi.libraries.FieldCalculusLibrary.share
import it.unibo.scafi.sensors.DistanceSensor
import it.unibo.scafi.sensors.DistanceSensor.senseDistance
import it.unibo.scafi.language.xc.calculus.ExchangeCalculus
import it.unibo.scafi.libraries.All
import it.unibo.scafi.libraries.All.given
import it.unibo.scafi.libraries.FieldCalculusLibrary.share
import it.unibo.scafi.message.Codables.given
import it.unibo.scafi.sensors.DistanceSensor
import it.unibo.scafi.sensors.DistanceSensor.senseDistance

object GradientInlined:
type Lang = AggregateFoundation { type DeviceId = Int } & FieldCalculusSyntax & DistanceSensor[Double] &
type Lang = ExchangeCalculus { type DeviceId = Int } & FieldCalculusSyntax & DistanceSensor[Double] &
AlchemistEnvironmentVariables

def gradientInlined(using Lang): Double =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package it.unibo.scafi.test

import it.unibo.scafi.alchemist.device.sensors.AlchemistEnvironmentVariables
import it.unibo.scafi.language.AggregateFoundation
import it.unibo.scafi.language.fc.syntax.FieldCalculusSyntax
import it.unibo.scafi.language.xc.calculus.ExchangeCalculus
import it.unibo.scafi.libraries.All
import it.unibo.scafi.libraries.All.given
import it.unibo.scafi.libraries.FieldCalculusLibrary.share
Expand All @@ -11,7 +11,7 @@ import it.unibo.scafi.sensors.DistanceSensor
import it.unibo.scafi.sensors.DistanceSensor.senseDistance

object Gradient:
type Lang = AggregateFoundation { type DeviceId = Int } & FieldCalculusSyntax & DistanceSensor[Double] &
type Lang = ExchangeCalculus { type DeviceId = Int } & FieldCalculusSyntax & DistanceSensor[Double] &
AlchemistEnvironmentVariables

def gradient(using Lang): Double =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package it.unibo.scafi.language

import it.unibo.scafi.collections.SafeIterable
import it.unibo.scafi.utils.SharedDataOps

import cats.Applicative

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package it.unibo.scafi.utils
package it.unibo.scafi.language

import it.unibo.scafi.collections.SafeIterable

Expand All @@ -22,7 +22,3 @@ trait SharedDataOps[F[A] <: SafeIterable[A]]:
* the value of the "self" node
*/
def onlySelf: A

def mapValues[B](f: A => B): F[B]

def alignedMap[B, C](other: F[B])(f: (A, B) => C): F[C]
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package it.unibo.scafi.language.xc

import scala.collection.MapView

import it.unibo.scafi.collections.SafeIterable
import it.unibo.scafi.language.ShareDataOps
import it.unibo.scafi.language.SharedDataOps
import it.unibo.scafi.language.xc.calculus.ExchangeCalculus
import it.unibo.scafi.utils.SharedDataOps

import cats.Applicative

Expand All @@ -14,34 +11,35 @@ import cats.Applicative
*/
trait FieldBasedSharedData:
this: ExchangeCalculus =>
override type SharedData[T] = Field[T]
override type SharedData[Value] = Field[Value]

/**
* A Field (NValue in https://doi.org/10.1016/j.jss.2024.111976) is a mapping from device ids to values of type T. For
* devices not aligned with the current device, the default value is used.
* @param default
* @param defaultValue
* the default value for unaligned devices
* @param neighborValues
* the values for all devices, aligned and unaligned
* @tparam Value
* the type of the values
*/
protected case class Field[+Value](
default: Value,
neighborValues: Map[DeviceId, Value] = Map.empty,
private[xc] val defaultValue: Value,
private[xc] val neighborValues: Map[DeviceId, Value] = Map.empty,
) extends SafeIterable[Value]:

/**
* @return
* a filtered view of the [[SharedData]] data that only contains the values for aligned devices
*/
def alignedValues: Map[DeviceId, Value] =
if neighborValues.isEmpty then Map(localId -> default) // self is always aligned, even if there are no neighbors
if neighborValues.isEmpty then
Map(localId -> defaultValue) // self is always aligned, even if there are no neighbors
else if alignedDevices.size == neighborValues.size then
neighborValues // all devices are aligned, there is no need to filter
else
alignedDevices
.map(id => id -> neighborValues.getOrElse(id, default))
.map(id => id -> neighborValues.getOrElse(id, defaultValue))
.toMap // in all other cases, I need to filter based on the aligned devices

/**
Expand All @@ -50,12 +48,12 @@ trait FieldBasedSharedData:
* @return
* the value for the given device id, or the default value if the device is not aligned
*/
def apply(id: DeviceId): Value = alignedValues.getOrElse(id, default)
def apply(id: DeviceId): Value = alignedValues.getOrElse(id, defaultValue)

override def iterator: Iterator[Value] = alignedDevices
.map(id => neighborValues.getOrElse(id, default))
.map(id => neighborValues.getOrElse(id, defaultValue))
.iterator
override def toString: String = s"Field($default, $neighborValues)"
override def toString: String = s"Field($defaultValue, $neighborValues)"
end Field

/**
Expand All @@ -64,52 +62,52 @@ trait FieldBasedSharedData:
*/
protected def alignedDevices: Iterable[DeviceId]

override given fieldOps: ShareDataOps[SharedData, DeviceId] = new ShareDataOps[SharedData, DeviceId]:
extension [T](nv: Field[T])
override def default: T = nv.default
override def values: MapView[DeviceId, T] = nv.alignedValues.view
override def set(id: DeviceId, value: T): SharedData[T] = Field[T](
nv.default,
nv.neighborValues + (id -> value),
)
override given neighborValuesOps: NeighborValuesOps[Field, DeviceId] =
new NeighborValuesOps[Field, DeviceId]:
extension [A](a: Field[A])
override def mapValues[B](f: A => B): SharedData[B] = Field[B](
f(a.default),
a.neighborValues.view.mapValues(f).toMap,
)

override def alignedMap[B, C](other: SharedData[B])(f: (A, B) => C): SharedData[C] =
require(
a.neighborValues.keySet.diff(other.neighborValues.keySet).isEmpty,
s"Cannot alignedMap fields with different aligned devices: ${a.neighborValues.keySet} vs ${other.neighborValues.keySet}",
)
Field[C](
f(a.default, other.default),
a.neighborValues.view.map { case (id, value) => id -> f(value, other(id)) }.toMap,
)
override def apply(id: DeviceId): A = a.neighborValues.getOrElse(id, a.defaultValue)
private[xc] override def set(id: DeviceId, value: A): SharedData[A] = Field[A](
a.default,
a.neighborValues + (id -> value),
)
override def default: A = a.defaultValue
override def values: Map[DeviceId, A] = a.neighborValues
override def get(id: DeviceId): Option[A] = a.neighborValues.get(id)
end extension

override given sharedDataApplicative: Applicative[SharedData] = new Applicative[SharedData]:
override given sharedDataApplicative: Applicative[Field] = new Applicative[Field]:
override def pure[A](x: A): Field[A] = Field(x, Map.empty)

override def ap[A, B](ff: Field[A => B])(fa: Field[A]): Field[B] = Field(
ff.default(fa.default),
ff.defaultValue(fa.defaultValue),
(ff.neighborValues.keySet ++ fa.neighborValues.keySet)
.map(deviceId => deviceId -> ff(deviceId)(fa(deviceId)))
.toMap,
)

override def map[A, B](fa: Field[A])(f: A => B): Field[B] = Field[B](
f(fa.default),
f(fa.defaultValue),
fa.neighborValues.view.mapValues(f).toMap,
)

override given sharedDataOps: SharedDataOps[SharedData] = new SharedDataOps[SharedData]:
extension [A](a: Field[A])
override def withoutSelf: SafeIterable[A] =
val filtered = a.alignedValues.view.filterKeys(_ != localId).values
SafeIterable(filtered)
override def onlySelf: A = a(localId)

override def mapValues[B](f: A => B): SharedData[B] = Field[B](
f(a.default),
a.neighborValues.view.mapValues(f).toMap,
)

override def alignedMap[B, C](other: SharedData[B])(f: (A, B) => C): SharedData[C] =
require(
a.neighborValues.keySet.diff(other.neighborValues.keySet).isEmpty,
s"Cannot alignedMap fields with different aligned devices: ${a.neighborValues.keySet} vs ${other.neighborValues.keySet}",
)
Field[C](
f(a.default, other.default),
a.neighborValues.view.map { case (id, value) => id -> f(value, other(id)) }.toMap,
)
end extension
override given sharedDataOps: SharedDataOps[Field] = new SharedDataOps[Field]:
extension [A](field: Field[A])
override def withoutSelf: SafeIterable[A] = SafeIterable(field.neighborValues.values)
override def onlySelf: A = field(localId)

override given convert[T]: Conversion[T, SharedData[T]] = Field[T](_)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package it.unibo.scafi.language.xc

/**
* This trait defines the operations that can be performed on NValues.
* @tparam SharedData
* the type of the SharedData.
* @tparam DeviceId
* the type of the device id
*/
trait NeighborValuesOps[SharedData[_], DeviceId]:
extension [A](sharedData: SharedData[A])
/**
* Returns the default value for unaligned devices.
* @return
* the default value.
*/
def default: A

/**
* Maps the values of the SharedData using the provided function.
* @param f
* the mapping function.
* @tparam B
* the type of the mapped values.
* @return
* a new SharedData with the mapped values.
*/
def mapValues[B](f: A => B): SharedData[B]

/**
* Maps the values of this SharedData and another SharedData using the provided function. Assumes both SharedData
* are aligned, i.e., they contain values for the same set of device ids.
* @param other
* the other SharedData to map with.
* @param f
* the mapping function that takes a value from this SharedData and a value from the other SharedData.
* @tparam B
* the type of the values in the other SharedData.
* @tparam C
* the type of the mapped values.
* @return
* a new SharedData with the mapped values.
*/
def alignedMap[B, C](other: SharedData[B])(f: (A, B) => C): SharedData[C]

/**
* Retrieves the value associated with the given device id. If the device is unaligned, returns the default value.
* @param id
* the device id.
* @return
* the value associated with the device id.
* @throws NoSuchElementException
* if the device id is unaligned.
*/
def apply(id: DeviceId): A

/**
* Retrieves the value associated with the given device id, if the device is aligned.
* @param id
* the device id.
* @return
* an option containing the value if the device is aligned, or None if it is unaligned.
*/
def get(id: DeviceId): Option[A]

/**
* Retrieves a map of all device ids to their associated values.
* @return
* a map of device ids to values.
*/
def values: Map[DeviceId, A]

private[xc] def set(id: DeviceId, value: A): SharedData[A]
end extension
end NeighborValuesOps
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package it.unibo.scafi.language.xc.calculus

import it.unibo.scafi.language.{ AggregateFoundation, ShareDataOps }
import it.unibo.scafi.language.AggregateFoundation
import it.unibo.scafi.language.xc.NeighborValuesOps
import it.unibo.scafi.message.CodableFromTo

/**
Expand All @@ -14,9 +15,9 @@ trait ExchangeCalculus extends AggregateFoundation:
* @return
* an instance of [[SharedDataOps]]
* @see
* [[ShareDataOps]]
* [[NeighborValuesOps]]
*/
given fieldOps: ShareDataOps[SharedData, DeviceId] = scala.compiletime.deferred
given neighborValuesOps: NeighborValuesOps[SharedData, DeviceId] = scala.compiletime.deferred

/**
* Local values can be considered [[SharedData]].
Expand Down
Loading