DirectSD-Python 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.
- directsd/__init__.py +141 -0
- directsd/analysis/__init__.py +4 -0
- directsd/analysis/charpol.py +89 -0
- directsd/analysis/errors.py +185 -0
- directsd/analysis/norms.py +630 -0
- directsd/design/__init__.py +9 -0
- directsd/design/convex.py +620 -0
- directsd/design/lifting.py +403 -0
- directsd/design/polynomial.py +5925 -0
- directsd/examples/__init__.py +0 -0
- directsd/examples/_common.py +36 -0
- directsd/examples/demos.py +1161 -0
- directsd/examples/examples.py +296 -0
- directsd/examples/help_examples.py +1043 -0
- directsd/glopt/__init__.py +4 -0
- directsd/glopt/advanced.py +1658 -0
- directsd/glopt/optimize.py +420 -0
- directsd/linalg/__init__.py +3 -0
- directsd/linalg/linsys.py +66 -0
- directsd/linalg/matrices.py +156 -0
- directsd/linalg/minreal.py +425 -0
- directsd/linalg/riccati.py +197 -0
- directsd/polynomial/__init__.py +11 -0
- directsd/polynomial/diophantine.py +426 -0
- directsd/polynomial/operations.py +247 -0
- directsd/polynomial/poln.py +742 -0
- directsd/polynomial/spectral.py +368 -0
- directsd/polynomial/transforms.py +124 -0
- directsd/polynomial/utils.py +449 -0
- directsd/sspace/__init__.py +4 -0
- directsd/sspace/design.py +1083 -0
- directsd/sspace/plant.py +198 -0
- directsd/tf/__init__.py +9 -0
- directsd/tf/interconnect.py +105 -0
- directsd/zpk/__init__.py +1 -0
- directsd/zpk/zpk.py +400 -0
- directsd_python-0.1.0.dist-info/METADATA +450 -0
- directsd_python-0.1.0.dist-info/RECORD +41 -0
- directsd_python-0.1.0.dist-info/WHEEL +5 -0
- directsd_python-0.1.0.dist-info/licenses/LICENSE +36 -0
- directsd_python-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"""
|
|
2
|
+
DirectSD Python Toolbox – Examples
|
|
3
|
+
===================================
|
|
4
|
+
Ports of the original MATLAB demo scripts.
|
|
5
|
+
Run this file directly or copy snippets into a Jupyter notebook.
|
|
6
|
+
|
|
7
|
+
Examples:
|
|
8
|
+
1. Polynomial arithmetic and spectral factorization
|
|
9
|
+
2. Diophantine equation solver
|
|
10
|
+
3. Double integrator: H2-optimal sampled-data design
|
|
11
|
+
4. L2-optimal controller design
|
|
12
|
+
5. H-infinity norm computation
|
|
13
|
+
6. State-space H2 controller design
|
|
14
|
+
7. Stability margins and characteristic polynomial
|
|
15
|
+
8. Global optimization (Nelder-Mead, Simulated Annealing)
|
|
16
|
+
9. Bilinear transformation (Tustin discretization)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import scipy.signal as sig
|
|
21
|
+
|
|
22
|
+
# ── Import DirectSD ─────────────────────────────────────────────────────────
|
|
23
|
+
from directsd import (
|
|
24
|
+
Poln, s_var, z_var,
|
|
25
|
+
deg, gcd, coprime, factor, sfactor,
|
|
26
|
+
dioph, striplz,
|
|
27
|
+
dtfm, ztrm,
|
|
28
|
+
charpol, sdmargin, sdh2norm, sdhinorm, dinfnorm,
|
|
29
|
+
sdh2, sdl2, ch2, dhinf,
|
|
30
|
+
h2reg, hinfreg, sdh2reg, sdfast,
|
|
31
|
+
neldermead, simanneal, sector, banana,
|
|
32
|
+
toep, hank, linsys, lyap,
|
|
33
|
+
)
|
|
34
|
+
from directsd.polynomial.utils import bilintr, improper, sumzpk, tf2nd
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def separator(title):
|
|
38
|
+
print("\n" + "=" * 60)
|
|
39
|
+
print(f" {title}")
|
|
40
|
+
print("=" * 60)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ============================================================
|
|
44
|
+
# 1. Polynomial arithmetic and spectral factorization
|
|
45
|
+
# ============================================================
|
|
46
|
+
separator("1. Polynomial arithmetic")
|
|
47
|
+
|
|
48
|
+
s = s_var()
|
|
49
|
+
p = Poln([1, 3, 2], 's') # s² + 3s + 2 = (s+1)(s+2)
|
|
50
|
+
q = Poln([1, 1], 's') # s + 1
|
|
51
|
+
|
|
52
|
+
print(f"p(s) = {p}")
|
|
53
|
+
print(f"q(s) = {q}")
|
|
54
|
+
print(f"p + q = {p + q}")
|
|
55
|
+
print(f"p * q = {p * q}")
|
|
56
|
+
print(f"p(0) = {p(0):.4f} (expected 2)")
|
|
57
|
+
print(f"p roots = {p.roots}")
|
|
58
|
+
|
|
59
|
+
# Spectral factorization: find f such that f(s)*f(-s) = R(s)
|
|
60
|
+
# R(s) = (s+1)(-s+1) = 1 - s² – a valid Hermitian polynomial
|
|
61
|
+
R = Poln([-1, 0, 1], 's') # 1 - s²
|
|
62
|
+
fs, _ = sfactor(R)
|
|
63
|
+
print(f"\nSpectral factorization of R = {R}")
|
|
64
|
+
print(f" fs = {fs} (expected ≈ s+1)")
|
|
65
|
+
|
|
66
|
+
# GCD
|
|
67
|
+
a = Poln([1, 3, 2], 's') # (s+1)(s+2)
|
|
68
|
+
b = Poln([1, 2], 's') # (s+2)
|
|
69
|
+
g = gcd(a, b)
|
|
70
|
+
print(f"\ngcd({a}, {b}) = {g} (expected ≈ s+2)")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ============================================================
|
|
74
|
+
# 2. Diophantine equation
|
|
75
|
+
# ============================================================
|
|
76
|
+
separator("2. Diophantine equation X·A + Y·B = C")
|
|
77
|
+
|
|
78
|
+
A = Poln([1, 2, 1], 's') # (s+1)²
|
|
79
|
+
B = Poln([1, 0], 's') # s
|
|
80
|
+
C = Poln([1, 1], 's') # s+1
|
|
81
|
+
|
|
82
|
+
X, Y, err, cond = dioph(A, B, C)
|
|
83
|
+
print(f"A = {A}, B = {B}, C = {C}")
|
|
84
|
+
print(f"Solution: X = {X}, Y = {Y}")
|
|
85
|
+
print(f"Residual ||X·A + Y·B - C|| = {err:.2e} (cond = {cond:.2f})")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ============================================================
|
|
89
|
+
# 3. Double integrator: H2-optimal sampled-data design
|
|
90
|
+
# ============================================================
|
|
91
|
+
separator("3. Double integrator – H2-optimal sampled-data design")
|
|
92
|
+
|
|
93
|
+
# Plant: F(s) = 1/(s²) (double integrator)
|
|
94
|
+
F = sig.lti([1], [1, 0, 0])
|
|
95
|
+
T = 0.1 # sampling period
|
|
96
|
+
|
|
97
|
+
# Design H2-optimal discrete controller
|
|
98
|
+
K_h2, err_h2 = sdh2(F, T)
|
|
99
|
+
print(f"Plant: F(s) = 1/s², T = {T}")
|
|
100
|
+
print(f"H2-optimal controller numerator: {K_h2[0]}")
|
|
101
|
+
print(f"H2-optimal controller denominator: {K_h2[1]}")
|
|
102
|
+
print(f"Optimal H2 cost: {err_h2:.6f}")
|
|
103
|
+
|
|
104
|
+
# Evaluate the H2-norm of closed loop
|
|
105
|
+
h2n = sdh2norm(F, K_h2, T)
|
|
106
|
+
print(f"H2-norm (verification): {h2n:.6f}")
|
|
107
|
+
|
|
108
|
+
# Closed-loop poles
|
|
109
|
+
delta = charpol(F, K_h2, T)
|
|
110
|
+
poles = np.roots(delta)
|
|
111
|
+
print(f"Closed-loop poles: {poles}")
|
|
112
|
+
margin, _ = sdmargin(F, K_h2, T)
|
|
113
|
+
print(f"Stability margin (1 - max|pole|): {margin:.4f}")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# ============================================================
|
|
117
|
+
# 4. L2-optimal controller
|
|
118
|
+
# ============================================================
|
|
119
|
+
separator("4. L2-optimal controller design")
|
|
120
|
+
|
|
121
|
+
# Plant: F(s) = 1/(5s² + s)
|
|
122
|
+
F2 = sig.lti([1], [5, 1, 0])
|
|
123
|
+
T2 = 0.2
|
|
124
|
+
|
|
125
|
+
K_l2, err_l2 = sdl2(F2, T2)
|
|
126
|
+
print(f"Plant: 1/(5s²+s), T = {T2}")
|
|
127
|
+
print(f"L2-optimal controller: {K_l2[0]} / {K_l2[1]}")
|
|
128
|
+
print(f"Optimal L2 cost: {err_l2:.6f}")
|
|
129
|
+
|
|
130
|
+
# Compare with H2
|
|
131
|
+
K_h2b, err_h2b = sdh2(F2, T2)
|
|
132
|
+
print(f"H2 cost (for comparison): {err_h2b:.6f}")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# ============================================================
|
|
136
|
+
# 5. H-infinity norm
|
|
137
|
+
# ============================================================
|
|
138
|
+
separator("5. H-infinity norm of discrete system")
|
|
139
|
+
|
|
140
|
+
# Discrete lowpass filter: G(z) = 0.1 / (z - 0.9)
|
|
141
|
+
num_d = np.array([0.1])
|
|
142
|
+
den_d = np.array([1.0, -0.9])
|
|
143
|
+
sys_d = sig.dlti(num_d, den_d, dt=0.1)
|
|
144
|
+
|
|
145
|
+
gamma, w_max = dinfnorm(sys_d)
|
|
146
|
+
print(f"G(z) = 0.1 / (z - 0.9)")
|
|
147
|
+
print(f"H∞ norm: {gamma:.4f} at ω = {w_max:.3f} rad/sample")
|
|
148
|
+
|
|
149
|
+
# H-infinity of closed loop
|
|
150
|
+
K_test = ([1.0], [1.0])
|
|
151
|
+
gamma_cl, w_cl = sdhinorm(sig.lti([1], [1, 1]), K_test, T=0.1)
|
|
152
|
+
print(f"\nClosed-loop H∞ norm: {gamma_cl:.4f}")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ============================================================
|
|
156
|
+
# 6. State-space H2 controller design
|
|
157
|
+
# ============================================================
|
|
158
|
+
separator("6. State-space H2 controller (h2reg)")
|
|
159
|
+
|
|
160
|
+
# Generalized plant (2×2 state-space)
|
|
161
|
+
A = np.array([[-1.0, 0.5],
|
|
162
|
+
[-0.5, -2.0]])
|
|
163
|
+
B = np.array([[1.0, 0.0],
|
|
164
|
+
[0.0, 1.0]]) # [B1 | B2]
|
|
165
|
+
C = np.array([[1.0, 0.0],
|
|
166
|
+
[0.0, 1.0]]) # [C1; C2]
|
|
167
|
+
D = np.array([[0.0, 1.0],
|
|
168
|
+
[1.0, 0.0]]) # [0 D12; D21 0]
|
|
169
|
+
|
|
170
|
+
plant_ss = sig.StateSpace(A, B, C, D)
|
|
171
|
+
K_ss, h2n_ss = h2reg(plant_ss, n_meas=1, n_ctrl=1)
|
|
172
|
+
|
|
173
|
+
print(f"Plant: 2×2 state-space system")
|
|
174
|
+
print(f"H2-optimal controller A_k:\n{K_ss.A}")
|
|
175
|
+
print(f"H2-optimal controller B_k:\n{K_ss.B}")
|
|
176
|
+
print(f"H2-optimal controller C_k:\n{K_ss.C}")
|
|
177
|
+
print(f"H2-norm of closed loop: {h2n_ss:.4f}")
|
|
178
|
+
|
|
179
|
+
# Sampled-data H2 via lifting
|
|
180
|
+
K_sd, h2n_sd = sdh2reg(plant_ss, T=0.1)
|
|
181
|
+
print(f"\nSampled-data H2 (lifting, T=0.1): H2-norm = {h2n_sd:.4f}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ============================================================
|
|
185
|
+
# 7. Fast discretization (sdfast)
|
|
186
|
+
# ============================================================
|
|
187
|
+
separator("7. Fast (exact ZOH) discretization")
|
|
188
|
+
|
|
189
|
+
plant_ct = sig.StateSpace(
|
|
190
|
+
np.array([[-1.0, 1.0], [0.0, -2.0]]),
|
|
191
|
+
np.array([[0.0], [1.0]]),
|
|
192
|
+
np.array([[1.0, 0.0]]),
|
|
193
|
+
np.array([[0.0]])
|
|
194
|
+
)
|
|
195
|
+
plant_dt = sdfast(plant_ct, T=0.1)
|
|
196
|
+
print(f"Continuous A:\n{plant_ct.A}")
|
|
197
|
+
print(f"Discrete A (T=0.1, exact ZOH):\n{np.round(plant_dt.A, 6)}")
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# ============================================================
|
|
201
|
+
# 8. Global optimization
|
|
202
|
+
# ============================================================
|
|
203
|
+
separator("8. Global optimization")
|
|
204
|
+
|
|
205
|
+
# Nelder-Mead: Rosenbrock's banana function
|
|
206
|
+
print("Nelder-Mead on banana function (min at [1,1]):")
|
|
207
|
+
x_nm, f_nm, n_nm = neldermead(banana, [-1.2, 1.0], tol=1e-8, max_iter=5000)
|
|
208
|
+
print(f" x* = [{x_nm[0]:.6f}, {x_nm[1]:.6f}]")
|
|
209
|
+
print(f" f* = {f_nm:.2e} (evals: {n_nm})")
|
|
210
|
+
|
|
211
|
+
# Simulated Annealing on a multimodal function
|
|
212
|
+
print("\nSimulated Annealing on f(x) = x² - 4x + sin(5x) + 4:")
|
|
213
|
+
f_sa = lambda x: x[0]**2 - 4*x[0] + np.sin(5*x[0]) + 4
|
|
214
|
+
np.random.seed(0)
|
|
215
|
+
x_sa, f_sa_val, n_sa, _ = simanneal(
|
|
216
|
+
f_sa, [3.0],
|
|
217
|
+
options={'maxFunEvals': 2000, 'startTemp': 5.0,
|
|
218
|
+
'tempDecRate': 0.97, 'display': 'off'}
|
|
219
|
+
)
|
|
220
|
+
print(f" x* = {x_sa[0]:.4f}, f* = {f_sa_val:.6f} (evals: {n_sa})")
|
|
221
|
+
|
|
222
|
+
# Stability sector analysis
|
|
223
|
+
print("\nStability sector analysis:")
|
|
224
|
+
cl_poles = np.array([-0.5 + 1.5j, -0.5 - 1.5j, -2.0])
|
|
225
|
+
alpha, beta = sector(cl_poles)
|
|
226
|
+
print(f" Poles: {cl_poles}")
|
|
227
|
+
print(f" Degree of stability α = {alpha:.3f}")
|
|
228
|
+
print(f" Degree of oscillation β = {beta:.3f}")
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
# ============================================================
|
|
232
|
+
# 9. Bilinear transformation (Tustin)
|
|
233
|
+
# ============================================================
|
|
234
|
+
separator("9. Bilinear transformation (Tustin discretization)")
|
|
235
|
+
|
|
236
|
+
# Continuous PID-like controller: K(s) = (s² + 3s + 2) / (s² + 0.1s)
|
|
237
|
+
K_ct = sig.lti([1, 3, 2], [1, 0.1, 0])
|
|
238
|
+
print(f"Continuous controller: {K_ct.num} / {K_ct.den}")
|
|
239
|
+
|
|
240
|
+
T_disc = 0.05
|
|
241
|
+
K_tustin_num, K_tustin_den = bilintr(K_ct, 'tustin', T_disc)
|
|
242
|
+
print(f"Tustin (T={T_disc}): num = {np.round(K_tustin_num, 4)}")
|
|
243
|
+
print(f" den = {np.round(K_tustin_den, 4)}")
|
|
244
|
+
|
|
245
|
+
# Verify: inverse Tustin should give back original (approximately)
|
|
246
|
+
K_back_num, K_back_den = bilintr((K_tustin_num, K_tustin_den), 'z2s', T_disc)
|
|
247
|
+
print(f"Inverse Tustin (back to CT):")
|
|
248
|
+
print(f" num ≈ {np.round(K_back_num / K_back_num[-1] * K_ct.num[-1], 3)}")
|
|
249
|
+
print(f" den ≈ {np.round(K_back_den / K_back_den[-1] * K_ct.den[-1], 3)}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
# ============================================================
|
|
253
|
+
# 10. DTF transform (discrete Laplace with ZOH)
|
|
254
|
+
# ============================================================
|
|
255
|
+
separator("10. Modified Z-transform (dtfm)")
|
|
256
|
+
|
|
257
|
+
# Discretize 1/(s+1) with ZOH, T=0.1
|
|
258
|
+
G_ct = sig.lti([1], [1, 1])
|
|
259
|
+
G_num_d, G_den_d = dtfm(G_ct, T=0.1)
|
|
260
|
+
print(f"G(s) = 1/(s+1) → G_d(z) num = {np.round(G_num_d, 6)}")
|
|
261
|
+
print(f" den = {np.round(G_den_d, 6)}")
|
|
262
|
+
|
|
263
|
+
# Verify with scipy
|
|
264
|
+
G_d_scipy = G_ct.to_discrete(0.1, method='zoh')
|
|
265
|
+
print(f"scipy ZOH: num = {np.round(G_d_scipy.num, 6)}")
|
|
266
|
+
print(f" den = {np.round(G_d_scipy.den, 6)}")
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# ============================================================
|
|
270
|
+
# 11. Linear algebra utilities
|
|
271
|
+
# ============================================================
|
|
272
|
+
separator("11. Linear algebra utilities")
|
|
273
|
+
|
|
274
|
+
# Toeplitz matrix from polynomial
|
|
275
|
+
a = np.array([1, 2, 3])
|
|
276
|
+
T_mat = toep(a, 4, 3)
|
|
277
|
+
print(f"Toeplitz matrix from [1,2,3]:\n{T_mat}")
|
|
278
|
+
|
|
279
|
+
# Lyapunov equation: A*P + P*A' + Q = 0
|
|
280
|
+
A_lyap = np.array([[-2.0, 1.0], [0.0, -3.0]])
|
|
281
|
+
Q_lyap = np.eye(2)
|
|
282
|
+
P_lyap = lyap(A_lyap, Q_lyap)
|
|
283
|
+
residual = A_lyap @ P_lyap + P_lyap @ A_lyap.T + Q_lyap
|
|
284
|
+
print(f"\nLyapunov residual ||A·P + P·A' + Q|| = {np.linalg.norm(residual):.2e}")
|
|
285
|
+
|
|
286
|
+
# Linear system solver (SVD)
|
|
287
|
+
A_mat = np.array([[2.0, 1.0], [1.0, 3.0], [0.5, 0.5]])
|
|
288
|
+
b_vec = np.array([[4.0], [5.0], [1.5]])
|
|
289
|
+
x_sol = linsys(A_mat, b_vec, method='svd')
|
|
290
|
+
print(f"\nLeast-squares solution to Ax=b: x = {x_sol.ravel()}")
|
|
291
|
+
print(f"Residual ||Ax - b|| = {np.linalg.norm(A_mat @ x_sol - b_vec):.2e}")
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
print("\n" + "=" * 60)
|
|
295
|
+
print(" All examples completed successfully!")
|
|
296
|
+
print("=" * 60 + "\n")
|