test_cont2discrete.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. from __future__ import division, print_function, absolute_import
  2. import numpy as np
  3. from numpy.testing import \
  4. assert_array_almost_equal, assert_almost_equal, \
  5. assert_allclose, assert_equal
  6. import warnings
  7. from scipy.signal import cont2discrete as c2d
  8. from scipy.signal import dlsim, ss2tf, ss2zpk, lsim2, lti
  9. # Author: Jeffrey Armstrong <jeff@approximatrix.com>
  10. # March 29, 2011
  11. class TestC2D(object):
  12. def test_zoh(self):
  13. ac = np.eye(2)
  14. bc = 0.5 * np.ones((2, 1))
  15. cc = np.array([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]])
  16. dc = np.array([[0.0], [0.0], [-0.33]])
  17. ad_truth = 1.648721270700128 * np.eye(2)
  18. bd_truth = 0.324360635350064 * np.ones((2, 1))
  19. # c and d in discrete should be equal to their continuous counterparts
  20. dt_requested = 0.5
  21. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested, method='zoh')
  22. assert_array_almost_equal(ad_truth, ad)
  23. assert_array_almost_equal(bd_truth, bd)
  24. assert_array_almost_equal(cc, cd)
  25. assert_array_almost_equal(dc, dd)
  26. assert_almost_equal(dt_requested, dt)
  27. def test_gbt(self):
  28. ac = np.eye(2)
  29. bc = 0.5 * np.ones((2, 1))
  30. cc = np.array([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]])
  31. dc = np.array([[0.0], [0.0], [-0.33]])
  32. dt_requested = 0.5
  33. alpha = 1.0 / 3.0
  34. ad_truth = 1.6 * np.eye(2)
  35. bd_truth = 0.3 * np.ones((2, 1))
  36. cd_truth = np.array([[0.9, 1.2],
  37. [1.2, 1.2],
  38. [1.2, 0.3]])
  39. dd_truth = np.array([[0.175],
  40. [0.2],
  41. [-0.205]])
  42. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested,
  43. method='gbt', alpha=alpha)
  44. assert_array_almost_equal(ad_truth, ad)
  45. assert_array_almost_equal(bd_truth, bd)
  46. assert_array_almost_equal(cd_truth, cd)
  47. assert_array_almost_equal(dd_truth, dd)
  48. def test_euler(self):
  49. ac = np.eye(2)
  50. bc = 0.5 * np.ones((2, 1))
  51. cc = np.array([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]])
  52. dc = np.array([[0.0], [0.0], [-0.33]])
  53. dt_requested = 0.5
  54. ad_truth = 1.5 * np.eye(2)
  55. bd_truth = 0.25 * np.ones((2, 1))
  56. cd_truth = np.array([[0.75, 1.0],
  57. [1.0, 1.0],
  58. [1.0, 0.25]])
  59. dd_truth = dc
  60. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested,
  61. method='euler')
  62. assert_array_almost_equal(ad_truth, ad)
  63. assert_array_almost_equal(bd_truth, bd)
  64. assert_array_almost_equal(cd_truth, cd)
  65. assert_array_almost_equal(dd_truth, dd)
  66. assert_almost_equal(dt_requested, dt)
  67. def test_backward_diff(self):
  68. ac = np.eye(2)
  69. bc = 0.5 * np.ones((2, 1))
  70. cc = np.array([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]])
  71. dc = np.array([[0.0], [0.0], [-0.33]])
  72. dt_requested = 0.5
  73. ad_truth = 2.0 * np.eye(2)
  74. bd_truth = 0.5 * np.ones((2, 1))
  75. cd_truth = np.array([[1.5, 2.0],
  76. [2.0, 2.0],
  77. [2.0, 0.5]])
  78. dd_truth = np.array([[0.875],
  79. [1.0],
  80. [0.295]])
  81. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested,
  82. method='backward_diff')
  83. assert_array_almost_equal(ad_truth, ad)
  84. assert_array_almost_equal(bd_truth, bd)
  85. assert_array_almost_equal(cd_truth, cd)
  86. assert_array_almost_equal(dd_truth, dd)
  87. def test_bilinear(self):
  88. ac = np.eye(2)
  89. bc = 0.5 * np.ones((2, 1))
  90. cc = np.array([[0.75, 1.0], [1.0, 1.0], [1.0, 0.25]])
  91. dc = np.array([[0.0], [0.0], [-0.33]])
  92. dt_requested = 0.5
  93. ad_truth = (5.0 / 3.0) * np.eye(2)
  94. bd_truth = (1.0 / 3.0) * np.ones((2, 1))
  95. cd_truth = np.array([[1.0, 4.0 / 3.0],
  96. [4.0 / 3.0, 4.0 / 3.0],
  97. [4.0 / 3.0, 1.0 / 3.0]])
  98. dd_truth = np.array([[0.291666666666667],
  99. [1.0 / 3.0],
  100. [-0.121666666666667]])
  101. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested,
  102. method='bilinear')
  103. assert_array_almost_equal(ad_truth, ad)
  104. assert_array_almost_equal(bd_truth, bd)
  105. assert_array_almost_equal(cd_truth, cd)
  106. assert_array_almost_equal(dd_truth, dd)
  107. assert_almost_equal(dt_requested, dt)
  108. # Same continuous system again, but change sampling rate
  109. ad_truth = 1.4 * np.eye(2)
  110. bd_truth = 0.2 * np.ones((2, 1))
  111. cd_truth = np.array([[0.9, 1.2], [1.2, 1.2], [1.2, 0.3]])
  112. dd_truth = np.array([[0.175], [0.2], [-0.205]])
  113. dt_requested = 1.0 / 3.0
  114. ad, bd, cd, dd, dt = c2d((ac, bc, cc, dc), dt_requested,
  115. method='bilinear')
  116. assert_array_almost_equal(ad_truth, ad)
  117. assert_array_almost_equal(bd_truth, bd)
  118. assert_array_almost_equal(cd_truth, cd)
  119. assert_array_almost_equal(dd_truth, dd)
  120. assert_almost_equal(dt_requested, dt)
  121. def test_transferfunction(self):
  122. numc = np.array([0.25, 0.25, 0.5])
  123. denc = np.array([0.75, 0.75, 1.0])
  124. numd = np.array([[1.0 / 3.0, -0.427419169438754, 0.221654141101125]])
  125. dend = np.array([1.0, -1.351394049721225, 0.606530659712634])
  126. dt_requested = 0.5
  127. num, den, dt = c2d((numc, denc), dt_requested, method='zoh')
  128. assert_array_almost_equal(numd, num)
  129. assert_array_almost_equal(dend, den)
  130. assert_almost_equal(dt_requested, dt)
  131. def test_zerospolesgain(self):
  132. zeros_c = np.array([0.5, -0.5])
  133. poles_c = np.array([1.j / np.sqrt(2), -1.j / np.sqrt(2)])
  134. k_c = 1.0
  135. zeros_d = [1.23371727305860, 0.735356894461267]
  136. polls_d = [0.938148335039729 + 0.346233593780536j,
  137. 0.938148335039729 - 0.346233593780536j]
  138. k_d = 1.0
  139. dt_requested = 0.5
  140. zeros, poles, k, dt = c2d((zeros_c, poles_c, k_c), dt_requested,
  141. method='zoh')
  142. assert_array_almost_equal(zeros_d, zeros)
  143. assert_array_almost_equal(polls_d, poles)
  144. assert_almost_equal(k_d, k)
  145. assert_almost_equal(dt_requested, dt)
  146. def test_gbt_with_sio_tf_and_zpk(self):
  147. """Test method='gbt' with alpha=0.25 for tf and zpk cases."""
  148. # State space coefficients for the continuous SIO system.
  149. A = -1.0
  150. B = 1.0
  151. C = 1.0
  152. D = 0.5
  153. # The continuous transfer function coefficients.
  154. cnum, cden = ss2tf(A, B, C, D)
  155. # Continuous zpk representation
  156. cz, cp, ck = ss2zpk(A, B, C, D)
  157. h = 1.0
  158. alpha = 0.25
  159. # Explicit formulas, in the scalar case.
  160. Ad = (1 + (1 - alpha) * h * A) / (1 - alpha * h * A)
  161. Bd = h * B / (1 - alpha * h * A)
  162. Cd = C / (1 - alpha * h * A)
  163. Dd = D + alpha * C * Bd
  164. # Convert the explicit solution to tf
  165. dnum, dden = ss2tf(Ad, Bd, Cd, Dd)
  166. # Compute the discrete tf using cont2discrete.
  167. c2dnum, c2dden, dt = c2d((cnum, cden), h, method='gbt', alpha=alpha)
  168. assert_allclose(dnum, c2dnum)
  169. assert_allclose(dden, c2dden)
  170. # Convert explicit solution to zpk.
  171. dz, dp, dk = ss2zpk(Ad, Bd, Cd, Dd)
  172. # Compute the discrete zpk using cont2discrete.
  173. c2dz, c2dp, c2dk, dt = c2d((cz, cp, ck), h, method='gbt', alpha=alpha)
  174. assert_allclose(dz, c2dz)
  175. assert_allclose(dp, c2dp)
  176. assert_allclose(dk, c2dk)
  177. def test_discrete_approx(self):
  178. """
  179. Test that the solution to the discrete approximation of a continuous
  180. system actually approximates the solution to the continuous system.
  181. This is an indirect test of the correctness of the implementation
  182. of cont2discrete.
  183. """
  184. def u(t):
  185. return np.sin(2.5 * t)
  186. a = np.array([[-0.01]])
  187. b = np.array([[1.0]])
  188. c = np.array([[1.0]])
  189. d = np.array([[0.2]])
  190. x0 = 1.0
  191. t = np.linspace(0, 10.0, 101)
  192. dt = t[1] - t[0]
  193. u1 = u(t)
  194. # Use lsim2 to compute the solution to the continuous system.
  195. t, yout, xout = lsim2((a, b, c, d), T=t, U=u1, X0=x0,
  196. rtol=1e-9, atol=1e-11)
  197. # Convert the continuous system to a discrete approximation.
  198. dsys = c2d((a, b, c, d), dt, method='bilinear')
  199. # Use dlsim with the pairwise averaged input to compute the output
  200. # of the discrete system.
  201. u2 = 0.5 * (u1[:-1] + u1[1:])
  202. t2 = t[:-1]
  203. td2, yd2, xd2 = dlsim(dsys, u=u2.reshape(-1, 1), t=t2, x0=x0)
  204. # ymid is the average of consecutive terms of the "exact" output
  205. # computed by lsim2. This is what the discrete approximation
  206. # actually approximates.
  207. ymid = 0.5 * (yout[:-1] + yout[1:])
  208. assert_allclose(yd2.ravel(), ymid, rtol=1e-4)
  209. def test_simo_tf(self):
  210. # See gh-5753
  211. tf = ([[1, 0], [1, 1]], [1, 1])
  212. num, den, dt = c2d(tf, 0.01)
  213. assert_equal(dt, 0.01) # sanity check
  214. assert_allclose(den, [1, -0.990404983], rtol=1e-3)
  215. assert_allclose(num, [[1, -1], [1, -0.99004983]], rtol=1e-3)
  216. def test_multioutput(self):
  217. ts = 0.01 # time step
  218. tf = ([[1, -3], [1, 5]], [1, 1])
  219. num, den, dt = c2d(tf, ts)
  220. tf1 = (tf[0][0], tf[1])
  221. num1, den1, dt1 = c2d(tf1, ts)
  222. tf2 = (tf[0][1], tf[1])
  223. num2, den2, dt2 = c2d(tf2, ts)
  224. # Sanity checks
  225. assert_equal(dt, dt1)
  226. assert_equal(dt, dt2)
  227. # Check that we get the same results
  228. assert_allclose(num, np.vstack((num1, num2)), rtol=1e-13)
  229. # Single input, so the denominator should
  230. # not be multidimensional like the numerator
  231. assert_allclose(den, den1, rtol=1e-13)
  232. assert_allclose(den, den2, rtol=1e-13)
  233. class TestC2dLti(object):
  234. def test_c2d_ss(self):
  235. # StateSpace
  236. A = np.array([[-0.3, 0.1], [0.2, -0.7]])
  237. B = np.array([[0], [1]])
  238. C = np.array([[1, 0]])
  239. D = 0
  240. A_res = np.array([[0.985136404135682, 0.004876671474795],
  241. [0.009753342949590, 0.965629718236502]])
  242. B_res = np.array([[0.000122937599964], [0.049135527547844]])
  243. sys_ssc = lti(A, B, C, D)
  244. sys_ssd = sys_ssc.to_discrete(0.05)
  245. assert_allclose(sys_ssd.A, A_res)
  246. assert_allclose(sys_ssd.B, B_res)
  247. assert_allclose(sys_ssd.C, C)
  248. assert_allclose(sys_ssd.D, D)
  249. def test_c2d_tf(self):
  250. sys = lti([0.5, 0.3], [1.0, 0.4])
  251. sys = sys.to_discrete(0.005)
  252. # Matlab results
  253. num_res = np.array([0.5, -0.485149004980066])
  254. den_res = np.array([1.0, -0.980198673306755])
  255. # Somehow a lot of numerical errors
  256. assert_allclose(sys.den, den_res, atol=0.02)
  257. assert_allclose(sys.num, num_res, atol=0.02)
  258. class TestC2dLti(object):
  259. def test_c2d_ss(self):
  260. # StateSpace
  261. A = np.array([[-0.3, 0.1], [0.2, -0.7]])
  262. B = np.array([[0], [1]])
  263. C = np.array([[1, 0]])
  264. D = 0
  265. A_res = np.array([[0.985136404135682, 0.004876671474795],
  266. [0.009753342949590, 0.965629718236502]])
  267. B_res = np.array([[0.000122937599964], [0.049135527547844]])
  268. sys_ssc = lti(A, B, C, D)
  269. sys_ssd = sys_ssc.to_discrete(0.05)
  270. assert_allclose(sys_ssd.A, A_res)
  271. assert_allclose(sys_ssd.B, B_res)
  272. assert_allclose(sys_ssd.C, C)
  273. assert_allclose(sys_ssd.D, D)
  274. def test_c2d_tf(self):
  275. sys = lti([0.5, 0.3], [1.0, 0.4])
  276. sys = sys.to_discrete(0.005)
  277. # Matlab results
  278. num_res = np.array([0.5, -0.485149004980066])
  279. den_res = np.array([1.0, -0.980198673306755])
  280. # Somehow a lot of numerical errors
  281. assert_allclose(sys.den, den_res, atol=0.02)
  282. assert_allclose(sys.num, num_res, atol=0.02)