classic-controlling 0.1.0__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.
- classic_controlling-0.1.0/LICENSE +21 -0
- classic_controlling-0.1.0/PKG-INFO +225 -0
- classic_controlling-0.1.0/README.md +178 -0
- classic_controlling-0.1.0/classic_controlling/__init__.py +17 -0
- classic_controlling-0.1.0/classic_controlling/adrc.py +252 -0
- classic_controlling-0.1.0/classic_controlling/autotune.py +147 -0
- classic_controlling-0.1.0/classic_controlling/mpc.py +164 -0
- classic_controlling-0.1.0/classic_controlling/sim.py +123 -0
- classic_controlling-0.1.0/classic_controlling/ukf.py +113 -0
- classic_controlling-0.1.0/classic_controlling.egg-info/PKG-INFO +225 -0
- classic_controlling-0.1.0/classic_controlling.egg-info/SOURCES.txt +18 -0
- classic_controlling-0.1.0/classic_controlling.egg-info/dependency_links.txt +1 -0
- classic_controlling-0.1.0/classic_controlling.egg-info/requires.txt +6 -0
- classic_controlling-0.1.0/classic_controlling.egg-info/top_level.txt +1 -0
- classic_controlling-0.1.0/pyproject.toml +31 -0
- classic_controlling-0.1.0/setup.cfg +4 -0
- classic_controlling-0.1.0/tests/test_adrc.py +64 -0
- classic_controlling-0.1.0/tests/test_autotune.py +245 -0
- classic_controlling-0.1.0/tests/test_mpc.py +84 -0
- classic_controlling-0.1.0/tests/test_ukf.py +136 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 classic_controlling contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: classic-controlling
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Self-tuning ADRC, vectorized UKF, and dependency-free linear MPC — classic control algorithms in pure NumPy, with benchmarks
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 classic_controlling contributors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Keywords: control,adrc,pid,mpc,kalman-filter,ukf,self-tuning
|
|
28
|
+
Classifier: Development Status :: 4 - Beta
|
|
29
|
+
Classifier: Intended Audience :: Science/Research
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Programming Language :: Python :: 3
|
|
32
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
33
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
34
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
35
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
37
|
+
Classifier: Topic :: Scientific/Engineering
|
|
38
|
+
Requires-Python: >=3.9
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
License-File: LICENSE
|
|
41
|
+
Requires-Dist: numpy
|
|
42
|
+
Requires-Dist: scipy
|
|
43
|
+
Provides-Extra: dev
|
|
44
|
+
Requires-Dist: pytest; extra == "dev"
|
|
45
|
+
Requires-Dist: matplotlib; extra == "dev"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# classic_controlling
|
|
49
|
+
|
|
50
|
+
**Self-tuning ADRC, vectorized UKF, and dependency-free MPC — classic control algorithms in pure NumPy, with benchmarks.**
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+

|
|
54
|
+

