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,1043 @@
1
+ """
2
+ Numerical examples from dsd_help.md — Python implementation.
3
+
4
+ Run all:
5
+ python -m directsd.examples.help_examples
6
+ Run one:
7
+ python -m directsd.examples.help_examples <name>
8
+
9
+ Note: norm values may differ from MATLAB due to different conventions.
10
+ sdh2/sdl2 for MIMO plants with P22≠0 currently fall back to a unit
11
+ controller (h2reg limitation); filtering examples (P22=0) work correctly.
12
+ Modal reduced-order controllers (modsdh2/modsdl2) always work.
13
+ """
14
+ import sys
15
+ import math
16
+ import numpy as np
17
+ import scipy.signal as sig
18
+
19
+ from directsd import (
20
+ sfactor, sdh2, sdl2, sdahinf, sdahinorm, sdh2norm, sdhinorm,
21
+ sdl2err, sd2dof, sd2doferr, sdh2hinf, sdtrhinf, sdtrhinferr,
22
+ charpol, ch2, bilintr, dhinf, modsdh2, modsdl2,
23
+ GeneralizedPlant,
24
+ )
25
+ from directsd.analysis.norms import dahinorm as _dahinorm
26
+ from directsd.tf import to_lti, nd, mul, neg, add, feedback, nd_mul, nd_neg
27
+ from directsd.examples._common import cl_poles as _cl_poles, _z2zeta
28
+
29
+ # ---------------------------------------------------------------------------
30
+ # helpers
31
+ # ---------------------------------------------------------------------------
32
+
33
+ def _pade(tau, n):
34
+ """Padé approximation for exp(-tau*s); returns (num, den) highest-power-first."""
35
+ c = [(-tau)**k / math.factorial(k) for k in range(2*n+2)]
36
+ num = np.zeros(n+1)
37
+ den = np.zeros(n+1)
38
+ for k in range(n+1):
39
+ coeff = (math.factorial(2*n - k) * math.factorial(n)
40
+ / (math.factorial(2*n) * math.factorial(k) * math.factorial(n-k)))
41
+ num[k] = coeff * (-tau)**k
42
+ den[k] = coeff * tau**k
43
+ return num[::-1], den[::-1]
44
+
45
+
46
+
47
+ def _zpk_str(obj):
48
+ """Compact ZPK summary for display."""
49
+ if isinstance(obj, tuple) and len(obj) == 2:
50
+ n, d = obj
51
+ if not (np.isscalar(n) or isinstance(n, np.ndarray)):
52
+ return str(obj)
53
+ obj = sig.TransferFunction(np.atleast_1d(n).ravel(),
54
+ np.atleast_1d(d).ravel())
55
+ if hasattr(obj, 'to_zpk'):
56
+ zpk = obj.to_zpk()
57
+ z, p, k = zpk.zeros, zpk.poles, zpk.gain
58
+ elif isinstance(obj, (sig.lti, sig.TransferFunction)):
59
+ zpk = obj.to_zpk()
60
+ z, p, k = zpk.zeros, zpk.poles, zpk.gain
61
+ else:
62
+ return str(obj)
63
+ z_str = " ".join(f"{v:.4f}" for v in sorted(z, key=lambda x: x.real))
64
+ p_str = " ".join(f"{v:.4f}" for v in sorted(p, key=lambda x: x.real))
65
+ return f"k={k:.4f} zeros=[{z_str}] poles=[{p_str}]"
66
+
67
+
68
+ def _check(label, computed, expected, tol=0.01):
69
+ if not np.isfinite(computed):
70
+ print(f" SKIP {label}: computed={computed} MATLAB={expected:.6g} (nan/inf)")
71
+ return False
72
+ err = abs(computed - expected) / (abs(expected) + 1e-12)
73
+ status = "OK" if err < tol else "MISMATCH"
74
+ print(f" {status:8s} {label}: computed={computed:.6g} MATLAB={expected:.6g} "
75
+ f"rel_err={err:.3%}")
76
+ return status == "OK"
77
+
78
+
79
+
80
+
81
+ # ---------------------------------------------------------------------------
82
+ # Example 1 – Basic getting-started example (line 181)
83
+ # F = 1/(25s^2+s), Fw = 2/(s+2), T=1
84
+ # sys = [F*Fw F; -F*Fw -F] (P22=-F, sdh2 via MIMO fallback)
85
+ # K_manual = (z-0.5)/(z-0.2), MATLAB sdh2norm(sys,K)=0.8332, opt=0.0292
86
+ # ---------------------------------------------------------------------------
87
+ def ex_getting_started():
88
+ print("\n=== Getting-started example ===")
89
+ F = sig.TransferFunction([1], [25, 1, 0])
90
+ Fw = sig.TransferFunction([2], [1, 2])
91
+ T = 1.0
92
+ sys = GeneralizedPlant([
93
+ [mul(F, Fw), F],
94
+ [neg(mul(F, Fw)), neg(F)],
95
+ ])
96
+
97
+ # Characteristic polynomial for manually specified K
98
+ K_man = sig.dlti([1, -0.5], [1, -0.2], dt=T)
99
+ try:
100
+ cp = charpol(F, K_man)
101
+ print(f" charpol coeffs (manual K): {np.round(cp, 4)}")
102
+ print(f" MATLAB expected: [1, -2.1411, 1.3626, -0.2019]")
103
+ except Exception as e:
104
+ print(f" charpol: {e}")
105
+
106
+ # sdh2norm for manual K: use full sys (same as MATLAB sdh2norm(sys,K))
107
+ try:
108
+ norm_man = sdh2norm(sys, (K_man.num, K_man.den), T)
109
+ print(f" sdh2norm(sys, K_manual, T) = {norm_man:.4f} (MATLAB: 0.8332)")
110
+ except Exception as e:
111
+ print(f" sdh2norm manual: {e}")
112
+
113
+ # Optimal H2 design via full sys (P22=-F, may fall back)
114
+ K_opt, err_opt = sdh2(sys, T)
115
+ print(f" sdh2 K_opt: {_zpk_str(K_opt)}")
116
+ print(f" sdh2 cost: {err_opt:.4g} (MATLAB sdh2norm(sys,Kopt)=0.0292)")
117
+ try:
118
+ norm_opt = sdh2norm(sys, K_opt, T)
119
+ print(f" sdh2norm(sys, K_opt, T) = {norm_opt:.4f} (MATLAB: 0.0292)")
120
+ except Exception as e:
121
+ print(f" sdh2norm: {e}")
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Example 2 – Deterministic / L2 tracking (line 333)
126
+ # F1=1/(25s+1), F2=1/s, R=1/s, rho=0.2, T=1
127
+ # MATLAB: sdl2err(sys,K)=3.2049
128
+ # ---------------------------------------------------------------------------
129
+ def ex_deterministic_l2():
130
+ print("\n=== Deterministic / L2 tracking (ship course, step) ===")
131
+ F1 = sig.TransferFunction([1], [25, 1])
132
+ F2 = sig.TransferFunction([1], [1, 0])
133
+ R = sig.TransferFunction([1], [1, 0])
134
+ rho = 0.2
135
+ T = 1.0
136
+ F_plant = mul(F2, F1)
137
+ sys = GeneralizedPlant([
138
+ [R, neg(F_plant)],
139
+ [0, rho],
140
+ [R, neg(F_plant)],
141
+ ])
142
+ K, err = sdl2(sys, T)
143
+ print(f" sdl2 K: {_zpk_str(K)}")
144
+ print(f" sdl2 cost: {err:.4g}")
145
+ try:
146
+ err_check = sdl2err(sys, K, T) # pass full sys, same as MATLAB sdl2err(sys,K)
147
+ print(f" sdl2err(sys, K, T) = {err_check:.4f} (MATLAB: 3.2049)")
148
+ except Exception as e:
149
+ print(f" sdl2err: {e}")
150
+
151
+
152
+ # ---------------------------------------------------------------------------
153
+ # Example 3 – Stochastic / H2 control (line 449)
154
+ # F=1/(25s^2+s), Fw=2/(s+2), rho=0.2, T=1
155
+ # MATLAB: sdh2norm(sys,K)^2=0.0328
156
+ # ---------------------------------------------------------------------------
157
+ def ex_stochastic_h2():
158
+ print("\n=== Stochastic / H2 control ===")
159
+ F = sig.TransferFunction([1], [25, 1, 0])
160
+ Fw = sig.TransferFunction([2], [1, 2])
161
+ rho = 0.2
162
+ T = 1.0
163
+ sys = GeneralizedPlant([
164
+ [mul(F, Fw), F],
165
+ [0, rho],
166
+ [neg(mul(F, Fw)), neg(F)],
167
+ ])
168
+ K, err = sdh2(sys, T)
169
+ print(f" sdh2 K: {_zpk_str(K)}")
170
+ print(f" sdh2 cost^2: {err**2:.4g} (MATLAB: 0.0328)")
171
+ try:
172
+ norm_sq = sdh2norm(sys, K, T) ** 2
173
+ print(f" sdh2norm(sys,K,T)^2 = {norm_sq:.4f} (MATLAB: 0.0328)")
174
+ except Exception as e:
175
+ print(f" sdh2norm: {e}")
176
+
177
+
178
+ # ---------------------------------------------------------------------------
179
+ # Example 4 – Reconstructing first-order process (line 697)
180
+ # F=1/(s+1), Sr=4/(−s^2+4), Fr=sfactor(Sr)=2/(s+2), T=0.1
181
+ # sys=[Fr 0 −F; Fr 1 0] (P22=0 → works)
182
+ # MATLAB: sdh2norm(sys,K)=0.7314, sdh2norm(sys,K,0)=0.7682
183
+ # ---------------------------------------------------------------------------
184
+ def ex_first_order_filter():
185
+ print("\n=== Reconstructing first-order process ===")
186
+ F = sig.TransferFunction([1], [1, 1])
187
+ Sr = sig.TransferFunction([4], [-1, 0, 4])
188
+ Fr_zpk, _ = sfactor(Sr)
189
+ Fr = Fr_zpk.to_tf()
190
+ T = 0.1
191
+ sys = GeneralizedPlant([
192
+ [Fr, 0, neg(F)],
193
+ [Fr, 1, 0],
194
+ ])
195
+ K, err = sdh2(sys, T)
196
+ print(f" sdh2 K: {_zpk_str(K)}")
197
+ print(f" sdh2 cost: {err:.4f} (MATLAB sdh2norm avg=0.7314)")
198
+ try:
199
+ norm_avg = sdh2norm(sys, K, T)
200
+ print(f" sdh2norm(sys,K,T) avg = {norm_avg:.4f} (MATLAB: 0.7314)")
201
+ except Exception as e:
202
+ print(f" sdh2norm avg: {e}")
203
+
204
+ # Discrete-time optimal (minimize error at sampling instants only)
205
+ K0, err0 = sdh2(sys, T, t=0.0, method='pol')
206
+ print(f" sdh2 K0 (t=0, pol): {_zpk_str(K0)}")
207
+ print(f" sdh2 cost (t=0): {err0:.4f} (MATLAB sdh2norm(sys,K0,0)=0.7577)")
208
+ try:
209
+ norm0 = sdh2norm(sys, K0, T)
210
+ print(f" sdh2norm(sys,K0,T) avg = {norm0:.4f} (MATLAB: 0.7682)")
211
+ except Exception as e:
212
+ print(f" sdh2norm t=0: {e}")
213
+
214
+
215
+ # ---------------------------------------------------------------------------
216
+ # Example 5 – Filtering with delay (line 882)
217
+ # Same as Example 4 but F has iodelay=0.051, T=0.1
218
+ # MATLAB: sdh2norm avg=0.7879, t=0=0.7735
219
+ # ---------------------------------------------------------------------------
220
+ def ex_filter_with_delay():
221
+ print("\n=== Filtering with delay (τ=0.051) ===")
222
+ Sr = sig.TransferFunction([4], [-1, 0, 4])
223
+ Fr_zpk, _ = sfactor(Sr)
224
+ Fr = Fr_zpk.to_tf()
225
+ # F = 1/(s+1) with delay 0.051 — approximate with 2nd-order Padé
226
+ pn, pd = _pade(0.051, 2)
227
+ F_delay = sig.TransferFunction(np.polymul([1], pn),
228
+ np.polymul([1, 1], pd))
229
+ T = 0.1
230
+ sys = GeneralizedPlant([
231
+ [Fr, 0, neg(F_delay)],
232
+ [Fr, 1, 0],
233
+ ])
234
+ K, err = sdh2(sys, T)
235
+ print(f" sdh2 K: {_zpk_str(K)}")
236
+ print(f" sdh2 cost: {err:.4f} (MATLAB sdh2norm avg≈0.7879)")
237
+ try:
238
+ norm_avg = sdh2norm(sys, K, T)
239
+ print(f" sdh2norm(sys,K,T) avg = {norm_avg:.4f} (MATLAB: 0.7879)")
240
+ except Exception as e:
241
+ print(f" sdh2norm: {e}")
242
+
243
+
244
+ # ---------------------------------------------------------------------------
245
+ # Example 6 – Reconstructing second-order process (line 1011)
246
+ # F=1/(s^2+s+1), same Fr, T=0.1
247
+ # MATLAB: sdh2norm avg=0.7738, t=0=0.7666
248
+ # ---------------------------------------------------------------------------
249
+ def ex_second_order_filter():
250
+ print("\n=== Reconstructing second-order process ===")
251
+ F = sig.TransferFunction([1], [1, 1, 1])
252
+ Sr = sig.TransferFunction([4], [-1, 0, 4])
253
+ Fr_zpk, _ = sfactor(Sr)
254
+ Fr = Fr_zpk.to_tf()
255
+ T = 0.1
256
+ sys = GeneralizedPlant([
257
+ [Fr, 0, neg(F)],
258
+ [Fr, 1, 0],
259
+ ])
260
+ K, err = sdh2(sys, T)
261
+ print(f" sdh2 K: {_zpk_str(K)}")
262
+ print(f" sdh2 cost: {err:.4f} (MATLAB sdh2norm avg=0.7738)")
263
+ try:
264
+ norm_avg = sdh2norm(sys, K, T)
265
+ print(f" sdh2norm(sys,K,T) avg = {norm_avg:.4f} (MATLAB: 0.7738)")
266
+ except Exception as e:
267
+ print(f" sdh2norm: {e}")
268
+
269
+ K0, err0 = sdh2(sys, T, t=0.0, method='pol')
270
+ print(f" sdh2 K0 (t=0): {_zpk_str(K0)}")
271
+ print(f" sdh2 cost (t=0): {err0:.4f} (MATLAB sdh2norm K0 avg=0.9524)")
272
+
273
+
274
+ # ---------------------------------------------------------------------------
275
+ # Example 7 – Generalized hold (line 1140)
276
+ # Fr=2/(s+2), Fn=0.1, F=−1 (plant is -1), sys=[Fr 0 -1; Fr 0.1 0], T=0.2
277
+ # MATLAB: sdh2norm^2(ZOH)=0.3274, sdh2norm^2(gen hold)=0.3184
278
+ # ---------------------------------------------------------------------------
279
+ def ex_generalized_hold():
280
+ print("\n=== Generalized hold ===")
281
+ Sr = sig.TransferFunction([4], [-1, 0, 4])
282
+ Fr_zpk, _ = sfactor(Sr)
283
+ Fr = Fr_zpk.to_tf()
284
+ Fn = 0.1
285
+ T = 0.2
286
+ F_plant = sig.TransferFunction([-1], [1]) # plant = -1
287
+
288
+ sys = GeneralizedPlant([
289
+ [Fr, 0, F_plant], # z = Fr*w - 1*u (i.e. F_plant = -1)
290
+ [Fr, Fn, 0],
291
+ ])
292
+ K0, err0 = sdh2(sys, T)
293
+ print(f" sdh2 K0 (ZOH): {_zpk_str(K0)}")
294
+ print(f" sdh2 cost^2 (ZOH): {err0**2:.4f} (MATLAB: 0.3274)")
295
+ try:
296
+ norm0_sq = sdh2norm(F_plant, K0, T) ** 2
297
+ print(f" sdh2norm(F,K0,T)^2 = {norm0_sq:.4f}")
298
+ except Exception as e:
299
+ print(f" sdh2norm ZOH: {e}")
300
+
301
+ H = sig.TransferFunction([1], [1, 2])
302
+ K, err = sdh2(sys, T, H=H)
303
+ print(f" sdh2 K (gen. hold): {_zpk_str(K)}")
304
+ print(f" sdh2 cost^2 (gen hold): {err**2:.4f} (MATLAB: 0.3184)")
305
+
306
+
307
+ # ---------------------------------------------------------------------------
308
+ # Example 8 – Optimal disturbance attenuation (line 1315)
309
+ # F=(s+0.5)/(s^2−s), Fw=2/(s+2), τ=0.093, T=0.2
310
+ # MATLAB: sdh2norm avg=0.1840, t=0=0.1600
311
+ # ---------------------------------------------------------------------------
312
+ def ex_disturbance_attenuation():
313
+ print("\n=== Optimal disturbance attenuation (with delay τ=0.093) ===")
314
+ F = sig.TransferFunction([1, 0.5], [1, -1, 0])
315
+ Fw = sig.TransferFunction([2], [1, 2])
316
+ T = 0.2
317
+ # F with delay 0.093 — 2nd-order Padé approx
318
+ pn, pd = _pade(0.093, 2)
319
+ Fd = sig.TransferFunction(np.polymul([1, 0.5], pn),
320
+ np.polymul([1, -1, 0], pd))
321
+ sys = GeneralizedPlant([
322
+ [mul(F, Fw), Fd],
323
+ [neg(mul(F, Fw)), neg(Fd)],
324
+ ])
325
+ K, err = sdh2(sys, T)
326
+ print(f" sdh2 K: {_zpk_str(K)}")
327
+ print(f" sdh2 cost: {err:.4f} (MATLAB sdh2norm avg≈0.1840)")
328
+ try:
329
+ norm_avg = sdh2norm(Fd, K, T)
330
+ print(f" sdh2norm(Fd,K,T) avg = {norm_avg:.4f}")
331
+ except Exception as e:
332
+ print(f" sdh2norm: {e}")
333
+
334
+
335
+ # ---------------------------------------------------------------------------
336
+ # Example 9 – Optimal ship course stabilization (line 1465)
337
+ # F=0.051/(25s^2+s), Sw given, Fw=sfactor(Sw), rho=sqrt(0.1), T=1
338
+ # MATLAB: sdh2norm=0.0190
339
+ # ---------------------------------------------------------------------------
340
+ def ex_ship_course():
341
+ print("\n=== Optimal ship course stabilization ===")
342
+ F = sig.TransferFunction([0.051], [25, 1, 0])
343
+ Sw = sig.TransferFunction([0.0757], [1, 0, 2.489, 0, 1.848])
344
+ Fw_zpk, _ = sfactor(Sw)
345
+ Fw = Fw_zpk.to_tf()
346
+ rho = np.sqrt(0.1)
347
+ T = 1.0
348
+ sys = GeneralizedPlant([
349
+ [mul(F, Fw), F],
350
+ [0, rho],
351
+ [neg(mul(F, Fw)), neg(F)],
352
+ ])
353
+ K, err = sdh2(sys, T)
354
+ print(f" sdh2 K: {_zpk_str(K)}")
355
+ print(f" sdh2 cost: {err:.4f} (MATLAB sdh2norm=0.0190)")
356
+ try:
357
+ norm = sdh2norm(F, K, T)
358
+ print(f" sdh2norm(F,K,T) = {norm:.4f}")
359
+ except Exception as e:
360
+ print(f" sdh2norm: {e}")
361
+
362
+
363
+ # ---------------------------------------------------------------------------
364
+ # Example 10 – Double integrator H2 control (line 1587)
365
+ # F=1/s^2, rho=1, T=0.1
366
+ # ---------------------------------------------------------------------------
367
+ def ex_double_integrator():
368
+ print("\n=== Double integrator H2 control ===")
369
+ F = sig.TransferFunction([1], [1, 0, 0])
370
+ rho = 1.0
371
+ T = 0.1
372
+ sys = GeneralizedPlant([
373
+ [neg(F), neg(F)],
374
+ [0, rho],
375
+ [neg(F), neg(F)],
376
+ ])
377
+ Kopt, err_opt = sdh2(sys, T)
378
+ print(f" sdh2 Kopt: {_zpk_str(Kopt)}")
379
+ print(f" sdh2 cost: {err_opt:.4g}")
380
+ poles = _cl_poles(F, Kopt, T)
381
+ if len(poles) > 0:
382
+ stable = all(abs(r) < 1.0 for r in poles)
383
+ print(f" closed-loop poles: {[f'{r:.4f}' for r in poles]}")
384
+ print(f" stable: {stable} (MATLAB: True)")
385
+
386
+ # Redesign method — ch2 + bilintr
387
+ try:
388
+ Kc_raw, _ = ch2(sys)
389
+ Kc = sig.TransferFunction(*Kc_raw)
390
+ Kc2d_nd = bilintr(Kc, 'tustin', T)
391
+ Kc2d = sig.TransferFunction(*Kc2d_nd)
392
+ poles2d = _cl_poles(F, Kc2d_nd, T)
393
+ if len(poles2d) > 0:
394
+ stable2d = all(abs(r) < 1.0 for r in poles2d)
395
+ print(f" redesign poles: {[f'{r:.4f}' for r in poles2d]}")
396
+ print(f" redesign stable: {stable2d} (MATLAB: False – marginal)")
397
+ except Exception as e:
398
+ print(f" redesign: skipped ({e})")
399
+
400
+
401
+ # ---------------------------------------------------------------------------
402
+ # Example 11 – L2-optimal tracking (line 1792)
403
+ # R=1/s, F=1/(5s^2+s), Q=1/(s+1), T=0.2
404
+ # MATLAB: sdl2err=4.6824e-4
405
+ # ---------------------------------------------------------------------------
406
+ def ex_l2_tracking():
407
+ print("\n=== L2-optimal tracking ===")
408
+ R = sig.TransferFunction([1], [1, 0])
409
+ F = sig.TransferFunction([1], [5, 1, 0])
410
+ Q = sig.TransferFunction([1], [1, 1])
411
+ T = 0.2
412
+ sys = GeneralizedPlant([
413
+ [mul(Q, R), neg(F)],
414
+ [R, neg(F)],
415
+ ])
416
+ Kopt, err = sdl2(sys, T)
417
+ print(f" sdl2 Kopt: {_zpk_str(Kopt)}")
418
+ print(f" sdl2 cost: {err:.4g} (MATLAB sdl2err=4.6824e-4)")
419
+ try:
420
+ err_check = sdl2err(sys, Kopt, T)
421
+ print(f" sdl2err(sys, K, T) = {err_check:.4e} (MATLAB: 4.6824e-4)")
422
+ except Exception as e:
423
+ print(f" sdl2err: {e}")
424
+
425
+ poles = _cl_poles(F, Kopt, T)
426
+ if len(poles) > 0:
427
+ stable = all(abs(r) < 1.0 for r in poles)
428
+ print(f" closed-loop poles: {[f'{r:.4f}' for r in poles]}")
429
+ print(f" stable: {stable} (MATLAB: True)")
430
+
431
+
432
+ # ---------------------------------------------------------------------------
433
+ # Example 12 – L2-optimal redesign (line 2037)
434
+ # F=10/(s^2+s), Kc=(0.416s+1)/(0.139s+1), τ=0.01, T=0.04
435
+ # MATLAB: sdl2err=6.7103e-7
436
+ # ---------------------------------------------------------------------------
437
+ def ex_l2_redesign():
438
+ print("\n=== L2-optimal redesign ===")
439
+ R = sig.TransferFunction([1], [1, 0])
440
+ F_ct = sig.TransferFunction([10], [1, 1, 0])
441
+ Kc = sig.TransferFunction([0.416, 1], [0.139, 1])
442
+ Q = feedback(mul(F_ct, Kc))
443
+ T = 0.04
444
+
445
+ # MATLAB: F.iodelay = 0.01, handled EXACTLY inside sdl2 via the modified
446
+ # Z-transform (udelay). A Padé substitute in the DESIGN plant introduces
447
+ # fast poles (~±600) whose discretized images span ~20 decades and
448
+ # destroy the polynomial pipeline — pass the delay separately.
449
+ tau = 0.01
450
+ sys_design = GeneralizedPlant([
451
+ [mul(Q, R), neg(F_ct)],
452
+ [R, neg(F_ct)],
453
+ ])
454
+ K, err = sdl2(sys_design, T, udelay=tau)
455
+ print(f" sdl2 K: {_zpk_str(K)}")
456
+ print(f" MATLAB K: 4.0371 z(z-0.9608)(z-0.9083)(z^2-0.0708z+0.07494)")
457
+ print(f" /((z+0.04358)(z-0.009759)(z+0.4438)(z-0.7342)(z-0.9559))")
458
+
459
+ # Verification: sdl2err has no delay support, so evaluate on a
460
+ # Padé-approximated plant (fine for EVALUATION — MATLAB's documented K
461
+ # scores 7.9e-7 on it, matching the documented 6.7103e-7).
462
+ pn, pd = _pade(tau, 2)
463
+ F_delay = sig.TransferFunction(np.polymul([10], pn),
464
+ np.polymul([1, 1, 0], pd))
465
+ sys_eval = GeneralizedPlant([
466
+ [mul(Q, R), neg(F_delay)],
467
+ [R, neg(F_delay)],
468
+ ])
469
+ try:
470
+ err_check = sdl2err(sys_eval, K, T)
471
+ print(f" sdl2err(Padé sys, K, T) = {err_check:.4e} (MATLAB: 6.7103e-7)")
472
+ except Exception as e:
473
+ print(f" sdl2err: {e}")
474
+
475
+
476
+ # ---------------------------------------------------------------------------
477
+ # Example 13 – 2-DOF optimal tracking (line 2159)
478
+ # R=1/s, F=1/(s-1), Q=1/(s+2), T=0.5
479
+ # MATLAB: 1-DOF sdl2err=2.5845, 2-DOF sd2doferr=5.6599e-4
480
+ # ---------------------------------------------------------------------------
481
+ def ex_2dof_tracking():
482
+ print("\n=== 2-DOF optimal tracking ===")
483
+ R = sig.TransferFunction([1], [1, 0])
484
+ F = sig.TransferFunction([1], [1, -1])
485
+ Q = sig.TransferFunction([1], [1, 2])
486
+ T = 0.5
487
+ sys = GeneralizedPlant([
488
+ [mul(Q, R), neg(F)],
489
+ [R, neg(F)],
490
+ ])
491
+ K1, err1 = sdl2(sys, T)
492
+ print(f" 1-DOF sdl2 K1: {_zpk_str(K1)}")
493
+ print(f" 1-DOF sdl2 cost: {err1:.4g} (MATLAB sdl2err=2.5845)")
494
+ try:
495
+ err_1dof = sdl2err(sys, K1, T)
496
+ print(f" sdl2err(sys, K1, T) = {err_1dof:.4f} (MATLAB: 2.5845)")
497
+ except Exception as e:
498
+ print(f" sdl2err 1-DOF: {e}")
499
+
500
+ # 2-DOF: sd2dof takes SISO plant F; sd2doferr takes full 3x2 generalized plant
501
+ # sys_2dof rows: [performance Q*R*d-F*u; reference measurement R*d; output measurement -F*u]
502
+ sys_2dof = GeneralizedPlant([
503
+ [mul(Q, R), neg(F)],
504
+ [R, 0 ],
505
+ [0, neg(F) ],
506
+ ], n_meas=2)
507
+ try:
508
+ KR, err_2dof = sd2dof(sys_2dof, K1, T)
509
+ print(f" 2-DOF KR: {_zpk_str(KR)}")
510
+ print(f" 2-DOF sd2dof cost: {err_2dof:.4e} (MATLAB: 5.6599e-4)")
511
+ err2 = sd2doferr(sys_2dof, K1, KR, T)
512
+ _check("2-DOF sd2doferr", err2, 5.6599e-4, tol=0.50)
513
+ except Exception as e:
514
+ print(f" sd2dof/sd2doferr: {e}")
515
+
516
+
517
+ # ---------------------------------------------------------------------------
518
+ # Example 14 – AHinf-optimal prediction (line 2341)
519
+ # F=1/(s+1), Fr=1/(5s+1), predict τ=0.15, T=0.1
520
+ # MATLAB: sdahinorm=0.0351
521
+ # ---------------------------------------------------------------------------
522
+ def ex_ahinf_prediction():
523
+ print("\n=== AHinf-optimal prediction ===")
524
+ F = sig.TransferFunction([1], [1, 1])
525
+ Fr = sig.TransferFunction([1], [5, 1])
526
+ # Q = exp(+0.15s) — Padé: exp(+tau*s) ≈ den/num of exp(-tau*s)
527
+ pn, pd = _pade(0.15, 2)
528
+ Q_num, Q_den = pd, pn # flip for positive delay
529
+ T = 0.1
530
+ sys = GeneralizedPlant([
531
+ [sig.TransferFunction(np.polymul(-Q_num, [1]), np.polymul(Q_den, [5, 1])),
532
+ F],
533
+ [Fr, 0],
534
+ ])
535
+ K, err = sdahinf(sys, T)
536
+ print(f" sdahinf K: {_zpk_str(K)}")
537
+ try:
538
+ ahinf_norm = sdahinorm(F, K, T)
539
+ print(f" sdahinorm(F,K,T) = {ahinf_norm:.4f} (MATLAB: 0.0351)")
540
+ except Exception as e:
541
+ print(f" sdahinorm: {e}")
542
+
543
+
544
+ # ---------------------------------------------------------------------------
545
+ # Example 15 – Discrete AHinf example 1
546
+ # T=1, F=z/(−2z+1), Fw=(z−2)/(−2z+1), V1=V2=1
547
+ # Generalised plant: [[Fw,F],[0,V2],[-Fw,-F]] (z-domain, 3×2)
548
+ # MATLAB (Source/dsd_help.md, verbatim): K = dhinf(sys) = 1.5 (constant!),
549
+ # dahinorm(sys,K) = 3.6056. The "~0.9487" figure previously here was NOT a
550
+ # real MATLAB value -- it was Python's own (buggy) dhinf lam matching its
551
+ # own (also-buggy, K-independent) dahinorm, a self-consistent-but-wrong
552
+ # pair. dahinorm is now correctly K-dependent; the remaining mismatch below
553
+ # is real (dhinf/_polhinf still doesn't find MATLAB's true optimal K here).
554
+ # ---------------------------------------------------------------------------
555
+
556
+ def ex_dhinf_example1():
557
+ print("\n=== Discrete AHinf – example 1 ===")
558
+ T = 1.0
559
+ F = ([1, 0], [-2, 1]) # z / (-2z+1)
560
+ Fw = ([1, -2], [-2, 1]) # (z-2) / (-2z+1)
561
+ V1, V2 = ([1], [1]), ([1], [1])
562
+ # Build 3x2 generalised plant
563
+ sys_plant = [
564
+ [(nd_mul(V1, Fw)), (nd_mul(V1, F))],
565
+ [([0], [1]), V2 ],
566
+ [(nd_neg(Fw)), (nd_neg(F)) ],
567
+ ]
568
+ try:
569
+ sys_conv = _z2zeta(sys_plant)
570
+ K, err_dh = dhinf(sys_conv, T=T)
571
+ print(f" dhinf K: {_zpk_str(K)}")
572
+ print(f" dhinf lam = {err_dh:.4f} (MATLAB: K=1.5 constant, dahinorm=3.6056)")
573
+ err_da = _dahinorm(sys_conv, K, T)
574
+ print(f" dahinorm = {err_da:.4f}")
575
+ _check("dhinf lam consistent with dahinorm", err_dh, err_da, tol=0.15)
576
+ except Exception as e:
577
+ print(f" dhinf: {e}")
578
+
579
+
580
+ # ---------------------------------------------------------------------------
581
+ # Example 16 – Discrete AHinf example 2 (generic case)
582
+ # T=1, F2=1/(−z²−2.1z+1), F1=(2z²+z), Fw=(0.3z+1)
583
+ # Generalised plant: [[F2*Fw, F2*F1],[0,V2],[-F2*Fw,-F2*F1]]
584
+ # MATLAB: dhinf errOpt ~ 2.1244
585
+ # ---------------------------------------------------------------------------
586
+ def ex_dhinf_example2():
587
+ print("\n=== Discrete AHinf – example 2 (generic) ===")
588
+ T = 1.0
589
+ F2 = ([1], [-1, -2.1, 1])
590
+ F1 = ([2, 1, 0],[1] )
591
+ Fw = ([0.3, 1], [1] )
592
+ V1, V2 = ([1],[1]), ([1],[1])
593
+ F2Fw = nd_mul(F2, Fw)
594
+ F2F1 = nd_mul(F2, F1)
595
+ sys_plant = [
596
+ [(nd_mul(V1, F2Fw)), (nd_mul(V1, F2F1))],
597
+ [([0], [1]), V2 ],
598
+ [(nd_neg(F2Fw)), (nd_neg(F2F1)) ],
599
+ ]
600
+ try:
601
+ sys_conv = _z2zeta(sys_plant)
602
+ K, err_dh = dhinf(sys_conv, T=T)
603
+ print(f" dhinf K: {_zpk_str(K)}")
604
+ print(f" dhinf lam = {err_dh:.4f} (MATLAB ~2.1244)")
605
+ err_da = _dahinorm(sys_conv, K, T)
606
+ print(f" dahinorm = {err_da:.4f}")
607
+ _check("dhinf lam (tol 50%)", err_dh, 2.1244, tol=0.50)
608
+ except Exception as e:
609
+ print(f" dhinf: {e}")
610
+
611
+
612
+ # ---------------------------------------------------------------------------
613
+ # Example 17 – H2 and AHinf optimal control (line 2647)
614
+ # F1=0.051/(25s+1), F2=1/s, rho=0.1, T=1
615
+ # MATLAB: sdh2norm(K2)=3.2563, sdahinorm(K2)=11.4122, sdhinorm(K2)=11.4082
616
+ # sdh2norm(Kinf)=7.6683, sdahinorm(Kinf)=7.6683, sdhinorm(Kinf)=7.6628
617
+ # ---------------------------------------------------------------------------
618
+ def ex_h2_ahinf_control():
619
+ print("\n=== H2 and AHinf optimal control ===")
620
+ F1 = sig.TransferFunction([0.051], [25, 1])
621
+ F2 = sig.TransferFunction([1], [1, 0])
622
+ F_plant = mul(F2, F1)
623
+ rho = 0.1
624
+ T = 1.0
625
+ sys = GeneralizedPlant([
626
+ [neg(F2), neg(F_plant)],
627
+ [0, rho],
628
+ [neg(F2), neg(F_plant)],
629
+ ])
630
+ K2, err_h2 = sdh2(sys, T)
631
+ print(f" H2-optimal K2: {_zpk_str(K2)}")
632
+ print(f" sdh2 cost: {err_h2:.4g} (MATLAB sdh2norm=3.2563)")
633
+ try:
634
+ ah_K2 = sdahinorm(sys, K2, T)
635
+ print(f" sdahinorm(sys,K2,T) = {ah_K2:.4f} (MATLAB: 11.4122)")
636
+ # sdhinorm(sys, ...) -- the FULL generalized plant, matching MATLAB's
637
+ # sdhinorm(sys, K2). Python's sdhinorm is currently an AHinf alias,
638
+ # so this prints the same value as sdahinorm above, not MATLAB's true
639
+ # 11.4082 -- a documented, deliberate scope decision, not a bug.
640
+ hinf_K2 = sdhinorm(sys, K2, T)[0]
641
+ print(f" sdhinorm(sys,K2,T) = {hinf_K2:.4f} (MATLAB: 11.4082; Python's"
642
+ " sdhinorm = AHinf alias)")
643
+ except Exception as e:
644
+ print(f" norm eval: {e}")
645
+
646
+ Kinf, err_inf = sdahinf(sys, T)
647
+ print(f" AHinf-optimal Kinf: {_zpk_str(Kinf)}")
648
+ print(f" sdahinf cost: {err_inf:.4g} (MATLAB sdh2norm(Kinf)=7.6683)")
649
+ try:
650
+ ah_inf = sdahinorm(sys, Kinf, T)
651
+ print(f" sdahinorm(sys,Kinf,T) = {ah_inf:.4f} (MATLAB: 7.6683)")
652
+ except Exception as e:
653
+ print(f" sdahinorm Kinf: {e}")
654
+
655
+
656
+ # ---------------------------------------------------------------------------
657
+ # Example 18 – Mixed H2/AHinf optimization (line 2807)
658
+ # F=1/(5s^2+s), kappa=1, T=1
659
+ # MATLAB: sdh2norm(KH2)=0.8153, sdahinorm(KH2)=1.7326
660
+ # sdh2norm(Kinf)=1.2251, sdahinorm(Kinf)=1.2251
661
+ # sdh2norm(Kmix)=0.9498, sdahinorm(Kmix)=1.3436
662
+ # ---------------------------------------------------------------------------
663
+ def ex_mixed_h2_ahinf():
664
+ print("\n=== Mixed H2/AHinf optimization ===")
665
+ F = sig.TransferFunction([1], [5, 1, 0])
666
+ kappa = 1.0
667
+ T = 1.0
668
+ sys = GeneralizedPlant([
669
+ [neg(F), neg(F)],
670
+ [0, kappa],
671
+ [neg(F), neg(F)],
672
+ ])
673
+ KH2, err_H2 = sdh2(sys, T)
674
+ print(f" H2-optimal KH2: {_zpk_str(KH2)}")
675
+ print(f" sdh2 cost: {err_H2:.4g} (MATLAB sdh2norm=0.8153)")
676
+ try:
677
+ ah_KH2 = sdahinorm(F, KH2, T)
678
+ print(f" sdahinorm(F,KH2,T) = {ah_KH2:.4f} (MATLAB: 1.7326)")
679
+ except Exception as e:
680
+ print(f" sdahinorm KH2: {e}")
681
+
682
+ Kinf, err_Kinf = sdahinf(sys, T)
683
+ print(f" AHinf-optimal Kinf: {_zpk_str(Kinf)}")
684
+ try:
685
+ ah_Kinf = sdahinorm(F, Kinf, T)
686
+ print(f" sdahinorm(F,Kinf,T) = {ah_Kinf:.4f} (MATLAB: 1.2251)")
687
+ except Exception as e:
688
+ print(f" sdahinorm Kinf: {e}")
689
+
690
+ try:
691
+ Kmix, err_mix = sdh2hinf(sys, T, 0.5, 2, 1)
692
+ print(f" Mixed Kmix (rho=0.5): {_zpk_str(Kmix)}")
693
+ ah_mix = sdahinorm(F, Kmix, T)
694
+ print(f" sdahinorm(F,Kmix,T) = {ah_mix:.4f} (MATLAB: 1.3436)")
695
+ except Exception as e:
696
+ print(f" sdh2hinf/sdahinorm: {e}")
697
+
698
+
699
+ # ---------------------------------------------------------------------------
700
+ # Example 19 – L2 and AHinf optimal tracking (line 2975)
701
+ # R=1/s, F=1/(4s^2+0.5s+1), rho=0.12, T=1
702
+ # MATLAB: sdl2err(K2)=1.0321, sdtrhinferr(K2)=1.2213
703
+ # sdl2err(Kinf)=1.1118, sdtrhinferr(Kinf)=1.0544
704
+ # ---------------------------------------------------------------------------
705
+ def ex_l2_ahinf_tracking():
706
+ print("\n=== L2 and AHinf optimal tracking ===")
707
+ R = sig.TransferFunction([1], [1, 0])
708
+ F = sig.TransferFunction([1], [4, 0.5, 1])
709
+ rho = 0.12
710
+ T = 1.0
711
+ sys = GeneralizedPlant([
712
+ [R, neg(F)],
713
+ [mul(rho, R), neg(rho)],
714
+ [R, neg(F)],
715
+ ])
716
+ K2, err2 = sdl2(sys, T)
717
+ print(f" L2-optimal K2: {_zpk_str(K2)}")
718
+ print(f" sdl2 cost: {err2:.4g} (MATLAB sdl2err=1.0321)")
719
+ try:
720
+ l2_K2 = sdl2err(sys, K2, T)
721
+ print(f" sdl2err(sys,K2,T) = {l2_K2:.4f} (MATLAB: 1.0321)")
722
+ ah_K2 = sdtrhinferr(sys, K2, T)
723
+ print(f" sdtrhinferr = {ah_K2:.4f} (MATLAB: 1.2213)")
724
+ except Exception as e:
725
+ print(f" norm eval: {e}")
726
+
727
+ try:
728
+ Kinf, err_tr = sdtrhinf(sys, T)
729
+ print(f" AHinf-optimal Kinf: {_zpk_str(Kinf)}")
730
+ l2_inf = sdl2err(sys, Kinf, T)
731
+ print(f" sdl2err(sys,Kinf,T) = {l2_inf:.4f} (MATLAB: 1.1118)")
732
+ ah_inf = sdtrhinferr(sys, Kinf, T)
733
+ print(f" sdtrhinferr(Kinf) = {ah_inf:.4f} (MATLAB: 1.0544)")
734
+ except Exception as e:
735
+ print(f" sdtrhinf: {e}")
736
+
737
+
738
+ # ---------------------------------------------------------------------------
739
+ # Example 20 – H2-optimal preview control (line 3135)
740
+ # Fr=1/(5s+1), Fn=0.2, F=1/(s-1) with delay 1.5, Q=exp(-2s)
741
+ # sys=[Q*Fr 0 -F; Fr Fn -F], T=1
742
+ # MATLAB: sdh2norm(sys,K)^2=11.6701
743
+ # ---------------------------------------------------------------------------
744
+ def ex_h2_preview():
745
+ print("\n=== H2-optimal preview control ===")
746
+ Fr = sig.TransferFunction([1], [5, 1])
747
+ Fn = 0.2
748
+ # F = 1/(s-1) with delay 1.5 → 3rd-order Padé
749
+ pn15, pd15 = _pade(1.5, 3)
750
+ F_delay = sig.TransferFunction(np.polymul([1], pn15),
751
+ np.polymul([1, -1], pd15))
752
+ # Q = exp(-2s) → 3rd-order Padé
753
+ pn2, pd2 = _pade(2.0, 3)
754
+ Q = sig.TransferFunction(pn2, pd2)
755
+ T = 1.0
756
+ sys = GeneralizedPlant([
757
+ [mul(Q, Fr), 0, neg(F_delay)],
758
+ [Fr, Fn, neg(F_delay)],
759
+ ])
760
+ K, err = sdh2(sys, T)
761
+ print(f" sdh2 K: {_zpk_str(K)}")
762
+ print(f" sdh2 cost^2: {err**2:.4f} (MATLAB: 11.6701)")
763
+ try:
764
+ norm_sq = sdh2norm(F_delay, K, T) ** 2
765
+ print(f" sdh2norm(F_delay,K,T)^2 = {norm_sq:.4f}")
766
+ except Exception as e:
767
+ print(f" sdh2norm: {e}")
768
+
769
+
770
+ # ---------------------------------------------------------------------------
771
+ # Example 21 – L2-optimal preview control (line 3243)
772
+ # R=1/(s^2+s), F=1/(5s+1) with delay 1.5, Q=1/(0.1s+1)
773
+ # preview=2 -- MATLAB removes the non-causal preview block by placing the
774
+ # delay in the ideal operator instead (Q.iodelay=preview) plus a remainder
775
+ # delay on R (R.iodelay=theta); see sdl2's refdelay parameter, which
776
+ # applies this sigma/theta split internally via the EXACT modified
777
+ # Z-transform (no Pade approximation needed -- unlike the delay-free
778
+ # F.iodelay=1.5, handled via udelay).
779
+ # sys=[Q*R -F; R -F], T=1
780
+ # MATLAB: sdl2err=0.0517
781
+ # ---------------------------------------------------------------------------
782
+ def ex_l2_preview():
783
+ print("\n=== L2-optimal preview control ===")
784
+ R = sig.TransferFunction([1], [1, 1, 0])
785
+ F = sig.TransferFunction([1], [5, 1])
786
+ Q = sig.TransferFunction([1], [0.1, 1])
787
+ T = 1.0
788
+ sys = GeneralizedPlant([
789
+ [mul(Q, R), neg(F)],
790
+ [R, neg(F)],
791
+ ])
792
+ K, err = sdl2(sys, T, udelay=1.5, refdelay=2.0)
793
+ print(f" sdl2 K: {_zpk_str(K)}")
794
+ print(f" sdl2 cost: {err:.4f} (MATLAB sdl2err=0.0517)")
795
+
796
+
797
+ # ---------------------------------------------------------------------------
798
+ # Example 22 – Optimal 2-DOF preview control (line 3387)
799
+ # Same plant setup as example 21 (exNo=2's unstable F=1/(5s-1) branch,
800
+ # matching demo_2dofp.m); 1-DOF K from sdl2, then KR from sd2dof.
801
+ # MATLAB: 1-DOF sdl2err=2.7072, 2-DOF sd2doferr=0.0602
802
+ # ---------------------------------------------------------------------------
803
+ def ex_2dof_preview():
804
+ print("\n=== Optimal 2-DOF preview control ===")
805
+ R = sig.TransferFunction([1], [1, 1, 0])
806
+ F = sig.TransferFunction([1], [5, -1])
807
+ Q = sig.TransferFunction([1], [0.1, 1])
808
+ T = 1.0
809
+ # 1-DOF system: exact delay support via sdl2's udelay/refdelay (see
810
+ # ex_l2_preview) -- no Pade approximation needed.
811
+ sys = GeneralizedPlant([
812
+ [mul(Q, R), neg(F)],
813
+ [R, neg(F)],
814
+ ])
815
+ K, err_1dof = sdl2(sys, T, udelay=1.5, refdelay=2.0)
816
+ print(f" 1-DOF K: {_zpk_str(K)}")
817
+ print(f" 1-DOF sdl2 cost: {err_1dof:.4f} (MATLAB sdl2err=2.7072)")
818
+
819
+ # 2-DOF feedforward (sd2dof): exact delay support via the same
820
+ # udelay/refdelay mechanism (sd2dof/_sd2dofcoef).
821
+ sys_2dof = GeneralizedPlant([
822
+ [mul(Q, R), neg(F)],
823
+ [R, 0],
824
+ [0, neg(F)],
825
+ ], n_meas=2)
826
+ KR, err_2dof = sd2dof(sys_2dof, K, T, udelay=1.5, refdelay=2.0)
827
+ print(f" 2-DOF KR: {_zpk_str(KR)}")
828
+ print(f" 2-DOF sd2dof cost: {err_2dof:.4f} (MATLAB sd2doferr=0.0602)")
829
+
830
+
831
+ # ---------------------------------------------------------------------------
832
+ # Example 23 – Reduced-order H2-optimal control (line 3556)
833
+ # Ship course stabilization; F=0.051/(25s^2+s), T=1
834
+ # Full-order H2 optimal, then reduced order 1 via modsdh2
835
+ # MATLAB: full sdh2norm=0.0190, reduced cost≈0.0193
836
+ # ---------------------------------------------------------------------------
837
+ def ex_reduced_h2():
838
+ print("\n=== Reduced-order H2-optimal control (ship course) ===")
839
+ F = sig.TransferFunction([0.051], [25, 1, 0])
840
+ Sw = sig.TransferFunction([0.0757], [1, 0, 2.489, 0, 1.848])
841
+ Fw_zpk, _ = sfactor(Sw)
842
+ Fw = Fw_zpk.to_tf()
843
+ rho = np.sqrt(0.1)
844
+ T = 1.0
845
+ sys = GeneralizedPlant([
846
+ [mul(F, Fw), F],
847
+ [0, rho],
848
+ [neg(mul(F, Fw)), neg(F)],
849
+ ])
850
+ # Full-order optimal
851
+ Kopt, err_opt = sdh2(sys, T)
852
+ print(f" Full-order Kopt: {_zpk_str(Kopt)}")
853
+ print(f" Full-order cost: {err_opt:.4f} (MATLAB sdh2norm=0.0190)")
854
+
855
+ # Reduced-order via modsdh2 with full generalized plant (P22 extracted internally)
856
+ print(" Searching reduced-order (ord=1, global randsearch)...")
857
+ Kred, cost_red = modsdh2(sys, T, ord_K=1, alpha=0.0, beta=np.inf,
858
+ method='randsearch', n_iter=500)
859
+ print(f" Reduced-order K (ord=1): {_zpk_str(Kred)}")
860
+ print(f" Reduced-order cost: {cost_red:.4f} (MATLAB cost≈0.0193)")
861
+
862
+ # Local refinement
863
+ Kloc, cost_loc = modsdh2(sys, T, ord_K=1, alpha=0.0, beta=np.inf,
864
+ method='dual_annealing', n_iter=200)
865
+ print(f" After local opt: K = {_zpk_str(Kloc)}")
866
+ print(f" Local opt cost: {cost_loc:.4f}")
867
+
868
+
869
+ # ---------------------------------------------------------------------------
870
+ # Example 24 – Optimal integral control (line 3725)
871
+ # F1=0.0694/(18.22s+1), F2=1/s, Fw given, rho=2, T=2
872
+ # Full-order K marginally stable (z=1 pole); reduced order with integrator
873
+ # MATLAB: full sdh2norm=7.2492 (marginal), reduced cost≈8.4768
874
+ # ---------------------------------------------------------------------------
875
+ def ex_integral_control():
876
+ print("\n=== Optimal integral control ===")
877
+ F1 = sig.TransferFunction([0.0694], [18.22, 1])
878
+ F2 = sig.TransferFunction([1], [1, 0])
879
+ lam = 0.3; w0 = 0.3; sigma_w = 7.25
880
+ Fw = sig.TransferFunction([2*lam*w0*sigma_w, 0], [1, 2*lam*w0, w0**2])
881
+ rho = 2.0
882
+ T = 2.0
883
+ F_plant = mul(F2, F1)
884
+ sys = GeneralizedPlant([
885
+ [mul(F2, Fw), F_plant],
886
+ [0, rho],
887
+ [neg(mul(F2, Fw)), neg(F_plant)],
888
+ ])
889
+ # Full-order optimal (will have z=1 pole → marginally stable)
890
+ Kopt, err_opt = sdh2(sys, T)
891
+ print(f" Full-order Kopt: {_zpk_str(Kopt)}")
892
+ print(f" Full-order cost: {err_opt:.4f} (MATLAB sdh2norm=7.2492, marginal)")
893
+
894
+ # Reduced-order with integrator constraint via modsdh2
895
+ # Note: Python modsdh2 uses modal parameterization (no explicit dK0 constraint)
896
+ # We require ord=2 and alpha=0.02, beta=2 to force stable controller
897
+ print(" Searching reduced-order with integrator (ord=2, alpha=0.02, beta=2)...")
898
+ Kred, cost_red = modsdh2(sys, T, ord_K=2, alpha=0.02, beta=2,
899
+ method='randsearch', n_iter=800)
900
+ print(f" Reduced-order K (ord=2): {_zpk_str(Kred)}")
901
+ print(f" Reduced-order cost: {cost_red:.4f} (MATLAB cost≈8.4768)")
902
+
903
+
904
+ # ---------------------------------------------------------------------------
905
+ # Example 25 – Reduced-order L2-optimal control (line 3920)
906
+ # R=1/s, F=10/(2s^2+s), Q=1/(s+1)^2, T=0.2
907
+ # Full-order sdl2err≈1.2116e-8, reduced order 1 modsdl2 cost≈4.752e-7
908
+ # ---------------------------------------------------------------------------
909
+ def ex_reduced_l2():
910
+ print("\n=== Reduced-order L2-optimal control ===")
911
+ R = sig.TransferFunction([1], [1, 0])
912
+ F = sig.TransferFunction([10], [2, 1, 0])
913
+ Q = sig.TransferFunction([1], [1, 2, 1])
914
+ T = 0.2
915
+ sys = GeneralizedPlant([
916
+ [mul(Q, R), neg(F)],
917
+ [R, neg(F)],
918
+ ])
919
+ # Full-order optimal
920
+ Kopt, err_opt = sdl2(sys, T)
921
+ print(f" Full-order Kopt: {_zpk_str(Kopt)}")
922
+ print(f" Full-order cost: {err_opt:.4e} (MATLAB sdl2err≈1.2116e-8)")
923
+ try:
924
+ err_full = sdl2err(sys, Kopt, T)
925
+ print(f" sdl2err(sys,Kopt,T) = {err_full:.4e} (MATLAB: 1.2116e-8)")
926
+ except Exception as e:
927
+ print(f" sdl2err: {e}")
928
+
929
+ # Reduced-order via modsdl2 with full generalized plant
930
+ print(" Searching reduced-order (ord=1, alpha=0.1)...")
931
+ Kred, cost_red = modsdl2(sys, T, ord_K=1, alpha=0.1, beta=np.inf,
932
+ method='randsearch', n_iter=500)
933
+ print(f" Reduced-order K (ord=1): {_zpk_str(Kred)}")
934
+ print(f" Reduced-order cost: {cost_red:.4e} (MATLAB cost≈4.752e-7)")
935
+
936
+ Kloc, cost_loc = modsdl2(sys, T, ord_K=1, alpha=0.1, beta=np.inf,
937
+ method='dual_annealing', n_iter=300)
938
+ print(f" After local opt: K = {_zpk_str(Kloc)}")
939
+ print(f" Local opt cost: {cost_loc:.4e}")
940
+
941
+
942
+ # ---------------------------------------------------------------------------
943
+ # Example 26 – Reduced-order redesign (line 4101)
944
+ # F=1/(s^2-s), Kc=(5s+1)/(s+3), Q=feedback(F*Kc,1), R=1/s, T=0.5
945
+ # Full-order sdl2err≈2.7771e-4, reduced order 2 modsdl2 cost≈0.0059
946
+ # ---------------------------------------------------------------------------
947
+ def ex_reduced_redesign():
948
+ print("\n=== Reduced-order redesign ===")
949
+ R = sig.TransferFunction([1], [1, 0])
950
+ F = sig.TransferFunction([1], [1, -1, 0])
951
+ Kc = sig.TransferFunction([5, 1], [1, 3])
952
+ Q = feedback(mul(F, Kc))
953
+ T = 0.5
954
+ sys = GeneralizedPlant([
955
+ [mul(add(Q, neg(F)), R), F],
956
+ [mul(F, R), neg(F)],
957
+ ])
958
+ # Full-order optimal
959
+ Kopt, err_opt = sdl2(sys, T)
960
+ print(f" Full-order Kopt: {_zpk_str(Kopt)}")
961
+ print(f" Full-order cost: {err_opt:.4e} (MATLAB sdl2err≈2.7771e-4)")
962
+ try:
963
+ err_full = sdl2err(sys, Kopt, T)
964
+ print(f" sdl2err(sys,Kopt,T) = {err_full:.4e} (MATLAB: 2.7771e-4)")
965
+ except Exception as e:
966
+ print(f" sdl2err: {e}")
967
+
968
+ # Reduced-order via modsdl2 with full generalized plant
969
+ print(" Searching reduced-order (ord=2, alpha=0.001)...")
970
+ Kred, cost_red = modsdl2(sys, T, ord_K=2, alpha=0.001, beta=np.inf,
971
+ method='randsearch', n_iter=800)
972
+ print(f" Reduced-order K (ord=2): {_zpk_str(Kred)}")
973
+ print(f" Reduced-order cost: {cost_red:.4f} (MATLAB cost≈0.0059)")
974
+
975
+ Kloc, cost_loc = modsdl2(sys, T, ord_K=2, alpha=0.001, beta=np.inf,
976
+ method='dual_annealing', n_iter=400)
977
+ print(f" After local opt: K = {_zpk_str(Kloc)}")
978
+ print(f" Local opt cost: {cost_loc:.4f}")
979
+
980
+
981
+ # ---------------------------------------------------------------------------
982
+ # registry & runner
983
+ # ---------------------------------------------------------------------------
984
+
985
+ _ALL = {
986
+ "getting_started": ex_getting_started,
987
+ "deterministic_l2": ex_deterministic_l2,
988
+ "stochastic_h2": ex_stochastic_h2,
989
+ "first_order_filter": ex_first_order_filter,
990
+ "filter_with_delay": ex_filter_with_delay,
991
+ "second_order_filter": ex_second_order_filter,
992
+ "generalized_hold": ex_generalized_hold,
993
+ "disturbance_atten": ex_disturbance_attenuation,
994
+ "ship_course": ex_ship_course,
995
+ "double_integrator": ex_double_integrator,
996
+ "l2_tracking": ex_l2_tracking,
997
+ "l2_redesign": ex_l2_redesign,
998
+ "2dof_tracking": ex_2dof_tracking,
999
+ "ahinf_prediction": ex_ahinf_prediction,
1000
+ "dhinf_ex1": ex_dhinf_example1,
1001
+ "dhinf_ex2": ex_dhinf_example2,
1002
+ "h2_ahinf_control": ex_h2_ahinf_control,
1003
+ "mixed_h2_ahinf": ex_mixed_h2_ahinf,
1004
+ "l2_ahinf_tracking": ex_l2_ahinf_tracking,
1005
+ "h2_preview": ex_h2_preview,
1006
+ "l2_preview": ex_l2_preview,
1007
+ "2dof_preview": ex_2dof_preview,
1008
+ "reduced_h2": ex_reduced_h2,
1009
+ "integral_control": ex_integral_control,
1010
+ "reduced_l2": ex_reduced_l2,
1011
+ "reduced_redesign": ex_reduced_redesign,
1012
+ }
1013
+
1014
+
1015
+ def main():
1016
+ import warnings
1017
+ warnings.filterwarnings('ignore')
1018
+ names = sys.argv[1:]
1019
+ if names:
1020
+ for n in names:
1021
+ if n in _ALL:
1022
+ try:
1023
+ _ALL[n]()
1024
+ except Exception as e:
1025
+ print(f" ERROR in {n}: {e}")
1026
+ import traceback; traceback.print_exc()
1027
+ else:
1028
+ print(f"Unknown example '{n}'. Available: {list(_ALL)}")
1029
+ else:
1030
+ fail = 0
1031
+ for name, fn in _ALL.items():
1032
+ try:
1033
+ fn()
1034
+ except Exception as e:
1035
+ print(f"\n ERROR in {name}: {e}")
1036
+ import traceback; traceback.print_exc()
1037
+ fail += 1
1038
+ print(f"\n{'='*60}")
1039
+ print(f"Ran {len(_ALL)} examples, {fail} errors")
1040
+
1041
+
1042
+ if __name__ == "__main__":
1043
+ main()