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.
Files changed (41) hide show
  1. directsd/__init__.py +141 -0
  2. directsd/analysis/__init__.py +4 -0
  3. directsd/analysis/charpol.py +89 -0
  4. directsd/analysis/errors.py +185 -0
  5. directsd/analysis/norms.py +630 -0
  6. directsd/design/__init__.py +9 -0
  7. directsd/design/convex.py +620 -0
  8. directsd/design/lifting.py +403 -0
  9. directsd/design/polynomial.py +5925 -0
  10. directsd/examples/__init__.py +0 -0
  11. directsd/examples/_common.py +36 -0
  12. directsd/examples/demos.py +1161 -0
  13. directsd/examples/examples.py +296 -0
  14. directsd/examples/help_examples.py +1043 -0
  15. directsd/glopt/__init__.py +4 -0
  16. directsd/glopt/advanced.py +1658 -0
  17. directsd/glopt/optimize.py +420 -0
  18. directsd/linalg/__init__.py +3 -0
  19. directsd/linalg/linsys.py +66 -0
  20. directsd/linalg/matrices.py +156 -0
  21. directsd/linalg/minreal.py +425 -0
  22. directsd/linalg/riccati.py +197 -0
  23. directsd/polynomial/__init__.py +11 -0
  24. directsd/polynomial/diophantine.py +426 -0
  25. directsd/polynomial/operations.py +247 -0
  26. directsd/polynomial/poln.py +742 -0
  27. directsd/polynomial/spectral.py +368 -0
  28. directsd/polynomial/transforms.py +124 -0
  29. directsd/polynomial/utils.py +449 -0
  30. directsd/sspace/__init__.py +4 -0
  31. directsd/sspace/design.py +1083 -0
  32. directsd/sspace/plant.py +198 -0
  33. directsd/tf/__init__.py +9 -0
  34. directsd/tf/interconnect.py +105 -0
  35. directsd/zpk/__init__.py +1 -0
  36. directsd/zpk/zpk.py +400 -0
  37. directsd_python-0.1.0.dist-info/METADATA +450 -0
  38. directsd_python-0.1.0.dist-info/RECORD +41 -0
  39. directsd_python-0.1.0.dist-info/WHEEL +5 -0
  40. directsd_python-0.1.0.dist-info/licenses/LICENSE +36 -0
  41. 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")