|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
> **Zero manual tuning:** `ADRC.auto_tune()` identifies the plant and reaches steady-state <5% error on first-order, second-order and nonlinear plants — blind-tested. Above: model-tuned PID vs zero-touch ADRC on three plants, with a step load disturbance at t=20s. On the open-loop-unstable nonlinear plant the Z-N tuned PID diverges into growing oscillation; the auto-tuned ADRC just locks on.
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **Self-tuning ADRC** (`ADRC.auto_tune`) — one step-response experiment → FOPDT identification → bandwidth selection under delay phase-margin/sampling hard-constraints. Steady-state error **0.9% / 3.2% / 1.0%** on the three blind-test plants, zero human parameters. Optional online RLS correction of b0 (experimental, off by default): rate-limited two-timescale updates, [0.3, 3.0]× rails, and automatic rollback-and-freeze if the estimate hugs a rail while tracking stagnates.
|
|
63
|
+
- **Vectorized UKF** — every sigma-point operation (generation, batch propagation, weighted mean/covariance rebuild) is one NumPy broadcast/einsum, no per-point Python loop: **2.2× / 6.0× / 11.2×** faster than a mathematically equivalent point-loop reference at dim_x = 2 / 8 / 32.
|
|
64
|
+
- **Dependency-free LinearMPC** — condensed QP compiled once, built-in OSQP-style ADMM solver with pre-factored KKT matrix, warm start over (z, s, y) cutting total iterations **1.9×** (3687 → 1935) on a constrained double integrator. No external solver (no OSQP/CVXPY) needed.
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install classic-controlling # or: pip install -e . from source
|
|
70
|
+
python -m pytest tests/ -v # 21 tests
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### ADRC — 5 lines to a self-tuned loop
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
from classic_controlling import ADRC, FOPDTPlant
|
|
77
|
+
|
|
78
|
+
plant = FOPDTPlant(gain=1.0, time_constant=0.5, dt=0.05)
|
|
79
|
+
ctl = ADRC.auto_tune(plant, dt=0.05) # identify + tune, zero knobs
|
|
80
|
+
# (online RLS: opt-in via adaptive=True)
|
|
81
|
+
for _ in range(300):
|
|
82
|
+
u = ctl.step(r=1.0, y=plant.y)
|
|
83
|
+
plant.step(u)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The full auto-tuning pipeline (step test → FOPDT fit → tracking → b0 adapting to a mid-run gain change), from `examples/demo_autotune.py`:
|
|
87
|
+
|
|
88
|
+

|
|
89
|
+
|
|
90
|
+
### UKF — vectorized sigma points
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import numpy as np
|
|
94
|
+
from classic_controlling import UKF
|
|
95
|
+
|
|
96
|
+
def fx(sigmas, dt): # (2n+1, 4) batch in, batch out
|
|
97
|
+
out = sigmas.copy()
|
|
98
|
+
out[:, 0] += dt * sigmas[:, 2]; out[:, 1] += dt * sigmas[:, 3]
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
ukf = UKF(4, 2, 0.1, fx, hx=lambda s: s[:, :2], Q=np.eye(4)*0.01, R=np.eye(2))
|
|
102
|
+
ests = ukf.batch_filter(zs) # zs: (N, 2) position measurements
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
From `examples/demo_ukf.py` — coordinated-turn tracking, RMSE 0.30 m under 0.5 m observation noise:
|
|
106
|
+
|
|
107
|
+

|
|
108
|
+
|
|
109
|
+
### LinearMPC — constrained MPC without a solver dependency
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import numpy as np
|
|
113
|
+
from classic_controlling import LinearMPC
|
|
114
|
+
|
|
115
|
+
A = np.array([[1.0, 0.1], [0.0, 1.0]]); B = np.array([[0.005], [0.1]])
|
|
116
|
+
mpc = LinearMPC(A, B, Q=np.eye(2), R=[[0.1]], horizon=40, u_min=-1.0, u_max=1.0)
|
|
117
|
+
x = np.array([3.0, 0.0])
|
|
118
|
+
for _ in range(120):
|
|
119
|
+
x = A @ x + B @ mpc.solve(x, warm_start=True)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
From `examples/demo_mpc.py`:
|
|
123
|
+
|
|
124
|
+

