Source code for stonesoup.movable.grid

from collections.abc import Sequence

import numpy as np

from ..base import Property
from ..types.state import State
from . import FixedMovable
from .action.move_position_action import NStepDirectionalGridActionGenerator


class _GridActionableMovable(FixedMovable):
    """Base class for movables which move in a grid like fashion. To be used with
    :class:`~.GridActionGenerator`."""

    generator = None
    _generator_kwargs = {'action_space', 'action_mapping', 'resolution'}

    action_space: np.ndarray = Property(
        default=None,
        doc="The bounds of the action space that should not be exceeded. Of shape (ndim, 2) "
            "where ndim is the length of the action_mapping. For example, "
            ":code:`np.array([[xmin, xmax], [ymin, ymax]])`."
    )

    action_mapping: Sequence[int] = Property(
        default=(0, 1),
        doc="The state dimensions that actions are applied to."
    )

    resolution: float = Property(
        default=1,
        doc="The size of each grid cell. Cells are assumed square."
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._next_action = None

    def actions(self, timestamp, start_timestamp=None):
        """Method to return a set of grid action generators available up to a provided timestamp.

        A generator is returned for each actionable property that the sensor has.

        Parameters
        ----------
        timestamp: datetime.datetime
            Time of action finish.
        start_timestamp: datetime.datetime, optional
            Time of action start.

        Returns
        -------
        : set of :class:`~.GridActionGenerator`
            Set of grid action generators, that describe the bounds of each action space.
        """
        generators = set()
        generators.add(self.generator(
            owner=self,
            attribute="position",
            start_time=start_timestamp,
            end_time=timestamp,
            **{name: getattr(self, name) for name in type(self)._generator_kwargs}))

        return generators

    def move(self, timestamp, *args, **kwargs):
        current_time = self.states[-1].timestamp
        new_state = State.from_state(self.state, timestamp=timestamp)
        new_state.state_vector = new_state.state_vector.copy()
        self.states.append(new_state)
        action = self._next_action
        if action is not None:
            self.position = action.act(current_time, timestamp, self.position)
        self._next_action = None

    def add_actions(self, actions):
        self._next_action = actions[0]
        return True

    def act(self, timestamp, *args, **kwargs):
        self.move(timestamp, *args, **kwargs)


[docs] class NStepDirectionalGridMovable(_GridActionableMovable): """This is a movable that enables movement in a grid like fashion according to a number of steps and step sizes. Actions are applied symmetrically on the action space allowing for movement in both positive and negative directions of each axis. This movable implements the :class:`~.NStepDirectionalGridActionGenerator`""" generator = NStepDirectionalGridActionGenerator _generator_kwargs = _GridActionableMovable._generator_kwargs | {'n_steps', 'step_size'} n_steps: int = Property( default=1, doc="The number of steps that can be moved in either direction " "along specified dimensions" ) step_size: int = Property( default=1, doc="The number of grid cells per step" )