mpc-control 0.1.0__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.
- mpc_control/__init__.py +21 -0
- mpc_control/discrete.py +632 -0
- mpc_control/kalman.py +364 -0
- mpc_control/mpc.py +781 -0
- mpc_control/plant.py +141 -0
- mpc_control/rls.py +159 -0
- mpc_control-0.1.0.dist-info/METADATA +212 -0
- mpc_control-0.1.0.dist-info/RECORD +11 -0
- mpc_control-0.1.0.dist-info/WHEEL +5 -0
- mpc_control-0.1.0.dist-info/licenses/LICENSE +21 -0
- mpc_control-0.1.0.dist-info/top_level.txt +1 -0
mpc_control/__init__.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from .plant import Plant, LoggedPlant
|
|
2
|
+
from .kalman import Ekf, Ukf
|
|
3
|
+
from .rls import Rls
|
|
4
|
+
from .discrete import LtiSystem, AtiSystem, HomogeneousSystem, NonlinearSystem
|
|
5
|
+
from .mpc import Mpc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _get_version() -> str:
|
|
9
|
+
"""Try to get the installed package version.
|
|
10
|
+
|
|
11
|
+
If the package is not installed (e.g., running from source in
|
|
12
|
+
development mode), fall back to "dev".
|
|
13
|
+
"""
|
|
14
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
15
|
+
try:
|
|
16
|
+
return version("mpc-control")
|
|
17
|
+
except PackageNotFoundError:
|
|
18
|
+
return "dev"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
__version__ = _get_version()
|
mpc_control/discrete.py
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""Discrete-time system models for Model Predictive Control.
|
|
4
|
+
|
|
5
|
+
This module provides abstract base classes and concrete
|
|
6
|
+
implementations for discrete-time systems. These systems define the
|
|
7
|
+
state transition, control input, and output relationships used by the
|
|
8
|
+
MPC controller.
|
|
9
|
+
|
|
10
|
+
Classes:
|
|
11
|
+
DiscreteSystem: abstract base class for discrete-time systems.
|
|
12
|
+
LtiSystem: discrete linear time-invariant system.
|
|
13
|
+
NonlinearSystem: generalized discrete non-linear system.
|
|
14
|
+
HomogeneousSystem: discrete homogeneous non-linear system.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import abc
|
|
20
|
+
from typing import Optional, Callable, override, final
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ('LtiSystem', 'AtiSystem',
|
|
25
|
+
'HomogeneousSystem', 'NonlinearSystem')
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Discrete(abc.ABC):
|
|
29
|
+
"""Abstract base class for discrete-time systems.
|
|
30
|
+
|
|
31
|
+
The system can be written as:
|
|
32
|
+
x[n+1] = f(x[n], u[n])
|
|
33
|
+
y[n] = g(x[n])
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@abc.abstractproperty
|
|
37
|
+
def n_state(self) -> int:
|
|
38
|
+
"""Dimension of state vector."""
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
@abc.abstractproperty
|
|
42
|
+
def n_control(self) -> int:
|
|
43
|
+
"""Dimension of control vector."""
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
@abc.abstractproperty
|
|
47
|
+
def n_output(self) -> int:
|
|
48
|
+
"""Dimension of output vector."""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
@abc.abstractmethod
|
|
52
|
+
def _linearize_transition(self,
|
|
53
|
+
state: Optional[np.ndarray] = None,
|
|
54
|
+
control: Optional[np.ndarray] = None
|
|
55
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
56
|
+
"""Linearize the state transition function.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
state: current state vector of shape (n_state, ).
|
|
60
|
+
control: current control input of shape (n_control, ).
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: the transition
|
|
64
|
+
matrix A, control matrix B, and disturbance w.
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
...
|
|
68
|
+
|
|
69
|
+
@abc.abstractmethod
|
|
70
|
+
def _linearize_output(self,
|
|
71
|
+
state: Optional[np.ndarray] = None,
|
|
72
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
73
|
+
"""Linearize the output function.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
state: current state vector of shape (n_state, ).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
tuple[np.ndarray, np.ndarray]: the output matrix C and the
|
|
80
|
+
output disturbance vector v.
|
|
81
|
+
"""
|
|
82
|
+
...
|
|
83
|
+
|
|
84
|
+
def linearize(self,
|
|
85
|
+
state: Optional[np.ndarray] = None,
|
|
86
|
+
control: Optional[np.ndarray] = None
|
|
87
|
+
) -> AffineTimeInvariant:
|
|
88
|
+
"""Return the lti system based on given states.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
state: current state vector of shape (n_state, ).
|
|
92
|
+
control: current control input of shape (n_control, ).
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
LtiSystem: the linearized system.
|
|
96
|
+
"""
|
|
97
|
+
transition_matrix, control_matrix, state_disturbance_vector = \
|
|
98
|
+
self._linearize_transition(state, control)
|
|
99
|
+
output_matrix, output_disturbance_vector = \
|
|
100
|
+
self._linearize_output(state)
|
|
101
|
+
return AtiSystem(transition_matrix,
|
|
102
|
+
control_matrix,
|
|
103
|
+
state_disturbance_vector,
|
|
104
|
+
output_matrix,
|
|
105
|
+
output_disturbance_vector)
|
|
106
|
+
|
|
107
|
+
def _get_state_one_step(self,
|
|
108
|
+
state: np.ndarray,
|
|
109
|
+
control: np.ndarray) -> np.ndarray:
|
|
110
|
+
"""Evaluate the next state vector.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
state: current state vector of shape (n_state, ).
|
|
114
|
+
control: current control input of shape (n_control, ).
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
np.ndarray: the state sequence of shape (n_state,).
|
|
118
|
+
"""
|
|
119
|
+
a, b, w = self._linearize_transition(state, control)
|
|
120
|
+
return a @ state + b @ control + w
|
|
121
|
+
|
|
122
|
+
def get_state(self,
|
|
123
|
+
initial_state: np.ndarray,
|
|
124
|
+
controls: np.ndarray
|
|
125
|
+
) -> np.ndarray:
|
|
126
|
+
"""Evaluate the states based on given input.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
initial_state: the initial state of shape (n_state, ).
|
|
130
|
+
controls: the control input of shape (n_steps, n_control).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
np.ndarray: the state sequence of shape (n_steps, n_state).
|
|
134
|
+
"""
|
|
135
|
+
n = controls.shape[0]
|
|
136
|
+
xs = np.zeros([n, self.n_state])
|
|
137
|
+
state = initial_state
|
|
138
|
+
for (i, control) in enumerate(controls):
|
|
139
|
+
next_state = self._get_state_one_step(state, control)
|
|
140
|
+
xs[i] = next_state
|
|
141
|
+
state = next_state
|
|
142
|
+
return xs
|
|
143
|
+
|
|
144
|
+
def _get_output_one_step(self,
|
|
145
|
+
state: np.ndarray
|
|
146
|
+
) -> np.ndarray:
|
|
147
|
+
"""Evaluate the output vector for a single step.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
state: current state vector of shape (n_state, ).
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
np.ndarray: the output vector of shape (n_output, ).
|
|
154
|
+
"""
|
|
155
|
+
c, v = self._linearize_output(state)
|
|
156
|
+
return c @ state + v
|
|
157
|
+
|
|
158
|
+
def get_output(self,
|
|
159
|
+
states: np.ndarray
|
|
160
|
+
) -> np.ndarray:
|
|
161
|
+
"""Evaluate the outputs based on given states.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
states: the state sequence of shape (n_steps, n_state),
|
|
165
|
+
typically obtained from get_state().
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
np.ndarray: the output sequence of shape (n_steps, n_output).
|
|
169
|
+
"""
|
|
170
|
+
n = states.shape[0]
|
|
171
|
+
ys = np.zeros([n, self.n_output])
|
|
172
|
+
for (i, state) in enumerate(states):
|
|
173
|
+
ys[i] = self._get_output_one_step(state)
|
|
174
|
+
return ys
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class AffineTimeInvariant(Discrete):
|
|
178
|
+
"""Base class (trait) for affine time invariant (ATI) systems.
|
|
179
|
+
|
|
180
|
+
x[n+1] = A @ x[n] + B @ u[n] + w
|
|
181
|
+
y[n] = C @ x[n]
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
@abc.abstractproperty
|
|
185
|
+
def transition_matrix(self) -> np.ndarray:
|
|
186
|
+
"""Return the state transition matrix A."""
|
|
187
|
+
...
|
|
188
|
+
|
|
189
|
+
@abc.abstractproperty
|
|
190
|
+
def control_matrix(self) -> np.ndarray:
|
|
191
|
+
"""Return the control matrix B."""
|
|
192
|
+
...
|
|
193
|
+
|
|
194
|
+
@abc.abstractproperty
|
|
195
|
+
def state_disturbance_vector(self) -> np.ndarray:
|
|
196
|
+
"""Return the state disturbance vector w."""
|
|
197
|
+
...
|
|
198
|
+
|
|
199
|
+
@abc.abstractproperty
|
|
200
|
+
def output_disturbance_vector(self) -> np.ndarray:
|
|
201
|
+
"""Return the output disturbance vector v."""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
@abc.abstractproperty
|
|
205
|
+
def output_matrix(self) -> np.ndarray:
|
|
206
|
+
"""Return the output matrix C."""
|
|
207
|
+
...
|
|
208
|
+
|
|
209
|
+
@override
|
|
210
|
+
def _linearize_transition(self,
|
|
211
|
+
state: Optional[np.ndarray] = None,
|
|
212
|
+
control: Optional[np.ndarray] = None
|
|
213
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
214
|
+
"""Linearize the state transition function.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
state: current state vector of shape (n_state, ).
|
|
218
|
+
control: current control input of shape (n_control, ).
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: the transition
|
|
222
|
+
matrix A, control matrix B, and disturbance w.
|
|
223
|
+
|
|
224
|
+
"""
|
|
225
|
+
return (self.transition_matrix,
|
|
226
|
+
self.control_matrix,
|
|
227
|
+
self.state_disturbance_vector)
|
|
228
|
+
|
|
229
|
+
@override
|
|
230
|
+
def _linearize_output(self,
|
|
231
|
+
state: Optional[np.ndarray] = None,
|
|
232
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
233
|
+
"""Linearize the output function.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
state: current state vector of shape (n_state, ).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
tuple[np.ndarray, np.ndarray]: the output matrix C and the
|
|
240
|
+
output disturbance vector v.
|
|
241
|
+
"""
|
|
242
|
+
return (self.output_matrix, self.output_disturbance_vector)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@final
|
|
246
|
+
class AtiSystem(AffineTimeInvariant):
|
|
247
|
+
"""Affine time invariant system.
|
|
248
|
+
|
|
249
|
+
x[n+1] = A @ x[n] + B @ u[n] + w
|
|
250
|
+
y[n] = C @ x[n] + v
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
def __init__(self,
|
|
254
|
+
transition_matrix: np.ndarray,
|
|
255
|
+
control_matrix: np.ndarray,
|
|
256
|
+
state_disturbance_vector: np.ndarray,
|
|
257
|
+
output_matrix: np.ndarray,
|
|
258
|
+
output_disturbance_vector: np.ndarray
|
|
259
|
+
):
|
|
260
|
+
"""Initialize the ATI system.
|
|
261
|
+
|
|
262
|
+
Args:
|
|
263
|
+
transition_matrix: the state transition matrix A of shape
|
|
264
|
+
(n_state, n_state).
|
|
265
|
+
control_matrix: the control matrix B of shape
|
|
266
|
+
(n_state, n_control).
|
|
267
|
+
state_disturbance_vector: the state disturbance vector w of shape
|
|
268
|
+
(n_state,).
|
|
269
|
+
output_matrix: the output matrix C of shape
|
|
270
|
+
(n_output, n_state).
|
|
271
|
+
output_disturbance_vector: the output disturbance vector v of
|
|
272
|
+
shape (n_output,).
|
|
273
|
+
"""
|
|
274
|
+
self._a = np.asarray(transition_matrix)
|
|
275
|
+
self._b = np.asarray(control_matrix)
|
|
276
|
+
self._w = np.asarray(state_disturbance_vector)
|
|
277
|
+
self._c = np.asarray(output_matrix)
|
|
278
|
+
self._v = np.asarray(output_disturbance_vector)
|
|
279
|
+
self._n_state = self._a.shape[0]
|
|
280
|
+
self._n_control = self._b.shape[1]
|
|
281
|
+
self._n_output = self._c.shape[0]
|
|
282
|
+
|
|
283
|
+
@override
|
|
284
|
+
@property
|
|
285
|
+
def n_state(self) -> int:
|
|
286
|
+
"""Dimension of state vector."""
|
|
287
|
+
return self._n_state
|
|
288
|
+
|
|
289
|
+
@override
|
|
290
|
+
@property
|
|
291
|
+
def n_control(self) -> int:
|
|
292
|
+
"""Dimension of control vector."""
|
|
293
|
+
return self._n_control
|
|
294
|
+
|
|
295
|
+
@override
|
|
296
|
+
@property
|
|
297
|
+
def n_output(self) -> int:
|
|
298
|
+
"""Dimension of output vector."""
|
|
299
|
+
return self._n_output
|
|
300
|
+
|
|
301
|
+
@override
|
|
302
|
+
@property
|
|
303
|
+
def transition_matrix(self) -> np.ndarray:
|
|
304
|
+
"""Return the state transition matrix A."""
|
|
305
|
+
return self._a
|
|
306
|
+
|
|
307
|
+
@override
|
|
308
|
+
@property
|
|
309
|
+
def control_matrix(self) -> np.ndarray:
|
|
310
|
+
"""Return the control matrix B."""
|
|
311
|
+
return self._b
|
|
312
|
+
|
|
313
|
+
@override
|
|
314
|
+
@property
|
|
315
|
+
def state_disturbance_vector(self) -> np.ndarray:
|
|
316
|
+
"""Return the state disturbance vector w."""
|
|
317
|
+
return self._w
|
|
318
|
+
|
|
319
|
+
@override
|
|
320
|
+
@property
|
|
321
|
+
def output_disturbance_vector(self) -> np.ndarray:
|
|
322
|
+
"""Return the output disturbance vector v."""
|
|
323
|
+
return self._v
|
|
324
|
+
|
|
325
|
+
@override
|
|
326
|
+
@property
|
|
327
|
+
def output_matrix(self) -> np.ndarray:
|
|
328
|
+
"""Return the output matrix C."""
|
|
329
|
+
return self._c
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
@final
|
|
333
|
+
class LtiSystem(AffineTimeInvariant):
|
|
334
|
+
"""
|
|
335
|
+
Discrete Linear Time-Invariant System.
|
|
336
|
+
|
|
337
|
+
Equation:
|
|
338
|
+
x[n+1] = A @ x[n] + B @ u[n]
|
|
339
|
+
y[n] = C @ x[n]
|
|
340
|
+
"""
|
|
341
|
+
|
|
342
|
+
def __init__(self,
|
|
343
|
+
transition_matrix: np.ndarray,
|
|
344
|
+
control_matrix: np.ndarray,
|
|
345
|
+
output_matrix: np.ndarray
|
|
346
|
+
):
|
|
347
|
+
"""Initialize the LTI system.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
transition_matrix: the state transition matrix A of shape
|
|
351
|
+
(n_state, n_state).
|
|
352
|
+
control_matrix: the control matrix B of shape
|
|
353
|
+
(n_state, n_control).
|
|
354
|
+
output_matrix: the output matrix C of shape
|
|
355
|
+
(n_output, n_state).
|
|
356
|
+
"""
|
|
357
|
+
self._a = np.asarray(transition_matrix)
|
|
358
|
+
self._b = np.asarray(control_matrix)
|
|
359
|
+
self._c = np.asarray(output_matrix)
|
|
360
|
+
self._n_state = self._a.shape[0]
|
|
361
|
+
self._n_control = self._b.shape[1]
|
|
362
|
+
self._n_output = self._c.shape[0]
|
|
363
|
+
|
|
364
|
+
@override
|
|
365
|
+
@property
|
|
366
|
+
def n_state(self) -> int:
|
|
367
|
+
"""Dimension of state vector."""
|
|
368
|
+
return self._n_state
|
|
369
|
+
|
|
370
|
+
@override
|
|
371
|
+
@property
|
|
372
|
+
def n_control(self) -> int:
|
|
373
|
+
"""Dimension of control vector."""
|
|
374
|
+
return self._n_control
|
|
375
|
+
|
|
376
|
+
@override
|
|
377
|
+
@property
|
|
378
|
+
def n_output(self) -> int:
|
|
379
|
+
"""Dimension of output vector."""
|
|
380
|
+
return self._n_output
|
|
381
|
+
|
|
382
|
+
@override
|
|
383
|
+
@property
|
|
384
|
+
def transition_matrix(self) -> np.ndarray:
|
|
385
|
+
"""Return the state transition matrix A."""
|
|
386
|
+
return self._a
|
|
387
|
+
|
|
388
|
+
@override
|
|
389
|
+
@property
|
|
390
|
+
def control_matrix(self) -> np.ndarray:
|
|
391
|
+
"""Return the control matrix B."""
|
|
392
|
+
return self._b
|
|
393
|
+
|
|
394
|
+
@override
|
|
395
|
+
@property
|
|
396
|
+
def state_disturbance_vector(self) -> np.ndarray:
|
|
397
|
+
"""Return the state disturbance vector w."""
|
|
398
|
+
return np.zeros([self.n_state])
|
|
399
|
+
|
|
400
|
+
@override
|
|
401
|
+
@property
|
|
402
|
+
def output_disturbance_vector(self) -> np.ndarray:
|
|
403
|
+
"""Return the output disturbance vector v."""
|
|
404
|
+
return np.zeros([self.n_output])
|
|
405
|
+
|
|
406
|
+
@override
|
|
407
|
+
@property
|
|
408
|
+
def output_matrix(self) -> np.ndarray:
|
|
409
|
+
"""Return the output matrix C."""
|
|
410
|
+
return self._c
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
@final
|
|
414
|
+
class HomogeneousSystem(Discrete):
|
|
415
|
+
"""
|
|
416
|
+
Discrete non-linear homogeneous system.
|
|
417
|
+
|
|
418
|
+
Equation:
|
|
419
|
+
x[n+1] = A(x[n], u[n]) @ x[n] + B(x[n], u[n]) @ u[n]
|
|
420
|
+
y[n] = C(x[n]) @ x[n]
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
def __init__(
|
|
424
|
+
self,
|
|
425
|
+
n_state: int,
|
|
426
|
+
n_control: int,
|
|
427
|
+
n_output: int,
|
|
428
|
+
transition_matrix: Callable[[np.ndarray, np.ndarray],
|
|
429
|
+
np.ndarray],
|
|
430
|
+
control_matrix: Callable[[np.ndarray, np.ndarray],
|
|
431
|
+
np.ndarray],
|
|
432
|
+
output_matrix: Callable[[np.ndarray],
|
|
433
|
+
np.ndarray]):
|
|
434
|
+
"""Initialize the discrete non-linear system with disturbance.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
n_state: dimension of the state vector.
|
|
438
|
+
n_control: dimension of the control vector.
|
|
439
|
+
n_output: dimension of the output vector.
|
|
440
|
+
transition_matrix: callable that returns the state transition
|
|
441
|
+
matrix A. Signature: A(state, control) -> np.ndarray of
|
|
442
|
+
shape (n_state, n_state).
|
|
443
|
+
control_matrix: callable that returns the control matrix B.
|
|
444
|
+
Signature: B(state, control) -> np.ndarray of shape
|
|
445
|
+
(n_state, n_control).
|
|
446
|
+
output_matrix: callable that returns the output matrix C.
|
|
447
|
+
Signature: C(state) -> np.ndarray of shape
|
|
448
|
+
(n_output, n_state).
|
|
449
|
+
"""
|
|
450
|
+
self._n_state = n_state
|
|
451
|
+
self._n_control = n_control
|
|
452
|
+
self._n_output = n_output
|
|
453
|
+
self._a = transition_matrix
|
|
454
|
+
self._b = control_matrix
|
|
455
|
+
self._c = output_matrix
|
|
456
|
+
|
|
457
|
+
@override
|
|
458
|
+
@property
|
|
459
|
+
def n_state(self) -> int:
|
|
460
|
+
"""Dimension of state vector."""
|
|
461
|
+
return self._n_state
|
|
462
|
+
|
|
463
|
+
@override
|
|
464
|
+
@property
|
|
465
|
+
def n_control(self) -> int:
|
|
466
|
+
"""Dimension of control vector."""
|
|
467
|
+
return self._n_control
|
|
468
|
+
|
|
469
|
+
@override
|
|
470
|
+
@property
|
|
471
|
+
def n_output(self) -> int:
|
|
472
|
+
"""Dimension of output vector."""
|
|
473
|
+
return self._n_output
|
|
474
|
+
|
|
475
|
+
@override
|
|
476
|
+
def _linearize_transition(self,
|
|
477
|
+
state: Optional[np.ndarray] = None,
|
|
478
|
+
control: Optional[np.ndarray] = None
|
|
479
|
+
) -> tuple[np.ndarray,
|
|
480
|
+
np.ndarray,
|
|
481
|
+
np.ndarray]:
|
|
482
|
+
"""Linearize the state transition function.
|
|
483
|
+
|
|
484
|
+
Args:
|
|
485
|
+
state: current state vector of shape (n_state, ).
|
|
486
|
+
control: current control input of shape (n_control, ).
|
|
487
|
+
|
|
488
|
+
Returns:
|
|
489
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: the transition
|
|
490
|
+
matrix A, control matrix B, and disturbance w.
|
|
491
|
+
"""
|
|
492
|
+
if state is None:
|
|
493
|
+
raise ValueError('state can not be None')
|
|
494
|
+
if control is None:
|
|
495
|
+
raise ValueError('control can not be None')
|
|
496
|
+
return (self._a(state, control),
|
|
497
|
+
self._b(state, control),
|
|
498
|
+
np.zeros([self.n_state]))
|
|
499
|
+
|
|
500
|
+
@override
|
|
501
|
+
def _linearize_output(self,
|
|
502
|
+
state: Optional[np.ndarray] = None,
|
|
503
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
504
|
+
"""Linearize the output function.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
state: current state vector of shape (n_state, ).
|
|
508
|
+
|
|
509
|
+
Returns:
|
|
510
|
+
tuple[np.ndarray, np.ndarray]: the output matrix C and the
|
|
511
|
+
output disturbance vector v.
|
|
512
|
+
"""
|
|
513
|
+
if state is None:
|
|
514
|
+
raise ValueError('state can not be None')
|
|
515
|
+
return (self._c(state), np.zeros(self.n_output))
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@final
|
|
519
|
+
class NonlinearSystem(Discrete):
|
|
520
|
+
"""
|
|
521
|
+
Discrete non-linear System.
|
|
522
|
+
|
|
523
|
+
Equation:
|
|
524
|
+
x[n+1] = A(x[n], u[n]) @ x[n] + B(x[n], u[n]) @ u[n] + w(x[n], u[n])
|
|
525
|
+
y[n] = C(x[n]) @ x[n] + v(x[n])
|
|
526
|
+
"""
|
|
527
|
+
|
|
528
|
+
def __init__(
|
|
529
|
+
self,
|
|
530
|
+
n_state: int,
|
|
531
|
+
n_control: int,
|
|
532
|
+
n_output: int,
|
|
533
|
+
transition_matrix: Callable[[np.ndarray, np.ndarray],
|
|
534
|
+
np.ndarray],
|
|
535
|
+
control_matrix: Callable[[np.ndarray, np.ndarray],
|
|
536
|
+
np.ndarray],
|
|
537
|
+
state_disturbance_vector: Callable[[np.ndarray, np.ndarray],
|
|
538
|
+
np.ndarray],
|
|
539
|
+
output_matrix: Callable[[np.ndarray],
|
|
540
|
+
np.ndarray],
|
|
541
|
+
output_disturbance_vector: Callable[[np.ndarray],
|
|
542
|
+
np.ndarray]):
|
|
543
|
+
"""Initialize the discrete non-linear system with disturbance.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
n_state: dimension of the state vector.
|
|
547
|
+
n_control: dimension of the control vector.
|
|
548
|
+
n_output: dimension of the output vector.
|
|
549
|
+
transition_matrix: callable that returns the state transition
|
|
550
|
+
matrix A. Signature: A(state, control) -> np.ndarray of
|
|
551
|
+
shape (n_state, n_state).
|
|
552
|
+
control_matrix: callable that returns the control matrix B.
|
|
553
|
+
Signature: B(state, control) -> np.ndarray of shape
|
|
554
|
+
(n_state, n_control).
|
|
555
|
+
state_disturbance_vector: callable that returns the state
|
|
556
|
+
disturbance vector w. Signature: w(state, control) ->
|
|
557
|
+
np.ndarray of shape (n_state,).
|
|
558
|
+
output_matrix: callable that returns the output matrix C.
|
|
559
|
+
Signature: C(state) -> np.ndarray of shape
|
|
560
|
+
(n_output, n_state).
|
|
561
|
+
output_disturbance_vector: callable that returns the output
|
|
562
|
+
disturbance vector v. Signature: v(state) -> np.ndarray
|
|
563
|
+
of shape (n_output,).
|
|
564
|
+
"""
|
|
565
|
+
self._n_state = n_state
|
|
566
|
+
self._n_control = n_control
|
|
567
|
+
self._n_output = n_output
|
|
568
|
+
self._a = transition_matrix
|
|
569
|
+
self._b = control_matrix
|
|
570
|
+
self._w = state_disturbance_vector
|
|
571
|
+
self._c = output_matrix
|
|
572
|
+
self._v = output_disturbance_vector
|
|
573
|
+
|
|
574
|
+
@override
|
|
575
|
+
@property
|
|
576
|
+
def n_state(self) -> int:
|
|
577
|
+
"""Dimension of state vector."""
|
|
578
|
+
return self._n_state
|
|
579
|
+
|
|
580
|
+
@override
|
|
581
|
+
@property
|
|
582
|
+
def n_control(self) -> int:
|
|
583
|
+
"""Dimension of control vector."""
|
|
584
|
+
return self._n_control
|
|
585
|
+
|
|
586
|
+
@override
|
|
587
|
+
@property
|
|
588
|
+
def n_output(self) -> int:
|
|
589
|
+
"""Dimension of output vector."""
|
|
590
|
+
return self._n_output
|
|
591
|
+
|
|
592
|
+
@override
|
|
593
|
+
def _linearize_transition(self,
|
|
594
|
+
state: Optional[np.ndarray] = None,
|
|
595
|
+
control: Optional[np.ndarray] = None
|
|
596
|
+
) -> tuple[np.ndarray,
|
|
597
|
+
np.ndarray,
|
|
598
|
+
np.ndarray]:
|
|
599
|
+
"""Linearize the state transition function.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
state: current state vector of shape (n_state, ).
|
|
603
|
+
control: current control input of shape (n_control, ).
|
|
604
|
+
|
|
605
|
+
Returns:
|
|
606
|
+
tuple[np.ndarray, np.ndarray, np.ndarray]: the transition
|
|
607
|
+
matrix A, control matrix B, and disturbance w.
|
|
608
|
+
"""
|
|
609
|
+
if state is None:
|
|
610
|
+
raise ValueError('state can not be None')
|
|
611
|
+
if control is None:
|
|
612
|
+
raise ValueError('control can not be None')
|
|
613
|
+
return (self._a(state, control),
|
|
614
|
+
self._b(state, control),
|
|
615
|
+
self._w(state, control))
|
|
616
|
+
|
|
617
|
+
@override
|
|
618
|
+
def _linearize_output(self,
|
|
619
|
+
state: Optional[np.ndarray] = None,
|
|
620
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
621
|
+
"""Linearize the output function.
|
|
622
|
+
|
|
623
|
+
Args:
|
|
624
|
+
state: current state vector of shape (n_state, ).
|
|
625
|
+
|
|
626
|
+
Returns:
|
|
627
|
+
tuple[np.ndarray, np.ndarray]: the output matrix C and the
|
|
628
|
+
output disturbance vector v.
|
|
629
|
+
"""
|
|
630
|
+
if state is None:
|
|
631
|
+
raise ValueError('state can not be None')
|
|
632
|
+
return (self._c(state), self._v(state))
|