|
|
125
|
+
|
|
126
|
+
## Benchmarks
|
|
127
|
+
|
|
128
|
+
### Vectorized UKF vs point-loop reference
|
|
129
|
+
|
|
130
|
+
`python benchmarks/bench_ukf.py` — 1000 predict+update steps each, mathematically equivalent implementations (Python 3.13.5 / NumPy 2.3.3, Windows):
|
|
131
|
+
|
|
132
|
+
| dim_x | vectorized (s) | point-loop (s) | speedup |
|
|
133
|
+
|------:|---------------:|---------------:|--------:|
|
|
134
|
+
| 2 | 0.0392 | 0.0855 | 2.2× |
|
|
135
|
+
| 8 | 0.0439 | 0.2623 | 6.0× |
|
|
136
|
+
| 32 | 0.1079 | 1.2066 | 11.2× |
|
|
137
|
+
|
|
138
|
+
The point-loop overhead grows linearly with `2n+1` sigma points; the vectorized path is eaten by BLAS.
|
|
139
|
+
|
|
140
|
+
### `auto_tune` blind test
|
|
141
|
+
|
|
142
|
+
Zero human parameters; the plants' true parameters are used only for assertions (`tests/test_autotune.py`, 300 closed-loop steps at dt=0.05):
|
|
143
|
+
|
|
144
|
+
| plant | model | steady-state error |
|
|
145
|
+
|---|---|---:|
|
|
146
|
+
| FOPDT | `0.5·ẏ + y = u` | 0.89% |
|
|
147
|
+
| second-order underdamped | `ζ=0.5, ωn=3` | 3.21% |
|
|
148
|
+
| nonlinear, open-loop unstable | `ÿ = -ẏ + u + 0.5y²` | 1.03% |
|
|
149
|
+
|
|
150
|
+
### MPC warm start
|
|
151
|
+
|
|
152
|
+
Constrained double integrator, horizon 40, 120 closed-loop steps (`examples/demo_mpc.py`):
|
|
153
|
+
|
|
154
|
+
| | total ADMM iters | mean iters/step |
|
|
155
|
+
|---|---:|---:|
|
|
156
|
+
| cold start | 3687 | 30.7 |
|
|
157
|
+
| warm start | 1935 | 16.1 |
|
|
158
|
+
| **speedup** | **1.9×** | **1.9×** |
|
|
159
|
+
|
|
160
|
+
### ADRC.auto_tune vs model-tuned PID
|
|
161
|
+
|
|
162
|
+
Step tracking `r=1` with a step load disturbance at t=20s (`examples/demo_adrc_vs_pid.py`; PID tuned from the *same* step-test data via Ziegler-Nichols reaction-curve rules, IMC-PI fallback when the identified delay ≈ 0):
|
|
163
|
+
|
|
164
|
+
| plant | controller | overshoot | settling (s) | disturbance recovery (s) | IAE |
|
|
165
|
+
|---|---|---:|---:|---:|---:|
|
|
166
|
+
| FOPDT | PID (IMC-PI) | 0.0% | 0.40 | 0.55 | 0.15 |
|
|
167
|
+
| FOPDT | ADRC | 0.0% | 12.45 | 6.25 | 4.12 |
|
|
168
|
+
| 2nd-order | PID (Z-N) | 6.7% | 2.05 | 1.05 | 0.51 |
|
|
169
|
+
| 2nd-order | ADRC | 0.0% | 17.05 | 6.35 | 5.06 |
|
|
170
|
+
| nonlinear | PID (Z-N) | 757% | — | ∞ | 170.86 |
|
|
171
|
+
| nonlinear | ADRC | 20.1% | 13.20 | 3.55 | 2.89 |
|
|
172
|
+
|
|
173
|
+
Honest read: on well-behaved LTI plants a model-tuned PID is faster — ADRC's auto-bandwidth trades speed for guarantees (the delay phase-margin cap is calibrated against measured divergence, not folklore). The trade: ADRC never rings, needs zero expertise, and is the only one left standing on the open-loop-unstable nonlinear plant.
|
|
174
|
+
|
|
175
|
+
## Examples
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
python examples/demo_adrc_vs_pid.py # hero comparison + metrics table
|
|
179
|
+
python examples/demo_autotune.py # auto_tune pipeline end-to-end
|
|
180
|
+
python examples/demo_mpc.py # constrained MPC + warm-start speedup
|
|
181
|
+
python examples/demo_ukf.py # coordinated-turn tracking
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
All figures are written to `docs/figures/`.
|
|
185
|
+
|
|
186
|
+
## Project layout
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
classic_controlling/ # the package
|
|
190
|
+
├── adrc.py # linear ADRC (bandwidth tuning + auto_tune + RLS b0 adaptation)
|
|
191
|
+
├── autotune.py # step-response FOPDT identification + automatic bandwidth selection
|
|
192
|
+
├── sim.py # virtual plants (FOPDT / 2nd-order / nonlinear), reset()/step(u) protocol
|
|
193
|
+
├── ukf.py # vectorized UKF
|
|
194
|
+
└── mpc.py # LinearMPC (condensed QP + built-in ADMM + warm start)
|
|
195
|
+
tests/ # 21 pytest tests (blind auto-tune assertions, SLSQP cross-check, ...)
|
|
196
|
+
benchmarks/bench_ukf.py # UKF vectorized vs point-loop benchmark
|
|
197
|
+
examples/ # runnable demos -> docs/figures/*.png
|
|
198
|
+
research/ # background research notes (Chinese)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 中文简介
|
|
202
|
+
|
|
203
|
+
三个经典控制/估计算法的高质量纯 NumPy 实现:
|
|
204
|
+
|
|
205
|
+
- **自整定 ADRC**:`ADRC.auto_tune(plant, dt)` 一键完成阶跃辨识 → FOPDT 拟合 → 带宽自动选择(时滞相位裕量帽 + 采样硬约束),全程零人工调参;一阶/二阶/非线性三个对象盲测稳态误差 0.9% / 3.2% / 1.0%。在线 RLS 修正 b0 为 experimental 可选项(默认关闭):限速双时间尺度更新 + [0.3, 3.0]× 护栏 + 贴护栏停滞自动回退冻结;
|
|
206
|
+
- **向量化 UKF**:sigma 点操作全部 NumPy 广播一次完成,无逐点循环,相比等价逐点实现加速 2.2× / 6.0× / 11.2×(dim 2/8/32);
|
|
207
|
+
- **轻量线性 MPC**:稠密式 QP 编译一次 + 内置 OSQP 风格 ADMM 求解器 + (z, s, y) 热启动(总迭代数减 1.9×),零外部求解器依赖。
|
|
208
|
+
|
|
209
|
+
快速开始:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
pip install -e .
|
|
213
|
+
python -m pytest tests/ -v # 21 项测试
|
|
214
|
+
python examples/demo_adrc_vs_pid.py # 核心对比演示,出图到 docs/figures/
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Roadmap
|
|
218
|
+
|
|
219
|
+
- Upstream the vectorized UKF sigma-point path as a PR to `filterpy`
|
|
220
|
+
- Optional Numba backend for the MPC ADMM iterations (per-iteration cost → microseconds)
|
|
221
|
+
- More virtual plant types (integrating, oscillatory, MIMO) and ADRC tracking of time-varying references
|
|
222
|
+
|
|
223
|
+
## License
|
|
224
|
+
|
|
225
|
+
MIT — see [LICENSE](LICENSE). Contributions welcome: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# classic_controlling
|
|
2
|
+
|
|
3
|
+
**Self-tuning ADRC, vectorized UKF, and dependency-free MPC — classic control algorithms in pure NumPy, with benchmarks.**
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
> **Zero manual tuning:** `ADRC.auto_tune()` identifies the plant and reaches steady-state <5% error on first-order, second-order and nonlinear plants — blind-tested. Above: model-tuned PID vs zero-touch ADRC on three plants, with a step load disturbance at t=20s. On the open-loop-unstable nonlinear plant the Z-N tuned PID diverges into growing oscillation; the auto-tuned ADRC just locks on.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Self-tuning ADRC** (`ADRC.auto_tune`) — one step-response experiment → FOPDT identification → bandwidth selection under delay phase-margin/sampling hard-constraints. Steady-state error **0.9% / 3.2% / 1.0%** on the three blind-test plants, zero human parameters. Optional online RLS correction of b0 (experimental, off by default): rate-limited two-timescale updates, [0.3, 3.0]× rails, and automatic rollback-and-freeze if the estimate hugs a rail while tracking stagnates.
|
|
16
|
+
- **Vectorized UKF** — every sigma-point operation (generation, batch propagation, weighted mean/covariance rebuild) is one NumPy broadcast/einsum, no per-point Python loop: **2.2× / 6.0× / 11.2×** faster than a mathematically equivalent point-loop reference at dim_x = 2 / 8 / 32.
|
|
17
|
+
- **Dependency-free LinearMPC** — condensed QP compiled once, built-in OSQP-style ADMM solver with pre-factored KKT matrix, warm start over (z, s, y) cutting total iterations **1.9×** (3687 → 1935) on a constrained double integrator. No external solver (no OSQP/CVXPY) needed.
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install classic-controlling # or: pip install -e . from source
|
|
23
|
+
python -m pytest tests/ -v # 21 tests
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### ADRC — 5 lines to a self-tuned loop
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from classic_controlling import ADRC, FOPDTPlant
|
|
30
|
+
|
|
31
|
+
plant = FOPDTPlant(gain=1.0, time_constant=0.5, dt=0.05)
|
|
32
|
+
ctl = ADRC.auto_tune(plant, dt=0.05) # identify + tune, zero knobs
|
|
33
|
+
# (online RLS: opt-in via adaptive=True)
|
|
34
|
+
for _ in range(300):
|
|
35
|
+
u = ctl.step(r=1.0, y=plant.y)
|
|
36
|
+
plant.step(u)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The full auto-tuning pipeline (step test → FOPDT fit → tracking → b0 adapting to a mid-run gain change), from `examples/demo_autotune.py`:
|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
### UKF — vectorized sigma points
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
import numpy as np
|
|
47
|
+
from classic_controlling import UKF
|
|
48
|
+
|
|
49
|
+
def fx(sigmas, dt): # (2n+1, 4) batch in, batch out
|
|
50
|
+
out = sigmas.copy()
|
|
51
|
+
out[:, 0] += dt * sigmas[:, 2]; out[:, 1] += dt * sigmas[:, 3]
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
ukf = UKF(4, 2, 0.1, fx, hx=lambda s: s[:, :2], Q=np.eye(4)*0.01, R=np.eye(2))
|
|
55
|
+
ests = ukf.batch_filter(zs) # zs: (N, 2) position measurements
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
From `examples/demo_ukf.py` — coordinated-turn tracking, RMSE 0.30 m under 0.5 m observation noise:
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+
### LinearMPC — constrained MPC without a solver dependency
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
import numpy as np
|
|
66
|
+
from classic_controlling import LinearMPC
|
|
67
|
+
|
|
68
|
+
A = np.array([[1.0, 0.1], [0.0, 1.0]]); B = np.array([[0.005], [0.1]])
|
|
69
|
+
mpc = LinearMPC(A, B, Q=np.eye(2), R=[[0.1]], horizon=40, u_min=-1.0, u_max=1.0)
|
|
70
|
+
x = np.array([3.0, 0.0])
|
|
71
|
+
for _ in range(120):
|
|
72
|
+
x = A @ x + B @ mpc.solve(x, warm_start=True)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
From `examples/demo_mpc.py`:
|
|
76
|
+
|
|
77
|
+

