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
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,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))
|