DirectSD-Python 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.
- directsd_python-0.1.0/DirectSD_Python.egg-info/PKG-INFO +450 -0
- directsd_python-0.1.0/DirectSD_Python.egg-info/SOURCES.txt +44 -0
- directsd_python-0.1.0/DirectSD_Python.egg-info/dependency_links.txt +1 -0
- directsd_python-0.1.0/DirectSD_Python.egg-info/requires.txt +20 -0
- directsd_python-0.1.0/DirectSD_Python.egg-info/top_level.txt +1 -0
- directsd_python-0.1.0/LICENSE +36 -0
- directsd_python-0.1.0/PKG-INFO +450 -0
- directsd_python-0.1.0/README.md +408 -0
- directsd_python-0.1.0/directsd/__init__.py +141 -0
- directsd_python-0.1.0/directsd/analysis/__init__.py +4 -0
- directsd_python-0.1.0/directsd/analysis/charpol.py +89 -0
- directsd_python-0.1.0/directsd/analysis/errors.py +185 -0
- directsd_python-0.1.0/directsd/analysis/norms.py +630 -0
- directsd_python-0.1.0/directsd/design/__init__.py +9 -0
- directsd_python-0.1.0/directsd/design/convex.py +620 -0
- directsd_python-0.1.0/directsd/design/lifting.py +403 -0
- directsd_python-0.1.0/directsd/design/polynomial.py +5925 -0
- directsd_python-0.1.0/directsd/examples/__init__.py +0 -0
- directsd_python-0.1.0/directsd/examples/_common.py +36 -0
- directsd_python-0.1.0/directsd/examples/demos.py +1161 -0
- directsd_python-0.1.0/directsd/examples/examples.py +296 -0
- directsd_python-0.1.0/directsd/examples/help_examples.py +1043 -0
- directsd_python-0.1.0/directsd/glopt/__init__.py +4 -0
- directsd_python-0.1.0/directsd/glopt/advanced.py +1658 -0
- directsd_python-0.1.0/directsd/glopt/optimize.py +420 -0
- directsd_python-0.1.0/directsd/linalg/__init__.py +3 -0
- directsd_python-0.1.0/directsd/linalg/linsys.py +66 -0
- directsd_python-0.1.0/directsd/linalg/matrices.py +156 -0
- directsd_python-0.1.0/directsd/linalg/minreal.py +425 -0
- directsd_python-0.1.0/directsd/linalg/riccati.py +197 -0
- directsd_python-0.1.0/directsd/polynomial/__init__.py +11 -0
- directsd_python-0.1.0/directsd/polynomial/diophantine.py +426 -0
- directsd_python-0.1.0/directsd/polynomial/operations.py +247 -0
- directsd_python-0.1.0/directsd/polynomial/poln.py +742 -0
- directsd_python-0.1.0/directsd/polynomial/spectral.py +368 -0
- directsd_python-0.1.0/directsd/polynomial/transforms.py +124 -0
- directsd_python-0.1.0/directsd/polynomial/utils.py +449 -0
- directsd_python-0.1.0/directsd/sspace/__init__.py +4 -0
- directsd_python-0.1.0/directsd/sspace/design.py +1083 -0
- directsd_python-0.1.0/directsd/sspace/plant.py +198 -0
- directsd_python-0.1.0/directsd/tf/__init__.py +9 -0
- directsd_python-0.1.0/directsd/tf/interconnect.py +105 -0
- directsd_python-0.1.0/directsd/zpk/__init__.py +1 -0
- directsd_python-0.1.0/directsd/zpk/zpk.py +400 -0
- directsd_python-0.1.0/pyproject.toml +59 -0
- directsd_python-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: DirectSD-Python
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Direct Sampled-Data Control Systems Toolbox — Python port of MATLAB DirectSD v3.0
|
|
5
|
+
Author-email: Roozbeh Dargahi <roozbehdargahi.0x7b4@gmail.com>
|
|
6
|
+
License: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://github.com/RooDK0x7b4/DirectSD-Python
|
|
8
|
+
Project-URL: Repository, https://github.com/RooDK0x7b4/DirectSD-Python
|
|
9
|
+
Project-URL: Issues, https://github.com/RooDK0x7b4/DirectSD-Python/issues
|
|
10
|
+
Keywords: control-systems,sampled-data,h2-control,h-infinity,diophantine-equations,polynomial-methods,digital-control
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.8
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: numpy>=1.21
|
|
27
|
+
Requires-Dist: scipy>=1.7
|
|
28
|
+
Provides-Extra: control
|
|
29
|
+
Requires-Dist: control>=0.9; extra == "control"
|
|
30
|
+
Provides-Extra: plot
|
|
31
|
+
Requires-Dist: matplotlib>=3.4; extra == "plot"
|
|
32
|
+
Provides-Extra: convex
|
|
33
|
+
Requires-Dist: cvxpy>=1.3; extra == "convex"
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
36
|
+
Requires-Dist: matplotlib>=3.4; extra == "dev"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: control>=0.9; extra == "all"
|
|
39
|
+
Requires-Dist: matplotlib>=3.4; extra == "all"
|
|
40
|
+
Requires-Dist: cvxpy>=1.3; extra == "all"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
# DirectSD — Python Package
|
|
44
|
+
|
|
45
|
+
[github.com/RooDK0x7b4/DirectSD-Python](https://github.com/RooDK0x7b4/DirectSD-Python)
|
|
46
|
+
|
|
47
|
+
Python port of the **DirectSD MATLAB Toolbox v3.0** by Konstantin Polyakov (1995–2006).
|
|
48
|
+
|
|
49
|
+
DirectSD is a toolbox for analysis and design of **sampled-data (hybrid) control systems** using:
|
|
50
|
+
- Polynomial (Diophantine-equation) methods
|
|
51
|
+
- Lifting / FR-operator approach (exact inter-sample modelling)
|
|
52
|
+
- State-space (LQR / Kalman / Riccati ARE) methods
|
|
53
|
+
- H2 and H∞ optimal control
|
|
54
|
+
- Global optimization (Strongin information algorithm, SA simplex, Hilbert-curve search)
|
|
55
|
+
- **L1 / L∞ convex synthesis** via Youla parameterisation (requires `cvxpy`)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install numpy scipy # runtime dependencies
|
|
63
|
+
pip install -e . # development install from source
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Or with optional extras:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install -e ".[dev]" # adds pytest + matplotlib
|
|
70
|
+
pip install -e ".[convex]" # adds cvxpy for L1/L∞ convex synthesis
|
|
71
|
+
pip install -e ".[control]" # adds python-control interoperability
|
|
72
|
+
pip install -e ".[all]" # installs all optional dependencies
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Package Structure
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
directsd/
|
|
81
|
+
├── __init__.py # flat public API — import everything from here
|
|
82
|
+
│
|
|
83
|
+
├── polynomial/ # Poln class, operations, spectral factorisation, transforms
|
|
84
|
+
│ ├── poln.py # Poln — the @poln OOP class equivalent
|
|
85
|
+
│ ├── operations.py # compat, deg, gcd, coprime, triple, factor,
|
|
86
|
+
│ │ # striplz, recip, vec, dioph, dioph2
|
|
87
|
+
│ ├── spectral.py # sfactor, sfactfft
|
|
88
|
+
│ ├── transforms.py # dtfm (ZOH discrete Laplace), ztrm (modified Z)
|
|
89
|
+
│ └── utils.py # bilintr, improper, diophsys, tf2nd,
|
|
90
|
+
│ # separtf, sumzpk, bilinss
|
|
91
|
+
│
|
|
92
|
+
├── zpk/ # Zpk class (root-list zero-pole-gain representation)
|
|
93
|
+
│ └── zpk.py
|
|
94
|
+
│
|
|
95
|
+
├── linalg/ # Linear algebra utilities
|
|
96
|
+
│ ├── matrices.py # toep, hank, givens, house, lyap, dlyap
|
|
97
|
+
│ └── linsys.py # linsys (QR / SVD with iterative refinement)
|
|
98
|
+
│
|
|
99
|
+
├── analysis/ # Sampled-data system analysis
|
|
100
|
+
│ ├── norms.py # sdh2norm, sdhinorm, dinfnorm, dahinorm,
|
|
101
|
+
│ │ # h2norm_ct, hinfnorm_ct
|
|
102
|
+
│ │ # ── all via Lyapunov / Hamiltonian ARE,
|
|
103
|
+
│ │ # no frequency gridding
|
|
104
|
+
│ ├── charpol.py # charpol, sdmargin
|
|
105
|
+
│ └── errors.py # quaderr, sdl2err, sd2doferr
|
|
106
|
+
│
|
|
107
|
+
├── design/ # Controller design
|
|
108
|
+
│ ├── lifting.py # lift_h2, lift_l2, compute_gamma
|
|
109
|
+
│ │ # ── Van Loan exact inter-sample lifting
|
|
110
|
+
│ │ # (FR-operator / Chen-Francis method)
|
|
111
|
+
│ ├── polynomial.py # ch2, sdh2, sdl2, dhinf, sd2dof, polquad
|
|
112
|
+
│ └── convex.py # sdl1_reg, sd_mixed_h2_l1, sd_constrained,
|
|
113
|
+
│ # sdl1norm, youla_basis [requires cvxpy]
|
|
114
|
+
│
|
|
115
|
+
├── sspace/ # State-space design
|
|
116
|
+
│ ├── plant.py # GeneralizedPlant class (augmented plant representation)
|
|
117
|
+
│ └── design.py # h2reg, hinfreg, sdh2reg, sdhinfreg,
|
|
118
|
+
│ # sdfast, separss
|
|
119
|
+
│
|
|
120
|
+
├── tf/ # Block-diagram arithmetic
|
|
121
|
+
│ ├── interconnect.py # mul, neg, add, feedback, nd, to_lti
|
|
122
|
+
│ └── __init__.py # re-exports all interconnect functions
|
|
123
|
+
│
|
|
124
|
+
├── glopt/ # Global optimization
|
|
125
|
+
│ ├── optimize.py # neldermead, simanneal, randsearch,
|
|
126
|
+
│ │ # crandsearch, dual_annealing, sector, banana
|
|
127
|
+
│ └── advanced.py # Full dsdglopt port: updateopt, uniproj, u2range,
|
|
128
|
+
│ # randbeta, randgamma, sa_testfun,
|
|
129
|
+
│ # val2bin, bin2val, coord2hilb, hilb2coord,
|
|
130
|
+
│ # r2range, r1range, admproj, par2cp, cp2par,
|
|
131
|
+
│ # guesspoles, k2ksi, go_par2k,
|
|
132
|
+
│ # f_sdh2p, f_sdl2p, go_sdh2p, go_sdl2p,
|
|
133
|
+
│ # sasimplex, arandsearch,
|
|
134
|
+
│ # infglob, infglobc, optglob, optglobc
|
|
135
|
+
│
|
|
136
|
+
├── examples/ # demos.py (25 MATLAB demo ports), help_examples.py
|
|
137
|
+
│ # (26 dsd_help.chm worked examples), examples.py
|
|
138
|
+
│ # (11 core-utility examples), _common.py (shared helpers)
|
|
139
|
+
│
|
|
140
|
+
└── tests/
|
|
141
|
+
└── test_directsd.py # 228 pytest tests (209 core + 19 convex,
|
|
142
|
+
# convex skipped automatically without cvxpy)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Quick Start
|
|
148
|
+
|
|
149
|
+
### Polynomial arithmetic
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from directsd import Poln, s_var, gcd, sfactor, dioph
|
|
153
|
+
|
|
154
|
+
s = s_var()
|
|
155
|
+
p = Poln([1, 3, 2], 's') # s² + 3s + 2
|
|
156
|
+
q = Poln([1, 1], 's') # s + 1
|
|
157
|
+
|
|
158
|
+
print(p * q) # s³ + 4s² + 5s + 2
|
|
159
|
+
print(p.roots) # [-2. -1.]
|
|
160
|
+
print(gcd(p, q)) # s + 1
|
|
161
|
+
|
|
162
|
+
# Spectral factorization: R(s) = N(s)·N(-s) → find N
|
|
163
|
+
R = Poln([-1, 0, 1], 's') # 1 − s² (Hermitian)
|
|
164
|
+
fs, _ = sfactor(R)
|
|
165
|
+
print(fs) # s + 1
|
|
166
|
+
|
|
167
|
+
# Diophantine equation: X·A + Y·B = C
|
|
168
|
+
x, y, err, cond = dioph(Poln([1,1],'s'), Poln([1,0],'s'), Poln([1],'s'))
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Lifting → H2 design (recommended workflow)
|
|
172
|
+
|
|
173
|
+
```python
|
|
174
|
+
import scipy.signal as sig
|
|
175
|
+
from directsd import lift_h2, h2reg, compute_gamma
|
|
176
|
+
import numpy as np
|
|
177
|
+
|
|
178
|
+
# Continuous-time generalized plant (standard 2×2 form)
|
|
179
|
+
plant = sig.StateSpace(
|
|
180
|
+
np.array([[-1., 0.5], [0., -2.]]),
|
|
181
|
+
np.array([[1., 0.], [0., 1.]]),
|
|
182
|
+
np.array([[1., 0.], [0., 1.]]),
|
|
183
|
+
np.array([[0., 1.], [1., 0.]])
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
T = 0.1 # sampling period
|
|
187
|
+
|
|
188
|
+
# Step 1 — lift to exact discrete equivalent (Van Loan / FR-operator)
|
|
189
|
+
P_lifted, gamma, _ = lift_h2(plant, T)
|
|
190
|
+
print(f"Lifted plant: {P_lifted.A.shape}, γ = {gamma:.4f}")
|
|
191
|
+
|
|
192
|
+
# Step 2 — H2-optimal controller on lifted plant
|
|
193
|
+
K, h2n = h2reg(P_lifted, n_meas=1, n_ctrl=1)
|
|
194
|
+
|
|
195
|
+
# Step 3 — exact total H2 cost
|
|
196
|
+
total_cost = np.sqrt(h2n**2 + gamma)
|
|
197
|
+
print(f"Optimal H2 cost: {total_cost:.4f}")
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Norms (algebraic, no frequency gridding)
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from directsd import sdh2norm, sdhinorm, h2norm_ct, hinfnorm_ct, dinfnorm
|
|
204
|
+
import scipy.signal as sig
|
|
205
|
+
|
|
206
|
+
plant = sig.lti([1], [1, 1])
|
|
207
|
+
|
|
208
|
+
# Continuous-time norms
|
|
209
|
+
print(h2norm_ct(plant)) # 0.7071 (= 1/√2)
|
|
210
|
+
gamma, w = hinfnorm_ct(plant) # 1.0 at ω=0
|
|
211
|
+
print(gamma, w)
|
|
212
|
+
|
|
213
|
+
# Sampled-data norms
|
|
214
|
+
K = ([0.5], [1.0])
|
|
215
|
+
print(sdh2norm(plant, K, T=0.1))
|
|
216
|
+
gamma, w = sdhinorm(plant, K, T=0.1)
|
|
217
|
+
print(gamma, w)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Stability analysis
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from directsd import charpol, sdmargin
|
|
224
|
+
import scipy.signal as sig
|
|
225
|
+
|
|
226
|
+
plant = sig.lti([1], [1, 1])
|
|
227
|
+
K = ([1.0], [1.0])
|
|
228
|
+
T = 0.1
|
|
229
|
+
|
|
230
|
+
delta = charpol(plant, K, T) # characteristic polynomial coefficients
|
|
231
|
+
margin, poles = sdmargin(plant, K, T)
|
|
232
|
+
print(f"Stability margin: {margin:.4f}") # positive = stable
|
|
233
|
+
print(f"Closed-loop poles: {poles}")
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Global optimization
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from directsd import neldermead, dual_annealing, simanneal, sector, banana
|
|
240
|
+
|
|
241
|
+
# Nelder-Mead (deterministic)
|
|
242
|
+
x, f, n = neldermead(banana, [-1.2, 1.0], tol=1e-8)
|
|
243
|
+
print(f"Banana min: x={x}, f={f:.2e}")
|
|
244
|
+
|
|
245
|
+
# Dual annealing (scipy hybrid, faster and more robust than simanneal)
|
|
246
|
+
x, f, res = dual_annealing(banana, [(-3,3),(-3,3)], seed=42)
|
|
247
|
+
print(f"Dual SA min: x={x}, f={f:.2e}, evals={res.nfev}")
|
|
248
|
+
|
|
249
|
+
# Stability sector
|
|
250
|
+
alpha, beta = sector([-1+2j, -1-2j, -3+0j])
|
|
251
|
+
print(f"Degree of stability α={alpha}, oscillation β={beta}")
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Advanced global optimization (dsdglopt full port)
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
from directsd import (
|
|
258
|
+
infglob, optglob, sasimplex, arandsearch,
|
|
259
|
+
par2cp, cp2par, admproj, guesspoles,
|
|
260
|
+
coord2hilb, hilb2coord, sa_testfun, randbeta, randgamma,
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Strongin information algorithm — 1D Lipschitz minimisation
|
|
264
|
+
f = lambda x: (x - 0.3)**2 + 0.1*np.sin(20*x)
|
|
265
|
+
x_best, z_best, n_iter, trace = infglob(f, {'maxIter': 300, 'r': 2.5})
|
|
266
|
+
|
|
267
|
+
# Multi-run with zooming
|
|
268
|
+
x_best, z_best, coef, loops, trace = optglob(f, {'maxLoop': 10, 'maxIter': 100})
|
|
269
|
+
|
|
270
|
+
# SA + Nelder-Mead hybrid
|
|
271
|
+
x, y, nevals = sasimplex(sa_testfun, [0.5, 0.5], {'startTemp': 5.0, 'maxFunEvals': 5000})
|
|
272
|
+
|
|
273
|
+
# Hilbert space-filling curve (N-D ↔ 1D)
|
|
274
|
+
coord = np.array([0.3, 0.7, 0.5])
|
|
275
|
+
x1d = coord2hilb(coord, precision=8) # 3D → scalar in [0,1]
|
|
276
|
+
back = hilb2coord(x1d, dimensions=3, precision=8)
|
|
277
|
+
|
|
278
|
+
# Stability-sector polynomial parameterisation
|
|
279
|
+
Delta, DeltaZ, poles = par2cp([0.4, 0.6, 0.5], alpha=0.2, beta=np.inf)
|
|
280
|
+
rho = cp2par(DeltaZ, alpha=0.2, beta=np.inf) # inverse
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Convex synthesis — L1 and mixed H2/L1 (requires `pip install cvxpy`)
|
|
284
|
+
|
|
285
|
+
The Youla (Q) parameterisation turns all stabilising controllers into an
|
|
286
|
+
affine family, making L1 and mixed-norm problems convex.
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
import numpy as np
|
|
290
|
+
import scipy.signal as sig
|
|
291
|
+
from directsd import lift_h2, sdl1_reg, sd_mixed_h2_l1, sd_constrained, sdl1norm
|
|
292
|
+
|
|
293
|
+
# Step 1 — continuous-time generalised plant (standard 2×2 form)
|
|
294
|
+
ct_plant = sig.StateSpace(
|
|
295
|
+
np.array([[-1.]]),
|
|
296
|
+
np.array([[1., 1.]]),
|
|
297
|
+
np.array([[1.], [1.]]),
|
|
298
|
+
np.array([[0., 1.], [1., 0.]])
|
|
299
|
+
)
|
|
300
|
+
T = 0.1
|
|
301
|
+
|
|
302
|
+
# Step 2 — lift to exact discrete equivalent
|
|
303
|
+
P_lifted, _, _ = lift_h2(ct_plant, T)
|
|
304
|
+
|
|
305
|
+
# L1-optimal controller (minimises peak output amplitude)
|
|
306
|
+
K_l1, l1_norm, Q_fir, info = sdl1_reg(P_lifted, N_fir=20)
|
|
307
|
+
print(f"L1-norm (peak-to-peak gain): {l1_norm:.4f} [{info['status']}]")
|
|
308
|
+
|
|
309
|
+
# Mixed H2/L1: minimise H2 subject to L1 ≤ bound
|
|
310
|
+
K_mix, cost, _, _ = sd_mixed_h2_l1(P_lifted, N_fir=20, l1_bound=5.0)
|
|
311
|
+
print(f"H2 cost: {cost['h2']:.4f}, L1: {cost['l1']:.4f}")
|
|
312
|
+
|
|
313
|
+
# Hard envelope constraint
|
|
314
|
+
lo = -1.5 * np.ones((15, 1))
|
|
315
|
+
hi = 1.5 * np.ones((15, 1))
|
|
316
|
+
K_con, achieved, _, _ = sd_constrained(
|
|
317
|
+
P_lifted, N_fir=20, objective='h2', envelope=(lo, hi)
|
|
318
|
+
)
|
|
319
|
+
print(f"Constrained H2: {achieved['h2']:.4f}, linf: {achieved['linf']:.4f}")
|
|
320
|
+
|
|
321
|
+
# L1-norm analysis of any existing closed loop (no cvxpy needed)
|
|
322
|
+
l1, h = sdl1norm(sig.lti([1], [1, 2, 1]), ([0.5], [1.0]), T=0.1)
|
|
323
|
+
print(f"Closed-loop L1-norm: {l1:.4f}")
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## MATLAB → Python mapping
|
|
329
|
+
|
|
330
|
+
| MATLAB | Python |
|
|
331
|
+
|--------|--------|
|
|
332
|
+
| `poln(a,'s')` | `Poln(a, 's')` |
|
|
333
|
+
| `s`, `z`, `p`, `q`, `d` | `s_var()`, `z_var()`, … |
|
|
334
|
+
| `gcd(a,b)` | `gcd(a, b)` |
|
|
335
|
+
| `coprime(a,b)` | `coprime(a, b)` |
|
|
336
|
+
| `factor(p,'s')` | `factor(p, 's')` |
|
|
337
|
+
| `sfactor(p)` | `sfactor(p)` |
|
|
338
|
+
| `sfactfft(p)` | `sfactfft(p)` |
|
|
339
|
+
| `dioph(a,b,c)` | `dioph(a, b, c)` |
|
|
340
|
+
| `dtfm(G,T,0)` | `dtfm(G, T)` |
|
|
341
|
+
| `ztrm(G,T,mu)` | `ztrm(G, T, mu)` |
|
|
342
|
+
| `bilintr(F,'tustin',T)` | `bilintr(F, 'tustin', T)` |
|
|
343
|
+
| `improper(R)` | `improper(R)` |
|
|
344
|
+
| `toep(a,r,c)` | `toep(a, r, c)` |
|
|
345
|
+
| `linsys(A,B,'svd')` | `linsys(A, B, method='svd')` |
|
|
346
|
+
| `sdgh2mod(sys,T)` | `lift_h2(plant_ss, T)` |
|
|
347
|
+
| `sdh2simple(sys,T)` | `lift_l2(plant_ss, T)` |
|
|
348
|
+
| `charpol(sys,K)` | `charpol(plant, K, T)` |
|
|
349
|
+
| `sdmargin(sys,K)` | `sdmargin(plant, K, T)` |
|
|
350
|
+
| `sdh2norm(sys,K)` | `sdh2norm(plant, K, T)` |
|
|
351
|
+
| `sdhinorm(sys,K)` | `sdhinorm(plant, K, T)` |
|
|
352
|
+
| `dinfnorm(sys)` | `dinfnorm(sys)` |
|
|
353
|
+
| `quaderr(A,B,E,M)` | `quaderr(plant_cl, T)` |
|
|
354
|
+
| `sdl2err(sys,K)` | `sdl2err(plant, K, T)` |
|
|
355
|
+
| `ch2(sys)` | `ch2(plant)` |
|
|
356
|
+
| `sdh2(sys,T)` | `sdh2(plant, T)` |
|
|
357
|
+
| `sdl2(sys,T)` | `sdl2(plant, T)` |
|
|
358
|
+
| `dhinf(plant_d)` | `dhinf(plant_d)` |
|
|
359
|
+
| `sd2dof(sys,K,T)` | `sd2dof(plant, K_fb, T)` |
|
|
360
|
+
| `h2reg(sys,1,1)` | `h2reg(sys, n_meas=1, n_ctrl=1)` |
|
|
361
|
+
| `hinfreg(sys,1,1)` | `hinfreg(sys, n_meas=1, n_ctrl=1)` |
|
|
362
|
+
| `sdh2reg(sys,T)` | `sdh2reg(plant, T)` |
|
|
363
|
+
| `sdhinfreg(sys,T)` | `sdhinfreg(plant, T)` |
|
|
364
|
+
| `sdfast(sys,T)` | `sdfast(plant, T)` |
|
|
365
|
+
| `neldermead(f,x0)` | `neldermead(f, x0)` |
|
|
366
|
+
| `simanneal(f,x0,opt)` | `simanneal(f, x0, options=opt)` |
|
|
367
|
+
| `sector(poles)` | `sector(poles)` |
|
|
368
|
+
| `sasimplex(f,x0,opt)` | `sasimplex(f, x0, options=opt)` |
|
|
369
|
+
| `arandsearch(f,x,opt)` | `arandsearch(f, x0, options=opt)` |
|
|
370
|
+
| `infglob(f,opt)` | `infglob(f, options=opt)` |
|
|
371
|
+
| `infglobc(f,opt)` | `infglobc(f, options=opt)` |
|
|
372
|
+
| `optglob(f,opt)` | `optglob(f, options=opt)` |
|
|
373
|
+
| `optglobc(f,opt)` | `optglobc(f, options=opt)` |
|
|
374
|
+
| `par2cp(rho,alpha,beta)` | `par2cp(rho, alpha, beta)` |
|
|
375
|
+
| `cp2par(DeltaZ,alpha,beta)` | `cp2par(DeltaZ, alpha, beta)` |
|
|
376
|
+
| `admproj(p,alpha,beta,dom,T)` | `admproj(p, alpha, beta, dom, T)` |
|
|
377
|
+
| `guesspoles(poles,n)` | `guesspoles(poles, n_poles)` |
|
|
378
|
+
| `r1range(r2,Ea,beta,sh)` | `r1range(r2, Ea, beta, shifted)` |
|
|
379
|
+
| `r2range(Ea,Eb,sh)` | `r2range(Ea, Eb, shifted)` |
|
|
380
|
+
| `coord2hilb(c,p)` | `coord2hilb(coord, precision)` |
|
|
381
|
+
| `hilb2coord(x,d,p)` | `hilb2coord(x, dimensions, precision)` |
|
|
382
|
+
| `val2bin(x,nf)` | `val2bin(x, nfrac)` |
|
|
383
|
+
| `bin2val(bi,bf)` | `bin2val(bint, bfrac)` |
|
|
384
|
+
| `k2ksi(sys,K,dK0)` | `k2ksi(plant, K, dK0, T)` |
|
|
385
|
+
| `go_par2k(coef)` | `go_par2k(coef, ctx)` |
|
|
386
|
+
| `f_sdh2p(coef)` | `f_sdh2p(coef, ctx)` |
|
|
387
|
+
| `f_sdl2p(coef)` | `f_sdl2p(coef, ctx)` |
|
|
388
|
+
| `go_sdh2p(x)` | `go_sdh2p(x, ctx)` |
|
|
389
|
+
| `go_sdl2p(x)` | `go_sdl2p(x, ctx)` |
|
|
390
|
+
| `randbeta(a,b,r,c)` | `randbeta(a, b, rows, cols)` |
|
|
391
|
+
| `randgamma(a,r,c)` | `randgamma(a, rows, cols)` |
|
|
392
|
+
| `sa_testfun(x)` | `sa_testfun(x)` |
|
|
393
|
+
| `updateopt(opt,new)` | `updateopt(options, opt)` |
|
|
394
|
+
| `uniproj(x)` | `uniproj(x)` |
|
|
395
|
+
| `u2range(u,lo,hi)` | `u2range(u, lo, hi)` |
|
|
396
|
+
| `sdnorm(sys,K,'l1')` | `sdl1norm(plant, K, T)` — L1/peak-to-peak norm analysis |
|
|
397
|
+
| *(no MATLAB equivalent)* | `sdl1_reg(P_lifted, N_fir)` — L1-optimal synthesis |
|
|
398
|
+
| *(no MATLAB equivalent)* | `sd_mixed_h2_l1(P_lifted, N_fir, l1_bound=…)` — mixed H2/L1 |
|
|
399
|
+
| *(no MATLAB equivalent)* | `sd_constrained(P_lifted, N_fir, envelope=(lo,hi))` — hard constraints |
|
|
400
|
+
| *(no MATLAB equivalent)* | `youla_basis(P_lifted, K0, N_fir)` — raw Youla maps for custom problems |
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## Key design decisions
|
|
405
|
+
|
|
406
|
+
**Lifting is first-class.** `design/lifting.py` implements the exact Van Loan
|
|
407
|
+
matrix-exponential method (Hagiwara-Araki 1995, Chen-Francis 1995).
|
|
408
|
+
Unlike simple ZOH discretization, the lifted plant accounts for inter-sample
|
|
409
|
+
signal energy — the `gamma` term captures what a continuous-time H2 design
|
|
410
|
+
recovers that a purely discrete design misses.
|
|
411
|
+
|
|
412
|
+
**No frequency gridding for norms.** All norm functions in `analysis/norms.py`
|
|
413
|
+
use Lyapunov equations or Hamiltonian bisection. This eliminates
|
|
414
|
+
`BadCoefficients` warnings from ill-conditioned transfer functions and gives
|
|
415
|
+
machine-precision results even for systems with integrators or sharp resonances.
|
|
416
|
+
|
|
417
|
+
**SVD-based Diophantine solver.** `dioph()` uses `scipy.linalg.lstsq` instead
|
|
418
|
+
of a custom QR solver, making it robust to rank-deficient Sylvester matrices
|
|
419
|
+
that arise when the plant has repeated or near-common poles.
|
|
420
|
+
|
|
421
|
+
**Convex synthesis goes beyond the MATLAB toolbox.** `design/convex.py`
|
|
422
|
+
implements L1 (peak-to-peak) optimal control and mixed H2/L1 design via the
|
|
423
|
+
Youla Q-parameterisation — a capability not present in the original MATLAB
|
|
424
|
+
DirectSD toolbox. All stabilising controllers are parameterised as
|
|
425
|
+
`K = K0 + Δ(Q)` where Q is a stable FIR sequence, reducing the synthesis
|
|
426
|
+
to a Linear Programme solved by CVXPY. This is an optional dependency:
|
|
427
|
+
the rest of the package works without it.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## Notes
|
|
432
|
+
|
|
433
|
+
- Simulink `.mdl` files (`dsdmodel/`) have no Python equivalent; use `scipy.signal.lsim` for simulation.
|
|
434
|
+
- The `@lti`, `@tf`, `@zpk` MATLAB overrides are replaced throughout by `scipy.signal`.
|
|
435
|
+
- All functions accept `scipy.signal.lti`, `scipy.signal.dlti`, `(num, den)` tuples, and `scipy.signal.StateSpace` objects interchangeably.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## How to Cite
|
|
440
|
+
|
|
441
|
+
If you use this package in your research, please cite it — see
|
|
442
|
+
[`CITATION.cff`](CITATION.cff), or use GitHub's "Cite this repository" button
|
|
443
|
+
on the repo page.
|
|
444
|
+
|
|
445
|
+
## License
|
|
446
|
+
|
|
447
|
+
This Python port is licensed under the [BSD 3-Clause License](LICENSE) ©
|
|
448
|
+
2026 Roozbeh Dargahi. The original MATLAB toolbox is © 1995–2006 Konstantin
|
|
449
|
+
Polyakov; this license covers only the Python implementation in this
|
|
450
|
+
repository, not the original MATLAB source.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
DirectSD_Python.egg-info/PKG-INFO
|
|
5
|
+
DirectSD_Python.egg-info/SOURCES.txt
|
|
6
|
+
DirectSD_Python.egg-info/dependency_links.txt
|
|
7
|
+
DirectSD_Python.egg-info/requires.txt
|
|
8
|
+
DirectSD_Python.egg-info/top_level.txt
|
|
9
|
+
directsd/__init__.py
|
|
10
|
+
directsd/analysis/__init__.py
|
|
11
|
+
directsd/analysis/charpol.py
|
|
12
|
+
directsd/analysis/errors.py
|
|
13
|
+
directsd/analysis/norms.py
|
|
14
|
+
directsd/design/__init__.py
|
|
15
|
+
directsd/design/convex.py
|
|
16
|
+
directsd/design/lifting.py
|
|
17
|
+
directsd/design/polynomial.py
|
|
18
|
+
directsd/examples/__init__.py
|
|
19
|
+
directsd/examples/_common.py
|
|
20
|
+
directsd/examples/demos.py
|
|
21
|
+
directsd/examples/examples.py
|
|
22
|
+
directsd/examples/help_examples.py
|
|
23
|
+
directsd/glopt/__init__.py
|
|
24
|
+
directsd/glopt/advanced.py
|
|
25
|
+
directsd/glopt/optimize.py
|
|
26
|
+
directsd/linalg/__init__.py
|
|
27
|
+
directsd/linalg/linsys.py
|
|
28
|
+
directsd/linalg/matrices.py
|
|
29
|
+
directsd/linalg/minreal.py
|
|
30
|
+
directsd/linalg/riccati.py
|
|
31
|
+
directsd/polynomial/__init__.py
|
|
32
|
+
directsd/polynomial/diophantine.py
|
|
33
|
+
directsd/polynomial/operations.py
|
|
34
|
+
directsd/polynomial/poln.py
|
|
35
|
+
directsd/polynomial/spectral.py
|
|
36
|
+
directsd/polynomial/transforms.py
|
|
37
|
+
directsd/polynomial/utils.py
|
|
38
|
+
directsd/sspace/__init__.py
|
|
39
|
+
directsd/sspace/design.py
|
|
40
|
+
directsd/sspace/plant.py
|
|
41
|
+
directsd/tf/__init__.py
|
|
42
|
+
directsd/tf/interconnect.py
|
|
43
|
+
directsd/zpk/__init__.py
|
|
44
|
+
directsd/zpk/zpk.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
directsd
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Roozbeh Dargahi
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
This is a Python port of algorithms and function structure from the DirectSD
|
|
33
|
+
MATLAB Toolbox v3.0 (Copyright (c) 1995-2006 Konstantin Polyakov). The
|
|
34
|
+
original MATLAB source is not distributed with this package and is not
|
|
35
|
+
covered by the license above; this license applies only to the Python
|
|
36
|
+
implementation in this repository.
|