|
|
78
|
+
|
|
79
|
+
## Benchmarks
|
|
80
|
+
|
|
81
|
+
### Vectorized UKF vs point-loop reference
|
|
82
|
+
|
|
83
|
+
`python benchmarks/bench_ukf.py` — 1000 predict+update steps each, mathematically equivalent implementations (Python 3.13.5 / NumPy 2.3.3, Windows):
|
|
84
|
+
|
|
85
|
+
| dim_x | vectorized (s) | point-loop (s) | speedup |
|
|
86
|
+
|------:|---------------:|---------------:|--------:|
|
|
87
|
+
| 2 | 0.0392 | 0.0855 | 2.2× |
|
|
88
|
+
| 8 | 0.0439 | 0.2623 | 6.0× |
|
|
89
|
+
| 32 | 0.1079 | 1.2066 | 11.2× |
|
|
90
|
+
|
|
91
|
+
The point-loop overhead grows linearly with `2n+1` sigma points; the vectorized path is eaten by BLAS.
|
|
92
|
+
|
|
93
|
+
### `auto_tune` blind test
|
|
94
|
+
|
|
95
|
+
Zero human parameters; the plants' true parameters are used only for assertions (`tests/test_autotune.py`, 300 closed-loop steps at dt=0.05):
|
|
96
|
+
|
|
97
|
+
| plant | model | steady-state error |
|
|
98
|
+
|---|---|---:|
|
|
99
|
+
| FOPDT | `0.5·ẏ + y = u` | 0.89% |
|
|
100
|
+
| second-order underdamped | `ζ=0.5, ωn=3` | 3.21% |
|
|
101
|
+
| nonlinear, open-loop unstable | `ÿ = -ẏ + u + 0.5y²` | 1.03% |
|
|
102
|
+
|
|
103
|
+
### MPC warm start
|
|
104
|
+
|
|
105
|
+
Constrained double integrator, horizon 40, 120 closed-loop steps (`examples/demo_mpc.py`):
|
|
106
|
+
|
|
107
|
+
| | total ADMM iters | mean iters/step |
|
|
108
|
+
|---|---:|---:|
|
|
109
|
+
| cold start | 3687 | 30.7 |
|
|
110
|
+
| warm start | 1935 | 16.1 |
|
|
111
|
+
| **speedup** | **1.9×** | **1.9×** |
|
|
112
|
+
|
|
113
|
+
### ADRC.auto_tune vs model-tuned PID
|
|
114
|
+
|
|
115
|
+
Step tracking `r=1` with a step load disturbance at t=20s (`examples/demo_adrc_vs_pid.py`; PID tuned from the *same* step-test data via Ziegler-Nichols reaction-curve rules, IMC-PI fallback when the identified delay ≈ 0):
|
|
116
|
+
|
|
117
|
+
| plant | controller | overshoot | settling (s) | disturbance recovery (s) | IAE |
|
|
118
|
+
|---|---|---:|---:|---:|---:|
|
|
119
|
+
| FOPDT | PID (IMC-PI) | 0.0% | 0.40 | 0.55 | 0.15 |
|
|
120
|
+
| FOPDT | ADRC | 0.0% | 12.45 | 6.25 | 4.12 |
|
|
121
|
+
| 2nd-order | PID (Z-N) | 6.7% | 2.05 | 1.05 | 0.51 |
|
|
122
|
+
| 2nd-order | ADRC | 0.0% | 17.05 | 6.35 | 5.06 |
|
|
123
|
+
| nonlinear | PID (Z-N) | 757% | — | ∞ | 170.86 |
|
|
124
|
+
| nonlinear | ADRC | 20.1% | 13.20 | 3.55 | 2.89 |
|
|
125
|
+
|
|
126
|
+
Honest read: on well-behaved LTI plants a model-tuned PID is faster — ADRC's auto-bandwidth trades speed for guarantees (the delay phase-margin cap is calibrated against measured divergence, not folklore). The trade: ADRC never rings, needs zero expertise, and is the only one left standing on the open-loop-unstable nonlinear plant.
|
|
127
|
+
|
|
128
|
+
## Examples
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
python examples/demo_adrc_vs_pid.py # hero comparison + metrics table
|
|
132
|
+
python examples/demo_autotune.py # auto_tune pipeline end-to-end
|
|
133
|
+
python examples/demo_mpc.py # constrained MPC + warm-start speedup
|
|
134
|
+
python examples/demo_ukf.py # coordinated-turn tracking
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
All figures are written to `docs/figures/`.
|
|
138
|
+
|
|
139
|
+
## Project layout
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
classic_controlling/ # the package
|
|
143
|
+
├── adrc.py # linear ADRC (bandwidth tuning + auto_tune + RLS b0 adaptation)
|
|
144
|
+
├── autotune.py # step-response FOPDT identification + automatic bandwidth selection
|
|
145
|
+
├── sim.py # virtual plants (FOPDT / 2nd-order / nonlinear), reset()/step(u) protocol
|
|
146
|
+
├── ukf.py # vectorized UKF
|
|
147
|
+
└── mpc.py # LinearMPC (condensed QP + built-in ADMM + warm start)
|
|
148
|
+
tests/ # 21 pytest tests (blind auto-tune assertions, SLSQP cross-check, ...)
|
|
149
|
+
benchmarks/bench_ukf.py # UKF vectorized vs point-loop benchmark
|
|
150
|
+
examples/ # runnable demos -> docs/figures/*.png
|
|
151
|
+
research/ # background research notes (Chinese)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 中文简介
|
|
155
|
+
|
|
156
|
+
三个经典控制/估计算法的高质量纯 NumPy 实现:
|
|
157
|
+
|
|
158
|
+
- **自整定 ADRC**:`ADRC.auto_tune(plant, dt)` 一键完成阶跃辨识 → FOPDT 拟合 → 带宽自动选择(时滞相位裕量帽 + 采样硬约束),全程零人工调参;一阶/二阶/非线性三个对象盲测稳态误差 0.9% / 3.2% / 1.0%。在线 RLS 修正 b0 为 experimental 可选项(默认关闭):限速双时间尺度更新 + [0.3, 3.0]× 护栏 + 贴护栏停滞自动回退冻结;
|
|
159
|
+
- **向量化 UKF**:sigma 点操作全部 NumPy 广播一次完成,无逐点循环,相比等价逐点实现加速 2.2× / 6.0× / 11.2×(dim 2/8/32);
|
|
160
|
+
- **轻量线性 MPC**:稠密式 QP 编译一次 + 内置 OSQP 风格 ADMM 求解器 + (z, s, y) 热启动(总迭代数减 1.9×),零外部求解器依赖。
|
|
161
|
+
|
|
162
|
+
快速开始:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
pip install -e .
|
|
166
|
+
python -m pytest tests/ -v # 21 项测试
|
|
167
|
+
python examples/demo_adrc_vs_pid.py # 核心对比演示,出图到 docs/figures/
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Roadmap
|
|
171
|
+
|
|
172
|
+
- Upstream the vectorized UKF sigma-point path as a PR to `filterpy`
|
|
173
|
+
- Optional Numba backend for the MPC ADMM iterations (per-iteration cost → microseconds)
|
|
174
|
+
- More virtual plant types (integrating, oscillatory, MIMO) and ADRC tracking of time-varying references
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT — see [LICENSE](LICENSE). Contributions welcome: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""classic_controlling: 三个经典控制/估计算法的高质量实现。
|
|
2
|
+
|
|
3
|
+
- ADRC : 线性自抗扰控制(带宽法 LADRC)
|
|
4
|
+
- UKF : 向量化无迹卡尔曼滤波
|
|
5
|
+
- LinearMPC : 轻量线性模型预测控制(稠密式 QP + 内置 ADMM 求解器 + 热启动)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from .adrc import ADRC
|
|
9
|
+
from .ukf import UKF
|
|
10
|
+
from .mpc import LinearMPC
|
|
11
|
+
from .sim import FOPDTPlant, SecondOrderPlant, NonlinearPlant
|
|
12
|
+
from .autotune import fit_fopdt, auto_bandwidth
|
|
13
|
+
|
|
14
|
+
__all__ = ["ADRC", "UKF", "LinearMPC",
|
|
15
|
+
"FOPDTPlant", "SecondOrderPlant", "NonlinearPlant",
|
|
16
|
+
"fit_fopdt", "auto_bandwidth"]
|
|
17
|
+
__version__ = "0.1.0"
|