test_lti.py 102 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273
  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, pi, 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, log)
  9. from sympy.functions.special.delta_functions import Heaviside
  10. from sympy.functions.elementary.miscellaneous import sqrt
  11. from sympy.functions.elementary.trigonometric import atan
  12. from sympy.matrices.dense import eye
  13. from sympy.physics.control.lti import SISOLinearTimeInvariant
  14. from sympy.polys.polytools import factor
  15. from sympy.polys.rootoftools import CRootOf
  16. from sympy.simplify.simplify import simplify
  17. from sympy.core.containers import Tuple
  18. from sympy.matrices import ImmutableMatrix, Matrix, ShapeError
  19. from sympy.functions.elementary.trigonometric import sin, cos
  20. from sympy.physics.control import (TransferFunction, PIDController, Series, Parallel,
  21. Feedback, TransferFunctionMatrix, MIMOSeries, MIMOParallel, MIMOFeedback,
  22. StateSpace, gbt, bilinear, forward_diff, backward_diff, phase_margin, gain_margin)
  23. from sympy.testing.pytest import raises
  24. a, x, b, c, s, g, d, p, k, tau, zeta, wn, T = symbols('a, x, b, c, s, g, d, p, k,\
  25. tau, zeta, wn, T')
  26. a0, a1, a2, a3, b0, b1, b2, b3, c0, c1, c2, c3, d0, d1, d2, d3 = symbols('a0:4,\
  27. b0:4, c0:4, d0:4')
  28. TF1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  29. TF2 = TransferFunction(k, 1, s)
  30. TF3 = TransferFunction(a2*p - s, a2*s + p, s)
  31. def test_TransferFunction_construction():
  32. tf = TransferFunction(s + 1, s**2 + s + 1, s)
  33. assert tf.num == (s + 1)
  34. assert tf.den == (s**2 + s + 1)
  35. assert tf.args == (s + 1, s**2 + s + 1, s)
  36. tf1 = TransferFunction(s + 4, s - 5, s)
  37. assert tf1.num == (s + 4)
  38. assert tf1.den == (s - 5)
  39. assert tf1.args == (s + 4, s - 5, s)
  40. # using different polynomial variables.
  41. tf2 = TransferFunction(p + 3, p**2 - 9, p)
  42. assert tf2.num == (p + 3)
  43. assert tf2.den == (p**2 - 9)
  44. assert tf2.args == (p + 3, p**2 - 9, p)
  45. tf3 = TransferFunction(p**3 + 5*p**2 + 4, p**4 + 3*p + 1, p)
  46. assert tf3.args == (p**3 + 5*p**2 + 4, p**4 + 3*p + 1, p)
  47. # no pole-zero cancellation on its own.
  48. tf4 = TransferFunction((s + 3)*(s - 1), (s - 1)*(s + 5), s)
  49. assert tf4.den == (s - 1)*(s + 5)
  50. assert tf4.args == ((s + 3)*(s - 1), (s - 1)*(s + 5), s)
  51. tf4_ = TransferFunction(p + 2, p + 2, p)
  52. assert tf4_.args == (p + 2, p + 2, p)
  53. tf5 = TransferFunction(s - 1, 4 - p, s)
  54. assert tf5.args == (s - 1, 4 - p, s)
  55. tf5_ = TransferFunction(s - 1, s - 1, s)
  56. assert tf5_.args == (s - 1, s - 1, s)
  57. tf6 = TransferFunction(5, 6, s)
  58. assert tf6.num == 5
  59. assert tf6.den == 6
  60. assert tf6.args == (5, 6, s)
  61. tf6_ = TransferFunction(1/2, 4, s)
  62. assert tf6_.num == 0.5
  63. assert tf6_.den == 4
  64. assert tf6_.args == (0.500000000000000, 4, s)
  65. tf7 = TransferFunction(3*s**2 + 2*p + 4*s, 8*p**2 + 7*s, s)
  66. tf8 = TransferFunction(3*s**2 + 2*p + 4*s, 8*p**2 + 7*s, p)
  67. assert not tf7 == tf8
  68. tf7_ = TransferFunction(a0*s + a1*s**2 + a2*s**3, b0*p - b1*s, s)
  69. tf8_ = TransferFunction(a0*s + a1*s**2 + a2*s**3, b0*p - b1*s, s)
  70. assert tf7_ == tf8_
  71. assert -(-tf7_) == tf7_ == -(-(-(-tf7_)))
  72. tf9 = TransferFunction(a*s**3 + b*s**2 + g*s + d, d*p + g*p**2 + g*s, s)
  73. assert tf9.args == (a*s**3 + b*s**2 + d + g*s, d*p + g*p**2 + g*s, s)
  74. tf10 = TransferFunction(p**3 + d, g*s**2 + d*s + a, p)
  75. tf10_ = TransferFunction(p**3 + d, g*s**2 + d*s + a, p)
  76. assert tf10.args == (d + p**3, a + d*s + g*s**2, p)
  77. assert tf10_ == tf10
  78. tf11 = TransferFunction(a1*s + a0, b2*s**2 + b1*s + b0, s)
  79. assert tf11.num == (a0 + a1*s)
  80. assert tf11.den == (b0 + b1*s + b2*s**2)
  81. assert tf11.args == (a0 + a1*s, b0 + b1*s + b2*s**2, s)
  82. # when just the numerator is 0, leave the denominator alone.
  83. tf12 = TransferFunction(0, p**2 - p + 1, p)
  84. assert tf12.args == (0, p**2 - p + 1, p)
  85. tf13 = TransferFunction(0, 1, s)
  86. assert tf13.args == (0, 1, s)
  87. # float exponents
  88. tf14 = TransferFunction(a0*s**0.5 + a2*s**0.6 - a1, a1*p**(-8.7), s)
  89. assert tf14.args == (a0*s**0.5 - a1 + a2*s**0.6, a1*p**(-8.7), s)
  90. tf15 = TransferFunction(a2**2*p**(1/4) + a1*s**(-4/5), a0*s - p, p)
  91. assert tf15.args == (a1*s**(-0.8) + a2**2*p**0.25, a0*s - p, p)
  92. omega_o, k_p, k_o, k_i = symbols('omega_o, k_p, k_o, k_i')
  93. tf18 = TransferFunction((k_p + k_o*s + k_i/s), s**2 + 2*omega_o*s + omega_o**2, s)
  94. assert tf18.num == k_i/s + k_o*s + k_p
  95. assert tf18.args == (k_i/s + k_o*s + k_p, omega_o**2 + 2*omega_o*s + s**2, s)
  96. # ValueError when denominator is zero.
  97. raises(ValueError, lambda: TransferFunction(4, 0, s))
  98. raises(ValueError, lambda: TransferFunction(s, 0, s))
  99. raises(ValueError, lambda: TransferFunction(0, 0, s))
  100. raises(TypeError, lambda: TransferFunction(Matrix([1, 2, 3]), s, s))
  101. raises(TypeError, lambda: TransferFunction(s**2 + 2*s - 1, s + 3, 3))
  102. raises(TypeError, lambda: TransferFunction(p + 1, 5 - p, 4))
  103. raises(TypeError, lambda: TransferFunction(3, 4, 8))
  104. def test_TransferFunction_functions():
  105. # classmethod from_rational_expression
  106. expr_1 = Mul(0, Pow(s, -1, evaluate=False), evaluate=False)
  107. expr_2 = s/0
  108. expr_3 = (p*s**2 + 5*s)/(s + 1)**3
  109. expr_4 = 6
  110. expr_5 = ((2 + 3*s)*(5 + 2*s))/((9 + 3*s)*(5 + 2*s**2))
  111. expr_6 = (9*s**4 + 4*s**2 + 8)/((s + 1)*(s + 9))
  112. tf = TransferFunction(s + 1, s**2 + 2, s)
  113. delay = exp(-s/tau)
  114. expr_7 = delay*tf.to_expr()
  115. H1 = TransferFunction.from_rational_expression(expr_7, s)
  116. H2 = TransferFunction(s + 1, (s**2 + 2)*exp(s/tau), s)
  117. expr_8 = Add(2, 3*s/(s**2 + 1), evaluate=False)
  118. assert TransferFunction.from_rational_expression(expr_1) == TransferFunction(0, s, s)
  119. raises(ZeroDivisionError, lambda: TransferFunction.from_rational_expression(expr_2))
  120. raises(ValueError, lambda: TransferFunction.from_rational_expression(expr_3))
  121. assert TransferFunction.from_rational_expression(expr_3, s) == TransferFunction((p*s**2 + 5*s), (s + 1)**3, s)
  122. assert TransferFunction.from_rational_expression(expr_3, p) == TransferFunction((p*s**2 + 5*s), (s + 1)**3, p)
  123. raises(ValueError, lambda: TransferFunction.from_rational_expression(expr_4))
  124. assert TransferFunction.from_rational_expression(expr_4, s) == TransferFunction(6, 1, s)
  125. assert TransferFunction.from_rational_expression(expr_5, s) == \
  126. TransferFunction((2 + 3*s)*(5 + 2*s), (9 + 3*s)*(5 + 2*s**2), s)
  127. assert TransferFunction.from_rational_expression(expr_6, s) == \
  128. TransferFunction((9*s**4 + 4*s**2 + 8), (s + 1)*(s + 9), s)
  129. assert H1 == H2
  130. assert TransferFunction.from_rational_expression(expr_8, s) == \
  131. TransferFunction(2*s**2 + 3*s + 2, s**2 + 1, s)
  132. # classmethod from_coeff_lists
  133. tf1 = TransferFunction.from_coeff_lists([1, 2], [3, 4, 5], s)
  134. num2 = [p**2, 2*p]
  135. den2 = [p**3, p + 1, 4]
  136. tf2 = TransferFunction.from_coeff_lists(num2, den2, s)
  137. num3 = [1, 2, 3]
  138. den3 = [0, 0]
  139. assert tf1 == TransferFunction(s + 2, 3*s**2 + 4*s + 5, s)
  140. assert tf2 == TransferFunction(p**2*s + 2*p, p**3*s**2 + s*(p + 1) + 4, s)
  141. raises(ZeroDivisionError, lambda: TransferFunction.from_coeff_lists(num3, den3, s))
  142. # classmethod from_zpk
  143. zeros = [4]
  144. poles = [-1+2j, -1-2j]
  145. gain = 3
  146. tf1 = TransferFunction.from_zpk(zeros, poles, gain, s)
  147. assert tf1 == TransferFunction(3*s - 12, (s + 1.0 - 2.0*I)*(s + 1.0 + 2.0*I), s)
  148. # explicitly cancel poles and zeros.
  149. tf0 = TransferFunction(s**5 + s**3 + s, s - s**2, s)
  150. a = TransferFunction(-(s**4 + s**2 + 1), s - 1, s)
  151. assert tf0.simplify() == simplify(tf0) == a
  152. tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  153. b = TransferFunction(p + 3, p + 5, p)
  154. assert tf1.simplify() == simplify(tf1) == b
  155. # expand the numerator and the denominator.
  156. G1 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  157. G2 = TransferFunction(1, -3, p)
  158. c = (a2*s**p + a1*s**s + a0*p**p)*(p**s + s**p)
  159. d = (b0*s**s + b1*p**s)*(b2*s*p + p**p)
  160. 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)
  161. f = b0*b2*p*s*s**s + b0*p**p*s**s + b1*b2*p*p**s*s + b1*p**p*p**s
  162. g = a1*a2*s*s**p + a1*p*s + a2*b1*p*s*s**p + b1*p**2*s
  163. G3 = TransferFunction(c, d, s)
  164. G4 = TransferFunction(a0*s**s - b0*p**p, (a1*s + b1*s*p)*(a2*s**p + p), p)
  165. assert G1.expand() == TransferFunction(s**2 - 2*s + 1, s**4 + 2*s**2 + 1, s)
  166. assert tf1.expand() == TransferFunction(p**2 + 2*p - 3, p**2 + 4*p - 5, p)
  167. assert G2.expand() == G2
  168. assert G3.expand() == TransferFunction(e, f, s)
  169. assert G4.expand() == TransferFunction(a0*s**s - b0*p**p, g, p)
  170. # purely symbolic polynomials.
  171. p1 = a1*s + a0
  172. p2 = b2*s**2 + b1*s + b0
  173. SP1 = TransferFunction(p1, p2, s)
  174. expect1 = TransferFunction(2.0*s + 1.0, 5.0*s**2 + 4.0*s + 3.0, s)
  175. expect1_ = TransferFunction(2*s + 1, 5*s**2 + 4*s + 3, s)
  176. assert SP1.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}) == expect1_
  177. assert SP1.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}).evalf() == expect1
  178. assert expect1_.evalf() == expect1
  179. c1, d0, d1, d2 = symbols('c1, d0:3')
  180. p3, p4 = c1*p, d2*p**3 + d1*p**2 - d0
  181. SP2 = TransferFunction(p3, p4, p)
  182. expect2 = TransferFunction(2.0*p, 5.0*p**3 + 2.0*p**2 - 3.0, p)
  183. expect2_ = TransferFunction(2*p, 5*p**3 + 2*p**2 - 3, p)
  184. assert SP2.subs({c1: 2, d0: 3, d1: 2, d2: 5}) == expect2_
  185. assert SP2.subs({c1: 2, d0: 3, d1: 2, d2: 5}).evalf() == expect2
  186. assert expect2_.evalf() == expect2
  187. SP3 = TransferFunction(a0*p**3 + a1*s**2 - b0*s + b1, a1*s + p, s)
  188. expect3 = TransferFunction(2.0*p**3 + 4.0*s**2 - s + 5.0, p + 4.0*s, s)
  189. expect3_ = TransferFunction(2*p**3 + 4*s**2 - s + 5, p + 4*s, s)
  190. assert SP3.subs({a0: 2, a1: 4, b0: 1, b1: 5}) == expect3_
  191. assert SP3.subs({a0: 2, a1: 4, b0: 1, b1: 5}).evalf() == expect3
  192. assert expect3_.evalf() == expect3
  193. SP4 = TransferFunction(s - a1*p**3, a0*s + p, p)
  194. expect4 = TransferFunction(7.0*p**3 + s, p - s, p)
  195. expect4_ = TransferFunction(7*p**3 + s, p - s, p)
  196. assert SP4.subs({a0: -1, a1: -7}) == expect4_
  197. assert SP4.subs({a0: -1, a1: -7}).evalf() == expect4
  198. assert expect4_.evalf() == expect4
  199. # evaluate the transfer function at particular frequencies.
  200. assert tf1.eval_frequency(wn) == wn**2/(wn**2 + 4*wn - 5) + 2*wn/(wn**2 + 4*wn - 5) - 3/(wn**2 + 4*wn - 5)
  201. assert G1.eval_frequency(1 + I) == S(3)/25 + S(4)*I/25
  202. assert G4.eval_frequency(S(5)/3) == \
  203. a0*s**s/(a1*a2*s**(S(8)/3) + S(5)*a1*s/3 + 5*a2*b1*s**(S(8)/3)/3 + S(25)*b1*s/9) - 5*3**(S(1)/3)*5**(S(2)/3)*b0/(9*a1*a2*s**(S(8)/3) + 15*a1*s + 15*a2*b1*s**(S(8)/3) + 25*b1*s)
  204. # Low-frequency (or DC) gain.
  205. assert tf0.dc_gain() == 1
  206. assert tf1.dc_gain() == Rational(3, 5)
  207. assert SP2.dc_gain() == 0
  208. assert expect4.dc_gain() == -1
  209. assert expect2_.dc_gain() == 0
  210. assert TransferFunction(1, s, s).dc_gain() == oo
  211. # Poles of a transfer function.
  212. tf_ = TransferFunction(x**3 - k, k, x)
  213. _tf = TransferFunction(k, x**4 - k, x)
  214. TF_ = TransferFunction(x**2, x**10 + x + x**2, x)
  215. _TF = TransferFunction(x**10 + x + x**2, x**2, x)
  216. assert G1.poles() == [I, I, -I, -I]
  217. assert G2.poles() == []
  218. assert tf1.poles() == [-5, 1]
  219. assert expect4_.poles() == [s]
  220. assert SP4.poles() == [-a0*s]
  221. assert expect3.poles() == [-0.25*p]
  222. assert str(expect2.poles()) == str([0.729001428685125, -0.564500714342563 - 0.710198984796332*I, -0.564500714342563 + 0.710198984796332*I])
  223. assert str(expect1.poles()) == str([-0.4 - 0.66332495807108*I, -0.4 + 0.66332495807108*I])
  224. assert _tf.poles() == [k**(Rational(1, 4)), -k**(Rational(1, 4)), I*k**(Rational(1, 4)), -I*k**(Rational(1, 4))]
  225. assert TF_.poles() == [CRootOf(x**9 + x + 1, 0), 0, CRootOf(x**9 + x + 1, 1), CRootOf(x**9 + x + 1, 2),
  226. CRootOf(x**9 + x + 1, 3), CRootOf(x**9 + x + 1, 4), CRootOf(x**9 + x + 1, 5), CRootOf(x**9 + x + 1, 6),
  227. CRootOf(x**9 + x + 1, 7), CRootOf(x**9 + x + 1, 8)]
  228. raises(NotImplementedError, lambda: TransferFunction(x**2, a0*x**10 + x + x**2, x).poles())
  229. # Stability of a transfer function.
  230. q, r = symbols('q, r', negative=True)
  231. t = symbols('t', positive=True)
  232. TF_ = TransferFunction(s**2 + a0 - a1*p, q*s - r, s)
  233. stable_tf = TransferFunction(s**2 + a0 - a1*p, q*s - 1, s)
  234. stable_tf_ = TransferFunction(s**2 + a0 - a1*p, q*s - t, s)
  235. assert G1.is_stable() is False
  236. assert G2.is_stable() is True
  237. assert tf1.is_stable() is False # as one pole is +ve, and the other is -ve.
  238. assert expect2.is_stable() is False
  239. assert expect1.is_stable() is True
  240. assert stable_tf.is_stable() is True
  241. assert stable_tf_.is_stable() is True
  242. assert TF_.is_stable() is False
  243. assert expect4_.is_stable() is None # no assumption provided for the only pole 's'.
  244. assert SP4.is_stable() is None
  245. # Zeros of a transfer function.
  246. assert G1.zeros() == [1, 1]
  247. assert G2.zeros() == []
  248. assert tf1.zeros() == [-3, 1]
  249. assert expect4_.zeros() == [7**(Rational(2, 3))*(-s)**(Rational(1, 3))/7, -7**(Rational(2, 3))*(-s)**(Rational(1, 3))/14 -
  250. 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]
  251. assert SP4.zeros() == [(s/a1)**(Rational(1, 3)), -(s/a1)**(Rational(1, 3))/2 - sqrt(3)*I*(s/a1)**(Rational(1, 3))/2,
  252. -(s/a1)**(Rational(1, 3))/2 + sqrt(3)*I*(s/a1)**(Rational(1, 3))/2]
  253. assert str(expect3.zeros()) == str([0.125 - 1.11102430216445*sqrt(-0.405063291139241*p**3 - 1.0),
  254. 1.11102430216445*sqrt(-0.405063291139241*p**3 - 1.0) + 0.125])
  255. assert tf_.zeros() == [k**(Rational(1, 3)), -k**(Rational(1, 3))/2 - sqrt(3)*I*k**(Rational(1, 3))/2,
  256. -k**(Rational(1, 3))/2 + sqrt(3)*I*k**(Rational(1, 3))/2]
  257. assert _TF.zeros() == [CRootOf(x**9 + x + 1, 0), 0, CRootOf(x**9 + x + 1, 1), CRootOf(x**9 + x + 1, 2),
  258. CRootOf(x**9 + x + 1, 3), CRootOf(x**9 + x + 1, 4), CRootOf(x**9 + x + 1, 5), CRootOf(x**9 + x + 1, 6),
  259. CRootOf(x**9 + x + 1, 7), CRootOf(x**9 + x + 1, 8)]
  260. raises(NotImplementedError, lambda: TransferFunction(a0*x**10 + x + x**2, x**2, x).zeros())
  261. # negation of TF.
  262. tf2 = TransferFunction(s + 3, s**2 - s**3 + 9, s)
  263. tf3 = TransferFunction(-3*p + 3, 1 - p, p)
  264. assert -tf2 == TransferFunction(-s - 3, s**2 - s**3 + 9, s)
  265. assert -tf3 == TransferFunction(3*p - 3, 1 - p, p)
  266. # taking power of a TF.
  267. tf4 = TransferFunction(p + 4, p - 3, p)
  268. tf5 = TransferFunction(s**2 + 1, 1 - s, s)
  269. expect2 = TransferFunction((s**2 + 1)**3, (1 - s)**3, s)
  270. expect1 = TransferFunction((p + 4)**2, (p - 3)**2, p)
  271. assert (tf4*tf4).doit() == tf4**2 == pow(tf4, 2) == expect1
  272. assert (tf5*tf5*tf5).doit() == tf5**3 == pow(tf5, 3) == expect2
  273. assert tf5**0 == pow(tf5, 0) == TransferFunction(1, 1, s)
  274. assert Series(tf4).doit()**-1 == tf4**-1 == pow(tf4, -1) == TransferFunction(p - 3, p + 4, p)
  275. assert (tf5*tf5).doit()**-1 == tf5**-2 == pow(tf5, -2) == TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  276. raises(ValueError, lambda: tf4**(s**2 + s - 1))
  277. raises(ValueError, lambda: tf5**s)
  278. raises(ValueError, lambda: tf4**tf5)
  279. # SymPy's own functions.
  280. tf = TransferFunction(s - 1, s**2 - 2*s + 1, s)
  281. tf6 = TransferFunction(s + p, p**2 - 5, s)
  282. assert factor(tf) == TransferFunction(s - 1, (s - 1)**2, s)
  283. assert tf.num.subs(s, 2) == tf.den.subs(s, 2) == 1
  284. # subs & xreplace
  285. assert tf.subs(s, 2) == TransferFunction(s - 1, s**2 - 2*s + 1, s)
  286. assert tf6.subs(p, 3) == TransferFunction(s + 3, 4, s)
  287. assert tf3.xreplace({p: s}) == TransferFunction(-3*s + 3, 1 - s, s)
  288. raises(TypeError, lambda: tf3.xreplace({p: exp(2)}))
  289. assert tf3.subs(p, exp(2)) == tf3
  290. tf7 = TransferFunction(a0*s**p + a1*p**s, a2*p - s, s)
  291. assert tf7.xreplace({s: k}) == TransferFunction(a0*k**p + a1*p**k, a2*p - k, k)
  292. assert tf7.subs(s, k) == TransferFunction(a0*s**p + a1*p**s, a2*p - s, s)
  293. # Conversion to Expr with to_expr()
  294. tf8 = TransferFunction(a0*s**5 + 5*s**2 + 3, s**6 - 3, s)
  295. tf9 = TransferFunction((5 + s), (5 + s)*(6 + s), s)
  296. tf10 = TransferFunction(0, 1, s)
  297. tf11 = TransferFunction(1, 1, s)
  298. assert tf8.to_expr() == Mul((a0*s**5 + 5*s**2 + 3), Pow((s**6 - 3), -1, evaluate=False), evaluate=False)
  299. assert tf9.to_expr() == Mul((s + 5), Pow((5 + s)*(6 + s), -1, evaluate=False), evaluate=False)
  300. assert tf10.to_expr() == Mul(S(0), Pow(1, -1, evaluate=False), evaluate=False)
  301. assert tf11.to_expr() == Pow(1, -1, evaluate=False)
  302. def test_TransferFunction_addition_and_subtraction():
  303. tf1 = TransferFunction(s + 6, s - 5, s)
  304. tf2 = TransferFunction(s + 3, s + 1, s)
  305. tf3 = TransferFunction(s + 1, s**2 + s + 1, s)
  306. tf4 = TransferFunction(p, 2 - p, p)
  307. # addition
  308. assert tf1 + tf2 == Parallel(tf1, tf2)
  309. assert tf3 + tf1 == Parallel(tf3, tf1)
  310. assert -tf1 + tf2 + tf3 == Parallel(-tf1, tf2, tf3)
  311. assert tf1 + (tf2 + tf3) == Parallel(tf1, tf2, tf3)
  312. c = symbols("c", commutative=False)
  313. raises(ValueError, lambda: tf1 + Matrix([1, 2, 3]))
  314. raises(ValueError, lambda: tf2 + c)
  315. raises(ValueError, lambda: tf3 + tf4)
  316. raises(ValueError, lambda: tf1 + (s - 1))
  317. raises(ValueError, lambda: tf1 + 8)
  318. raises(ValueError, lambda: (1 - p**3) + tf1)
  319. # subtraction
  320. assert tf1 - tf2 == Parallel(tf1, -tf2)
  321. assert tf3 - tf2 == Parallel(tf3, -tf2)
  322. assert -tf1 - tf3 == Parallel(-tf1, -tf3)
  323. assert tf1 - tf2 + tf3 == Parallel(tf1, -tf2, tf3)
  324. raises(ValueError, lambda: tf1 - Matrix([1, 2, 3]))
  325. raises(ValueError, lambda: tf3 - tf4)
  326. raises(ValueError, lambda: tf1 - (s - 1))
  327. raises(ValueError, lambda: tf1 - 8)
  328. raises(ValueError, lambda: (s + 5) - tf2)
  329. raises(ValueError, lambda: (1 + p**4) - tf1)
  330. def test_TransferFunction_multiplication_and_division():
  331. G1 = TransferFunction(s + 3, -s**3 + 9, s)
  332. G2 = TransferFunction(s + 1, s - 5, s)
  333. G3 = TransferFunction(p, p**4 - 6, p)
  334. G4 = TransferFunction(p + 4, p - 5, p)
  335. G5 = TransferFunction(s + 6, s - 5, s)
  336. G6 = TransferFunction(s + 3, s + 1, s)
  337. G7 = TransferFunction(1, 1, s)
  338. # multiplication
  339. assert G1*G2 == Series(G1, G2)
  340. assert -G1*G5 == Series(-G1, G5)
  341. assert -G2*G5*-G6 == Series(-G2, G5, -G6)
  342. assert -G1*-G2*-G5*-G6 == Series(-G1, -G2, -G5, -G6)
  343. assert G3*G4 == Series(G3, G4)
  344. assert (G1*G2)*-(G5*G6) == \
  345. Series(G1, G2, TransferFunction(-1, 1, s), Series(G5, G6))
  346. assert G1*G2*(G5 + G6) == Series(G1, G2, Parallel(G5, G6))
  347. # division - See ``test_Feedback_functions()`` for division by Parallel objects.
  348. assert G5/G6 == Series(G5, pow(G6, -1))
  349. assert -G3/G4 == Series(-G3, pow(G4, -1))
  350. assert (G5*G6)/G7 == Series(G5, G6, pow(G7, -1))
  351. c = symbols("c", commutative=False)
  352. raises(ValueError, lambda: G3 * Matrix([1, 2, 3]))
  353. raises(ValueError, lambda: G1 * c)
  354. raises(ValueError, lambda: G3 * G5)
  355. raises(ValueError, lambda: G5 * (s - 1))
  356. raises(ValueError, lambda: 9 * G5)
  357. raises(ValueError, lambda: G3 / Matrix([1, 2, 3]))
  358. raises(ValueError, lambda: G6 / 0)
  359. raises(ValueError, lambda: G3 / G5)
  360. raises(ValueError, lambda: G5 / 2)
  361. raises(ValueError, lambda: G5 / s**2)
  362. raises(ValueError, lambda: (s - 4*s**2) / G2)
  363. raises(ValueError, lambda: 0 / G4)
  364. raises(ValueError, lambda: G7 / (1 + G6))
  365. raises(ValueError, lambda: G7 / (G5 * G6))
  366. raises(ValueError, lambda: G7 / (G7 + (G5 + G6)))
  367. def test_TransferFunction_is_proper():
  368. omega_o, zeta, tau = symbols('omega_o, zeta, tau')
  369. G1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  370. G2 = TransferFunction(tau - s**3, tau + p**4, tau)
  371. G3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  372. G4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  373. assert G1.is_proper
  374. assert G2.is_proper
  375. assert G3.is_proper
  376. assert not G4.is_proper
  377. def test_TransferFunction_is_strictly_proper():
  378. omega_o, zeta, tau = symbols('omega_o, zeta, tau')
  379. tf1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  380. tf2 = TransferFunction(tau - s**3, tau + p**4, tau)
  381. tf3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  382. tf4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  383. assert not tf1.is_strictly_proper
  384. assert not tf2.is_strictly_proper
  385. assert tf3.is_strictly_proper
  386. assert not tf4.is_strictly_proper
  387. def test_TransferFunction_is_biproper():
  388. tau, omega_o, zeta = symbols('tau, omega_o, zeta')
  389. tf1 = TransferFunction(omega_o**2, s**2 + p*omega_o*zeta*s + omega_o**2, omega_o)
  390. tf2 = TransferFunction(tau - s**3, tau + p**4, tau)
  391. tf3 = TransferFunction(a*b*s**3 + s**2 - a*p + s, b - s*p**2, p)
  392. tf4 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  393. assert tf1.is_biproper
  394. assert tf2.is_biproper
  395. assert not tf3.is_biproper
  396. assert not tf4.is_biproper
  397. def test_PIDController():
  398. kp, ki, kd, tf = symbols("kp ki kd tf")
  399. p1 = PIDController(kp, ki, kd, tf)
  400. p2 = PIDController()
  401. # Type Checking
  402. assert isinstance(p1, PIDController)
  403. assert isinstance(p1, TransferFunction)
  404. # Properties checking
  405. assert p1 == PIDController(kp, ki, kd, tf, s)
  406. assert p2 == PIDController(kp, ki, kd, 0, s)
  407. assert p1.num == kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s
  408. assert p1.den == s**2*tf + s
  409. assert p1.var == s
  410. assert p1.kp == kp
  411. assert p1.ki == ki
  412. assert p1.kd == kd
  413. assert p1.tf == tf
  414. # Functionality checking
  415. assert p1.doit() == TransferFunction(kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s, s**2*tf + s, s)
  416. assert p1.is_proper == True
  417. assert p1.is_biproper == True
  418. assert p1.is_strictly_proper == False
  419. assert p2.doit() == TransferFunction(kd*s**2 + ki + kp*s, s, s)
  420. # Using PIDController with TransferFunction
  421. tf1 = TransferFunction(s, s + 1, s)
  422. par1 = Parallel(p1, tf1)
  423. ser1 = Series(p1, tf1)
  424. fed1 = Feedback(p1, tf1)
  425. assert par1 == Parallel(PIDController(kp, ki, kd, tf, s), TransferFunction(s, s + 1, s))
  426. assert ser1 == Series(PIDController(kp, ki, kd, tf, s), TransferFunction(s, s + 1, s))
  427. assert fed1 == Feedback(PIDController(kp, ki, kd, tf, s), TransferFunction(s, s + 1, s))
  428. assert par1.doit() == TransferFunction(s*(s**2*tf + s) + (s + 1)*(kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s),
  429. (s + 1)*(s**2*tf + s), s)
  430. assert ser1.doit() == TransferFunction(s*(kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s),
  431. (s + 1)*(s**2*tf + s), s)
  432. assert fed1.doit() == TransferFunction((s + 1)*(s**2*tf + s)*(kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s),
  433. (s*(kd*s**2 + ki*s*tf + ki + kp*s**2*tf + kp*s) + (s + 1)*(s**2*tf + s))*(s**2*tf + s), s)
  434. def test_Series_construction():
  435. tf = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  436. tf2 = TransferFunction(a2*p - s, a2*s + p, s)
  437. tf3 = TransferFunction(a0*p + p**a1 - s, p, p)
  438. tf4 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  439. inp = Function('X_d')(s)
  440. out = Function('X')(s)
  441. s0 = Series(tf, tf2)
  442. assert s0.args == (tf, tf2)
  443. assert s0.var == s
  444. s1 = Series(Parallel(tf, -tf2), tf2)
  445. assert s1.args == (Parallel(tf, -tf2), tf2)
  446. assert s1.var == s
  447. tf3_ = TransferFunction(inp, 1, s)
  448. tf4_ = TransferFunction(-out, 1, s)
  449. s2 = Series(tf, Parallel(tf3_, tf4_), tf2)
  450. assert s2.args == (tf, Parallel(tf3_, tf4_), tf2)
  451. s3 = Series(tf, tf2, tf4)
  452. assert s3.args == (tf, tf2, tf4)
  453. s4 = Series(tf3_, tf4_)
  454. assert s4.args == (tf3_, tf4_)
  455. assert s4.var == s
  456. s6 = Series(tf2, tf4, Parallel(tf2, -tf), tf4)
  457. assert s6.args == (tf2, tf4, Parallel(tf2, -tf), tf4)
  458. s7 = Series(tf, tf2)
  459. assert s0 == s7
  460. assert not s0 == s2
  461. raises(ValueError, lambda: Series(tf, tf3))
  462. raises(ValueError, lambda: Series(tf, tf2, tf3, tf4))
  463. raises(ValueError, lambda: Series(-tf3, tf2))
  464. raises(TypeError, lambda: Series(2, tf, tf4))
  465. raises(TypeError, lambda: Series(s**2 + p*s, tf3, tf2))
  466. raises(TypeError, lambda: Series(tf3, Matrix([1, 2, 3, 4])))
  467. def test_MIMOSeries_construction():
  468. tf_1 = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  469. tf_2 = TransferFunction(a2*p - s, a2*s + p, s)
  470. tf_3 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  471. tfm_1 = TransferFunctionMatrix([[tf_1, tf_2, tf_3], [-tf_3, -tf_2, tf_1]])
  472. tfm_2 = TransferFunctionMatrix([[-tf_2], [-tf_2], [-tf_3]])
  473. tfm_3 = TransferFunctionMatrix([[-tf_3]])
  474. tfm_4 = TransferFunctionMatrix([[TF3], [TF2], [-TF1]])
  475. tfm_5 = TransferFunctionMatrix.from_Matrix(Matrix([1/p]), p)
  476. s8 = MIMOSeries(tfm_2, tfm_1)
  477. assert s8.args == (tfm_2, tfm_1)
  478. assert s8.var == s
  479. assert s8.shape == (s8.num_outputs, s8.num_inputs) == (2, 1)
  480. s9 = MIMOSeries(tfm_3, tfm_2, tfm_1)
  481. assert s9.args == (tfm_3, tfm_2, tfm_1)
  482. assert s9.var == s
  483. assert s9.shape == (s9.num_outputs, s9.num_inputs) == (2, 1)
  484. s11 = MIMOSeries(tfm_3, MIMOParallel(-tfm_2, -tfm_4), tfm_1)
  485. assert s11.args == (tfm_3, MIMOParallel(-tfm_2, -tfm_4), tfm_1)
  486. assert s11.shape == (s11.num_outputs, s11.num_inputs) == (2, 1)
  487. # arg cannot be empty tuple.
  488. raises(ValueError, lambda: MIMOSeries())
  489. # arg cannot contain SISO as well as MIMO systems.
  490. raises(TypeError, lambda: MIMOSeries(tfm_1, tf_1))
  491. # for all the adjacent transfer function matrices:
  492. # no. of inputs of first TFM must be equal to the no. of outputs of the second TFM.
  493. raises(ValueError, lambda: MIMOSeries(tfm_1, tfm_2, -tfm_1))
  494. # all the TFMs must use the same complex variable.
  495. raises(ValueError, lambda: MIMOSeries(tfm_3, tfm_5))
  496. # Number or expression not allowed in the arguments.
  497. raises(TypeError, lambda: MIMOSeries(2, tfm_2, tfm_3))
  498. raises(TypeError, lambda: MIMOSeries(s**2 + p*s, -tfm_2, tfm_3))
  499. raises(TypeError, lambda: MIMOSeries(Matrix([1/p]), tfm_3))
  500. def test_Series_functions():
  501. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  502. tf2 = TransferFunction(k, 1, s)
  503. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  504. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  505. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  506. assert tf1*tf2*tf3 == Series(tf1, tf2, tf3) == Series(Series(tf1, tf2), tf3) \
  507. == Series(tf1, Series(tf2, tf3))
  508. assert tf1*(tf2 + tf3) == Series(tf1, Parallel(tf2, tf3))
  509. assert tf1*tf2 + tf5 == Parallel(Series(tf1, tf2), tf5)
  510. assert tf1*tf2 - tf5 == Parallel(Series(tf1, tf2), -tf5)
  511. assert tf1*tf2 + tf3 + tf5 == Parallel(Series(tf1, tf2), tf3, tf5)
  512. assert tf1*tf2 - tf3 - tf5 == Parallel(Series(tf1, tf2), -tf3, -tf5)
  513. assert tf1*tf2 - tf3 + tf5 == Parallel(Series(tf1, tf2), -tf3, tf5)
  514. assert tf1*tf2 + tf3*tf5 == Parallel(Series(tf1, tf2), Series(tf3, tf5))
  515. assert tf1*tf2 - tf3*tf5 == Parallel(Series(tf1, tf2), Series(TransferFunction(-1, 1, s), Series(tf3, tf5)))
  516. assert tf2*tf3*(tf2 - tf1)*tf3 == Series(tf2, tf3, Parallel(tf2, -tf1), tf3)
  517. assert -tf1*tf2 == Series(-tf1, tf2)
  518. assert -(tf1*tf2) == Series(TransferFunction(-1, 1, s), Series(tf1, tf2))
  519. raises(ValueError, lambda: tf1*tf2*tf4)
  520. raises(ValueError, lambda: tf1*(tf2 - tf4))
  521. raises(ValueError, lambda: tf3*Matrix([1, 2, 3]))
  522. # evaluate=True -> doit()
  523. assert Series(tf1, tf2, evaluate=True) == Series(tf1, tf2).doit() == \
  524. TransferFunction(k, s**2 + 2*s*wn*zeta + wn**2, s)
  525. assert Series(tf1, tf2, Parallel(tf1, -tf3), evaluate=True) == Series(tf1, tf2, Parallel(tf1, -tf3)).doit() == \
  526. 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)
  527. assert Series(tf2, tf1, -tf3, evaluate=True) == Series(tf2, tf1, -tf3).doit() == \
  528. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  529. assert not Series(tf1, -tf2, evaluate=False) == Series(tf1, -tf2).doit()
  530. assert Series(Parallel(tf1, tf2), Parallel(tf2, -tf3)).doit() == \
  531. 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)
  532. assert Series(-tf1, -tf2, -tf3).doit() == \
  533. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  534. assert -Series(tf1, tf2, tf3).doit() == \
  535. TransferFunction(-k*(a2*p - s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  536. assert Series(tf2, tf3, Parallel(tf2, -tf1), tf3).doit() == \
  537. 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)
  538. assert Series(tf1, tf2).rewrite(TransferFunction) == TransferFunction(k, s**2 + 2*s*wn*zeta + wn**2, s)
  539. assert Series(tf2, tf1, -tf3).rewrite(TransferFunction) == \
  540. TransferFunction(k*(-a2*p + s), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  541. S1 = Series(Parallel(tf1, tf2), Parallel(tf2, -tf3))
  542. assert S1.is_proper
  543. assert not S1.is_strictly_proper
  544. assert S1.is_biproper
  545. S2 = Series(tf1, tf2, tf3)
  546. assert S2.is_proper
  547. assert S2.is_strictly_proper
  548. assert not S2.is_biproper
  549. S3 = Series(tf1, -tf2, Parallel(tf1, -tf3))
  550. assert S3.is_proper
  551. assert S3.is_strictly_proper
  552. assert not S3.is_biproper
  553. def test_MIMOSeries_functions():
  554. tfm1 = TransferFunctionMatrix([[TF1, TF2, TF3], [-TF3, -TF2, TF1]])
  555. tfm2 = TransferFunctionMatrix([[-TF1], [-TF2], [-TF3]])
  556. tfm3 = TransferFunctionMatrix([[-TF1]])
  557. tfm4 = TransferFunctionMatrix([[-TF2, -TF3], [-TF1, TF2]])
  558. tfm5 = TransferFunctionMatrix([[TF2, -TF2], [-TF3, -TF2]])
  559. tfm6 = TransferFunctionMatrix([[-TF3], [TF1]])
  560. tfm7 = TransferFunctionMatrix([[TF1], [-TF2]])
  561. assert tfm1*tfm2 + tfm6 == MIMOParallel(MIMOSeries(tfm2, tfm1), tfm6)
  562. assert tfm1*tfm2 + tfm7 + tfm6 == MIMOParallel(MIMOSeries(tfm2, tfm1), tfm7, tfm6)
  563. assert tfm1*tfm2 - tfm6 - tfm7 == MIMOParallel(MIMOSeries(tfm2, tfm1), -tfm6, -tfm7)
  564. assert tfm4*tfm5 + (tfm4 - tfm5) == MIMOParallel(MIMOSeries(tfm5, tfm4), tfm4, -tfm5)
  565. assert tfm4*-tfm6 + (-tfm4*tfm6) == MIMOParallel(MIMOSeries(-tfm6, tfm4), MIMOSeries(tfm6, -tfm4))
  566. raises(ValueError, lambda: tfm1*tfm2 + TF1)
  567. raises(TypeError, lambda: tfm1*tfm2 + a0)
  568. raises(TypeError, lambda: tfm4*tfm6 - (s - 1))
  569. raises(TypeError, lambda: tfm4*-tfm6 - 8)
  570. raises(TypeError, lambda: (-1 + p**5) + tfm1*tfm2)
  571. # Shape criteria.
  572. raises(TypeError, lambda: -tfm1*tfm2 + tfm4)
  573. raises(TypeError, lambda: tfm1*tfm2 - tfm4 + tfm5)
  574. raises(TypeError, lambda: tfm1*tfm2 - tfm4*tfm5)
  575. assert tfm1*tfm2*-tfm3 == MIMOSeries(-tfm3, tfm2, tfm1)
  576. assert (tfm1*-tfm2)*tfm3 == MIMOSeries(tfm3, -tfm2, tfm1)
  577. # Multiplication of a Series object with a SISO TF not allowed.
  578. raises(ValueError, lambda: tfm4*tfm5*TF1)
  579. raises(TypeError, lambda: tfm4*tfm5*a1)
  580. raises(TypeError, lambda: tfm4*-tfm5*(s - 2))
  581. raises(TypeError, lambda: tfm5*tfm4*9)
  582. raises(TypeError, lambda: (-p**3 + 1)*tfm5*tfm4)
  583. # Transfer function matrix in the arguments.
  584. assert (MIMOSeries(tfm2, tfm1, evaluate=True) == MIMOSeries(tfm2, tfm1).doit()
  585. == 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,
  586. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2, s),),
  587. (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),
  588. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**2, s),))))
  589. # doit() should not cancel poles and zeros.
  590. mat_1 = Matrix([[1/(1+s), (1+s)/(1+s**2+2*s)**3]])
  591. mat_2 = Matrix([[(1+s)], [(1+s**2+2*s)**3/(1+s)]])
  592. tm_1, tm_2 = TransferFunctionMatrix.from_Matrix(mat_1, s), TransferFunctionMatrix.from_Matrix(mat_2, s)
  593. assert (MIMOSeries(tm_2, tm_1).doit()
  594. == TransferFunctionMatrix(((TransferFunction(2*(s + 1)**2*(s**2 + 2*s + 1)**3, (s + 1)**2*(s**2 + 2*s + 1)**3, s),),)))
  595. assert MIMOSeries(tm_2, tm_1).doit().simplify() == TransferFunctionMatrix(((TransferFunction(2, 1, s),),))
  596. # calling doit() will expand the internal Series and Parallel objects.
  597. assert (MIMOSeries(-tfm3, -tfm2, tfm1, evaluate=True)
  598. == MIMOSeries(-tfm3, -tfm2, tfm1).doit()
  599. == 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,
  600. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**3, s),),
  601. (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),
  602. (a2*s + p)**2*(s**2 + 2*s*wn*zeta + wn**2)**3, s),))))
  603. assert (MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5, evaluate=True)
  604. == MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5).doit()
  605. == 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 - \
  606. 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), \
  607. TransferFunction((-a2*p + s)*(-a2*p - k*(a2*s + p) + s), (a2*s + p)**2, s)))) == MIMOSeries(MIMOParallel(tfm4, tfm5), tfm5).rewrite(TransferFunctionMatrix))
  608. def test_Parallel_construction():
  609. tf = TransferFunction(a0*s**3 + a1*s**2 - a2*s, b0*p**4 + b1*p**3 - b2*s*p, s)
  610. tf2 = TransferFunction(a2*p - s, a2*s + p, s)
  611. tf3 = TransferFunction(a0*p + p**a1 - s, p, p)
  612. tf4 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  613. inp = Function('X_d')(s)
  614. out = Function('X')(s)
  615. p0 = Parallel(tf, tf2)
  616. assert p0.args == (tf, tf2)
  617. assert p0.var == s
  618. p1 = Parallel(Series(tf, -tf2), tf2)
  619. assert p1.args == (Series(tf, -tf2), tf2)
  620. assert p1.var == s
  621. tf3_ = TransferFunction(inp, 1, s)
  622. tf4_ = TransferFunction(-out, 1, s)
  623. p2 = Parallel(tf, Series(tf3_, -tf4_), tf2)
  624. assert p2.args == (tf, Series(tf3_, -tf4_), tf2)
  625. p3 = Parallel(tf, tf2, tf4)
  626. assert p3.args == (tf, tf2, tf4)
  627. p4 = Parallel(tf3_, tf4_)
  628. assert p4.args == (tf3_, tf4_)
  629. assert p4.var == s
  630. p5 = Parallel(tf, tf2)
  631. assert p0 == p5
  632. assert not p0 == p1
  633. p6 = Parallel(tf2, tf4, Series(tf2, -tf4))
  634. assert p6.args == (tf2, tf4, Series(tf2, -tf4))
  635. p7 = Parallel(tf2, tf4, Series(tf2, -tf), tf4)
  636. assert p7.args == (tf2, tf4, Series(tf2, -tf), tf4)
  637. raises(ValueError, lambda: Parallel(tf, tf3))
  638. raises(ValueError, lambda: Parallel(tf, tf2, tf3, tf4))
  639. raises(ValueError, lambda: Parallel(-tf3, tf4))
  640. raises(TypeError, lambda: Parallel(2, tf, tf4))
  641. raises(TypeError, lambda: Parallel(s**2 + p*s, tf3, tf2))
  642. raises(TypeError, lambda: Parallel(tf3, Matrix([1, 2, 3, 4])))
  643. def test_MIMOParallel_construction():
  644. tfm1 = TransferFunctionMatrix([[TF1], [TF2], [TF3]])
  645. tfm2 = TransferFunctionMatrix([[-TF3], [TF2], [TF1]])
  646. tfm3 = TransferFunctionMatrix([[TF1]])
  647. tfm4 = TransferFunctionMatrix([[TF2], [TF1], [TF3]])
  648. tfm5 = TransferFunctionMatrix([[TF1, TF2], [TF2, TF1]])
  649. tfm6 = TransferFunctionMatrix([[TF2, TF1], [TF1, TF2]])
  650. tfm7 = TransferFunctionMatrix.from_Matrix(Matrix([[1/p]]), p)
  651. p8 = MIMOParallel(tfm1, tfm2)
  652. assert p8.args == (tfm1, tfm2)
  653. assert p8.var == s
  654. assert p8.shape == (p8.num_outputs, p8.num_inputs) == (3, 1)
  655. p9 = MIMOParallel(MIMOSeries(tfm3, tfm1), tfm2)
  656. assert p9.args == (MIMOSeries(tfm3, tfm1), tfm2)
  657. assert p9.var == s
  658. assert p9.shape == (p9.num_outputs, p9.num_inputs) == (3, 1)
  659. p10 = MIMOParallel(tfm1, MIMOSeries(tfm3, tfm4), tfm2)
  660. assert p10.args == (tfm1, MIMOSeries(tfm3, tfm4), tfm2)
  661. assert p10.var == s
  662. assert p10.shape == (p10.num_outputs, p10.num_inputs) == (3, 1)
  663. p11 = MIMOParallel(tfm2, tfm1, tfm4)
  664. assert p11.args == (tfm2, tfm1, tfm4)
  665. assert p11.shape == (p11.num_outputs, p11.num_inputs) == (3, 1)
  666. p12 = MIMOParallel(tfm6, tfm5)
  667. assert p12.args == (tfm6, tfm5)
  668. assert p12.shape == (p12.num_outputs, p12.num_inputs) == (2, 2)
  669. p13 = MIMOParallel(tfm2, tfm4, MIMOSeries(-tfm3, tfm4), -tfm4)
  670. assert p13.args == (tfm2, tfm4, MIMOSeries(-tfm3, tfm4), -tfm4)
  671. assert p13.shape == (p13.num_outputs, p13.num_inputs) == (3, 1)
  672. # arg cannot be empty tuple.
  673. raises(TypeError, lambda: MIMOParallel(()))
  674. # arg cannot contain SISO as well as MIMO systems.
  675. raises(TypeError, lambda: MIMOParallel(tfm1, tfm2, TF1))
  676. # all TFMs must have same shapes.
  677. raises(TypeError, lambda: MIMOParallel(tfm1, tfm3, tfm4))
  678. # all TFMs must be using the same complex variable.
  679. raises(ValueError, lambda: MIMOParallel(tfm3, tfm7))
  680. # Number or expression not allowed in the arguments.
  681. raises(TypeError, lambda: MIMOParallel(2, tfm1, tfm4))
  682. raises(TypeError, lambda: MIMOParallel(s**2 + p*s, -tfm4, tfm2))
  683. def test_Parallel_functions():
  684. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  685. tf2 = TransferFunction(k, 1, s)
  686. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  687. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  688. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  689. assert tf1 + tf2 + tf3 == Parallel(tf1, tf2, tf3)
  690. assert tf1 + tf2 + tf3 + tf5 == Parallel(tf1, tf2, tf3, tf5)
  691. assert tf1 + tf2 - tf3 - tf5 == Parallel(tf1, tf2, -tf3, -tf5)
  692. assert tf1 + tf2*tf3 == Parallel(tf1, Series(tf2, tf3))
  693. assert tf1 - tf2*tf3 == Parallel(tf1, -Series(tf2,tf3))
  694. assert -tf1 - tf2 == Parallel(-tf1, -tf2)
  695. assert -(tf1 + tf2) == Series(TransferFunction(-1, 1, s), Parallel(tf1, tf2))
  696. assert (tf2 + tf3)*tf1 == Series(Parallel(tf2, tf3), tf1)
  697. assert (tf1 + tf2)*(tf3*tf5) == Series(Parallel(tf1, tf2), tf3, tf5)
  698. assert -(tf2 + tf3)*-tf5 == Series(TransferFunction(-1, 1, s), Parallel(tf2, tf3), -tf5)
  699. assert tf2 + tf3 + tf2*tf1 + tf5 == Parallel(tf2, tf3, Series(tf2, tf1), tf5)
  700. assert tf2 + tf3 + tf2*tf1 - tf3 == Parallel(tf2, tf3, Series(tf2, tf1), -tf3)
  701. assert (tf1 + tf2 + tf5)*(tf3 + tf5) == Series(Parallel(tf1, tf2, tf5), Parallel(tf3, tf5))
  702. raises(ValueError, lambda: tf1 + tf2 + tf4)
  703. raises(ValueError, lambda: tf1 - tf2*tf4)
  704. raises(ValueError, lambda: tf3 + Matrix([1, 2, 3]))
  705. # evaluate=True -> doit()
  706. assert Parallel(tf1, tf2, evaluate=True) == Parallel(tf1, tf2).doit() == \
  707. TransferFunction(k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s)
  708. assert Parallel(tf1, tf2, Series(-tf1, tf3), evaluate=True) == \
  709. Parallel(tf1, tf2, Series(-tf1, tf3)).doit() == TransferFunction(k*(a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)**2 + \
  710. (-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 + \
  711. 2*s*wn*zeta + wn**2)**2, s)
  712. assert Parallel(tf2, tf1, -tf3, evaluate=True) == Parallel(tf2, tf1, -tf3).doit() == \
  713. 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) \
  714. , (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  715. assert not Parallel(tf1, -tf2, evaluate=False) == Parallel(tf1, -tf2).doit()
  716. assert Parallel(Series(tf1, tf2), Series(tf2, tf3)).doit() == \
  717. 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)
  718. assert Parallel(-tf1, -tf2, -tf3).doit() == \
  719. 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), \
  720. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  721. assert -Parallel(tf1, tf2, tf3).doit() == \
  722. 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), \
  723. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  724. assert Parallel(tf2, tf3, Series(tf2, -tf1), tf3).doit() == \
  725. 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 \
  726. + wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  727. assert Parallel(tf1, tf2).rewrite(TransferFunction) == \
  728. TransferFunction(k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s)
  729. assert Parallel(tf2, tf1, -tf3).rewrite(TransferFunction) == \
  730. 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 + \
  731. wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  732. assert Parallel(tf1, Parallel(tf2, tf3)) == Parallel(tf1, tf2, tf3) == Parallel(Parallel(tf1, tf2), tf3)
  733. P1 = Parallel(Series(tf1, tf2), Series(tf2, tf3))
  734. assert P1.is_proper
  735. assert not P1.is_strictly_proper
  736. assert P1.is_biproper
  737. P2 = Parallel(tf1, -tf2, -tf3)
  738. assert P2.is_proper
  739. assert not P2.is_strictly_proper
  740. assert P2.is_biproper
  741. P3 = Parallel(tf1, -tf2, Series(tf1, tf3))
  742. assert P3.is_proper
  743. assert not P3.is_strictly_proper
  744. assert P3.is_biproper
  745. def test_MIMOParallel_functions():
  746. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  747. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  748. tfm1 = TransferFunctionMatrix([[TF1], [TF2], [TF3]])
  749. tfm2 = TransferFunctionMatrix([[-TF2], [tf5], [-TF1]])
  750. tfm3 = TransferFunctionMatrix([[tf5], [-tf5], [TF2]])
  751. tfm4 = TransferFunctionMatrix([[TF2, -tf5], [TF1, tf5]])
  752. tfm5 = TransferFunctionMatrix([[TF1, TF2], [TF3, -tf5]])
  753. tfm6 = TransferFunctionMatrix([[-TF2]])
  754. tfm7 = TransferFunctionMatrix([[tf4], [-tf4], [tf4]])
  755. assert tfm1 + tfm2 + tfm3 == MIMOParallel(tfm1, tfm2, tfm3) == MIMOParallel(MIMOParallel(tfm1, tfm2), tfm3)
  756. assert tfm2 - tfm1 - tfm3 == MIMOParallel(tfm2, -tfm1, -tfm3)
  757. assert tfm2 - tfm3 + (-tfm1*tfm6*-tfm6) == MIMOParallel(tfm2, -tfm3, MIMOSeries(-tfm6, tfm6, -tfm1))
  758. assert tfm1 + tfm1 - (-tfm1*tfm6) == MIMOParallel(tfm1, tfm1, -MIMOSeries(tfm6, -tfm1))
  759. assert tfm2 - tfm3 - tfm1 + tfm2 == MIMOParallel(tfm2, -tfm3, -tfm1, tfm2)
  760. assert tfm1 + tfm2 - tfm3 - tfm1 == MIMOParallel(tfm1, tfm2, -tfm3, -tfm1)
  761. raises(ValueError, lambda: tfm1 + tfm2 + TF2)
  762. raises(TypeError, lambda: tfm1 - tfm2 - a1)
  763. raises(TypeError, lambda: tfm2 - tfm3 - (s - 1))
  764. raises(TypeError, lambda: -tfm3 - tfm2 - 9)
  765. raises(TypeError, lambda: (1 - p**3) - tfm3 - tfm2)
  766. # All TFMs must use the same complex var. tfm7 uses 'p'.
  767. raises(ValueError, lambda: tfm3 - tfm2 - tfm7)
  768. raises(ValueError, lambda: tfm2 - tfm1 + tfm7)
  769. # (tfm1 +/- tfm2) has (3, 1) shape while tfm4 has (2, 2) shape.
  770. raises(TypeError, lambda: tfm1 + tfm2 + tfm4)
  771. raises(TypeError, lambda: (tfm1 - tfm2) - tfm4)
  772. assert (tfm1 + tfm2)*tfm6 == MIMOSeries(tfm6, MIMOParallel(tfm1, tfm2))
  773. assert (tfm2 - tfm3)*tfm6*-tfm6 == MIMOSeries(-tfm6, tfm6, MIMOParallel(tfm2, -tfm3))
  774. assert (tfm2 - tfm1 - tfm3)*(tfm6 + tfm6) == MIMOSeries(MIMOParallel(tfm6, tfm6), MIMOParallel(tfm2, -tfm1, -tfm3))
  775. raises(ValueError, lambda: (tfm4 + tfm5)*TF1)
  776. raises(TypeError, lambda: (tfm2 - tfm3)*a2)
  777. raises(TypeError, lambda: (tfm3 + tfm2)*(s - 6))
  778. raises(TypeError, lambda: (tfm1 + tfm2 + tfm3)*0)
  779. raises(TypeError, lambda: (1 - p**3)*(tfm1 + tfm3))
  780. # (tfm3 - tfm2) has (3, 1) shape while tfm4*tfm5 has (2, 2) shape.
  781. raises(ValueError, lambda: (tfm3 - tfm2)*tfm4*tfm5)
  782. # (tfm1 - tfm2) has (3, 1) shape while tfm5 has (2, 2) shape.
  783. raises(ValueError, lambda: (tfm1 - tfm2)*tfm5)
  784. # TFM in the arguments.
  785. assert (MIMOParallel(tfm1, tfm2, evaluate=True) == MIMOParallel(tfm1, tfm2).doit()
  786. == MIMOParallel(tfm1, tfm2).rewrite(TransferFunctionMatrix)
  787. == TransferFunctionMatrix(((TransferFunction(-k*(s**2 + 2*s*wn*zeta + wn**2) + 1, s**2 + 2*s*wn*zeta + wn**2, s),), \
  788. (TransferFunction(-a0 + a1*s**2 + a2*s + k*(a0 + s), a0 + s, s),), (TransferFunction(-a2*s - p + (a2*p - s)* \
  789. (s**2 + 2*s*wn*zeta + wn**2), (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s),))))
  790. def test_Feedback_construction():
  791. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  792. tf2 = TransferFunction(k, 1, s)
  793. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  794. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  795. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  796. tf6 = TransferFunction(s - p, p + s, p)
  797. f1 = Feedback(TransferFunction(1, 1, s), tf1*tf2*tf3)
  798. assert f1.args == (TransferFunction(1, 1, s), Series(tf1, tf2, tf3), -1)
  799. assert f1.sys1 == TransferFunction(1, 1, s)
  800. assert f1.sys2 == Series(tf1, tf2, tf3)
  801. assert f1.var == s
  802. f2 = Feedback(tf1, tf2*tf3)
  803. assert f2.args == (tf1, Series(tf2, tf3), -1)
  804. assert f2.sys1 == tf1
  805. assert f2.sys2 == Series(tf2, tf3)
  806. assert f2.var == s
  807. f3 = Feedback(tf1*tf2, tf5)
  808. assert f3.args == (Series(tf1, tf2), tf5, -1)
  809. assert f3.sys1 == Series(tf1, tf2)
  810. f4 = Feedback(tf4, tf6)
  811. assert f4.args == (tf4, tf6, -1)
  812. assert f4.sys1 == tf4
  813. assert f4.var == p
  814. f5 = Feedback(tf5, TransferFunction(1, 1, s))
  815. assert f5.args == (tf5, TransferFunction(1, 1, s), -1)
  816. assert f5.var == s
  817. assert f5 == Feedback(tf5) # When sys2 is not passed explicitly, it is assumed to be unit tf.
  818. f6 = Feedback(TransferFunction(1, 1, p), tf4)
  819. assert f6.args == (TransferFunction(1, 1, p), tf4, -1)
  820. assert f6.var == p
  821. f7 = -Feedback(tf4*tf6, TransferFunction(1, 1, p))
  822. assert f7.args == (Series(TransferFunction(-1, 1, p), Series(tf4, tf6)), -TransferFunction(1, 1, p), -1)
  823. assert f7.sys1 == Series(TransferFunction(-1, 1, p), Series(tf4, tf6))
  824. # denominator can't be a Parallel instance
  825. raises(TypeError, lambda: Feedback(tf1, tf2 + tf3))
  826. raises(TypeError, lambda: Feedback(tf1, Matrix([1, 2, 3])))
  827. raises(TypeError, lambda: Feedback(TransferFunction(1, 1, s), s - 1))
  828. raises(TypeError, lambda: Feedback(1, 1))
  829. # raises(ValueError, lambda: Feedback(TransferFunction(1, 1, s), TransferFunction(1, 1, s)))
  830. raises(ValueError, lambda: Feedback(tf2, tf4*tf5))
  831. raises(ValueError, lambda: Feedback(tf2, tf1, 1.5)) # `sign` can only be -1 or 1
  832. raises(ValueError, lambda: Feedback(tf1, -tf1**-1)) # denominator can't be zero
  833. raises(ValueError, lambda: Feedback(tf4, tf5)) # Both systems should use the same `var`
  834. def test_Feedback_functions():
  835. tf = TransferFunction(1, 1, s)
  836. tf1 = TransferFunction(1, s**2 + 2*zeta*wn*s + wn**2, s)
  837. tf2 = TransferFunction(k, 1, s)
  838. tf3 = TransferFunction(a2*p - s, a2*s + p, s)
  839. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  840. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  841. tf6 = TransferFunction(s - p, p + s, p)
  842. assert (tf1*tf2*tf3 / tf3*tf5) == Series(tf1, tf2, tf3, pow(tf3, -1), tf5)
  843. assert (tf1*tf2*tf3) / (tf3*tf5) == Series((tf1*tf2*tf3).doit(), pow((tf3*tf5).doit(),-1))
  844. assert tf / (tf + tf1) == Feedback(tf, tf1)
  845. assert tf / (tf + tf1*tf2*tf3) == Feedback(tf, tf1*tf2*tf3)
  846. assert tf1 / (tf + tf1*tf2*tf3) == Feedback(tf1, tf2*tf3)
  847. assert (tf1*tf2) / (tf + tf1*tf2) == Feedback(tf1*tf2, tf)
  848. assert (tf1*tf2) / (tf + tf1*tf2*tf5) == Feedback(tf1*tf2, tf5)
  849. assert (tf1*tf2) / (tf + tf1*tf2*tf5*tf3) in (Feedback(tf1*tf2, tf5*tf3), Feedback(tf1*tf2, tf3*tf5))
  850. assert tf4 / (TransferFunction(1, 1, p) + tf4*tf6) == Feedback(tf4, tf6)
  851. assert tf5 / (tf + tf5) == Feedback(tf5, tf)
  852. raises(TypeError, lambda: tf1*tf2*tf3 / (1 + tf1*tf2*tf3))
  853. raises(ValueError, lambda: tf2*tf3 / (tf + tf2*tf3*tf4))
  854. assert Feedback(tf, tf1*tf2*tf3).doit() == \
  855. TransferFunction((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), k*(a2*p - s) + \
  856. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), s)
  857. assert Feedback(tf, tf1*tf2*tf3).sensitivity == \
  858. 1/(k*(a2*p - s)/((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  859. assert Feedback(tf1, tf2*tf3).doit() == \
  860. TransferFunction((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2), (k*(a2*p - s) + \
  861. (a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  862. assert Feedback(tf1, tf2*tf3).sensitivity == \
  863. 1/(k*(a2*p - s)/((a2*s + p)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  864. assert Feedback(tf1*tf2, tf5).doit() == \
  865. TransferFunction(k*(a0 + s)*(s**2 + 2*s*wn*zeta + wn**2), (k*(-a0 + a1*s**2 + a2*s) + \
  866. (a0 + s)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  867. assert Feedback(tf1*tf2, tf5, 1).sensitivity == \
  868. 1/(-k*(-a0 + a1*s**2 + a2*s)/((a0 + s)*(s**2 + 2*s*wn*zeta + wn**2)) + 1)
  869. assert Feedback(tf4, tf6).doit() == \
  870. TransferFunction(p*(p + s)*(a0*p + p**a1 - s), p*(p*(p + s) + (-p + s)*(a0*p + p**a1 - s)), p)
  871. assert -Feedback(tf4*tf6, TransferFunction(1, 1, p)).doit() == \
  872. 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)
  873. assert Feedback(tf, tf).doit() == TransferFunction(1, 2, s)
  874. assert Feedback(tf1, tf2*tf5).rewrite(TransferFunction) == \
  875. TransferFunction((a0 + s)*(s**2 + 2*s*wn*zeta + wn**2), (k*(-a0 + a1*s**2 + a2*s) + \
  876. (a0 + s)*(s**2 + 2*s*wn*zeta + wn**2))*(s**2 + 2*s*wn*zeta + wn**2), s)
  877. assert Feedback(TransferFunction(1, 1, p), tf4).rewrite(TransferFunction) == \
  878. TransferFunction(p, a0*p + p + p**a1 - s, p)
  879. def test_Feedback_with_Series():
  880. # Solves issue https://github.com/sympy/sympy/issues/26161
  881. tf1 = TransferFunction(s+1, 1, s)
  882. tf2 = TransferFunction(s+2, 1, s)
  883. fd1 = Feedback(tf1, tf2, -1) # Negative Feedback system
  884. fd2 = Feedback(tf1, tf2, 1) # Positive Feedback system
  885. unit = TransferFunction(1, 1, s)
  886. # Checking the type
  887. assert isinstance(fd1, SISOLinearTimeInvariant)
  888. assert isinstance(fd1, Feedback)
  889. # Testing the numerator and denominator
  890. assert fd1.num == tf1
  891. assert fd2.num == tf1
  892. assert fd1.den == Parallel(unit, Series(tf2, tf1))
  893. assert fd2.den == Parallel(unit, -Series(tf2, tf1))
  894. # Testing the Series and Parallel Combination with Feedback and TransferFunction
  895. s1 = Series(tf1, fd1)
  896. p1 = Parallel(tf1, fd1)
  897. assert tf1 * fd1 == s1
  898. assert tf1 + fd1 == p1
  899. assert s1.doit() == TransferFunction((s + 1)**2, (s + 1)*(s + 2) + 1, s)
  900. assert p1.doit() == TransferFunction(s + (s + 1)*((s + 1)*(s + 2) + 1) + 1, (s + 1)*(s + 2) + 1, s)
  901. # Testing the use of Feedback and TransferFunction with Feedback
  902. fd3 = Feedback(tf1*fd1, tf2, -1)
  903. assert fd3 == Feedback(Series(tf1, fd1), tf2)
  904. assert fd3.num == tf1 * fd1
  905. assert fd3.den == Parallel(unit, Series(tf2, Series(tf1, fd1)))
  906. # Testing the use of Feedback and TransferFunction with TransferFunction
  907. tf3 = TransferFunction(tf1*fd1, tf2, s)
  908. assert tf3 == TransferFunction(Series(tf1, fd1), tf2, s)
  909. assert tf3.num == tf1*fd1
  910. def test_issue_26161():
  911. # Issue https://github.com/sympy/sympy/issues/26161
  912. Ib, Is, m, h, l2, l1 = symbols('I_b, I_s, m, h, l2, l1',
  913. real=True, nonnegative=True)
  914. KD, KP, v = symbols('K_D, K_P, v', real=True)
  915. tau1_sq = (Ib + m * h ** 2) / m / g / h
  916. tau2 = l2 / v
  917. tau3 = v / (l1 + l2)
  918. K = v ** 2 / g / (l1 + l2)
  919. Gtheta = TransferFunction(-K * (tau2 * s + 1), tau1_sq * s ** 2 - 1, s)
  920. Gdelta = TransferFunction(1, Is * s ** 2 + c * s, s)
  921. Gpsi = TransferFunction(1, tau3 * s, s)
  922. Dcont = TransferFunction(KD * s, 1, s)
  923. PIcont = TransferFunction(KP, s, s)
  924. Gunity = TransferFunction(1, 1, s)
  925. Ginner = Feedback(Dcont * Gdelta, Gtheta)
  926. Gouter = Feedback(PIcont * Ginner * Gpsi, Gunity)
  927. assert Gouter == Feedback(Series(PIcont, Series(Ginner, Gpsi)), Gunity)
  928. assert Gouter.num == Series(PIcont, Series(Ginner, Gpsi))
  929. assert Gouter.den == Parallel(Gunity, Series(Gunity, Series(PIcont, Series(Ginner, Gpsi))))
  930. expr = (KD*KP*g*s**3*v**2*(l1 + l2)*(Is*s**2 + c*s)**2*(-g*h*m + s**2*(Ib + h**2*m))*(-KD*g*h*m*s*v**2*(l2*s + v) + \
  931. g*v*(l1 + l2)*(Is*s**2 + c*s)*(-g*h*m + s**2*(Ib + h**2*m))))/((s**2*v*(Is*s**2 + c*s)*(-KD*g*h*m*s*v**2* \
  932. (l2*s + v) + g*v*(l1 + l2)*(Is*s**2 + c*s)*(-g*h*m + s**2*(Ib + h**2*m)))*(KD*KP*g*s*v*(l1 + l2)**2* \
  933. (Is*s**2 + c*s)*(-g*h*m + s**2*(Ib + h**2*m)) + s**2*v*(Is*s**2 + c*s)*(-KD*g*h*m*s*v**2*(l2*s + v) + \
  934. g*v*(l1 + l2)*(Is*s**2 + c*s)*(-g*h*m + s**2*(Ib + h**2*m))))/(l1 + l2)))
  935. assert (Gouter.to_expr() - expr).simplify() == 0
  936. def test_MIMOFeedback_construction():
  937. tf1 = TransferFunction(1, s, s)
  938. tf2 = TransferFunction(s, s**3 - 1, s)
  939. tf3 = TransferFunction(s, s + 1, s)
  940. tf4 = TransferFunction(s, s**2 + 1, s)
  941. tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  942. tfm_2 = TransferFunctionMatrix([[tf2, tf3], [tf4, tf1]])
  943. tfm_3 = TransferFunctionMatrix([[tf3, tf4], [tf1, tf2]])
  944. f1 = MIMOFeedback(tfm_1, tfm_2)
  945. assert f1.args == (tfm_1, tfm_2, -1)
  946. assert f1.sys1 == tfm_1
  947. assert f1.sys2 == tfm_2
  948. assert f1.var == s
  949. assert f1.sign == -1
  950. assert -(-f1) == f1
  951. f2 = MIMOFeedback(tfm_2, tfm_1, 1)
  952. assert f2.args == (tfm_2, tfm_1, 1)
  953. assert f2.sys1 == tfm_2
  954. assert f2.sys2 == tfm_1
  955. assert f2.var == s
  956. assert f2.sign == 1
  957. f3 = MIMOFeedback(tfm_1, MIMOSeries(tfm_3, tfm_2))
  958. assert f3.args == (tfm_1, MIMOSeries(tfm_3, tfm_2), -1)
  959. assert f3.sys1 == tfm_1
  960. assert f3.sys2 == MIMOSeries(tfm_3, tfm_2)
  961. assert f3.var == s
  962. assert f3.sign == -1
  963. mat = Matrix([[1, 1/s], [0, 1]])
  964. sys1 = controller = TransferFunctionMatrix.from_Matrix(mat, s)
  965. f4 = MIMOFeedback(sys1, controller)
  966. assert f4.args == (sys1, controller, -1)
  967. assert f4.sys1 == f4.sys2 == sys1
  968. def test_MIMOFeedback_errors():
  969. tf1 = TransferFunction(1, s, s)
  970. tf2 = TransferFunction(s, s**3 - 1, s)
  971. tf3 = TransferFunction(s, s - 1, s)
  972. tf4 = TransferFunction(s, s**2 + 1, s)
  973. tf5 = TransferFunction(1, 1, s)
  974. tf6 = TransferFunction(-1, s - 1, s)
  975. tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  976. tfm_2 = TransferFunctionMatrix([[tf2, tf3], [tf4, tf1]])
  977. tfm_3 = TransferFunctionMatrix.from_Matrix(eye(2), var=s)
  978. tfm_4 = TransferFunctionMatrix([[tf1, tf5], [tf5, tf5]])
  979. tfm_5 = TransferFunctionMatrix([[-tf3, tf3], [tf3, tf6]])
  980. # tfm_4 is inverse of tfm_5. Therefore tfm_5*tfm_4 = I
  981. tfm_6 = TransferFunctionMatrix([[-tf3]])
  982. tfm_7 = TransferFunctionMatrix([[tf3, tf4]])
  983. # Unsupported Types
  984. raises(TypeError, lambda: MIMOFeedback(tf1, tf2))
  985. raises(TypeError, lambda: MIMOFeedback(MIMOParallel(tfm_1, tfm_2), tfm_3))
  986. # Shape Errors
  987. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_6, 1))
  988. raises(ValueError, lambda: MIMOFeedback(tfm_7, tfm_7))
  989. # sign not 1/-1
  990. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_2, -2))
  991. # Non-Invertible Systems
  992. raises(ValueError, lambda: MIMOFeedback(tfm_5, tfm_4, 1))
  993. raises(ValueError, lambda: MIMOFeedback(tfm_4, -tfm_5))
  994. raises(ValueError, lambda: MIMOFeedback(tfm_3, tfm_3, 1))
  995. # Variable not same in both the systems
  996. tfm_8 = TransferFunctionMatrix.from_Matrix(eye(2), var=p)
  997. raises(ValueError, lambda: MIMOFeedback(tfm_1, tfm_8, 1))
  998. def test_MIMOFeedback_functions():
  999. tf1 = TransferFunction(1, s, s)
  1000. tf2 = TransferFunction(s, s - 1, s)
  1001. tf3 = TransferFunction(1, 1, s)
  1002. tf4 = TransferFunction(-1, s - 1, s)
  1003. tfm_1 = TransferFunctionMatrix.from_Matrix(eye(2), var=s)
  1004. tfm_2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf3]])
  1005. tfm_3 = TransferFunctionMatrix([[-tf2, tf2], [tf2, tf4]])
  1006. tfm_4 = TransferFunctionMatrix([[tf1, tf2], [-tf2, tf1]])
  1007. # sensitivity, doit(), rewrite()
  1008. F_1 = MIMOFeedback(tfm_2, tfm_3)
  1009. F_2 = MIMOFeedback(tfm_2, MIMOSeries(tfm_4, -tfm_1), 1)
  1010. assert F_1.sensitivity == Matrix([[S.Half, 0], [0, S.Half]])
  1011. assert F_2.sensitivity == Matrix([[(-2*s**4 + s**2)/(s**2 - s + 1),
  1012. (2*s**3 - s**2)/(s**2 - s + 1)], [-s**2, s]])
  1013. assert F_1.doit() == \
  1014. TransferFunctionMatrix(((TransferFunction(1, 2*s, s),
  1015. TransferFunction(1, 2, s)), (TransferFunction(1, 2, s),
  1016. TransferFunction(1, 2, s)))) == F_1.rewrite(TransferFunctionMatrix)
  1017. assert F_2.doit(cancel=False, expand=True) == \
  1018. 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),
  1019. TransferFunction(-2*s**4 + 2*s**3, s**2 - s + 1, s)), (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  1020. assert F_2.doit(cancel=False) == \
  1021. TransferFunctionMatrix(((TransferFunction(s*(2*s**3 - s**2)*(s**2 - s + 1) + \
  1022. (-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)),
  1023. (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  1024. assert F_2.doit() == \
  1025. TransferFunctionMatrix(((TransferFunction(s*(-2*s**2 + s*(2*s - 1) + 1), s**2 - s + 1, s),
  1026. TransferFunction(-2*s**3*(s - 1), s**2 - s + 1, s)), (TransferFunction(0, 1, s), TransferFunction(s*(1 - s), 1, s))))
  1027. assert F_2.doit(expand=True) == \
  1028. TransferFunctionMatrix(((TransferFunction(-s**2 + s, s**2 - s + 1, s), TransferFunction(-2*s**4 + 2*s**3, s**2 - s + 1, s)),
  1029. (TransferFunction(0, 1, s), TransferFunction(-s**2 + s, 1, s))))
  1030. assert -(F_1.doit()) == (-F_1).doit() # First negating then calculating vs calculating then negating.
  1031. def test_TransferFunctionMatrix_construction():
  1032. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  1033. tf4 = TransferFunction(a0*p + p**a1 - s, p, p)
  1034. tfm3_ = TransferFunctionMatrix([[-TF3]])
  1035. assert tfm3_.shape == (tfm3_.num_outputs, tfm3_.num_inputs) == (1, 1)
  1036. assert tfm3_.args == Tuple(Tuple(Tuple(-TF3)))
  1037. assert tfm3_.var == s
  1038. tfm5 = TransferFunctionMatrix([[TF1, -TF2], [TF3, tf5]])
  1039. assert tfm5.shape == (tfm5.num_outputs, tfm5.num_inputs) == (2, 2)
  1040. assert tfm5.args == Tuple(Tuple(Tuple(TF1, -TF2), Tuple(TF3, tf5)))
  1041. assert tfm5.var == s
  1042. tfm7 = TransferFunctionMatrix([[TF1, TF2], [TF3, -tf5], [-tf5, TF2]])
  1043. assert tfm7.shape == (tfm7.num_outputs, tfm7.num_inputs) == (3, 2)
  1044. assert tfm7.args == Tuple(Tuple(Tuple(TF1, TF2), Tuple(TF3, -tf5), Tuple(-tf5, TF2)))
  1045. assert tfm7.var == s
  1046. # all transfer functions will use the same complex variable. tf4 uses 'p'.
  1047. raises(ValueError, lambda: TransferFunctionMatrix([[TF1], [TF2], [tf4]]))
  1048. raises(ValueError, lambda: TransferFunctionMatrix([[TF1, tf4], [TF3, tf5]]))
  1049. # length of all the lists in the TFM should be equal.
  1050. raises(ValueError, lambda: TransferFunctionMatrix([[TF1], [TF3, tf5]]))
  1051. raises(ValueError, lambda: TransferFunctionMatrix([[TF1, TF3], [tf5]]))
  1052. # lists should only support transfer functions in them.
  1053. raises(TypeError, lambda: TransferFunctionMatrix([[TF1, TF2], [TF3, Matrix([1, 2])]]))
  1054. raises(TypeError, lambda: TransferFunctionMatrix([[TF1, Matrix([1, 2])], [TF3, TF2]]))
  1055. # `arg` should strictly be nested list of TransferFunction
  1056. raises(ValueError, lambda: TransferFunctionMatrix([TF1, TF2, tf5]))
  1057. raises(ValueError, lambda: TransferFunctionMatrix([TF1]))
  1058. def test_TransferFunctionMatrix_functions():
  1059. tf5 = TransferFunction(a1*s**2 + a2*s - a0, s + a0, s)
  1060. # Classmethod (from_matrix)
  1061. mat_1 = ImmutableMatrix([
  1062. [s*(s + 1)*(s - 3)/(s**4 + 1), 2],
  1063. [p, p*(s + 1)/(s*(s**1 + 1))]
  1064. ])
  1065. mat_2 = ImmutableMatrix([[(2*s + 1)/(s**2 - 9)]])
  1066. mat_3 = ImmutableMatrix([[1, 2], [3, 4]])
  1067. assert TransferFunctionMatrix.from_Matrix(mat_1, s) == \
  1068. TransferFunctionMatrix([[TransferFunction(s*(s - 3)*(s + 1), s**4 + 1, s), TransferFunction(2, 1, s)],
  1069. [TransferFunction(p, 1, s), TransferFunction(p, s, s)]])
  1070. assert TransferFunctionMatrix.from_Matrix(mat_2, s) == \
  1071. TransferFunctionMatrix([[TransferFunction(2*s + 1, s**2 - 9, s)]])
  1072. assert TransferFunctionMatrix.from_Matrix(mat_3, p) == \
  1073. TransferFunctionMatrix([[TransferFunction(1, 1, p), TransferFunction(2, 1, p)],
  1074. [TransferFunction(3, 1, p), TransferFunction(4, 1, p)]])
  1075. # Negating a TFM
  1076. tfm1 = TransferFunctionMatrix([[TF1], [TF2]])
  1077. assert -tfm1 == TransferFunctionMatrix([[-TF1], [-TF2]])
  1078. tfm2 = TransferFunctionMatrix([[TF1, TF2, TF3], [tf5, -TF1, -TF3]])
  1079. assert -tfm2 == TransferFunctionMatrix([[-TF1, -TF2, -TF3], [-tf5, TF1, TF3]])
  1080. # subs()
  1081. H_1 = TransferFunctionMatrix.from_Matrix(mat_1, s)
  1082. H_2 = TransferFunctionMatrix([[TransferFunction(a*p*s, k*s**2, s), TransferFunction(p*s, k*(s**2 - a), s)]])
  1083. 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)]])
  1084. 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)]])
  1085. 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`
  1086. assert H_2.subs(p, 2) == TransferFunctionMatrix([[TransferFunction(2*a*s, k*s**2, s), TransferFunction(2*s, k*(-a + s**2), s)]])
  1087. assert H_2.subs(k, 1) == TransferFunctionMatrix([[TransferFunction(a*p*s, s**2, s), TransferFunction(p*s, -a + s**2, s)]])
  1088. assert H_2.subs(a, 0) == TransferFunctionMatrix([[TransferFunction(0, k*s**2, s), TransferFunction(p*s, k*s**2, s)]])
  1089. assert H_2.subs({p: 1, k: 1, a: a0}) == TransferFunctionMatrix([[TransferFunction(a0*s, s**2, s), TransferFunction(s, -a0 + s**2, s)]])
  1090. # eval_frequency()
  1091. assert H_2.eval_frequency(S(1)/2 + I) == Matrix([[2*a*p/(5*k) - 4*I*a*p/(5*k), I*p/(-a*k - 3*k/4 + I*k) + p/(-2*a*k - 3*k/2 + 2*I*k)]])
  1092. # transpose()
  1093. 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)]])
  1094. assert H_2.transpose() == TransferFunctionMatrix([[TransferFunction(a*p*s, k*s**2, s)], [TransferFunction(p*s, k*(-a + s**2), s)]])
  1095. assert H_1.transpose().transpose() == H_1
  1096. assert H_2.transpose().transpose() == H_2
  1097. # elem_poles()
  1098. 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], []],
  1099. [[], [0]]]
  1100. assert H_2.elem_poles() == [[[0, 0], [sqrt(a), -sqrt(a)]]]
  1101. assert tfm2.elem_poles() == [[[wn*(-zeta + sqrt((zeta - 1)*(zeta + 1))), wn*(-zeta - sqrt((zeta - 1)*(zeta + 1)))], [], [-p/a2]],
  1102. [[-a0], [wn*(-zeta + sqrt((zeta - 1)*(zeta + 1))), wn*(-zeta - sqrt((zeta - 1)*(zeta + 1)))], [-p/a2]]]
  1103. # elem_zeros()
  1104. assert H_1.elem_zeros() == [[[-1, 0, 3], []], [[], []]]
  1105. assert H_2.elem_zeros() == [[[0], [0]]]
  1106. assert tfm2.elem_zeros() == [[[], [], [a2*p]],
  1107. [[-a2/(2*a1) - sqrt(4*a0*a1 + a2**2)/(2*a1), -a2/(2*a1) + sqrt(4*a0*a1 + a2**2)/(2*a1)], [], [a2*p]]]
  1108. # doit()
  1109. H_3 = TransferFunctionMatrix([[Series(TransferFunction(1, s**3 - 3, s), TransferFunction(s**2 - 2*s + 5, 1, s), TransferFunction(1, s, s))]])
  1110. 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))]])
  1111. assert H_3.doit() == TransferFunctionMatrix([[TransferFunction(s**2 - 2*s + 5, s*(s**3 - 3), s)]])
  1112. assert H_4.doit() == TransferFunctionMatrix([[TransferFunction(1, 4*s**4 - s**2 - 2*s + 5, s)]])
  1113. # _flat()
  1114. 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)]
  1115. assert H_2._flat() == [TransferFunction(a*p*s, k*s**2, s), TransferFunction(p*s, k*(-a + s**2), s)]
  1116. assert H_3._flat() == [Series(TransferFunction(1, s**3 - 3, s), TransferFunction(s**2 - 2*s + 5, 1, s), TransferFunction(1, s, s))]
  1117. 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))]
  1118. # evalf()
  1119. assert H_1.evalf() == \
  1120. 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))))
  1121. assert H_2.subs({a:3.141, p:2.88, k:2}).evalf() == \
  1122. TransferFunctionMatrix(((TransferFunction(4.5230399999999999494093572138808667659759521484375, s, s),
  1123. TransferFunction(2.87999999999999989341858963598497211933135986328125*s, 2.0*s**2 - 6.282000000000000028421709430404007434844970703125, s)),))
  1124. # simplify()
  1125. H_5 = TransferFunctionMatrix([[TransferFunction(s**5 + s**3 + s, s - s**2, s),
  1126. TransferFunction((s + 3)*(s - 1), (s - 1)*(s + 5), s)]])
  1127. assert H_5.simplify() == simplify(H_5) == \
  1128. TransferFunctionMatrix(((TransferFunction(-s**4 - s**2 - 1, s - 1, s), TransferFunction(s + 3, s + 5, s)),))
  1129. # expand()
  1130. assert (H_1.expand()
  1131. == TransferFunctionMatrix(((TransferFunction(s**3 - 2*s**2 - 3*s, s**4 + 1, s), TransferFunction(2, 1, s)),
  1132. (TransferFunction(p, 1, s), TransferFunction(p, s, s)))))
  1133. assert H_5.expand() == \
  1134. TransferFunctionMatrix(((TransferFunction(s**5 + s**3 + s, -s**2 + s, s), TransferFunction(s**2 + 2*s - 3, s**2 + 4*s - 5, s)),))
  1135. def test_TransferFunction_gbt():
  1136. # simple transfer function, e.g. ohms law
  1137. tf = TransferFunction(1, a*s+b, s)
  1138. numZ, denZ = gbt(tf, T, 0.5)
  1139. # discretized transfer function with coefs from tf.gbt()
  1140. tf_test_bilinear = TransferFunction(s * numZ[0] + numZ[1], s * denZ[0] + denZ[1], s)
  1141. # corresponding tf with manually calculated coefs
  1142. tf_test_manual = TransferFunction(s * T/(2*(a + b*T/2)) + T/(2*(a + b*T/2)), s + (-a + b*T/2)/(a + b*T/2), s)
  1143. assert S.Zero == (tf_test_bilinear.simplify()-tf_test_manual.simplify()).simplify().num
  1144. tf = TransferFunction(1, a*s+b, s)
  1145. numZ, denZ = gbt(tf, T, 0)
  1146. # discretized transfer function with coefs from tf.gbt()
  1147. tf_test_forward = TransferFunction(numZ[0], s*denZ[0]+denZ[1], s)
  1148. # corresponding tf with manually calculated coefs
  1149. tf_test_manual = TransferFunction(T/a, s + (-a + b*T)/a, s)
  1150. assert S.Zero == (tf_test_forward.simplify()-tf_test_manual.simplify()).simplify().num
  1151. tf = TransferFunction(1, a*s+b, s)
  1152. numZ, denZ = gbt(tf, T, 1)
  1153. # discretized transfer function with coefs from tf.gbt()
  1154. tf_test_backward = TransferFunction(s*numZ[0], s*denZ[0]+denZ[1], s)
  1155. # corresponding tf with manually calculated coefs
  1156. tf_test_manual = TransferFunction(s * T/(a + b*T), s - a/(a + b*T), s)
  1157. assert S.Zero == (tf_test_backward.simplify()-tf_test_manual.simplify()).simplify().num
  1158. tf = TransferFunction(1, a*s+b, s)
  1159. numZ, denZ = gbt(tf, T, 0.3)
  1160. # discretized transfer function with coefs from tf.gbt()
  1161. tf_test_gbt = TransferFunction(s*numZ[0]+numZ[1], s*denZ[0]+denZ[1], s)
  1162. # corresponding tf with manually calculated coefs
  1163. tf_test_manual = TransferFunction(s*3*T/(10*(a + 3*b*T/10)) + 7*T/(10*(a + 3*b*T/10)), s + (-a + 7*b*T/10)/(a + 3*b*T/10), s)
  1164. assert S.Zero == (tf_test_gbt.simplify()-tf_test_manual.simplify()).simplify().num
  1165. def test_TransferFunction_bilinear():
  1166. # simple transfer function, e.g. ohms law
  1167. tf = TransferFunction(1, a*s+b, s)
  1168. numZ, denZ = bilinear(tf, T)
  1169. # discretized transfer function with coefs from tf.bilinear()
  1170. tf_test_bilinear = TransferFunction(s*numZ[0]+numZ[1], s*denZ[0]+denZ[1], s)
  1171. # corresponding tf with manually calculated coefs
  1172. tf_test_manual = TransferFunction(s * T/(2*(a + b*T/2)) + T/(2*(a + b*T/2)), s + (-a + b*T/2)/(a + b*T/2), s)
  1173. assert S.Zero == (tf_test_bilinear.simplify()-tf_test_manual.simplify()).simplify().num
  1174. def test_TransferFunction_forward_diff():
  1175. # simple transfer function, e.g. ohms law
  1176. tf = TransferFunction(1, a*s+b, s)
  1177. numZ, denZ = forward_diff(tf, T)
  1178. # discretized transfer function with coefs from tf.forward_diff()
  1179. tf_test_forward = TransferFunction(numZ[0], s*denZ[0]+denZ[1], s)
  1180. # corresponding tf with manually calculated coefs
  1181. tf_test_manual = TransferFunction(T/a, s + (-a + b*T)/a, s)
  1182. assert S.Zero == (tf_test_forward.simplify()-tf_test_manual.simplify()).simplify().num
  1183. def test_TransferFunction_backward_diff():
  1184. # simple transfer function, e.g. ohms law
  1185. tf = TransferFunction(1, a*s+b, s)
  1186. numZ, denZ = backward_diff(tf, T)
  1187. # discretized transfer function with coefs from tf.backward_diff()
  1188. tf_test_backward = TransferFunction(s*numZ[0]+numZ[1], s*denZ[0]+denZ[1], s)
  1189. # corresponding tf with manually calculated coefs
  1190. tf_test_manual = TransferFunction(s * T/(a + b*T), s - a/(a + b*T), s)
  1191. assert S.Zero == (tf_test_backward.simplify()-tf_test_manual.simplify()).simplify().num
  1192. def test_TransferFunction_phase_margin():
  1193. # Test for phase margin
  1194. tf1 = TransferFunction(10, p**3 + 1, p)
  1195. tf2 = TransferFunction(s**2, 10, s)
  1196. tf3 = TransferFunction(1, a*s+b, s)
  1197. tf4 = TransferFunction((s + 1)*exp(s/tau), s**2 + 2, s)
  1198. tf_m = TransferFunctionMatrix([[tf2],[tf3]])
  1199. assert phase_margin(tf1) == -180 + 180*atan(3*sqrt(11))/pi
  1200. assert phase_margin(tf2) == 0
  1201. raises(NotImplementedError, lambda: phase_margin(tf4))
  1202. raises(ValueError, lambda: phase_margin(tf3))
  1203. raises(ValueError, lambda: phase_margin(MIMOSeries(tf_m)))
  1204. def test_TransferFunction_gain_margin():
  1205. # Test for gain margin
  1206. tf1 = TransferFunction(s**2, 5*(s+1)*(s-5)*(s-10), s)
  1207. tf2 = TransferFunction(s**2 + 2*s + 1, 1, s)
  1208. tf3 = TransferFunction(1, a*s+b, s)
  1209. tf4 = TransferFunction((s + 1)*exp(s/tau), s**2 + 2, s)
  1210. tf_m = TransferFunctionMatrix([[tf2],[tf3]])
  1211. assert gain_margin(tf1) == -20*log(S(7)/540)/log(10)
  1212. assert gain_margin(tf2) == oo
  1213. raises(NotImplementedError, lambda: gain_margin(tf4))
  1214. raises(ValueError, lambda: gain_margin(tf3))
  1215. raises(ValueError, lambda: gain_margin(MIMOSeries(tf_m)))
  1216. def test_StateSpace_construction():
  1217. # using different numbers for a SISO system.
  1218. A1 = Matrix([[0, 1], [1, 0]])
  1219. B1 = Matrix([1, 0])
  1220. C1 = Matrix([[0, 1]])
  1221. D1 = Matrix([0])
  1222. ss1 = StateSpace(A1, B1, C1, D1)
  1223. assert ss1.state_matrix == Matrix([[0, 1], [1, 0]])
  1224. assert ss1.input_matrix == Matrix([1, 0])
  1225. assert ss1.output_matrix == Matrix([[0, 1]])
  1226. assert ss1.feedforward_matrix == Matrix([0])
  1227. assert ss1.args == (Matrix([[0, 1], [1, 0]]), Matrix([[1], [0]]), Matrix([[0, 1]]), Matrix([[0]]))
  1228. # using different symbols for a SISO system.
  1229. ss2 = StateSpace(Matrix([a0]), Matrix([a1]),
  1230. Matrix([a2]), Matrix([a3]))
  1231. assert ss2.state_matrix == Matrix([[a0]])
  1232. assert ss2.input_matrix == Matrix([[a1]])
  1233. assert ss2.output_matrix == Matrix([[a2]])
  1234. assert ss2.feedforward_matrix == Matrix([[a3]])
  1235. assert ss2.args == (Matrix([[a0]]), Matrix([[a1]]), Matrix([[a2]]), Matrix([[a3]]))
  1236. # using different numbers for a MIMO system.
  1237. ss3 = StateSpace(Matrix([[-1.5, -2], [1, 0]]),
  1238. Matrix([[0.5, 0], [0, 1]]),
  1239. Matrix([[0, 1], [0, 2]]),
  1240. Matrix([[2, 2], [1, 1]]))
  1241. assert ss3.state_matrix == Matrix([[-1.5, -2], [1, 0]])
  1242. assert ss3.input_matrix == Matrix([[0.5, 0], [0, 1]])
  1243. assert ss3.output_matrix == Matrix([[0, 1], [0, 2]])
  1244. assert ss3.feedforward_matrix == Matrix([[2, 2], [1, 1]])
  1245. assert ss3.args == (Matrix([[-1.5, -2],
  1246. [1, 0]]),
  1247. Matrix([[0.5, 0],
  1248. [0, 1]]),
  1249. Matrix([[0, 1],
  1250. [0, 2]]),
  1251. Matrix([[2, 2],
  1252. [1, 1]]))
  1253. # using different symbols for a MIMO system.
  1254. A4 = Matrix([[a0, a1], [a2, a3]])
  1255. B4 = Matrix([[b0, b1], [b2, b3]])
  1256. C4 = Matrix([[c0, c1], [c2, c3]])
  1257. D4 = Matrix([[d0, d1], [d2, d3]])
  1258. ss4 = StateSpace(A4, B4, C4, D4)
  1259. assert ss4.state_matrix == Matrix([[a0, a1], [a2, a3]])
  1260. assert ss4.input_matrix == Matrix([[b0, b1], [b2, b3]])
  1261. assert ss4.output_matrix == Matrix([[c0, c1], [c2, c3]])
  1262. assert ss4.feedforward_matrix == Matrix([[d0, d1], [d2, d3]])
  1263. assert ss4.args == (Matrix([[a0, a1],
  1264. [a2, a3]]),
  1265. Matrix([[b0, b1],
  1266. [b2, b3]]),
  1267. Matrix([[c0, c1],
  1268. [c2, c3]]),
  1269. Matrix([[d0, d1],
  1270. [d2, d3]]))
  1271. # using less matrices. Rest will be filled with a minimum of zeros.
  1272. ss5 = StateSpace()
  1273. assert ss5.args == (Matrix([[0]]), Matrix([[0]]), Matrix([[0]]), Matrix([[0]]))
  1274. A6 = Matrix([[0, 1], [1, 0]])
  1275. B6 = Matrix([1, 1])
  1276. ss6 = StateSpace(A6, B6)
  1277. assert ss6.state_matrix == Matrix([[0, 1], [1, 0]])
  1278. assert ss6.input_matrix == Matrix([1, 1])
  1279. assert ss6.output_matrix == Matrix([[0, 0]])
  1280. assert ss6.feedforward_matrix == Matrix([[0]])
  1281. assert ss6.args == (Matrix([[0, 1],
  1282. [1, 0]]),
  1283. Matrix([[1],
  1284. [1]]),
  1285. Matrix([[0, 0]]),
  1286. Matrix([[0]]))
  1287. # Check if the system is SISO or MIMO.
  1288. # If system is not SISO, then it is definitely MIMO.
  1289. assert ss1.is_SISO == True
  1290. assert ss2.is_SISO == True
  1291. assert ss3.is_SISO == False
  1292. assert ss4.is_SISO == False
  1293. assert ss5.is_SISO == True
  1294. assert ss6.is_SISO == True
  1295. # ShapeError if matrices do not fit.
  1296. raises(ShapeError, lambda: StateSpace(Matrix([s, (s+1)**2]), Matrix([s+1]),
  1297. Matrix([s**2 - 1]), Matrix([2*s])))
  1298. raises(ShapeError, lambda: StateSpace(Matrix([s]), Matrix([s+1, s**3 + 1]),
  1299. Matrix([s**2 - 1]), Matrix([2*s])))
  1300. raises(ShapeError, lambda: StateSpace(Matrix([s]), Matrix([s+1]),
  1301. Matrix([[s**2 - 1], [s**2 + 2*s + 1]]), Matrix([2*s])))
  1302. raises(ShapeError, lambda: StateSpace(Matrix([[-s, -s], [s, 0]]),
  1303. Matrix([[s/2, 0], [0, s]]),
  1304. Matrix([[0, s]]),
  1305. Matrix([[2*s, 2*s], [s, s]])))
  1306. # TypeError if arguments are not sympy matrices.
  1307. raises(TypeError, lambda: StateSpace(s**2, s+1, 2*s, 1))
  1308. raises(TypeError, lambda: StateSpace(Matrix([2, 0.5]), Matrix([-1]),
  1309. Matrix([1]), 0))
  1310. def test_StateSpace_add():
  1311. A1 = Matrix([[4, 1],[2, -3]])
  1312. B1 = Matrix([[5, 2],[-3, -3]])
  1313. C1 = Matrix([[2, -4],[0, 1]])
  1314. D1 = Matrix([[3, 2],[1, -1]])
  1315. ss1 = StateSpace(A1, B1, C1, D1)
  1316. A2 = Matrix([[-3, 4, 2],[-1, -3, 0],[2, 5, 3]])
  1317. B2 = Matrix([[1, 4],[-3, -3],[-2, 1]])
  1318. C2 = Matrix([[4, 2, -3],[1, 4, 3]])
  1319. D2 = Matrix([[-2, 4],[0, 1]])
  1320. ss2 = StateSpace(A2, B2, C2, D2)
  1321. ss3 = StateSpace()
  1322. ss4 = StateSpace(Matrix([1]), Matrix([2]), Matrix([3]), Matrix([4]))
  1323. expected_add = \
  1324. StateSpace(
  1325. Matrix([
  1326. [4, 1, 0, 0, 0],
  1327. [2, -3, 0, 0, 0],
  1328. [0, 0, -3, 4, 2],
  1329. [0, 0, -1, -3, 0],
  1330. [0, 0, 2, 5, 3]]),
  1331. Matrix([
  1332. [ 5, 2],
  1333. [-3, -3],
  1334. [ 1, 4],
  1335. [-3, -3],
  1336. [-2, 1]]),
  1337. Matrix([
  1338. [2, -4, 4, 2, -3],
  1339. [0, 1, 1, 4, 3]]),
  1340. Matrix([
  1341. [1, 6],
  1342. [1, 0]]))
  1343. expected_mul = \
  1344. StateSpace(
  1345. Matrix([
  1346. [ -3, 4, 2, 0, 0],
  1347. [ -1, -3, 0, 0, 0],
  1348. [ 2, 5, 3, 0, 0],
  1349. [ 22, 18, -9, 4, 1],
  1350. [-15, -18, 0, 2, -3]]),
  1351. Matrix([
  1352. [ 1, 4],
  1353. [ -3, -3],
  1354. [ -2, 1],
  1355. [-10, 22],
  1356. [ 6, -15]]),
  1357. Matrix([
  1358. [14, 14, -3, 2, -4],
  1359. [ 3, -2, -6, 0, 1]]),
  1360. Matrix([
  1361. [-6, 14],
  1362. [-2, 3]]))
  1363. assert ss1 + ss2 == expected_add
  1364. assert ss1*ss2 == expected_mul
  1365. assert ss3 + 1/2 == StateSpace(Matrix([[0]]), Matrix([[0]]), Matrix([[0]]), Matrix([[0.5]]))
  1366. assert ss4*1.5 == StateSpace(Matrix([[1]]), Matrix([[2]]), Matrix([[4.5]]), Matrix([[6.0]]))
  1367. assert 1.5*ss4 == StateSpace(Matrix([[1]]), Matrix([[3.0]]), Matrix([[3]]), Matrix([[6.0]]))
  1368. raises(ShapeError, lambda: ss1 + ss3)
  1369. raises(ShapeError, lambda: ss2*ss4)
  1370. def test_StateSpace_negation():
  1371. A = Matrix([[a0, a1], [a2, a3]])
  1372. B = Matrix([[b0, b1], [b2, b3]])
  1373. C = Matrix([[c0, c1], [c1, c2], [c2, c3]])
  1374. D = Matrix([[d0, d1], [d1, d2], [d2, d3]])
  1375. SS = StateSpace(A, B, C, D)
  1376. SS_neg = -SS
  1377. state_mat = Matrix([[-1, 1], [1, -1]])
  1378. input_mat = Matrix([1, -1])
  1379. output_mat = Matrix([[-1, 1]])
  1380. feedforward_mat = Matrix([1])
  1381. system = StateSpace(state_mat, input_mat, output_mat, feedforward_mat)
  1382. assert SS_neg == \
  1383. StateSpace(Matrix([[a0, a1],
  1384. [a2, a3]]),
  1385. Matrix([[b0, b1],
  1386. [b2, b3]]),
  1387. Matrix([[-c0, -c1],
  1388. [-c1, -c2],
  1389. [-c2, -c3]]),
  1390. Matrix([[-d0, -d1],
  1391. [-d1, -d2],
  1392. [-d2, -d3]]))
  1393. assert -system == \
  1394. StateSpace(Matrix([[-1, 1],
  1395. [ 1, -1]]),
  1396. Matrix([[ 1],[-1]]),
  1397. Matrix([[1, -1]]),
  1398. Matrix([[-1]]))
  1399. assert -SS_neg == SS
  1400. assert -(-(-(-system))) == system
  1401. def test_SymPy_substitution_functions():
  1402. # subs
  1403. ss1 = StateSpace(Matrix([s]), Matrix([(s + 1)**2]), Matrix([s**2 - 1]), Matrix([2*s]))
  1404. ss2 = StateSpace(Matrix([s + p]), Matrix([(s + 1)*(p - 1)]), Matrix([p**3 - s**3]), Matrix([s - p]))
  1405. assert ss1.subs({s:5}) == StateSpace(Matrix([[5]]), Matrix([[36]]), Matrix([[24]]), Matrix([[10]]))
  1406. assert ss2.subs({p:1}) == StateSpace(Matrix([[s + 1]]), Matrix([[0]]), Matrix([[1 - s**3]]), Matrix([[s - 1]]))
  1407. # xreplace
  1408. assert ss1.xreplace({s:p}) == \
  1409. StateSpace(Matrix([[p]]), Matrix([[(p + 1)**2]]), Matrix([[p**2 - 1]]), Matrix([[2*p]]))
  1410. assert ss2.xreplace({s:a, p:b}) == \
  1411. StateSpace(Matrix([[a + b]]), Matrix([[(a + 1)*(b - 1)]]), Matrix([[-a**3 + b**3]]), Matrix([[a - b]]))
  1412. # evalf
  1413. p1 = a1*s + a0
  1414. p2 = b2*s**2 + b1*s + b0
  1415. G = StateSpace(Matrix([p1]), Matrix([p2]))
  1416. expect = StateSpace(Matrix([[2*s + 1]]), Matrix([[5*s**2 + 4*s + 3]]), Matrix([[0]]), Matrix([[0]]))
  1417. expect_ = StateSpace(Matrix([[2.0*s + 1.0]]), Matrix([[5.0*s**2 + 4.0*s + 3.0]]), Matrix([[0]]), Matrix([[0]]))
  1418. assert G.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}) == expect
  1419. assert G.subs({a0: 1, a1: 2, b0: 3, b1: 4, b2: 5}).evalf() == expect_
  1420. assert expect.evalf() == expect_
  1421. def test_conversion():
  1422. # StateSpace to TransferFunction for SISO
  1423. A1 = Matrix([[-5, -1], [3, -1]])
  1424. B1 = Matrix([2, 5])
  1425. C1 = Matrix([[1, 2]])
  1426. D1 = Matrix([0])
  1427. H1 = StateSpace(A1, B1, C1, D1)
  1428. H3 = StateSpace(Matrix([[a0, a1], [a2, a3]]), B = Matrix([[b1], [b2]]), C = Matrix([[c1, c2]]))
  1429. tm1 = H1.rewrite(TransferFunction)
  1430. tm2 = (-H1).rewrite(TransferFunction)
  1431. tf1 = tm1[0][0]
  1432. tf2 = tm2[0][0]
  1433. assert tf1 == TransferFunction(12*s + 59, s**2 + 6*s + 8, s)
  1434. assert tf2.num == -tf1.num
  1435. assert tf2.den == tf1.den
  1436. # StateSpace to TransferFunction for MIMO
  1437. A2 = Matrix([[-1.5, -2, 3], [1, 0, 1], [2, 1, 1]])
  1438. B2 = Matrix([[0.5, 0, 1], [0, 1, 2], [2, 2, 3]])
  1439. C2 = Matrix([[0, 1, 0], [0, 2, 1], [1, 0, 2]])
  1440. D2 = Matrix([[2, 2, 0], [1, 1, 1], [3, 2, 1]])
  1441. H2 = StateSpace(A2, B2, C2, D2)
  1442. tm3 = H2.rewrite(TransferFunction)
  1443. # outputs for input i obtained at Index i-1. Consider input 1
  1444. assert tm3[0][0] == TransferFunction(2.0*s**3 + 1.0*s**2 - 10.5*s + 4.5, 1.0*s**3 + 0.5*s**2 - 6.5*s - 2.5, s)
  1445. assert tm3[0][1] == TransferFunction(2.0*s**3 + 2.0*s**2 - 10.5*s - 3.5, 1.0*s**3 + 0.5*s**2 - 6.5*s - 2.5, s)
  1446. assert tm3[0][2] == TransferFunction(2.0*s**2 + 5.0*s - 0.5, 1.0*s**3 + 0.5*s**2 - 6.5*s - 2.5, s)
  1447. assert H3.rewrite(TransferFunction) == [[TransferFunction(-c1*(a1*b2 - a3*b1 + b1*s) - c2*(-a0*b2 + a2*b1 + b2*s),
  1448. -a0*a3 + a0*s + a1*a2 + a3*s - s**2, s)]]
  1449. # TransferFunction to StateSpace
  1450. SS = TF1.rewrite(StateSpace)
  1451. assert SS == \
  1452. StateSpace(Matrix([[ 0, 1],
  1453. [-wn**2, -2*wn*zeta]]),
  1454. Matrix([[0],
  1455. [1]]),
  1456. Matrix([[1, 0]]),
  1457. Matrix([[0]]))
  1458. assert SS.rewrite(TransferFunction)[0][0] == TF1
  1459. # Transfer function has to be proper
  1460. raises(ValueError, lambda: TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s).rewrite(StateSpace))
  1461. def test_StateSpace_dsolve():
  1462. # https://web.mit.edu/2.14/www/Handouts/StateSpaceResponse.pdf
  1463. # https://lpsa.swarthmore.edu/Transient/TransMethSS.html
  1464. A1 = Matrix([[0, 1], [-2, -3]])
  1465. B1 = Matrix([[0], [1]])
  1466. C1 = Matrix([[1, -1]])
  1467. D1 = Matrix([0])
  1468. I1 = Matrix([[1], [2]])
  1469. t = symbols('t')
  1470. ss1 = StateSpace(A1, B1, C1, D1)
  1471. # Zero input and Zero initial conditions
  1472. assert ss1.dsolve() == Matrix([[0]])
  1473. assert ss1.dsolve(initial_conditions=I1) == Matrix([[8*exp(-t) - 9*exp(-2*t)]])
  1474. A2 = Matrix([[-2, 0], [1, -1]])
  1475. C2 = eye(2,2)
  1476. I2 = Matrix([2, 3])
  1477. ss2 = StateSpace(A=A2, C=C2)
  1478. assert ss2.dsolve(initial_conditions=I2) == Matrix([[2*exp(-2*t)], [5*exp(-t) - 2*exp(-2*t)]])
  1479. A3 = Matrix([[-1, 1], [-4, -4]])
  1480. B3 = Matrix([[0], [4]])
  1481. C3 = Matrix([[0, 1]])
  1482. D3 = Matrix([0])
  1483. U3 = Matrix([10])
  1484. ss3 = StateSpace(A3, B3, C3, D3)
  1485. op = ss3.dsolve(input_vector=U3, var=t)
  1486. assert str(op.simplify().expand().evalf()[0]) == str(5.0 + 20.7880460155075*exp(-5*t/2)*sin(sqrt(7)*t/2)
  1487. - 5.0*exp(-5*t/2)*cos(sqrt(7)*t/2))
  1488. # Test with Heaviside as input
  1489. A4 = Matrix([[-1, 1], [-4, -4]])
  1490. B4 = Matrix([[0], [4]])
  1491. C4 = Matrix([[0, 1]])
  1492. U4 = Matrix([[10*Heaviside(t)]])
  1493. ss4 = StateSpace(A4, B4, C4)
  1494. op4 = str(ss4.dsolve(var=t, input_vector=U4)[0].simplify().expand().evalf())
  1495. assert op4 == str(5.0*Heaviside(t) + 20.7880460155075*exp(-5*t/2)*sin(sqrt(7)*t/2)*Heaviside(t)
  1496. - 5.0*exp(-5*t/2)*cos(sqrt(7)*t/2)*Heaviside(t))
  1497. # Test with Symbolic Matrices
  1498. m, a, x0 = symbols('m a x_0')
  1499. A5 = Matrix([[0, 1], [0, 0]])
  1500. B5 = Matrix([[0], [1 / m]])
  1501. C5 = Matrix([[1, 0]])
  1502. I5 = Matrix([[x0], [0]])
  1503. U5 = Matrix([[exp(-a * t)]])
  1504. ss5 = StateSpace(A5, B5, C5)
  1505. op5 = ss5.dsolve(initial_conditions=I5, input_vector=U5, var=t).simplify()
  1506. assert op5[0].args[0][0] == x0 + t/(a*m) - 1/(a**2*m) + exp(-a*t)/(a**2*m)
  1507. a11, a12, a21, a22, b1, b2, c1, c2, i1, i2 = symbols('a_11 a_12 a_21 a_22 b_1 b_2 c_1 c_2 i_1 i_2')
  1508. A6 = Matrix([[a11, a12], [a21, a22]])
  1509. B6 = Matrix([b1, b2])
  1510. C6 = Matrix([[c1, c2]])
  1511. I6 = Matrix([i1, i2])
  1512. ss6 = StateSpace(A6, B6, C6)
  1513. expr6 = ss6.dsolve(initial_conditions=I6)[0]
  1514. expr6 = expr6.subs([(a11, 0), (a12, 1), (a21, -2), (a22, -3), (b1, 0), (b2, 1), (c1, 1), (c2, -1), (i1, 1), (i2, 2)])
  1515. assert expr6 == 8*exp(-t) - 9*exp(-2*t)
  1516. def test_StateSpace_functions():
  1517. # https://in.mathworks.com/help/control/ref/statespacemodel.obsv.html
  1518. A_mat = Matrix([[-1.5, -2], [1, 0]])
  1519. B_mat = Matrix([0.5, 0])
  1520. C_mat = Matrix([[0, 1]])
  1521. D_mat = Matrix([1])
  1522. SS1 = StateSpace(A_mat, B_mat, C_mat, D_mat)
  1523. SS2 = StateSpace(Matrix([[1, 1], [4, -2]]),Matrix([[0, 1], [0, 2]]),Matrix([[-1, 1], [1, -1]]))
  1524. SS3 = StateSpace(Matrix([[1, 1], [4, -2]]),Matrix([[1, -1], [1, -1]]))
  1525. SS4 = StateSpace(Matrix([[a0, a1], [a2, a3]]), Matrix([[b1], [b2]]), Matrix([[c1, c2]]))
  1526. # Observability
  1527. assert SS1.is_observable() == True
  1528. assert SS2.is_observable() == False
  1529. assert SS1.observability_matrix() == Matrix([[0, 1], [1, 0]])
  1530. assert SS2.observability_matrix() == Matrix([[-1, 1], [ 1, -1], [ 3, -3], [-3, 3]])
  1531. assert SS1.observable_subspace() == [Matrix([[0], [1]]), Matrix([[1], [0]])]
  1532. assert SS2.observable_subspace() == [Matrix([[-1], [ 1], [ 3], [-3]])]
  1533. Qo = SS4.observability_matrix().subs([(a0, 0), (a1, -6), (a2, 1), (a3, -5), (c1, 0), (c2, 1)])
  1534. assert Qo == Matrix([[0, 1], [1, -5]])
  1535. # Controllability
  1536. assert SS1.is_controllable() == True
  1537. assert SS3.is_controllable() == False
  1538. assert SS1.controllability_matrix() == Matrix([[0.5, -0.75], [ 0, 0.5]])
  1539. assert SS3.controllability_matrix() == Matrix([[1, -1, 2, -2], [1, -1, 2, -2]])
  1540. assert SS1.controllable_subspace() == [Matrix([[0.5], [ 0]]), Matrix([[-0.75], [ 0.5]])]
  1541. assert SS3.controllable_subspace() == [Matrix([[1], [1]])]
  1542. assert SS4.controllable_subspace() == [Matrix([
  1543. [b1],
  1544. [b2]]), Matrix([
  1545. [a0*b1 + a1*b2],
  1546. [a2*b1 + a3*b2]])]
  1547. Qc = SS4.controllability_matrix().subs([(a0, 0), (a1, 1), (a2, -6), (a3, -5), (b1, 0), (b2, 1)])
  1548. assert Qc == Matrix([[0, 1], [1, -5]])
  1549. # Append
  1550. A1 = Matrix([[0, 1], [1, 0]])
  1551. B1 = Matrix([[0], [1]])
  1552. C1 = Matrix([[0, 1]])
  1553. D1 = Matrix([[0]])
  1554. ss1 = StateSpace(A1, B1, C1, D1)
  1555. ss2 = StateSpace(Matrix([[1, 0], [0, 1]]), Matrix([[1], [0]]), Matrix([[1, 0]]), Matrix([[1]]))
  1556. ss3 = ss1.append(ss2)
  1557. ss4 = SS4.append(ss1)
  1558. assert ss3.num_states == ss1.num_states + ss2.num_states
  1559. assert ss3.num_inputs == ss1.num_inputs + ss2.num_inputs
  1560. assert ss3.num_outputs == ss1.num_outputs + ss2.num_outputs
  1561. assert ss3.state_matrix == Matrix([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]])
  1562. assert ss3.input_matrix == Matrix([[0, 0], [1, 0], [0, 1], [0, 0]])
  1563. assert ss3.output_matrix == Matrix([[0, 1, 0, 0], [0, 0, 1, 0]])
  1564. assert ss3.feedforward_matrix == Matrix([[0, 0], [0, 1]])
  1565. # Using symbolic matrices
  1566. assert ss4.num_states == SS4.num_states + ss1.num_states
  1567. assert ss4.num_inputs == SS4.num_inputs + ss1.num_inputs
  1568. assert ss4.num_outputs == SS4.num_outputs + ss1.num_outputs
  1569. assert ss4.state_matrix == Matrix([[a0, a1, 0, 0], [a2, a3, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])
  1570. assert ss4.input_matrix == Matrix([[b1, 0], [b2, 0], [0, 0], [0, 1]])
  1571. assert ss4.output_matrix == Matrix([[c1, c2, 0, 0], [0, 0, 0, 1]])
  1572. assert ss4.feedforward_matrix == Matrix([[0, 0], [0, 0]])
  1573. def test_StateSpace_series():
  1574. # For SISO Systems
  1575. a1 = Matrix([[0, 1], [1, 0]])
  1576. b1 = Matrix([[0], [1]])
  1577. c1 = Matrix([[0, 1]])
  1578. d1 = Matrix([[0]])
  1579. a2 = Matrix([[1, 0], [0, 1]])
  1580. b2 = Matrix([[1], [0]])
  1581. c2 = Matrix([[1, 0]])
  1582. d2 = Matrix([[1]])
  1583. ss1 = StateSpace(a1, b1, c1, d1)
  1584. ss2 = StateSpace(a2, b2, c2, d2)
  1585. tf1 = TransferFunction(s, s+1, s)
  1586. ser1 = Series(ss1, ss2)
  1587. assert ser1 == Series(StateSpace(Matrix([
  1588. [0, 1],
  1589. [1, 0]]), Matrix([
  1590. [0],
  1591. [1]]), Matrix([[0, 1]]), Matrix([[0]])), StateSpace(Matrix([
  1592. [1, 0],
  1593. [0, 1]]), Matrix([
  1594. [1],
  1595. [0]]), Matrix([[1, 0]]), Matrix([[1]])))
  1596. assert ser1.doit() == StateSpace(
  1597. Matrix([
  1598. [0, 1, 0, 0],
  1599. [1, 0, 0, 0],
  1600. [0, 1, 1, 0],
  1601. [0, 0, 0, 1]]),
  1602. Matrix([
  1603. [0],
  1604. [1],
  1605. [0],
  1606. [0]]),
  1607. Matrix([[0, 1, 1, 0]]),
  1608. Matrix([[0]]))
  1609. assert ser1.num_inputs == 1
  1610. assert ser1.num_outputs == 1
  1611. assert ser1.rewrite(TransferFunction) == TransferFunction(s**2, s**3 - s**2 - s + 1, s)
  1612. ser2 = Series(ss1)
  1613. ser3 = Series(ser2, ss2)
  1614. assert ser3.doit() == ser1.doit()
  1615. # TransferFunction interconnection with StateSpace
  1616. ser_tf = Series(tf1, ss1)
  1617. assert ser_tf == Series(TransferFunction(s, s + 1, s), StateSpace(Matrix([
  1618. [0, 1],
  1619. [1, 0]]), Matrix([
  1620. [0],
  1621. [1]]), Matrix([[0, 1]]), Matrix([[0]])))
  1622. assert ser_tf.doit() == StateSpace(
  1623. Matrix([
  1624. [-1, 0, 0],
  1625. [0, 0, 1],
  1626. [-1, 1, 0]]),
  1627. Matrix([
  1628. [1],
  1629. [0],
  1630. [1]]),
  1631. Matrix([[0, 0, 1]]),
  1632. Matrix([[0]]))
  1633. assert ser_tf.rewrite(TransferFunction) == TransferFunction(s**2, s**3 + s**2 - s - 1, s)
  1634. # For MIMO Systems
  1635. a3 = Matrix([[4, 1], [2, -3]])
  1636. b3 = Matrix([[5, 2], [-3, -3]])
  1637. c3 = Matrix([[2, -4], [0, 1]])
  1638. d3 = Matrix([[3, 2], [1, -1]])
  1639. a4 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  1640. b4 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  1641. c4 = Matrix([[4, 2, -3], [1, 4, 3]])
  1642. d4 = Matrix([[-2, 4], [0, 1]])
  1643. ss3 = StateSpace(a3, b3, c3, d3)
  1644. ss4 = StateSpace(a4, b4, c4, d4)
  1645. ser4 = MIMOSeries(ss3, ss4)
  1646. assert ser4 == MIMOSeries(StateSpace(Matrix([
  1647. [4, 1],
  1648. [2, -3]]), Matrix([
  1649. [ 5, 2],
  1650. [-3, -3]]), Matrix([
  1651. [2, -4],
  1652. [0, 1]]), Matrix([
  1653. [3, 2],
  1654. [1, -1]])), StateSpace(Matrix([
  1655. [-3, 4, 2],
  1656. [-1, -3, 0],
  1657. [ 2, 5, 3]]), Matrix([
  1658. [ 1, 4],
  1659. [-3, -3],
  1660. [-2, 1]]), Matrix([
  1661. [4, 2, -3],
  1662. [1, 4, 3]]), Matrix([
  1663. [-2, 4],
  1664. [ 0, 1]])))
  1665. assert ser4.doit() == StateSpace(
  1666. Matrix([
  1667. [4, 1, 0, 0, 0],
  1668. [2, -3, 0, 0, 0],
  1669. [2, 0, -3, 4, 2],
  1670. [-6, 9, -1, -3, 0],
  1671. [-4, 9, 2, 5, 3]]),
  1672. Matrix([
  1673. [5, 2],
  1674. [-3, -3],
  1675. [7, -2],
  1676. [-12, -3],
  1677. [-5, -5]]),
  1678. Matrix([
  1679. [-4, 12, 4, 2, -3],
  1680. [0, 1, 1, 4, 3]]),
  1681. Matrix([
  1682. [-2, -8],
  1683. [1, -1]]))
  1684. assert ser4.num_inputs == ss3.num_inputs
  1685. assert ser4.num_outputs == ss4.num_outputs
  1686. ser5 = MIMOSeries(ss3)
  1687. ser6 = MIMOSeries(ser5, ss4)
  1688. assert ser6.doit() == ser4.doit()
  1689. assert ser6.rewrite(TransferFunctionMatrix) == ser4.rewrite(TransferFunctionMatrix)
  1690. tf2 = TransferFunction(1, s, s)
  1691. tf3 = TransferFunction(1, s+1, s)
  1692. tf4 = TransferFunction(s, s+2, s)
  1693. tfm = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  1694. ser6 = MIMOSeries(ss3, tfm)
  1695. assert ser6 == MIMOSeries(StateSpace(Matrix([
  1696. [4, 1],
  1697. [2, -3]]), Matrix([
  1698. [ 5, 2],
  1699. [-3, -3]]), Matrix([
  1700. [2, -4],
  1701. [0, 1]]), Matrix([
  1702. [3, 2],
  1703. [1, -1]])), TransferFunctionMatrix((
  1704. (TransferFunction(s, s + 1, s), TransferFunction(1, s, s)),
  1705. (TransferFunction(1, s + 1, s), TransferFunction(s, s + 2, s)))))
  1706. def test_StateSpace_parallel():
  1707. # For SISO system
  1708. a1 = Matrix([[0, 1], [1, 0]])
  1709. b1 = Matrix([[0], [1]])
  1710. c1 = Matrix([[0, 1]])
  1711. d1 = Matrix([[0]])
  1712. a2 = Matrix([[1, 0], [0, 1]])
  1713. b2 = Matrix([[1], [0]])
  1714. c2 = Matrix([[1, 0]])
  1715. d2 = Matrix([[1]])
  1716. ss1 = StateSpace(a1, b1, c1, d1)
  1717. ss2 = StateSpace(a2, b2, c2, d2)
  1718. p1 = Parallel(ss1, ss2)
  1719. assert p1 == Parallel(StateSpace(Matrix([[0, 1], [1, 0]]), Matrix([[0], [1]]), Matrix([[0, 1]]), Matrix([[0]])),
  1720. StateSpace(Matrix([[1, 0],[0, 1]]), Matrix([[1],[0]]), Matrix([[1, 0]]), Matrix([[1]])))
  1721. assert p1.doit() == StateSpace(Matrix([
  1722. [0, 1, 0, 0],
  1723. [1, 0, 0, 0],
  1724. [0, 0, 1, 0],
  1725. [0, 0, 0, 1]]),
  1726. Matrix([
  1727. [0],
  1728. [1],
  1729. [1],
  1730. [0]]),
  1731. Matrix([[0, 1, 1, 0]]),
  1732. Matrix([[1]]))
  1733. assert p1.rewrite(TransferFunction) == TransferFunction(s*(s + 2), s**2 - 1, s)
  1734. # Connecting StateSpace with TransferFunction
  1735. tf1 = TransferFunction(s, s+1, s)
  1736. p2 = Parallel(ss1, tf1)
  1737. assert p2 == Parallel(StateSpace(Matrix([
  1738. [0, 1],
  1739. [1, 0]]), Matrix([
  1740. [0],
  1741. [1]]), Matrix([[0, 1]]), Matrix([[0]])), TransferFunction(s, s + 1, s))
  1742. assert p2.doit() == StateSpace(
  1743. Matrix([
  1744. [0, 1, 0],
  1745. [1, 0, 0],
  1746. [0, 0, -1]]),
  1747. Matrix([
  1748. [0],
  1749. [1],
  1750. [1]]),
  1751. Matrix([[0, 1, -1]]),
  1752. Matrix([[1]]))
  1753. assert p2.rewrite(TransferFunction) == TransferFunction(s**2, s**2 - 1, s)
  1754. # For MIMO
  1755. a3 = Matrix([[4, 1], [2, -3]])
  1756. b3 = Matrix([[5, 2], [-3, -3]])
  1757. c3 = Matrix([[2, -4], [0, 1]])
  1758. d3 = Matrix([[3, 2], [1, -1]])
  1759. a4 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  1760. b4 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  1761. c4 = Matrix([[4, 2, -3], [1, 4, 3]])
  1762. d4 = Matrix([[-2, 4], [0, 1]])
  1763. ss3 = StateSpace(a3, b3, c3, d3)
  1764. ss4 = StateSpace(a4, b4, c4, d4)
  1765. p3 = MIMOParallel(ss3, ss4)
  1766. assert p3 == MIMOParallel(StateSpace(Matrix([
  1767. [4, 1],
  1768. [2, -3]]), Matrix([
  1769. [ 5, 2],
  1770. [-3, -3]]), Matrix([
  1771. [2, -4],
  1772. [0, 1]]), Matrix([
  1773. [3, 2],
  1774. [1, -1]])), StateSpace(Matrix([
  1775. [-3, 4, 2],
  1776. [-1, -3, 0],
  1777. [ 2, 5, 3]]), Matrix([
  1778. [ 1, 4],
  1779. [-3, -3],
  1780. [-2, 1]]), Matrix([
  1781. [4, 2, -3],
  1782. [1, 4, 3]]), Matrix([
  1783. [-2, 4],
  1784. [ 0, 1]])))
  1785. assert p3.doit() == StateSpace(Matrix([
  1786. [4, 1, 0, 0, 0],
  1787. [2, -3, 0, 0, 0],
  1788. [0, 0, -3, 4, 2],
  1789. [0, 0, -1, -3, 0],
  1790. [0, 0, 2, 5, 3]]),
  1791. Matrix([
  1792. [5, 2],
  1793. [-3, -3],
  1794. [1, 4],
  1795. [-3, -3],
  1796. [-2, 1]]),
  1797. Matrix([
  1798. [2, -4, 4, 2, -3],
  1799. [0, 1, 1, 4, 3]]),
  1800. Matrix([
  1801. [1, 6],
  1802. [1, 0]]))
  1803. # Using StateSpace with MIMOParallel.
  1804. tf2 = TransferFunction(1, s, s)
  1805. tf3 = TransferFunction(1, s + 1, s)
  1806. tf4 = TransferFunction(s, s + 2, s)
  1807. tfm = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  1808. p4 = MIMOParallel(tfm, ss3)
  1809. assert p4 == MIMOParallel(TransferFunctionMatrix((
  1810. (TransferFunction(s, s + 1, s), TransferFunction(1, s, s)),
  1811. (TransferFunction(1, s + 1, s), TransferFunction(s, s + 2, s)))),
  1812. StateSpace(Matrix([
  1813. [4, 1],
  1814. [2, -3]]), Matrix([
  1815. [5, 2],
  1816. [-3, -3]]), Matrix([
  1817. [2, -4],
  1818. [0, 1]]), Matrix([
  1819. [3, 2],
  1820. [1, -1]])))
  1821. def test_StateSpace_feedback():
  1822. # For SISO
  1823. a1 = Matrix([[0, 1], [1, 0]])
  1824. b1 = Matrix([[0], [1]])
  1825. c1 = Matrix([[0, 1]])
  1826. d1 = Matrix([[0]])
  1827. a2 = Matrix([[1, 0], [0, 1]])
  1828. b2 = Matrix([[1], [0]])
  1829. c2 = Matrix([[1, 0]])
  1830. d2 = Matrix([[1]])
  1831. ss1 = StateSpace(a1, b1, c1, d1)
  1832. ss2 = StateSpace(a2, b2, c2, d2)
  1833. fd1 = Feedback(ss1, ss2)
  1834. # Negative feedback
  1835. assert fd1 == Feedback(StateSpace(Matrix([[0, 1], [1, 0]]), Matrix([[0], [1]]), Matrix([[0, 1]]), Matrix([[0]])),
  1836. StateSpace(Matrix([[1, 0],[0, 1]]), Matrix([[1],[0]]), Matrix([[1, 0]]), Matrix([[1]])), -1)
  1837. assert fd1.doit() == StateSpace(Matrix([
  1838. [0, 1, 0, 0],
  1839. [1, -1, -1, 0],
  1840. [0, 1, 1, 0],
  1841. [0, 0, 0, 1]]), Matrix([
  1842. [0],
  1843. [1],
  1844. [0],
  1845. [0]]), Matrix(
  1846. [[0, 1, 0, 0]]), Matrix(
  1847. [[0]]))
  1848. assert fd1.rewrite(TransferFunction) == TransferFunction(s*(s - 1), s**3 - s + 1, s)
  1849. # Positive Feedback
  1850. fd2 = Feedback(ss1, ss2, 1)
  1851. assert fd2.doit() == StateSpace(Matrix([
  1852. [0, 1, 0, 0],
  1853. [1, 1, 1, 0],
  1854. [0, 1, 1, 0],
  1855. [0, 0, 0, 1]]), Matrix([
  1856. [0],
  1857. [1],
  1858. [0],
  1859. [0]]), Matrix(
  1860. [[0, 1, 0, 0]]), Matrix(
  1861. [[0]]))
  1862. assert fd2.rewrite(TransferFunction) == TransferFunction(s*(s - 1), s**3 - 2*s**2 - s + 1, s)
  1863. # Connection with TransferFunction
  1864. tf1 = TransferFunction(s, s+1, s)
  1865. fd3 = Feedback(ss1, tf1)
  1866. assert fd3 == Feedback(StateSpace(Matrix([
  1867. [0, 1],
  1868. [1, 0]]), Matrix([
  1869. [0],
  1870. [1]]), Matrix([[0, 1]]), Matrix([[0]])),
  1871. TransferFunction(s, s + 1, s), -1)
  1872. assert fd3.doit() == StateSpace (Matrix([
  1873. [0, 1, 0],
  1874. [1, -1, 1],
  1875. [0, 1, -1]]), Matrix([
  1876. [0],
  1877. [1],
  1878. [0]]), Matrix(
  1879. [[0, 1, 0]]), Matrix(
  1880. [[0]]))
  1881. # For MIMO
  1882. a3 = Matrix([[4, 1], [2, -3]])
  1883. b3 = Matrix([[5, 2], [-3, -3]])
  1884. c3 = Matrix([[2, -4], [0, 1]])
  1885. d3 = Matrix([[3, 2], [1, -1]])
  1886. a4 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  1887. b4 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  1888. c4 = Matrix([[4, 2, -3], [1, 4, 3]])
  1889. d4 = Matrix([[-2, 4], [0, 1]])
  1890. ss3 = StateSpace(a3, b3, c3, d3)
  1891. ss4 = StateSpace(a4, b4, c4, d4)
  1892. # Negative Feedback
  1893. fd4 = MIMOFeedback(ss3, ss4)
  1894. assert fd4 == MIMOFeedback(StateSpace(Matrix([
  1895. [4, 1],
  1896. [2, -3]]), Matrix([
  1897. [ 5, 2],
  1898. [-3, -3]]), Matrix([
  1899. [2, -4],
  1900. [0, 1]]), Matrix([
  1901. [3, 2],
  1902. [1, -1]])), StateSpace(Matrix([
  1903. [-3, 4, 2],
  1904. [-1, -3, 0],
  1905. [ 2, 5, 3]]), Matrix([
  1906. [ 1, 4],
  1907. [-3, -3],
  1908. [-2, 1]]), Matrix([
  1909. [4, 2, -3],
  1910. [1, 4, 3]]), Matrix([
  1911. [-2, 4],
  1912. [ 0, 1]])), -1)
  1913. assert fd4.doit() == StateSpace(Matrix([
  1914. [Rational(3), Rational(-3, 4), Rational(-15, 4), Rational(-37, 2), Rational(-15)],
  1915. [Rational(7, 2), Rational(-39, 8), Rational(9, 8), Rational(39, 4), Rational(9)],
  1916. [Rational(3), Rational(-41, 4), Rational(-45, 4), Rational(-51, 2), Rational(-19)],
  1917. [Rational(-9, 2), Rational(129, 8), Rational(73, 8), Rational(171, 4), Rational(36)],
  1918. [Rational(-3, 2), Rational(47, 8), Rational(31, 8), Rational(85, 4), Rational(18)]]), Matrix([
  1919. [Rational(-1, 4), Rational(19, 4)],
  1920. [Rational(3, 8), Rational(-21, 8)],
  1921. [Rational(1, 4), Rational(29, 4)],
  1922. [Rational(3, 8), Rational(-93, 8)],
  1923. [Rational(5, 8), Rational(-35, 8)]]), Matrix([
  1924. [Rational(1), Rational(-15, 4), Rational(-7, 4), Rational(-21, 2), Rational(-9)],
  1925. [Rational(1, 2), Rational(-13, 8), Rational(-13, 8), Rational(-19, 4), Rational(-3)]]), Matrix([
  1926. [Rational(-1, 4), Rational(11, 4)],
  1927. [Rational(1, 8), Rational(9, 8)]]))
  1928. # Positive Feedback
  1929. fd5 = MIMOFeedback(ss3, ss4, 1)
  1930. assert fd5.doit() == StateSpace(Matrix([
  1931. [Rational(4, 7), Rational(62, 7), Rational(1), Rational(-8), Rational(-69, 7)],
  1932. [Rational(32, 7), Rational(-135, 14), Rational(-3, 2), Rational(3), Rational(36, 7)],
  1933. [Rational(-10, 7), Rational(41, 7), Rational(-4), Rational(-12), Rational(-97, 7)],
  1934. [Rational(12, 7), Rational(-111, 14), Rational(-5, 2), Rational(18), Rational(171, 7)],
  1935. [Rational(2, 7), Rational(-29, 14), Rational(-1, 2), Rational(10), Rational(81, 7)]]), Matrix([
  1936. [Rational(6, 7), Rational(-17, 7)],
  1937. [Rational(-9, 14), Rational(15, 14)],
  1938. [Rational(6, 7), Rational(-31, 7)],
  1939. [Rational(-27, 14), Rational(87, 14)],
  1940. [Rational(-15, 14), Rational(25, 14)]]), Matrix([
  1941. [Rational(-2, 7), Rational(11, 7), Rational(1), Rational(-4), Rational(-39, 7)],
  1942. [Rational(-2, 7), Rational(15, 14), Rational(-1, 2), Rational(-3), Rational(-18, 7)]]), Matrix([
  1943. [Rational(4, 7), Rational(-9, 7)],
  1944. [Rational(1, 14), Rational(-11, 14)]]))