test_SISO_c2d¶
wield.control.SISO.test.test_SISO_c2d
This is a pytest module needing documentation
Functions
|
Transform a continuous to a discrete state-space system. :param system: The following gives the number of elements in the tuple and the interpretation: * 1: (instance of lti) * 2: (num, den) * 3: (zeros, poles, gain) * 4: (A, B, C, D) :type system: a tuple describing the system or an instance of lti :param dt: The discretization time step. :type dt: float :param method: Which method to use: * gbt: generalized bilinear transformation * bilinear: Tustin's approximation ("gbt" with alpha=0.5) * euler: Euler (or forward differencing) method ("gbt" with alpha=0) * backward_diff: Backwards differencing ("gbt" with alpha=1.0) * zoh: zero-order hold (default) * foh: first-order hold (versionadded: 1.3.0) * impulse: equivalent impulse response (versionadded: 1.3.0) :type method: str, optional :param alpha: The generalized bilinear transformation weighting parameter, which should only be specified with method="gbt", and is ignored otherwise :type alpha: float within [0, 1], optional. |
|
Test the conversions to and from ZPK representation and statespace representation using a delay filter |
Details
- cont2discrete(system, dt, method='zoh', alpha=None)[source][github]¶
Transform a continuous to a discrete state-space system. :param system: The following gives the number of elements in the tuple and
- the interpretation:
1: (instance of lti)
2: (num, den)
3: (zeros, poles, gain)
4: (A, B, C, D)
- Parameters:
dt (float) – The discretization time step.
method (str, optional) –
- Which method to use:
gbt: generalized bilinear transformation
bilinear: Tustin’s approximation (“gbt” with alpha=0.5)
euler: Euler (or forward differencing) method (“gbt” with alpha=0)
backward_diff: Backwards differencing (“gbt” with alpha=1.0)
zoh: zero-order hold (default)
foh: first-order hold (versionadded: 1.3.0)
impulse: equivalent impulse response (versionadded: 1.3.0)
alpha (float within [0, 1], optional) – The generalized bilinear transformation weighting parameter, which should only be specified with method=”gbt”, and is ignored otherwise
- Returns:
sysd – Based on the input type, the output will be of the form * (num, den, dt) for transfer function input * (zeros, poles, gain, dt) for zeros-poles-gain input * (A, B, C, D, dt) for state-space system input
- Return type:
tuple containing the discrete system
Notes
By default, the routine uses a Zero-Order Hold (zoh) method to perform the transformation. Alternatively, a generalized bilinear transformation may be used, which includes the common Tustin’s bilinear approximation, an Euler’s method technique, or a backwards differencing technique. The Zero-Order Hold (zoh) method is based on [1], the generalized bilinear approximation is based on [2] and [3], the First-Order Hold (foh) method is based on [4].
Examples
We can transform a continuous state-space system to a discrete one: >>> import matplotlib.pyplot as plt >>> from scipy.signal import cont2discrete, lti, dlti, dstep Define a continuous state-space system. >>> A = np.array([[0, 1],[-10., -3]]) >>> B = np.array([[0],[10.]]) >>> C = np.array([[1., 0]]) >>> D = np.array([[0.]]) >>> l_system = lti(A, B, C, D) >>> t, x = l_system.step(T=np.linspace(0, 5, 100)) >>> fig, ax = plt.subplots() >>> ax.plot(t, x, label=’Continuous’, linewidth=3) Transform it to a discrete state-space system using several methods. >>> dt = 0.1 >>> for method in [‘zoh’, ‘bilinear’, ‘euler’, ‘backward_diff’, ‘foh’, ‘impulse’]: … d_system = cont2discrete((A, B, C, D), dt, method=method) … s, x_d = dstep(d_system) … ax.step(s, np.squeeze(x_d), label=method, where=’post’) >>> ax.axis([t[0], t[-1], x[0], 1.4]) >>> ax.legend(loc=’best’) >>> fig.tight_layout() >>> plt.show()
References
code
docstring
""" Transform a continuous to a discrete state-space system. Parameters ---------- system : a tuple describing the system or an instance of `lti` The following gives the number of elements in the tuple and the interpretation: * 1: (instance of `lti`) * 2: (num, den) * 3: (zeros, poles, gain) * 4: (A, B, C, D) dt : float The discretization time step. method : str, optional Which method to use: * gbt: generalized bilinear transformation * bilinear: Tustin's approximation ("gbt" with alpha=0.5) * euler: Euler (or forward differencing) method ("gbt" with alpha=0) * backward_diff: Backwards differencing ("gbt" with alpha=1.0) * zoh: zero-order hold (default) * foh: first-order hold (*versionadded: 1.3.0*) * impulse: equivalent impulse response (*versionadded: 1.3.0*) alpha : float within [0, 1], optional The generalized bilinear transformation weighting parameter, which should only be specified with method="gbt", and is ignored otherwise Returns ------- sysd : tuple containing the discrete system Based on the input type, the output will be of the form * (num, den, dt) for transfer function input * (zeros, poles, gain, dt) for zeros-poles-gain input * (A, B, C, D, dt) for state-space system input Notes ----- By default, the routine uses a Zero-Order Hold (zoh) method to perform the transformation. Alternatively, a generalized bilinear transformation may be used, which includes the common Tustin's bilinear approximation, an Euler's method technique, or a backwards differencing technique. The Zero-Order Hold (zoh) method is based on [1]_, the generalized bilinear approximation is based on [2]_ and [3]_, the First-Order Hold (foh) method is based on [4]_. Examples -------- We can transform a continuous state-space system to a discrete one: >>> import matplotlib.pyplot as plt >>> from scipy.signal import cont2discrete, lti, dlti, dstep Define a continuous state-space system. >>> A = np.array([[0, 1],[-10., -3]]) >>> B = np.array([[0],[10.]]) >>> C = np.array([[1., 0]]) >>> D = np.array([[0.]]) >>> l_system = lti(A, B, C, D) >>> t, x = l_system.step(T=np.linspace(0, 5, 100)) >>> fig, ax = plt.subplots() >>> ax.plot(t, x, label='Continuous', linewidth=3) Transform it to a discrete state-space system using several methods. >>> dt = 0.1 >>> for method in ['zoh', 'bilinear', 'euler', 'backward_diff', 'foh', 'impulse']: ... d_system = cont2discrete((A, B, C, D), dt, method=method) ... s, x_d = dstep(d_system) ... ax.step(s, np.squeeze(x_d), label=method, where='post') >>> ax.axis([t[0], t[-1], x[0], 1.4]) >>> ax.legend(loc='best') >>> fig.tight_layout() >>> plt.show() References ---------- .. [1] https://en.wikipedia.org/wiki/Discretization#Discretization_of_linear_state_space_models .. [2] http://techteach.no/publications/discretetime_signals_systems/discrete.pdf .. [3] G. Zhang, X. Chen, and T. Chen, Digital redesign via the generalized bilinear transformation, Int. J. Control, vol. 82, no. 4, pp. 741-754, 2009. (https://www.mypolyuweb.hk/~magzhang/Research/ZCC09_IJC.pdf) .. [4] G. F. Franklin, J. D. Powell, and M. L. Workman, Digital control of dynamic systems, 3rd ed. Menlo Park, Calif: Addison-Wesley, pp. 204-206, 1998. """
1def cont2discrete(system, dt, method="zoh", alpha=None): 2 3 a, b, c, d = system 4 5 if method == 'gbt': 6 if alpha is None: 7 raise ValueError("Alpha parameter must be specified for the " 8 "generalized bilinear transform (gbt) method") 9 elif alpha < 0 or alpha > 1: 10 raise ValueError("Alpha parameter must be within the interval " 11 "[0,1] for the gbt method") 12 13 if method == 'gbt': 14 # This parameter is used repeatedly - compute once here 15 ima = np.eye(a.shape[0]) - alpha*dt*a 16 ad = scipy.linalg.solve(ima, np.eye(a.shape[0]) + (1.0-alpha)*dt*a) 17 bd = scipy.linalg.solve(ima, dt*b) 18 19 # Similarly solve for the output equation matrices 20 cd = scipy.linalg.solve(ima.transpose(), c.transpose()) 21 cd = cd.transpose() 22 dd = d + alpha*np.dot(c, bd) 23 24 elif method == 'bilinear' or method == 'tustin': 25 return cont2discrete(system, dt, method="gbt", alpha=0.5) 26 27 elif method == 'euler' or method == 'forward_diff': 28 return cont2discrete(system, dt, method="gbt", alpha=0.0) 29 30 elif method == 'backward_diff': 31 return cont2discrete(system, dt, method="gbt", alpha=1.0) 32 33 elif method == 'zoh': 34 # Build an exponential matrix 35 em_upper = np.hstack((a, b)) 36 37 # Need to stack zeros under the a and b matrices 38 em_lower = np.hstack((np.zeros((b.shape[1], a.shape[0])), 39 np.zeros((b.shape[1], b.shape[1])))) 40 41 em = np.vstack((em_upper, em_lower)) 42 ms = scipy.linalg.expm(dt * em) 43 44 # Dispose of the lower rows 45 ms = ms[:a.shape[0], :] 46 47 ad = ms[:, 0:a.shape[1]] 48 bd = ms[:, a.shape[1]:] 49 50 cd = c 51 dd = d 52 53 elif method == 'foh': 54 # Size parameters for convenience 55 n = a.shape[0] 56 m = b.shape[1] 57 58 # Build an exponential matrix similar to 'zoh' method 59 em_upper = scipy.linalg.block_diag(np.block([a, b]) * dt, np.eye(m)) 60 em_lower = np.zeros((m, n + 2 * m)) 61 em = np.block([[em_upper], [em_lower]]) 62 63 ms = scipy.linalg.expm(em) 64 65 # Get the three blocks from upper rows 66 ms11 = ms[:n, 0:n] 67 ms12 = ms[:n, n:n + m] 68 ms13 = ms[:n, n + m:] 69 70 ad = ms11 71 bd = ms12 - ms13 + ms11 @ ms13 72 cd = c 73 dd = d + c @ ms13 74 75 elif method == 'impulse': 76 if not np.allclose(d, 0): 77 raise ValueError("Impulse method is only applicable" 78 "to strictly proper systems") 79 80 ad = scipy.linalg.expm(a * dt) 81 bd = ad @ b * dt 82 cd = c 83 dd = c @ b * dt 84 85 else: 86 raise ValueError("Unknown transformation method '%s'" % method) 87 88 return ad, bd, cd, dd, dt
pytest information
This code is wrapped in a pytest function using conventions detailed in Pytest Conventions. The full name of this test, as known by the documentation, is:
wield.control.SISO.test.test_SISO_c2d.cont2discreteThe full name is useful when building documentation, to link a reference to this page using
:func:`name`, or directly include it with an autofunction directive. The collapse nodes below show every instance of the test run. There may only be one, but if the test was run multiple times through pytest parametrizations, the list can be longer.
- test_ZPK_c2d_various(zpk)[source][github]¶
Test the conversions to and from ZPK representation and statespace representation using a delay filter
code
docstring
""" Test the conversions to and from ZPK representation and statespace representation using a delay filter """
1@pytest.mark.parametrize('zpk', [ 2 ((-1+450j, -1-450j), (-100, -100, -10), 0.01), 3 ((-200+450j, -200-450j), (-100, -100, -10), 0.01), 4 ((-100, -10), (-1+450j, -1-450j), 0.01), 5]) 6def test_ZPK_c2d_various(zpk): 7 8 axB = mplfigB(Nrows=2) 9 fs = 2048 10 F_Hz = logspaced(1, fs/2, 1000) 11 12 sfilt = SISO.zpk(zpk, angular=False, fiducial_rtol=1e-7) 13 zfilt = zpk_d2c_c2d.c2d_zpk(sfilt, fs=fs, method='tustin') 14 zfilt2 = zpk_d2c_c2d.c2d_zpk(sfilt, fs=fs, method='matched', pad=True) 15 print(sfilt) 16 sfiltss = sfilt.asSS 17 A, B, C, D, dt = cont2discrete((sfiltss.A, sfiltss.B, sfiltss.C, sfiltss.D), 1/fs, method='tustin') 18 zfiltss = SISO.SISOStateSpace(A, B, C, D, dt=dt) 19 20 print('------') 21 print(zfilt) 22 xfer_s1 = sfilt.fresponse(f=F_Hz) 23 axB.ax0.loglog(*xfer_s1.fplot_mag, label="Direct ZPK") 24 axB.ax1.semilogx(*xfer_s1.fplot_deg135) 25 26 xfer_z1 = zfilt.fresponse(f=F_Hz) 27 xfer_z2 = zfilt2.fresponse(f=F_Hz) 28 axB.ax0.loglog(*xfer_z1.fplot_mag, label="Direct ZPK") 29 axB.ax1.semilogx(*xfer_z1.fplot_deg135) 30 axB.ax0.loglog(*xfer_z2.fplot_mag, label="Direct ZPK", ls='--') 31 axB.ax1.semilogx(*xfer_z2.fplot_deg135, ls='--') 32 33 xfer_z2 = zfiltss.fresponse(f=F_Hz) 34 axB.ax0.loglog(*xfer_z2.fplot_mag, label="Statespace ZPK", ls='--') 35 axB.ax1.semilogx(*xfer_z2.fplot_deg135, ls='--') 36 37 axB.ax0.set_ylim(1e-8, 1) 38 axB.save(tjoin("test_ZPK")) 39 40 #np.testing.assert_almost_equal(xfer4, 1/xfer4c)
pytest information
This code is wrapped in a pytest function using conventions detailed in Pytest Conventions. The full name of this test, as known by the documentation, is:
wield.control.SISO.test.test_SISO_c2d.test_ZPK_c2d_variousThe full name is useful when building documentation, to link a reference to this page using
:func:`name`, or directly include it with an autofunction directive. The collapse nodes below show every instance of the test run. There may only be one, but if the test was run multiple times through pytest parametrizations, the list can be longer.test_ZPK_c2d_various[zpk0]
output
zpk(z=2π·[-1.0 ± 450.0j], p=2π·[-100.0, -100.0, -10.0], k=0.01) ------ zpk(z=[-0.9995117187500000, 0.9979242997333204 ^±1·e^(1.208362155524933j)], p=[ 0.7340067031329183, 0.7340067031329183, 0.9697838935106804], k=2.674930326299474e-06)
test_ZPK_c2d_various[zpk2]
output
zpk(z=2π·[-100.0, -10.0], p=2π·[-1.0 ± 450.0j], k=0.01) ------ zpk(z=[ 0.7340067031329183, 0.9697838935106804], p=[0.9979242997333204 ^±1·e^(1.208362155524933j)], k=0.007915063360467356)
test_ZPK_c2d_various[zpk1]
output
zpk(z=2π·[-200.0 ± 450.0j], p=2π·[-100.0, -100.0, -10.0], k=0.01) ------ zpk(z=[-0.9995117187500000, 0.6619353799351133 ^±1·e^(1.269270174638110j)], p=[ 0.7340067031329183, 0.7340067031329183, 0.9697838935106804], k=3.9488649803054825e-06)