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
directsd/__init__.py ADDED
@@ -0,0 +1,141 @@
1
+ """
2
+ DirectSD - Direct Sampled-Data Control Systems Toolbox
3
+ Python port of the MATLAB DirectSD Toolbox v3.0 by Konstantin Polyakov.
4
+
5
+ Modules:
6
+ polynomial - Polynomial class and operations
7
+ linalg - Linear algebra routines
8
+ design - Polynomial/H2/Hinf controller design
9
+ analysis - Sampled-data system analysis (norms, margins)
10
+ sspace - State-space design methods
11
+ glopt - Global optimization routines
12
+ """
13
+
14
+ __version__ = "0.1.0"
15
+ __author__ = "Python port. Original MATLAB toolbox by Konstantin Polyakov (1995-2006)."
16
+
17
+ # Generalized plant
18
+ from directsd.sspace.plant import GeneralizedPlant
19
+
20
+ # Polynomial objects and operations
21
+ from directsd.polynomial.poln import (
22
+ Poln,
23
+ s_var, z_var, q_var, p_var, d_var,
24
+ )
25
+ from directsd.polynomial.operations import (
26
+ compat, deg, gcd, coprime, triple,
27
+ factor, striplz, recip, vec, delzero,
28
+ )
29
+ from directsd.polynomial.diophantine import dioph, dioph2
30
+ from directsd.polynomial.spectral import sfactor, sfactfft
31
+ from directsd.polynomial.transforms import ztrm, dtfm
32
+
33
+ # Linear algebra
34
+ from directsd.linalg.matrices import toep, hank, givens, house, lyap, dlyap
35
+ from directsd.linalg.linsys import linsys
36
+
37
+ # Analysis
38
+ from directsd.analysis.norms import (
39
+ sdh2norm, sdhinorm, dinfnorm, dahinorm, sdfreq,
40
+ )
41
+ from directsd.analysis.charpol import charpol, sdmargin
42
+ from directsd.analysis.errors import quaderr, sdl2err, sd2doferr
43
+
44
+ # Design
45
+ from directsd.design.polynomial import (
46
+ ch2, sdh2, sdl2,
47
+ sdahinf, sdahinorm,
48
+ sdh2hinf,
49
+ sdtrhinf, sdtrhinferr,
50
+ modsdh2, modsdl2,
51
+ dhinf, sd2dof, split2dof, polquad, polhinf, whquad, ssquad, psigain, polopth2, dtfm2,
52
+ )
53
+ from directsd.sspace.design import (
54
+ h2reg, hinfreg, sdh2reg, sdhinfreg, sdfast, separss,
55
+ sdh2simple, sdgh2mod, sdnorm, sdsim,
56
+ )
57
+
58
+ # Global optimization
59
+ from directsd.glopt.optimize import (
60
+ neldermead, simanneal, randsearch, crandsearch,
61
+ sector, banana,
62
+ )
63
+
64
+ __all__ = [
65
+ # Generalized plant
66
+ 'GeneralizedPlant',
67
+ # Polynomial
68
+ 'Poln', 's_var', 'z_var', 'q_var', 'p_var', 'd_var',
69
+ 'compat', 'deg', 'gcd', 'coprime', 'triple',
70
+ 'factor', 'striplz', 'recip', 'vec', 'delzero', 'zpk',
71
+ 'dioph', 'dioph2',
72
+ 'sfactor', 'sfactfft',
73
+ 'ztrm', 'dtfm',
74
+ # LinAlg
75
+ 'toep', 'hank', 'givens', 'house', 'lyap', 'dlyap',
76
+ 'linsys',
77
+ # Analysis
78
+ 'charpol', 'sdmargin',
79
+ 'sdh2norm', 'sdhinorm', 'dinfnorm', 'dahinorm',
80
+ 'quaderr', 'sdl2err', 'sd2doferr',
81
+ # Design
82
+ 'ch2', 'sdh2', 'sdl2',
83
+ 'sdahinf', 'sdahinorm',
84
+ 'sdh2hinf',
85
+ 'sdtrhinf', 'sdtrhinferr',
86
+ 'modsdh2', 'modsdl2',
87
+ 'dhinf', 'sd2dof', 'split2dof', 'polquad', 'polhinf', 'whquad', 'ssquad', 'psigain', 'polopth2', 'dtfm2',
88
+ 'h2reg', 'hinfreg', 'sdh2reg', 'sdhinfreg', 'sdfast', 'separss',
89
+ 'sdh2simple', 'sdgh2mod', 'sdnorm', 'sdsim',
90
+ 'sdfreq', 'diophsys', 'diophsys2',
91
+ # Convex synthesis (available when cvxpy is installed)
92
+ 'sdl1_reg', 'sd_mixed_h2_l1', 'sd_constrained',
93
+ 'sdl1norm', 'youla_basis',
94
+ # Optimization
95
+ 'neldermead', 'simanneal', 'randsearch', 'crandsearch',
96
+ 'sector', 'banana',
97
+ # Advanced glopt
98
+ 'updateopt', 'uniproj', 'u2range',
99
+ 'randbeta', 'randgamma', 'sa_testfun',
100
+ 'val2bin', 'bin2val', 'coord2hilb', 'hilb2coord',
101
+ 'r2range', 'r1range', 'admproj', 'par2cp', 'cp2par', 'guesspoles',
102
+ 'k2ksi', 'go_par2k', 'f_sdh2p', 'f_sdl2p', 'go_sdh2p', 'go_sdl2p',
103
+ 'sasimplex', 'arandsearch',
104
+ 'infglob', 'infglobc', 'optglob', 'optglobc',
105
+ ]
106
+
107
+ # Utilities
108
+ from directsd.polynomial.utils import (
109
+ bilintr, improper, tf2nd, separtf, sumzpk, bilinss, zpk,
110
+ )
111
+ from directsd.polynomial.diophantine import diophsys, diophsys2
112
+
113
+ # Lifting (first-class module)
114
+ from directsd.design.lifting import lift_h2, lift_l2, compute_gamma
115
+
116
+ # Additional norm functions
117
+ from directsd.analysis.norms import h2norm_ct, hinfnorm_ct
118
+
119
+ # Enhanced optimization
120
+ from directsd.glopt.optimize import dual_annealing
121
+
122
+ # Convex synthesis (optional — requires cvxpy)
123
+ try:
124
+ from directsd.design.convex import (
125
+ sdl1_reg, sd_mixed_h2_l1, sd_constrained,
126
+ sdl1norm, youla_basis,
127
+ )
128
+ _CONVEX_AVAILABLE = True
129
+ except ImportError:
130
+ _CONVEX_AVAILABLE = False
131
+
132
+ # Advanced global optimization variants (dsdglopt port)
133
+ from directsd.glopt.advanced import (
134
+ updateopt, uniproj, u2range,
135
+ randbeta, randgamma, sa_testfun,
136
+ val2bin, bin2val, coord2hilb, hilb2coord,
137
+ r2range, r1range, admproj, par2cp, cp2par, guesspoles,
138
+ k2ksi, go_par2k, f_sdh2p, f_sdl2p, go_sdh2p, go_sdl2p,
139
+ sasimplex, arandsearch,
140
+ infglob, infglobc, optglob, optglobc,
141
+ )
@@ -0,0 +1,4 @@
1
+ from directsd.analysis.norms import sdh2norm, sdhinorm, dinfnorm, dahinorm, sdfreq
2
+ from directsd.analysis.charpol import charpol, sdmargin
3
+ from directsd.analysis.errors import quaderr, sdl2err, sd2doferr
4
+ from directsd.analysis.norms import h2norm_ct, hinfnorm_ct
@@ -0,0 +1,89 @@
1
+ """
2
+ Characteristic polynomial and stability analysis.
3
+
4
+ Ports of: charpol, sdmargin
5
+ """
6
+
7
+ import numpy as np
8
+
9
+
10
+ def charpol(plant, K, T=None, hold='zoh'):
11
+ """
12
+ Characteristic polynomial of a sampled-data closed-loop system.
13
+
14
+ Parameters
15
+ ----------
16
+ plant : (num, den) tuple or scipy lti
17
+ Continuous-time plant P22 channel.
18
+ K : (num, den) tuple or scipy dlti
19
+ Discrete-time controller.
20
+ T : float, optional
21
+ Sampling period (required when K is a tuple).
22
+ hold : str
23
+ Hold type: 'zoh' (default).
24
+
25
+ Returns
26
+ -------
27
+ Delta : np.ndarray
28
+ Monic characteristic polynomial coefficients.
29
+ """
30
+ try:
31
+ import scipy.signal as sig
32
+ except ImportError:
33
+ raise ImportError("scipy is required.")
34
+
35
+ if isinstance(K, sig.dlti):
36
+ T = K.dt
37
+ Knum, Kden = K.num, K.den
38
+ elif isinstance(K, tuple):
39
+ Knum, Kden = K
40
+ if T is None:
41
+ raise ValueError("T must be provided when K is a tuple")
42
+ else:
43
+ raise TypeError(f"Unsupported controller type {type(K)}")
44
+
45
+ if isinstance(plant, sig.lti):
46
+ Pnum, Pden = plant.num, plant.den
47
+ elif isinstance(plant, tuple):
48
+ Pnum, Pden = plant
49
+ else:
50
+ raise TypeError(f"Unsupported plant type {type(plant)}")
51
+
52
+ from directsd.polynomial.transforms import dtfm
53
+ D22num, D22den = dtfm((Pnum, Pden), T, hold=hold)
54
+
55
+ # 1 + D22*K => D22_den * K_den + D22_num * K_num (over D22_den * K_den)
56
+ char_num = np.polyadd(
57
+ np.polymul(D22den, Kden),
58
+ np.polymul(D22num, Knum)
59
+ )
60
+
61
+ # Normalize to monic
62
+ char_num = np.real_if_close(char_num / char_num[0], tol=1e6)
63
+ return char_num
64
+
65
+
66
+ def sdmargin(plant, K, T=None):
67
+ """
68
+ Stability margin for a sampled-data system.
69
+
70
+ Parameters
71
+ ----------
72
+ plant : (num, den) tuple or scipy lti
73
+ K : (num, den) tuple or scipy dlti
74
+ T : float, optional
75
+
76
+ Returns
77
+ -------
78
+ margin : float
79
+ Minimum distance of closed-loop poles from the unit circle boundary
80
+ (positive = stable, negative = unstable).
81
+ poles : np.ndarray
82
+ Closed-loop poles in z-plane.
83
+ """
84
+ Delta = charpol(plant, K, T)
85
+ poles = np.roots(Delta)
86
+ # Distance from unit circle: 1 - |pole| for each pole
87
+ # Stability margin = min(1 - |pole|): positive means all poles inside unit circle
88
+ margin = float(1.0 - np.max(np.abs(poles)))
89
+ return margin, poles
@@ -0,0 +1,185 @@
1
+ """
2
+ Integral error functions for sampled-data systems.
3
+
4
+ Ports of: quaderr, sdl2err, sd2doferr
5
+ """
6
+
7
+ import numpy as np
8
+ from directsd.sspace.plant import GeneralizedPlant
9
+
10
+ _trapezoid = getattr(np, 'trapezoid', getattr(np, 'trapz', None))
11
+
12
+
13
+ def quaderr(plant_cl, T, N=4096):
14
+ """
15
+ Squared integral error for a sampled-data closed-loop system.
16
+
17
+ Computes the H2-norm squared of a discrete-time closed-loop system
18
+ evaluated over one period via frequency-domain integration.
19
+
20
+ Parameters
21
+ ----------
22
+ plant_cl : (num, den) tuple or scipy dlti
23
+ Closed-loop discrete-time transfer function.
24
+ T : float
25
+ Sampling period.
26
+ N : int
27
+ Number of frequency points.
28
+
29
+ Returns
30
+ -------
31
+ err : float
32
+ Squared integral error (H2-norm squared).
33
+ """
34
+ try:
35
+ import scipy.signal as sig
36
+ except ImportError:
37
+ raise ImportError("scipy is required.")
38
+
39
+ if isinstance(plant_cl, sig.dlti):
40
+ num, den = plant_cl.num, plant_cl.den
41
+ elif isinstance(plant_cl, tuple):
42
+ num, den = plant_cl
43
+ else:
44
+ raise TypeError(f"Unsupported type {type(plant_cl)}")
45
+
46
+ w = np.linspace(-np.pi, np.pi, N)
47
+ z = np.exp(1j * w)
48
+ H = np.polyval(num, z) / (np.polyval(den, z) + 1e-300)
49
+ err = _trapezoid(np.abs(H) ** 2, w) / (2 * np.pi)
50
+ return float(np.real(err))
51
+
52
+
53
+ def sdl2err(plant, K, T=None):
54
+ """
55
+ Integral quadratic (L2) error for a sampled-data system.
56
+
57
+ Mirrors MATLAB sdl2err.m (ss path)::
58
+
59
+ dsysL2 = sdh2simple(sys, K.Ts) % lift_l2
60
+ err = K.Ts * norm(lft(dsysL2, K))^2 % squared energy, NOT sqrt
61
+
62
+ Parameters
63
+ ----------
64
+ plant : scipy.signal.StateSpace or (num, den) tuple or scipy lti
65
+ Continuous-time generalised plant (preferred) or SISO plant.
66
+ Pass the **same** plant that was used for ``sdl2`` design to get
67
+ results comparable to MATLAB's ``sdl2err(sys, K)``.
68
+ For SISO plants a standard regulation generalized plant is constructed
69
+ automatically via ``_parse_plant``.
70
+ K : (num, den) tuple or scipy.signal.StateSpace (discrete)
71
+ Discrete-time controller.
72
+ T : float, optional
73
+ Sampling period (inferred from K when K is a StateSpace/dlti).
74
+
75
+ Returns
76
+ -------
77
+ err : float
78
+ Square-root of L2 cost = sqrt(T · ||Fcl||²_H2).
79
+ """
80
+ import scipy.signal as sig
81
+ from directsd.design.lifting import lift_l2, lft_dt
82
+ from directsd.design.polynomial import _parse_plant
83
+ from directsd.linalg.minreal import Minreal
84
+ from directsd.analysis.norms import _h2norm_dt, _K_to_ss
85
+
86
+ K_ss, T = _K_to_ss(K, T)
87
+
88
+ plant_ss, n_meas, n_ctrl = _parse_plant(plant)
89
+
90
+ n_K_in = K_ss.B.shape[1]
91
+ if n_K_in < n_meas:
92
+ raise ValueError(
93
+ f"sdl2err: controller has {n_K_in} input(s) but plant has "
94
+ f"n_meas={n_meas} measurement output(s). "
95
+ f"For 2-DOF systems call sd2doferr(sys, K_fb, K_ff, T) instead."
96
+ )
97
+
98
+ plant_min = Minreal.ss(plant_ss)
99
+
100
+ try:
101
+ dsysL2 = lift_l2(plant_min, T, n_meas=n_meas, n_ctrl=n_ctrl)
102
+ except Exception as exc:
103
+ import warnings
104
+ warnings.warn(f"sdl2err: lift_l2 failed ({exc})")
105
+ return float('nan')
106
+
107
+ try:
108
+ dcl = lft_dt(dsysL2, K_ss, n_meas=n_meas, n_ctrl=n_ctrl)
109
+ h2n = _h2norm_dt(dcl.A, dcl.B, dcl.C, dcl.D)
110
+ except Exception as exc:
111
+ import warnings
112
+ warnings.warn(f"sdl2err: LFT/norm failed ({exc})")
113
+ return float('nan')
114
+
115
+ return float(max(T * h2n ** 2, 0.0)) # L2 cost = T * ‖·‖²_H2 (matches MATLAB sdl2err.m)
116
+
117
+
118
+ def sd2doferr(sys, Kfb, Kff, T=None):
119
+ """
120
+ CT L2 tracking error for a 2-DOF sampled-data system.
121
+
122
+ Parameters
123
+ ----------
124
+ sys : scipy.signal.StateSpace
125
+ Full continuous-time generalized plant with structure::
126
+
127
+ [z ] [P11 P12] [d]
128
+ [y1] = [P21 0 ] [u]
129
+ [y2] [ 0 P22]
130
+
131
+ Must have exactly 1 performance output, 2 measurement outputs
132
+ (last 2 rows), 1 exogenous input, and 1 control input (last column).
133
+ Kfb : (num, den) tuple or scipy.signal.StateSpace (discrete)
134
+ Feedback controller acting on measurement y2 (plant-output channel).
135
+ Kff : (num, den) tuple or scipy.signal.StateSpace (discrete)
136
+ Reference feedforward controller acting on measurement y1 (reference
137
+ channel).
138
+ T : float, optional
139
+ Sampling period (inferred from Kfb/Kff when they are StateSpace/dlti).
140
+
141
+ Returns
142
+ -------
143
+ err : float
144
+ Squared CT L2 tracking error T·‖F_cl‖²_H2.
145
+ """
146
+ import scipy.signal as sig
147
+ import scipy.linalg
148
+ from directsd.design.lifting import lift_l2, lft_dt
149
+ from directsd.linalg.minreal import Minreal
150
+ from directsd.analysis.norms import _h2norm_dt, _K_to_ss
151
+
152
+ Kff_ss, T = _K_to_ss(Kff, T)
153
+ Kfb_ss, _ = _K_to_ss(Kfb, T)
154
+
155
+ # Build 2-input 1-output DT controller: u = Kff*y1 + Kfb*y2
156
+ n_ff = Kff_ss.A.shape[0]
157
+ n_fb = Kfb_ss.A.shape[0]
158
+ A_k = scipy.linalg.block_diag(Kff_ss.A, Kfb_ss.A)
159
+ B_k = np.block([
160
+ [Kff_ss.B, np.zeros((n_ff, 1))],
161
+ [np.zeros((n_fb, 1)), Kfb_ss.B],
162
+ ])
163
+ C_k = np.hstack([Kff_ss.C, Kfb_ss.C])
164
+ D_k = np.hstack([Kff_ss.D, Kfb_ss.D])
165
+ K_2dof = sig.StateSpace(A_k, B_k, C_k, D_k, dt=T)
166
+
167
+ if isinstance(sys, GeneralizedPlant):
168
+ plant_ss = sys.to_statespace()
169
+ n_meas, n_ctrl = sys.n_meas, sys.n_ctrl
170
+ elif isinstance(sys, sig.StateSpace):
171
+ plant_ss = sys
172
+ n_meas, n_ctrl = 2, 1
173
+ elif isinstance(sys, sig.lti):
174
+ plant_ss = sys.to_ss()
175
+ n_meas, n_ctrl = 2, 1
176
+ else:
177
+ raise TypeError(f"sd2doferr: sys must be a StateSpace or lti, got {type(sys)}")
178
+
179
+ plant_min = Minreal.ss(plant_ss)
180
+
181
+ dsysL2 = lift_l2(plant_min, T, n_meas=n_meas, n_ctrl=n_ctrl)
182
+ dcl = lft_dt(dsysL2, K_2dof, n_meas=n_meas, n_ctrl=n_ctrl)
183
+
184
+ h2n = _h2norm_dt(dcl.A, dcl.B, dcl.C, dcl.D)
185
+ return float(max(T * h2n ** 2, 0.0))