test_lti.py 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245
  1. from sympy.core.add import Add
  2. from sympy.core.function import Function
  3. from sympy.core.mul import Mul
  4. from sympy.core.numbers import (I, Rational, oo)
  5. from sympy.core.power import Pow
  6. from sympy.core.singleton import S
  7. from sympy.core.symbol import symbols
  8. from sympy.functions.elementary.exponential import exp
  9. from sympy.functions.elementary.miscellaneous import sqrt
  10. from sympy.matrices.dense import eye
  11. from sympy.polys.polytools import factor
  12. from sympy.polys.rootoftools import CRootOf
  13. from sympy.simplify.simplify import simplify
  14. from sympy.core.containers import Tuple
  15. from sympy.matrices import ImmutableMatrix, Matrix
  16. from sympy.physics.control import (TransferFunction, Series, Parallel,
  17. Feedback, TransferFunctionMatrix, MIMOSeries, MIMOParallel, MIMOFeedback,
  18. bilinear, backward_diff)
  19. from sympy.testing.pytest import raises
  20. a, x, b, s, g, d, p, k, a0, a1, a2, b0, b1, b2, tau, zeta, wn, T = symbols('a, x, b, s, g, d, p, k,\
  21. a0:3, b0:3, tau, zeta, wn, T')
  22. TF1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  23. TF2 = TransferFunction(k, 1, s)
  24. TF3 = TransferFunction(a2*p - s, a2*s + p, s)
  25. def test_TransferFunction_construction():
  26. tf = TransferFunction(s + 1, s**2 + s + 1, s)
  27. assert tf.num == (s + 1)
  28. assert tf.den == (s**2 + s + 1)
  29. assert tf.args == (s + 1, s**2 + s + 1, s)
  30. tf1 = TransferFunction(s + 4, s - 5, s)
  31. assert tf1.num == (s + 4)
  32. assert tf1.den == (s - 5)
  33. assert tf1.args == (s + 4, s - 5, s)
  34. # using different polynomial variables.
  35. tf2 = TransferFunction(p + 3, p**2 - 9, p)
  36. assert tf2.num == (p + 3)
  37. assert tf2.den == (p**2 - 9)
  38. assert tf2.args == (p + 3, p**2 - 9, p)
  39. tf3 = TransferFunction(p**3 + 5*p**2 + 4, p**4 + 3*p + 1, p)
  40. assert tf3.args == (p**3 + 5*p**2 + 4, p**4 + 3*p + 1, p)
  41. # no pole-zero cancellation on its own.
  42. tf4 = TransferFunction((s + 3)*(s - 1), (s - 1)*(s + 5), s)
  43. assert tf4.den == (s - 1)*(s + 5)
  44. assert tf4.args == ((s + 3)*(s - 1), (s - 1)*(s + 5), s)
  45. tf4_ = TransferFunction(p + 2, p + 2, p)
  46. assert tf4_.args == (p + 2, p + 2, p)
  47. tf5 = TransferFunction(s - 1, 4 - p, s)
  48. assert tf5.args == (s - 1, 4 - p, s)
  49. tf5_ = TransferFunction(s - 1, s - 1, s)
  50. assert tf5_.args == (s - 1, s - 1, s)
  51. tf6 = TransferFunction(5, 6, s)
  52. assert tf6.num == 5
  53. assert tf6.den == 6
  54. assert tf6.args == (5, 6, s)
  55. tf6_ = TransferFunction(1/2, 4, s)
  56. assert tf6_.num == 0.5
  57. assert tf6_.den == 4
  58. assert tf6_.args == (0.500000000000000, 4, s)
  59. tf7 = TransferFunction(3*s**2 + 2*p + 4*s, 8*p**2 + 7*s, s)
  60. tf8 = TransferFunction(3*s**2 + 2*p + 4*s, 8*p**2 + 7*s, p)
  61. assert not tf7 == tf8
  62. tf7_ = TransferFunction(a0*s + a1*s**2 + a2*s**3, b0*p - b1*s, s)
  63. tf8_ = TransferFunction(a0*s + a1*s**2 + a2*s**3, b0*p - b1*s, s)
  64. assert tf7_ == tf8_
  65. assert -(-tf7_) == tf7_ == -(-(-(-tf7_)))
  66. tf9 = TransferFunction(a*s**3 + b*s**2 + g*s + d, d*p + g*p**2 + g*s, s)
  67. assert tf9.args == (a*s**3 + b*s**2 + d + g*s, d*p + g*p**2 + g*s, s)
  68. tf10 = TransferFunction(p**3 + d, g*s**2 + d*s + a, p)
  69. tf10_ = TransferFunction(p**3 + d, g*s**2 + d*s + a, p)
  70. assert tf10.args == (d + p**3, a + d*s + g*s**2, p)
  71. assert tf10_ == tf10
  72. tf11 = TransferFunction(a1*s + a0, b2*s**2 + b1*s + b0, s)
  73. assert tf11.num == (a0 + a1*s)
  74. assert tf11.den == (b0 + b1*s + b2*s**2)
  75. assert tf11.args == (a0 + a1*s, b0 + b1*s + b2*s**2, s)
  76. # when just the numerator is 0, leave the denominator alone.
  77. tf12 = TransferFunction(0, p**2 - p + 1, p)
  78. assert tf12.args == (0, p**2 - p + 1, p)
  79. tf13 = TransferFunction(0, 1, s)
  80. assert tf13.args == (0, 1, s)
  81. # float exponents
  82. tf14 = TransferFunction(a0*s**0.5 + a2*s**0.6 - a1, a1*p**(-8.7), s)
  83. assert tf14.args == (a0*s**0.5 - a1 + a2*s**0.6, a1*p**(-8.7), s)
  84. tf15 = TransferFunction(a2**2*p**(1/4) + a1*s**(-4/5), a0*s - p, p)
  85. assert tf15.args == (a1*s**(-0.8) + a2**2*p**0.25, a0*s - p, p)
  86. omega_o, k_p, k_o, k_i = symbols('omega_o, k_p, k_o, k_i')
  87. tf18 = TransferFunction((k_p + k_o*s + k_i/s), s**2 + 2*omega_o*s + omega_o**2, s)
  88. assert tf18.num == k_i/s + k_o*s + k_p
  89. assert tf18.args == (k_i/s + k_o*s + k_p, omega_o**2 + 2*omega_o*s + s**2, s)
  90. # ValueError when denominator is zero.
  91. raises(ValueError, lambda: TransferFunction(4, 0, s))
  92. raises(ValueError, lambda: TransferFunction(s, 0, s))
  93. raises(ValueError, lambda: TransferFunction(0, 0, s))
  94. raises(TypeError, lambda: TransferFunction(Matrix([1, 2, 3]), s, s))
  95. raises(TypeError, lambda: TransferFunction(s**2 + 2*s - 1, s + 3, 3))
  96. raises(TypeError, lambda: TransferFunction(p + 1, 5 - p, 4))
  97. raises(TypeError, lambda: TransferFunction(3, 4, 8))
  98. def test_TransferFunction_functions():
  99. # classmethod from_rational_expression
  100. expr_1 = Mul(0, Pow(s, -1, evaluate=False), evaluate=False)
  101. expr_2 = s/0
  102. expr_3 = (p*s**2 + 5*s)/(s + 1)**3
  103. expr_4 = 6
  104. expr_5 = ((2 + 3*s)*(5 + 2*s))/((9 + 3*s)*(5 + 2*s**2))
  105. expr_6 = (9*s**4 + 4*s**2 + 8)/((s + 1)*(s + 9))
  106. tf = TransferFunction(s + 1, s**2 + 2, s)
  107. delay = exp(-s/tau)
  108. expr_7 = delay*tf.to_expr()
  109. H1 = TransferFunction.from_rational_expression(expr_7, s)
  110. H2 = TransferFunction(s + 1, (s**2 + 2)*exp(s/tau), s)
  111. expr_8 = Add(2, 3*s/(s**2 + 1), evaluate=False)
  112. assert TransferFunction.from_rational_expression(expr_1) == TransferFunction(0, s, s)
  113. raises(ZeroDivisionError, lambda: TransferFunction.from_rational_expression(expr_2))
  114. raises(ValueError, lambda: TransferFunction.from_rational_expression(expr_3))
  115. assert TransferFunction.from_rational_expression(expr_3, s) == TransferFunction((p*s**2 + 5*s), (s + 1)**3, s)
  116. assert TransferFunction.from_rational_expression(expr_3, p) == TransferFunction((p*s**2 + 5*s), (s + 1)**3, p)
  117. raises(ValueError, lambda: TransferFunction.from_rational_expression(expr_4))
  118. assert TransferFunction.from_rational_expression(expr_4, s) == TransferFunction(6, 1, s)
  119. assert TransferFunction.from_rational_expression(expr_5, s) == \
  120. TransferFunction((2 + 3*s)*(5 + 2*s), (9 + 3*s)*(5 + 2*s**2), s)
  121. assert TransferFunction.from_rational_expression(expr_6, s) == \
  122. TransferFunction((9*s**4 + 4*s**2 + 8), (s + 1)*(s + 9), s)
  123. assert H1 == H2
  124. assert TransferFunction.from_rational_expression(expr_8, s) == \
  125. TransferFunction(2*s**2 + 3*s + 2, s**2 + 1, s)
  126. # explicitly cancel poles and zeros.
  127. tf0 = TransferFunction(s**5 + s**3 + s, s - s**2, s)
  128. a = TransferFunction(-(s**4 + s**2 + 1), s - 1, s)
  129. assert tf0.simplify() == simplify(tf0) == a
  130. tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  131. b = TransferFunction(p + 3, p + 5, p)
  132. assert tf1.simplify() == simplify(tf1) == b
  133. # expand the numerator and the denominator.
  134. G1 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  135. G2 = TransferFunction(1, -3, p)
  136. c = (a2*s**p + a1*s**s + a0*p**p)*(p**s + s**p)
  137. d = (b0*s**s + b1*p**s)*(b2*s*p + p**p)
  138. e = a0*p**p*p**s + a0*p**p*s**p + a1*p**s*s**s + a1*s**p*s**s + a2*p**s*s**p + a2*s**(2*p)
  139. f = b0*b2*p*s*s**s + b0*p**p*s**s + b1*b2*p*p**s*s + b1*p**p*p**s
  140. g = a1*a2*s*s**p + a1*p*s + a2*b1*p*s*s**p + b1*p**2*s
  141. G3 = TransferFunction(c, d, s)
  142. G4 = TransferFunction(a0*s**s - b0*p**p, (a1*s + b1*s*p)*(a2*s**p + p), p)
  143. assert G1.expand() == TransferFunction(s**2 - 2*s + 1, s**4 + 2*s**2 + 1, s)
  144. assert tf1.expand() == TransferFunction(p**2 + 2*p - 3, p**2 + 4*p - 5, p)
  145. assert G2.expand() == G2
  146. assert G3.expand() == TransferFunction(e, f, s)
  147. assert G4.expand() == TransferFunction(a0*s**s - b0*p**p, g, p)
  148. # purely symbolic polynomials.
  149. p1 = a1*s + a0
  150. p2 = b2*s**2 + b1*s + b0
  151. SP1 = TransferFunction(p1, p2, s)
  152. expect1 = TransferFunction(2.0*s + 1.0, 5.0*s**2 + 4.0*s + 3.0, s)
  153. expect1_ = TransferFunction(2*s + 1, 5*s**2 + 4*s + 3, s)
  154. assert SP1.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}) == expect1_
  155. assert SP1.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}).evalf() == expect1
  156. assert expect1_.evalf() == expect1
  157. c1, d0, d1, d2 = symbols('c1, d0:3')
  158. p3, p4 = c1*p, d2*p**3 + d1*p**2 - d0
  159. SP2 = TransferFunction(p3, p4, p)
  160. expect2 = TransferFunction(2.0*p, 5.0*p**3 + 2.0*p**2 - 3.0, p)
  161. expect2_ = TransferFunction(2*p, 5*p**3 + 2*p**2 - 3, p)
  162. assert SP2.subs({c1: 2, d0: 3, d1: 2, d2: 5}) == expect2_
  163. assert SP2.subs({c1: 2, d0: 3, d1: 2, d2: 5}).evalf() == expect2
  164. assert expect2_.evalf() == expect2
  165. SP3 = TransferFunction(a0*p**3 + a1*s**2 - b0*s + b1, a1*s + p, s)
  166. expect3 = TransferFunction(2.0*p**3 + 4.0*s**2 - s + 5.0, p + 4.0*s, s)
  167. expect3_ = TransferFunction(2*p**3 + 4*s**2 - s + 5, p + 4*s, s)
  168. assert SP3.subs({a0: 2, a1: 4, b0: 1, b1: 5}) == expect3_
  169. assert SP3.subs({a0: 2, a1: 4, b0: 1, b1: 5}).evalf() == expect3
  170. assert expect3_.evalf() == expect3
  171. SP4 = TransferFunction(s - a1*p**3, a0*s + p, p)
  172. expect4 = TransferFunction(7.0*p**3 + s, p - s, p)
  173. expect4_ = TransferFunction(7*p**3 + s, p - s, p)
  174. assert SP4.subs({a0: -1, a1: -7}) == expect4_
  175. assert SP4.subs({a0: -1, a1: -7}).evalf() == expect4
  176. assert expect4_.evalf() == expect4
  177. # Low-frequency (or DC) gain.
  178. assert tf0.dc_gain() == 1
  179. assert tf1.dc_gain() == Rational(3, 5)
  180. assert SP2.dc_gain() == 0
  181. assert expect4.dc_gain() == -1
  182. assert expect2_.dc_gain() == 0
  183. assert TransferFunction(1, s, s).dc_gain() == oo
  184. # Poles of a transfer function.
  185. tf_ = TransferFunction(x**3 - k, k, x)
  186. _tf = TransferFunction(k, x**4 - k, x)
  187. TF_ = TransferFunction(x**2, x**10 + x + x**2, x)
  188. _TF = TransferFunction(x**10 + x + x**2, x**2, x)
  189. assert G1.poles() == [I, I, -I, -I]
  190. assert G2.poles() == []
  191. assert tf1.poles() == [-5, 1]
  192. assert expect4_.poles() == [s]
  193. assert SP4.poles() == [-a0*s]
  194. assert expect3.poles() == [-0.25*p]
  195. assert str(expect2.poles()) == str([0.729001428685125, -0.564500714342563 - 0.710198984796332*I, -0.564500714342563 + 0.710198984796332*I])
  196. assert str(expect1.poles()) == str([-0.4 - 0.66332495807108*I, -0.4 + 0.66332495807108*I])
  197. assert _tf.poles() == [k**(Rational(1, 4)), -k**(Rational(1, 4)), I*k**(Rational(1, 4)), -I*k**(Rational(1, 4))]
  198. assert TF_.poles() == [CRootOf(x**9 + x + 1, 0), 0, CRootOf(x**9 + x + 1, 1), CRootOf(x**9 + x + 1, 2),
  199. CRootOf(x**9 + x + 1, 3), CRootOf(x**9 + x + 1, 4), CRootOf(x**9 + x + 1, 5), CRootOf(x**9 + x + 1, 6),
  200. CRootOf(x**9 + x + 1, 7), CRootOf(x**9 + x + 1, 8)]
  201. raises(NotImplementedError, lambda: TransferFunction(x**2, a0*x**10 + x + x**2, x).poles())
  202. # Stability of a transfer function.
  203. q, r = symbols('q, r', negative=True)
  204. t = symbols('t', positive=True)
  205. TF_ = TransferFunction(s**2 + a0 - a1*p, q*s - r, s)
  206. stable_tf = TransferFunction(s**2 + a0 - a1*p, q*s - 1, s)
  207. stable_tf_ = TransferFunction(s**2 + a0 - a1*p, q*s - t, s)
  208. assert G1.is_stable() is False
  209. assert G2.is_stable() is True
  210. assert tf1.is_stable() is False # as one pole is +ve, and the other is -ve.
  211. assert expect2.is_stable() is False
  212. assert expect1.is_stable() is True
  213. assert stable_tf.is_stable() is True
  214. assert stable_tf_.is_stable() is True
  215. assert TF_.is_stable() is False
  216. assert expect4_.is_stable() is None # no assumption provided for the only pole 's'.
  217. assert SP4.is_stable() is None
  218. # Zeros of a transfer function.
  219. assert G1.zeros() == [1, 1]
  220. assert G2.zeros() == []
  221. assert tf1.zeros() == [-3, 1]
  222. assert expect4_.zeros() == [7**(Rational(2, 3))*(-s)**(Rational(1, 3))/7, -7**(Rational(2, 3))*(-s)**(Rational(1, 3))/14 -
  223. sqrt(3)*7**(Rational(2, 3))*I*(-s)**(Rational(1, 3))/14, -7**(Rational(2, 3))*(-s)**(Rational(1, 3))/14 + sqrt(3)*7**(Rational(2, 3))*I*(-s)**(Rational(1, 3))/14]
  224. assert SP4.zeros() == [(s/a1)**(Rational(1, 3)), -(s/a1)**(Rational(1, 3))/2 - sqrt(3)*I*(s/a1)**(Rational(1, 3))/2,
  225. -(s/a1)**(Rational(1, 3))/2 + sqrt(3)*I*(s/a1)**(Rational(1, 3))/2]
  226. assert str(expect3.zeros()) == str([0.125 - 1.11102430216445*sqrt(-0.405063291139241*p**3 - 1.0),
  227. 1.11102430216445*sqrt(-0.405063291139241*p**3 - 1.0) + 0.125])
  228. assert tf_.zeros() == [k**(Rational(1, 3)), -k**(Rational(1, 3))/2 - sqrt(3)*I*k**(Rational(1, 3))/2,
  229. -k**(Rational(1, 3))/2 + sqrt(3)*I*k**(Rational(1, 3))/2]
  230. assert _TF.zeros() == [CRootOf(x**9 + x + 1, 0), 0, CRootOf(x**9 + x + 1, 1), CRootOf(x**9 + x + 1, 2),
  231. CRootOf(x**9 + x + 1, 3), CRootOf(x**9 + x + 1, 4), CRootOf(x**9 + x + 1, 5), CRootOf(x**9 + x + 1, 6),
  232. CRootOf(x**9 + x + 1, 7), CRootOf(x**9 + x + 1, 8)]
  233. raises(NotImplementedError, lambda: TransferFunction(a0*x**10 + x + x**2, x**2, x).zeros())
  234. # negation of TF.
  235. tf2 = TransferFunction(s + 3, s**2 - s**3 + 9, s)
  236. tf3 = TransferFunction(-3*p + 3, 1 - p, p)
  237. assert -tf2 == TransferFunction(-s - 3, s**2 - s**3 + 9, s)
  238. assert -tf3 == TransferFunction(3*p - 3, 1 - p, p)
  239. # taking power of a TF.
  240. tf4 = TransferFunction(p + 4, p - 3, p)
  241. tf5 = TransferFunction(s**2 + 1, 1 - s, s)
  242. expect2 = TransferFunction((s**2 + 1)**3, (1 - s)**3, s)
  243. expect1 = TransferFunction((p + 4)**2, (p - 3)**2, p)
  244. assert (tf4*tf4).doit() == tf4**2 == pow(tf4, 2) == expect1
  245. assert (tf5*tf5*tf5).doit() == tf5**3 == pow(tf5, 3) == expect2
  246. assert tf5**0 == pow(tf5, 0) == TransferFunction(1, 1, s)
  247. assert Series(tf4).doit()**-1 == tf4**-1 == pow(tf4, -1) == TransferFunction(p - 3, p + 4, p)
  248. assert (tf5*tf5).doit()**-1 == tf5**-2 == pow(tf5, -2) == TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  249. raises(ValueError, lambda: tf4**(s**2 + s - 1))
  250. raises(ValueError, lambda: tf5**s)
  251. raises(ValueError, lambda: tf4**tf5)
  252. # SymPy's own functions.
  253. tf = TransferFunction(s - 1, s**2 - 2*s + 1, s)
  254. tf6 = TransferFunction(s + p, p**2 - 5, s)
  255. assert factor(tf) == TransferFunction(s - 1, (s - 1)**2, s)
  256. assert tf.num.subs(s, 2) == tf.den.subs(s, 2) == 1
  257. # subs & xreplace
  258. assert tf.subs(s, 2) == TransferFunction(s - 1, s**2 - 2*s + 1, s)
  259. assert tf6.subs(p, 3) == TransferFunction(s + 3, 4, s)
  260. assert tf3.xreplace({p: s}) == TransferFunction(-3*s + 3, 1 - s, s)
  261. raises(TypeError, lambda: tf3.xreplace({p: exp(2)}))
  262. assert tf3.subs(p, exp(2)) == tf3
  263. tf7 = TransferFunction(a0*s**p + a1*p**s, a2*p - s, s)
  264. assert tf7.xreplace({s: k}) == TransferFunction(a0*k**p + a1*p**k, a2*p - k, k)
  265. assert tf7.subs(s, k) == TransferFunction(a0*s**p + a1*p**s, a2*p - s, s)
  266. # Conversion to Expr with to_expr()
  267. tf8 = TransferFunction(a0*s**5 + 5*s**2 + 3, s**6 - 3, s)
  268. tf9 = TransferFunction((5 + s), (5 + s)*(6 + s), s)
  269. tf10 = TransferFunction(0, 1, s)
  270. tf11 = TransferFunction(1, 1, s)
  271. assert tf8.to_expr() == Mul((a0*s**5 + 5*s**2 + 3), Pow((s**6 - 3), -1, evaluate=False), evaluate=False)
  272. assert tf9.to_expr() == Mul((s + 5), Pow((5 + s)*(6 + s), -1, evaluate=False), evaluate=False)
  273. assert tf10.to_expr() == Mul(S(0), Pow(1, -1, evaluate=False), evaluate=False)
  274. assert tf11.to_expr() == Pow(1, -1, evaluate=False)
  275. def test_TransferFunction_addition_and_subtraction():
  276. tf1 = TransferFunction(s + 6, s - 5, s)
  277. tf2 = TransferFunction(s + 3, s + 1, s)
  278. tf3 = TransferFunction(s + 1, s**2 + s + 1, s)
  279. tf4 = TransferFunction(p, 2 - p, p)
  280. # addition
  281. assert tf1 + tf2 == Parallel(tf1, tf2)
  282. assert tf3 + tf1 == Parallel(tf3, tf1)
  283. assert -tf1 + tf2 + tf3 == Parallel(-tf1, tf2, tf3)
  284. assert tf1 + (tf2 + tf3) == Parallel(tf1, tf2, tf3)
  285. c = symbols("c", commutative=False)
  286. raises(ValueError, lambda: tf1 + Matrix([1, 2, 3]))
  287. raises(ValueError, lambda: tf2 + c)
  288. raises(ValueError, lambda: tf3 + tf4)
  289. raises(ValueError, lambda: tf1 + (s - 1))
  290. raises(ValueError, lambda: tf1 + 8)
  291. raises(ValueError, lambda: (1 - p**3) + tf1)
  292. # subtraction
  293. assert tf1 - tf2 == Parallel(tf1, -tf2)
  294. assert tf3 - tf2 == Parallel(tf3, -tf2)
  295. assert -tf1 - tf3 == Parallel(-tf1, -tf3)
  296. assert tf1 - tf2 + tf3 == Parallel(tf1, -tf2, tf3)
  297. raises(ValueError, lambda: tf1 - Matrix([1, 2, 3]))
  298. raises(ValueError, lambda: tf3 - tf4)
  299. raises(ValueError, lambda: tf1 - (s - 1))
  300. raises(ValueError, lambda: tf1 - 8)
  301. raises(ValueError, lambda: (s + 5) - tf2)
  302. raises(ValueError, lambda: (1 + p**4) - tf1)
  303. def test_TransferFunction_multiplication_and_division():
  304. G1 = TransferFunction(s + 3, -s**3 + 9, s)
  305. G2 = TransferFunction(s + 1, s - 5, s)
  306. G3 = TransferFunction(p, p**4 - 6, p)
  307. G4 = TransferFunction(p + 4, p - 5, p)
  308. G5 = TransferFunction(s + 6, s - 5, s)
  309. G6 = TransferFunction(s + 3, s + 1, s)
  310. G7 = TransferFunction(1, 1, s)
  311. # multiplication
  312. assert G1*G2 == Series(G1, G2)
  313. assert -G1*G5 == Series(-G1, G5)
  314. assert -G2*G5*-G6 == Series(-G2, G5, -G6)
  315. assert -G1*-G2*-G5*-G6 == Series(-G1, -G2, -G5, -G6)
  316. assert G3*G4 == Series(G3, G4)
  317. assert (G1*G2)*-(G5*G6) == \
  318. Series(G1, G2, TransferFunction(-1, 1, s), Series(G5, G6))
  319. assert G1*G2*(G5 + G6) == Series(G1, G2, Parallel(G5, G6))
  320. c = symbols("c", commutative=False)
  321. raises(ValueError, lambda: G3 * Matrix([1, 2, 3]))
  322. raises(ValueError, lambda: G1 * c)
  323. raises(ValueError, lambda: G3 * G5)
  324. raises(ValueError, lambda: G5 * (s - 1))
  325. raises(ValueError, lambda: 9 * G5)
  326. raises(ValueError, lambda: G3 / Matrix([1, 2, 3]))
  327. raises(ValueError, lambda: G6 / 0)
  328. raises(ValueError, lambda: G3 / G5)
  329. raises(ValueError, lambda: G5 / 2)
  330. raises(ValueError, lambda: G5 / s**2)
  331. raises(ValueError, lambda: (s - 4*s**2) / G2)
  332. raises(ValueError, lambda: 0 / G4)
  333. raises(ValueError, lambda: G5 / G6)
  334. raises(ValueError, lambda: -G3 /G4)
  335. raises(ValueError, lambda: G7 / (1 + G6))
  336. raises(ValueError, lambda: G7 / (G5 * G6))
  337. raises(ValueError, lambda: G7 / (G7 + (G5 + G6)))
  338. def test_TransferFunction_is_proper():
  339. omega_o, zeta, tau = symbols('omega_o, zeta, tau')
  340. G1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  341. G2 = TransferFunction(tau - s**3, tau + p**4, tau)
  342. G3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  343. G4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  344. assert G1.is_proper
  345. assert G2.is_proper
  346. assert G3.is_proper
  347. assert not G4.is_proper
  348. def test_TransferFunction_is_strictly_proper():
  349. omega_o, zeta, tau = symbols('omega_o, zeta, tau')
  350. tf1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  351. tf2 = TransferFunction(tau - s**3, tau + p**4, tau)
  352. tf3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  353. tf4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  354. assert not tf1.is_strictly_proper
  355. assert not tf2.is_strictly_proper
  356. assert tf3.is_strictly_proper
  357. assert not tf4.is_strictly_proper
  358. def test_TransferFunction_is_biproper():
  359. tau, omega_o, zeta = symbols('tau, omega_o, zeta')
  360. tf1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  361. tf2 = TransferFunction(tau - s**3, tau + p**4, tau)
  362. tf3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  363. tf4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  364. assert tf1.is_biproper
  365. assert tf2.is_biproper
  366. assert not tf3.is_biproper
  367. assert not tf4.is_biproper
  368. def test_Series_construction():
  369. tf = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  370. tf2 = TransferFunction(a2*p - s, a2*s + p, s)
  371. tf3 = TransferFunction(a0*p + p**a1 - s, p, p)
  372. tf4 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  373. inp = Function('X_d')(s)
  374. out = Function('X')(s)
  375. s0 = Series(tf, tf2)
  376. assert s0.args == (tf, tf2)
  377. assert s0.var == s
  378. s1 = Series(Parallel(tf, -tf2), tf2)
  379. assert s1.args == (Parallel(tf, -tf2), tf2)
  380. assert s1.var == s
  381. tf3_ = TransferFunction(inp, 1, s)
  382. tf4_ = TransferFunction(-out, 1, s)
  383. s2 = Series(tf, Parallel(tf3_, tf4_), tf2)
  384. assert s2.args == (tf, Parallel(tf3_, tf4_), tf2)
  385. s3 = Series(tf, tf2, tf4)
  386. assert s3.args == (tf, tf2, tf4)
  387. s4 = Series(tf3_, tf4_)
  388. assert s4.args == (tf3_, tf4_)
  389. assert s4.var == s
  390. s6 = Series(tf2, tf4, Parallel(tf2, -tf), tf4)
  391. assert s6.args == (tf2, tf4, Parallel(tf2, -tf), tf4)
  392. s7 = Series(tf, tf2)
  393. assert s0 == s7
  394. assert not s0 == s2
  395. raises(ValueError, lambda: Series(tf, tf3))
  396. raises(ValueError, lambda: Series(tf, tf2, tf3, tf4))
  397. raises(ValueError, lambda: Series(-tf3, tf2))
  398. raises(TypeError, lambda: Series(2, tf, tf4))
  399. raises(TypeError, lambda: Series(s**2 + p*s, tf3, tf2))
  400. raises(TypeError, lambda: Series(tf3, Matrix([1, 2, 3, 4])))
  401. def test_MIMOSeries_construction():
  402. tf_1 = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  403. tf_2 = TransferFunction(a2*p - s, a2*s + p, s)
  404. tf_3 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  405. tfm_1 = TransferFunctionMatrix([[tf_1, tf_2, tf_3], [-tf_3, -tf_2, tf_1]])
  406. tfm_2 = TransferFunctionMatrix([[-tf_2], [-tf_2], [-tf_3]])
  407. tfm_3 = TransferFunctionMatrix([[-tf_3]])
  408. tfm_4 = TransferFunctionMatrix([[TF3], [TF2], [-TF1]])
  409. tfm_5 = TransferFunctionMatrix.from_Matrix(Matrix([1/p]), p)
  410. s8 = MIMOSeries(tfm_2, tfm_1)
  411. assert s8.args == (tfm_2, tfm_1)
  412. assert s8.var == s
  413. assert s8.shape == (s8.num_outputs, s8.num_inputs) == (2, 1)
  414. s9 = MIMOSeries(tfm_3, tfm_2, tfm_1)
  415. assert s9.args == (tfm_3, tfm_2, tfm_1)
  416. assert s9.var == s
  417. assert s9.shape == (s9.num_outputs, s9.num_inputs) == (2, 1)
  418. s11 = MIMOSeries(tfm_3, MIMOParallel(-tfm_2, -tfm_4), tfm_1)
  419. assert s11.args == (tfm_3, MIMOParallel(-tfm_2, -tfm_4), tfm_1)
  420. assert s11.shape == (s11.num_outputs, s11.num_inputs) == (2, 1)
  421. # arg cannot be empty tuple.
  422. raises(ValueError, lambda: MIMOSeries())
  423. # arg cannot contain SISO as well as MIMO systems.
  424. raises(TypeError, lambda: MIMOSeries(tfm_1, tf_1))
  425. # for all the adjacent transfer function matrices:
  426. # no. of inputs of first TFM must be equal to the no. of outputs of the second TFM.
  427. raises(ValueError, lambda: MIMOSeries(tfm_1, tfm_2, -tfm_1))
  428. # all the TFMs must use the same complex variable.
  429. raises(ValueError, lambda: MIMOSeries(tfm_3, tfm_5))
  430. # Number or expression not allowed in the arguments.
  431. raises(TypeError, lambda: MIMOSeries(2, tfm_2, tfm_3))
  432. raises(TypeError, lambda: MIMOSeries(s**2 + p*s, -tfm_2, tfm_3))
  433. raises(TypeError, lambda: MIMOSeries(Matrix([1/p]), tfm_3))
  434. def test_Series_functions():
  435. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  436. tf2 = TransferFunction(k, 1, s)
  437. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  438. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  439. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  440. assert tf1*tf2*tf3 == Series(tf1, tf2, tf3) == Series(Series(tf1, tf2), tf3) \
  441. == Series(tf1, Series(tf2, tf3))
  442. assert tf1*(tf2 + tf3) == Series(tf1, Parallel(tf2, tf3))
  443. assert tf1*tf2 + tf5 == Parallel(Series(tf1, tf2), tf5)
  444. assert tf1*tf2 - tf5 == Parallel(Series(tf1, tf2), -tf5)
  445. assert tf1*tf2 + tf3 + tf5 == Parallel(Series(tf1, tf2), tf3, tf5)
  446. assert tf1*tf2 - tf3 - tf5 == Parallel(Series(tf1, tf2), -tf3, -tf5)
  447. assert tf1*tf2 - tf3 + tf5 == Parallel(Series(tf1, tf2), -tf3, tf5)
  448. assert tf1*tf2 + tf3*tf5 == Parallel(Series(tf1, tf2), Series(tf3, tf5))
  449. assert tf1*tf2 - tf3*tf5 == Parallel(Series(tf1, tf2), Series(TransferFunction(-1, 1, s), Series(tf3, tf5)))
  450. assert tf2*tf3*(tf2 - tf1)*tf3 == Series(tf2, tf3, Parallel(tf2, -tf1), tf3)
  451. assert -tf1*tf2 == Series(-tf1, tf2)
  452. assert -(tf1*tf2) == Series(TransferFunction(-1, 1, s), Series(tf1, tf2))
  453. raises(ValueError, lambda: tf1*tf2*tf4)
  454. raises(ValueError, lambda: tf1*(tf2 - tf4))
  455. raises(ValueError, lambda: tf3*Matrix([1, 2, 3]))
  456. # evaluate=True -> doit()
  457. assert Series(tf1, tf2, evaluate=True) == Series(tf1, tf2).doit() == \
  458. TransferFunction(k, s**2 + 2*s*wn*zeta + wn**2, s)
  459. assert Series(tf1, tf2, Parallel(tf1, -tf3), evaluate=True) == Series(tf1, tf2, Parallel(tf1, -tf3)).doit() == \
  460. TransferFunction(k*(a2*s + p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2)), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)**2, s)
  461. assert Series(tf2, tf1, -tf3, evaluate=True) == Series(tf2, tf1, -tf3).doit() == \
  462. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  463. assert not Series(tf1, -tf2, evaluate=False) == Series(tf1, -tf2).doit()
  464. assert Series(Parallel(tf1, tf2), Parallel(tf2, -tf3)).doit() == \
  465. TransferFunction((k*(s**2 + 2*s*wn*zeta + wn**2) + 1)*(-a2*p + k*(a2*s + p) + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  466. assert Series(-tf1, -tf2, -tf3).doit() == \
  467. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  468. assert -Series(tf1, tf2, tf3).doit() == \
  469. TransferFunction(-k*(a2*p - s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  470. assert Series(tf2, tf3, Parallel(tf2, -tf1), tf3).doit() == \
  471. TransferFunction(k*(a2*p - s)**2*(k*(s**2 + 2*s*wn*zeta + wn**2) - 1), (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2), s)
  472. assert Series(tf1, tf2).rewrite(TransferFunction) == TransferFunction(k, s**2 + 2*s*wn*zeta + wn**2, s)
  473. assert Series(tf2, tf1, -tf3).rewrite(TransferFunction) == \
  474. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  475. S1 = Series(Parallel(tf1, tf2), Parallel(tf2, -tf3))
  476. assert S1.is_proper
  477. assert not S1.is_strictly_proper
  478. assert S1.is_biproper
  479. S2 = Series(tf1, tf2, tf3)
  480. assert S2.is_proper
  481. assert S2.is_strictly_proper
  482. assert not S2.is_biproper
  483. S3 = Series(tf1, -tf2, Parallel(tf1, -tf3))
  484. assert S3.is_proper
  485. assert S3.is_strictly_proper
  486. assert not S3.is_biproper
  487. def test_MIMOSeries_functions():
  488. tfm1 = TransferFunctionMatrix([[TF1, TF2, TF3], [-TF3, -TF2, TF1]])
  489. tfm2 = TransferFunctionMatrix([[-TF1], [-TF2], [-TF3]])
  490. tfm3 = TransferFunctionMatrix([[-TF1]])
  491. tfm4 = TransferFunctionMatrix([[-TF2, -TF3], [-TF1, TF2]])
  492. tfm5 = TransferFunctionMatrix([[TF2, -TF2], [-TF3, -TF2]])
  493. tfm6 = TransferFunctionMatrix([[-TF3], [TF1]])
  494. tfm7 = TransferFunctionMatrix([[TF1], [-TF2]])
  495. assert tfm1*tfm2 + tfm6 == MIMOParallel(MIMOSeries(tfm2, tfm1), tfm6)
  496. assert tfm1*tfm2 + tfm7 + tfm6 == MIMOParallel(MIMOSeries(tfm2, tfm1), tfm7, tfm6)
  497. assert tfm1*tfm2 - tfm6 - tfm7 == MIMOParallel(MIMOSeries(tfm2, tfm1), -tfm6, -tfm7)
  498. assert tfm4*tfm5 + (tfm4 - tfm5) == MIMOParallel(MIMOSeries(tfm5, tfm4), tfm4, -tfm5)
  499. assert tfm4*-tfm6 + (-tfm4*tfm6) == MIMOParallel(MIMOSeries(-tfm6, tfm4), MIMOSeries(tfm6, -tfm4))
  500. raises(ValueError, lambda: tfm1*tfm2 + TF1)
  501. raises(TypeError, lambda: tfm1*tfm2 + a0)
  502. raises(TypeError, lambda: tfm4*tfm6 - (s - 1))
  503. raises(TypeError, lambda: tfm4*-tfm6 - 8)
  504. raises(TypeError, lambda: (-1 + p**5) + tfm1*tfm2)
  505. # Shape criteria.
  506. raises(TypeError, lambda: -tfm1*tfm2 + tfm4)
  507. raises(TypeError, lambda: tfm1*tfm2 - tfm4 + tfm5)
  508. raises(TypeError, lambda: tfm1*tfm2 - tfm4*tfm5)
  509. assert tfm1*tfm2*-tfm3 == MIMOSeries(-tfm3, tfm2, tfm1)
  510. assert (tfm1*-tfm2)*tfm3 == MIMOSeries(tfm3, -tfm2, tfm1)
  511. # Multiplication of a Series object with a SISO TF not allowed.
  512. raises(ValueError, lambda: tfm4*tfm5*TF1)
  513. raises(TypeError, lambda: tfm4*tfm5*a1)
  514. raises(TypeError, lambda: tfm4*-tfm5*(s - 2))
  515. raises(TypeError, lambda: tfm5*tfm4*9)
  516. raises(TypeError, lambda: (-p**3 + 1)*tfm5*tfm4)
  517. # Transfer function matrix in the arguments.
  518. assert (MIMOSeries(tfm2, tfm1, evaluate=True) == MIMOSeries(tfm2, tfm1).doit()
  519. == TransferFunctionMatrix(((TransferFunction(-k**2*(a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2 + (-a2*p + s)*(a2*p - s)*(s**2 + 2*s*wn*zeta + wn**2)**2 - (a2*s + p)**2,
  520. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2, s),),
  521. (TransferFunction(k**2*(a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2 + (-a2*p + s)*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) + (a2*p - s)*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2),
  522. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2, s),))))
  523. # doit() should not cancel poles and zeros.
  524. mat_1 = Matrix([[1/(1+s), (1+s)/(1+s**2+2*s)**3]])
  525. mat_2 = Matrix([[(1+s)], [(1+s**2+2*s)**3/(1+s)]])
  526. tm_1, tm_2 = TransferFunctionMatrix.from_Matrix(mat_1, s), TransferFunctionMatrix.from_Matrix(mat_2, s)
  527. assert (MIMOSeries(tm_2, tm_1).doit()
  528. == TransferFunctionMatrix(((TransferFunction(2*(s + 1)**2*(s**2 + 2*s + 1)**3, (s + 1)**2*(s**2 + 2*s + 1)**3, s),),)))
  529. assert MIMOSeries(tm_2, tm_1).doit().simplify() == TransferFunctionMatrix(((TransferFunction(2, 1, s),),))
  530. # calling doit() will expand the internal Series and Parallel objects.
  531. assert (MIMOSeries(-tfm3, -tfm2, tfm1, evaluate=True)
  532. == MIMOSeries(-tfm3, -tfm2, tfm1).doit()
  533. == TransferFunctionMatrix(((TransferFunction(k**2*(a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2 + (a2*p - s)**2*(s**2 + 2*s*wn*zeta + wn**2)**2 + (a2*s + p)**2,
  534. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**3, s),),
  535. (TransferFunction(-k**2*(a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2 + (-a2*p + s)*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) + (a2*p - s)*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2),
  536. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**3, s),))))
  537. assert (MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5, evaluate=True)
  538. == MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5).doit()
  539. == TransferFunctionMatrix(((TransferFunction(-k*(-a2*s - p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2)), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s), TransferFunction(k*(-a2*p - \
  540. k*(a2*s + p) + s), a2*s + p, s)), (TransferFunction(-k*(-a2*s - p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2)), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s), \
  541. TransferFunction((-a2*p + s)*(-a2*p - k*(a2*s + p) + s), (a2*s + p)**2, s)))) == MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5).rewrite(TransferFunctionMatrix))
  542. def test_Parallel_construction():
  543. tf = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  544. tf2 = TransferFunction(a2*p - s, a2*s + p, s)
  545. tf3 = TransferFunction(a0*p + p**a1 - s, p, p)
  546. tf4 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  547. inp = Function('X_d')(s)
  548. out = Function('X')(s)
  549. p0 = Parallel(tf, tf2)
  550. assert p0.args == (tf, tf2)
  551. assert p0.var == s
  552. p1 = Parallel(Series(tf, -tf2), tf2)
  553. assert p1.args == (Series(tf, -tf2), tf2)
  554. assert p1.var == s
  555. tf3_ = TransferFunction(inp, 1, s)
  556. tf4_ = TransferFunction(-out, 1, s)
  557. p2 = Parallel(tf, Series(tf3_, -tf4_), tf2)
  558. assert p2.args == (tf, Series(tf3_, -tf4_), tf2)
  559. p3 = Parallel(tf, tf2, tf4)
  560. assert p3.args == (tf, tf2, tf4)
  561. p4 = Parallel(tf3_, tf4_)
  562. assert p4.args == (tf3_, tf4_)
  563. assert p4.var == s
  564. p5 = Parallel(tf, tf2)
  565. assert p0 == p5
  566. assert not p0 == p1
  567. p6 = Parallel(tf2, tf4, Series(tf2, -tf4))
  568. assert p6.args == (tf2, tf4, Series(tf2, -tf4))
  569. p7 = Parallel(tf2, tf4, Series(tf2, -tf), tf4)
  570. assert p7.args == (tf2, tf4, Series(tf2, -tf), tf4)
  571. raises(ValueError, lambda: Parallel(tf, tf3))
  572. raises(ValueError, lambda: Parallel(tf, tf2, tf3, tf4))
  573. raises(ValueError, lambda: Parallel(-tf3, tf4))
  574. raises(TypeError, lambda: Parallel(2, tf, tf4))
  575. raises(TypeError, lambda: Parallel(s**2 + p*s, tf3, tf2))
  576. raises(TypeError, lambda: Parallel(tf3, Matrix([1, 2, 3, 4])))
  577. def test_MIMOParallel_construction():
  578. tfm1 = TransferFunctionMatrix([[TF1], [TF2], [TF3]])
  579. tfm2 = TransferFunctionMatrix([[-TF3], [TF2], [TF1]])
  580. tfm3 = TransferFunctionMatrix([[TF1]])
  581. tfm4 = TransferFunctionMatrix([[TF2], [TF1], [TF3]])
  582. tfm5 = TransferFunctionMatrix([[TF1, TF2], [TF2, TF1]])
  583. tfm6 = TransferFunctionMatrix([[TF2, TF1], [TF1, TF2]])
  584. tfm7 = TransferFunctionMatrix.from_Matrix(Matrix([[1/p]]), p)
  585. p8 = MIMOParallel(tfm1, tfm2)
  586. assert p8.args == (tfm1, tfm2)
  587. assert p8.var == s
  588. assert p8.shape == (p8.num_outputs, p8.num_inputs) == (3, 1)
  589. p9 = MIMOParallel(MIMOSeries(tfm3, tfm1), tfm2)
  590. assert p9.args == (MIMOSeries(tfm3, tfm1), tfm2)
  591. assert p9.var == s
  592. assert p9.shape == (p9.num_outputs, p9.num_inputs) == (3, 1)
  593. p10 = MIMOParallel(tfm1, MIMOSeries(tfm3, tfm4), tfm2)
  594. assert p10.args == (tfm1, MIMOSeries(tfm3, tfm4), tfm2)
  595. assert p10.var == s
  596. assert p10.shape == (p10.num_outputs, p10.num_inputs) == (3, 1)
  597. p11 = MIMOParallel(tfm2, tfm1, tfm4)
  598. assert p11.args == (tfm2, tfm1, tfm4)
  599. assert p11.shape == (p11.num_outputs, p11.num_inputs) == (3, 1)
  600. p12 = MIMOParallel(tfm6, tfm5)
  601. assert p12.args == (tfm6, tfm5)
  602. assert p12.shape == (p12.num_outputs, p12.num_inputs) == (2, 2)
  603. p13 = MIMOParallel(tfm2, tfm4, MIMOSeries(-tfm3, tfm4), -tfm4)
  604. assert p13.args == (tfm2, tfm4, MIMOSeries(-tfm3, tfm4), -tfm4)
  605. assert p13.shape == (p13.num_outputs, p13.num_inputs) == (3, 1)
  606. # arg cannot be empty tuple.
  607. raises(TypeError, lambda: MIMOParallel(()))
  608. # arg cannot contain SISO as well as MIMO systems.
  609. raises(TypeError, lambda: MIMOParallel(tfm1, tfm2, TF1))
  610. # all TFMs must have same shapes.
  611. raises(TypeError, lambda: MIMOParallel(tfm1, tfm3, tfm4))
  612. # all TFMs must be using the same complex variable.
  613. raises(ValueError, lambda: MIMOParallel(tfm3, tfm7))
  614. # Number or expression not allowed in the arguments.
  615. raises(TypeError, lambda: MIMOParallel(2, tfm1, tfm4))
  616. raises(TypeError, lambda: MIMOParallel(s**2 + p*s, -tfm4, tfm2))
  617. def test_Parallel_functions():
  618. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  619. tf2 = TransferFunction(k, 1, s)
  620. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  621. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  622. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  623. assert tf1 + tf2 + tf3 == Parallel(tf1, tf2, tf3)
  624. assert tf1 + tf2 + tf3 + tf5 == Parallel(tf1, tf2, tf3, tf5)
  625. assert tf1 + tf2 - tf3 - tf5 == Parallel(tf1, tf2, -tf3, -tf5)
  626. assert tf1 + tf2*tf3 == Parallel(tf1, Series(tf2, tf3))
  627. assert tf1 - tf2*tf3 == Parallel(tf1, -Series(tf2,tf3))
  628. assert -tf1 - tf2 == Parallel(-tf1, -tf2)
  629. assert -(tf1 + tf2) == Series(TransferFunction(-1, 1, s), Parallel(tf1, tf2))
  630. assert (tf2 + tf3)*tf1 == Series(Parallel(tf2, tf3), tf1)
  631. assert (tf1 + tf2)*(tf3*tf5) == Series(Parallel(tf1, tf2), tf3, tf5)
  632. assert -(tf2 + tf3)*-tf5 == Series(TransferFunction(-1, 1, s), Parallel(tf2, tf3), -tf5)
  633. assert tf2 + tf3 + tf2*tf1 + tf5 == Parallel(tf2, tf3, Series(tf2, tf1), tf5)
  634. assert tf2 + tf3 + tf2*tf1 - tf3 == Parallel(tf2, tf3, Series(tf2, tf1), -tf3)
  635. assert (tf1 + tf2 + tf5)*(tf3 + tf5) == Series(Parallel(tf1, tf2, tf5), Parallel(tf3, tf5))
  636. raises(ValueError, lambda: tf1 + tf2 + tf4)
  637. raises(ValueError, lambda: tf1 - tf2*tf4)
  638. raises(ValueError, lambda: tf3 + Matrix([1, 2, 3]))
  639. # evaluate=True -> doit()
  640. assert Parallel(tf1, tf2, evaluate=True) == Parallel(tf1, tf2).doit() == \
  641. TransferFunction(k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s)
  642. assert Parallel(tf1, tf2, Series(-tf1, tf3), evaluate=True) == \
  643. Parallel(tf1, tf2, Series(-tf1, tf3)).doit() == TransferFunction(k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)**2 + \
  644. (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2) + (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), (a2*s + p)*(s**2 + \
  645. 2*s*wn*zeta + wn**2)**2, s)
  646. assert Parallel(tf2, tf1, -tf3, evaluate=True) == Parallel(tf2, tf1, -tf3).doit() == \
  647. TransferFunction(a2*s + k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) + p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2) \
  648. , (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  649. assert not Parallel(tf1, -tf2, evaluate=False) == Parallel(tf1, -tf2).doit()
  650. assert Parallel(Series(tf1, tf2), Series(tf2, tf3)).doit() == \
  651. TransferFunction(k*(a2*p - s)*(s**2 + 2*s*wn*zeta + wn**2) + k*(a2*s + p), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  652. assert Parallel(-tf1, -tf2, -tf3).doit() == \
  653. TransferFunction(-a2*s - k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) - p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + wn**2), \
  654. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  655. assert -Parallel(tf1, tf2, tf3).doit() == \
  656. TransferFunction(-a2*s - k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) - p - (a2*p - s)*(s**2 + 2*s*wn*zeta + wn**2), \
  657. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  658. assert Parallel(tf2, tf3, Series(tf2, -tf1), tf3).doit() == \
  659. TransferFunction(k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) - k*(a2*s + p) + (2*a2*p - 2*s)*(s**2 + 2*s*wn*zeta \
  660. + wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  661. assert Parallel(tf1, tf2).rewrite(TransferFunction) == \
  662. TransferFunction(k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s)
  663. assert Parallel(tf2, tf1, -tf3).rewrite(TransferFunction) == \
  664. TransferFunction(a2*s + k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2) + p + (-a2*p + s)*(s**2 + 2*s*wn*zeta + \
  665. wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  666. assert Parallel(tf1, Parallel(tf2, tf3)) == Parallel(tf1, tf2, tf3) == Parallel(Parallel(tf1, tf2), tf3)
  667. P1 = Parallel(Series(tf1, tf2), Series(tf2, tf3))
  668. assert P1.is_proper
  669. assert not P1.is_strictly_proper
  670. assert P1.is_biproper
  671. P2 = Parallel(tf1, -tf2, -tf3)
  672. assert P2.is_proper
  673. assert not P2.is_strictly_proper
  674. assert P2.is_biproper
  675. P3 = Parallel(tf1, -tf2, Series(tf1, tf3))
  676. assert P3.is_proper
  677. assert not P3.is_strictly_proper
  678. assert P3.is_biproper
  679. def test_MIMOParallel_functions():
  680. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  681. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  682. tfm1 = TransferFunctionMatrix([[TF1], [TF2], [TF3]])
  683. tfm2 = TransferFunctionMatrix([[-TF2], [tf5], [-TF1]])
  684. tfm3 = TransferFunctionMatrix([[tf5], [-tf5], [TF2]])
  685. tfm4 = TransferFunctionMatrix([[TF2, -tf5], [TF1, tf5]])
  686. tfm5 = TransferFunctionMatrix([[TF1, TF2], [TF3, -tf5]])
  687. tfm6 = TransferFunctionMatrix([[-TF2]])
  688. tfm7 = TransferFunctionMatrix([[tf4], [-tf4], [tf4]])
  689. assert tfm1 + tfm2 + tfm3 == MIMOParallel(tfm1, tfm2, tfm3) == MIMOParallel(MIMOParallel(tfm1, tfm2), tfm3)
  690. assert tfm2 - tfm1 - tfm3 == MIMOParallel(tfm2, -tfm1, -tfm3)
  691. assert tfm2 - tfm3 + (-tfm1*tfm6*-tfm6) == MIMOParallel(tfm2, -tfm3, MIMOSeries(-tfm6, tfm6, -tfm1))
  692. assert tfm1 + tfm1 - (-tfm1*tfm6) == MIMOParallel(tfm1, tfm1, -MIMOSeries(tfm6, -tfm1))
  693. assert tfm2 - tfm3 - tfm1 + tfm2 == MIMOParallel(tfm2, -tfm3, -tfm1, tfm2)
  694. assert tfm1 + tfm2 - tfm3 - tfm1 == MIMOParallel(tfm1, tfm2, -tfm3, -tfm1)
  695. raises(ValueError, lambda: tfm1 + tfm2 + TF2)
  696. raises(TypeError, lambda: tfm1 - tfm2 - a1)
  697. raises(TypeError, lambda: tfm2 - tfm3 - (s - 1))
  698. raises(TypeError, lambda: -tfm3 - tfm2 - 9)
  699. raises(TypeError, lambda: (1 - p**3) - tfm3 - tfm2)
  700. # All TFMs must use the same complex var. tfm7 uses 'p'.
  701. raises(ValueError, lambda: tfm3 - tfm2 - tfm7)
  702. raises(ValueError, lambda: tfm2 - tfm1 + tfm7)
  703. # (tfm1 +/- tfm2) has (3, 1) shape while tfm4 has (2, 2) shape.
  704. raises(TypeError, lambda: tfm1 + tfm2 + tfm4)
  705. raises(TypeError, lambda: (tfm1 - tfm2) - tfm4)
  706. assert (tfm1 + tfm2)*tfm6 == MIMOSeries(tfm6, MIMOParallel(tfm1, tfm2))
  707. assert (tfm2 - tfm3)*tfm6*-tfm6 == MIMOSeries(-tfm6, tfm6, MIMOParallel(tfm2, -tfm3))
  708. assert (tfm2 - tfm1 - tfm3)*(tfm6 + tfm6) == MIMOSeries(MIMOParallel(tfm6, tfm6), MIMOParallel(tfm2, -tfm1, -tfm3))
  709. raises(ValueError, lambda: (tfm4 + tfm5)*TF1)
  710. raises(TypeError, lambda: (tfm2 - tfm3)*a2)
  711. raises(TypeError, lambda: (tfm3 + tfm2)*(s - 6))
  712. raises(TypeError, lambda: (tfm1 + tfm2 + tfm3)*0)
  713. raises(TypeError, lambda: (1 - p**3)*(tfm1 + tfm3))
  714. # (tfm3 - tfm2) has (3, 1) shape while tfm4*tfm5 has (2, 2) shape.
  715. raises(ValueError, lambda: (tfm3 - tfm2)*tfm4*tfm5)
  716. # (tfm1 - tfm2) has (3, 1) shape while tfm5 has (2, 2) shape.
  717. raises(ValueError, lambda: (tfm1 - tfm2)*tfm5)
  718. # TFM in the arguments.
  719. assert (MIMOParallel(tfm1, tfm2, evaluate=True) == MIMOParallel(tfm1, tfm2).doit()
  720. == MIMOParallel(tfm1, tfm2).rewrite(TransferFunctionMatrix)
  721. == TransferFunctionMatrix(((TransferFunction(-k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s),), \
  722. (TransferFunction(-a0 + a1*s**2 + a2*s + k*(a0 + s), a0 + s, s),), (TransferFunction(-a2*s - p + (a2*p - s)* \
  723. (s**2 + 2*s*wn*zeta + wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s),))))
  724. def test_Feedback_construction():
  725. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  726. tf2 = TransferFunction(k, 1, s)
  727. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  728. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  729. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  730. tf6 = TransferFunction(s - p, p + s, p)
  731. f1 = Feedback(TransferFunction(1, 1, s), tf1*tf2*tf3)
  732. assert f1.args == (TransferFunction(1, 1, s), Series(tf1, tf2, tf3), -1)
  733. assert f1.sys1 == TransferFunction(1, 1, s)
  734. assert f1.sys2 == Series(tf1, tf2, tf3)
  735. assert f1.var == s
  736. f2 = Feedback(tf1, tf2*tf3)
  737. assert f2.args == (tf1, Series(tf2, tf3), -1)
  738. assert f2.sys1 == tf1
  739. assert f2.sys2 == Series(tf2, tf3)
  740. assert f2.var == s
  741. f3 = Feedback(tf1*tf2, tf5)
  742. assert f3.args == (Series(tf1, tf2), tf5, -1)
  743. assert f3.sys1 == Series(tf1, tf2)
  744. f4 = Feedback(tf4, tf6)
  745. assert f4.args == (tf4, tf6, -1)
  746. assert f4.sys1 == tf4
  747. assert f4.var == p
  748. f5 = Feedback(tf5, TransferFunction(1, 1, s))
  749. assert f5.args == (tf5, TransferFunction(1, 1, s), -1)
  750. assert f5.var == s
  751. assert f5 == Feedback(tf5) # When sys2 is not passed explicitly, it is assumed to be unit tf.
  752. f6 = Feedback(TransferFunction(1, 1, p), tf4)
  753. assert f6.args == (TransferFunction(1, 1, p), tf4, -1)
  754. assert f6.var == p
  755. f7 = -Feedback(tf4*tf6, TransferFunction(1, 1, p))
  756. assert f7.args == (Series(TransferFunction(-1, 1, p), Series(tf4, tf6)), -TransferFunction(1, 1, p), -1)
  757. assert f7.sys1 == Series(TransferFunction(-1, 1, p), Series(tf4, tf6))
  758. # denominator can't be a Parallel instance
  759. raises(TypeError, lambda: Feedback(tf1, tf2 + tf3))
  760. raises(TypeError, lambda: Feedback(tf1, Matrix([1, 2, 3])))
  761. raises(TypeError, lambda: Feedback(TransferFunction(1, 1, s), s - 1))
  762. raises(TypeError, lambda: Feedback(1, 1))
  763. # raises(ValueError, lambda: Feedback(TransferFunction(1, 1, s), TransferFunction(1, 1, s)))
  764. raises(ValueError, lambda: Feedback(tf2, tf4*tf5))
  765. raises(ValueError, lambda: Feedback(tf2, tf1, 1.5)) # `sign` can only be -1 or 1
  766. raises(ValueError, lambda: Feedback(tf1, -tf1**-1)) # denominator can't be zero
  767. raises(ValueError, lambda: Feedback(tf4, tf5)) # Both systems should use the same `var`
  768. def test_Feedback_functions():
  769. tf = TransferFunction(1, 1, s)
  770. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  771. tf2 = TransferFunction(k, 1, s)
  772. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  773. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  774. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  775. tf6 = TransferFunction(s - p, p + s, p)
  776. assert tf / (tf + tf1) == Feedback(tf, tf1)
  777. assert tf / (tf + tf1*tf2*tf3) == Feedback(tf, tf1*tf2*tf3)
  778. assert tf1 / (tf + tf1*tf2*tf3) == Feedback(tf1, tf2*tf3)
  779. assert (tf1*tf2) / (tf + tf1*tf2) == Feedback(tf1*tf2, tf)
  780. assert (tf1*tf2) / (tf + tf1*tf2*tf5) == Feedback(tf1*tf2, tf5)
  781. assert (tf1*tf2) / (tf + tf1*tf2*tf5*tf3) in (Feedback(tf1*tf2, tf5*tf3), Feedback(tf1*tf2, tf3*tf5))
  782. assert tf4 / (TransferFunction(1, 1, p) + tf4*tf6) == Feedback(tf4, tf6)
  783. assert tf5 / (tf + tf5) == Feedback(tf5, tf)
  784. raises(TypeError, lambda: tf1*tf2*tf3 / (1 + tf1*tf2*tf3))
  785. raises(ValueError, lambda: tf1*tf2*tf3 / tf3*tf5)
  786. raises(ValueError, lambda: tf2*tf3 / (tf + tf2*tf3*tf4))
  787. assert Feedback(tf, tf1*tf2*tf3).doit() == \
  788. TransferFunction((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), k*(a2*p - s) + \
  789. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  790. assert Feedback(tf, tf1*tf2*tf3).sensitivity == \
  791. 1/(k*(a2*p - s)/((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  792. assert Feedback(tf1, tf2*tf3).doit() == \
  793. TransferFunction((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), (k*(a2*p - s) + \
  794. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  795. assert Feedback(tf1, tf2*tf3).sensitivity == \
  796. 1/(k*(a2*p - s)/((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  797. assert Feedback(tf1*tf2, tf5).doit() == \
  798. TransferFunction(k*(a0 + s)*(s**2 + 2*s*wn*zeta + wn**2), (k*(-a0 + a1*s**2 + a2*s) + \
  799. (a0 + s)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  800. assert Feedback(tf1*tf2, tf5, 1).sensitivity == \
  801. 1/(-k*(-a0 + a1*s**2 + a2*s)/((a0 + s)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  802. assert Feedback(tf4, tf6).doit() == \
  803. TransferFunction(p*(p + s)*(a0*p + p**a1 - s), p*(p*(p + s) + (-p + s)*(a0*p + p**a1 - s)), p)
  804. assert -Feedback(tf4*tf6, TransferFunction(1, 1, p)).doit() == \
  805. TransferFunction(-p*(-p + s)*(p + s)*(a0*p + p**a1 - s), p*(p + s)*(p*(p + s) + (-p + s)*(a0*p + p**a1 - s)), p)
  806. assert Feedback(tf, tf).doit() == TransferFunction(1, 2, s)
  807. assert Feedback(tf1, tf2*tf5).rewrite(TransferFunction) == \
  808. TransferFunction((a0 + s)*(s**2 + 2*s*wn*zeta + wn**2), (k*(-a0 + a1*s**2 + a2*s) + \
  809. (a0 + s)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  810. assert Feedback(TransferFunction(1, 1, p), tf4).rewrite(TransferFunction) == \
  811. TransferFunction(p, a0*p + p + p**a1 - s, p)
  812. def test_MIMOFeedback_construction():
  813. tf1 = TransferFunction(1, s, s)
  814. tf2 = TransferFunction(s, s**3 - 1, s)
  815. tf3 = TransferFunction(s, s + 1, s)
  816. tf4 = TransferFunction(s, s**2 + 1, s)
  817. tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  818. tfm_2 = TransferFunctionMatrix([[tf2, tf3], [tf4, tf1]])
  819. tfm_3 = TransferFunctionMatrix([[tf3, tf4], [tf1, tf2]])
  820. f1 = MIMOFeedback(tfm_1, tfm_2)
  821. assert f1.args == (tfm_1, tfm_2, -1)
  822. assert f1.sys1 == tfm_1
  823. assert f1.sys2 == tfm_2
  824. assert f1.var == s
  825. assert f1.sign == -1
  826. assert -(-f1) == f1
  827. f2 = MIMOFeedback(tfm_2, tfm_1, 1)
  828. assert f2.args == (tfm_2, tfm_1, 1)
  829. assert f2.sys1 == tfm_2
  830. assert f2.sys2 == tfm_1
  831. assert f2.var == s
  832. assert f2.sign == 1
  833. f3 = MIMOFeedback(tfm_1, MIMOSeries(tfm_3, tfm_2))
  834. assert f3.args == (tfm_1, MIMOSeries(tfm_3, tfm_2), -1)
  835. assert f3.sys1 == tfm_1
  836. assert f3.sys2 == MIMOSeries(tfm_3, tfm_2)
  837. assert f3.var == s
  838. assert f3.sign == -1
  839. mat = Matrix([[1, 1/s], [0, 1]])
  840. sys1 = controller = TransferFunctionMatrix.from_Matrix(mat, s)
  841. f4 = MIMOFeedback(sys1, controller)
  842. assert f4.args == (sys1, controller, -1)
  843. assert f4.sys1 == f4.sys2 == sys1
  844. def test_MIMOFeedback_errors():
  845. tf1 = TransferFunction(1, s, s)
  846. tf2 = TransferFunction(s, s**3 - 1, s)
  847. tf3 = TransferFunction(s, s - 1, s)
  848. tf4 = TransferFunction(s, s**2 + 1, s)
  849. tf5 = TransferFunction(1, 1, s)
  850. tf6 = TransferFunction(-1, s - 1, s)
  851. tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  852. tfm_2 = TransferFunctionMatrix([[tf2, tf3], [tf4, tf1]])
  853. tfm_3 = TransferFunctionMatrix.from_Matrix(eye(2), var=s)
  854. tfm_4 = TransferFunctionMatrix([[tf1, tf5], [tf5, tf5]])
  855. tfm_5 = TransferFunctionMatrix([[-tf3, tf3], [tf3, tf6]])
  856. # tfm_4 is inverse of tfm_5. Therefore tfm_5*tfm_4 = I
  857. tfm_6 = TransferFunctionMatrix([[-tf3]])
  858. tfm_7 = TransferFunctionMatrix([[tf3, tf4]])
  859. # Unsupported Types
  860. raises(TypeError, lambda: MIMOFeedback(tf1, tf2))
  861. raises(TypeError, lambda: MIMOFeedback(MIMOParallel(tfm_1, tfm_2), tfm_3))
  862. # Shape Errors
  863. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_6, 1))
  864. raises(ValueError, lambda: MIMOFeedback(tfm_7, tfm_7))
  865. # sign not 1/-1
  866. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_2, -2))
  867. # Non-Invertible Systems
  868. raises(ValueError, lambda: MIMOFeedback(tfm_5, tfm_4, 1))
  869. raises(ValueError, lambda: MIMOFeedback(tfm_4, -tfm_5))
  870. raises(ValueError, lambda: MIMOFeedback(tfm_3, tfm_3, 1))
  871. # Variable not same in both the systems
  872. tfm_8 = TransferFunctionMatrix.from_Matrix(eye(2), var=p)
  873. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_8, 1))
  874. def test_MIMOFeedback_functions():
  875. tf1 = TransferFunction(1, s, s)
  876. tf2 = TransferFunction(s, s - 1, s)
  877. tf3 = TransferFunction(1, 1, s)
  878. tf4 = TransferFunction(-1, s - 1, s)
  879. tfm_1 = TransferFunctionMatrix.from_Matrix(eye(2), var=s)
  880. tfm_2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf3]])
  881. tfm_3 = TransferFunctionMatrix([[-tf2, tf2], [tf2, tf4]])
  882. tfm_4 = TransferFunctionMatrix([[tf1, tf2], [-tf2, tf1]])
  883. # sensitivity, doit(), rewrite()
  884. F_1 = MIMOFeedback(tfm_2, tfm_3)
  885. F_2 = MIMOFeedback(tfm_2, MIMOSeries(tfm_4, -tfm_1), 1)
  886. assert F_1.sensitivity == Matrix([[S.Half, 0], [0, S.Half]])
  887. assert F_2.sensitivity == Matrix([[(-2*s**4 + s**2)/(s**2 - s + 1),
  888. (2*s**3 - s**2)/(s**2 - s + 1)], [-s**2, s]])
  889. assert F_1.doit() == \
  890. TransferFunctionMatrix(((TransferFunction(1, 2*s, s),
  891. TransferFunction(1, 2, s)), (TransferFunction(1, 2, s),
  892. TransferFunction(1, 2, s)))) == F_1.rewrite(TransferFunctionMatrix)
  893. assert F_2.doit(cancel=False, expand=True) == \
  894. TransferFunctionMatrix(((TransferFunction(-s**5 + 2*s**4 - 2*s**3 + s**2, s**5 - 2*s**4 + 3*s**3 - 2*s**2 + s, s),
  895. TransferFunction(-2*s**4 + 2*s**3, s**2 - s + 1, s)), (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  896. assert F_2.doit(cancel=False) == \
  897. TransferFunctionMatrix(((TransferFunction(s*(2*s**3 - s**2)*(s**2 - s + 1) + \
  898. (-2*s**4 + s**2)*(s**2 - s + 1), s*(s**2 - s + 1)**2, s), TransferFunction(-2*s**4 + 2*s**3, s**2 - s + 1, s)),
  899. (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  900. assert F_2.doit() == \
  901. TransferFunctionMatrix(((TransferFunction(s*(-2*s**2 + s*(2*s - 1) + 1), s**2 - s + 1, s),
  902. TransferFunction(-2*s**3*(s - 1), s**2 - s + 1, s)), (TransferFunction(0, 1, s), TransferFunction(s*(1 - s), 1, s))))
  903. assert F_2.doit(expand=True) == \
  904. TransferFunctionMatrix(((TransferFunction(-s**2 + s, s**2 - s + 1, s), TransferFunction(-2*s**4 + 2*s**3, s**2 - s + 1, s)),
  905. (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  906. assert -(F_1.doit()) == (-F_1).doit() # First negating then calculating vs calculating then negating.
  907. def test_TransferFunctionMatrix_construction():
  908. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  909. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  910. tfm3_ = TransferFunctionMatrix([[-TF3]])
  911. assert tfm3_.shape == (tfm3_.num_outputs, tfm3_.num_inputs) == (1, 1)
  912. assert tfm3_.args == Tuple(Tuple(Tuple(-TF3)))
  913. assert tfm3_.var == s
  914. tfm5 = TransferFunctionMatrix([[TF1, -TF2], [TF3, tf5]])
  915. assert tfm5.shape == (tfm5.num_outputs, tfm5.num_inputs) == (2, 2)
  916. assert tfm5.args == Tuple(Tuple(Tuple(TF1, -TF2), Tuple(TF3, tf5)))
  917. assert tfm5.var == s
  918. tfm7 = TransferFunctionMatrix([[TF1, TF2], [TF3, -tf5], [-tf5, TF2]])
  919. assert tfm7.shape == (tfm7.num_outputs, tfm7.num_inputs) == (3, 2)
  920. assert tfm7.args == Tuple(Tuple(Tuple(TF1, TF2), Tuple(TF3, -tf5), Tuple(-tf5, TF2)))
  921. assert tfm7.var == s
  922. # all transfer functions will use the same complex variable. tf4 uses 'p'.
  923. raises(ValueError, lambda: TransferFunctionMatrix([[TF1], [TF2], [tf4]]))
  924. raises(ValueError, lambda: TransferFunctionMatrix([[TF1, tf4], [TF3, tf5]]))
  925. # length of all the lists in the TFM should be equal.
  926. raises(ValueError, lambda: TransferFunctionMatrix([[TF1], [TF3, tf5]]))
  927. raises(ValueError, lambda: TransferFunctionMatrix([[TF1, TF3], [tf5]]))
  928. # lists should only support transfer functions in them.
  929. raises(TypeError, lambda: TransferFunctionMatrix([[TF1, TF2], [TF3, Matrix([1, 2])]]))
  930. raises(TypeError, lambda: TransferFunctionMatrix([[TF1, Matrix([1, 2])], [TF3, TF2]]))
  931. # `arg` should strictly be nested list of TransferFunction
  932. raises(ValueError, lambda: TransferFunctionMatrix([TF1, TF2, tf5]))
  933. raises(ValueError, lambda: TransferFunctionMatrix([TF1]))
  934. def test_TransferFunctionMatrix_functions():
  935. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  936. # Classmethod (from_matrix)
  937. mat_1 = ImmutableMatrix([
  938. [s*(s + 1)*(s - 3)/(s**4 + 1), 2],
  939. [p, p*(s + 1)/(s*(s**1 + 1))]
  940. ])
  941. mat_2 = ImmutableMatrix([[(2*s + 1)/(s**2 - 9)]])
  942. mat_3 = ImmutableMatrix([[1, 2], [3, 4]])
  943. assert TransferFunctionMatrix.from_Matrix(mat_1, s) == \
  944. TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s)],
  945. [TransferFunction(p, 1, s), TransferFunction(p, s, s)]])
  946. assert TransferFunctionMatrix.from_Matrix(mat_2, s) == \
  947. TransferFunctionMatrix([[TransferFunction(2*s + 1, s**2 - 9, s)]])
  948. assert TransferFunctionMatrix.from_Matrix(mat_3, p) == \
  949. TransferFunctionMatrix([[TransferFunction(1, 1, p), TransferFunction(2, 1, p)],
  950. [TransferFunction(3, 1, p), TransferFunction(4, 1, p)]])
  951. # Negating a TFM
  952. tfm1 = TransferFunctionMatrix([[TF1], [TF2]])
  953. assert -tfm1 == TransferFunctionMatrix([[-TF1], [-TF2]])
  954. tfm2 = TransferFunctionMatrix([[TF1, TF2, TF3], [tf5, -TF1, -TF3]])
  955. assert -tfm2 == TransferFunctionMatrix([[-TF1, -TF2, -TF3], [-tf5, TF1, TF3]])
  956. # subs()
  957. H_1 = TransferFunctionMatrix.from_Matrix(mat_1, s)
  958. H_2 = TransferFunctionMatrix([[TransferFunction(a*p*s, k*s**2, s), TransferFunction(p*s, k*(s**2 - a), s)]])
  959. assert H_1.subs(p, 1) == TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s)], [TransferFunction(1, 1, s), TransferFunction(1, s, s)]])
  960. assert H_1.subs({p: 1}) == TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s)], [TransferFunction(1, 1, s), TransferFunction(1, s, s)]])
  961. assert H_1.subs({p: 1, s: 1}) == TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s)], [TransferFunction(1, 1, s), TransferFunction(1, s, s)]]) # This should ignore `s` as it is `var`
  962. assert H_2.subs(p, 2) == TransferFunctionMatrix([[TransferFunction(2*a*s, k*s**2, s), TransferFunction(2*s, k*(-a + s**2), s)]])
  963. assert H_2.subs(k, 1) == TransferFunctionMatrix([[TransferFunction(a*p*s, s**2, s), TransferFunction(p*s, -a + s**2, s)]])
  964. assert H_2.subs(a, 0) == TransferFunctionMatrix([[TransferFunction(0, k*s**2, s), TransferFunction(p*s, k*s**2, s)]])
  965. assert H_2.subs({p: 1, k: 1, a: a0}) == TransferFunctionMatrix([[TransferFunction(a0*s, s**2, s), TransferFunction(s, -a0 + s**2, s)]])
  966. # transpose()
  967. assert H_1.transpose() == TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(p, 1, s)], [TransferFunction(2, 1, s), TransferFunction(p, s, s)]])
  968. assert H_2.transpose() == TransferFunctionMatrix([[TransferFunction(a*p*s, k*s**2, s)], [TransferFunction(p*s, k*(-a + s**2), s)]])
  969. assert H_1.transpose().transpose() == H_1
  970. assert H_2.transpose().transpose() == H_2
  971. # elem_poles()
  972. assert H_1.elem_poles() == [[[-sqrt(2)/2 - sqrt(2)*I/2, -sqrt(2)/2 + sqrt(2)*I/2, sqrt(2)/2 - sqrt(2)*I/2, sqrt(2)/2 + sqrt(2)*I/2], []],
  973. [[], [0]]]
  974. assert H_2.elem_poles() == [[[0, 0], [sqrt(a), -sqrt(a)]]]
  975. assert tfm2.elem_poles() == [[[wn*(-zeta + sqrt((zeta - 1)*(zeta + 1))), wn*(-zeta - sqrt((zeta - 1)*(zeta + 1)))], [], [-p/a2]],
  976. [[-a0], [wn*(-zeta + sqrt((zeta - 1)*(zeta + 1))), wn*(-zeta - sqrt((zeta - 1)*(zeta + 1)))], [-p/a2]]]
  977. # elem_zeros()
  978. assert H_1.elem_zeros() == [[[-1, 0, 3], []], [[], []]]
  979. assert H_2.elem_zeros() == [[[0], [0]]]
  980. assert tfm2.elem_zeros() == [[[], [], [a2*p]],
  981. [[-a2/(2*a1) - sqrt(4*a0*a1 + a2**2)/(2*a1), -a2/(2*a1) + sqrt(4*a0*a1 + a2**2)/(2*a1)], [], [a2*p]]]
  982. # doit()
  983. H_3 = TransferFunctionMatrix([[Series(TransferFunction(1, s**3 - 3, s), TransferFunction(s**2 - 2*s + 5, 1, s), TransferFunction(1, s, s))]])
  984. H_4 = TransferFunctionMatrix([[Parallel(TransferFunction(s**3 - 3, 4*s**4 - s**2 - 2*s + 5, s), TransferFunction(4 - s**3, 4*s**4 - s**2 - 2*s + 5, s))]])
  985. assert H_3.doit() == TransferFunctionMatrix([[TransferFunction(s**2 - 2*s + 5, s*(s**3 - 3), s)]])
  986. assert H_4.doit() == TransferFunctionMatrix([[TransferFunction(1, 4*s**4 - s**2 - 2*s + 5, s)]])
  987. # _flat()
  988. assert H_1._flat() == [TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s), TransferFunction(p, 1, s), TransferFunction(p, s, s)]
  989. assert H_2._flat() == [TransferFunction(a*p*s, k*s**2, s), TransferFunction(p*s, k*(-a + s**2), s)]
  990. assert H_3._flat() == [Series(TransferFunction(1, s**3 - 3, s), TransferFunction(s**2 - 2*s + 5, 1, s), TransferFunction(1, s, s))]
  991. assert H_4._flat() == [Parallel(TransferFunction(s**3 - 3, 4*s**4 - s**2 - 2*s + 5, s), TransferFunction(4 - s**3, 4*s**4 - s**2 - 2*s + 5, s))]
  992. # evalf()
  993. assert H_1.evalf() == \
  994. TransferFunctionMatrix(((TransferFunction(s*(s - 3.0)*(s + 1.0), s**4 + 1.0, s), TransferFunction(2.0, 1, s)), (TransferFunction(1.0*p, 1, s), TransferFunction(p, s, s))))
  995. assert H_2.subs({a:3.141, p:2.88, k:2}).evalf() == \
  996. TransferFunctionMatrix(((TransferFunction(4.5230399999999999494093572138808667659759521484375, s, s),
  997. TransferFunction(2.87999999999999989341858963598497211933135986328125*s, 2.0*s**2 - 6.282000000000000028421709430404007434844970703125, s)),))
  998. # simplify()
  999. H_5 = TransferFunctionMatrix([[TransferFunction(s**5 + s**3 + s, s - s**2, s),
  1000. TransferFunction((s + 3)*(s - 1), (s - 1)*(s + 5), s)]])
  1001. assert H_5.simplify() == simplify(H_5) == \
  1002. TransferFunctionMatrix(((TransferFunction(-s**4 - s**2 - 1, s - 1, s), TransferFunction(s + 3, s + 5, s)),))
  1003. # expand()
  1004. assert (H_1.expand()
  1005. == TransferFunctionMatrix(((TransferFunction(s**3 - 2*s**2 - 3*s, s**4 + 1, s), TransferFunction(2, 1, s)),
  1006. (TransferFunction(p, 1, s), TransferFunction(p, s, s)))))
  1007. assert H_5.expand() == \
  1008. TransferFunctionMatrix(((TransferFunction(s**5 + s**3 + s, -s**2 + s, s), TransferFunction(s**2 + 2*s - 3, s**2 + 4*s - 5, s)),))
  1009. def test_TransferFunction_bilinear():
  1010. # simple transfer function, e.g. ohms law
  1011. tf = TransferFunction(1, a*s+b, s)
  1012. numZ, denZ = bilinear(tf, T)
  1013. # discretized transfer function with coefs from tf.bilinear()
  1014. tf_test_bilinear = TransferFunction(s*numZ[0]+numZ[1], s*denZ[0]+denZ[1], s)
  1015. # corresponding tf with manually calculated coefs
  1016. tf_test_manual = TransferFunction(s*T+T, s*(T*b+2*a)+T*b-2*a, s)
  1017. assert S.Zero == (tf_test_bilinear-tf_test_manual).simplify().num
  1018. def test_TransferFunction_backward_diff():
  1019. # simple transfer function, e.g. ohms law
  1020. tf = TransferFunction(1, a*s+b, s)
  1021. numZ, denZ = backward_diff(tf, T)
  1022. # discretized transfer function with coefs from tf.bilinear()
  1023. tf_test_bilinear = TransferFunction(s*numZ[0]+numZ[1], s*denZ[0]+denZ[1], s)
  1024. # corresponding tf with manually calculated coefs
  1025. tf_test_manual = TransferFunction(s*T, s*(T*b+a)-a, s)
  1026. assert S.Zero == (tf_test_bilinear-tf_test_manual).simplify().num