mpc-control 0.2.0__tar.gz → 0.2.2__tar.gz
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-0.2.0/mpc_control.egg-info → mpc_control-0.2.2}/PKG-INFO +4 -4
- {mpc_control-0.2.0 → mpc_control-0.2.2}/README.md +2 -2
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/kalman.py +2 -2
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/mpc.py +95 -60
- {mpc_control-0.2.0 → mpc_control-0.2.2/mpc_control.egg-info}/PKG-INFO +4 -4
- {mpc_control-0.2.0 → mpc_control-0.2.2}/pyproject.toml +2 -2
- {mpc_control-0.2.0 → mpc_control-0.2.2}/test/test_kalman.py +8 -2
- {mpc_control-0.2.0 → mpc_control-0.2.2}/LICENSE +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/__init__.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/discrete.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/plant.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc/rls.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc_control.egg-info/SOURCES.txt +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc_control.egg-info/dependency_links.txt +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc_control.egg-info/requires.txt +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/mpc_control.egg-info/top_level.txt +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/setup.cfg +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/test/test_mpc.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/test/test_plant.py +0 -0
- {mpc_control-0.2.0 → mpc_control-0.2.2}/test/test_rls.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mpc-control
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A Python library for Model Predictive Control (MPC)
|
|
5
5
|
Author-email: Zhen Ni <z.ni@hotmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,7 @@ Project-URL: Issues, https://github.com/Zhen-Ni/mpc-control/issues
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Topic :: Scientific/Engineering
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: numpy>=2.0
|
|
@@ -42,7 +42,7 @@ pip install mpc-control
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
**Requirements:**
|
|
45
|
-
- Python 3.
|
|
45
|
+
- Python 3.12+
|
|
46
46
|
- NumPy, SciPy, OSQP
|
|
47
47
|
|
|
48
48
|
## 🚀 Quick Start
|
|
@@ -51,7 +51,7 @@ Here is a basic example of how to define a system and solve an MPC problem:
|
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
53
|
import numpy as np
|
|
54
|
-
import
|
|
54
|
+
import mpc
|
|
55
55
|
|
|
56
56
|
# 1. Define a discrete LTI system
|
|
57
57
|
# x[n+1] = A x[n] + B u[n]
|
|
@@ -23,7 +23,7 @@ pip install mpc-control
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
**Requirements:**
|
|
26
|
-
- Python 3.
|
|
26
|
+
- Python 3.12+
|
|
27
27
|
- NumPy, SciPy, OSQP
|
|
28
28
|
|
|
29
29
|
## 🚀 Quick Start
|
|
@@ -32,7 +32,7 @@ Here is a basic example of how to define a system and solve an MPC problem:
|
|
|
32
32
|
|
|
33
33
|
```python
|
|
34
34
|
import numpy as np
|
|
35
|
-
import
|
|
35
|
+
import mpc
|
|
36
36
|
|
|
37
37
|
# 1. Define a discrete LTI system
|
|
38
38
|
# x[n+1] = A x[n] + B u[n]
|
|
@@ -134,14 +134,14 @@ class Ekf:
|
|
|
134
134
|
"""
|
|
135
135
|
if self._linearized is None:
|
|
136
136
|
raise RuntimeError('Should call `predict` before updating.')
|
|
137
|
-
c = self.
|
|
137
|
+
c, v = self._system._linearize_output(self._x)
|
|
138
138
|
# Calculate innovation covariance `S`.
|
|
139
139
|
S = c @ self._p @ c.T + r
|
|
140
140
|
# Calculate Kalman gain `k`.
|
|
141
141
|
# k = p @ C.T @ inv(S) is equivalent to k.T = solve(S, C @ p)
|
|
142
142
|
k = np.linalg.solve(S, c @ self._p).T
|
|
143
143
|
# Calculate the updated state vector `x`.
|
|
144
|
-
x = self._x + k @ (np.asarray(z) - c @ self._x)
|
|
144
|
+
x = self._x + k @ (np.asarray(z) - c @ self._x - v)
|
|
145
145
|
# Calculate the updated uncertainty `p` for the state vector.
|
|
146
146
|
T = np.eye(k.shape[0]) - k @ c
|
|
147
147
|
p = T @ self._p @ T.T + k @ r @ k.T
|
|
@@ -678,74 +678,54 @@ class Mpc:
|
|
|
678
678
|
|
|
679
679
|
self._qp_internal.q = q
|
|
680
680
|
|
|
681
|
-
def
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
target_output:
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
to x_{N-1}).
|
|
704
|
-
control_ref: Reference control for system
|
|
705
|
-
linearization. This corresponds to the controls from
|
|
706
|
-
step 0 to step N-1 (u_0 to u_{N-1}).
|
|
707
|
-
max_iter: Maximum iterations for OSQP solver.
|
|
708
|
-
eps_abs: Absolute convergence tolerance for OSQP solver.
|
|
709
|
-
eps_rel: Relative convergence tolerance for OSQP solver.
|
|
710
|
-
|
|
711
|
-
Returns:
|
|
712
|
-
The optimal control sequence U with shape (horizon,
|
|
713
|
-
n_control), corresponding to the controls from step 0 to
|
|
714
|
-
step N-1 (u_0 to u_{N-1}), or None if the problem is not
|
|
715
|
-
solved successfully.
|
|
716
|
-
|
|
717
|
-
"""
|
|
718
|
-
use_cached_p = (
|
|
719
|
-
self._qp_internal and
|
|
720
|
-
isinstance(self._system, AffineTimeInvariant))
|
|
721
|
-
use_cached_a = (
|
|
722
|
-
(not self._qp_constraint.modified) and
|
|
723
|
-
isinstance(self._system, AffineTimeInvariant))
|
|
724
|
-
|
|
725
|
-
if previous_control is None:
|
|
726
|
-
if (self._control_delta_weighting is None and
|
|
727
|
-
self._qp_constraint.control_delta_bound is None):
|
|
728
|
-
# Set to empty because it will not be used anyway.
|
|
729
|
-
previous_control = np.empty(self._system.n_control)
|
|
730
|
-
else:
|
|
731
|
-
raise ValueError(
|
|
732
|
-
'previous_control must be provided if '
|
|
733
|
-
'control changing rate is involved.')
|
|
734
|
-
|
|
681
|
+
def _validate_previous_control(
|
|
682
|
+
self,
|
|
683
|
+
previous_control: Optional[np.ndarray]) -> np.ndarray:
|
|
684
|
+
if previous_control is not None:
|
|
685
|
+
return previous_control
|
|
686
|
+
# Use a placeholder for previous control, as it will not be
|
|
687
|
+
# used in this case.
|
|
688
|
+
if (self._control_delta_weighting is None and
|
|
689
|
+
self._qp_constraint.control_delta_bound is None):
|
|
690
|
+
return np.empty(self._system.n_control)
|
|
691
|
+
raise ValueError(
|
|
692
|
+
'previous_control must be provided if '
|
|
693
|
+
'control changing rate is involved.')
|
|
694
|
+
|
|
695
|
+
def _prepare_qp_matrices(
|
|
696
|
+
self,
|
|
697
|
+
target_output: np.ndarray,
|
|
698
|
+
initial_state: np.ndarray,
|
|
699
|
+
previous_control: np.ndarray,
|
|
700
|
+
state_ref: Optional[np.ndarray],
|
|
701
|
+
control_ref: Optional[np.ndarray],
|
|
702
|
+
use_cached_p: bool) -> None:
|
|
735
703
|
if use_cached_p:
|
|
736
|
-
self._update_qp(target_output,
|
|
737
|
-
initial_state, previous_control)
|
|
704
|
+
self._update_qp(target_output, initial_state, previous_control)
|
|
738
705
|
else:
|
|
739
|
-
self._build_qp(target_output,
|
|
740
|
-
initial_state, previous_control,
|
|
706
|
+
self._build_qp(target_output, initial_state, previous_control,
|
|
741
707
|
state_ref, control_ref)
|
|
742
708
|
|
|
709
|
+
def _prepare_constraints(
|
|
710
|
+
self,
|
|
711
|
+
initial_state: np.ndarray,
|
|
712
|
+
previous_control: np.ndarray,
|
|
713
|
+
use_cached_a: bool) -> None:
|
|
743
714
|
if use_cached_a:
|
|
744
715
|
self._update_constraints(initial_state, previous_control)
|
|
745
716
|
else:
|
|
746
717
|
self._build_constraints(initial_state, previous_control)
|
|
747
718
|
|
|
719
|
+
def _setup_and_solve_osqp(
|
|
720
|
+
self,
|
|
721
|
+
max_iter: Optional[int],
|
|
722
|
+
eps_abs: Optional[float],
|
|
723
|
+
eps_rel: Optional[float],
|
|
724
|
+
control_ref: Optional[np.ndarray],
|
|
725
|
+
use_cached_p: bool,
|
|
726
|
+
use_cached_a: bool) -> Optional[np.ndarray]:
|
|
748
727
|
assert self._qp_internal is not None # Make mypy happy
|
|
728
|
+
|
|
749
729
|
p = self._qp_internal.csc_p
|
|
750
730
|
q = self._qp_internal.q
|
|
751
731
|
a = self._qp_constraint.a
|
|
@@ -773,9 +753,64 @@ class Mpc:
|
|
|
773
753
|
|
|
774
754
|
if control_ref is not None:
|
|
775
755
|
self._osqp.warm_start(x=np.asarray(control_ref).reshape(-1))
|
|
756
|
+
|
|
776
757
|
res = self._osqp.solve(raise_error=False)
|
|
777
758
|
self._result = res
|
|
778
759
|
if res.info.status == 'solved':
|
|
779
|
-
|
|
780
|
-
return u
|
|
760
|
+
return res.x.reshape(self._horizon, self._system.n_control)
|
|
781
761
|
return None
|
|
762
|
+
|
|
763
|
+
def solve(self,
|
|
764
|
+
target_output: np.ndarray,
|
|
765
|
+
initial_state: np.ndarray,
|
|
766
|
+
previous_control: Optional[np.ndarray] = None,
|
|
767
|
+
state_ref: Optional[np.ndarray] = None,
|
|
768
|
+
control_ref: Optional[np.ndarray] = None,
|
|
769
|
+
max_iter: Optional[int] = None,
|
|
770
|
+
eps_abs: Optional[float] = None,
|
|
771
|
+
eps_rel: Optional[float] = None
|
|
772
|
+
) -> Optional[np.ndarray]:
|
|
773
|
+
"""Solve the mpc problem.
|
|
774
|
+
|
|
775
|
+
Args:
|
|
776
|
+
initial_state: Initial state (x_0).
|
|
777
|
+
previous_control: The control input in the previous
|
|
778
|
+
timestep (u_{-1}).
|
|
779
|
+
target_output: Reference output sequence Y_ref. Note that
|
|
780
|
+
this corresponds to the outputs from step 1 to step N
|
|
781
|
+
(y_1 to y_N), as the output at the current step 0
|
|
782
|
+
(y_0) cannot be influenced by future controls.
|
|
783
|
+
state_ref: Reference state for system linearization. This
|
|
784
|
+
corresponds to the states from step 0 to step N-1 (x_0
|
|
785
|
+
to x_{N-1}).
|
|
786
|
+
control_ref: Reference control for system
|
|
787
|
+
linearization. This corresponds to the controls from
|
|
788
|
+
step 0 to step N-1 (u_0 to u_{N-1}).
|
|
789
|
+
max_iter: Maximum iterations for OSQP solver.
|
|
790
|
+
eps_abs: Absolute convergence tolerance for OSQP solver.
|
|
791
|
+
eps_rel: Relative convergence tolerance for OSQP solver.
|
|
792
|
+
|
|
793
|
+
Returns:
|
|
794
|
+
The optimal control sequence U with shape (horizon,
|
|
795
|
+
n_control), corresponding to the controls from step 0 to
|
|
796
|
+
step N-1 (u_0 to u_{N-1}), or None if the problem is not
|
|
797
|
+
solved successfully.
|
|
798
|
+
|
|
799
|
+
"""
|
|
800
|
+
use_cached_p = bool(self._qp_internal and
|
|
801
|
+
isinstance(self._system, AffineTimeInvariant))
|
|
802
|
+
use_cached_a = bool((not self._qp_constraint.modified) and
|
|
803
|
+
isinstance(self._system, AffineTimeInvariant))
|
|
804
|
+
|
|
805
|
+
previous_control = self._validate_previous_control(previous_control)
|
|
806
|
+
|
|
807
|
+
self._prepare_qp_matrices(
|
|
808
|
+
target_output, initial_state, previous_control,
|
|
809
|
+
state_ref, control_ref, use_cached_p)
|
|
810
|
+
|
|
811
|
+
self._prepare_constraints(
|
|
812
|
+
initial_state, previous_control, use_cached_a)
|
|
813
|
+
|
|
814
|
+
return self._setup_and_solve_osqp(
|
|
815
|
+
max_iter, eps_abs, eps_rel, control_ref,
|
|
816
|
+
use_cached_p, use_cached_a)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mpc-control
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: A Python library for Model Predictive Control (MPC)
|
|
5
5
|
Author-email: Zhen Ni <z.ni@hotmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -9,7 +9,7 @@ Project-URL: Issues, https://github.com/Zhen-Ni/mpc-control/issues
|
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: Topic :: Scientific/Engineering
|
|
12
|
-
Requires-Python: >=3.
|
|
12
|
+
Requires-Python: >=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: numpy>=2.0
|
|
@@ -42,7 +42,7 @@ pip install mpc-control
|
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
**Requirements:**
|
|
45
|
-
- Python 3.
|
|
45
|
+
- Python 3.12+
|
|
46
46
|
- NumPy, SciPy, OSQP
|
|
47
47
|
|
|
48
48
|
## 🚀 Quick Start
|
|
@@ -51,7 +51,7 @@ Here is a basic example of how to define a system and solve an MPC problem:
|
|
|
51
51
|
|
|
52
52
|
```python
|
|
53
53
|
import numpy as np
|
|
54
|
-
import
|
|
54
|
+
import mpc
|
|
55
55
|
|
|
56
56
|
# 1. Define a discrete LTI system
|
|
57
57
|
# x[n+1] = A x[n] + B u[n]
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mpc-control"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.2"
|
|
8
8
|
description = "A Python library for Model Predictive Control (MPC)"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
11
|
license = "MIT"
|
|
12
12
|
license-files = ["LICENSE"]
|
|
13
13
|
authors = [
|
|
@@ -76,6 +76,7 @@ class TestEKF(unittest.TestCase):
|
|
|
76
76
|
|
|
77
77
|
def test_observe_state_noised(self):
|
|
78
78
|
"""State observer of a linear system with noise."""
|
|
79
|
+
np.random.seed(0)
|
|
79
80
|
m1, m2 = 1.0, 1.5
|
|
80
81
|
c1, c2 = 0.2, 0.3
|
|
81
82
|
k1, k2 = 15.0, 20.0
|
|
@@ -137,10 +138,12 @@ class TestEKF(unittest.TestCase):
|
|
|
137
138
|
x_true = np.array(x_true_list)
|
|
138
139
|
x_pred = np.array(x_pred_list)
|
|
139
140
|
|
|
140
|
-
self.assertTrue(abs((
|
|
141
|
+
self.assertTrue((abs((x_true - x_pred)[-100:]) < 0.1).all())
|
|
141
142
|
|
|
142
143
|
def test_observe_nonlinear_state(self):
|
|
143
144
|
"""State observer of a nonlinear system."""
|
|
145
|
+
np.random.seed(0)
|
|
146
|
+
|
|
144
147
|
def A_true(x, u):
|
|
145
148
|
return np.array([0.5, 1.,
|
|
146
149
|
0., 0.48 + np.sin(u[0]+0.02)]).reshape(2, 2)
|
|
@@ -256,6 +259,7 @@ class TestUKF(unittest.TestCase):
|
|
|
256
259
|
|
|
257
260
|
def test_observe_state_noised(self):
|
|
258
261
|
"""State observer of a linear system with noise."""
|
|
262
|
+
np.random.seed(0)
|
|
259
263
|
m1, m2 = 1.0, 1.5
|
|
260
264
|
c1, c2 = 0.2, 0.3
|
|
261
265
|
k1, k2 = 15.0, 20.0
|
|
@@ -317,10 +321,12 @@ class TestUKF(unittest.TestCase):
|
|
|
317
321
|
x_true = np.array(x_true_list)
|
|
318
322
|
x_pred = np.array(x_pred_list)
|
|
319
323
|
|
|
320
|
-
self.assertTrue(abs((
|
|
324
|
+
self.assertTrue((abs((x_true - x_pred)[-100:]) < 0.1).all())
|
|
321
325
|
|
|
322
326
|
def test_observe_nonlinear_state(self):
|
|
323
327
|
"""State observer of a nonlinear system."""
|
|
328
|
+
np.random.seed(0)
|
|
329
|
+
|
|
324
330
|
def A_true(x, u):
|
|
325
331
|
return np.array([0.5, 1., 0.,
|
|
326
332
|
0.48 + np.sin(u[0]+0.02)]).reshape(2, 2)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|