Skip to content

Commit

Permalink
feat(api)!: add capabilities to nodes (#1073)
Browse files Browse the repository at this point in the history
Nodes can now get decorated with a set of `NodeProperty`. As such, it is no longer needed to subclass `Node`, and it is indeed considered a bad practice. This modification is not backwards compatible.

Co-authored-by: Danilo Pianini <[email protected]>
  • Loading branch information
kelvin-olaiya and DanySK authored Mar 10, 2022
1 parent 422b2ce commit c0150e1
Show file tree
Hide file tree
Showing 301 changed files with 5,020 additions and 4,678 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ interface CognitiveModel {
fun update(frequency: Double)

/**
* Whether or not this pedestrian intends to escape.
* Whether or not this node intends to escape.
*/
fun wantsToEscape(): Boolean = escapeIntention() > remainIntention()
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package it.unibo.alchemist.model.implementations.actions

import it.unibo.alchemist.model.interfaces.Environment
import it.unibo.alchemist.model.interfaces.GroupSteeringAction
import it.unibo.alchemist.model.interfaces.Pedestrian
import it.unibo.alchemist.model.interfaces.Node
import it.unibo.alchemist.model.interfaces.Position
import it.unibo.alchemist.model.interfaces.Reaction
import it.unibo.alchemist.model.interfaces.geometry.GeometricTransformation
Expand All @@ -21,21 +21,19 @@ import it.unibo.alchemist.model.interfaces.geometry.Vector
* An abstract [GroupSteeringAction].
*/
abstract class AbstractGroupSteeringAction<T, P, A>(
/**
* The environment in which this action executes.
*/
protected open val env: Environment<T, P>,
environment: Environment<T, P>,
reaction: Reaction<T>,
pedestrian: Pedestrian<T, P, A>
) : AbstractSteeringAction<T, P, A>(env, reaction, pedestrian),
node: Node<T>,
) : AbstractSteeringAction<T, P, A>(environment, reaction, node),
GroupSteeringAction<T, P>
where P : Position<P>, P : Vector<P>,
where P : Position<P>,
P : Vector<P>,
A : GeometricTransformation<P> {

/**
* Computes the centroid of the [group] in absolute coordinates.
*/
protected fun centroid(): P = with(group()) {
map { env.getPosition(it) }.reduce { acc, pos -> acc + pos } / size.toDouble()
map { environment.getPosition(it) }.reduce { acc, pos -> acc + pos } / size.toDouble()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import it.unibo.alchemist.model.implementations.layers.BidimensionalGaussianLaye
import it.unibo.alchemist.model.implementations.positions.Euclidean2DPosition
import it.unibo.alchemist.model.interfaces.Layer
import it.unibo.alchemist.model.interfaces.Molecule
import it.unibo.alchemist.model.interfaces.Pedestrian
import it.unibo.alchemist.model.interfaces.Pedestrian2D
import it.unibo.alchemist.model.interfaces.Node
import it.unibo.alchemist.model.interfaces.Position
import it.unibo.alchemist.model.interfaces.Reaction
import it.unibo.alchemist.model.interfaces.environments.Euclidean2DEnvironment
Expand All @@ -15,27 +14,22 @@ import it.unibo.alchemist.model.interfaces.geometry.euclidean2d.Euclidean2DTrans
* Abstract implementation of an action influenced by the concentration of a given molecule in the environment.
*
* @param environment
* the environment inside which the pedestrian moves.
* the environment inside which the node moves.
* @param reaction
* the reaction which executes this action.
* @param pedestrian
* @param node
* the owner of this action.
* @param targetMolecule
* the {@link Molecule} you want to know the concentration in the different positions of the environment.
*/
abstract class AbstractLayerAction(
protected val environment: Euclidean2DEnvironment<Number>,
reaction: Reaction<Number>,
override val pedestrian: Pedestrian2D<Number>,
protected val targetMolecule: Molecule
) : AbstractSteeringAction<Number, Euclidean2DPosition, Euclidean2DTransformation>(environment, reaction, pedestrian) {
node: Node<Number>,
protected val targetMolecule: Molecule,
) : AbstractSteeringAction<Number, Euclidean2DPosition, Euclidean2DTransformation>(environment, reaction, node) {

override fun cloneAction(
n: Pedestrian<Number, Euclidean2DPosition, Euclidean2DTransformation>,
r: Reaction<Number>
) = requireNodeTypeAndProduce<Pedestrian2D<Number>, AbstractLayerAction>(n) { cloneAction(it, r) }

protected abstract fun cloneAction(n: Pedestrian2D<Number>, r: Reaction<Number>): AbstractLayerAction
abstract override fun cloneAction(node: Node<Number>, reaction: Reaction<Number>): AbstractLayerAction

/**
* @returns the layer containing [targetMolecule] or fails.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ package it.unibo.alchemist.model.implementations.actions

import it.unibo.alchemist.model.interfaces.NavigationAction
import it.unibo.alchemist.model.interfaces.NavigationStrategy
import it.unibo.alchemist.model.interfaces.OrientingPedestrian
import it.unibo.alchemist.model.interfaces.Position
import it.unibo.alchemist.model.interfaces.Reaction
import it.unibo.alchemist.model.interfaces.environments.EnvironmentWithGraph
Expand All @@ -26,75 +25,88 @@ import it.unibo.alchemist.model.implementations.actions.AbstractNavigationAction
import it.unibo.alchemist.model.implementations.actions.AbstractNavigationAction.NavigationState.CROSSING_DOOR
import it.unibo.alchemist.model.implementations.actions.AbstractNavigationAction.NavigationState.MOVING_TO_FINAL
import it.unibo.alchemist.model.implementations.actions.AbstractNavigationAction.NavigationState.ARRIVED
import it.unibo.alchemist.model.interfaces.Node
import it.unibo.alchemist.model.interfaces.properties.OccupiesSpaceProperty
import it.unibo.alchemist.model.interfaces.Node.Companion.asProperty
import it.unibo.alchemist.model.interfaces.properties.OrientingProperty

/**
* An abstract [NavigationAction], taking care of properly moving the pedestrian in the
* An abstract [NavigationAction], taking care of properly moving the node in the
* environment while delegating the decision on where to move it to a [NavigationStrategy].
*
* @param T the concentration type.
* @param P the [Position] type and [Vector] type for the space the pedestrian is into.
* @param P the [Position] type and [Vector] type for the space the node is into.
* @param A the transformations supported by the shapes in this space.
* @param L the type of landmarks of the pedestrian's cognitive map.
* @param R the type of edges of the pedestrian's cognitive map, representing the [R]elations between landmarks.
* @param L the type of landmarks of the node's cognitive map.
* @param R the type of edges of the node's cognitive map, representing the [R]elations between landmarks.
* @param N the type of nodes of the navigation graph provided by the [environment].
* @param E the type of edges of the navigation graph provided by the [environment].
*/
abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
override val environment: EnvironmentWithGraph<*, T, P, A, N, E>,
override val reaction: Reaction<T>,
final override val pedestrian: OrientingPedestrian<T, P, A, L, R>
) : AbstractSteeringAction<T, P, A>(environment, reaction, pedestrian),
node: Node<T>,
) : AbstractSteeringAction<T, P, A>(environment, reaction, node),
NavigationAction<T, P, A, L, R, N, E>
where P : Position<P>, P : Vector<P>,
A : GeometricTransformation<P>,
L : ConvexGeometricShape<P, A>,
N : ConvexGeometricShape<P, A> {

override val navigatingNode = node

/**
* The strategy used to navigate the environment.
*/
protected open lateinit var strategy: NavigationStrategy<T, P, A, L, R, N, E>

/**
* The position of the [pedestrian] in the [environment], this is cached and updated
* The position of the [navigatingNode] in the [environment], this is cached and updated
* every time [update] is called so as to avoid potentially costly re-computations.
*/
override lateinit var pedestrianPosition: P

/**
* The room (= environment's area) the [pedestrian] is into, this is cached and updated
* The room (= environment's area) the [navigatingNode] is into, this is cached and updated
* every time [update] is called so as to avoid potentially costly re-computations.
*/
override var currentRoom: N? = null

/**
* Minimum distance to consider a target reached. Using zero (even with fuzzy equals) may lead to some
* boundary cases in which the pedestrian remains blocked due to how the environment manage collisions
* at present. This workaround allows to specify a minimum distance which is dependent on the pedestrian
* boundary cases in which the node remains blocked due to how the environment manage collisions
* at present. This workaround allows to specify a minimum distance which is dependent on the node
* shape. In the future, something better could be done.
*/
protected val minDistance: Double = pedestrian.shape.diameter
protected val minDistance: Double = node.asProperty<T, OccupiesSpaceProperty<T, P, A>>().shape.diameter

/**
* @returns true if the distance to [pedestrianPosition] is smaller than or equal to [minDistance].
*/
protected open fun P.isReached(): Boolean = distanceTo(pedestrianPosition) <= minDistance

/**
* The navigation state.
*/
protected var state: NavigationState = START

/**
* Caches the room the pedestrian is into when he/she starts moving. When the pedestrian is crossing a door, it
* contains the room being left. When in [NavigationState.MOVING_TO_FINAL], it contains the room the pedestrian
* was (and should be) into. It's used to detect if the pedestrian ended up in an unexpected room while moving.
* Caches the room the node is into when he/she starts moving. When the node is crossing a door, it
* contains the room being left. When in [NavigationState.MOVING_TO_FINAL], it contains the room the node
* was (and should be) into. It's used to detect if the node ended up in an unexpected room while moving.
*/
protected var previousRoom: N? = null

/**
* Defined when crossing a door. See [crossDoor].
*/
protected var crossingPoints: Pair<P, P>? = null

/**
* Defined when crossing a door.
*/
protected var expectedNewRoom: N? = null

/**
* Defined in [NavigationState.MOVING_TO_FINAL].
*/
Expand All @@ -109,16 +121,16 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(

/**
* Updates [pedestrianPosition] and [currentRoom], this can be costly.
* Depending on how [ConvexGeometricShape.contains] manage points on the boundary, the pedestrian could
* Depending on how [ConvexGeometricShape.contains] manage points on the boundary, the node could
* be inside two (adjacent) rooms at once. This can happen in two cases:
* - when in [NavigationState.MOVING_TO_CROSSING_POINT_1] or [NavigationState.MOVING_TO_FINAL] and the pedestrian
* - when in [NavigationState.MOVING_TO_CROSSING_POINT_1] or [NavigationState.MOVING_TO_FINAL] and the node
* is moving on [previousRoom]'s boundary. In such case [previousRoom] is used.
* - when crossing a door or in [NavigationState.NEW_ROOM] and [expectedNewRoom] is adjacent to [previousRoom].
* In such case [expectedNewRoom] is used.
* Otherwise the first room containing [pedestrianPosition] is used.
*/
protected open fun updateCachedVariables() {
pedestrianPosition = environment.getPosition(pedestrian)
pedestrianPosition = environment.getPosition(navigatingNode)
currentRoom = when {
(state == MOVING_TO_CROSSING_POINT_1 || state == MOVING_TO_FINAL) &&
previousRoom.orFail().contains(pedestrianPosition) ->
Expand All @@ -131,11 +143,14 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
}
}

/**
* Execute on navigation start.
*/
protected open fun onStart() {
state = when {
currentRoom != null -> NEW_ROOM
/*
* If the pedestrian cannot locate itself inside any room on start, it simply won't move.
* If the node cannot locate itself inside any room on start, it simply won't move.
*/
else -> ARRIVED
}
Expand All @@ -153,7 +168,7 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
protected open val E.target: N get() = environment.graph.getEdgeTarget(this)

/**
* Moves the pedestrian across the provided [door], which must be among [doorsInSight].
* Moves the node across the provided [door], which must be among [doorsInSight].
* Since connected rooms may be non-adjacent, a pair of [crossingPoints] has to be provided:
* - the first point must belong to the current room's boundary and will be reached first,
* - the second point must belong to the next room's boundary and will be pursued after
Expand Down Expand Up @@ -198,7 +213,7 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
}

/**
* The position the pedestrian wants to reach.
* The position the node wants to reach.
*/
val desiredPosition: P get() = when (state) {
MOVING_TO_CROSSING_POINT_1 -> crossingPoints.orFail().first
Expand All @@ -208,18 +223,19 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
/*
* Always up to date current position.
*/
else -> environment.getPosition(pedestrian)
else -> environment.getPosition(navigatingNode)
}

/**
* Updates the internal state but does not move the pedestrian.
* Updates the internal state but does not move the node.
*/
open fun update() {
updateCachedVariables()
when (state) {
START -> onStart()
NEW_ROOM -> currentRoom.orFail().let {
pedestrian.registerVisit(it)
navigatingNode.asProperty<T, OrientingProperty<T, P, A, L, N, E>>()
.registerVisit(it)
strategy.inNewRoom(it)
}
in MOVING_TO_CROSSING_POINT_1..MOVING_TO_FINAL -> moving()
Expand All @@ -242,7 +258,7 @@ abstract class AbstractNavigationAction<T, P, A, L, R, N, E>(
*/
MOVING_TO_CROSSING_POINT_2,
/**
* When the second crossing point [isReached] (see [crossDoor]), the pedestrian may still be outside
* When the second crossing point [isReached] (see [crossDoor]), the node may still be outside
* any room. In such case it moves towards [expectedNewRoom] centroid until he/she enters a room.
*/
CROSSING_DOOR,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,47 +12,60 @@ package it.unibo.alchemist.model.implementations.actions
import it.unibo.alchemist.model.interfaces.Action
import it.unibo.alchemist.model.interfaces.Environment
import it.unibo.alchemist.model.interfaces.Node
import it.unibo.alchemist.model.interfaces.Pedestrian
import it.unibo.alchemist.model.interfaces.Position
import it.unibo.alchemist.model.interfaces.Reaction
import it.unibo.alchemist.model.interfaces.SteeringAction
import it.unibo.alchemist.model.interfaces.properties.PedestrianProperty
import it.unibo.alchemist.model.interfaces.geometry.GeometricTransformation
import it.unibo.alchemist.model.interfaces.geometry.Vector
import it.unibo.alchemist.model.interfaces.Node.Companion.asProperty

/**
* A [SteeringAction] in a vector space. The implementation of [nextPosition] is left to subclasses.
*/
abstract class AbstractSteeringAction<T, P, A>(
env: Environment<T, P>,
environment: Environment<T, P>,
/**
* The reaction in which this action is executed.
*/
protected open val reaction: Reaction<T>,
/**
* The owner of this action.
*/
protected open val pedestrian: Pedestrian<T, P, A>
) : AbstractMoveNode<T, P>(env, pedestrian),
node: Node<T>,
) : AbstractMoveNode<T, P>(environment, node),
SteeringAction<T, P>
where P : Position<P>, P : Vector<P>,
where P : Position<P>,
P : Vector<P>,
A : GeometricTransformation<P> {

/**
* The maximum distance the pedestrian can walk, this is a length.
* The maximum distance the node can walk, this is a length.
*/
open val maxWalk: Double get() = pedestrian.speed() / reaction.rate
open val maxWalk: Double get() = node.asProperty<T, PedestrianProperty<T>>().speed() / reaction.rate

/**
* @return The next position where to move, in absolute or relative coordinates depending on the
* value of isAbsolute.
*/
override fun getNextPosition(): P = nextPosition()

override fun getNode(): Pedestrian<T, P, A> = pedestrian

override fun cloneAction(node: Node<T>, reaction: Reaction<T>): Action<T> =
requireNodeTypeAndProduce<Pedestrian<T, P, A>, AbstractSteeringAction<T, P, A>>(node) {
cloneAction(it, reaction)
}

protected abstract fun cloneAction(n: Pedestrian<T, P, A>, r: Reaction<T>): AbstractSteeringAction<T, P, A>
/**
* 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.
*
* @param [node]
* The node where to clone this {@link Action}
* @param [reaction]
* The reaction to which the CURRENT action is assigned
* @return the cloned action
*/
abstract override fun cloneAction(node: Node<T>, reaction: Reaction<T>): AbstractSteeringAction<T, P, A>

/**
* Ensures that the passed [node] has type [N].
*/
protected inline fun <reified N : Node<*>, S : Action<*>> requireNodeTypeAndProduce(
node: Node<*>,
builder: (N) -> S
Expand Down
Loading

0 comments on commit c0150e1

Please sign in to comment.