Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to platform to allow it to control the positions of its sensors #177

Merged
merged 52 commits into from
Apr 27, 2020
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
02e1511
Update to platform to allow it to control the positions of its sensor…
erogers-dstl Mar 30, 2020
ed62b50
Multiple changes to address pull request comments:
erogers-dstl Apr 3, 2020
523df2d
Simplify h2d function using exiting rot[x/y/z] functions
erogers-dstl Apr 3, 2020
5e575e8
Use coerce_to_valid_mapping everywhere it is relevant
erogers-dstl Apr 3, 2020
24f5874
Remove multiple Sensor base classes and leave on the Sensor class, as…
erogers-dstl Apr 6, 2020
143bc38
Give sensors a default fixed platform if position and or/orientation …
erogers-dstl Apr 6, 2020
3795dec
Revert unnecessary platform creation in light of new default platform
erogers-dstl Apr 6, 2020
edc12c2
Move Sensor class to more logical location
erogers-dstl Apr 7, 2020
0250be0
Fix to test_3d_platform: some permutations of test were missing
erogers-dstl Apr 7, 2020
14f836a
Convert is_moving to property
erogers-dstl Apr 7, 2020
eff943c
Improve logic of is_moving, move() and bugfix in orientation of movin…
erogers-dstl Apr 7, 2020
c27d186
Add base platform movement and orientation tests
erogers-dstl Apr 7, 2020
fa41bc8
Add position setting of fixed platforms with tests
erogers-dstl Apr 7, 2020
a47fa31
Add test of setting orientation of fixed and moving platforms
erogers-dstl Apr 7, 2020
9de3ba5
Bugfix to off-by-one error on bounds check of mappings
erogers-dstl Apr 7, 2020
1b78bac
Add tests of defaults and error handling in sensor platform
erogers-dstl Apr 7, 2020
7e39377
Add tests of base sensor functionality
erogers-dstl Apr 7, 2020
6df31b3
Add position/orientation setting of sensors with default platforms an…
erogers-dstl Apr 7, 2020
364c8d8
Add 3d rotation offset tests
erogers-dstl Apr 8, 2020
f5401ae
Add 2d rotation offset tests
erogers-dstl Apr 8, 2020
de87870
Simplify sensor_position_test code
erogers-dstl Apr 8, 2020
81923fe
Remove mounting_mappings and tests. They were complicating the code a…
erogers-dstl Apr 14, 2020
82c5481
Rename Platform.mapping to position_mapping and add velocity_mapping
erogers-dstl Apr 14, 2020
d181647
Remove acceleration property from Platform
erogers-dstl Apr 14, 2020
6b83188
Add platform/base.py documentation
erogers-dstl Apr 14, 2020
b57649e
Add check that default creation of a fixed platform in a Sensor does …
erogers-dstl Apr 14, 2020
d2ff222
Add BaseSensor and Sensor documentation
erogers-dstl Apr 14, 2020
29565bb
Sensor platform docs
erogers-dstl Apr 14, 2020
edbe37c
Add test and docs for the case where a platform is asked about a miss…
erogers-dstl Apr 14, 2020
7c44e2e
Clean up imports
erogers-dstl Apr 14, 2020
2bdc4af
Add a couple of missing type hints as per Rich Green's comments
erogers-dstl Apr 15, 2020
f36f825
Updaet docs as per Rich Green's comments
erogers-dstl Apr 15, 2020
c2230f4
Doc changes to address Steve Hiscocks' comments
erogers-dstl Apr 15, 2020
7966603
Add tests of various mapping types in platforms:wq
erogers-dstl Apr 15, 2020
026d9ed
Remove coerce_to_valid_mapping function
erogers-dstl Apr 15, 2020
7543ee9
Change platform_system from Property to property.
erogers-dstl Apr 15, 2020
3f874fc
Change orientation and position properties of sensor to make use of t…
erogers-dstl Apr 17, 2020
0961480
Add more type hinting to sensor
erogers-dstl Apr 17, 2020
112d651
Merge branch 'master' into platform_extensions
erogers-dstl Apr 17, 2020
11730e7
Fixes after merge of 'master'
erogers-dstl Apr 17, 2020
4813a1e
Make base platform have sensors. Simplifies the inheritance considera…
erogers-dstl Apr 17, 2020
e7995b5
Allow setting position of moving platforms, provided their transition…
erogers-dstl Apr 17, 2020
2a67827
Fix tests for update of position-setting logic.
erogers-dstl Apr 17, 2020
e173ac3
Remove type hinting for variable: not compatible with Python 3.5
erogers-dstl Apr 20, 2020
927c6ec
Remove mutable defaults
erogers-dstl Apr 22, 2020
6831a3c
Reorder Properties of Platform to make required arguments first.
erogers-dstl Apr 22, 2020
29c9bd4
Handle case of missing platform in position/orientation getters
erogers-dstl Apr 22, 2020
18f431b
Add docs and waring on serialisation of Sensors.
erogers-dstl Apr 22, 2020
4323fe1
Add special case to serialisation code for platform with internal sen…
erogers-dstl Apr 22, 2020
51fd530
Fix reassignment of platform during tests of detection simulator
erogers-dstl Apr 24, 2020
80a7591
Rename test sensor classes to avoid pytest warnings
erogers-dstl Apr 24, 2020
aac2f65
Add additional tests of edge cases to check defaults, errors and warn…
erogers-dstl Apr 27, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 25 additions & 1 deletion stonesoup/functions.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
"""Mathematical functions used within Stone Soup"""
from typing import List, Union

