pntos-api 2.1.0.post0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,306 @@
1
+ """Python API of pntOS."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, TypeVar
5
+
6
+ from numpy import float64
7
+ from numpy.typing import NDArray
8
+
9
+ from pntos.api import CommonPlugin
10
+
11
+ from .state_modeling import (
12
+ StandardDynamicsModel,
13
+ StandardMeasurementModel,
14
+ )
15
+
16
+
17
+ class StandardFusionStrategy(ABC):
18
+ """
19
+ A Fusion strategy making linearized Bayesian assumptions.
20
+
21
+ An implementation of the standard fusion strategy that is capable of
22
+ Bayesian inference on a linearized discrete-time system with Gaussian noise
23
+ inputs (e.g. an EKF).
24
+
25
+ At a fundamental level, this class manages an estimate of a state space as
26
+ it propagates (changes over time) and updates (incorporates new measurements
27
+ and observations). An estimate of a set of values (states) is stored and
28
+ maintained within this object. The estimate is then mutated and updated over
29
+ time by the various method calls.
30
+
31
+ A typical usage pattern of this class is as follows:
32
+
33
+ 1. The user adds a set of states by calling the :meth:`add_states` method. As part
34
+ of this step, the user all passes in initial conditions for the estimate.
35
+ The fusion strategy is now storing an estimate of the states, set to the
36
+ initial conditions.
37
+ 2. The user propagates this estimate forward in time by calling the
38
+ :meth:`propagate` method. For example, if the initial conditions were the
39
+ estimates of the states at time 0.0s, but we now want to know the
40
+ estimate of the values at time 5.0s, the user would call :meth:`propagate` with
41
+ a dynamics model parameter that specifies how to take an estimate at time
42
+ 0.0 and use it to compute the estimate at time 5.0.
43
+ 3. The user updates this estimate by using observations of the states at the
44
+ current time. For example, if the filter is currently propagated to time
45
+ 5.0s and a measurement is received that observes the states' values at
46
+ time 5.0s, the user would call :meth:`update` with a measurement model
47
+ parameter that describes how to take the current estimate at 5.0s and
48
+ incorporate the new information from the measurement.
49
+ 4. At any point, when the user wants to know the latest estimate given all
50
+ the propagate/updates that have occurred, they may call :attr:`estimate`.
51
+
52
+ Note:
53
+ This class must have an operational ``__deepcopy__`` method. For most classes, the default
54
+ ``__deepcopy__`` method provided by Python will be sufficient. However, if the class has a
55
+ field which does not properly implement its own ``__deepcopy__`` (such as a C object wrapped
56
+ to Python), then the class will need to implement a custom ``__deepcopy__`` which properly
57
+ copies all fields.
58
+ """
59
+
60
+ @property
61
+ @abstractmethod
62
+ def num_states(self) -> int:
63
+ """
64
+ Get the total number of states this filter is estimating.
65
+
66
+ The count will initially be zero, until :meth:`add_states` is called.
67
+
68
+ Returns:
69
+ int: Number of states being estimated in this filter.
70
+ """
71
+ ...
72
+
73
+ @abstractmethod
74
+ def add_states(
75
+ self,
76
+ initial_estimate: NDArray[float64],
77
+ initial_covariance: NDArray[float64],
78
+ cross_covariance: NDArray[float64] | None = None,
79
+ ) -> int:
80
+ """
81
+ Add new states to this filter.
82
+
83
+ Increases number of filter states and set the initial conditions of the new states. Returns
84
+ index of the first added state. If ``cross_covariance`` is ``None``, cross covariance between
85
+ the existing states and the added states will be set to zeroes.
86
+
87
+ Args:
88
+ initial_estimate (NDArray[float64]): The initial estimate to populate the new states with.
89
+ initial_covariance (NDArray[float64]): The initial covariance matrix used to initialize the
90
+ uncertainty of the new states.
91
+ cross_covariance (NDArray[float64] | None): A covariance matrix that describes the cross terms
92
+ between the previous states and the new states. If ``None``, the
93
+ cross-terms will be set to zero. Otherwise, if there are ``n`` existing states and
94
+ ``m`` new states being added, this argument should have shape ``[n, m]``.
95
+
96
+ Returns:
97
+ int: Index of first state being added to this filter (zero indexed).
98
+ """
99
+ ...
100
+
101
+ @abstractmethod
102
+ def remove_states(self, first_index: int, count: int) -> None:
103
+ """
104
+ Removes a set of states from the filter.
105
+
106
+ Args:
107
+ first_index (int): Index of the first state to be removed (zero indexed).
108
+ count (int): The number of states to be removed.
109
+ """
110
+ ...
111
+
112
+ @property
113
+ @abstractmethod
114
+ def estimate(self) -> NDArray[float64] | None:
115
+ """
116
+ Get the current internal estimate managed by this strategy.
117
+
118
+ This class manages a current estimate that is initially populated by :meth:`add_states` and
119
+ then is modified iteratively by :meth:`propagate`, :meth:`update`, and other method calls.
120
+ This method returns the current estimate, incorporating all changes made by previous method
121
+ calls to this strategy.
122
+
123
+ Returns:
124
+ NDArray[float64] | None: An estimate if available. Returns ``None`` if no states have been added
125
+ yet.
126
+ """
127
+
128
+ @abstractmethod
129
+ def set_estimate_slice(
130
+ self, new_estimate: NDArray[float64], first_index: int
131
+ ) -> None:
132
+ """
133
+ Set a slice of the state estimates to a given set of values.
134
+
135
+ This class manages a current estimate that is initially populated by :meth:`add_states` and
136
+ then is modified iteratively by :meth:`propagate`, :meth:`update`, and other method calls.
137
+ This method allows for manually overriding the current estimate. Sets a block of states to
138
+ new values, starting with ``first_index`` and overwriting a number of states equal to the
139
+ length of ``new_estimate``.
140
+
141
+ Args:
142
+ new_estimate (NDArray[float64]): The new estimate values that will overwrite the previous values.
143
+ first_index (int): The index of the first state to overwrite.
144
+ """
145
+ ...
146
+
147
+ @property
148
+ @abstractmethod
149
+ def covariance(self) -> NDArray[float64] | None:
150
+ """
151
+ Get the covariance of the current estimate.
152
+
153
+ This class manages a current estimate that is initially populated by :meth:`add_states` and
154
+ then is modified iteratively by :meth:`propagate`, :meth:`update`, and other method calls.
155
+ In addition to the estimate itself, a covariance of the current estimate is computed. This
156
+ method returns this covariance, incorporating all changes made by previous method calls to
157
+ this strategy.
158
+
159
+ Returns:
160
+ NDArray[float64] | None: The covariance of the current estimate. Returns ``None`` if no states
161
+ have been added yet.
162
+ """
163
+
164
+ @abstractmethod
165
+ def set_covariance_slice(
166
+ self,
167
+ new_covariance: NDArray[float64],
168
+ first_row: int,
169
+ first_col: int | None = None,
170
+ ) -> None:
171
+ """
172
+ Set a slice of the covariance matrix to a given set of values.
173
+
174
+ This class manages a current estimate that is initially populated by :meth:`add_states` and
175
+ then is modified iteratively by :meth:`propagate`, :meth:`update`, and other method calls.
176
+ In addition to the estimate itself, a covariance of the current estimate is computed.
177
+
178
+ Allows for manually overriding the current covariance matrix. Sets a block of the covariance
179
+ matrix to new values. The overwritten values are those in a rectangular area defined by the
180
+ upper left corner at ``first_row``, ``first_col`` and extending down and right to cover an
181
+ area equal to the size of ``new_covariance``. If ``first_col`` is not set or set to ``None``, the value of
182
+ ``first_row`` will be used as a column index as well.
183
+
184
+ Args:
185
+ new_covariance (NDArray[float64]): The new covariance values that will overwrite a slice of the
186
+ previous covariance matrix.
187
+ first_row (int): The row of the first value to overwrite.
188
+ first_col (int | None, optional): The column of the first value to overwrite.
189
+ """
190
+ ...
191
+
192
+ @abstractmethod
193
+ def propagate(self, dynamics_model: StandardDynamicsModel) -> None:
194
+ """
195
+ Propagates the estimate of the state space forward in time.
196
+
197
+ This method assumes that a state space is already initialized with a set
198
+ of states via the :meth:`add_states` method. The ``dynamics_model`` parameter
199
+ includes a description of how the current state estimate can be
200
+ propagated from the current time to a new time. Note that the actual
201
+ numerical values of the current/new times are not specified anywhere, as
202
+ that information is not needed to perform the computation. This method
203
+ then takes the current state space estimate and the ``dynamics_model`` and
204
+ uses both to compute an estimate at the new time. The new estimate
205
+ clobbers the old estimate managed by this class, and be acquired by
206
+ calling :attr:`estimate`.
207
+
208
+ Args:
209
+ dynamics_model (StandardDynamicsModel)
210
+ """
211
+ ...
212
+
213
+ @abstractmethod
214
+ def update(self, measurement_model: StandardMeasurementModel) -> None:
215
+ """
216
+ Updates the estimate of the state space, incorporating a new measurement.
217
+
218
+ This method assumes that a state space is already initialized with a set of states via the
219
+ :meth:`add_states` method. The ``measurement_model`` parameter includes both the measurement
220
+ itself and a description of how the measurement relates to the state space. This method then
221
+ takes the current state space estimate and updates it using information from the
222
+ ``measurement_model``. The updated estimate can be acquired by calling :attr:`estimate`.
223
+
224
+ Args:
225
+ measurement_model (StandardMeasurementModel): The measurement with which to update the
226
+ filter, as well as a model that describes how the measurement relates to the states
227
+ this strategy is estimating.
228
+ """
229
+
230
+
231
+ FusionStrategyType = TypeVar('FusionStrategyType', StandardFusionStrategy, Any)
232
+ """Enumerates the types of fusion strategies.
233
+
234
+ Currently only StandardFusionStrategy is defined, but this TypeVar also includes "Any" in the type
235
+ list for future compatibility.
236
+ """
237
+
238
+
239
+ class FusionStrategyPlugin(CommonPlugin, ABC):
240
+ """
241
+ A plugin that provides computational engines for estimation.
242
+
243
+ At the high level, a fusion strategy is a computation engine that knows how
244
+ to estimating one or more states, given a set of observations/measurements.
245
+ For example, the EKF equations are an implementation of a fusion strategy.
246
+ This plugin is a factory that produces fusion strategies on demand, which is
247
+ useful for multi-filter approaches where the system may need several fusion
248
+ strategies running simultaneously.
249
+
250
+ There are many ways to model sensor fusion, including very simple (linear
251
+ Kalman filter) and complex (neural networks, factor graphs, etc.). This
252
+ plugin aims to allow any and all of those models to be represented. It
253
+ achieves this by having multiple fusion strategies, so that the user may select
254
+ what kind of fusion they want. Currently, only the :class:`pntos.api.StandardFusionStrategy` is
255
+ implemented, which is suitable for EKFs and filters that have similar
256
+ interfaces to an EKF (such as a UKF). However, more strategy types are
257
+ planned to be added in the future.
258
+ """
259
+
260
+ @abstractmethod
261
+ def is_fusion_type_supported(self, fusion_type: type[FusionStrategyType]) -> bool:
262
+ """
263
+ Check if a particular fusion strategy is supported by :meth:`new_fusion_strategy`.
264
+
265
+ The :meth:`new_fusion_strategy` factory method on this class can create one or more types of
266
+ fusion strategy. However, :class:`pntos.api.FusionStrategyPlugin` may not support all types
267
+ (they must support at least one). Therefore, when a user receives a
268
+ :class:`pntos.api.FusionStrategyPlugin`, they should:
269
+
270
+ 1. Initialize the plugin by calling :meth:`pntos.api.CommonPlugin.init_plugin` (see
271
+ :class:`pntos.api.CommonPlugin` for more information).
272
+ 2. Call :meth:`is_fusion_type_supported` to check that the plugin supports the type that the
273
+ user wants to use.
274
+ 3. If :meth:`is_fusion_type_supported` returned True, call the :meth:`new_fusion_strategy`
275
+ factory method to get a new fusion strategy of the desired fusion strategy.
276
+ 4. Proceed to use the fusion strategy to do estimation.
277
+
278
+ Args:
279
+ fusion_type (type[FusionStrategyType]): The fusion strategy type we are checking.
280
+
281
+ Returns:
282
+ bool: Whether or not we support the requested ``fusion_type``.
283
+ """
284
+ ...
285
+
286
+ @abstractmethod
287
+ def new_fusion_strategy(
288
+ self, fusion_type: type[FusionStrategyType]
289
+ ) -> FusionStrategyType | None:
290
+ """
291
+ Create a new fusion strategy of the requested type.
292
+
293
+ Users must first ensure that the fusion strategy is supported by calling
294
+ :meth:`is_fusion_type_supported`.
295
+
296
+ Args:
297
+ fusion_type (type[FusionStrategyType]): The type of fusion strategy that we want
298
+ returned.
299
+
300
+ Returns:
301
+ FusionStrategyType | None: The newly created fusion strategy, which is an
302
+ implementation of the type specified by ``fusion_type``. For example, if the user calls
303
+ :meth:`new_fusion_strategy` with a ``fusion_type`` of ``StandardFusionStrategy``, then
304
+ the returned object will be an implementation of :class:`pntos.api.StandardFusionStrategy`.
305
+ """
306
+ ...