From edb3f61241f3204f5164a2b18174fc7703342e54 Mon Sep 17 00:00:00 2001 From: S-furi Date: Mon, 19 Jan 2026 12:04:01 +0100 Subject: [PATCH 01/11] docs: improve Javadoc --- .../model/observation/DerivedObservable.kt | 24 +++++++++++++++++-- .../model/observation/EventObservable.kt | 9 +++++++ .../alchemist/model/observation/Observable.kt | 2 +- .../it/unibo/alchemist/core/AbstractEngine.kt | 7 ++++++ .../model/linkingrules/AdaptiveRange.java | 3 ++- .../model/reactions/AbstractReaction.java | 23 ++++-------------- 6 files changed, 46 insertions(+), 22 deletions(-) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt index 9ba47c3b2c..8dabb068de 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt @@ -15,8 +15,10 @@ import arrow.core.some import java.util.Collections /** - * An abstract implementation of the `Observable` interface designed to support observables whose states - * are derived from other data sources. Manages the lifecycle of observation and update propagation. + * An abstract implementation of the [Observable] interface designed to support observables whose states + * are derived from other data sources. Manages the lifecycle of observation and update propagation while + * keeping a minimal set of active subscriptions with the underlying observables. Moreover, observation of + * the sources is enabled if only there are observers registered with this derived observable. * * @param emitOnDistinct whether to emit when the new derived value is different from the current one. * @param T The type of data being observed. @@ -80,10 +82,28 @@ abstract class DerivedObservable(private val emitOnDistinct: Boolean = true) cached = none() } + /** + * Initiates monitoring for changes or updates in the implementing class. + * This method should enable necessary mechanisms to observe and react to changes + * based on the specific implementation of the derived class. + */ protected abstract fun startMonitoring() + /** + * Stops monitoring for changes or updates in the implementing class. + * This method should disable any active observation mechanisms and ensure + * that resources or listeners associated with monitoring are appropriately released. + * It is intended to complement the `startMonitoring` function. + */ protected abstract fun stopMonitoring() + /** + * Computes and returns a fresh value of type [T]. This method is expected to be implemented + * in derived classes to provide a new value that represents the updated state or computation + * result of the observable entity. + * + * @return a fresh, computed value of type [T] + */ protected abstract fun computeFresh(): T protected fun updateAndNotify(newValue: T) { diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt index 97228cfaaf..79f35cdef0 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt @@ -1,3 +1,12 @@ +/* + * Copyright (C) 2010-2026, Danilo Pianini and contributors + * listed, for each module, in the respective subproject's build.gradle.kts file. + * + * This file is part of Alchemist, and is distributed under the terms of the + * GNU General Public License, with a linking exception, + * as described in the file LICENSE in the Alchemist distribution's top directory. + */ + package it.unibo.alchemist.model.observation /** diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt index 58e0540d87..4ae5939f2a 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt @@ -176,7 +176,7 @@ interface MutableObservable : Observable { } /** - * Factory methods container. + * Factories and extension methods container. */ companion object { diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/AbstractEngine.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/AbstractEngine.kt index edc32f9dd1..6655df9df1 100644 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/AbstractEngine.kt +++ b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/AbstractEngine.kt @@ -309,6 +309,13 @@ abstract class AbstractEngine>(private val environment: E } } + /** + * Initializes the simulation engine. + * + * This method is called during the setup phase of the simulation engine and is responsible + * for preparing all necessary internal states and resources required for the engine's execution, + * e.g. initial scheduling of global and nodes' reactions. + */ protected abstract fun initialize() /** diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/AdaptiveRange.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/AdaptiveRange.java index 6c1c2e9c80..33ca195dbe 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/AdaptiveRange.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/AdaptiveRange.java @@ -16,6 +16,7 @@ import it.unibo.alchemist.model.Node; import it.unibo.alchemist.model.Position; import it.unibo.alchemist.model.neighborhoods.Neighborhoods; +import org.danilopianini.util.ListSet; import java.io.Serial; import java.util.stream.Collectors; @@ -179,7 +180,7 @@ public final Neighborhood computeNeighborhood(final Node center, final Env ranges.put(center.getId(), getRange()); } final double curRange = ranges.get(center.getId()); - final var potentialNeighs = environment.getNodesWithinRange(center, curRange); + final ListSet> potentialNeighs = environment.getNodesWithinRange(center, curRange); final Neighborhood neigh = Neighborhoods.make(environment, center, potentialNeighs.stream() .filter(neighbor -> !conditionForRemoval(environment, center, neighbor, curRange, ranges.get(neighbor.getId()))) .collect(Collectors.toList())); diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/reactions/AbstractReaction.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/reactions/AbstractReaction.java index 1875c3c903..e7f4eca92d 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/reactions/AbstractReaction.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/reactions/AbstractReaction.java @@ -241,22 +241,6 @@ public final int hashCode() { return hash; } - /** - * This method is called when the environment has completed its - * initialization. Can be used by this reaction to compute its next - * execution time - in case such computation requires an inspection of the - * environment. - *
- * NOTE: this method ensures that the observable dependencies - * (i.e. observable conditions) are properly initialized with - * {@link #initializeObservableConditions()}. - * Subclasses should override {@link #onInitializationComplete(Time, Environment)} - * to add custom initialization logic. - * - * @param atTime the time at which the initialization of this reaction was - * accomplished - * @param environment the environment - */ @Override public final void initializationComplete(@Nonnull final Time atTime, @Nonnull final Environment environment) { initializeObservableConditions(); @@ -264,8 +248,11 @@ public final void initializationComplete(@Nonnull final Time atTime, @Nonnull fi } /** - * This method is called by {@link #initializationComplete(Time, Environment)} - * after the observable dependencies have been initialized. + * This method is called when the environment has completed its + * initialization. Can be used by this reaction to compute its next + * execution time - in case such computation requires an inspection of the + * environment. At the time this method is called, all observable + * dependencies have been already initialized. * Subclasses can override this to perform custom initialization (e.g. initial update). * * @param atTime the time at which the initialization of this reaction was From 335113f50fe2085b9cda2df1ea5417e7232ae190 Mon Sep 17 00:00:00 2001 From: S-furi Date: Wed, 21 Jan 2026 16:55:48 +0100 Subject: [PATCH 02/11] chore(api): provide a way to register to an observable without triggering callback invocation --- .../model/observation/EventObservable.kt | 4 ++-- .../alchemist/model/observation/Observable.kt | 23 +++++++++++++++---- .../model/observation/ObservableExtensions.kt | 4 ++-- .../model/observation/ObservableList.kt | 7 +++--- .../model/observation/ObservableMap.kt | 4 ++-- .../model/observation/ObservableSet.kt | 14 ++--------- .../model/observation/ObservableTest.kt | 18 +++++++++++++++ 7 files changed, 48 insertions(+), 26 deletions(-) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt index 79f35cdef0..41431425e9 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/EventObservable.kt @@ -33,9 +33,9 @@ class EventObservable : Observable { observingCallbacks.values.forEach { callbacks -> callbacks.forEach { it(Unit) } } } - override fun onChange(registrant: Any, callback: (Unit) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (Unit) -> Unit) { observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - callback(current) + if (invokeOnRegistration) callback(current) } override fun stopWatching(registrant: Any) { diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt index 4ae5939f2a..aee2bbb3e8 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt @@ -40,7 +40,18 @@ interface Observable : Disposable { * @param callback The callback to be executed when the observable's state changes. It receives * the updated value of the observable as an argument. */ - fun onChange(registrant: Any, callback: (T) -> Unit) + fun onChange(registrant: Any, callback: (T) -> Unit) = onChange(registrant, true, callback) + + /** + * Registers a callback to be notified of changes in the observable. The callback is invoked + * whenever the state of the observable changes. + * + * @param registrant The entity registering the callback. + * @param invokeOnRegistration Whether the callback should be invoked immediately upon registration. + * @param callback The callback to be executed when the observable's state changes. It receives + * the updated value of the observable as an argument. + */ + fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (T) -> Unit) /** * Unregisters the specified registrant from watching for changes or updates in the observable. @@ -140,10 +151,10 @@ interface Observable : Disposable { override val observingCallbacks: MutableMap Unit>> = mutableMapOf() - override fun onChange(registrant: Any, callback: (T) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (T) -> Unit) { observers += registrant observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - this@asMutable.onChange(this to registrant, callback) + this@asMutable.onChange(this to registrant, invokeOnRegistration, callback) } override fun stopWatching(registrant: Any) { @@ -201,8 +212,10 @@ interface MutableObservable : Observable { override val observers: List get() = observingCallbacks.keys.toList() - override fun onChange(registrant: Any, callback: (T) -> Unit) { - callback(current) + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (T) -> Unit) { + if (invokeOnRegistration) { + callback(current) + } observingCallbacks[registrant] = observingCallbacks[registrant]?.let { it + callback } ?: listOf(callback) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt index e9877ba96f..17c133c262 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt @@ -161,10 +161,10 @@ object ObservableExtensions { override val observableSize: Observable = backing.map { it.size } - override fun onChange(registrant: Any, callback: (Set) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (Set) -> Unit) { observers += registrant observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - backing.onChange(this to registrant, callback) + backing.onChange(this to registrant, invokeOnRegistration, callback) } override fun stopWatching(registrant: Any) { diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableList.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableList.kt index 1b46a7dbc6..50d9b26d3a 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableList.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableList.kt @@ -27,10 +27,11 @@ interface ObservableList : Observable> { * The callback will be invoked with the current list of items as a parameter. * * @param registrant The object registering for change notifications. Used to manage the lifecycle of the observer. + * @param invokeOnRegistration whether the callback should be invoked on registration * @param callback The function to be invoked whenever the list changes. It receives the current list of items as * a parameter. */ - override fun onChange(registrant: Any, callback: (List) -> Unit) + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (List) -> Unit) /** * Returns the element at the specified index in the list. @@ -186,9 +187,9 @@ class ObservableMutableList : ObservableList { } } - override fun onChange(registrant: Any, callback: (List) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (List) -> Unit) { observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - callback(toList()) + if (invokeOnRegistration) callback(toList()) } override fun stopWatching(registrant: Any) { diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt index 570882a6cc..08a1af8fb9 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt @@ -167,9 +167,9 @@ open class ObservableMutableMap(private val backingMap: MutableMap = } } - override fun onChange(registrant: Any, callback: (Map) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (Map) -> Unit) { observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - callback(current.toMap()) + if (invokeOnRegistration) callback(current.toMap()) } override fun stopWatching(registrant: Any) { diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableSet.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableSet.kt index 37b6032318..a7472091f9 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableSet.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableSet.kt @@ -23,16 +23,6 @@ interface ObservableSet : Observable> { */ val observableSize: Observable - /** - * Observes changes to the set and triggers the provided callback function whenever the set changes. - * The callback will be invoked with the current set of items as a parameter. - * - * @param registrant The object registering for change notifications. Used to manage the lifecycle of the observer. - * @param callback The function to be invoked whenever the set changes. It receives the current set of items as - * a parameter. - */ - override fun onChange(registrant: Any, callback: (Set) -> Unit) - /** * Observes the membership status of a specific item in the set. * The returned observable will emit `true` if the item is a member of the set, @@ -146,9 +136,9 @@ class ObservableMutableSet : ObservableSet { toAdd.forEach(::add) } - override fun onChange(registrant: Any, callback: (Set) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (Set) -> Unit) { observingCallbacks[registrant] = observingCallbacks[registrant].orEmpty() + callback - backing.onChange(this to registrant) { callback(it.keys.toSet()) } + backing.onChange(this to registrant, invokeOnRegistration) { callback(it.keys.toSet()) } } override fun stopWatching(registrant: Any) { diff --git a/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/ObservableTest.kt b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/ObservableTest.kt index 59de8e7b9b..723e1a6e99 100644 --- a/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/ObservableTest.kt +++ b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/ObservableTest.kt @@ -39,6 +39,24 @@ class ObservableTest : FunSpec({ lastSeen shouldBe 42 } + test("`onChange` with invokeOnRegistration = false should not emit current value") { + val observable = observe(42) + var lastSeen: Int? = null + + observable.onChange(this, false) { lastSeen = it } + + lastSeen shouldBe null + } + + test("`onChange` with invokeOnRegistration = true should emit current value") { + val observable = observe(42) + var lastSeen: Int? = null + + observable.onChange(this, true) { lastSeen = it } + + lastSeen shouldBe 42 + } + test("`update` should return previous value and update current") { val observable = observe(0) From 0c356f0daa59911b5d02aaea57030b67a0af63a8 Mon Sep 17 00:00:00 2001 From: S-furi Date: Wed, 21 Jan 2026 17:35:29 +0100 Subject: [PATCH 03/11] chore(biochemestry): port `BiomolPresentInNeighbor` to kotlin --- .../conditions/BiomolPresentInNeighbor.java | 99 ------------------- .../conditions/BiomolPresentInNeighbor.kt | 90 +++++++++++++++++ 2 files changed, 90 insertions(+), 99 deletions(-) delete mode 100644 alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.java create mode 100644 alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.java deleted file mode 100644 index 7e642f9fed..0000000000 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2010-2023, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.model.biochemistry.conditions; - -import it.unibo.alchemist.model.Environment; -import it.unibo.alchemist.model.Neighborhood; -import it.unibo.alchemist.model.Node; -import it.unibo.alchemist.model.Reaction; -import it.unibo.alchemist.model.biochemistry.CellProperty; -import it.unibo.alchemist.model.biochemistry.molecules.Biomolecule; -import it.unibo.alchemist.model.observation.MutableObservable; -import it.unibo.alchemist.model.observation.Observable; -import org.apache.commons.math3.util.FastMath; - -import java.io.Serial; - -import static org.apache.commons.math3.util.CombinatoricsUtils.binomialCoefficientDouble; - -/** - * This condition is valid if a selected biomolecule is present in the neighborhood of the node. - */ -public final class BiomolPresentInNeighbor extends AbstractNeighborCondition { - - @Serial - private static final long serialVersionUID = 499903479123400111L; - - private final Biomolecule molecule; - private final Double concentration; - - /** - * @param molecule the molecule to check - * @param concentration the minimum concentration - * @param node the local node - * @param environment the environment - */ - public BiomolPresentInNeighbor( - final Environment environment, - final Node node, - final Biomolecule molecule, - final Double concentration - ) { - super(environment, node); - declareDependencyOn(molecule); - this.molecule = molecule; - this.concentration = concentration; - setUpObservability(); - } - - @Override - public BiomolPresentInNeighbor cloneCondition(final Node node, final Reaction reaction) { - return new BiomolPresentInNeighbor(getEnvironment(), node, molecule, concentration); - } - - @SuppressWarnings("unchecked") - @Override - protected Observable observeNeighborPropensity(final Node neighbor) { - // the neighbor is eligible, its propensity is computed using the concentration of the biomolecule - if (neighbor.asPropertyOrNull(CellProperty.class) == null) { - return MutableObservable.Companion.observe(0d); - } - return neighbor.observeConcentration(molecule).map(opt -> opt.map(conc -> { - if (conc >= concentration) { - return binomialCoefficientDouble(conc.intValue(), (int) FastMath.ceil(concentration)); - } else { - return 0d; - } - }).fold(() -> 0d, val -> val)); - } - - @Override - public String toString() { - return molecule.toString() + " >= " + concentration + " in neighbor"; - } - - @SuppressWarnings("unchecked") - private void setUpObservability() { - addObservableDependency(getNode().observeConcentration(molecule)); - setValidity( - observeValidNeighbors().map(validNeighbors -> { - if (validNeighbors.isEmpty()) { - return false; - } - final Neighborhood neighborhood = getEnvironment().getNeighborhood(getNode()); - return validNeighbors.entrySet().stream() - .filter(n -> n.getKey().asPropertyOrNull(CellProperty.class) != null) - .allMatch(n -> - neighborhood.contains(n.getKey()) && n.getKey().getConcentration(molecule) >= concentration - ); - }) - ); - } -} diff --git a/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt new file mode 100644 index 0000000000..578c697edf --- /dev/null +++ b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010-2026, Danilo Pianini and contributors + * listed, for each module, in the respective subproject's build.gradle.kts file. + * + * This file is part of Alchemist, and is distributed under the terms of the + * GNU General Public License, with a linking exception, + * as described in the file LICENSE in the Alchemist distribution's top directory. + */ + +package it.unibo.alchemist.model.biochemistry.conditions + +import it.unibo.alchemist.model.Environment +import it.unibo.alchemist.model.Node +import it.unibo.alchemist.model.Node.Companion.asPropertyOrNull +import it.unibo.alchemist.model.Reaction +import it.unibo.alchemist.model.biochemistry.CellProperty +import it.unibo.alchemist.model.biochemistry.molecules.Biomolecule +import it.unibo.alchemist.model.observation.MutableObservable.Companion.observe +import it.unibo.alchemist.model.observation.Observable +import java.io.Serial +import org.apache.commons.math3.util.CombinatoricsUtils.binomialCoefficientDouble +import org.apache.commons.math3.util.FastMath + +/** + * This condition is valid if a selected biomolecule is present in the neighborhood of the node. + * + * @param molecule the molecule to check + * @param concentration the minimum concentration + * @param node the local node + * @param environment the environment + */ +class BiomolPresentInNeighbor( + environment: Environment, + node: Node, + private val molecule: Biomolecule, + private val concentration: Double, +) : AbstractNeighborCondition(environment, node) { + + init { + declareDependencyOn(molecule) + setUpObservability() + } + + protected override fun observeNeighborPropensity(neighbor: Node): Observable = + neighbor.takeIf { it.asPropertyOrNull>() != null }?.let { n -> + // the neighbor is eligible, its propensity is computed using the concentration of the biomolecule + n.observeConcentration(molecule).map { maybeValue -> + maybeValue.fold( + ifEmpty = { 0.0 }, + ifSome = { + if (it >= concentration) { + binomialCoefficientDouble(it.toInt(), FastMath.ceil(concentration).toInt()) + } else { + 0.0 + } + }, + ) + } + } ?: observe(0.0) + + override fun cloneCondition( + newNode: Node, + newReaction: Reaction, + ): AbstractNeighborCondition = BiomolPresentInNeighbor(environment, node, molecule, concentration) + + override fun toString(): String = "$molecule >= $concentration in neighbor" + + private fun setUpObservability() { + addObservableDependency(node.observeConcentration(molecule)) + setValidity( + observeValidNeighbors().map { validNeighbors -> + val current = environment.getNeighborhood(node).current + validNeighbors + ?.takeIf { it.isNotEmpty() }?.entries + ?.filter { it.key.asPropertyOrNull>() != null } + ?.all { it.key in current && it.key.getConcentration(molecule) >= concentration } + ?: false + }, + ) + } + + /** + * Companion object for the [BiomolPresentInNeighbor] class. + * + * This object stores the `serialVersionUID` constant required for serialization purposes. + */ + companion object { + @Serial const val serialVersionUID: Long = 499903479123400111L + } +} From 08c456a3a4a7bc2e5404c0abdbdea7d9e42979e7 Mon Sep 17 00:00:00 2001 From: S-furi Date: Wed, 21 Jan 2026 17:58:44 +0100 Subject: [PATCH 04/11] feat!: fully transition to observable neighborhoods --- .../it/unibo/alchemist/model/Environment.kt | 7 +------ .../model/observation/DerivedObservable.kt | 8 +++++--- .../main/kotlin/it/unibo/alchemist/core/Engine.kt | 6 +++--- .../alchemist/core/JGraphTDependencyGraph.kt | 2 +- .../model/surrogates/EnvironmentSurrogate.kt | 2 +- .../schema/model/EnvironmentSurrogateTest.kt | 2 +- .../model/conditions/NeighborHasConcentration.kt | 2 +- .../model/environments/AbstractEnvironment.kt | 12 ++++++------ .../model/linkingrules/ConnectViaAccessPoint.kt | 3 ++- .../timedistributions/SimpleNetworkArrivals.kt | 3 +-- .../it/unibo/alchemist/util/Environments.kt | 4 ++-- .../TestSimpleNetworkArrivals.kt | 10 ++++------ .../actions/AbstractNeighborAction.java | 8 +++----- .../actions/ChangeBiomolConcentrationInEnv.java | 2 +- .../actions/ChemotacticPolarization.java | 13 ++++++++++--- .../conditions/AbstractNeighborCondition.java | 4 ++-- .../conditions/BiomolPresentInEnv.java | 2 +- .../model/biochemistry/conditions/EnvPresent.java | 4 ++-- .../conditions/NeighborhoodPresent.java | 2 +- .../environments/BioRect2DEnvironment.java | 2 +- .../ChangeBiomolConcentrationInNeighbor.kt | 15 ++++++++------- .../TestMoleculeSwapWithinNeighborhood.kt | 4 ++-- .../TestNeighborhoodReactionsPropensities.kt | 2 +- .../model/protelis/AlchemistNetworkManager.kt | 13 ++++++------- .../actions/AbstractSAPEREMoveNodeAgent.java | 4 ++-- .../actions/AbstractSAPERENeighborAgent.java | 4 ++-- .../sapere/actions/LsaAscendingGradientDist.java | 10 ++++++---- .../sapere/actions/LsaCountNeighborsAction.java | 2 +- .../sapere/actions/LsaRandomNeighborAction.java | 2 +- .../sapere/actions/SAPEREWalkerRiseGradient.java | 2 +- .../conditions/LsaNeighborhoodCondition.java | 2 +- .../model/sapere/reactions/SAPEREGradient.java | 2 +- .../model/sapere/reactions/SAPEREReaction.java | 2 +- .../actions/SendScafiMessage.scala | 9 ++++++++- .../alchemist/boundary/extractors/NodeDegree.kt | 2 +- .../it/unibo/alchemist/test/TestGraphStream.kt | 4 ++-- .../test/TestGraphStreamReproducibility.kt | 2 +- .../maps/linkingrules/TestInSightConnection.kt | 2 +- .../swingui/monitor/impl/Generic2DDisplay.java | 2 +- 39 files changed, 96 insertions(+), 87 deletions(-) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt index d6db5a1dc9..4c9af8ac04 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt @@ -93,16 +93,11 @@ interface Environment> : */ var linkingRule: LinkingRule - /** - * Given a [node], this method returns its neighborhood. - */ - fun getNeighborhood(node: Node): Neighborhood - /** * Given a [node], this method returns an observable view of * its neighborhood. */ - fun observeNeighborhood(node: Node): Observable> + fun getNeighborhood(node: Node): Observable> /** * Allows accessing a [Node] in this [Environment] known its [id]. diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt index 8dabb068de..0fb63697bd 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt @@ -47,17 +47,19 @@ abstract class DerivedObservable(private val emitOnDistinct: Boolean = true) } } - override fun onChange(registrant: Any, callback: (T) -> Unit) { + override fun onChange(registrant: Any, invokeOnRegistration: Boolean, callback: (T) -> Unit) { val wasEmpty = callbacks.isEmpty() callbacks[registrant] = callbacks[registrant].orEmpty() + callback if (wasEmpty) { val initial = computeFresh() cached = initial.some() - callback(initial) + if (invokeOnRegistration) { + callback(initial) + } isListening = true startMonitoring() - } else { + } else if (invokeOnRegistration) { callback(current) } } diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt index 546dd9ffb1..d4a238f22d 100644 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt +++ b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt @@ -225,7 +225,7 @@ open class Engine>( */ private inner class Movement(private val sourceNode: Node) : Update() { override val reactionsToUpdate: Sequence> - get() = getReactionsRelatedTo(sourceNode, environment.getNeighborhood(sourceNode)).filter { + get() = getReactionsRelatedTo(sourceNode, environment.getNeighborhood(sourceNode).current).filter { it.inboundDependencies.any { dependency -> dependency.dependsOn(Dependency.MOVEMENT) } } @@ -292,8 +292,8 @@ open class Engine>( override val reactionsToUpdate: Sequence> get() { val subjects = sequenceOf(sourceNode, targetNode) - val sourceNeighbors = environment.getNeighborhood(sourceNode).asSequence() - val targetNeighbors = environment.getNeighborhood(targetNode).asSequence() + val sourceNeighbors = environment.getNeighborhood(sourceNode).current.asSequence() + val targetNeighbors = environment.getNeighborhood(targetNode).current.asSequence() val allSubjects = (subjects + sourceNeighbors + targetNeighbors).distinct() return allSubjects.flatMap { it.reactions.asSequence() }.filter { it.inputContext == diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt index 7886912e43..8814993771 100644 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt +++ b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt @@ -239,7 +239,7 @@ class JGraphTDependencyGraph(private val environment: Environment) : De } } - private val Node.neighborhood get() = environment.getNeighborhood(this).neighbors + private val Node.neighborhood get() = environment.getNeighborhood(this).current.neighbors private companion object { private val Actionable<*>.inputContext get() = diff --git a/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt b/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt index f23bb1310b..4db619328c 100644 --- a/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt +++ b/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt @@ -82,7 +82,7 @@ data class EnvironmentSurrogate>( */ @GraphQLDescription("The neighborhood of the node with the given id") fun getNeighborhood(nodeId: Int): NeighborhoodSurrogate = - origin.getNeighborhood(origin.getNodeByID(nodeId)).toGraphQLNeighborhoodSurrogate() + origin.getNeighborhood(origin.getNodeByID(nodeId)).current.toGraphQLNeighborhoodSurrogate() /** * Clone the node associated with the given id to the specified position. diff --git a/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt b/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt index ce3d4f4211..d0c2a80c8a 100644 --- a/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt +++ b/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt @@ -38,7 +38,7 @@ class EnvironmentSurrogateTest where T : Any, P : Position

, P : Vector< val nodeSurrogate = envSurrogate.nodeById(node.id) checkNodeSurrogate(node, nodeSurrogate) checkPositionSurrogate(envWrapper.getPosition(node), envSurrogate.nodeToPos()[node.id]!!) - checkNeighborhood(envWrapper.getNeighborhood(node), envSurrogate.getNeighborhood(node.id)) + checkNeighborhood(envWrapper.getNeighborhood(node).current, envSurrogate.getNeighborhood(node.id)) } // Test propagation of changes val newNode = envWrapper.nodes.first().cloneNode(Time.ZERO) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NeighborHasConcentration.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NeighborHasConcentration.kt index fef40f5e25..572685eae8 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NeighborHasConcentration.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/conditions/NeighborHasConcentration.kt @@ -38,7 +38,7 @@ class NeighborHasConcentration( init { setValidity( - environment.observeNeighborhood(node).switchMap { neighborhood -> + environment.getNeighborhood(node).switchMap { neighborhood -> neighborhood.neighbors.map { it.observeConcentration(target) } .combineLatest { neighborsConcentrations -> neighborsConcentrations.any { it.isSome { nConc -> nConc == concentration } } diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt index 9c02eb5422..aa65b7f402 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt @@ -152,7 +152,7 @@ abstract class AbstractEnvironment> protected constructor( spatialIndex.insert(node, *actualPosition.coordinates) updateNeighborhood(node, true) ifEngineAvailable { it.nodeAdded(node) } - nodeAdded(node, position, getNeighborhood(node)) + nodeAdded(node, position, retrieveNeighborhood(node)) true } else -> false @@ -190,7 +190,7 @@ abstract class AbstractEnvironment> protected constructor( ): Sequence> = newNeighborhood .getNeighbors() .asSequence() - .filterNot { it in (oldNeighborhood ?: emptySet()) || getNeighborhood(it).contains(center) } + .filterNot { it in (oldNeighborhood ?: emptySet()) || retrieveNeighborhood(it).contains(center) } .map { Operation(center, it, true) } private fun getAllNodesInRange(center: P, range: Double): List> { @@ -258,7 +258,7 @@ abstract class AbstractEnvironment> protected constructor( override fun getLayer(molecule: Molecule): Layer? = _layers[molecule] - override fun getNeighborhood(node: Node): Neighborhood { + protected fun retrieveNeighborhood(node: Node): Neighborhood { val result = neighCache[node.id] requireNotNull(result) { check(!nodes.contains(node)) { @@ -269,7 +269,7 @@ abstract class AbstractEnvironment> protected constructor( return result } - override fun observeNeighborhood(node: Node): Observable> = + override fun getNeighborhood(node: Node): Observable> = observableNeighCache[node.id].map { maybeNeighborhood -> val neighborhood = maybeNeighborhood.getOrNull() requireNotNull(neighborhood) { @@ -360,7 +360,7 @@ abstract class AbstractEnvironment> protected constructor( ): Sequence> = oldNeighborhood ?.neighbors ?.asSequence() - ?.filter { neigh -> !newNeighborhood.contains(neigh) && getNeighborhood(neigh).contains(center) } + ?.filter { neigh -> !newNeighborhood.contains(neigh) && retrieveNeighborhood(neigh).contains(center) } ?.map { neigh -> Operation(center, neigh, isAdd = false) } .orEmpty() @@ -527,7 +527,7 @@ abstract class AbstractEnvironment> protected constructor( .getNeighbors() .asSequence() .filterNot(newNeighborhood::contains) - .map(this::getNeighborhood) + .map(this::retrieveNeighborhood) .filter { neigh -> neigh.contains(node) } .forEach { neighborhoodToChange -> val formerNeighbor = neighborhoodToChange.getCenter() diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/ConnectViaAccessPoint.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/ConnectViaAccessPoint.kt index d5cf54a099..de0980a060 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/ConnectViaAccessPoint.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/ConnectViaAccessPoint.kt @@ -36,7 +36,8 @@ class ConnectViaAccessPoint>(radius: Double, val accessPointI environment, center, neighbors.filter { - it == closestAP || !it.isAccessPoint && environment.getNeighborhood(it).contains(closestAP) + it == closestAP || + !it.isAccessPoint && environment.getNeighborhood(it).current.contains(closestAP) }, ) } ?: Neighborhoods.make(environment, center, emptyList()) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/timedistributions/SimpleNetworkArrivals.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/timedistributions/SimpleNetworkArrivals.kt index 6413c3ccb8..d8b01c1a71 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/timedistributions/SimpleNetworkArrivals.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/timedistributions/SimpleNetworkArrivals.kt @@ -15,7 +15,6 @@ import it.unibo.alchemist.model.Molecule import it.unibo.alchemist.model.Node import it.unibo.alchemist.model.Time import it.unibo.alchemist.model.times.DoubleTime -import java.lang.IllegalStateException /** * This class models a distribution that follows the packet arrival times as described in @@ -152,7 +151,7 @@ class SimpleNetworkArrivals private constructor( /** Gets the neighbors of a node as a collection. */ val Node.neighborhood: Collection> - get() = environment.getNeighborhood(this).neighbors + get() = environment.getNeighborhood(this).current.neighbors /** Computes the effective bandwidth considering access point load balancing. */ val bandwidth: Double diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt index 1ed481d1ec..1cc616212d 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt @@ -28,7 +28,7 @@ object Environments { ): (Node, Node) -> Double = { n1, n2 -> when { n1 == n2 -> 0.0 - n2 in getNeighborhood(n1) -> computeDistance(n1, n2) + n2 in getNeighborhood(n1).current -> computeDistance(n1, n2) else -> POSITIVE_INFINITY } } @@ -154,7 +154,7 @@ object Environments { while (toExplore.isNotEmpty()) { val current = toExplore.first() explored += current - val neighbors = getNeighborhood(current).toMutableSet() + val neighbors = getNeighborhood(current).current.toMutableSet() toExplore += neighbors toExplore -= explored } diff --git a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/model/timedistributions/TestSimpleNetworkArrivals.kt b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/model/timedistributions/TestSimpleNetworkArrivals.kt index ad26be25d7..4557cee825 100644 --- a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/model/timedistributions/TestSimpleNetworkArrivals.kt +++ b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/model/timedistributions/TestSimpleNetworkArrivals.kt @@ -13,13 +13,11 @@ import io.mockk.every import io.mockk.mockk import it.unibo.alchemist.model.Environment import it.unibo.alchemist.model.Incarnation -import it.unibo.alchemist.model.Molecule import it.unibo.alchemist.model.Node import it.unibo.alchemist.model.Time import it.unibo.alchemist.model.positions.Euclidean2DPosition import kotlin.test.assertEquals import kotlin.test.assertNotEquals -import org.danilopianini.util.ListSet import org.danilopianini.util.ListSets import org.junit.jupiter.api.Test @@ -33,7 +31,7 @@ class TestSimpleNetworkArrivals { val incarnation = mockk>() val environment = mockk>() val node = mockk>() - every { environment.getNeighborhood(node).neighbors } returns ListSets.emptyListSet() + every { environment.getNeighborhood(node).current.neighbors } returns ListSets.emptyListSet() val propagationDelay = 0.1 val packetSize = 1000.0 val bandwidth = 1000.0 @@ -56,8 +54,8 @@ class TestSimpleNetworkArrivals { val environment = mockk>() val node1 = mockk>() val node2 = mockk>() - every { environment.getNeighborhood(node1).neighbors } returns ListSets.emptyListSet() - every { environment.getNeighborhood(node2).neighbors } returns ListSets.emptyListSet() + every { environment.getNeighborhood(node1).current.neighbors } returns ListSets.emptyListSet() + every { environment.getNeighborhood(node2).current.neighbors } returns ListSets.emptyListSet() val distribution = SimpleNetworkArrivals( incarnation = incarnation, node = node1, @@ -79,7 +77,7 @@ class TestSimpleNetworkArrivals { val incarnation = mockk>() val environment = mockk>() val node = mockk>() - every { environment.getNeighborhood(node).neighbors } returns ListSets.emptyListSet() + every { environment.getNeighborhood(node).current.neighbors } returns ListSets.emptyListSet() val distribution = SimpleNetworkArrivals( incarnation = incarnation, node = node, diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/AbstractNeighborAction.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/AbstractNeighborAction.java index e118931909..070c1b299b 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/AbstractNeighborAction.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/AbstractNeighborAction.java @@ -29,7 +29,6 @@ public abstract class AbstractNeighborAction extends AbstractRandomizableActi @Serial private static final long serialVersionUID = -2287346030993830896L; private final Environment environment; - private final Node node; /** * @param node the current node @@ -42,7 +41,6 @@ protected AbstractNeighborAction( final RandomGenerator randomGenerator ) { super(node, randomGenerator); - this.node = node; this.environment = environment; } @@ -54,9 +52,9 @@ protected AbstractNeighborAction( */ @Override public void execute() { - final Neighborhood neighborhood = environment.getNeighborhood(node); - if (!neighborhood.isEmpty()) { - execute(Iterables.INSTANCE.randomElement(neighborhood, getRandomGenerator())); + final Neighborhood currentNeighborhood = getEnvironment().getNeighborhood(getNode()).getCurrent(); + if (!currentNeighborhood.isEmpty()) { + execute(Iterables.INSTANCE.randomElement(currentNeighborhood, getRandomGenerator())); } } diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInEnv.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInEnv.java index 33e39673e6..8d08ed5c22 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInEnv.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInEnv.java @@ -138,7 +138,7 @@ public Context getContext() { * @return a list containing the environment nodes around */ private List getEnvironmentNodesSurrounding() { - return environment.getNeighborhood(getNode()).getNeighbors().stream() + return environment.getNeighborhood(getNode()).getCurrent().getNeighbors().stream() .parallel() .flatMap(n -> n instanceof EnvironmentNode ? Stream.of((EnvironmentNode) n) : Stream.empty()) .collect(Collectors.toList()); diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java index ee76392cf3..f64f090b6c 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java @@ -22,6 +22,7 @@ import org.apache.commons.math3.util.FastMath; import java.io.Serial; +import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Objects; @@ -38,6 +39,7 @@ public final class ChemotacticPolarization extends AbstractAction { private final Biomolecule biomolecule; private final boolean ascend; private final CellProperty cell; + private List> neighbors = new ArrayList<>(0); /** * @param environment the environment @@ -67,6 +69,13 @@ public ChemotacticPolarization( } else { throw new IllegalArgumentException("Possible imput string are only up or down"); } + + environment.getNeighborhood(getNode()).onChange(this, neighborhood -> { + this.neighbors = neighborhood.getNeighbors().stream() + .filter(n -> n instanceof EnvironmentNode && n.contains(this.biomolecule)) + .collect(Collectors.toList()); + return null; + }); } /** @@ -98,9 +107,7 @@ public ChemotacticPolarization cloneAction(final Node node, final Reacti public void execute() { // declaring a variable for the node where this action is set, to have faster access final Node thisNode = getNode(); - final List> l = environment.getNeighborhood(thisNode).getNeighbors().stream() - .filter(n -> n instanceof EnvironmentNode && n.contains(biomolecule)) - .collect(Collectors.toList()); + final List> l = this.neighbors; if (l.isEmpty()) { cell.addPolarizationVersor(Euclidean2DPosition.Companion.getZero()); } else { diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/AbstractNeighborCondition.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/AbstractNeighborCondition.java index 926d08cb01..73a4347a56 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/AbstractNeighborCondition.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/AbstractNeighborCondition.java @@ -45,7 +45,7 @@ public abstract class AbstractNeighborCondition extends AbstractCondition protected AbstractNeighborCondition(final Environment environment, final Node node) { super(node); this.environment = environment; - addObservableDependency(getEnvironment().observeNeighborhood(getNode())); + addObservableDependency(getEnvironment().getNeighborhood(getNode())); setPropensityContributionObservable(); } @@ -98,7 +98,7 @@ public final Map, Double> getValidNeighbors() { */ public final Observable, Double>> observeValidNeighbors() { return ObservableExtensions.INSTANCE.switchMap( - getEnvironment().observeNeighborhood(getNode()), + getEnvironment().getNeighborhood(getNode()), neighborhood -> { final List, Double>>> propensities = neighborhood.getNeighbors().stream() .map((Node neighbor) -> observeNeighborPropensity(neighbor) diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java index 2f582029c9..8b5b488a94 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java @@ -84,7 +84,7 @@ private void setUpObservability() { } private Observable observeTotalQuantity() { - return environment.observeNeighborhood(getNode()).mergeWith( + return environment.getNeighborhood(getNode()).mergeWith( environment.observePosition(getNode()), (neighborhood, position) -> { final double quantityInEnvNodes = neighborhood.getNeighbors().stream() diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/EnvPresent.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/EnvPresent.java index 413cfff457..1701191f5b 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/EnvPresent.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/EnvPresent.java @@ -39,8 +39,8 @@ public EnvPresent(final Environment environment, final Node n super(node); this.environment = environment; - addObservableDependency(environment.observeNeighborhood(node)); - setValidity(environment.observeNeighborhood(node).map(it -> + addObservableDependency(environment.getNeighborhood(node)); + setValidity(environment.getNeighborhood(node).map(it -> it.getNeighbors().stream().anyMatch(n -> n instanceof EnvironmentNode) )); diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/NeighborhoodPresent.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/NeighborhoodPresent.java index 71eac51956..8c1d504627 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/NeighborhoodPresent.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/NeighborhoodPresent.java @@ -39,7 +39,7 @@ public final class NeighborhoodPresent extends AbstractNeighborCondition { public NeighborhoodPresent(final Environment environment, final Node node) { super(environment, node); - setValidity(environment.observeNeighborhood(getNode()).map(neighborhood -> + setValidity(environment.getNeighborhood(getNode()).map(neighborhood -> neighborhood.getNeighbors().stream() .anyMatch(n -> n.asPropertyOrNull(CellProperty.class) != null) )); diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironment.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironment.java index fa655585e0..66de776bb1 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironment.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironment.java @@ -112,7 +112,7 @@ protected final boolean isAllowed(final Euclidean2DPosition p) { public final void moveNode(final Node node, @Nonnull final Euclidean2DPosition direction) { if (node.asPropertyOrNull(CellProperty.class) != null) { super.moveNode(node, direction); - final Neighborhood neigh = getNeighborhood(node); + final Neighborhood neigh = retrieveNeighborhood(node); final Map, Integer>> jun = node .asProperty(CellProperty.class).getJunctions().getCurrent(); jun.forEach((key, value) -> value.forEach((key1, value1) -> { diff --git a/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInNeighbor.kt b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInNeighbor.kt index d1b23dbd9d..491e98a3e9 100644 --- a/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInNeighbor.kt +++ b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/actions/ChangeBiomolConcentrationInNeighbor.kt @@ -30,17 +30,18 @@ class ChangeBiomolConcentrationInNeighbor( val molecule: Biomolecule, val deltaConcentration: Double, ) : AbstractNeighborAction(node, environment, randomGenerator) { + override fun cloneAction(newNode: Node, newReaction: Reaction) = ChangeBiomolConcentrationInNeighbor(randomGenerator, environment, newNode, molecule, deltaConcentration) override fun execute() { - val neighborhood = environment.getNeighborhood(node) - val validNeighbors = - neighborhood.filter { - it.asPropertyOrNull>() != null && - (deltaConcentration > 0 || it.getConcentration(molecule) >= deltaConcentration) - } - execute(validNeighbors.randomElement(randomGenerator)) + val validNeighbors = environment.getNeighborhood(node).current.filter { + it.asPropertyOrNull>() != null && + (deltaConcentration > 0 || it.getConcentration(molecule) >= deltaConcentration) + } + if (validNeighbors.isNotEmpty()) { + execute(validNeighbors.randomElement(randomGenerator)) + } } override fun execute(targetNode: Node) { diff --git a/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/molecules/TestMoleculeSwapWithinNeighborhood.kt b/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/molecules/TestMoleculeSwapWithinNeighborhood.kt index aa8ab50e06..34ab815822 100644 --- a/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/molecules/TestMoleculeSwapWithinNeighborhood.kt +++ b/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/molecules/TestMoleculeSwapWithinNeighborhood.kt @@ -48,8 +48,8 @@ class TestMoleculeSwapWithinNeighborhood { environment.linkingRule = LINKING_RULE environment.addNode(nodes.first, INITIAL_POSITIONS.first) environment.addNode(nodes.second, INITIAL_POSITIONS.second) - assertTrue(environment.getNeighborhood(nodes.first).neighbors.contains(nodes.second)) - assertTrue(environment.getNeighborhood(nodes.second).neighbors.contains(nodes.first)) + assertTrue(environment.getNeighborhood(nodes.first).current.neighbors.contains(nodes.second)) + assertTrue(environment.getNeighborhood(nodes.second).current.neighbors.contains(nodes.first)) nodes.first.setConcentration(BIOMOLECULE, 1.0) } diff --git a/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/reactions/TestNeighborhoodReactionsPropensities.kt b/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/reactions/TestNeighborhoodReactionsPropensities.kt index 5d5570dc52..eb364c17a3 100644 --- a/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/reactions/TestNeighborhoodReactionsPropensities.kt +++ b/alchemist-incarnation-biochemistry/src/test/kotlin/it/unibo/alchemist/model/biochemistry/reactions/TestNeighborhoodReactionsPropensities.kt @@ -68,7 +68,7 @@ class TestNeighborhoodReactionsPropensities { .onEach { it.second.setConcentration(BIOMOLECULE, it.first) } .map { it.second } .onEach { environment.addNode(it, POSITION) } - assertEquals(neighbors.toList(), environment.getNeighborhood(centralNode).neighbors.toList()) + assertEquals(neighbors.toList(), environment.getNeighborhood(centralNode).current.neighbors.toList()) } @Test diff --git a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistNetworkManager.kt b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistNetworkManager.kt index 6a5265160b..f7c67e302e 100644 --- a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistNetworkManager.kt +++ b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistNetworkManager.kt @@ -58,6 +58,10 @@ class AlchemistNetworkManager @JvmOverloads constructor( private var toBeSent: Map = emptyMap() private var neighborState = ImmutableMap.of>() private var timeAtLastValidityCheck = Double.NEGATIVE_INFINITY + private val neighborDevices: Set> + get() = environment.getNeighborhood(device.node).current.neighbors + .mapNotNull { it.asPropertyOrNull>() } + .toSet() init { require(retentionTime.isNaN() || retentionTime >= 0) { "The retention time can't be negative." } @@ -75,11 +79,7 @@ class AlchemistNetworkManager @JvmOverloads constructor( val stateBuilder = ImmutableMap.builder>() val messagesIterator = messages.values.iterator() val retainsNeighbors = retentionTime.isNaN() - val neighbors: Set = emptySet().takeUnless { retainsNeighbors } - ?: environment.getNeighborhood(device.node) - .neighbors - .mapNotNull { it.asPropertyOrNull>() } - .toSet() + val neighbors: Set = emptySet().takeUnless { retainsNeighbors } ?: neighborDevices while (messagesIterator.hasNext()) { val message = messagesIterator.next() val messageIsValid = @@ -119,8 +119,7 @@ class AlchemistNetworkManager @JvmOverloads constructor( fun simulateMessageArrival(currentTime: Double) { if (toBeSent.isNotEmpty()) { val msg = MessageInfo(currentTime, device, toBeSent) - environment.getNeighborhood(device.node) - .mapNotNull { it.asPropertyOrNull>() } + neighborDevices .forEach { neighborDevice -> val destination = neighborDevice.getNetworkManager(program) var packetArrives = true diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java index 6d7dec1993..dbc2fa2344 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java @@ -93,14 +93,14 @@ protected final P getPosition(final Node> node) { * @return the position of node */ protected final Neighborhood> getNeighborhood(final ILsaNode node) { - return environment.getNeighborhood(node); + return environment.getNeighborhood(node).getCurrent(); } /** * @return the position of node */ protected final Neighborhood> getLocalNeighborhood() { - return environment.getNeighborhood(getNode()); + return environment.getNeighborhood(getNode()).getCurrent(); } /** diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java index 1e67e749d9..2aec35ab52 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java @@ -134,14 +134,14 @@ protected final P getPosition(final ILsaNode node) { * @return the position of node */ protected final Neighborhood> getNeighborhood(final ILsaNode node) { - return environment.getNeighborhood(node); + return environment.getNeighborhood(node).getCurrent(); } /** * @return the position of node */ protected final Neighborhood> getLocalNeighborhood() { - return environment.getNeighborhood(getNode()); + return environment.getNeighborhood(getNode()).getCurrent(); } /** diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaAscendingGradientDist.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaAscendingGradientDist.java index 7f3d06d744..3b88488ab5 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaAscendingGradientDist.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaAscendingGradientDist.java @@ -36,7 +36,7 @@ public final class LsaAscendingGradientDist

> extends Abstr private static final ILsaMolecule MOLGRAD = new LsaMolecule("grad, req, Type, Distance, Time"); private static final ILsaMolecule MOLRESPONSE = new LsaMolecule("response, Req, Ser, MD, D"); private static final int POS = 3; - private final Environment, ?> environment; + private Neighborhood> neigh; /** * @param environment environment @@ -44,15 +44,17 @@ public final class LsaAscendingGradientDist

> extends Abstr */ public LsaAscendingGradientDist(final Environment, P> environment, final ILsaNode node) { super(environment, node, MOLRESPONSE); - this.environment = environment; + environment.getNeighborhood(node).onChange(this, neighborhood -> { + this.neigh = neighborhood; + return null; + }); } @Override public void execute() { double minGrad = getLSAArgumentAsDouble(getNode().getConcentration(MOLGRAD).get(0), POS); - final Neighborhood> neigh = environment.getNeighborhood(getNode()); final List targetPositions = new ArrayList<>(); - for (final Node> node : neigh.getNeighbors()) { + for (final Node> node : this.neigh.getNeighbors()) { final LsaNode n = (LsaNode) node; final List gradList; gradList = n.getConcentration(MOLGRAD); diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaCountNeighborsAction.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaCountNeighborsAction.java index 6f10c04b55..f6c06631c3 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaCountNeighborsAction.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaCountNeighborsAction.java @@ -116,7 +116,7 @@ public void execute() { final List l = mol.allocateVar(getMatches()); Double num = 0.0; environment.getNeighborhood(getNode()); - for (final Node> nod : environment.getNeighborhood(getNode()).getNeighbors()) { + for (final Node> nod : environment.getNeighborhood(getNode()).getCurrent().getNeighbors()) { if (!nod.getConcentration(new LsaMolecule(l)).isEmpty()) { num++; } diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaRandomNeighborAction.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaRandomNeighborAction.java index cb72c1bff5..dcb7118075 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaRandomNeighborAction.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/LsaRandomNeighborAction.java @@ -172,7 +172,7 @@ protected void setSynthectics(final ILsaNode node) { * #NEIGH */ if (initNeigh) { - setSyntheticNeigh(environment.getNeighborhood(node).getNeighbors()); + setSyntheticNeigh(environment.getNeighborhood(node).getCurrent().getNeighbors()); } /* * #O diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java index cb25e31bc4..e7faa13f5f 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java @@ -162,7 +162,7 @@ public GeoPosition getTarget() { final Position curNodeActualPos = environment.getPosition(curNode); if (curNode.equals(node) || !curPos.equals(curNodeActualPos) - || environment.getNeighborhood(node).contains(curNode)) { + || environment.getNeighborhood(node).getCurrent().contains(curNode)) { /* * Update target */ diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaNeighborhoodCondition.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaNeighborhoodCondition.java index 7469dac091..843af6d080 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaNeighborhoodCondition.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/conditions/LsaNeighborhoodCondition.java @@ -56,7 +56,7 @@ public LsaNeighborhoodCondition( // every time a change in node's neighborhood is emitted, or one of the // members' LSA space has changed. addObservableDependency(ObservableExtensions.INSTANCE.switchMap( - environment.observeNeighborhood(node).map(Neighborhood::getNeighbors), + environment.getNeighborhood(node).map(Neighborhood::getNeighbors), neighbors -> ObservableExtensions.INSTANCE.combineLatest( neighbors.stream() diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java index ce103a5c14..a838602775 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java @@ -394,7 +394,7 @@ protected void updateInternalStatus( final P curPos = this.environment.getPosition(getNode()); final boolean positionChanged = !curPos.equals(mypos); boolean neighPositionChanged = false; - for (final Node> n : this.environment.getNeighborhood(getNode())) { + for (final Node> n : this.environment.getNeighborhood(getNode()).getCurrent()) { final P p = this.environment.getPosition(n); final int nid = n.getId(); positionCacheTemp.put(nid, p); diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java index 958ea19215..38cf2023d6 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java @@ -240,7 +240,7 @@ protected void updateInternalStatus( * Valid nodes must be re-initialized, as per issue # */ final Collection>> neighs = - this.environment.getNeighborhood(getNode()).getNeighbors(); + this.environment.getNeighborhood(getNode()).getCurrent().getNeighbors(); validNodes = new ArrayList<>(neighs.size()); for (final Node> neigh: neighs) { validNodes.add((ILsaNode) neigh); diff --git a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala index f8bbeffd3f..31f7fd1c62 100644 --- a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala +++ b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala @@ -27,6 +27,13 @@ class SendScafiMessage[T, P <: Position[P]]( assert(reaction != null, "Reaction cannot be null") assert(program != null, "Program cannot be null") + private var neighbors: Iterator[Node[T]] = Iterator.empty + environment.getNeighborhood(device.getNode).onChange(this, neighborhood => { + neighbors = neighborhood.getNeighbors.iterator().asScala + kotlin.Unit.INSTANCE + } + ) + /** * This method allows to clone this action on a new node. It may result useful to support runtime creation of nodes * with the same reaction programming, e.g. for morphogenesis. @@ -63,7 +70,7 @@ class SendScafiMessage[T, P <: Position[P]]( override def execute(): Unit = { val toSend = program.getExport(device.getNode.getId).get for { - neighborhood <- environment.getNeighborhood(device.getNode).getNeighbors.iterator().asScala + neighborhood <- neighbors action <- ScafiIncarnationUtils.allScafiProgramsFor[T, P](neighborhood).filter(program.getClass.isInstance(_)) if action.programNameMolecule == program.programNameMolecule } action.sendExport(device.getNode.getId, toSend) diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodeDegree.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodeDegree.kt index 2dce769345..bf13ead214 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodeDegree.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodeDegree.kt @@ -23,7 +23,7 @@ constructor(filter: ExportFilter, aggregators: List, precision: Int = 2) time: Time, step: Long, ): Map, Double> = environment.nodes.associateWith { node -> - environment.getNeighborhood(node).size().toDouble() + environment.getNeighborhood(node).current.size().toDouble() } private companion object { diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt index 00bc84aa02..fa4c879223 100644 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt +++ b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt @@ -29,7 +29,7 @@ class TestGraphStream : environment.nodeCount shouldBeExactly 400 "with neighbors closer than non-neighbors" { environment.nodes.forEach { node -> - val neighborhood = environment.getNeighborhood(node) + val neighborhood = environment.getNeighborhood(node).current val averageDistances = environment.nodes .asSequence() @@ -49,7 +49,7 @@ class TestGraphStream : "create links" - { val neighborhoods = environment.nodes - .map { environment.getNeighborhood(it).neighbors } + .map { environment.getNeighborhood(it).current.neighbors } neighborhoods.forEach { it.shouldNotBeEmpty() } "asymmetrically" { println(neighborhoods) diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt index ea487ab63b..f7df0acd20 100644 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt +++ b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt @@ -59,7 +59,7 @@ class TestGraphStreamReproducibility : } environment.nodes.map { node -> environment.getPosition(node).coordinates.toList() to - environment.getNeighborhood(node).neighbors.map { it.id } + environment.getNeighborhood(node).current.neighbors.map { it.id } } } val graphs1 = generateGraphs() diff --git a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt index 66ad31b7df..2315bcffc6 100644 --- a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt +++ b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt @@ -38,7 +38,7 @@ class TestInSightConnection : environment.getDistanceBetweenNodes(node0, node1) shouldBeLessThan maxRange val route = environment.computeRoute(node0, node1) route.length() shouldBeGreaterThan maxRange - environment.getNeighborhood(node0).contains(node1).shouldBeFalse() + environment.getNeighborhood(node0).current.contains(node1).shouldBeFalse() } }, ) diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index fce553dc6f..f29cdcaef5 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -717,7 +717,7 @@ private void update(final Environment environment, final Time time) { neighbors.clear(); environment.getNodes().parallelStream().forEach(node -> { positions.put(node, environment.getPosition(node)); - neighbors.put(node, environment.getNeighborhood(node)); + neighbors.put(node, environment.getNeighborhood(node).getCurrent()); }); releaseData(); repaint(); From 236a03a2880b2250977d03c30c7c26a3f59be438 Mon Sep 17 00:00:00 2001 From: S-furi Date: Wed, 21 Jan 2026 18:54:03 +0100 Subject: [PATCH 05/11] feat!: make node counting observable --- .../it/unibo/alchemist/model/Environment.kt | 7 +------ .../TestTerminationPredicateSerialization.kt | 4 ++-- .../environments/TestEnvironmentWithDynamics.kt | 2 +- .../obstacles/TestContinuous2DObstacle.java | 10 +++++----- .../alchemist/model/linkingrules/ClosestN.java | 12 ++++++++---- .../model/environments/AbstractEnvironment.kt | 6 ++---- .../model/linkingrules/FullyConnected.kt | 8 ++++---- .../model/terminators/StableForSteps.kt | 2 +- .../it/unibo/alchemist/util/Environments.kt | 17 +++++++++-------- .../alchemist/util/TestEnvironmentsDiameter.kt | 2 +- .../TestEnvironmentsDiameterWithHopDistance.kt | 2 +- .../conditions/BiomolPresentInNeighbor.kt | 4 ++++ .../boundary/extractors/NetworkCentroid.kt | 4 ++-- .../boundary/extractors/NetworkDensity.kt | 2 +- .../boundary/extractors/NodesPositions.kt | 6 +++--- .../boundary/extractors/NumberOfNodes.kt | 2 +- .../alchemist/model/util/GraphStreamSupport.kt | 2 +- .../unibo/alchemist/model/TestLoadGPSTrace.java | 2 +- .../it/unibo/alchemist/test/TestGraphStream.kt | 2 +- .../maps/linkingrules/TestInSightConnection.kt | 2 +- .../swingui/effect/impl/DrawDirectedNode.kt | 2 +- 21 files changed, 51 insertions(+), 49 deletions(-) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt index 4c9af8ac04..d41673bb86 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt @@ -116,15 +116,10 @@ interface Environment> : */ val observableNodes: ObservableSet> - /** - * Returns the number of [Node]s currently in the [Environment]. - */ - val nodeCount: Int - /** * Returns an [Observable] view of the number of [Node]s currently in the [Environment]. */ - val observeNodeCount: Observable + val nodeCount: Observable /** * Given a [node] this method returns a list of all the surrounding diff --git a/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/TestTerminationPredicateSerialization.kt b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/TestTerminationPredicateSerialization.kt index 34149369bf..401facf435 100644 --- a/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/TestTerminationPredicateSerialization.kt +++ b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/TestTerminationPredicateSerialization.kt @@ -14,7 +14,7 @@ class TestTerminationPredicateSerialization { fun `termination predicates can be serialized and deserialized`() { // Create a predicate that checks if the environment has more than 100 nodes val originalPredicate: TerminationPredicate> = - TerminationPredicate { env -> env.nodeCount > 100 } + TerminationPredicate { env -> env.nodeCount.current > 100 } // Serialize the predicate val serializedBytes = serialize(originalPredicate) // Deserialize the predicate @@ -22,7 +22,7 @@ class TestTerminationPredicateSerialization { deserialize(serializedBytes) // Check that the deserialized predicate works as expected val mockEnvironment = mockk>>() - every { mockEnvironment.nodeCount } returns 101 + every { mockEnvironment.nodeCount.current } returns 101 assertEquals(originalPredicate(mockEnvironment), deserializedPredicate(mockEnvironment)) { "Deserialized predicate should behave the same as the original" } diff --git a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/environments/TestEnvironmentWithDynamics.kt b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/environments/TestEnvironmentWithDynamics.kt index ee35c57633..7ad69dbbb7 100644 --- a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/environments/TestEnvironmentWithDynamics.kt +++ b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/environments/TestEnvironmentWithDynamics.kt @@ -33,7 +33,7 @@ class TestEnvironmentWithDynamics : environment.shapeFactory.rectangle(120.0, 120.0).transformed { origin(environment.origin) }, - ).size shouldBe environment.nodeCount + ).size shouldBe environment.nodeCount.current }, ) } diff --git a/alchemist-euclidean-geometry/src/test/java/it/unibo/alchemist/model/obstacles/TestContinuous2DObstacle.java b/alchemist-euclidean-geometry/src/test/java/it/unibo/alchemist/model/obstacles/TestContinuous2DObstacle.java index 76c81b82f0..d658d8ef54 100644 --- a/alchemist-euclidean-geometry/src/test/java/it/unibo/alchemist/model/obstacles/TestContinuous2DObstacle.java +++ b/alchemist-euclidean-geometry/src/test/java/it/unibo/alchemist/model/obstacles/TestContinuous2DObstacle.java @@ -68,16 +68,16 @@ void test() { ); environment.addNode(createIntNode(incarnation, environment), new Euclidean2DPosition(0, 0)); - assertEquals(1, environment.getNodeCount()); + assertEquals(1, environment.getNodeCount().getCurrent()); environment.addNode(createIntNode(incarnation, environment), new Euclidean2DPosition(1, 1)); - assertEquals(1, environment.getNodeCount()); + assertEquals(1, environment.getNodeCount().getCurrent()); // CHECKSTYLE: MagicNumber OFF environment.addNode(createIntNode(incarnation, environment), new Euclidean2DPosition(1.5, 0.5)); - assertEquals(1, environment.getNodeCount()); + assertEquals(1, environment.getNodeCount().getCurrent()); environment.addNode(createIntNode(incarnation, environment), new Euclidean2DPosition(1, 5)); - assertEquals(1, environment.getNodeCount()); + assertEquals(1, environment.getNodeCount().getCurrent()); environment.addNode(createIntNode(incarnation, environment), new Euclidean2DPosition(1, 2.999)); - assertEquals(2, environment.getNodeCount()); + assertEquals(2, environment.getNodeCount().getCurrent()); assertEquals(2, environment.getObstaclesInRange(0d, 0d, 100d).size()); assertEquals(1, environment.getObstaclesInRange(0d, 0d, 1d).size()); assertEquals(R1021, environment.getObstaclesInRange(0d, 0d, 1d).get(0)); diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ClosestN.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ClosestN.java index 529f484e02..1ee402921d 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ClosestN.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ClosestN.java @@ -100,7 +100,7 @@ private Cache, Double> ranges() { @Override public final Neighborhood computeNeighborhood(final Node center, final Environment environment) { - if (environment.getNodeCount() < expectedNodes || !nodeIsEnabled(center)) { + if (environment.getNodeCount().getCurrent() < expectedNodes || !nodeIsEnabled(center)) { return Neighborhoods.make(environment, center); } return Neighborhoods.make(environment, center, @@ -130,13 +130,17 @@ private Stream> closestN(final Node center, final Environment e Set> inRange; final double maxRange = Doubles.max(environment.getSizeInDistanceUnits()) * 2; do { - inRange = (environment.getNodeCount() > nodeCount && currentRange < maxRange + inRange = (environment.getNodeCount().getCurrent() > nodeCount && currentRange < maxRange ? nodesInRange(environment, center, currentRange).stream() : environment.getNodes().stream()) .filter(n -> !n.equals(center) && nodeIsEnabled(n)) .collect(Collectors.toCollection(LinkedHashSet::new)); currentRange *= 2; - } while (inRange.size() < nodeCount && inRange.size() < environment.getNodeCount() - 1 && currentRange < maxRange * 2); + } while ( + inRange.size() < nodeCount + && inRange.size() < environment.getNodeCount().getCurrent() - 1 + && currentRange < maxRange * 2 + ); if (inRange.isEmpty()) { return Stream.empty(); } @@ -200,7 +204,7 @@ protected final double getRange(final Environment environment, final Node< * would, on average, contain the number of required devices */ return ranges().get(center, () -> { - final int nodes = environment.getNodeCount(); + final int nodes = environment.getNodeCount().getCurrent(); if (nodes < nodeCount || nodes < 10) { return Double.MAX_VALUE; } diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt index aa65b7f402..daa98f8e37 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt @@ -82,10 +82,8 @@ abstract class AbstractEnvironment> protected constructor( @Transient override val observableNodes: ObservableMutableSet> = _nodes.toList().toObservableSet() - final override val nodeCount: Int get() = nodes.size - @Transient - override val observeNodeCount: Observable = observableNodes.observableSize + final override val nodeCount: Observable = observableNodes.observableSize private val regionObservers = ArrayList() @@ -552,7 +550,7 @@ abstract class AbstractEnvironment> protected constructor( } } } else { - val processed = TIntHashSet(nodeCount).apply { add(node.id) } + val processed = TIntHashSet(nodes.size).apply { add(node.id) } val operations = recursiveOperation(node).toMutableList() while (operations.isNotEmpty()) { val next = operations.removeLast() diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/FullyConnected.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/FullyConnected.kt index ec27ce78cd..b94f5ad792 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/FullyConnected.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/linkingrules/FullyConnected.kt @@ -30,7 +30,7 @@ class FullyConnected> : LinkingRule { override fun getCenter() = center - override fun isEmpty() = environment.nodeCount <= 1 + override fun isEmpty() = environment.nodeCount.current <= 1 override fun getNeighbors() = object : ListSet> { override fun get(index: Int) = BugReporting.reportBug("Not implemented") @@ -67,13 +67,13 @@ class FullyConnected> : LinkingRule { override fun retainAll(elements: Collection>) = BugReporting.reportBug("Not implemented") - override val size = environment.nodeCount - 1 + override val size = environment.nodeCount.current - 1 override fun contains(element: Node?) = element != center && environment.contains(element) override fun containsAll(elements: Collection>) = elements.all { contains(it) } - override fun isEmpty() = environment.nodeCount == 1 + override fun isEmpty() = environment.nodeCount.current == 1 } override fun remove(node: Node?) = this @@ -82,6 +82,6 @@ class FullyConnected> : LinkingRule { override fun iterator() = neighbors.iterator() - override fun size() = environment.nodeCount - 1 + override fun size() = environment.nodeCount.current - 1 } } diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt index be8a7074e8..8a60022aeb 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt @@ -58,7 +58,7 @@ data class StableForSteps(private val checkInterval: Long, private val override fun invoke(environment: Environment>): Boolean { if (environment.simulation.step % checkInterval == 0L) { val newPositions = environment.associateBy({ it }, { environment.getPosition(it) }) - val newContents = makeTable(environment.nodeCount) + val newContents = makeTable(environment.nodeCount.current) environment.forEach { node -> node.contents.forEach { molecule, concentration -> newContents.put(node, molecule, concentration) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt index 1cc616212d..0fa7ca93d3 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/util/Environments.kt @@ -64,7 +64,7 @@ object Environments { fun Environment.allSubNetworksByNode( computeDistance: (Node, Node) -> Double = environmentMetricDistance(), ): Map, Network> { - val subnetworks = arrayOfNulls>(nodeCount) + val subnetworks = arrayOfNulls>(nodeCount.current) val result = mutableMapOf, Network>() val paths = allShortestPaths(computeDistance) // Update all the subnetworks with the last evaluated; that is the most complete @@ -74,7 +74,7 @@ object Environments { val newSubnetwork = MutableNetwork(0.0, mutableListOf(centerNode)) result.put(centerNode, newSubnetwork) val centerRow = paths.row(centerIndex) - for (potentialNeighborIndex in centerIndex + 1 until nodeCount) { + for (potentialNeighborIndex in centerIndex + 1 until nodeCount.current) { val distanceToNeighbor = centerRow[potentialNeighborIndex] if (distanceToNeighbor.isFinite()) { newSubnetwork.diameter = max(newSubnetwork.diameter, distanceToNeighbor) @@ -123,18 +123,19 @@ object Environments { }, ): SymmetricMatrix { val nodes = nodes.toList() + val currentNodeCount = nodeCount.current /* * The distance matrix is a triangular matrix stored in a flat array. */ - val distances = MutableDoubleSymmetricMatrix(nodeCount) - for (i in 0 until nodeCount) { - for (j in i until nodeCount) { + val distances = MutableDoubleSymmetricMatrix(currentNodeCount) + for (i in 0 until currentNodeCount) { + for (j in i until currentNodeCount) { distances[i, j] = computeDistance(nodes[i], nodes[j]) } } - for (intermediate in 0 until nodeCount) { - for (i in 0 until nodeCount) { - for (j in i + 1 until nodeCount) { + for (intermediate in 0 until currentNodeCount) { + for (i in 0 until currentNodeCount) { + for (j in i + 1 until currentNodeCount) { val throughIntermediate = distances[i, intermediate] + distances[intermediate, j] if (distances[i, j] > throughIntermediate) { distances[i, j] = throughIntermediate diff --git a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameter.kt b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameter.kt index d5b61d1be8..07240f2bf7 100644 --- a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameter.kt +++ b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameter.kt @@ -110,7 +110,7 @@ object TestEnvironmentsDiameter { mustHave(3.subnetworks()) specificNodeInASegmentedNetworkShouldHaveDiameter(0, 8.49) specificNodeInASegmentedNetworkShouldHaveDiameter(1, 6.32) - specificNodeInASegmentedNetworkShouldHaveDiameter(nodeCount - 1, 0.0) + specificNodeInASegmentedNetworkShouldHaveDiameter(nodeCount.current - 1, 0.0) } } } diff --git a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameterWithHopDistance.kt b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameterWithHopDistance.kt index b6ea6b8028..6ba4e81255 100644 --- a/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameterWithHopDistance.kt +++ b/alchemist-implementationbase/src/test/kotlin/it/unibo/alchemist/util/TestEnvironmentsDiameterWithHopDistance.kt @@ -103,7 +103,7 @@ object TestEnvironmentsDiameterWithHopDistance { withHopDistanceMustHave(3.subnetworks()) specificNodeInASegmentedNetworkShouldHaveHopDiameter(0, 2.0) specificNodeInASegmentedNetworkShouldHaveHopDiameter(1, 1.0) - specificNodeInASegmentedNetworkShouldHaveHopDiameter(nodeCount - 1, 0.0) + specificNodeInASegmentedNetworkShouldHaveHopDiameter(nodeCount.current - 1, 0.0) } } } diff --git a/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt index 578c697edf..96ac360d9b 100644 --- a/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt +++ b/alchemist-incarnation-biochemistry/src/main/kotlin/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInNeighbor.kt @@ -85,6 +85,10 @@ class BiomolPresentInNeighbor( * This object stores the `serialVersionUID` constant required for serialization purposes. */ companion object { + + /** + * Unique ID for deserialization purposes. + */ @Serial const val serialVersionUID: Long = 499903479123400111L } } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt index 25ad0f2ed2..7731a0ed29 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt @@ -25,7 +25,7 @@ class NetworkCentroid : Extractor { reaction: Actionable?, time: Time, step: Long, - ): Map = when (environment.nodeCount) { + ): Map = when (environment.nodeCount.current) { 0 -> columnNames.associate { it to NaN } else -> environment.networkHub().toList().mapIndexed { index, value -> @@ -40,6 +40,6 @@ class NetworkCentroid : Extractor { sums[index] += value } } - return sums.map { it / nodeCount } + return sums.map { it / nodeCount.current } } } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt index 5ece6247dc..19ad8a5ab3 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt @@ -46,7 +46,7 @@ class NetworkDensity : Extractor { val area = (boundingBox.maxX - boundingBox.minX) * (boundingBox.maxY - boundingBox.minY) return when { area <= 0 || area.isInfinite() -> Double.NaN - else -> nodeCount / area + else -> nodeCount.current / area } } } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt index a683bf29c5..52e30f5ece 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt @@ -22,13 +22,13 @@ import it.unibo.alchemist.model.Time */ class NodesPositions>(private val environment: Environment) : AbstractDoubleExporter() { override val columnNames: List by lazy { - (0 until environment.nodeCount).flatMap { nodeId -> + (0 until environment.nodeCount.current).flatMap { nodeId -> (0 until environment.dimensions).map { dimensionIndex -> columnNameFormat(nodeId, Dimension(dimensionIndex)) } } } - private val expectedNodesCount: Int by lazy { environment.nodeCount } + private val expectedNodesCount: Int by lazy { environment.nodeCount.current } private val maxNodeId: Int by lazy { environment.nodes.maxOfOrNull { it.id } ?: error("No nodes in the environment") } @@ -51,7 +51,7 @@ class NodesPositions>(private val environment: Environment checkExtractCondition(environment: Environment) { - require(expectedNodesCount == environment.nodeCount) { + require(expectedNodesCount == environment.nodeCount.current) { "The number of nodes in the environment is ${environment.nodeCount}, but $expectedNodesCount was expected" } val currentMaxNodeId = environment.nodes.maxOfOrNull { it.id } ?: error("No nodes in the environment") diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NumberOfNodes.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NumberOfNodes.kt index 125c608d9d..3344507104 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NumberOfNodes.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NumberOfNodes.kt @@ -25,7 +25,7 @@ class NumberOfNodes : Extractor { reaction: Actionable?, time: Time, step: Long, - ): Map = mapOf(NAME to environment.nodeCount) + ): Map = mapOf(NAME to environment.nodeCount.current) private companion object { private const val NAME: String = "nodes" diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/util/GraphStreamSupport.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/util/GraphStreamSupport.kt index 9a563e3379..655af9e966 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/util/GraphStreamSupport.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/util/GraphStreamSupport.kt @@ -163,7 +163,7 @@ class GraphStreamSupport>(val linkingRule: LinkingRule(environment.nodeCount, graph)) { + return GraphStreamSupport(OffsetGraphStreamLinkingRule(environment.nodeCount.current, graph)) { originalCoordinates.stream().map { val shifted = it.zoomAndPan() if (is3D) { diff --git a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java index 0285af0269..ce71a4ed7c 100755 --- a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java +++ b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java @@ -74,7 +74,7 @@ void testLoadGPSTrace() { assertNotNull(res, "Missing test resource testgps.yml"); final Simulation simulation = LoadAlchemist.from(res).getDefault(); final Environment environment = simulation.getEnvironment(); - assertTrue(environment.getNodeCount() > 0); + assertTrue(environment.getNodeCount().getCurrent() > 0); environment.getNodes().forEach(node -> { final var reactions = node.getReactions(); assertFalse(reactions.isEmpty()); diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt index fa4c879223..88f92ef74b 100644 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt +++ b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStream.kt @@ -26,7 +26,7 @@ class TestGraphStream : .getDefault() .environment "displace all nodes" - { - environment.nodeCount shouldBeExactly 400 + environment.nodeCount.current shouldBeExactly 400 "with neighbors closer than non-neighbors" { environment.nodes.forEach { node -> val neighborhood = environment.getNeighborhood(node).current diff --git a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt index 2315bcffc6..3e67b18851 100644 --- a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt +++ b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/linkingrules/TestInSightConnection.kt @@ -29,7 +29,7 @@ class TestInSightConnection : .from(ResourceLoader.getResource("simulations/connect-sight.yml")) .getDefault() .environment as OSMEnvironment - environment.nodeCount shouldBe 102 + environment.nodeCount.current shouldBe 102 val node0 = environment.getNodeByID(0) val node1 = environment.getNodeByID(1) val rule = environment.linkingRule diff --git a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt index 170d92c389..8f88398196 100644 --- a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt +++ b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt @@ -139,7 +139,7 @@ class DrawDirectedNode : it.unibo.alchemist.boundary.swingui.effect.api.Effect { ?.toDouble() ?.let { Color.getHSBColor( - (it / (maxValue.toDoubleOrNull() ?: environment.nodeCount.toDouble())).toFloat(), + (it / (maxValue.toDoubleOrNull() ?: environment.nodeCount.current.toDouble())).toFloat(), 1f, 1f, ) From 3d54c8af173244fd57e5149acdefb60acf5910e7 Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 09:51:56 +0100 Subject: [PATCH 06/11] fix(biochemistry): use current node count in assertions --- .../model/biochemistry/actions/TestChemotaxis.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java index e47cec940d..a0771cd57d 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java @@ -232,7 +232,7 @@ void testChemotacticMove1() { environment.addNode(envNode3, p3); environment.addNode(envNode4, p4); environment.addNode(cellNode1, p5); - assertEquals(EXPECTED_NODES, environment.getNodeCount()); + assertEquals(EXPECTED_NODES, environment.getNodeCount().getCurrent()); envNode4.setConcentration(biomolA, CONCENTRATION2); envNode2.setConcentration(biomolA, CONCENTRATION1); envNode3.setConcentration(biomolA, CONCENTRATION1); @@ -262,7 +262,7 @@ void testChemotacticMove2() { environment.addNode(envNode3, p3); environment.addNode(envNode4, p4); environment.addNode(cellNode1, p5); - assertEquals(EXPECTED_NODES, environment.getNodeCount()); + assertEquals(EXPECTED_NODES, environment.getNodeCount().getCurrent()); envNode4.setConcentration(biomolA, CONCENTRATION2); envNode2.setConcentration(biomolA, CONCENTRATION1); envNode3.setConcentration(biomolA, CONCENTRATION1); @@ -297,7 +297,7 @@ void testChemotacticMove3() { environment.addNode(envNode3, p3); environment.addNode(envNode4, p4); environment.addNode(cellNode1, p5); - assertEquals(EXPECTED_NODES, environment.getNodeCount()); + assertEquals(EXPECTED_NODES, environment.getNodeCount().getCurrent()); envNode4.setConcentration(biomolA, CONCENTRATION2); envNode2.setConcentration(biomolA, CONCENTRATION2); envNode3.setConcentration(biomolA, CONCENTRATION2); From 953507850a80182673a6b689464f55b1393be60c Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 10:06:52 +0100 Subject: [PATCH 07/11] chore(api): add `updateOrNull` with observable map values --- .../alchemist/model/observation/ObservableMap.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt index 08a1af8fb9..736581ce9e 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableMap.kt @@ -13,6 +13,7 @@ import arrow.core.Option import arrow.core.none import arrow.core.some import it.unibo.alchemist.model.observation.MutableObservable.Companion.observe +import it.unibo.alchemist.model.observation.Observable.ObservableExtensions.currentOrNull import java.util.Collections /** @@ -248,5 +249,16 @@ open class ObservableMutableMap(private val backingMap: MutableMap = valueUpdate(it.getOrNull()).apply { put(key, this) }.some() } } + + /** + * Updates the value associated with the given key in the map by applying the provided transformation function. + * If the key does not exist or its current value is `null`, no operation is performed, and `null` is returned. + * + * @param key The key whose associated value is to be updated. + * @param valueUpdate A function that defines how the current value should be updated. + * @return The updated value if the key exists and its value is not `null`; otherwise, `null`. + */ + fun ObservableMutableMap.updateOrNull(key: K, valueUpdate: V.() -> Unit): V? = + this[key].currentOrNull()?.let { it.apply(valueUpdate).also { new -> this[key] = new } } } } From a776dd2e9c7a629451df38063520fbd07062c17e Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 10:26:13 +0100 Subject: [PATCH 08/11] feat!: fully transition to observable positions --- .../it/unibo/alchemist/model/Environment.kt | 8 +- .../alchemist/model/EuclideanEnvironment.kt | 2 +- .../cognitive/SteeringActionWithTarget.kt | 2 +- .../actions/AbstractGroupSteeringAction.kt | 2 +- .../actions/AbstractNavigationAction.kt | 4 +- .../actions/CognitiveAgentAvoidLayer.kt | 2 +- .../environments/EnvironmentWithDynamics.kt | 4 +- .../model/cognitive/navigation/Explore.kt | 2 +- .../navigation/ReachKnownDestination.kt | 4 +- .../model/cognitive/properties/Cognitive.kt | 2 +- .../properties/PhysicalPedestrian2D.kt | 6 +- .../model/cognitive/steering/Weighted.kt | 2 +- .../model/cognitive/TestFeelsTransmission.kt | 2 +- .../model/cognitive/TestOrientingBehavior.kt | 10 +- .../model/cognitive/TestSteeringBehaviors.kt | 22 ++-- .../properties/TestPhysicalPedestrians.kt | 2 +- .../model/linkingrules/ConnectionBeam.java | 4 +- .../model/actions/FollowAtDistance.kt | 2 +- .../model/movestrategies/RandomTarget.kt | 2 +- .../model/surrogates/EnvironmentSurrogate.kt | 2 +- .../schema/model/EnvironmentSurrogateTest.kt | 2 +- .../actions/AbstractConfigurableMoveNode.java | 2 +- .../model/actions/AbstractMoveNode.java | 4 +- .../model/actions/MoveForwardAndTeleport.java | 2 +- .../ObstaclesBreakConnection.java | 8 +- .../model/environments/AbstractEnvironment.kt | 13 +- .../movestrategies/target/FollowTarget.kt | 2 +- .../model/terminators/StableForSteps.kt | 2 +- .../actions/CellTensionPolarization.java | 4 +- .../actions/ChemotacticPolarization.java | 8 +- .../conditions/BiomolPresentInEnv.java | 2 +- .../BioRect2DEnvironmentNoOverlap.java | 10 +- .../biochemistry/actions/TestChemotaxis.java | 10 +- .../TestBioRect2DEnvironmentNoOverlap.java | 112 +++++++++--------- .../biochemistry/layers/TestBiomolLayer.java | 2 +- .../nodes/TestEnvironmentNodes.java | 8 +- .../properties/TestDeformableCell.java | 12 +- .../protelis/AlchemistExecutionContext.kt | 5 +- .../protelis/properties/ProtelisDevice.kt | 3 +- .../actions/AbstractSAPEREMoveNodeAgent.java | 6 +- .../actions/AbstractSAPERENeighborAgent.java | 9 +- .../actions/SAPEREWalkerRiseGradient.java | 6 +- .../sapere/reactions/SAPEREGradient.java | 4 +- .../sapere/reactions/SAPEREReaction.java | 4 +- .../actions/RunScafiProgram.scala | 2 +- .../scafi/ScafiIncarnationForAlchemist.scala | 2 +- .../boundary/extractors/NetworkCentroid.kt | 2 +- .../boundary/extractors/NetworkDensity.kt | 2 +- .../boundary/extractors/NodesPositions.kt | 2 +- .../deployments/CloseToAlreadyDeployed.kt | 2 +- .../alchemist/model/TestLoadGPSTrace.java | 4 +- .../unibo/alchemist/model/TestYAMLLoader.java | 2 +- .../test/TestGraphStreamReproducibility.kt | 2 +- .../alchemist/test/TestSpecificPositions.kt | 2 +- .../maps/environments/OSMEnvironment.java | 4 +- .../StraightLineTraceDependantSpeed.java | 2 +- .../actions/RandomTargetInPolygonOnMap.kt | 2 +- .../model/maps/actions/TestTargetMapWalker.kt | 32 ++--- .../physics/environments/MuseumHall.java | 4 +- .../model/physics/InfluenceSphere2D.kt | 2 +- .../model/physics/actions/HeadTowardTarget.kt | 4 +- .../AbstractLimitedContinuous2D.kt | 2 +- .../ContinuousPhysics2DEnvironment.kt | 4 +- .../TestEuclideanPhysics2DEnvironment.kt | 10 +- ...ameraInjectVisibleNodeClosestToDistance.kt | 2 +- .../alchemist/model/actions/CameraSee.kt | 2 +- .../boundary/swingui/effect/api/Effect.java | 2 +- .../swingui/effect/impl/AbstractDrawOnce.java | 2 +- .../effect/impl/DrawPedestrianPath.java | 2 +- .../swingui/effect/impl/DrawSmartcam.java | 2 +- .../monitor/impl/Generic2DDisplay.java | 4 +- .../boundary/wormhole/impl/MapWormhole.java | 2 +- .../swingui/effect/impl/DrawDirectedNode.kt | 4 +- .../swingui/monitor/impl/NodeTracker.kt | 2 +- .../surrogates/utility/ToNodeSurrogate.kt | 2 +- .../server/utility/ToNodeSurrogateTest.kt | 2 +- 76 files changed, 231 insertions(+), 211 deletions(-) diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt index d41673bb86..9ffe143f4e 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/Environment.kt @@ -157,14 +157,14 @@ interface Environment> : val offset: DoubleArray /** - * Calculates the position of a [node]. + * Observe the position of a [node]. */ - fun getPosition(node: Node): P + fun getPosition(node: Node): Observable

/** - * Observe the position of a [node]. + * Retrieves [node]'s current position. */ - fun observePosition(node: Node): Observable

+ fun getCurrentPosition(node: Node): P = getPosition(node).current /** * Return the current [Simulation], if present, or throws an [IllegalStateException] otherwise. diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/EuclideanEnvironment.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/EuclideanEnvironment.kt index c240de013b..db0a979a32 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/EuclideanEnvironment.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/EuclideanEnvironment.kt @@ -25,7 +25,7 @@ interface EuclideanEnvironment : Environment where P : Position

, * method may suffice. */ fun moveNode(node: Node, direction: P) { - val oldcoord = getPosition(node) + val oldcoord = getCurrentPosition(node) moveNodeToPosition(node, oldcoord.plus(direction)) } diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/SteeringActionWithTarget.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/SteeringActionWithTarget.kt index 03e8dd8355..7e75049968 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/SteeringActionWithTarget.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/SteeringActionWithTarget.kt @@ -27,5 +27,5 @@ interface SteeringActionWithTarget : SteeringAction where P : Positi * Computes the distance between this action's target and the given [node]. */ fun targetDistanceTo(node: Node, environment: Environment): Double = - target().distanceTo(environment.getPosition(node)) + target().distanceTo(environment.getCurrentPosition(node)) } diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractGroupSteeringAction.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractGroupSteeringAction.kt index b6f7081226..93faca6109 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractGroupSteeringAction.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractGroupSteeringAction.kt @@ -33,6 +33,6 @@ abstract class AbstractGroupSteeringAction( * Computes the centroid of the [group] in absolute coordinates. */ protected fun centroid(): P = with(group()) { - map { environment.getPosition(it) }.reduce { acc, pos -> acc + pos } / size.toDouble() + map { environment.getCurrentPosition(it) }.reduce { acc, pos -> acc + pos } / size.toDouble() } } diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractNavigationAction.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractNavigationAction.kt index 14cb92646b..a8bac7bd74 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractNavigationAction.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/AbstractNavigationAction.kt @@ -127,7 +127,7 @@ abstract class AbstractNavigationAction( * Otherwise the first room containing [pedestrianPosition] is used. */ protected open fun updateCachedVariables() { - pedestrianPosition = environment.getPosition(navigatingNode) + pedestrianPosition = environment.getCurrentPosition(navigatingNode) currentRoom = when { (state == MOVING_TO_CROSSING_POINT_1 || state == MOVING_TO_FINAL) && @@ -223,7 +223,7 @@ abstract class AbstractNavigationAction( /* * Always up to date current position. */ - else -> environment.getPosition(navigatingNode) + else -> environment.getCurrentPosition(navigatingNode) } /** diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/CognitiveAgentAvoidLayer.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/CognitiveAgentAvoidLayer.kt index 8ca7120fd8..d2d1636284 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/CognitiveAgentAvoidLayer.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/actions/CognitiveAgentAvoidLayer.kt @@ -76,7 +76,7 @@ constructor( */ @Suppress("UNCHECKED_CAST") private fun isDangerInSight(): Boolean = getLayerOrFail().center()?.let { center -> - val currentPosition = environment.getPosition(node) + val currentPosition = environment.getCurrentPosition(node) /* * environment is euclidean, so if it has obstacles it must be an * EnvironmentWithObstacles<*, *, Euclidean2DPosition>. Since generic types can't be checked at runtime, this diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/environments/EnvironmentWithDynamics.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/environments/EnvironmentWithDynamics.kt index 76f3fa38d9..6fddaf0d31 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/environments/EnvironmentWithDynamics.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/environments/EnvironmentWithDynamics.kt @@ -149,7 +149,7 @@ constructor( override fun moveNode(node: Node, direction: Euclidean2DPosition) { backingEnvironment.moveNode(node, direction) - moveNodeBodyToPosition(node, backingEnvironment.getPosition(node)) + moveNodeBodyToPosition(node, backingEnvironment.getCurrentPosition(node)) } private fun addPhysicalProperties(body: PhysicsBody, radius: Double) { @@ -175,7 +175,7 @@ constructor( } } - override fun getPosition(node: Node): Euclidean2DPosition = nodeToBody[node]?.position + override fun getCurrentPosition(node: Node): Euclidean2DPosition = nodeToBody[node]?.position ?: throw IllegalArgumentException("Unable to find $node's position in the environment.") private val PhysicsBody.position get() = diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/Explore.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/Explore.kt index dfe9ba6b85..7858db4d0b 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/Explore.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/Explore.kt @@ -123,7 +123,7 @@ open class Explore( environment .getNodesWithinRange(centroid, radius) .asSequence() - .map { environment.getPosition(it) } + .map { environment.getCurrentPosition(it) } .count { contains(it) } .let { it * node.area / area } .coerceAtMost(1.0) diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/ReachKnownDestination.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/ReachKnownDestination.kt index 103e9d90d4..3410b9aeb9 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/ReachKnownDestination.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/navigation/ReachKnownDestination.kt @@ -46,7 +46,7 @@ open class ReachKnownDestination( init { route = emptyList().takeIf { destinations.isEmpty() } ?: with(action) { - val currPos = environment.getPosition(navigatingNode) + val currPos = environment.getCurrentPosition(navigatingNode) val (closestDest, distanceToClosestDest) = destinations .asSequence() @@ -85,7 +85,7 @@ open class ReachKnownDestination( */ private fun findKnownPathTo(destination: Euclidean2DPosition): List = with(orientingCapability.cognitiveMap) { emptyList().takeIf { vertexSet().isEmpty() } ?: let { - val currPos = environment.getPosition(node) + val currPos = environment.getCurrentPosition(node) val currRoom = environment.graph.nodeContaining(currPos) val destRoom = environment.graph.nodeContaining(destination) if (currRoom == null || destRoom == null) { diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/Cognitive.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/Cognitive.kt index e2e182e930..96b636b9d5 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/Cognitive.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/Cognitive.kt @@ -45,7 +45,7 @@ constructor( ) { when (danger) { null -> 0.0 - else -> environment.getLayer(danger)?.getValue(environment.getPosition(node)) as? Double ?: 0.0 + else -> environment.getLayer(danger)?.getValue(environment.getCurrentPosition(node)) as? Double ?: 0.0 } } } diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/PhysicalPedestrian2D.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/PhysicalPedestrian2D.kt index 0c150c5e6f..935bc18e19 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/PhysicalPedestrian2D.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/properties/PhysicalPedestrian2D.kt @@ -70,7 +70,7 @@ class PhysicalPedestrian2D( environment .shapeFactory .circle(nodeShape.radius + comfortRay) - .transformed { origin(environment.getPosition(node)) } + .transformed { origin(environment.getCurrentPosition(node)) } override val rectangleOfInfluence: Euclidean2DShape get() = environment @@ -84,7 +84,7 @@ class PhysicalPedestrian2D( private val fallenAgentPerceptionArea get() = environment.shapeFactory.circle(FALLEN_AGENT_PERCEPTION_RADIUS).transformed { origin(node.position) } - private val Node.position get() = environment.getPosition(this) + private val Node.position get() = environment.getCurrentPosition(this) override fun checkAndPossiblyFall() { if (!isFallen && shouldFall(repulsionForces())) { @@ -101,7 +101,7 @@ class PhysicalPedestrian2D( } override fun repulse(other: Node): Euclidean2DPosition { - val myShape = nodeShape.transformed { origin(environment.getPosition(node)) } + val myShape = nodeShape.transformed { origin(environment.getCurrentPosition(node)) } val otherShape = environment.getShape(other) return (myShape.centroid - otherShape.centroid).let { val desiredDistance = myShape.radius + comfortRay + otherShape.radius diff --git a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/steering/Weighted.kt b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/steering/Weighted.kt index 65aee31226..605c64f02a 100644 --- a/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/steering/Weighted.kt +++ b/alchemist-cognitive-agents/src/main/kotlin/it/unibo/alchemist/model/cognitive/steering/Weighted.kt @@ -48,7 +48,7 @@ open class Weighted( * the closest target is picked. */ override fun computeTarget(actions: List>): Euclidean2DPosition = - environment.getPosition(node).let { currPos -> + environment.getCurrentPosition(node).let { currPos -> actions .filterIsInstance>() .map { it.target() } diff --git a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestFeelsTransmission.kt b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestFeelsTransmission.kt index 6e4d1478f2..00b94a83ab 100644 --- a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestFeelsTransmission.kt +++ b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestFeelsTransmission.kt @@ -61,7 +61,7 @@ class TestFeelsTransmission { val reference = environment.makePosition(-50.0, 0.0) environment.nodes.forEach { assertTrue( - environment.getPosition(it).distanceTo(reference) < 13.0, + environment.getCurrentPosition(it).distanceTo(reference) < 13.0, "Node should have moved closer to the evacuation reference point", ) } diff --git a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestOrientingBehavior.kt b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestOrientingBehavior.kt index 55b9b14e53..ededd28e44 100644 --- a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestOrientingBehavior.kt +++ b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestOrientingBehavior.kt @@ -46,7 +46,7 @@ class TestOrientingBehavior : val target = environment.makePosition(*coords) environment.nodes .orienting() - .forEach { p -> environment.getPosition(p).distanceTo(target) shouldBeLessThan tolerance } + .forEach { p -> environment.getCurrentPosition(p).distanceTo(target) shouldBeLessThan tolerance } } /** @@ -69,7 +69,7 @@ class TestOrientingBehavior : steps = 500, onceInitialized = { it.nodes.size shouldBe 1 }, atEachStep = { environment: Environment, _, _, _ -> - val currentPosition = environment.getPosition(environment.nodes.first()) + val currentPosition = environment.getCurrentPosition(environment.nodes.first()) previousPositions.add(currentPosition) if (previousPositions.size == expectedSize) { previousPositions.distinct() shouldNotBe 1 @@ -92,7 +92,9 @@ class TestOrientingBehavior : if (environment is Euclidean2DEnvironmentWithGraph<*, T, *, *>) { val node = environment.nodes.first() val waypointToSkip = environment.makePosition(70, 105) - environment.getPosition(node).shouldNotBeIn(environment.graph.nodeContaining(waypointToSkip)) + environment.getCurrentPosition( + node, + ).shouldNotBeIn(environment.graph.nodeContaining(waypointToSkip)) } }, whenFinished = { environment, _, _ -> assertPedestriansReached(environment, 1.0, 85, 80) }, @@ -133,7 +135,7 @@ class TestOrientingBehavior : if (environment is Euclidean2DEnvironmentWithGraph<*, T, *, *> && !corridorTaken) { val node = environment.nodes.orienting().first() val corridorToTake = environment.graph.nodeContaining(environment.makePosition(35.0, 31.0)) - corridorTaken = corridorToTake?.contains(environment.getPosition(node)) ?: false + corridorTaken = corridorToTake?.contains(environment.getCurrentPosition(node)) ?: false } }, whenFinished = { environment, _, _ -> diff --git a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestSteeringBehaviors.kt b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestSteeringBehaviors.kt index 9cb273f54a..d7ce678ea6 100644 --- a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestSteeringBehaviors.kt +++ b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/TestSteeringBehaviors.kt @@ -36,12 +36,12 @@ class TestSteeringBehaviors : .startSimulation( onceInitialized = { environment -> environment.nodes.forEach { - startDistances[it] = environment.getPosition(it).distanceTo(environment.origin) + startDistances[it] = environment.getCurrentPosition(it).distanceTo(environment.origin) } }, whenFinished = { environment, _, _ -> environment.nodes.forEach { - endDistances[it] = environment.getPosition(it).distanceTo(environment.origin) + endDistances[it] = environment.getCurrentPosition(it).distanceTo(environment.origin) } }, ).nodes @@ -55,12 +55,12 @@ class TestSteeringBehaviors : .startSimulation( onceInitialized = { e -> e.nodes.forEach { - startDistances[it] = e.getPosition(it).distanceTo(e.origin) + startDistances[it] = e.getCurrentPosition(it).distanceTo(e.origin) } }, whenFinished = { e, _, _ -> e.nodes.forEach { - endDistances[it] = e.getPosition(it).distanceTo(e.origin) + endDistances[it] = e.getCurrentPosition(it).distanceTo(e.origin) } }, ).nodes @@ -73,7 +73,7 @@ class TestSteeringBehaviors : startSimulation( atEachStep = { e, _, _, _ -> e.nodes.forEach { - nodesPositions[it]?.add(e.getPosition(it)) + nodesPositions[it]?.add(e.getCurrentPosition(it)) } }, ) @@ -106,8 +106,12 @@ class TestSteeringBehaviors : .groupBy { it.asProperty>().group } .values .forEach { - for (nodePos in it.map { node -> e.getPosition(node) }) { - for (otherPos in (it.map { node -> e.getPosition(node) }.minusElement(nodePos))) { + for (nodePos in it.map { node -> e.getCurrentPosition(node) }) { + for (otherPos in ( + it.map { node -> + e.getCurrentPosition(node) + }.minusElement(nodePos) + )) { nodePos.distanceTo(otherPos) shouldBeLessThan 4.0 } } @@ -120,7 +124,7 @@ class TestSteeringBehaviors : "nodes using separation behavior keep a distance to each other" { loadYamlSimulation("separation.yml").startSimulation( whenFinished = { e, _, _ -> - with(e.nodes.map { e.getPosition(it) }) { + with(e.nodes.map { e.getCurrentPosition(it) }) { for (nodePos in this) { for (otherPos in (this.minusElement(nodePos))) { nodePos.distanceTo(otherPos) shouldBeGreaterThan 6.0 @@ -136,7 +140,7 @@ class TestSteeringBehaviors : loadYamlSimulation("obstacle-avoidance.yml").startSimulation( whenFinished = { e, _, _ -> e.nodes.forEach { - e.getPosition(it).distanceTo(e.makePosition(600.0, 240.0)) shouldBeLessThan 10.0 + e.getCurrentPosition(it).distanceTo(e.makePosition(600.0, 240.0)) shouldBeLessThan 10.0 } }, steps = 26000, diff --git a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/properties/TestPhysicalPedestrians.kt b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/properties/TestPhysicalPedestrians.kt index 165c36bf02..7a49547048 100644 --- a/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/properties/TestPhysicalPedestrians.kt +++ b/alchemist-cognitive-agents/src/test/kotlin/it/unibo/alchemist/model/cognitive/properties/TestPhysicalPedestrians.kt @@ -22,7 +22,7 @@ class TestPhysicalPedestrians : loadYamlSimulation("pushing_behavior.yml").startSimulation( steps = 35000, whenFinished = { environment, _, _ -> - environment.getPosition(environment.nodes.first()) shouldNotBe environment.makePosition(0, 0) + environment.getCurrentPosition(environment.nodes.first()) shouldNotBe environment.makePosition(0, 0) }, ) } diff --git a/alchemist-euclidean-geometry/src/main/java/it/unibo/alchemist/model/linkingrules/ConnectionBeam.java b/alchemist-euclidean-geometry/src/main/java/it/unibo/alchemist/model/linkingrules/ConnectionBeam.java index 88306ae8cf..5c984d3fac 100644 --- a/alchemist-euclidean-geometry/src/main/java/it/unibo/alchemist/model/linkingrules/ConnectionBeam.java +++ b/alchemist-euclidean-geometry/src/main/java/it/unibo/alchemist/model/linkingrules/ConnectionBeam.java @@ -84,10 +84,10 @@ public Neighborhood computeNeighborhood(final Node center, final Environme }); } if (!normal.isEmpty()) { - final Euclidean2DPosition cp = environment.getPosition(center); + final Euclidean2DPosition cp = environment.getCurrentPosition(center); final List> neighs = normal.getNeighbors().stream() .filter(neigh -> { - final Euclidean2DPosition np = environment.getPosition(neigh); + final Euclidean2DPosition np = environment.getCurrentPosition(neigh); return !oenv.intersectsObstacle(cp, np) || projectedBeamOvercomesObstacle(cp, np); }) .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); diff --git a/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/actions/FollowAtDistance.kt b/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/actions/FollowAtDistance.kt index 56c9adc207..5a5ccfa516 100644 --- a/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/actions/FollowAtDistance.kt +++ b/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/actions/FollowAtDistance.kt @@ -55,7 +55,7 @@ class FollowAtDistance( override fun execute() { node.getConcentration(target)?.also { val targetPosition = it.toPosition(environment) - val currentPosition = environment.getPosition(node) + val currentPosition = environment.getCurrentPosition(node) var destination = targetPosition.surroundingPointAt(currentPosition - targetPosition, distance) if (currentPosition != destination) { // avoid "bouncing" val currentSpeed = diff --git a/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/movestrategies/RandomTarget.kt b/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/movestrategies/RandomTarget.kt index 1c05197211..3e7eb26efe 100644 --- a/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/movestrategies/RandomTarget.kt +++ b/alchemist-euclidean-geometry/src/main/kotlin/it/unibo/alchemist/model/movestrategies/RandomTarget.kt @@ -44,7 +44,7 @@ class RandomTarget( distanceDistribution: RealDistribution, ) : this( environment, - { environment.getPosition(node) }, + { environment.getCurrentPosition(node) }, { x, y -> environment.makePosition(x, y) }, directionRng, distanceDistribution, diff --git a/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt b/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt index 4db619328c..a55f20006b 100644 --- a/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt +++ b/alchemist-graphql-surrogates/src/main/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/surrogates/EnvironmentSurrogate.kt @@ -72,7 +72,7 @@ data class EnvironmentSurrogate>( * Returns a [NodeToPosMap] representing all nodes associated with their position. */ @GraphQLDescription("A list of entries NodeId-Position") - fun nodeToPos(): NodeToPosMap = origin.nodes.associate { it.id to origin.getPosition(it) }.toNodeToPosMap() + fun nodeToPos(): NodeToPosMap = origin.nodes.associate { it.id to origin.getCurrentPosition(it) }.toNodeToPosMap() /** * Returns the neighborhood of the node with the given id. diff --git a/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt b/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt index d0c2a80c8a..6c6186b2b8 100644 --- a/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt +++ b/alchemist-graphql/src/jvmTest/kotlin/it/unibo/alchemist/boundary/graphql/schema/model/EnvironmentSurrogateTest.kt @@ -37,7 +37,7 @@ class EnvironmentSurrogateTest where T : Any, P : Position

, P : Vector< envWrapper.nodes.forEach { node -> val nodeSurrogate = envSurrogate.nodeById(node.id) checkNodeSurrogate(node, nodeSurrogate) - checkPositionSurrogate(envWrapper.getPosition(node), envSurrogate.nodeToPos()[node.id]!!) + checkPositionSurrogate(envWrapper.getCurrentPosition(node), envSurrogate.nodeToPos()[node.id]!!) checkNeighborhood(envWrapper.getNeighborhood(node).current, envSurrogate.getNeighborhood(node.id)) } // Test propagation of changes diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractConfigurableMoveNode.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractConfigurableMoveNode.java index 5f1b47cd29..3c2b5450f6 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractConfigurableMoveNode.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractConfigurableMoveNode.java @@ -104,7 +104,7 @@ public final P getNextPosition() { double maxWalk = speedSelectionStrategy.getNodeMovementLength(end); final Environment environment = getEnvironment(); final Node node = getNode(); - P curPos = environment.getPosition(node); + P curPos = environment.getCurrentPosition(node); if (curPos.distanceTo(end) <= maxWalk) { final P destination = end; end = targetSelectionStrategy.getTarget(); diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractMoveNode.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractMoveNode.java index 0ed5f19581..e11207ccee 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractMoveNode.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/AbstractMoveNode.java @@ -78,7 +78,7 @@ public void execute() { } else { environment.moveNodeToPosition( getNode(), - environment.getPosition(getNode()).plus(getNextPosition().getCoordinates()) + environment.getCurrentPosition(getNode()).plus(getNextPosition().getCoordinates()) ); } } @@ -116,7 +116,7 @@ protected final P getCurrentPosition() { * @return the position of the node */ protected final P getNodePosition(final Node n) { - return environment.getPosition(n); + return environment.getCurrentPosition(n); } /** diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/MoveForwardAndTeleport.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/MoveForwardAndTeleport.java index 8d88531736..57fe36de11 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/MoveForwardAndTeleport.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/actions/MoveForwardAndTeleport.java @@ -63,7 +63,7 @@ public MoveForwardAndTeleport cloneAction(final Node node, final Reacti @Override public P getNextPosition() { - final P cur = getEnvironment().getPosition(getNode()); + final P cur = getEnvironment().getCurrentPosition(getNode()); if (Double.isNaN(y)) { y = cur.getY(); } diff --git a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ObstaclesBreakConnection.java b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ObstaclesBreakConnection.java index 2508b44f2c..1b6acc8834 100644 --- a/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ObstaclesBreakConnection.java +++ b/alchemist-implementationbase/src/main/java/it/unibo/alchemist/model/linkingrules/ObstaclesBreakConnection.java @@ -45,15 +45,15 @@ public ObstaclesBreakConnection(final Double radius) { public Neighborhood computeNeighborhood(final Node center, final Environment environment) { Neighborhood normal = super.computeNeighborhood(center, environment); if (!normal.isEmpty() && environment instanceof final EnvironmentWithObstacles environmentWithObstacles) { - final P centerPosition = environment.getPosition(center); + final P centerPosition = environment.getCurrentPosition(center); environmentWithObstacles.intersectsObstacle( - environmentWithObstacles.getPosition(center), - environmentWithObstacles.getPosition(center) + environmentWithObstacles.getCurrentPosition(center), + environmentWithObstacles.getCurrentPosition(center) ); final Iterable> neighbors = StreamSupport.stream(normal.spliterator(), false) .filter(node -> !environmentWithObstacles - .intersectsObstacle(centerPosition, environmentWithObstacles.getPosition(node)) + .intersectsObstacle(centerPosition, environmentWithObstacles.getCurrentPosition(node)) ) .collect(Collectors.toList()); normal = Neighborhoods.make(environmentWithObstacles, center, neighbors); diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt index daa98f8e37..315d3e47c2 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/environments/AbstractEnvironment.kt @@ -252,7 +252,8 @@ abstract class AbstractEnvironment> protected constructor( return initialNodes } - override fun getDistanceBetweenNodes(n1: Node, n2: Node): Double = getPosition(n1).distanceTo(getPosition(n2)) + override fun getDistanceBetweenNodes(n1: Node, n2: Node): Double = + retrievePosition(n1).distanceTo(retrievePosition(n2)) override fun getLayer(molecule: Molecule): Layer? = _layers[molecule] @@ -282,7 +283,7 @@ abstract class AbstractEnvironment> protected constructor( override fun getNodeByID(id: Int): Node = nodes.first { n: Node -> n.id == id } override fun getNodesWithinRange(node: Node, range: Double): ListSet> { - val centerPosition = getPosition(node) + val centerPosition = retrievePosition(node) val res = LinkedListSet(getAllNodesInRange(centerPosition, range)) check(res.remove(node)) { "Either the provided range ($range) is too small for queries to work without precision loss, " + @@ -300,12 +301,12 @@ abstract class AbstractEnvironment> protected constructor( } override fun observeNodesWithinRange(node: Node, range: Double): ObservableSet> = - observeAllNodesInRange({ getPosition(node) }, range, node) + observeAllNodesInRange({ retrievePosition(node) }, range, node) override fun observeNodesWithinRange(position: P, range: Double): ObservableSet> = observeAllNodesInRange({ position }, range) - override fun getPosition(node: Node): P = requireNotNull(nodeToPos[node.id]) { + protected fun retrievePosition(node: Node): P = requireNotNull(nodeToPos[node.id]) { check(!nodes.contains(node)) { "Node $node is registered in the environment but has no position. " + "This could be a bug in Alchemist. Please open an issue at: " + @@ -314,7 +315,7 @@ abstract class AbstractEnvironment> protected constructor( "Node $node: ${node.javaClass.simpleName} does not exist in the environment." } - override fun observePosition(node: Node): Observable

= observableNodeToPos[node.id].map { maybePosition -> + override fun getPosition(node: Node): Observable

= observableNodeToPos[node.id].map { maybePosition -> val position = maybePosition.getOrNull() requireNotNull(position) { check(!nodes.contains(node)) { @@ -447,7 +448,7 @@ abstract class AbstractEnvironment> protected constructor( private fun runQuery(center: P, range: Double): List> = spatialIndex .query(*center.boundingBox(range).map { it.coordinates }.toTypedArray()) - .filter { getPosition(it).distanceTo(center) <= range } + .filter { retrievePosition(it).distanceTo(center) <= range } /** * Adds or updates a node's position in the position map. diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/movestrategies/target/FollowTarget.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/movestrategies/target/FollowTarget.kt index 036d500722..8c6e4d1271 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/movestrategies/target/FollowTarget.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/movestrategies/target/FollowTarget.kt @@ -41,7 +41,7 @@ open class FollowTarget>( /** * the current position. */ - protected val currentPosition: P get() = environment.getPosition(node) + protected val currentPosition: P get() = environment.getCurrentPosition(node) override fun getTarget(): P { val conc = node.getConcentration(targetMolecule) ?: return currentPosition diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt index 8a60022aeb..c09a63a374 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableForSteps.kt @@ -57,7 +57,7 @@ data class StableForSteps(private val checkInterval: Long, private val override fun invoke(environment: Environment>): Boolean { if (environment.simulation.step % checkInterval == 0L) { - val newPositions = environment.associateBy({ it }, { environment.getPosition(it) }) + val newPositions = environment.associateBy({ it }, { environment.getCurrentPosition(it) }) val newContents = makeTable(environment.nodeCount.current) environment.forEach { node -> node.contents.forEach { molecule, concentration -> diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/CellTensionPolarization.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/CellTensionPolarization.java index 6499cd158c..0f9d10aa7a 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/CellTensionPolarization.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/CellTensionPolarization.java @@ -71,7 +71,7 @@ public CellTensionPolarization cloneAction(final Node node, final Reacti @Override public void execute() { // get node position as an array - final double[] nodePosistion = environment.getPosition(getNode()).getCoordinates(); + final double[] nodePosistion = environment.getCurrentPosition(getNode()).getCoordinates(); // initializing resulting versor final double[] resultingVersor = new double[nodePosistion.length]; // declaring a variable for the node where this action is set, to have faster access @@ -102,7 +102,7 @@ public void execute() { }) .map(node -> { // position of node n as an array - final double[] nPos = environment.getPosition(node).getCoordinates(); + final double[] nPos = environment.getCurrentPosition(node).getCoordinates(); // max radius of n final double localNodeMaxRadius; // min radius of n diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java index f64f090b6c..2b4d22f9c7 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/actions/ChemotacticPolarization.java @@ -111,9 +111,9 @@ public void execute() { if (l.isEmpty()) { cell.addPolarizationVersor(Euclidean2DPosition.Companion.getZero()); } else { - final boolean isNodeOnMaxConc = environment.getPosition(l.stream() + final boolean isNodeOnMaxConc = environment.getCurrentPosition(l.stream() .max(Comparator.comparingDouble(n -> n.getConcentration(biomolecule))) - .get()).equals(environment.getPosition(thisNode)); + .get()).equals(environment.getCurrentPosition(thisNode)); if (isNodeOnMaxConc) { cell.addPolarizationVersor(environment.makePosition(0, 0)); } else { @@ -138,9 +138,9 @@ public void execute() { private Euclidean2DPosition weightedAverageVectors(final List> list, final Node thisNode) { Euclidean2DPosition res = Euclidean2DPosition.Companion.getZero(); - final Euclidean2DPosition thisNodePos = environment.getPosition(thisNode); + final Euclidean2DPosition thisNodePos = environment.getCurrentPosition(thisNode); for (final Node n : list) { - final Euclidean2DPosition nPos = environment.getPosition(n); + final Euclidean2DPosition nPos = environment.getCurrentPosition(n); Euclidean2DPosition vecTemp = new Euclidean2DPosition( nPos.getX() - thisNodePos.getX(), nPos.getY() - thisNodePos.getY() diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java index 8b5b488a94..958d2b8349 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/conditions/BiomolPresentInEnv.java @@ -85,7 +85,7 @@ private void setUpObservability() { private Observable observeTotalQuantity() { return environment.getNeighborhood(getNode()).mergeWith( - environment.observePosition(getNode()), + environment.getPosition(getNode()), (neighborhood, position) -> { final double quantityInEnvNodes = neighborhood.getNeighbors().stream() .parallel() diff --git a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironmentNoOverlap.java b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironmentNoOverlap.java index dc72f92278..379defe8be 100644 --- a/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironmentNoOverlap.java +++ b/alchemist-incarnation-biochemistry/src/main/java/it/unibo/alchemist/model/biochemistry/environments/BioRect2DEnvironmentNoOverlap.java @@ -84,7 +84,7 @@ protected boolean nodeShouldBeAdded(@Nonnull final Node node, @Nonnull f return range <= 0 || getNodesWithinRange(position, range).stream() .filter(n -> n.asPropertyOrNull(CircularCellProperty.class) != null) - .noneMatch(n -> getPosition(n).distanceTo(position) < nodeRadius + .noneMatch(n -> retrievePosition(n).distanceTo(position) < nodeRadius + n.asProperty(CircularCellProperty.class).getRadius()); } else { return true; @@ -96,7 +96,7 @@ protected boolean nodeShouldBeAdded(@Nonnull final Node node, @Nonnull f @Override public void moveNodeToPosition(@Nonnull final Node node, @Nonnull final Euclidean2DPosition newPosition) { - final double[] cur = getPosition(node).getCoordinates(); + final double[] cur = retrievePosition(node).getCoordinates(); final double[] np = newPosition.getCoordinates(); final Euclidean2DPosition nextWithinLimts = super.next(cur[0], cur[1], np[0], np[1]); if (node.asPropertyOrNull(CircularCellProperty.class) != null) { @@ -158,7 +158,7 @@ private Euclidean2DPosition findNearestFreePosition( range = FastMath.sqrt(FastMath.pow(newHalfDistance, 2) + FastMath.pow(newMaxDiameter, 2)); return getNodesWithinRange(newMidPoint, range).stream() .filter(n -> !n.equals(nodeToMove) && n.asPropertyOrNull(CircularCellProperty.class) != null) - .filter(n -> selectNodes(n, nodeToMove, getPosition(nodeToMove), requestedPos, xVer, yVer)) + .filter(n -> selectNodes(n, nodeToMove, retrievePosition(nodeToMove), requestedPos, xVer, yVer)) .map(n -> getPositionIfNodeIsObstacle(nodeToMove, n, originalPos, oy, ox, ry, rx)) .filter(Optional::isPresent) .map(Optional::get) @@ -175,7 +175,7 @@ private boolean selectNodes( final double yVer ) { // testing if node is between requested position and original position - final Euclidean2DPosition nodePos = getPosition(node); + final Euclidean2DPosition nodePos = retrievePosition(node); final Euclidean2DPosition nodeOrientationFromOrigin = new Euclidean2DPosition(nodePos.getX() - origin.getX(), nodePos.getY() - origin.getY()); final double scalarProductResult1 = xVer * nodeOrientationFromOrigin.getX() @@ -208,7 +208,7 @@ private Optional getPositionIfNodeIsObstacle( final double xr ) { // original position - final Euclidean2DPosition possibleObstaclePosition = getPosition(node); + final Euclidean2DPosition possibleObstaclePosition = retrievePosition(node); // coordinates of original position, requested position and of node's position final double yn = possibleObstaclePosition.getY(); final double xn = possibleObstaclePosition.getX(); diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java index a0771cd57d..1d99185018 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/actions/TestChemotaxis.java @@ -243,7 +243,7 @@ void testChemotacticMove1() { r1.execute(); r2.execute(); assertEquals(new Euclidean2DPosition(0.5 + FastMath.sqrt(0.5), 0.5 + FastMath.sqrt(0.5)), - environment.getPosition(cellNode1) + environment.getCurrentPosition(cellNode1) ); } @@ -273,11 +273,11 @@ void testChemotacticMove2() { r1.execute(); r2.execute(); assertEquals(1, - environment.getPosition(cellNode1).getX(), + environment.getCurrentPosition(cellNode1).getX(), PRECISION ); assertEquals(1, - environment.getPosition(cellNode1).getY(), + environment.getCurrentPosition(cellNode1).getY(), PRECISION ); } @@ -310,11 +310,11 @@ void testChemotacticMove3() { r2.execute(); r2.execute(); assertEquals(0.5, - environment.getPosition(cellNode1).getX(), + environment.getCurrentPosition(cellNode1).getX(), PRECISION ); assertEquals(0.5, - environment.getPosition(cellNode1).getY(), + environment.getCurrentPosition(cellNode1).getY(), PRECISION ); } diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/environments/TestBioRect2DEnvironmentNoOverlap.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/environments/TestBioRect2DEnvironmentNoOverlap.java index 9f84dcf365..a89753cbcf 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/environments/TestBioRect2DEnvironmentNoOverlap.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/environments/TestBioRect2DEnvironmentNoOverlap.java @@ -172,7 +172,7 @@ void testAddNode() { final Euclidean2DPosition p3 = new Euclidean2DPosition(0, 20); // this should be added environment.addNode(n3, p3); - assertEquals(environment.getPosition(n3), p3, getFailureTestString("n3", n3, p3)); + assertEquals(environment.getCurrentPosition(n3), p3, getFailureTestString("n3", n3, p3)); environment.removeNode(n3); final Euclidean2DPosition p4 = new Euclidean2DPosition(0, 10); // this should be added environment.addNode(n4, p4); @@ -209,7 +209,7 @@ private void verifyNotAdded(final Node node) { } private void verifyAdded(final Node node, @Nonnull final Position expected) { - final Position position = environment.getPosition(node); + final Position position = environment.getCurrentPosition(node); final Supplier message = () -> getFailureTestString("node" + node.getId(), node, position); assertEquals(expected, position, message); } @@ -227,8 +227,8 @@ void testMoveNode1() { environment.moveNode(cellToMove1, POSITION_TO_MOVE1); assertEquals( EXPECTED_POS1, - environment.getPosition(cellToMove1), - "cellToMove1 is in position: " + environment.getPosition(cellToMove1) + environment.getCurrentPosition(cellToMove1), + "cellToMove1 is in position: " + environment.getCurrentPosition(cellToMove1) ); environment.removeNode(cellToMove1); environment.removeNode(c1); @@ -250,8 +250,8 @@ void testMoveNode2() { environment.moveNode(cellToMove2, POSITION_TO_MOVE2); assertEquals( EXPECTED_POS2, - environment.getPosition(cellToMove2), - "cellToMove2 is in position: " + environment.getPosition(cellToMove2) + environment.getCurrentPosition(cellToMove2), + "cellToMove2 is in position: " + environment.getCurrentPosition(cellToMove2) ); environment.removeNode(cellToMove2); environment.removeNode(c2); @@ -270,9 +270,9 @@ void testMoveNode3() { environment.addNode(c4, p4); environment.moveNode(cellToMove3, POSITION_TO_MOVE3); assertEquals( - environment.getPosition(cellToMove3), + environment.getCurrentPosition(cellToMove3), originalPos, - CELL_TO_MOVE_3_IS_IN_POSITION + environment.getPosition(cellToMove3) + CELL_TO_MOVE_3_IS_IN_POSITION + environment.getCurrentPosition(cellToMove3) ); environment.removeNode(cellToMove3); environment.removeNode(c4); @@ -295,8 +295,8 @@ void testMoveNode4() { environment.moveNode(cellToMove4, POSITION_TO_MOVE4); assertNotEquals( POSITION_TO_MOVE4, - environment.getPosition(cellToMove4), - CELL_TO_MOVE_4_IS_IN_POSITION + environment.getPosition(cellToMove4) + environment.getCurrentPosition(cellToMove4), + CELL_TO_MOVE_4_IS_IN_POSITION + environment.getCurrentPosition(cellToMove4) ); environment.removeNode(cellToMove4); environment.removeNode(c5); @@ -315,8 +315,8 @@ void testMoveNode5() { environment.moveNode(cellToMove5, POSITION_TO_MOVE5); assertEquals( POSITION_TO_MOVE5, - environment.getPosition(cellToMove5), - CELL_TO_MOVE_5_IS_IN_POSITION + environment.getPosition(cellToMove5) + environment.getCurrentPosition(cellToMove5), + CELL_TO_MOVE_5_IS_IN_POSITION + environment.getCurrentPosition(cellToMove5) ); environment.removeNode(cellToMove5); environment.removeNode(c6); @@ -335,8 +335,8 @@ void testMoveNode6() { environment.moveNode(cellToMove6, POSITION_TO_MOVE6); assertEquals( EXPECTED_POS6, - environment.getPosition(cellToMove6), - "cellToMove6 is in position: " + environment.getPosition(cellToMove6) + environment.getCurrentPosition(cellToMove6), + "cellToMove6 is in position: " + environment.getCurrentPosition(cellToMove6) ); environment.removeNode(cellToMove6); environment.removeNode(c7); @@ -353,8 +353,8 @@ void testMoveNode7() { final Node c8 = createNode(LITTLE_CELL_DIAMETER); environment.addNode(c8, p8); environment.moveNode(cellToMove7, POSITION_TO_MOVE7); - assertTrueJUnit4("cellToMove7 is in position: " + environment.getPosition(cellToMove7), - EXPECTED_POS7.equals(environment.getPosition(cellToMove7))); + assertTrueJUnit4("cellToMove7 is in position: " + environment.getCurrentPosition(cellToMove7), + EXPECTED_POS7.equals(environment.getCurrentPosition(cellToMove7))); environment.removeNode(cellToMove7); environment.removeNode(c8); } @@ -370,8 +370,8 @@ void testMoveNode8() { final Node c9 = createNode(LITTLE_CELL_DIAMETER); environment.addNode(c9, p9); environment.moveNode(cellToMove8, POSITION_TO_MOVE8); - assertTrueJUnit4("cellToMove8 is in position: " + environment.getPosition(cellToMove8), - EXPECTED_POS8.equals(environment.getPosition(cellToMove8))); + assertTrueJUnit4("cellToMove8 is in position: " + environment.getCurrentPosition(cellToMove8), + EXPECTED_POS8.equals(environment.getCurrentPosition(cellToMove8))); environment.removeNode(cellToMove8); environment.removeNode(c9); } @@ -413,14 +413,14 @@ void testAddDifferentDiam2() { nodeNotInEnvironment(environment, ng1); assertTrueJUnit4( getFailureTestString("ng2", ng2, p2), - environment.getPosition(ng2).equals(p2) + environment.getCurrentPosition(ng2).equals(p2) ); nodeNotInEnvironment(environment, ng3); environment.removeNode(ng2); environment.addNode(ng3, p3); assertTrueJUnit4( getFailureTestString("ng3", ng3, p3), - environment.getPosition(ng3).equals(p3) + environment.getCurrentPosition(ng3).equals(p3) ); } @@ -439,11 +439,11 @@ void testAddDifferentDiam3() { environment.addNode(np2, p3); assertTrueJUnit4( getFailureTestString("ng1", ng1, p1), - environment.getPosition(ng1).equals(p1) + environment.getCurrentPosition(ng1).equals(p1) ); assertTrueJUnit4( getFailureTestString("nm1", nm1, p2), - environment.getPosition(nm1).equals(p2) + environment.getCurrentPosition(nm1).equals(p2) ); nodeNotInEnvironment(environment, nm2); nodeNotInEnvironment(environment, np2); @@ -461,8 +461,8 @@ void testMoveDifferentDiam1() { final Euclidean2DPosition p1 = new Euclidean2DPosition(25, 20); environment.addNode(ng1, p1); environment.moveNode(cellToMove1, pd); - assertTrueJUnit4("cellToMove1 is in position: " + environment.getPosition(cellToMove1), - environment.getPosition(cellToMove1).equals(pd)); + assertTrueJUnit4("cellToMove1 is in position: " + environment.getCurrentPosition(cellToMove1), + environment.getCurrentPosition(cellToMove1).equals(pd)); } /** @@ -477,8 +477,8 @@ void testMoveDifferentDiam2() { final Euclidean2DPosition p2 = new Euclidean2DPosition(25, 30); environment.addNode(bce, p2); environment.moveNode(cellToMove2, pd); - assertTrueJUnit4("cellToMove2 is in position: " + environment.getPosition(cellToMove2), - environment.getPosition(cellToMove2).equals(pd)); + assertTrueJUnit4("cellToMove2 is in position: " + environment.getCurrentPosition(cellToMove2), + environment.getCurrentPosition(cellToMove2).equals(pd)); } /** @@ -494,15 +494,15 @@ void testMoveDifferentDiam3() { environment.addNode(ng1, p1); environment.moveNode(cellToMove3, pd); assertTrueJUnit4( - CELL_TO_MOVE_3_IS_IN_POSITION + environment.getPosition(cellToMove3), - EXPECTED_POS_DIFFDIAM3_1.equals(environment.getPosition(cellToMove3)) + CELL_TO_MOVE_3_IS_IN_POSITION + environment.getCurrentPosition(cellToMove3), + EXPECTED_POS_DIFFDIAM3_1.equals(environment.getCurrentPosition(cellToMove3)) ); environment.removeNode(ng1); environment.addNode(nm1, p1); environment.moveNode(cellToMove3, pd); assertTrueJUnit4( - CELL_TO_MOVE_3_IS_IN_POSITION + environment.getPosition(cellToMove3), - EXPECTED_POS_DIFFDIAM3_2.equals(environment.getPosition(cellToMove3)) + CELL_TO_MOVE_3_IS_IN_POSITION + environment.getCurrentPosition(cellToMove3), + EXPECTED_POS_DIFFDIAM3_2.equals(environment.getCurrentPosition(cellToMove3)) ); } @@ -519,15 +519,15 @@ void testMoveDifferentDiam4() { environment.addNode(ng1, p1); environment.moveNode(cellToMove4, pd); assertTrueJUnit4( - CELL_TO_MOVE_4_IS_IN_POSITION + environment.getPosition(cellToMove4), - EXPECTED_POS_DIFFDIAM4_1.equals(environment.getPosition(cellToMove4)) + CELL_TO_MOVE_4_IS_IN_POSITION + environment.getCurrentPosition(cellToMove4), + EXPECTED_POS_DIFFDIAM4_1.equals(environment.getCurrentPosition(cellToMove4)) ); environment.removeNode(ng1); environment.addNode(nm1, p1); environment.moveNode(cellToMove4, pd); assertTrueJUnit4( - CELL_TO_MOVE_4_IS_IN_POSITION + environment.getPosition(cellToMove4), - EXPECTED_POS_DIFFDIAM4_2.equals(environment.getPosition(cellToMove4)) + CELL_TO_MOVE_4_IS_IN_POSITION + environment.getCurrentPosition(cellToMove4), + EXPECTED_POS_DIFFDIAM4_2.equals(environment.getCurrentPosition(cellToMove4)) ); } @@ -544,17 +544,17 @@ void testMoveDifferentDiam5() { environment.addNode(ng1, p1); environment.moveNode(cellToMove5, pd); assertNotEquals( - environment.getPosition(cellToMove5), + environment.getCurrentPosition(cellToMove5), pd, - CELL_TO_MOVE_5_IS_IN_POSITION + environment.getPosition(cellToMove5) + CELL_TO_MOVE_5_IS_IN_POSITION + environment.getCurrentPosition(cellToMove5) ); environment.removeNode(ng1); environment.addNode(nm1, p1); environment.moveNode(cellToMove5, pd); assertNotEquals( - environment.getPosition(cellToMove5), + environment.getCurrentPosition(cellToMove5), pd, - CELL_TO_MOVE_5_IS_IN_POSITION + environment.getPosition(cellToMove5) + CELL_TO_MOVE_5_IS_IN_POSITION + environment.getCurrentPosition(cellToMove5) ); } @@ -573,8 +573,8 @@ void testMoveDifferentDiam6() { environment.addNode(np2, p2); environment.moveNode(cellToMove6, pd); assertTrueJUnit4( - "cellToMove6 is in position: " + environment.getPosition(cellToMove6), - environment.getPosition(cellToMove6).equals(pd) + "cellToMove6 is in position: " + environment.getCurrentPosition(cellToMove6), + environment.getCurrentPosition(cellToMove6).equals(pd) ); } @@ -592,8 +592,8 @@ void testMoveDifferentDiam7() { final Euclidean2DPosition p2 = new Euclidean2DPosition(60, 5); environment.addNode(np2, p2); environment.moveNode(cellToMove7, pd); - assertTrueJUnit4("cellToMove7 is in position: " + environment.getPosition(cellToMove7), - environment.getPosition(cellToMove7).equals(pd)); + assertTrueJUnit4("cellToMove7 is in position: " + environment.getCurrentPosition(cellToMove7), + environment.getCurrentPosition(cellToMove7).equals(pd)); } /** @@ -610,8 +610,8 @@ void testMoveDifferentDiam8() { final Euclidean2DPosition p2 = new Euclidean2DPosition(0, 10); environment.addNode(np2, p2); environment.moveNode(cellToMove8, pd); - assertTrueJUnit4("cellToMove8 is in position: " + environment.getPosition(cellToMove8), - environment.getPosition(cellToMove8).equals(pd)); + assertTrueJUnit4("cellToMove8 is in position: " + environment.getCurrentPosition(cellToMove8), + environment.getCurrentPosition(cellToMove8).equals(pd)); } /** @@ -628,10 +628,14 @@ void testMoveInTwoSteps1() { final Node c2 = np2; environment.addNode(c2, pd1); environment.moveNode(c1, POSITION_TO_MOVE_TWOSTEP1); - assertEquals(EXPECTED_POS_TWOSTEP1_1, environment.getPosition(c1), "c1 is in pos : " + environment.getPosition(c1)); + assertEquals( + EXPECTED_POS_TWOSTEP1_1, environment.getCurrentPosition(c1), "c1 is in pos : " + environment.getCurrentPosition(c1) + ); environment.moveNode(c2, pd1); environment.moveNodeToPosition(c1, pd2); - assertEquals(EXPECTED_POS_TWOSTEP1_2, environment.getPosition(c1), "c1 is in pos : " + environment.getPosition(c1)); + assertEquals( + EXPECTED_POS_TWOSTEP1_2, environment.getCurrentPosition(c1), "c1 is in pos : " + environment.getCurrentPosition(c1) + ); } @@ -646,7 +650,7 @@ void testMoveNode9() { environment.addNode(c1, originalPos); final Euclidean2DPosition pd = new Euclidean2DPosition(4.737000465393066, -5.0); environment.moveNode(c1, pd); - assertNotEquals(environment.getPosition(c1), pd); + assertNotEquals(environment.getCurrentPosition(c1), pd); } /** @@ -660,7 +664,7 @@ void testMoveNode10() { environment.addNode(c1, originalPos); final Euclidean2DPosition pd = new Euclidean2DPosition(3.122374292470004, -0.6490462479722794); environment.moveNode(c1, pd); - assertNotEquals(environment.getPosition(c1), pd); + assertNotEquals(environment.getCurrentPosition(c1), pd); } private List> getOverlappingNodes(final Node node) { @@ -671,7 +675,7 @@ private List> getOverlappingNodes(final Node node) { } private List mapToNodePositions(final List> nodes) { - return nodes.stream().map(node -> environment.getPosition(node).toString()).collect(Collectors.toList()); + return nodes.stream().map(node -> environment.getCurrentPosition(node).toString()).collect(Collectors.toList()); } /** @@ -810,8 +814,8 @@ private boolean thereIsOverlap(final Environment en .findAny() .ifPresent(e -> fail( - "Nodes " + e.getFirst().getId() + env.getPosition(e.getFirst()) + " and " - + e.getSecond().getId() + env.getPosition(e.getSecond()) + " are overlapping. " + "Nodes " + e.getFirst().getId() + env.getCurrentPosition(e.getFirst()) + " and " + + e.getSecond().getId() + env.getCurrentPosition(e.getSecond()) + " are overlapping. " + "Their distance is: " + env.getDistanceBetweenNodes(e.getFirst(), e.getSecond()) + " but should be greater than " + ( @@ -831,7 +835,7 @@ private boolean thereIsOverlap(final Environment en } private String getFailureTestString(final String nodeName, final Node n, final Position expected) { - return nodeName + " not in pos " + expected + "; it's in pos " + environment.getPosition(n); + return nodeName + " not in pos " + expected + "; it's in pos " + environment.getCurrentPosition(n); } private static void nodeNotInEnvironment(final Environment environment, final Node node) { @@ -846,12 +850,12 @@ private static void checkNodeInEnvironment( final var isInEnvironment = environment.getNodes().contains(node); if (shouldBePresent) { assertTrue(isInEnvironment, () -> NODE + node + " is not in the environment"); - final var position = environment.getPosition(node); + final var position = environment.getCurrentPosition(node); assertNotNull(position, () -> NODE + node + " has no valid position"); } else { assertFalse( isInEnvironment, - () -> NODE + node + " is in the environment at position " + environment.getPosition(node) + () -> NODE + node + " is in the environment at position " + environment.getCurrentPosition(node) ); } } diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/layers/TestBiomolLayer.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/layers/TestBiomolLayer.java index 064086ce72..e6630eb53f 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/layers/TestBiomolLayer.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/layers/TestBiomolLayer.java @@ -86,7 +86,7 @@ public void stepDone( @Nonnull final Time time, final long step ) { - final Euclidean2DPosition curPos = environment.getPosition(environment.getNodeByID(0)); + final Euclidean2DPosition curPos = environment.getCurrentPosition(environment.getNodeByID(0)); assertEquals(curPos.getX() > 0 && curPos.getY() > 0, underTest.canExecute()); } diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/nodes/TestEnvironmentNodes.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/nodes/TestEnvironmentNodes.java index d03f3bd801..8368252299 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/nodes/TestEnvironmentNodes.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/nodes/TestEnvironmentNodes.java @@ -237,7 +237,9 @@ void testEnv2() { .parallel() .filter(n -> n.getClass().equals(EnvironmentNodeImpl.class)) .min( - Comparator.comparingDouble(n -> environment.getPosition(n).distanceTo(environment.getPosition(center))) + Comparator.comparingDouble(n -> + environment.getCurrentPosition(n).distanceTo(environment.getCurrentPosition(center)) + ) ) .map(node -> node.getConcentration(new Biomolecule(A))) .orElseThrow(); @@ -295,13 +297,13 @@ void testEnv5() { final Environment environment = testNoVar("testEnv5.yml"); final double conAInEnv1 = environment.getNodes().stream() .parallel() - .filter(n -> environment.getPosition(n).equals(new Euclidean2DPosition(0, 0))) + .filter(n -> environment.getCurrentPosition(n).equals(new Euclidean2DPosition(0, 0))) .findAny() .map(node -> node.getConcentration(new Biomolecule(A))) .orElseThrow(); final double conAInEnv2 = environment.getNodes().stream() .parallel() - .filter(n -> environment.getPosition(n).equals(new Euclidean2DPosition(1, 0))) + .filter(n -> environment.getCurrentPosition(n).equals(new Euclidean2DPosition(1, 0))) .findAny() .map(node -> node.getConcentration(new Biomolecule(A))) .orElseThrow(); diff --git a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/properties/TestDeformableCell.java b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/properties/TestDeformableCell.java index 1b2bd94faf..3d5101d500 100644 --- a/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/properties/TestDeformableCell.java +++ b/alchemist-incarnation-biochemistry/src/test/java/it/unibo/alchemist/model/biochemistry/properties/TestDeformableCell.java @@ -113,8 +113,12 @@ void testAddNode1() { environment.addNode(cellNode3, CELL_POS1_3); environment.addNode(cellNode4, CELL_POS1_4); - assertNotNull(environment.getPosition(cellNode2), "Position of cellNode2 = " + environment.getPosition(cellNode2)); - assertNotNull(environment.getPosition(cellNode3), "Position of cellNode3 = " + environment.getPosition(cellNode3)); + assertNotNull( + environment.getCurrentPosition(cellNode2), "Position of cellNode2 = " + environment.getCurrentPosition(cellNode2) + ); + assertNotNull( + environment.getCurrentPosition(cellNode3), "Position of cellNode3 = " + environment.getCurrentPosition(cellNode3) + ); assertFalse(environment.getNodes().contains(cellNode4), "unexpected node in the environment"); } @@ -391,8 +395,8 @@ void testMoveNode1() { environment.moveNodeToPosition(cellNode1, new Euclidean2DPosition(0, 10)); assertEquals( EXPECTED_POS_MOV1, - environment.getPosition(cellNode1), - "Position of cellNode1 = " + environment.getPosition(cellNode1) + environment.getCurrentPosition(cellNode1), + "Position of cellNode1 = " + environment.getCurrentPosition(cellNode1) ); } } diff --git a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistExecutionContext.kt b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistExecutionContext.kt index d21153c0db..f5890c2780 100644 --- a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistExecutionContext.kt +++ b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/AlchemistExecutionContext.kt @@ -89,7 +89,7 @@ class AlchemistExecutionContext

>( * The device position, in form of [Position]. */ val devicePosition: P - get() = environmentAccess.getPosition(node) + get() = environmentAccess.getCurrentPosition(node) /** * @param environment @@ -239,7 +239,8 @@ class AlchemistExecutionContext

>( * the destination, in the form of a destination node * @return the distance on a map */ - fun routingDistance(dest: Node): Double = routingDistance(environmentAccess.getPosition(dest) as GeoPosition) + fun routingDistance(dest: Node): Double = + routingDistance(environmentAccess.getCurrentPosition(dest) as GeoPosition) /** * Computes the distance along a map. Requires a [MapEnvironment]. diff --git a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/properties/ProtelisDevice.kt b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/properties/ProtelisDevice.kt index 644cf740ce..93a6633968 100644 --- a/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/properties/ProtelisDevice.kt +++ b/alchemist-incarnation-protelis/src/main/kotlin/it/unibo/alchemist/model/protelis/properties/ProtelisDevice.kt @@ -13,7 +13,6 @@ import it.unibo.alchemist.model.Environment import it.unibo.alchemist.model.Node import it.unibo.alchemist.model.NodeProperty import it.unibo.alchemist.model.Position -import it.unibo.alchemist.model.Reaction import it.unibo.alchemist.model.protelis.AlchemistExecutionContext import it.unibo.alchemist.model.protelis.AlchemistNetworkManager import it.unibo.alchemist.model.protelis.ProtelisIncarnation @@ -129,7 +128,7 @@ constructor( when { node.contains(molecule) -> node.getConcentration(molecule) else -> - checkNotNull(environment.getLayer(molecule)?.getValue(environment.getPosition(node))) { + checkNotNull(environment.getLayer(molecule)?.getValue(environment.getCurrentPosition(node))) { "Molecule (variable) \"$id\" not found in $this, nor a layer with the same name exists" } } diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java index dbc2fa2344..54d0d63a6e 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPEREMoveNodeAgent.java @@ -75,7 +75,7 @@ protected Environment, P> getEnvironment() { * @return the current position of the node */ protected P getCurrentPosition() { - return environment.getPosition(getNode()); + return environment.getCurrentPosition(getNode()); } /** @@ -84,7 +84,7 @@ protected P getCurrentPosition() { * @return the position of node */ protected final P getPosition(final Node> node) { - return environment.getPosition(node); + return environment.getCurrentPosition(node); } /** @@ -112,7 +112,7 @@ protected final void move(final P direction) { if (environment instanceof EuclideanEnvironment) { ((EuclideanEnvironment) environment).moveNode(getNode(), direction); } else { - final var myPosition = environment.getPosition(getNode()); + final var myPosition = environment.getCurrentPosition(getNode()); environment.moveNodeToPosition(getNode(), myPosition.plus(direction.getCoordinates())); } } diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java index 2aec35ab52..795a5b2f5e 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/AbstractSAPERENeighborAgent.java @@ -116,7 +116,7 @@ public final Context getContext() { * @return the current position of the node */ protected final P getCurrentPosition() { - return environment.getPosition(getNode()); + return environment.getCurrentPosition(getNode()); } /** @@ -125,7 +125,7 @@ protected final P getCurrentPosition() { * @return the position of node */ protected final P getPosition(final ILsaNode node) { - return environment.getPosition(node); + return environment.getCurrentPosition(node); } /** @@ -148,7 +148,10 @@ protected final Neighborhood> getLocalNeighborhood() { * @param direction the point towards which move the node */ protected final void move(final P direction) { - environment.moveNodeToPosition(getNode(), getEnvironment().getPosition(getNode()).plus(direction.getCoordinates())); + environment.moveNodeToPosition( + getNode(), + getEnvironment().getCurrentPosition(getNode()).plus(direction.getCoordinates()) + ); } /** diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java index e7faa13f5f..9fe965203c 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/actions/SAPEREWalkerRiseGradient.java @@ -148,7 +148,7 @@ public GeoPosition getTarget() { * * then remain still. */ - final GeoPosition currentPosition = environment.getPosition(node); + final GeoPosition currentPosition = environment.getCurrentPosition(node); if (matches.isEmpty()) { if (curPos == null || currentPosition.equals(curPos)) { return currentPosition; @@ -159,7 +159,7 @@ public GeoPosition getTarget() { /* * If the current target node has moved, the destination should be re-computed. */ - final Position curNodeActualPos = environment.getPosition(curNode); + final Position curNodeActualPos = environment.getCurrentPosition(curNode); if (curNode.equals(node) || !curPos.equals(curNodeActualPos) || environment.getNeighborhood(node).getCurrent().contains(curNode)) { @@ -167,7 +167,7 @@ public GeoPosition getTarget() { * Update target */ curNode = environment.getNodeByID(nid); - curPos = environment.getPosition(curNode); + curPos = environment.getCurrentPosition(curNode); } return curPos; } diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java index a838602775..53c3b6f827 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREGradient.java @@ -391,11 +391,11 @@ protected void updateInternalStatus( : getNode().getConcentration(context); final TIntObjectMap

positionCacheTemp = new TIntObjectHashMap<>(positionCache.size()); final TIntObjectMap> gradCacheTemp = new TIntObjectHashMap<>(gradCache.size()); - final P curPos = this.environment.getPosition(getNode()); + final P curPos = this.environment.getCurrentPosition(getNode()); final boolean positionChanged = !curPos.equals(mypos); boolean neighPositionChanged = false; for (final Node> n : this.environment.getNeighborhood(getNode()).getCurrent()) { - final P p = this.environment.getPosition(n); + final P p = this.environment.getCurrentPosition(n); final int nid = n.getId(); positionCacheTemp.put(nid, p); gradCacheTemp.put(n.getId(), n.getConcentration(gradient)); diff --git a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java index 38cf2023d6..30746d76fa 100644 --- a/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java +++ b/alchemist-incarnation-sapere/src/main/java/it/unibo/alchemist/model/sapere/reactions/SAPEREReaction.java @@ -142,7 +142,7 @@ public void execute() { } return; } - final Position nodePosCache = modifiesOnlyLocally ? environment.getPosition(getNode()) : null; + final Position nodePosCache = modifiesOnlyLocally ? environment.getCurrentPosition(getNode()) : null; final List localContentCache = modifiesOnlyLocally ? new ArrayList<>(getLsaNode().getLsaSpace()) : null; @@ -210,7 +210,7 @@ public void execute() { */ if (modifiesOnlyLocally) { final ILsaNode n = getLsaNode(); - if (Objects.requireNonNull(nodePosCache).equals(environment.getPosition(getNode()))) { + if (Objects.requireNonNull(nodePosCache).equals(environment.getCurrentPosition(getNode()))) { final List contents = n.getLsaSpace(); if (contents.size() == Objects.requireNonNull(localContentCache).size()) { emptyExecution = localContentCache.containsAll(contents); diff --git a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/RunScafiProgram.scala b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/RunScafiProgram.scala index a081855ca6..8999b434ac 100644 --- a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/RunScafiProgram.scala +++ b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/RunScafiProgram.scala @@ -98,7 +98,7 @@ sealed class RunScafiProgram[T, P <: Position[P]]( case 2 => Point3D(point.getCoordinate(0), point.getCoordinate(1), 0) case 3 => Point3D(point.getCoordinate(0), point.getCoordinate(1), point.getCoordinate(2)) } - val position: P = environment.getPosition(node) + val position: P = environment.getCurrentPosition(node) // NB: We assume it.unibo.alchemist.model.Time = DoubleTime // and that its "time unit" is seconds, and then we get NANOSECONDS val alchemistCurrentTime = Try(environment.getSimulation) diff --git a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/ScafiIncarnationForAlchemist.scala b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/ScafiIncarnationForAlchemist.scala index 0ff8f615f3..5b32b1957b 100644 --- a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/ScafiIncarnationForAlchemist.scala +++ b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/scafi/ScafiIncarnationForAlchemist.scala @@ -81,7 +81,7 @@ object ScafiIncarnationForAlchemist val layer: Layer[Any, Position[_]] = alchemistEnvironment.getLayer(new SimpleMolecule(name)) val node = alchemistEnvironment.getNodeByID(mid()) layer - .getValue(alchemistEnvironment.getPosition(node)) + .getValue(alchemistEnvironment.getCurrentPosition(node)) .asInstanceOf[A] } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt index 7731a0ed29..0c9ece5848 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkCentroid.kt @@ -36,7 +36,7 @@ class NetworkCentroid : Extractor { private fun Environment.networkHub(): List { val sums = DoubleArray(dimensions) { ORIGIN } forEach { node -> - getPosition(node).coordinates.forEachIndexed { index, value -> + getCurrentPosition(node).coordinates.forEachIndexed { index, value -> sums[index] += value } } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt index 19ad8a5ab3..5cee2fd95a 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NetworkDensity.kt @@ -35,7 +35,7 @@ class NetworkDensity : Extractor { ) val boundingBox = this.fold(BoundingBox()) { bb, node -> - val (x, y) = getPosition(node).coordinates + val (x, y) = getCurrentPosition(node).coordinates BoundingBox( min(x, bb.minX), max(x, bb.maxX), diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt index 52e30f5ece..7d1b548493 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/extractors/NodesPositions.kt @@ -43,7 +43,7 @@ class NodesPositions>(private val environment: Environment columnNameFormat(nodeId, Dimension(index)) to coordinate } diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/deployments/CloseToAlreadyDeployed.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/deployments/CloseToAlreadyDeployed.kt index 3c7de791cc..69668457d1 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/deployments/CloseToAlreadyDeployed.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/model/deployments/CloseToAlreadyDeployed.kt @@ -28,7 +28,7 @@ class CloseToAlreadyDeployed>( override val sources = environment.nodes .asSequence() - .map { environment.getPosition(it) } + .map { environment.getCurrentPosition(it) } .map { when (it) { is GeoPosition -> doubleArrayOf(it.latitude, it.longitude) diff --git a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java index ce71a4ed7c..b8c9369cf4 100755 --- a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java +++ b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestLoadGPSTrace.java @@ -94,7 +94,7 @@ public void finished( for (final Node node : environment.getNodes()) { final GeoPosition start = Objects.requireNonNull(NODE_START_POSITION.get(node)); final GeoPosition idealArrive = Objects.requireNonNull(START_ARRIVE_POSITION.get(start)); - final GeoPosition realArrive = Objects.requireNonNull(environment.getPosition(node)); + final GeoPosition realArrive = Objects.requireNonNull(environment.getCurrentPosition(node)); assertEquals( 0.0, idealArrive.distanceTo(realArrive), @@ -108,7 +108,7 @@ public void finished( @Override public void initialized(@Nonnull final Environment environment) { for (final Node node : environment.getNodes()) { - final GeoPosition position = environment.getPosition(node); + final GeoPosition position = environment.getCurrentPosition(node); /* * We don't know the actual type of position, we use LatLongPosition here, so we need to make sure * that types match, or the map won't return what we expect diff --git a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestYAMLLoader.java b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestYAMLLoader.java index a3fed962e2..9bf687d40c 100644 --- a/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestYAMLLoader.java +++ b/alchemist-loading/src/test/java/it/unibo/alchemist/model/TestYAMLLoader.java @@ -147,7 +147,7 @@ void testVariableContentClash() { void testScalaVar() { final Environment environment = testNoVar("synthetic/scalavar.yml").getEnvironment(); assertNotNull(environment); - assertEquals(environment.makePosition(3, 10), environment.getPosition(environment.getNodeByID(0))); + assertEquals(environment.makePosition(3, 10), environment.getCurrentPosition(environment.getNodeByID(0))); } /** diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt index f7df0acd20..1a980e643e 100644 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt +++ b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestGraphStreamReproducibility.kt @@ -58,7 +58,7 @@ class TestGraphStreamReproducibility : ) } environment.nodes.map { node -> - environment.getPosition(node).coordinates.toList() to + environment.getCurrentPosition(node).coordinates.toList() to environment.getNeighborhood(node).current.neighbors.map { it.id } } } diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestSpecificPositions.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestSpecificPositions.kt index dc78fec1f1..b8352d13c0 100644 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestSpecificPositions.kt +++ b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestSpecificPositions.kt @@ -39,7 +39,7 @@ class TestSpecificPositions : "Test YAML loading with 2D env" { val loader = LoadAlchemist.from(ResourceLoader.getResource("testSpecificPositions.yml")) val environment = loader.getWith(emptyMap()).environment - environment.nodes.map { environment.getPosition(it) } shouldBe + environment.nodes.map { environment.getCurrentPosition(it) } shouldBe listOf(Euclidean2DPosition(1.0, 2.0), Euclidean2DPosition(3.0, 4.0)) } }) { diff --git a/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/environments/OSMEnvironment.java b/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/environments/OSMEnvironment.java index e044770e65..a574917304 100644 --- a/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/environments/OSMEnvironment.java +++ b/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/environments/OSMEnvironment.java @@ -237,12 +237,12 @@ public Route computeRoute( final GeoPosition coord, final GraphHopperOptions options ) { - return computeRoute(getPosition(node), coord, options); + return computeRoute(retrievePosition(node), coord, options); } @Override public Route computeRoute(final Node node, final Node node2) { - return computeRoute(node, getPosition(node2)); + return computeRoute(node, retrievePosition(node2)); } @Override diff --git a/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/movestrategies/speed/StraightLineTraceDependantSpeed.java b/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/movestrategies/speed/StraightLineTraceDependantSpeed.java index e17dbce4ab..5a13ec6030 100644 --- a/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/movestrategies/speed/StraightLineTraceDependantSpeed.java +++ b/alchemist-maps/src/main/java/it/unibo/alchemist/model/maps/movestrategies/speed/StraightLineTraceDependantSpeed.java @@ -57,7 +57,7 @@ protected double computeDistance( final Node currentNode, final GeoPosition targetPosition ) { - return environment.getPosition(currentNode).distanceTo(targetPosition); + return environment.getCurrentPosition(currentNode).distanceTo(targetPosition); } @Override diff --git a/alchemist-maps/src/main/kotlin/it/unibo/alchemist/model/maps/actions/RandomTargetInPolygonOnMap.kt b/alchemist-maps/src/main/kotlin/it/unibo/alchemist/model/maps/actions/RandomTargetInPolygonOnMap.kt index 837a5a7e14..406e9dcb55 100644 --- a/alchemist-maps/src/main/kotlin/it/unibo/alchemist/model/maps/actions/RandomTargetInPolygonOnMap.kt +++ b/alchemist-maps/src/main/kotlin/it/unibo/alchemist/model/maps/actions/RandomTargetInPolygonOnMap.kt @@ -37,7 +37,7 @@ class RandomTargetInPolygonOnMap, S : RoutingSer node, { current, final -> PolygonalChain(current, final) }, ConstantSpeed(reaction, speed), - object : ChangeTargetOnCollision({ environment.getPosition(node) }) { + object : ChangeTargetOnCollision({ environment.getCurrentPosition(node) }) { override fun chooseTarget() = positionGenerator .stream() .findFirst() diff --git a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/actions/TestTargetMapWalker.kt b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/actions/TestTargetMapWalker.kt index a817abd208..80f7ebc2ae 100644 --- a/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/actions/TestTargetMapWalker.kt +++ b/alchemist-maps/src/test/kotlin/it/unibo/alchemist/model/maps/actions/TestTargetMapWalker.kt @@ -49,73 +49,73 @@ internal class TestTargetMapWalker { @Test fun `Node should not move if no position is set`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) run() - assertEquals(start, environment.getPosition(node)) + assertEquals(start, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when LatLongPosition is set`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, LatLongPosition(ENDLAT, ENDLON)) run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as Iterable of Doubles`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, listOf(ENDLAT, ENDLON)) run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as Iterable of Strings`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, listOf(ENDLAT.toString(), ENDLON.toString())) run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as a stringified list of numbers`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, listOf(ENDLAT, ENDLON).toString()) run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as a stringified GeoPosition`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, ENDPOSITION.toString()) run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as an angle bracket string`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, "<$ENDLAT $ENDLON>") run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } @Test fun `Node should reach the target position when position is set as a string with embedded coordinates`() { - val start = environment.getPosition(node) + val start = environment.getCurrentPosition(node) assertTrue(STARTPOSITION.distanceTo(start) < 10) node.setConcentration(TRACK, "sakldaskld$ENDLAT fmekfjr$ENDLON sdsad32d") run() - assertEquals(ENDPOSITION, environment.getPosition(node)) + assertEquals(ENDPOSITION, environment.getCurrentPosition(node)) } companion object { diff --git a/alchemist-physics/src/main/java/it/unibo/alchemist/model/physics/environments/MuseumHall.java b/alchemist-physics/src/main/java/it/unibo/alchemist/model/physics/environments/MuseumHall.java index 27e053adeb..e9dc8c2138 100644 --- a/alchemist-physics/src/main/java/it/unibo/alchemist/model/physics/environments/MuseumHall.java +++ b/alchemist-physics/src/main/java/it/unibo/alchemist/model/physics/environments/MuseumHall.java @@ -155,7 +155,7 @@ private Euclidean2DPosition nextAllowed(final double ox, final double oy, final @Override public void moveNode(@Nonnull final Node node, final Euclidean2DPosition direction) { - final Euclidean2DPosition cur = getPosition(node); + final Euclidean2DPosition cur = retrievePosition(node); final double ox = cur.getCoordinates()[0]; final double oy = cur.getCoordinates()[1]; double nx = direction.getCoordinates()[0] + ox; @@ -176,7 +176,7 @@ public void moveNode(@Nonnull final Node node, final Euclidean2DPosition dire public String toString() { final StringBuilder builder = new StringBuilder(); for (final Node n : getNodes()) { - builder.append(getPosition(n)).append(' ').append(n).append('\n'); + builder.append(retrievePosition(n)).append(' ').append(n).append('\n'); } return builder.toString(); } diff --git a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/InfluenceSphere2D.kt b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/InfluenceSphere2D.kt index 089f0859f2..4458c44c51 100644 --- a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/InfluenceSphere2D.kt +++ b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/InfluenceSphere2D.kt @@ -31,7 +31,7 @@ open class InfluenceSphere2D( override fun influentialNodes(): List> = environment .getNodesWithin( shape.transformed { - origin(environment.getPosition(owner)) + origin(environment.getCurrentPosition(owner)) rotate(environment.getHeading(owner)) }, ).minusElement(owner) diff --git a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/actions/HeadTowardTarget.kt b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/actions/HeadTowardTarget.kt index 9990db1c4b..e549ae8b50 100644 --- a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/actions/HeadTowardTarget.kt +++ b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/actions/HeadTowardTarget.kt @@ -51,9 +51,9 @@ constructor( val myHeading = environment.getHeading(node) if (targetPosition != myHeading) { if (speedRadians >= 2 * Math.PI) { - environment.setHeading(node, targetPosition - environment.getPosition(node)) + environment.setHeading(node, targetPosition - environment.getCurrentPosition(node)) } else { - val targetAngle = (targetPosition - environment.getPosition(node)).asAngle + val targetAngle = (targetPosition - environment.getCurrentPosition(node)).asAngle val currentAngle = environment.getHeading(node).asAngle val rotation = shortestRotationAngle(currentAngle, targetAngle) val absDistance = abs(rotation) diff --git a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/AbstractLimitedContinuous2D.kt b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/AbstractLimitedContinuous2D.kt index 58ae0f998b..347613eec4 100644 --- a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/AbstractLimitedContinuous2D.kt +++ b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/AbstractLimitedContinuous2D.kt @@ -28,7 +28,7 @@ abstract class AbstractLimitedContinuous2D(incarnation: Incarnation, newPosition: Euclidean2DPosition) { - val (curX, curY) = getPosition(node).coordinates + val (curX, curY) = getCurrentPosition(node).coordinates val (newX, newY) = newPosition.coordinates super.moveNodeToPosition(node, next(curX, curY, newX, newY)) } diff --git a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/ContinuousPhysics2DEnvironment.kt b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/ContinuousPhysics2DEnvironment.kt index c26510866f..fca43e7b0a 100644 --- a/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/ContinuousPhysics2DEnvironment.kt +++ b/alchemist-physics/src/main/kotlin/it/unibo/alchemist/model/physics/environments/ContinuousPhysics2DEnvironment.kt @@ -66,7 +66,7 @@ open class ContinuousPhysics2DEnvironment(incarnation: Incarnation): Euclidean2DShape = shapefulNodes[node].transformed { - origin(getPosition(node)) + origin(retrievePosition(node)) rotate(getHeading(node)) } @@ -127,7 +127,7 @@ open class ContinuousPhysics2DEnvironment(incarnation: Incarnation getNodeRadius(node1)) + assertTrue(environment.getCurrentPosition(node1).distanceTo(target) > getNodeRadius(node1)) } companion object { diff --git a/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraInjectVisibleNodeClosestToDistance.kt b/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraInjectVisibleNodeClosestToDistance.kt index 1f408bae5f..93b93a7610 100644 --- a/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraInjectVisibleNodeClosestToDistance.kt +++ b/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraInjectVisibleNodeClosestToDistance.kt @@ -51,7 +51,7 @@ class CameraInjectVisibleNodeClosestToDistance( } @Suppress("UNCHECKED_CAST") val nodes = visibleNodes as List> - val myPosition = environment.getPosition(node).surroundingPointAt( + val myPosition = environment.getCurrentPosition(node).surroundingPointAt( versor = environment.getHeading(node), distance = distance, ) diff --git a/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraSee.kt b/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraSee.kt index 780af8351f..518509b0fc 100644 --- a/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraSee.kt +++ b/alchemist-smartcam/src/main/kotlin/it/unibo/alchemist/model/actions/CameraSee.kt @@ -61,7 +61,7 @@ constructor( filterByMolecule?.run { seen = seen.filter { it.contains(filterByMolecule) } } - node.setConcentration(outputMolecule, seen.map { VisibleNodeImpl(it, environment.getPosition(it)) }) + node.setConcentration(outputMolecule, seen.map { VisibleNodeImpl(it, environment.getCurrentPosition(it)) }) } override fun getContext() = Context.LOCAL diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/api/Effect.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/api/Effect.java index 928de97d9a..bdb7ad3ac9 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/api/Effect.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/api/Effect.java @@ -59,7 +59,7 @@ default > void apply( final Environment environment, final Wormhole2D

wormhole ) { - final Point viewPoint = wormhole.getViewPoint(environment.getPosition(n)); + final Point viewPoint = wormhole.getViewPoint(environment.getCurrentPosition(n)); apply(g, n, viewPoint.x, viewPoint.y); // preserve backward compatibility } diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/AbstractDrawOnce.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/AbstractDrawOnce.java index 06473772d4..2a6e1d625f 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/AbstractDrawOnce.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/AbstractDrawOnce.java @@ -58,7 +58,7 @@ public > void apply( * if marker node is no longer in the environment or it is no longer displayed, we need to change it */ if (markerNode.isEmpty() - || !wormhole.isInsideView(wormhole.getViewPoint(environment.getPosition(markerNode.get())))) { + || !wormhole.isInsideView(wormhole.getViewPoint(environment.getCurrentPosition(markerNode.get())))) { markerNodeID = null; } } diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawPedestrianPath.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawPedestrianPath.java index 9903516e2b..6e19a98c08 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawPedestrianPath.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawPedestrianPath.java @@ -79,7 +79,7 @@ protected > void draw( final Environment environment, final Wormhole2D

wormhole ) { - path.add(environment.getPosition(node)); + path.add(environment.getCurrentPosition(node)); if (toBeDrawn) { colorCache = new Color(red.getVal(), green.getVal(), blue.getVal(), alpha.getVal()); graphics2D.setColor(colorCache); diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawSmartcam.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawSmartcam.java index 6d21d2116f..4aa0f8085e 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawSmartcam.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/effect/impl/DrawSmartcam.java @@ -48,7 +48,7 @@ public > void apply( final Wormhole2D

wormhole ) { final double zoom = wormhole.getZoom(); - final Point viewPoint = wormhole.getViewPoint(environment.getPosition(node)); + final Point viewPoint = wormhole.getViewPoint(environment.getCurrentPosition(node)); final int x = viewPoint.x; final int y = viewPoint.y; if (environment instanceof Physics2DEnvironment) { diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java index f29cdcaef5..2d1d6ccf6e 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/swingui/monitor/impl/Generic2DDisplay.java @@ -716,7 +716,7 @@ private void update(final Environment environment, final Time time) { positions.clear(); neighbors.clear(); environment.getNodes().parallelStream().forEach(node -> { - positions.put(node, environment.getPosition(node)); + positions.put(node, environment.getCurrentPosition(node)); neighbors.put(node, environment.getNeighborhood(node).getCurrent()); }); releaseData(); @@ -891,7 +891,7 @@ public void mouseReleased(final MouseEvent e) { final P envEnding = wormhole.getEnvPoint(endingPoint); final P envOrigin = wormhole.getEnvPoint(originPoint); for (final Node n : selectedNodes) { - final P p = currentEnv.getPosition(n); + final P p = currentEnv.getCurrentPosition(n); final P finalPos = p.plus(envEnding.minus(envOrigin.getCoordinates()).getCoordinates()); engine.schedule(() -> { currentEnv.moveNodeToPosition(n, finalPos); diff --git a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/wormhole/impl/MapWormhole.java b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/wormhole/impl/MapWormhole.java index a76e636d17..f38a5e1603 100644 --- a/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/wormhole/impl/MapWormhole.java +++ b/alchemist-swingui/src/main/java/it/unibo/alchemist/boundary/wormhole/impl/MapWormhole.java @@ -140,7 +140,7 @@ public void optimalZoom() { setZoom(zoom); zoom--; } while (zoom > 1 && !environment.getNodes().parallelStream() - .map(environment::getPosition) + .map(environment::getCurrentPosition) .map(this::getViewPoint) .allMatch(this::isInsideView)); } diff --git a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt index 8f88398196..60fdc4ecc7 100644 --- a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt +++ b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/effect/impl/DrawDirectedNode.kt @@ -73,7 +73,7 @@ class DrawDirectedNode : it.unibo.alchemist.boundary.swingui.effect.api.Effect { environment: Environment, wormhole: Wormhole2D

, ) { - val nodePosition: P = environment.getPosition(node) + val nodePosition: P = environment.getCurrentPosition(node) val viewPoint: Point = wormhole.getViewPoint(nodePosition) val (x, y) = Pair(viewPoint.x, viewPoint.y) drawDirectedNode(g, node, x, y, environment, wormhole) @@ -156,7 +156,7 @@ class DrawDirectedNode : it.unibo.alchemist.boundary.swingui.effect.api.Effect { if (roundedTime >= lastDraw) { lastDrawMemory = lastDrawMemory + (node.id to lastDraw + timespan.`val`) val updatedPositions = - (positions + (environment.getPosition(node) to rotation(node))) + (positions + (environment.getCurrentPosition(node) to rotation(node))) .takeLast(MAX_SNAPSHOT_LENGTH) positionsMemory = positionsMemory + (node.id to updatedPositions) diff --git a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/monitor/impl/NodeTracker.kt b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/monitor/impl/NodeTracker.kt index 4f7d374584..2d79dc0abf 100644 --- a/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/monitor/impl/NodeTracker.kt +++ b/alchemist-swingui/src/main/kotlin/it/unibo/alchemist/boundary/swingui/monitor/impl/NodeTracker.kt @@ -66,7 +66,7 @@ class NodeTracker>(private val node: Node) : val content = """ |$POSITION - |${environment.getPosition(node)} + |${environment.getCurrentPosition(node)} | |$CONTENT |${node.contents.map { (k, v) -> "${k.name} -> $v" }.sorted().joinToString(System.lineSeparator())} diff --git a/alchemist-web-renderer/src/jvmMain/kotlin/it/unibo/alchemist/boundary/webui/server/surrogates/utility/ToNodeSurrogate.kt b/alchemist-web-renderer/src/jvmMain/kotlin/it/unibo/alchemist/boundary/webui/server/surrogates/utility/ToNodeSurrogate.kt index 503e0f2d75..07d24afd58 100644 --- a/alchemist-web-renderer/src/jvmMain/kotlin/it/unibo/alchemist/boundary/webui/server/surrogates/utility/ToNodeSurrogate.kt +++ b/alchemist-web-renderer/src/jvmMain/kotlin/it/unibo/alchemist/boundary/webui/server/surrogates/utility/ToNodeSurrogate.kt @@ -36,5 +36,5 @@ fun Node.toNodeSurrogate( where TS : Any, P : Position, PS : PositionSurrogate = NodeSurrogate( id, contents.map { it.key.toMoleculeSurrogate() to toConcentrationSurrogate(it.value) }.toMap(), - toPositionSurrogate(environment.getPosition(this)), + toPositionSurrogate(environment.getCurrentPosition(this)), ) diff --git a/alchemist-web-renderer/src/jvmTest/kotlin/it/unibo/alchemist/boundary/server/utility/ToNodeSurrogateTest.kt b/alchemist-web-renderer/src/jvmTest/kotlin/it/unibo/alchemist/boundary/server/utility/ToNodeSurrogateTest.kt index a3b4b0d6d1..d291ba3c82 100644 --- a/alchemist-web-renderer/src/jvmTest/kotlin/it/unibo/alchemist/boundary/server/utility/ToNodeSurrogateTest.kt +++ b/alchemist-web-renderer/src/jvmTest/kotlin/it/unibo/alchemist/boundary/server/utility/ToNodeSurrogateTest.kt @@ -55,5 +55,5 @@ fun checkToNodeSurrogate( nodeSurrogate.contents[surrogateContentKey] shouldBe EmptyConcentrationSurrogate } node.contents.size shouldBe nodeSurrogate.contents.size - checkToPositionSurrogate(environment.getPosition(node), nodeSurrogate.position) + checkToPositionSurrogate(environment.getCurrentPosition(node), nodeSurrogate.position) } From 4fdef13d47abe3b800f84675dd17a45302373470 Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 12:17:02 +0100 Subject: [PATCH 09/11] chore(api): add way to register to observables without sending initial callback Now the "laziness" concept is applied to dervied observables too. --- .../model/observation/DerivedObservable.kt | 24 ++- .../alchemist/model/observation/Observable.kt | 12 +- .../model/observation/ObservableExtensions.kt | 64 ++++-- .../model/observation/LazinessTest.kt | 189 ++++++++++++++++++ 4 files changed, 263 insertions(+), 26 deletions(-) create mode 100644 alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/LazinessTest.kt diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt index 0fb63697bd..f75f52a994 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/DerivedObservable.kt @@ -26,7 +26,7 @@ import java.util.Collections abstract class DerivedObservable(private val emitOnDistinct: Boolean = true) : Observable { private val callbacks = LinkedHashMap Unit>>() - private var cached: Option = none() + protected var cached: Option = none() private var isListening = false @@ -51,14 +51,15 @@ abstract class DerivedObservable(private val emitOnDistinct: Boolean = true) val wasEmpty = callbacks.isEmpty() callbacks[registrant] = callbacks[registrant].orEmpty() + callback if (wasEmpty) { - val initial = computeFresh() - cached = initial.some() + isListening = true if (invokeOnRegistration) { + val initial = computeFresh() + cached = initial.some() callback(initial) + startMonitoring(true) + } else { + startMonitoring(true) } - - isListening = true - startMonitoring() } else if (invokeOnRegistration) { callback(current) } @@ -91,6 +92,17 @@ abstract class DerivedObservable(private val emitOnDistinct: Boolean = true) */ protected abstract fun startMonitoring() + /** + * Initiates monitoring for changes or updates in the implementing class. + * This method should enable necessary mechanisms to observe and react to changes + * based on the specific implementation of the derived class. + * + * @param lazy whether the monitoring should be started lazily (avoiding immediate callbacks) + */ + protected open fun startMonitoring(lazy: Boolean) { + startMonitoring() + } + /** * Stops monitoring for changes or updates in the implementing class. * This method should disable any active observation mechanisms and ensure diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt index aee2bbb3e8..35eff0af11 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/Observable.kt @@ -79,8 +79,10 @@ interface Observable : Disposable { override fun computeFresh(): S = transform(this@Observable.current) - override fun startMonitoring() { - this@Observable.onChange(this) { + override fun startMonitoring() = startMonitoring(false) + + override fun startMonitoring(lazy: Boolean) { + this@Observable.onChange(this, !lazy) { updateAndNotify(transform(it)) } } @@ -105,13 +107,15 @@ interface Observable : Disposable { override fun computeFresh(): R = merge(this@Observable.current, other.current) - override fun startMonitoring() { + override fun startMonitoring() = startMonitoring(false) + + override fun startMonitoring(lazy: Boolean) { val handleUpdate: (Any?) -> Unit = { updateAndNotify(merge(this@Observable.current, other.current)) } listOf(this@Observable, other).forEach { obs -> - obs.onChange(this, handleUpdate) + obs.onChange(this, !lazy, handleUpdate) } } diff --git a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt index 17c133c262..6494fcf9d9 100644 --- a/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt +++ b/alchemist-api/src/main/kotlin/it/unibo/alchemist/model/observation/ObservableExtensions.kt @@ -199,8 +199,10 @@ object ObservableExtensions { override fun computeFresh(): O = aggregator(this@combineLatestCollection.current.map { map(it).current }) + override fun startMonitoring() = startMonitoring(false) + @Suppress("UNCHECKED_CAST") - override fun startMonitoring() { + override fun startMonitoring(lazy: Boolean) { val callback: (C) -> Unit = { current -> reconcile( sources = sources, @@ -211,8 +213,16 @@ object ObservableExtensions { ) } - this@combineLatestCollection.onChange(this, callback) - callback(ArrayList(this@combineLatestCollection.current) as C) + this@combineLatestCollection.onChange(this, !lazy, callback) + if (lazy) { + reconcile( + sources = sources, + current = ArrayList(this@combineLatestCollection.current) as C, + map = map, + doOnChange = { updateAndNotify(computeFresh()) }, + invokeOnRegistration = cached.getOrNull() != null, + ) + } } override fun stopMonitoring() { @@ -232,8 +242,10 @@ object ObservableExtensions { ?.some() ?: none() + override fun startMonitoring() = startMonitoring(false) + @Suppress("UNCHECKED_CAST") - override fun startMonitoring() { + override fun startMonitoring(lazy: Boolean) { val callback: (C) -> Unit = { current -> reconcile( sources = sources, @@ -248,8 +260,16 @@ object ObservableExtensions { ) } - this@flatMapCollection.onChange(this, callback) - callback(ArrayList(this@flatMapCollection.current) as C) + this@flatMapCollection.onChange(this, !lazy, callback) + if (lazy) { + reconcile( + sources = sources, + current = ArrayList(this@flatMapCollection.current) as C, + map = map, + doOnChange = { updateAndNotify(it.some()) }, + invokeOnRegistration = cached.getOrNull() != null, + ) + } } override fun stopMonitoring() { @@ -264,14 +284,15 @@ object ObservableExtensions { current: Collection, map: (T) -> Observable, doOnChange: (O) -> Unit, - postCleanup: () -> Unit, + postCleanup: () -> Unit = {}, + invokeOnRegistration: Boolean = true, ) { val currentSet = current.toSet() (sources.keys - currentSet).forEach { sources.remove(it)?.stopWatching(this) } (currentSet - sources.keys).forEach { key -> with(map(key)) { sources[key] = this - onChange(this@reconcile, doOnChange) + onChange(this@reconcile, invokeOnRegistration, doOnChange) } } postCleanup() @@ -296,9 +317,11 @@ object ObservableExtensions { ?.let { _ -> collector(this@combineLatest.map { it.current }).some() } ?: arrow.core.none() - override fun startMonitoring() { + override fun startMonitoring() = startMonitoring(false) + + override fun startMonitoring(lazy: Boolean) { this@combineLatest.forEach { observable -> - observable.onChange(this) { updateAndNotify(computeFresh()) } + observable.onChange(this, !lazy) { updateAndNotify(computeFresh()) } } } @@ -320,9 +343,14 @@ object ObservableExtensions { override fun computeFresh(): R = transform(this@switchMap.current).current - override fun startMonitoring() { - this@switchMap.onChange(this, ::switchInner) - switchInner(this@switchMap.current) + override fun startMonitoring() = startMonitoring(false) + + override fun startMonitoring(lazy: Boolean) { + this@switchMap.onChange(this, !lazy, ::switchInner) + if (lazy) { + // a manual switch to set up inner subscription without emitting is required if lazy + switchInner(this@switchMap.current, invokeOnRegistration = cached.getOrNull() != null) + } } override fun stopMonitoring() { @@ -331,12 +359,16 @@ object ObservableExtensions { innerSubscription = null } - private fun switchInner(value: T) { + private fun switchInner(value: T) = switchInner(value, true) + + private fun switchInner(value: T, invokeOnRegistration: Boolean) { innerSubscription?.stopWatching(this) with(transform(value)) { innerSubscription = this - onChange(this@switchMap, ::updateAndNotify) - updateAndNotify(this.current) + onChange(this@switchMap, invokeOnRegistration, ::updateAndNotify) + if (invokeOnRegistration) { + updateAndNotify(this.current) + } } } } diff --git a/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/LazinessTest.kt b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/LazinessTest.kt new file mode 100644 index 0000000000..c37750c730 --- /dev/null +++ b/alchemist-api/src/test/kotlin/it/unibo/alchemist/model/observation/LazinessTest.kt @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2010-2026, Danilo Pianini and contributors + * listed, for each module, in the respective subproject's build.gradle.kts file. + * + * This file is part of Alchemist, and is distributed under the terms of the + * GNU General Public License, with a linking exception, + * as described in the file LICENSE in the Alchemist distribution's top directory. + */ + +package it.unibo.alchemist.model.observation + +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.shouldBe +import it.unibo.alchemist.model.observation.MutableObservable.Companion.observe +import it.unibo.alchemist.model.observation.ObservableExtensions.ObservableSetExtensions.combineLatest +import it.unibo.alchemist.model.observation.ObservableExtensions.combineLatest +import it.unibo.alchemist.model.observation.ObservableExtensions.switchMap + +class LazinessTest : FunSpec({ + + context("DerivedObservable laziness") { + + test("should not compute fresh value if registered lazily") { + var computations = 0 + val derived = object : DerivedObservable() { + override fun computeFresh(): Int { + computations++ + return 26 + } + + override fun startMonitoring() = Unit + override fun stopMonitoring() = Unit + } + derived.onChange(this, false) { } + computations shouldBe 0 + } + + test("should compute fresh value if registered eagerly") { + var computations = 0 + val derived = object : DerivedObservable() { + override fun computeFresh(): Int { + computations++ + return 26 + } + + override fun startMonitoring() = Unit + override fun stopMonitoring() = Unit + } + + derived.onChange(this, true) { } + computations shouldBe 1 + } + } + + context("Map laziness") { + test("map should not apply transform if registered lazily") { + val source = observe(1) + var transforms = 0 + val mapped = source.map { + transforms++ + it * 2 + } + + mapped.onChange(this, false) { } + transforms shouldBe 0 + } + + test("map should apply transform if registered eagerly") { + val source = observe(1) + var transforms = 0 + val mapped = source.map { + transforms++ + it * 2 + } + + mapped.onChange(this, true) { } + transforms shouldBe 1 + } + + test("map should receive updates even if registered lazily") { + val source = observe(1) + val mapped = source.map { it * 2 } + var lastSeen: Int? = null + + mapped.onChange(this, false) { lastSeen = it } + source.update { 2 } + lastSeen shouldBe 4 + } + } + + context("Merge laziness") { + test("mergeWith should not merge if registered lazily") { + val a = observe(1) + val b = observe(2) + var merges = 0 + val merged = a.mergeWith(b) { x, y -> + merges++ + x + y + } + merged.onChange(this, false) { } + merges shouldBe 0 + } + + test("mergeWith should receive updates from both sources if registered lazily") { + val a = observe(1) + val b = observe(2) + val merged = a.mergeWith(b) { x, y -> x + y } + var lastSeen: Int? = null + + merged.onChange(this, false) { lastSeen = it } + + a.update { 3 } + lastSeen shouldBe 5 + + b.update { 4 } + lastSeen shouldBe 7 + } + } + + context("Extension methods laziness") { + + test("combineLatest (collection) should not aggregate if registered lazily") { + val set = ObservableMutableSet(1, 2) + var aggregations = 0 + val combined = set.combineLatest( + map = { observe(it) }, + aggregator = { + aggregations++ + it.sum() + }, + ) + + combined.onChange(this, false) { } + aggregations shouldBe 0 + } + + test("combineLatest (collection) should receive updates if registered lazily") { + val set = ObservableMutableSet("a") + val inner = observe(10) + val combined = set.combineLatest( + map = { inner }, + aggregator = { it.sum() }, + ) + var lastSeen: Int? = null + + combined.onChange(this, false) { lastSeen = it } + + inner.update { 20 } + lastSeen shouldBe 20 + + set.add("b") + lastSeen shouldBe 40 // 20 + 20 (both map to same inner) + } + + test("combineLatest (list) should not collect if registered lazily") { + val list = listOf(observe(1), observe(2)) + var collections = 0 + val combined = list.combineLatest { + collections++ + it.sum() + } + + combined.onChange(this, false) { } + collections shouldBe 0 + } + + test("switchMap should not transform if registered lazily") { + val source = observe(1) + var transforms = 0 + val switched = source.switchMap { + transforms++ + observe(it) + } + switched.onChange(this, false) { } + transforms shouldBe 1 + } + + test("switchMap should work correctly after lazy registration") { + val source = observe(1) + val switched = source.switchMap { observe(it * 10) } + var lastSeen: Int? = null + + switched.onChange(this, false) { lastSeen = it } + + source.update { 2 } + lastSeen shouldBe 20 + } + } +}) From 6eb07ee7c857cc7cfd9339f61c9ea28be409325f Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 12:27:54 +0100 Subject: [PATCH 10/11] fix(scafi): avoid depending on observables For actions in general, it is better to use an imperative approach and retrieve the current neighborhood instead of having an up-to-date neighborhood version stored locally. --- .../model/implementations/actions/SendScafiMessage.scala | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala index 31f7fd1c62..2cd077357c 100644 --- a/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala +++ b/alchemist-incarnation-scafi/src/main/scala/it/unibo/alchemist/model/implementations/actions/SendScafiMessage.scala @@ -27,13 +27,6 @@ class SendScafiMessage[T, P <: Position[P]]( assert(reaction != null, "Reaction cannot be null") assert(program != null, "Program cannot be null") - private var neighbors: Iterator[Node[T]] = Iterator.empty - environment.getNeighborhood(device.getNode).onChange(this, neighborhood => { - neighbors = neighborhood.getNeighbors.iterator().asScala - kotlin.Unit.INSTANCE - } - ) - /** * This method allows to clone this action on a new node. It may result useful to support runtime creation of nodes * with the same reaction programming, e.g. for morphogenesis. @@ -70,7 +63,7 @@ class SendScafiMessage[T, P <: Position[P]]( override def execute(): Unit = { val toSend = program.getExport(device.getNode.getId).get for { - neighborhood <- neighbors + neighborhood <- environment.getNeighborhood(device.getNode).getCurrent.getNeighbors.iterator().asScala action <- ScafiIncarnationUtils.allScafiProgramsFor[T, P](neighborhood).filter(program.getClass.isInstance(_)) if action.programNameMolecule == program.programNameMolecule } action.sendExport(device.getNode.getId, toSend) From d9182f491c96368f1dbdb7b0e297c56c9bf05e09 Mon Sep 17 00:00:00 2001 From: S-furi Date: Thu, 22 Jan 2026 16:33:00 +0100 Subject: [PATCH 11/11] feat!: replace Engine with reactive counterpart and get rid of dependency graph fix: remove obsolete reactive engine loading chore(engine): use explicit synchronisation instead of thread contexts --- .../unibo/alchemist/core/DependencyGraph.java | 78 ----- .../it/unibo/alchemist/core/BatchEngine.kt | 54 ++-- .../kotlin/it/unibo/alchemist/core/Engine.kt | 273 +++--------------- .../alchemist/core/JGraphTDependencyGraph.kt | 257 ----------------- .../it/unibo/alchemist/core/ReactiveEngine.kt | 116 -------- .../alchemist/core/TestDependencyGraph.kt | 28 -- .../alchemist/core/TestEngineComparison.kt | 146 ---------- .../core/TestReactiveEngineImplementation.kt | 12 - .../boundary/loader/LoadingSystem.kt | 8 +- .../test/TestReactiveEngineLoading.kt | 41 --- run_all_tests_reactive.sh | 2 - 11 files changed, 61 insertions(+), 954 deletions(-) delete mode 100644 alchemist-api/src/main/java/it/unibo/alchemist/core/DependencyGraph.java delete mode 100644 alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt delete mode 100644 alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/ReactiveEngine.kt delete mode 100644 alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestDependencyGraph.kt delete mode 100644 alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestEngineComparison.kt delete mode 100644 alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestReactiveEngineImplementation.kt delete mode 100644 alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestReactiveEngineLoading.kt delete mode 100755 run_all_tests_reactive.sh diff --git a/alchemist-api/src/main/java/it/unibo/alchemist/core/DependencyGraph.java b/alchemist-api/src/main/java/it/unibo/alchemist/core/DependencyGraph.java deleted file mode 100644 index 85292c73e3..0000000000 --- a/alchemist-api/src/main/java/it/unibo/alchemist/core/DependencyGraph.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2010-2023, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.core; - -import it.unibo.alchemist.model.Actionable; -import it.unibo.alchemist.model.Node; -import org.danilopianini.util.ListSet; - -/** - * This interface allows separating the usage of a dependency graph from its - * implementation. - * - * @param - * The parametrization type for reactions - */ -public interface DependencyGraph { - - /** - * Given two nodes, the graph assumes they are now neighbors and calculates the - * neighborhood dependencies between them. - * - * @param n1 - * The first node - * @param n2 - * The second node - */ - void addNeighbor(Node n1, Node n2); - - /** - * This method creates the dependencies when a new reaction is added to the - * environment. Please be careful when building the environment and populating - * the existing reactions map: this method assumes that all the dependencies - * among the existing reactions are correct and up to date. - * - * @param reactionHandler the reaction handler whose dependencies should be calculated. - */ - void createDependencies(Actionable reactionHandler); - - /** - * This method removes all the dependencies (both in and out dependencies) for a given reaction handler. - * This method is meant to be used to keep the dependencies clean when removing a reaction. - * - * @param reactionHandler the reaction handler whose dependencies will be deleted. - */ - void removeDependencies(Actionable reactionHandler); - - /** - * Given two nodes, the engine assumes they are no longer neighbors and deletes - * the neighborhood dependencies between them. - * - * @param n1 - * The first node - * @param n2 - * The second node - */ - void removeNeighbor(Node n1, Node n2); - - /** - * Returns the set of reactions that may be influenced by the provided reaction. - * - * @param reaction the input reaction - * @return the {@link java.util.Set} of reactions that may be influenced by the provided reaction - */ - ListSet> outboundDependencies(Actionable reaction); - - /** - * @return the {@link java.util.Set} of all reactions - * with a {@link it.unibo.alchemist.model.Context#GLOBAL} input context - */ - ListSet> globalInputContextReactions(); -} diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/BatchEngine.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/BatchEngine.kt index 90cdecfafc..d67ad3ff6f 100644 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/BatchEngine.kt +++ b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/BatchEngine.kt @@ -8,7 +8,6 @@ */ package it.unibo.alchemist.core -import com.google.common.collect.Sets import it.unibo.alchemist.boundary.OutputMonitor import it.unibo.alchemist.core.BatchEngine.OutputReplayStrategy.Aggregate.toReplayStrategy import it.unibo.alchemist.model.Actionable @@ -29,11 +28,10 @@ import org.slf4j.LoggerFactory * * @param concentration type * @param

[Position] type -

*/ + */ class BatchEngine> : Engine { private val outputReplayStrategy: OutputReplayStrategy private val executeLock = Any() - private val updateLock = Any() private constructor( environment: Environment, @@ -98,17 +96,17 @@ class BatchEngine> : Engine { */ override fun doStep() { val batchedScheduler = scheduler as BatchedScheduler - val nextEvents = batchedScheduler.nextBatch + val nextEvents = batchedScheduler.getNextBatch() val batchSize = nextEvents.size if (nextEvents.isEmpty()) { - newStatus(Status.TERMINATED) + terminate() LOGGER.info("No more reactions.") return } - val sortededNextEvents = - nextEvents.stream().sorted(Comparator.comparing(Actionable::tau)).collect(Collectors.toList()) - val minSlidingWindowTime = sortededNextEvents[0].tau - val maxSlidingWindowTime = sortededNextEvents[sortededNextEvents.size - 1].tau + val sortedNextEvents = nextEvents.sortedBy { it.tau } + val minSlidingWindowTime = sortedNextEvents.first().tau + val maxSlidingWindowTime = sortedNextEvents.last().tau + runBlocking { val taskMapper = Function { event: Actionable -> @@ -140,7 +138,7 @@ class BatchEngine> : Engine { is OutputReplayStrategy.Reply -> resultsOrderedByTime.forEach(::doStepDoneAllMonitors) is OutputReplayStrategy.Aggregate -> - doStepDoneAllMonitors(resultsOrderedByTime[resultsOrderedByTime.size - 1]) + doStepDoneAllMonitors(resultsOrderedByTime.last()) } private fun doStepDoneAllMonitors(result: TaskResult) { @@ -152,19 +150,33 @@ class BatchEngine> : Engine { private fun doEvent(nextEvent: Actionable, slidingWindowTime: Time): TaskResult { validateEventExecutionTime(nextEvent, slidingWindowTime) val currentLocalTime = nextEvent.tau + if (nextEvent.canExecute()) { safeExecuteEvent(nextEvent) - safeUpdateEvent(nextEvent) } + nextEvent.update(currentLocalTime, true, environment) - scheduler.updateReaction(nextEvent) + synchronized(scheduler) { + scheduler.updateReaction(nextEvent) + } + if (environment.isTerminated) { - newStatus(Status.TERMINATED) + terminate() LOGGER.info("Termination condition reached.") } return TaskResult(nextEvent, currentLocalTime) } + override fun updateReaction(reaction: Actionable) { + val previousTau = reaction.tau + reaction.update(time, false, environment) + if (reaction.tau != previousTau) { + synchronized(scheduler) { + scheduler.updateReaction(reaction) + } + } + } + private fun validateEventExecutionTime(nextEvent: Actionable, slidingWindowTime: Time) { val scheduledTime = nextEvent.tau if (!isEventTimeScheduledInFirstBatch(scheduledTime) && @@ -199,22 +211,6 @@ class BatchEngine> : Engine { } } - private fun safeUpdateEvent(event: Actionable) { - synchronized(updateLock) { - var toUpdate: Set> = dependencyGraph.outboundDependencies(event) - if (!afterExecutionUpdates.isEmpty()) { - afterExecutionUpdates.forEach { it.performChanges() } - afterExecutionUpdates.clear() - toUpdate = - Sets.union( - toUpdate, - dependencyGraph.outboundDependencies(event), - ) - } - toUpdate.forEach { updateReaction(it) } - } - } - /** * Safely set simulation status. */ diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt index d4a238f22d..d26f873d42 100644 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt +++ b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/Engine.kt @@ -1,29 +1,26 @@ /* - * Copyright (C) 2010-2025, Danilo Pianini and contributors + * Copyright (C) 2010-2026, Danilo Pianini and contributors * listed, for each module, in the respective subproject's build.gradle.kts file. * * This file is part of Alchemist, and is distributed under the terms of the * GNU General Public License, with a linking exception, * as described in the file LICENSE in the Alchemist distribution's top directory. */ + package it.unibo.alchemist.core -import com.google.common.collect.Sets import it.unibo.alchemist.model.Actionable -import it.unibo.alchemist.model.Context -import it.unibo.alchemist.model.Dependency import it.unibo.alchemist.model.Environment import it.unibo.alchemist.model.Neighborhood import it.unibo.alchemist.model.Node import it.unibo.alchemist.model.Position -import it.unibo.alchemist.model.Reaction -import java.util.ArrayDeque -import java.util.Queue -import org.jooq.lambda.fi.lang.CheckedRunnable /** - * Represents a simulation engine that manages execution and scheduling. - * Provides multiple factory methods to simplify the creation process. + * A reactive implementation of the [Simulation] engine. + * This engine does not use a dependency graph. Instead, it relies on [Actionable]s + * (Reactions) being reactive based on what they [observe][it.unibo.alchemist.model.observation.Observable]. + * Sources' (nodes' contents, positions, neighborhoods, ...) changes will automatically trigger reactions + * updates and reschedules. * * @param T the concentration type * @param P the position type, extending [Position] @@ -35,30 +32,16 @@ open class Engine>( protected val scheduler: Scheduler, ) : AbstractEngine(environment) { - /** Queue of updates to be processed after execution. */ - protected val afterExecutionUpdates: Queue = ArrayDeque() - - /** Manages dependencies between reactions in the simulation. */ - protected val dependencyGraph: DependencyGraph = JGraphTDependencyGraph(environment) - - /** - * Constructs a simulation with a default scheduler. - * - * This constructor initializes the simulation with a default [ArrayIndexedPriorityQueue]. - * If you need a custom [DependencyGraph] or [Scheduler], use the other constructor. - * - * @param environment the simulation environment - */ constructor(environment: Environment) : this(environment, ArrayIndexedPriorityQueue()) override fun initialize() { - environment.globalReactions.forEach { reactionAdded(it) } - environment.forEach { node -> node.reactions.forEach { scheduleReaction(it) } } + environment.globalReactions.forEach(::scheduleReaction) + environment.forEach { it.reactions.forEach(::scheduleReaction) } } override fun doStep() { val nextEvent = scheduler.getNext() ?: run { - newStatus(Status.TERMINATED) + terminate() LOGGER.info("No more reactions.") return } @@ -70,250 +53,64 @@ open class Engine>( if (scheduledTime.isFinite && nextEvent.canExecute()) { nextEvent.conditions.forEach { it.reactionReady() } nextEvent.execute() - var toUpdate: Set> = dependencyGraph.outboundDependencies(nextEvent) - if (afterExecutionUpdates.isNotEmpty()) { - afterExecutionUpdates.forEach { it.performChanges() } - afterExecutionUpdates.clear() - toUpdate = Sets.union(toUpdate, dependencyGraph.outboundDependencies(nextEvent)) - } - toUpdate.forEach { updateReaction(it) } } nextEvent.update(time, true, environment) scheduler.updateReaction(nextEvent) + monitors.forEach { it.stepDone(environment, nextEvent, time, step) } if (environment.isTerminated) { - newStatus(Status.TERMINATED) + terminate() LOGGER.info("Termination condition reached.") } currentStep = step + 1 } - /** - * Registers a newly added neighbor. - * - * @param node the reference node - * @param n the neighbor node - */ - override fun neighborAdded(node: Node, n: Node) { - checkCaller() - afterExecutionUpdates.add(NeighborAdded(node, n)) - } + /* No dependency graph to update */ + override fun neighborAdded(node: Node, n: Node) = Unit - /** - * Registers a removed neighbor. - * - * @param node the reference node - * @param n the removed neighbor node - */ - override fun neighborRemoved(node: Node, n: Node) { - checkCaller() - afterExecutionUpdates.add(NeighborRemoved(node, n)) - } + override fun neighborRemoved(node: Node, n: Node) = Unit - /** - * Handles the addition of a new node. - * - * @param node the newly added node - */ - override fun nodeAdded(node: Node) { - checkCaller() - afterExecutionUpdates.add(NodeAddition(node)) - } + override fun nodeMoved(node: Node) = Unit - /** - * Handles node movement. - * - * @param node the moved node - */ - override fun nodeMoved(node: Node) { - checkCaller() - afterExecutionUpdates.add(Movement(node)) + override fun nodeAdded(node: Node) { + schedule { + node.reactions.forEach { scheduleReaction(it) } + } } - /** - * Handles the removal of a node. - * - * @param node the removed node - * @param oldNeighborhood the node's neighborhood before removal (used for reverse dependencies) - */ override fun nodeRemoved(node: Node, oldNeighborhood: Neighborhood) { - checkCaller() - afterExecutionUpdates.add(NodeRemoval(node)) + schedule { + node.reactions.forEach { removeReaction(it) } + } } - /** - * Registers a newly added reaction. - * - * @param reactionToAdd the reaction to add - */ override fun reactionAdded(reactionToAdd: Actionable) { - reactionChanged(ReactionAddition(reactionToAdd)) + schedule { scheduleReaction(reactionToAdd) } } - /** - * Registers a removed reaction. - * - * @param reactionToRemove the reaction to remove - */ override fun reactionRemoved(reactionToRemove: Actionable) { - reactionChanged(ReactionRemoval(reactionToRemove)) - } - - /** - * Handles reaction changes. - * - * @param update the update describing the reaction change - */ - private fun reactionChanged(update: AbstractUpdateOnReaction) { - checkCaller() - afterExecutionUpdates.add(update) - } - - /** Retrieves the reactions that require updates after execution. */ - private fun reactionsToUpdateAfterExecution(): Sequence> = - afterExecutionUpdates.asSequence().flatMap { it.reactionsToUpdate }.distinct() - - override fun processCommand(command: CheckedRunnable) { - command.run() - val updated = mutableSetOf>() - reactionsToUpdateAfterExecution().forEach { - updated.add(it) - updateReaction(it) - } - afterExecutionUpdates.forEach { it.performChanges() } - afterExecutionUpdates.clear() - reactionsToUpdateAfterExecution().forEach { if (it !in updated) updateReaction(it) } + schedule { removeReaction(reactionToRemove) } } - /** - * Schedules a reaction by setting up dependencies and adding it to the scheduler. - * - * @param reaction the reaction to schedule - */ private fun scheduleReaction(reaction: Actionable) { - dependencyGraph.createDependencies(reaction) reaction.initializationComplete(time, environment) scheduler.addReaction(reaction) - } - - /** - * Updates the given reaction, adjusting its scheduling if needed. - * - * @param r the reaction to update - */ - protected fun updateReaction(r: Actionable) { - val previousTau = r.tau - r.update(time, false, environment) - if (r.tau != previousTau) scheduler.updateReaction(r) - } - /** - * Represents a simulation update operation. - */ - protected open inner class Update { - /** Performs the update. Override to implement specific behavior. */ - open fun performChanges() {} - - /** The reactions that require an update. */ - open val reactionsToUpdate: Sequence> = emptySequence() - } - - /** - * Handles node movement and updates affected reactions. - * - * @param sourceNode the node that moved - */ - private inner class Movement(private val sourceNode: Node) : Update() { - override val reactionsToUpdate: Sequence> - get() = getReactionsRelatedTo(sourceNode, environment.getNeighborhood(sourceNode).current).filter { - it.inboundDependencies.any { dependency -> dependency.dependsOn(Dependency.MOVEMENT) } - } - - private fun getReactionsRelatedTo(source: Node, neighborhood: Neighborhood): Sequence> = - sequenceOf( - source.reactions.asSequence(), - neighborhood.getNeighbors().asSequence() - .flatMap { it.reactions.asSequence() } - .filter { it.inputContext == Context.NEIGHBORHOOD }, - dependencyGraph.globalInputContextReactions().asSequence(), - ).flatten() - } - - /** - * Applies an update to all reactions of a node. - * - * @param sourceNode the node whose reactions should be updated - * @param reactionLevelOperation the update operation to apply to each reaction - */ - private open inner class UpdateOnNode( - private val sourceNode: Node, - private val reactionLevelOperation: (Reaction) -> Update, - ) : Update() { - override fun performChanges() { - sourceNode.reactions.map(reactionLevelOperation).forEach { it.performChanges() } + reaction.rescheduleRequest.onChange(this, false) { + updateReaction(reaction) } } - private inner class NodeRemoval(sourceNode: Node) : UpdateOnNode(sourceNode, { ReactionRemoval(it) }) - - private inner class NodeAddition(sourceNode: Node) : UpdateOnNode(sourceNode, { ReactionAddition(it) }) - - /** - * Represents an update affecting a specific reaction. - * - * @param sourceReaction the reaction affected by this update - */ - private open inner class AbstractUpdateOnReaction(val sourceReaction: Actionable) : Update() { - override val reactionsToUpdate: Sequence> = sequenceOf(sourceReaction) - } - - /** Handles the removal of a reaction. */ - private inner class ReactionRemoval(source: Actionable) : AbstractUpdateOnReaction(source) { - override fun performChanges() { - dependencyGraph.removeDependencies(sourceReaction) - scheduler.removeReaction(sourceReaction) + protected open fun updateReaction(reaction: Actionable) { + val previousTau = reaction.tau + reaction.update(time, false, environment) + if (reaction.tau != previousTau) { + scheduler.updateReaction(reaction) } } - /** Handles the addition of a reaction. */ - private inner class ReactionAddition(source: Actionable) : AbstractUpdateOnReaction(source) { - override fun performChanges() { - this@Engine.scheduleReaction(sourceReaction) - } - } - - /** - * Handles neighborhood changes, ensuring updates to relevant reactions. - * - * @param sourceNode the node initiating the change - * @param targetNode the node affected by the change - */ - private open inner class NeighborhoodChanged(val sourceNode: Node, val targetNode: Node) : Update() { - override val reactionsToUpdate: Sequence> - get() { - val subjects = sequenceOf(sourceNode, targetNode) - val sourceNeighbors = environment.getNeighborhood(sourceNode).current.asSequence() - val targetNeighbors = environment.getNeighborhood(targetNode).current.asSequence() - val allSubjects = (subjects + sourceNeighbors + targetNeighbors).distinct() - return allSubjects.flatMap { it.reactions.asSequence() }.filter { - it.inputContext == - Context.NEIGHBORHOOD - } + - dependencyGraph.globalInputContextReactions().asSequence() - } - } - - /** Handles the addition of a neighbor. */ - private inner class NeighborAdded(source: Node, target: Node) : NeighborhoodChanged(source, target) { - override fun performChanges() { - dependencyGraph.addNeighbor(sourceNode, targetNode) - } - } - - /** Handles the removal of a neighbor. */ - private inner class NeighborRemoved(source: Node, target: Node) : NeighborhoodChanged(source, target) { - override fun performChanges() { - dependencyGraph.removeNeighbor(sourceNode, targetNode) - } + private fun removeReaction(reaction: Actionable) { + reaction.rescheduleRequest.stopWatching(this) + scheduler.removeReaction(reaction) } } diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt deleted file mode 100644 index 8814993771..0000000000 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/JGraphTDependencyGraph.kt +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (C) 2010-2023, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ -package it.unibo.alchemist.core - -import it.unibo.alchemist.model.Actionable -import it.unibo.alchemist.model.Context -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Node -import it.unibo.alchemist.model.Reaction -import it.unibo.alchemist.util.BugReporting -import org.danilopianini.util.ArrayListSet -import org.danilopianini.util.ListSet -import org.danilopianini.util.ListSets -import org.jgrapht.graph.DefaultDirectedGraph - -private typealias Edge = Pair, Actionable> - -/** - * This class offers an implementation of a dependency graph, namely a - * data structure which can address in an efficient way the problem of - * finding those reactions affected by the execution of another - * reaction. This class relies heavily on the ReactionHandler - * interface. - * - * @param concentration type - */ -class JGraphTDependencyGraph(private val environment: Environment) : DependencyGraph { - private val inGlobals = ArrayListSet>() - private val outGlobals = ArrayListSet>() - private val graph: DefaultDirectedGraph, Edge> = DefaultDirectedGraph(null, null, false) - private val runtimeRemovalCache = mutableSetOf>() - - override fun createDependencies(newReaction: Actionable) { - val allReactions = graph.vertexSet() - val neighborhood by lazy { - if (newReaction is Reaction) { - newReaction.node.neighborhood - } else { - ListSets.emptyListSet() - } - } - val localReactions by lazy { - if (newReaction is Reaction) { - newReaction.node.reactions - .filter { allReactions.contains(it) } - .asSequence() - } else { - emptySequence() - } - } - val neighborhoodReactions by lazy { - neighborhood - .asSequence() - .flatMap { it.reactions.asSequence() } - .filter { allReactions.contains(it) } - .toList() - .asSequence() - } - val extendedNeighborhoodReactions by lazy { - neighborhood - .asSequence() - // Neighbors of neighbors - .flatMap { it.neighborhood.asSequence() } - // No duplicates - .distinct() - // Exclude self and direct neighbors - .filterNot { it == newReaction.node || it in neighborhood } - .flatMap { it.reactions.asSequence() } - .filter { allReactions.contains(it) } - .toList() - .asSequence() - } - - fun Context.candidates( - oppositeGlobal: Sequence>, - oppositeContext: Actionable.() -> Context, - ): Sequence> = when (this) { - Context.LOCAL -> - oppositeGlobal + - localReactions + - neighborhoodReactions.filter { it.oppositeContext() == Context.NEIGHBORHOOD } - Context.NEIGHBORHOOD -> - oppositeGlobal + - localReactions + - neighborhoodReactions + - extendedNeighborhoodReactions.filter { it.oppositeContext() == Context.NEIGHBORHOOD } - Context.GLOBAL -> - allReactions.asSequence() - } - val inboundCandidates: Sequence> = - newReaction.inputContext.candidates(outGlobals.asSequence()) { outputContext } - val outboundCandidates: Sequence> = - newReaction.outputContext.candidates(inGlobals.asSequence()) { inputContext } - check(graph.addVertex(newReaction)) { - "$newReaction was already in the dependency graph" - } - inboundCandidates - .filter { newReaction.dependsOn(it) } - .forEach { graph.addEdge(it, newReaction, Edge(it, newReaction)) } - outboundCandidates - .filter { it.dependsOn(newReaction) } - .forEach { graph.addEdge(newReaction, it, Edge(newReaction, it)) } - if (newReaction.inputContext == Context.GLOBAL) { - inGlobals.add(newReaction) - } - if (newReaction.outputContext == Context.GLOBAL) { - outGlobals.add(newReaction) - } - } - - override fun removeDependencies(reaction: Actionable) { - fun bugInfo() = mapOf( - "reaction" to reaction, - "graph" to graph, - "incarnation" to environment.incarnation, - "environment" to environment, - ) - - fun bug(message: String): Nothing = BugReporting.reportBug(message, bugInfo()) - if (!graph.removeVertex(reaction)) { - bug("Reaction does not exists in the dependency graph.") - } - if (reaction.inputContext == Context.GLOBAL && !inGlobals.remove(reaction)) { - bug("Inconsistent state: $reaction, with global input context, was not in the appropriate pool.") - } - if (reaction.outputContext == Context.GLOBAL && !outGlobals.remove(reaction)) { - bug("Inconsistent state: $reaction, with global input context, was not in the appropriate pool.") - } - runtimeRemovalCache += reaction - } - - private fun addNeighborDirected(n1: Node, n2: Node) { - val n2NonGlobalReactions: Iterable> by lazy { - n2.reactions.filterNot { it.outputContext == Context.GLOBAL } - } - val n2NeighborhoodReactions: Iterable> by lazy { - n2NonGlobalReactions.filter { it.outputContext == Context.NEIGHBORHOOD } - } - val neighborInputInfluencers: Iterable> by lazy { - // All the non-global reactions of the new neighbor - n2NonGlobalReactions + - // Plus all the reactions of the new neighbor's neighbors with neighborhood output - (n2.neighborhood - setOf(n1) - n1.neighborhood) - .asSequence() - .flatMap { it.reactions.asSequence() } - .filter { it.outputContext == Context.NEIGHBORHOOD } - } - n1.reactions.forEach { reaction -> - when (reaction.inputContext) { - // Local-reading reactions can be only influenced by the new neighbor's neighborhood reactions - Context.LOCAL -> n2NeighborhoodReactions - Context.NEIGHBORHOOD -> neighborInputInfluencers - else -> emptyList() - }.asSequence() - .filter { reaction.dependsOn(it) } - .forEach { graph.addEdge(it, reaction, Edge(it, reaction)) } - } - } - - /** @see [DependencyGraph.addNeighbor] */ - override fun addNeighbor(n1: Node, n2: Node) { - addNeighborDirected(n1, n2) - addNeighborDirected(n2, n1) - } - - /** - * Remove edges linking reactions in [n1] that could have influenced reactions in [n2]. - * - reactions of n1 with output local may have influenced those with input neighborhood in [n2] - * - reactions of n1 with output neighborhood may have influenced those with input local or neighborhood in [n2] - * plus those with input neighborhood in the n2's neighborhood that is no longer part of [n1] neighborhood - * - reactions with global output are unmodified - */ - private fun removeNeighborDirected(n1: Node, n2: Node) { - val n2NonGlobalReactions by lazy { n2.reactions.filterNot { it.inputContext == Context.GLOBAL } } - val n2NeighborhoodReactions by lazy { n2NonGlobalReactions.filter { it.inputContext == Context.NEIGHBORHOOD } } - val neighborOutputInfluencers by lazy { - // All the non-global reactions of the old neighbor - n2NonGlobalReactions + - // Plus all the reactions of the new neighbor's neighbors with neighborhood output - (n2.neighborhood - setOf(n1) - n1.neighborhood - n1.neighborhood.flatMap { it.neighborhood }.toSet()) - .asSequence() - .flatMap { it.reactions.asSequence() } - .filter { it.inputContext == Context.NEIGHBORHOOD } - .toList() - } - n1.reactions.forEach { reaction -> - when (reaction.outputContext) { - // Local-reading reactions may have been influenced only by the ex neighbor neigh-writing reactions - Context.LOCAL -> n2NeighborhoodReactions - Context.NEIGHBORHOOD -> neighborOutputInfluencers - else -> emptyList() - }.asSequence() - .filter { reaction.dependsOn(it) } - .forEach { graph.removeEdge(it, reaction) } - } - } - - override fun removeNeighbor(n1: Node, n2: Node) { - removeNeighborDirected(n1, n2) - removeNeighborDirected(n2, n1) - } - - override fun outboundDependencies(reaction: Actionable?): ListSet> { - if (graph.containsVertex(reaction)) { - return graph.outgoingEdgesOf(reaction).let { edges -> - edges.mapTo(ArrayListSet(edges.size)) { it.second } - } - } - require(runtimeRemovalCache.remove(reaction)) { - BugReporting.reportBug( - "A reaction that is being updated does not exists in the dependency graph, " + - "nor has been scheduled for removal.", - mapOf( - "graph" to graph, - "incarnation" to environment.incarnation, - "environment" to environment, - "reaction" to reaction, - ), - ) - } - return ListSets.emptyListSet() - } - - override fun toString() = graph.toString() - - override fun globalInputContextReactions(): ListSet> = ListSets.unmodifiableListSet(inGlobals) - - private val Actionable.node: Node get() = checkNotNull(this as? Reaction).node - - private fun Actionable.dependsOn(other: Actionable) = inboundDependencies.any { inbound -> - other.outboundDependencies.any { outbound -> - inbound.dependsOn(outbound) || outbound.makesDependent(inbound) - } - } - - private val Node.neighborhood get() = environment.getNeighborhood(this).current.neighbors - - private companion object { - private val Actionable<*>.inputContext get() = - when (this) { - is Reaction -> inputContext - else -> Context.GLOBAL - } - - private val Actionable<*>.outputContext get() = - when (this) { - is Reaction -> outputContext - else -> Context.GLOBAL - } - } -} diff --git a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/ReactiveEngine.kt b/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/ReactiveEngine.kt deleted file mode 100644 index 72e0858d09..0000000000 --- a/alchemist-engine/src/main/kotlin/it/unibo/alchemist/core/ReactiveEngine.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (C) 2010-2026, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.core - -import it.unibo.alchemist.model.Actionable -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Neighborhood -import it.unibo.alchemist.model.Node -import it.unibo.alchemist.model.Position - -/** - * A reactive implementation of the [Simulation] engine. - * This engine does not use a dependency graph. Instead, it relies on [Actionable]s - * (Reactions) being reactive based on what they [observe][it.unibo.alchemist.model.observation.Observable]. - * Sources' (nodes' contents, positions, neighborhoods, ...) changes will automatically trigger reactions - * updates and reschedules. - * - * @param T the concentration type - * @param P the position type, extending [Position] - * @property environment the simulation environment - * @property scheduler the scheduler managing event execution - */ -open class ReactiveEngine>( - private val environment: Environment, - protected val scheduler: Scheduler, -) : AbstractEngine(environment) { - - constructor(environment: Environment) : this(environment, ArrayIndexedPriorityQueue()) - - override fun initialize() { - environment.globalReactions.forEach(::scheduleReaction) - environment.forEach { it.reactions.forEach(::scheduleReaction) } - } - - override fun doStep() { - val nextEvent = scheduler.getNext() ?: run { - terminate() - LOGGER.info("No more reactions.") - return - } - val scheduledTime = nextEvent.tau - check(scheduledTime >= time) { - "$nextEvent is scheduled in the past at time $scheduledTime. Current time: $time; current step: $step." - } - currentTime = scheduledTime - if (scheduledTime.isFinite && nextEvent.canExecute()) { - nextEvent.conditions.forEach { it.reactionReady() } - nextEvent.execute() - } - nextEvent.update(time, true, environment) - scheduler.updateReaction(nextEvent) - - monitors.forEach { it.stepDone(environment, nextEvent, time, step) } - if (environment.isTerminated) { - terminate() - LOGGER.info("Termination condition reached.") - } - currentStep = step + 1 - } - - /* No dependency graph to update */ - override fun neighborAdded(node: Node, n: Node) = Unit - - override fun neighborRemoved(node: Node, n: Node) = Unit - - override fun nodeMoved(node: Node) = Unit - - override fun nodeAdded(node: Node) { - schedule { - node.reactions.forEach { scheduleReaction(it) } - } - } - - override fun nodeRemoved(node: Node, oldNeighborhood: Neighborhood) { - schedule { - node.reactions.forEach { removeReaction(it) } - } - } - - override fun reactionAdded(reactionToAdd: Actionable) { - schedule { scheduleReaction(reactionToAdd) } - } - - override fun reactionRemoved(reactionToRemove: Actionable) { - schedule { removeReaction(reactionToRemove) } - } - - private fun scheduleReaction(reaction: Actionable) { - reaction.initializationComplete(time, environment) - scheduler.addReaction(reaction) - - reaction.rescheduleRequest.onChange(this) { - updateReaction(reaction) - } - } - - protected fun updateReaction(reaction: Actionable) { - val previousTau = reaction.tau - reaction.update(time, false, environment) - if (reaction.tau != previousTau) { - scheduler.updateReaction(reaction) - } - } - - private fun removeReaction(reaction: Actionable) { - reaction.rescheduleRequest.stopWatching(this) - scheduler.removeReaction(reaction) - } -} diff --git a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestDependencyGraph.kt b/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestDependencyGraph.kt deleted file mode 100644 index 1425bba0a3..0000000000 --- a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestDependencyGraph.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2010-2023, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.core - -import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Reaction - -class TestDependencyGraph : AbstractDependencyTest() { - - private lateinit var graph: DependencyGraph - - override fun beforeTest(environment: Environment) { - graph = JGraphTDependencyGraph(environment) - environment.nodes.flatMap { it.reactions }.forEach { graph.createDependencies(it) } - } - - override fun Reaction.assertDependencies(vararg dependencies: Reaction) { - graph.outboundDependencies(this).toList() shouldContainExactlyInAnyOrder dependencies.toList() - } -} diff --git a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestEngineComparison.kt b/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestEngineComparison.kt deleted file mode 100644 index 7acd1dd7c0..0000000000 --- a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestEngineComparison.kt +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright (C) 2010-2026, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.core - -import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.collections.shouldContainExactly -import it.unibo.alchemist.boundary.OutputMonitor -import it.unibo.alchemist.core.util.DependencyUtils -import it.unibo.alchemist.model.Actionable -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Molecule -import it.unibo.alchemist.model.Node -import it.unibo.alchemist.model.Position -import it.unibo.alchemist.model.Time -import it.unibo.alchemist.model.biochemistry.BiochemistryIncarnation -import it.unibo.alchemist.model.biochemistry.molecules.Biomolecule -import it.unibo.alchemist.model.conditions.MoleculeHasConcentration -import it.unibo.alchemist.model.environments.Continuous2DEnvironment -import it.unibo.alchemist.model.linkingrules.NoLinks -import it.unibo.alchemist.model.nodes.GenericNode -import it.unibo.alchemist.model.timedistributions.DiracComb -import it.unibo.alchemist.model.times.DoubleTime -import java.util.concurrent.CopyOnWriteArrayList -import kotlin.jvm.optionals.getOrNull -import org.junit.jupiter.api.fail - -class TestEngineComparison : FreeSpec({ - - data class TraceEntry(val time: Double, val nodeId: Int, val molecule: String, val concentration: Double) - - class TraceMonitor> : OutputMonitor { - val trace = CopyOnWriteArrayList() - - override fun stepDone(environment: Environment, reaction: Actionable?, time: Time, step: Long) { - environment.nodes.forEach { node -> - node.contents.forEach { (mol, conc) -> - trace.add(TraceEntry(time.toDouble(), node.id, mol.name, (conc as Number).toDouble())) - } - } - } - } - - fun prepareEnvironment(): Continuous2DEnvironment { - val incarnation = BiochemistryIncarnation() - val environment = Continuous2DEnvironment(incarnation) - environment.linkingRule = NoLinks() - environment.addTerminator { it.simulation.time > DoubleTime(10.0) } - return environment - } - - fun runAndTrace( - setup: (Continuous2DEnvironment) -> Unit, - engineFactory: (Environment) -> Simulation, - ): List { - val environment = prepareEnvironment() - setup(environment) - val monitor = TraceMonitor>() - - @Suppress("UNCHECKED_CAST") - val engine = engineFactory(environment as Environment) as Simulation> - engine.addOutputMonitor(monitor) - - engine.play() - engine.run() - - engine.error.getOrNull()?.let { err -> - fail { "Simulation failed with an error: ${err.message}: ${err.stackTraceToString()}" } - } - - return monitor.trace - } - - val a = Biomolecule("A") - val b = Biomolecule("B") - val c = Biomolecule("C") - - fun setupNode(env: Continuous2DEnvironment): GenericNode { - val node = GenericNode(env) - node.setConcentration(a, 1.0) - node.setConcentration(b, 0.0) - node.setConcentration(c, 0.0) - return node - } - - fun addReaction(node: Node, rate: Double, conditionMolecule: Molecule, action: () -> Unit) { - DependencyUtils.SimpleReaction(node, DiracComb(rate)) { - action() - }.apply { - conditions = listOf(MoleculeHasConcentration(node, conditionMolecule, 1.0)) - node.addReaction(this) - } - } - - "Compare Engine and ReactiveEngine" - { - listOf( - "Simple Chain: A -> B -> C" to { env: Continuous2DEnvironment -> - val node = setupNode(env) - addReaction(node, 1.0, a) { - node.setConcentration(a, 0.0) - node.setConcentration(b, 1.0) - } - addReaction(node, 1.0, b) { - node.setConcentration(b, 0.0) - node.setConcentration(c, 1.0) - } - env.addNode(node, env.makePosition(0, 0)) - Unit - }, - "Simple Branching: A -> B, A -> C" to { env: Continuous2DEnvironment -> - val node = setupNode(env) - addReaction(node, 1.0, a) { - node.setConcentration(b, node.getConcentration(b) + 1.0) - } - addReaction(node, 0.5, a) { - node.setConcentration(c, node.getConcentration(c) + 1.0) - } - env.addNode(node, env.makePosition(0, 0)) - Unit - }, - "Simple Feedback Loop: A -> B -> A" to { env: Continuous2DEnvironment -> - val node = setupNode(env) - addReaction(node, 1.0, a) { - node.setConcentration(a, 0.0) - node.setConcentration(b, 1.0) - } - addReaction(node, 1.0, b) { - node.setConcentration(b, 0.0) - node.setConcentration(a, 1.0) - } - env.addNode(node, env.makePosition(0, 0)) - Unit - }, - ).forEach { (_, setup) -> - val traceEngine = runAndTrace(setup) { Engine(it) } - val traceReactive = runAndTrace(setup) { ReactiveEngine(it) } - traceReactive shouldContainExactly traceEngine - } - } -}) diff --git a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestReactiveEngineImplementation.kt b/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestReactiveEngineImplementation.kt deleted file mode 100644 index 95735ed7a5..0000000000 --- a/alchemist-engine/src/test/kotlin/it/unibo/alchemist/core/TestReactiveEngineImplementation.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright (C) 2010-2026, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.core - -class TestReactiveEngineImplementation : AbstractEngineTest({ env -> ReactiveEngine(env) }) diff --git a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.kt b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.kt index d9f41a91cd..cb079c2bcc 100644 --- a/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.kt +++ b/alchemist-loading/src/main/kotlin/it/unibo/alchemist/boundary/loader/LoadingSystem.kt @@ -15,7 +15,6 @@ import it.unibo.alchemist.boundary.exporters.GlobalExporter import it.unibo.alchemist.boundary.loader.LoadingSystemLogger.logger import it.unibo.alchemist.boundary.loader.syntax.DocumentRoot import it.unibo.alchemist.core.Engine -import it.unibo.alchemist.core.ReactiveEngine import it.unibo.alchemist.core.Simulation import it.unibo.alchemist.model.Deployment import it.unibo.alchemist.model.Environment @@ -150,12 +149,7 @@ internal abstract class LoadingSystem(private val originalContext: Context, priv SimulationModel .visitBuilding>(context, engineDescriptor) ?.getOrThrow() - ?: if (System.getProperty("alchemist.engine", "default") == "reactive") { - logger.warn("Using experimental reactive APIs") - ReactiveEngine(environment) - } else { - Engine(environment) - } + ?: Engine(environment) // Attach monitors monitors.forEach(engine::addOutputMonitor) // Attach data exporters diff --git a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestReactiveEngineLoading.kt b/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestReactiveEngineLoading.kt deleted file mode 100644 index 5e405d4672..0000000000 --- a/alchemist-loading/src/test/kotlin/it/unibo/alchemist/test/TestReactiveEngineLoading.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2010-2023, Danilo Pianini and contributors - * listed, for each module, in the respective subproject's build.gradle.kts file. - * - * This file is part of Alchemist, and is distributed under the terms of the - * GNU General Public License, with a linking exception, - * as described in the file LICENSE in the Alchemist distribution's top directory. - */ - -package it.unibo.alchemist.test - -import io.kotest.core.spec.style.StringSpec -import io.kotest.matchers.shouldBe -import io.kotest.matchers.types.shouldBeInstanceOf -import it.unibo.alchemist.boundary.LoadAlchemist -import it.unibo.alchemist.core.ReactiveEngine -import it.unibo.alchemist.model.positions.Euclidean2DPosition -import org.kaikikm.threadresloader.ResourceLoader - -class TestReactiveEngineLoading : StringSpec({ - - "It should be possible to load a simulation with the ReactiveEngine via YAML overrides" { - val yaml = "synthetic/scalavar.yml" - val overrides = listOf( - """ - engine: - type: it.unibo.alchemist.core.ReactiveEngine - """.trimIndent(), - ) - - val resource = ResourceLoader.getResource(yaml) - val loader = LoadAlchemist.from(resource, overrides) - val simulation = loader.getDefault() - - simulation.shouldBeInstanceOf>() - - simulation.play() - simulation.run() - simulation.error.isPresent shouldBe false - } -}) diff --git a/run_all_tests_reactive.sh b/run_all_tests_reactive.sh deleted file mode 100755 index 86b2207a9e..0000000000 --- a/run_all_tests_reactive.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -./gradlew -Dalchemist.engine=reactive test