import numpy as np
from copy import copy

from .types.numeric import Probability
from .types.array import Matrix
from .types.array import Matrix, StateVector


def tria(matrix):
Expand Down Expand Up @@ -566,3 +567,26 @@ def mod_elevation(x):
elif N == 3:
x = x - 2.0 * np.pi
return x


def coerce_to_valid_mapping(mapping: Union[List[int], np.ndarray, StateVector]):
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
"""Function to take a mapping and convert it to a suitable form for indexing a
:class:`StateVector`. Needed because if you index a :class:`StateVector` with another
:class:`StateVector` (which would be the obvious technique) you get a 3d :class:`ndarray`.
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

The input as either a List, :class:`ndarray` or a :class:`StateVector`, all of which should
contain integers. It returns a 1d :class:`ndarray` of with `shape == (ndim,)` where `ndim` is
the number of elements in the mapping array.

Parameters
----------
mapping: :class:`Union[List, np.ndarray]`
A mapping that needs to be formatted to a valid mapping shape

Returns
-------
mapping: :class:`np.ndarray`
A flattened np.ndarray suitable for indexing a :class:`StateVector`
"""

return np.asarray(mapping).flatten()
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
14 changes: 9 additions & 5 deletions stonesoup/models/measurement/base.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod
from abc import abstractmethod, ABC

import scipy as sp
import numpy as np

from stonesoup.functions import coerce_to_valid_mapping
from ..base import Model
from ...base import Property


class MeasurementModel(Model):
class MeasurementModel(Model, ABC):
"""Measurement Model base class"""

ndim_state = Property(int, doc="Number of state dimensions")
mapping = Property(
sp.ndarray, doc="Mapping between measurement and state dims")
mapping = Property(np.ndarray, doc="Mapping between measurement and state dims")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mapping = coerce_to_valid_mapping(self.mapping)
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

@property
def ndim(self):
Expand Down
15 changes: 6 additions & 9 deletions stonesoup/models/measurement/nonlinear.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*-
from abc import ABC
from typing import List
import copy

import numpy as np
Expand All @@ -15,8 +17,7 @@
from .base import MeasurementModel


class CombinedReversibleGaussianMeasurementModel(
ReversibleModel, GaussianModel, MeasurementModel):
class CombinedReversibleGaussianMeasurementModel(ReversibleModel, GaussianModel, MeasurementModel):
r"""Combine multiple models into a single model by stacking them.

The assumption is that all models are Gaussian, and must be combination of
Expand All @@ -29,8 +30,7 @@ class CombinedReversibleGaussianMeasurementModel(
:class:`~.LinearModel` or :class:`~.ReversibleModel`.
"""
mapping = None
model_list = Property(
[MeasurementModel], doc="List of Measurement Models.")
model_list = Property(List[MeasurementModel], doc="List of Measurement Models.")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
Expand Down Expand Up @@ -90,9 +90,7 @@ def rvs(self, num_samples=1, **kwargs):
return rvs_vectors.view(Matrix)


class NonLinearGaussianMeasurement(MeasurementModel,
NonLinearModel,
GaussianModel):
class NonLinearGaussianMeasurement(MeasurementModel, NonLinearModel, GaussianModel, ABC):
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
r"""This class combines the MeasurementModel, NonLinearModel and \
GaussianModel classes. It is not meant to be instantiated directly \
but subclasses should be derived from this class.
Expand Down Expand Up @@ -272,8 +270,7 @@ def rvs(self, num_samples=1, **kwargs):
return out


class CartesianToBearingRange(
NonLinearGaussianMeasurement, ReversibleModel):
class CartesianToBearingRange(NonLinearGaussianMeasurement, ReversibleModel):
r"""This is a class implementation of a time-invariant measurement model, \
where measurements are assumed to be received in the form of bearing \
(:math:`\phi`) and range (:math:`r`), with Gaussian noise in each dimension.
Expand Down
136 changes: 128 additions & 8 deletions stonesoup/platform/base.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,143 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod, ABC

from ..functions import cart2sphere, cart2pol, coerce_to_valid_mapping
from ..types.array import StateVector
from ..base import Base, Property
from ..types.state import State
from ..models.transition import TransitionModel
import numpy as np


class Platform(Base):
"""Platform base class
class Platform(Base, ABC):
state = Property(State, doc="The platform state at any given point")
mapping = Property(np.ndarray, doc="Mapping between platform position and state dims")
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.mapping = coerce_to_valid_mapping(self.mapping)

@property
def state_vector(self):
return self.state.state_vector

@property
def position(self):
return self.state_vector[self.mapping]

@position.setter
def position(self, value):
self._set_position(value)
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

@property
def ndim(self):
return len(self.mapping)

@property
@abstractmethod
def orientation(self):
raise NotImplementedError

@property
@abstractmethod
def velocity(self):
raise NotImplementedError

@property
@abstractmethod
def acceleration(self):
raise NotImplementedError

@abstractmethod
def is_moving(self):
raise NotImplementedError

@abstractmethod
def move(self, timestamp):
raise NotImplementedError

@abstractmethod
def _set_position(self, value):
raise NotImplementedError


class FixedPlatform(Platform):
sdhiscocks marked this conversation as resolved.
Show resolved Hide resolved
orientation = Property(StateVector, default=StateVector([0, 0, 0]),
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
doc='A fixed orientation of the static platform')

def _set_position(self, value):
self.state_vector[self.mapping] = value

@property
def velocity(self):
return StateVector([0] * self.ndim)

@property
def acceleration(self):
return StateVector([0] * self.ndim)

@property
def is_moving(self):
return False

def move(self, timestamp):
# Return without moving static platforms
self.state.timestamp = timestamp


class MovingPlatform(Platform):
"""Moving platform base class

A platform represents a random object defined as a :class:`~.State`
that moves according to a given :class:`~.TransitionModel`.
"""
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

state = Property(State, doc="The platform state at any given point")
transition_model = Property(
TransitionModel, doc="Transition model")

@property
def velocity(self):
# TODO docs
# TODO return zeros?
try:
return self.state_vector[self.mapping + 1]
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
except IndexError:
raise AttributeError('Velocity is not defined for this platform')

@property
def acceleration(self):
# TODO docs
# TODO return zeros?
try:
return self.state_vector[self.mapping + 2]
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
except IndexError:
raise AttributeError('Acceleration is not defined for this platform')

@property
def orientation(self):
# TODO docs
# TODO handle roll?
if not self.is_moving:
raise AttributeError('Orientation of a zero-velocity moving platform is not defined')
velocity = self.velocity

if self.ndim == 3:
_, bearing, elevation = cart2sphere(*velocity.flat)
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
return StateVector([0, bearing, elevation])
elif self.ndim == 2:
_, bearing = cart2pol(*velocity.flat)
return StateVector([0, bearing])
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

@property
def is_moving(self):
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
# TODO docs
# Note: a platform without a transition model can be given a velocity as part of it's
# StateVector. It just won't move
# This inconsistency will be handled in the move logic
return np.any(self.velocity != 0)

def _set_position(self, value):
raise AttributeError('Cannot set the position of a moving platform')
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved

def move(self, timestamp=None, **kwargs):
erogers-dstl marked this conversation as resolved.
Show resolved Hide resolved
"""Propagate the platform position using the :attr:`transition_model`.

Expand All @@ -40,12 +162,10 @@ def move(self, timestamp=None, **kwargs):
time_interval = timestamp - self.state.timestamp
except TypeError:
# TypeError: (timestamp or prior.timestamp) is None
time_interval = None
return

# Return without moving static platforms
if self.transition_model is None:
self.state.timestamp = timestamp
return self
raise AttributeError('Platform without a transition model cannot be moved')

self.state = State(
state_vector=self.transition_model.function(
Expand Down
Loading