lti.py 112 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036
  1. from typing import Type
  2. from sympy.core.add import Add
  3. from sympy.core.basic import Basic
  4. from sympy.core.containers import Tuple
  5. from sympy.core.evalf import EvalfMixin
  6. from sympy.core.expr import Expr
  7. from sympy.core.function import expand
  8. from sympy.core.logic import fuzzy_and
  9. from sympy.core.mul import Mul
  10. from sympy.core.power import Pow
  11. from sympy.core.singleton import S
  12. from sympy.core.symbol import Dummy, Symbol
  13. from sympy.core.sympify import sympify, _sympify
  14. from sympy.matrices import ImmutableMatrix, eye
  15. from sympy.matrices.expressions import MatMul, MatAdd
  16. from sympy.polys import Poly, rootof
  17. from sympy.polys.polyroots import roots
  18. from sympy.polys.polytools import (cancel, degree)
  19. from sympy.series import limit
  20. from mpmath.libmp.libmpf import prec_to_dps
  21. __all__ = ['TransferFunction', 'Series', 'MIMOSeries', 'Parallel', 'MIMOParallel',
  22. 'Feedback', 'MIMOFeedback', 'TransferFunctionMatrix', 'bilinear', 'backward_diff']
  23. def _roots(poly, var):
  24. """ like roots, but works on higher-order polynomials. """
  25. r = roots(poly, var, multiple=True)
  26. n = degree(poly)
  27. if len(r) != n:
  28. r = [rootof(poly, var, k) for k in range(n)]
  29. return r
  30. def bilinear(tf, sample_per):
  31. """
  32. Returns falling coefficients of H(z) from numerator and denominator.
  33. Where H(z) is the corresponding discretized transfer function,
  34. discretized with the bilinear transform method.
  35. H(z) is obtained from the continuous transfer function H(s)
  36. by substituting s(z) = 2/T * (z-1)/(z+1) into H(s), where T is the
  37. sample period.
  38. Coefficients are falling, i.e. H(z) = (az+b)/(cz+d) is returned
  39. as [a, b], [c, d].
  40. Examples
  41. ========
  42. >>> from sympy.physics.control.lti import TransferFunction, bilinear
  43. >>> from sympy.abc import s, L, R, T
  44. >>> tf = TransferFunction(1, s*L + R, s)
  45. >>> numZ, denZ = bilinear(tf, T)
  46. >>> numZ
  47. [T, T]
  48. >>> denZ
  49. [2*L + R*T, -2*L + R*T]
  50. """
  51. T = sample_per # and sample period T
  52. s = tf.var
  53. z = s # dummy discrete variable z
  54. np = tf.num.as_poly(s).all_coeffs()
  55. dp = tf.den.as_poly(s).all_coeffs()
  56. # The next line results from multiplying H(z) with (z+1)^N/(z+1)^N
  57. N = max(len(np), len(dp)) - 1
  58. num = Add(*[ T**(N-i)*2**i*c*(z-1)**i*(z+1)**(N-i) for c, i in zip(np[::-1], range(len(np))) ])
  59. den = Add(*[ T**(N-i)*2**i*c*(z-1)**i*(z+1)**(N-i) for c, i in zip(dp[::-1], range(len(dp))) ])
  60. num_coefs = num.as_poly(z).all_coeffs()
  61. den_coefs = den.as_poly(z).all_coeffs()
  62. return num_coefs, den_coefs
  63. def backward_diff(tf, sample_per):
  64. """
  65. Returns falling coefficients of H(z) from numerator and denominator.
  66. Where H(z) is the corresponding discretized transfer function,
  67. discretized with the backward difference transform method.
  68. H(z) is obtained from the continuous transfer function H(s)
  69. by substituting s(z) = (z-1)/(T*z) into H(s), where T is the
  70. sample period.
  71. Coefficients are falling, i.e. H(z) = (az+b)/(cz+d) is returned
  72. as [a, b], [c, d].
  73. Examples
  74. ========
  75. >>> from sympy.physics.control.lti import TransferFunction, backward_diff
  76. >>> from sympy.abc import s, L, R, T
  77. >>> tf = TransferFunction(1, s*L + R, s)
  78. >>> numZ, denZ = backward_diff(tf, T)
  79. >>> numZ
  80. [T, 0]
  81. >>> denZ
  82. [L + R*T, -L]
  83. """
  84. T = sample_per # and sample period T
  85. s = tf.var
  86. z = s # dummy discrete variable z
  87. np = tf.num.as_poly(s).all_coeffs()
  88. dp = tf.den.as_poly(s).all_coeffs()
  89. # The next line results from multiplying H(z) with z^N/z^N
  90. N = max(len(np), len(dp)) - 1
  91. num = Add(*[ T**(N-i)*c*(z-1)**i*(z)**(N-i) for c, i in zip(np[::-1], range(len(np))) ])
  92. den = Add(*[ T**(N-i)*c*(z-1)**i*(z)**(N-i) for c, i in zip(dp[::-1], range(len(dp))) ])
  93. num_coefs = num.as_poly(z).all_coeffs()
  94. den_coefs = den.as_poly(z).all_coeffs()
  95. return num_coefs, den_coefs
  96. class LinearTimeInvariant(Basic, EvalfMixin):
  97. """A common class for all the Linear Time-Invariant Dynamical Systems."""
  98. _clstype: Type
  99. # Users should not directly interact with this class.
  100. def __new__(cls, *system, **kwargs):
  101. if cls is LinearTimeInvariant:
  102. raise NotImplementedError('The LTICommon class is not meant to be used directly.')
  103. return super(LinearTimeInvariant, cls).__new__(cls, *system, **kwargs)
  104. @classmethod
  105. def _check_args(cls, args):
  106. if not args:
  107. raise ValueError("Atleast 1 argument must be passed.")
  108. if not all(isinstance(arg, cls._clstype) for arg in args):
  109. raise TypeError(f"All arguments must be of type {cls._clstype}.")
  110. var_set = {arg.var for arg in args}
  111. if len(var_set) != 1:
  112. raise ValueError("All transfer functions should use the same complex variable"
  113. f" of the Laplace transform. {len(var_set)} different values found.")
  114. @property
  115. def is_SISO(self):
  116. """Returns `True` if the passed LTI system is SISO else returns False."""
  117. return self._is_SISO
  118. class SISOLinearTimeInvariant(LinearTimeInvariant):
  119. """A common class for all the SISO Linear Time-Invariant Dynamical Systems."""
  120. # Users should not directly interact with this class.
  121. _is_SISO = True
  122. class MIMOLinearTimeInvariant(LinearTimeInvariant):
  123. """A common class for all the MIMO Linear Time-Invariant Dynamical Systems."""
  124. # Users should not directly interact with this class.
  125. _is_SISO = False
  126. SISOLinearTimeInvariant._clstype = SISOLinearTimeInvariant
  127. MIMOLinearTimeInvariant._clstype = MIMOLinearTimeInvariant
  128. def _check_other_SISO(func):
  129. def wrapper(*args, **kwargs):
  130. if not isinstance(args[-1], SISOLinearTimeInvariant):
  131. return NotImplemented
  132. else:
  133. return func(*args, **kwargs)
  134. return wrapper
  135. def _check_other_MIMO(func):
  136. def wrapper(*args, **kwargs):
  137. if not isinstance(args[-1], MIMOLinearTimeInvariant):
  138. return NotImplemented
  139. else:
  140. return func(*args, **kwargs)
  141. return wrapper
  142. class TransferFunction(SISOLinearTimeInvariant):
  143. r"""
  144. A class for representing LTI (Linear, time-invariant) systems that can be strictly described
  145. by ratio of polynomials in the Laplace transform complex variable. The arguments
  146. are ``num``, ``den``, and ``var``, where ``num`` and ``den`` are numerator and
  147. denominator polynomials of the ``TransferFunction`` respectively, and the third argument is
  148. a complex variable of the Laplace transform used by these polynomials of the transfer function.
  149. ``num`` and ``den`` can be either polynomials or numbers, whereas ``var``
  150. has to be a :py:class:`~.Symbol`.
  151. Explanation
  152. ===========
  153. Generally, a dynamical system representing a physical model can be described in terms of Linear
  154. Ordinary Differential Equations like -
  155. $\small{b_{m}y^{\left(m\right)}+b_{m-1}y^{\left(m-1\right)}+\dots+b_{1}y^{\left(1\right)}+b_{0}y=
  156. a_{n}x^{\left(n\right)}+a_{n-1}x^{\left(n-1\right)}+\dots+a_{1}x^{\left(1\right)}+a_{0}x}$
  157. Here, $x$ is the input signal and $y$ is the output signal and superscript on both is the order of derivative
  158. (not exponent). Derivative is taken with respect to the independent variable, $t$. Also, generally $m$ is greater
  159. than $n$.
  160. It is not feasible to analyse the properties of such systems in their native form therefore, we use
  161. mathematical tools like Laplace transform to get a better perspective. Taking the Laplace transform
  162. of both the sides in the equation (at zero initial conditions), we get -
  163. $\small{\mathcal{L}[b_{m}y^{\left(m\right)}+b_{m-1}y^{\left(m-1\right)}+\dots+b_{1}y^{\left(1\right)}+b_{0}y]=
  164. \mathcal{L}[a_{n}x^{\left(n\right)}+a_{n-1}x^{\left(n-1\right)}+\dots+a_{1}x^{\left(1\right)}+a_{0}x]}$
  165. Using the linearity property of Laplace transform and also considering zero initial conditions
  166. (i.e. $\small{y(0^{-}) = 0}$, $\small{y'(0^{-}) = 0}$ and so on), the equation
  167. above gets translated to -
  168. $\small{b_{m}\mathcal{L}[y^{\left(m\right)}]+\dots+b_{1}\mathcal{L}[y^{\left(1\right)}]+b_{0}\mathcal{L}[y]=
  169. a_{n}\mathcal{L}[x^{\left(n\right)}]+\dots+a_{1}\mathcal{L}[x^{\left(1\right)}]+a_{0}\mathcal{L}[x]}$
  170. Now, applying Derivative property of Laplace transform,
  171. $\small{b_{m}s^{m}\mathcal{L}[y]+\dots+b_{1}s\mathcal{L}[y]+b_{0}\mathcal{L}[y]=
  172. a_{n}s^{n}\mathcal{L}[x]+\dots+a_{1}s\mathcal{L}[x]+a_{0}\mathcal{L}[x]}$
  173. Here, the superscript on $s$ is **exponent**. Note that the zero initial conditions assumption, mentioned above, is very important
  174. and cannot be ignored otherwise the dynamical system cannot be considered time-independent and the simplified equation above
  175. cannot be reached.
  176. Collecting $\mathcal{L}[y]$ and $\mathcal{L}[x]$ terms from both the sides and taking the ratio
  177. $\frac{ \mathcal{L}\left\{y\right\} }{ \mathcal{L}\left\{x\right\} }$, we get the typical rational form of transfer
  178. function.
  179. The numerator of the transfer function is, therefore, the Laplace transform of the output signal
  180. (The signals are represented as functions of time) and similarly, the denominator
  181. of the transfer function is the Laplace transform of the input signal. It is also a convention
  182. to denote the input and output signal's Laplace transform with capital alphabets like shown below.
  183. $H(s) = \frac{Y(s)}{X(s)} = \frac{ \mathcal{L}\left\{y(t)\right\} }{ \mathcal{L}\left\{x(t)\right\} }$
  184. $s$, also known as complex frequency, is a complex variable in the Laplace domain. It corresponds to the
  185. equivalent variable $t$, in the time domain. Transfer functions are sometimes also referred to as the Laplace
  186. transform of the system's impulse response. Transfer function, $H$, is represented as a rational
  187. function in $s$ like,
  188. $H(s) =\ \frac{a_{n}s^{n}+a_{n-1}s^{n-1}+\dots+a_{1}s+a_{0}}{b_{m}s^{m}+b_{m-1}s^{m-1}+\dots+b_{1}s+b_{0}}$
  189. Parameters
  190. ==========
  191. num : Expr, Number
  192. The numerator polynomial of the transfer function.
  193. den : Expr, Number
  194. The denominator polynomial of the transfer function.
  195. var : Symbol
  196. Complex variable of the Laplace transform used by the
  197. polynomials of the transfer function.
  198. Raises
  199. ======
  200. TypeError
  201. When ``var`` is not a Symbol or when ``num`` or ``den`` is not a
  202. number or a polynomial.
  203. ValueError
  204. When ``den`` is zero.
  205. Examples
  206. ========
  207. >>> from sympy.abc import s, p, a
  208. >>> from sympy.physics.control.lti import TransferFunction
  209. >>> tf1 = TransferFunction(s + a, s**2 + s + 1, s)
  210. >>> tf1
  211. TransferFunction(a + s, s**2 + s + 1, s)
  212. >>> tf1.num
  213. a + s
  214. >>> tf1.den
  215. s**2 + s + 1
  216. >>> tf1.var
  217. s
  218. >>> tf1.args
  219. (a + s, s**2 + s + 1, s)
  220. Any complex variable can be used for ``var``.
  221. >>> tf2 = TransferFunction(a*p**3 - a*p**2 + s*p, p + a**2, p)
  222. >>> tf2
  223. TransferFunction(a*p**3 - a*p**2 + p*s, a**2 + p, p)
  224. >>> tf3 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  225. >>> tf3
  226. TransferFunction((p - 1)*(p + 3), (p - 1)*(p + 5), p)
  227. To negate a transfer function the ``-`` operator can be prepended:
  228. >>> tf4 = TransferFunction(-a + s, p**2 + s, p)
  229. >>> -tf4
  230. TransferFunction(a - s, p**2 + s, p)
  231. >>> tf5 = TransferFunction(s**4 - 2*s**3 + 5*s + 4, s + 4, s)
  232. >>> -tf5
  233. TransferFunction(-s**4 + 2*s**3 - 5*s - 4, s + 4, s)
  234. You can use a float or an integer (or other constants) as numerator and denominator:
  235. >>> tf6 = TransferFunction(1/2, 4, s)
  236. >>> tf6.num
  237. 0.500000000000000
  238. >>> tf6.den
  239. 4
  240. >>> tf6.var
  241. s
  242. >>> tf6.args
  243. (0.5, 4, s)
  244. You can take the integer power of a transfer function using the ``**`` operator:
  245. >>> tf7 = TransferFunction(s + a, s - a, s)
  246. >>> tf7**3
  247. TransferFunction((a + s)**3, (-a + s)**3, s)
  248. >>> tf7**0
  249. TransferFunction(1, 1, s)
  250. >>> tf8 = TransferFunction(p + 4, p - 3, p)
  251. >>> tf8**-1
  252. TransferFunction(p - 3, p + 4, p)
  253. Addition, subtraction, and multiplication of transfer functions can form
  254. unevaluated ``Series`` or ``Parallel`` objects.
  255. >>> tf9 = TransferFunction(s + 1, s**2 + s + 1, s)
  256. >>> tf10 = TransferFunction(s - p, s + 3, s)
  257. >>> tf11 = TransferFunction(4*s**2 + 2*s - 4, s - 1, s)
  258. >>> tf12 = TransferFunction(1 - s, s**2 + 4, s)
  259. >>> tf9 + tf10
  260. Parallel(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(-p + s, s + 3, s))
  261. >>> tf10 - tf11
  262. Parallel(TransferFunction(-p + s, s + 3, s), TransferFunction(-4*s**2 - 2*s + 4, s - 1, s))
  263. >>> tf9 * tf10
  264. Series(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(-p + s, s + 3, s))
  265. >>> tf10 - (tf9 + tf12)
  266. Parallel(TransferFunction(-p + s, s + 3, s), TransferFunction(-s - 1, s**2 + s + 1, s), TransferFunction(s - 1, s**2 + 4, s))
  267. >>> tf10 - (tf9 * tf12)
  268. Parallel(TransferFunction(-p + s, s + 3, s), Series(TransferFunction(-1, 1, s), TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(1 - s, s**2 + 4, s)))
  269. >>> tf11 * tf10 * tf9
  270. Series(TransferFunction(4*s**2 + 2*s - 4, s - 1, s), TransferFunction(-p + s, s + 3, s), TransferFunction(s + 1, s**2 + s + 1, s))
  271. >>> tf9 * tf11 + tf10 * tf12
  272. Parallel(Series(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(4*s**2 + 2*s - 4, s - 1, s)), Series(TransferFunction(-p + s, s + 3, s), TransferFunction(1 - s, s**2 + 4, s)))
  273. >>> (tf9 + tf12) * (tf10 + tf11)
  274. Series(Parallel(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(1 - s, s**2 + 4, s)), Parallel(TransferFunction(-p + s, s + 3, s), TransferFunction(4*s**2 + 2*s - 4, s - 1, s)))
  275. These unevaluated ``Series`` or ``Parallel`` objects can convert into the
  276. resultant transfer function using ``.doit()`` method or by ``.rewrite(TransferFunction)``.
  277. >>> ((tf9 + tf10) * tf12).doit()
  278. TransferFunction((1 - s)*((-p + s)*(s**2 + s + 1) + (s + 1)*(s + 3)), (s + 3)*(s**2 + 4)*(s**2 + s + 1), s)
  279. >>> (tf9 * tf10 - tf11 * tf12).rewrite(TransferFunction)
  280. TransferFunction(-(1 - s)*(s + 3)*(s**2 + s + 1)*(4*s**2 + 2*s - 4) + (-p + s)*(s - 1)*(s + 1)*(s**2 + 4), (s - 1)*(s + 3)*(s**2 + 4)*(s**2 + s + 1), s)
  281. See Also
  282. ========
  283. Feedback, Series, Parallel
  284. References
  285. ==========
  286. .. [1] https://en.wikipedia.org/wiki/Transfer_function
  287. .. [2] https://en.wikipedia.org/wiki/Laplace_transform
  288. """
  289. def __new__(cls, num, den, var):
  290. num, den = _sympify(num), _sympify(den)
  291. if not isinstance(var, Symbol):
  292. raise TypeError("Variable input must be a Symbol.")
  293. if den == 0:
  294. raise ValueError("TransferFunction cannot have a zero denominator.")
  295. if (((isinstance(num, Expr) and num.has(Symbol)) or num.is_number) and
  296. ((isinstance(den, Expr) and den.has(Symbol)) or den.is_number)):
  297. obj = super(TransferFunction, cls).__new__(cls, num, den, var)
  298. obj._num = num
  299. obj._den = den
  300. obj._var = var
  301. return obj
  302. else:
  303. raise TypeError("Unsupported type for numerator or denominator of TransferFunction.")
  304. @classmethod
  305. def from_rational_expression(cls, expr, var=None):
  306. r"""
  307. Creates a new ``TransferFunction`` efficiently from a rational expression.
  308. Parameters
  309. ==========
  310. expr : Expr, Number
  311. The rational expression representing the ``TransferFunction``.
  312. var : Symbol, optional
  313. Complex variable of the Laplace transform used by the
  314. polynomials of the transfer function.
  315. Raises
  316. ======
  317. ValueError
  318. When ``expr`` is of type ``Number`` and optional parameter ``var``
  319. is not passed.
  320. When ``expr`` has more than one variables and an optional parameter
  321. ``var`` is not passed.
  322. ZeroDivisionError
  323. When denominator of ``expr`` is zero or it has ``ComplexInfinity``
  324. in its numerator.
  325. Examples
  326. ========
  327. >>> from sympy.abc import s, p, a
  328. >>> from sympy.physics.control.lti import TransferFunction
  329. >>> expr1 = (s + 5)/(3*s**2 + 2*s + 1)
  330. >>> tf1 = TransferFunction.from_rational_expression(expr1)
  331. >>> tf1
  332. TransferFunction(s + 5, 3*s**2 + 2*s + 1, s)
  333. >>> expr2 = (a*p**3 - a*p**2 + s*p)/(p + a**2) # Expr with more than one variables
  334. >>> tf2 = TransferFunction.from_rational_expression(expr2, p)
  335. >>> tf2
  336. TransferFunction(a*p**3 - a*p**2 + p*s, a**2 + p, p)
  337. In case of conflict between two or more variables in a expression, SymPy will
  338. raise a ``ValueError``, if ``var`` is not passed by the user.
  339. >>> tf = TransferFunction.from_rational_expression((a + a*s)/(s**2 + s + 1))
  340. Traceback (most recent call last):
  341. ...
  342. ValueError: Conflicting values found for positional argument `var` ({a, s}). Specify it manually.
  343. This can be corrected by specifying the ``var`` parameter manually.
  344. >>> tf = TransferFunction.from_rational_expression((a + a*s)/(s**2 + s + 1), s)
  345. >>> tf
  346. TransferFunction(a*s + a, s**2 + s + 1, s)
  347. ``var`` also need to be specified when ``expr`` is a ``Number``
  348. >>> tf3 = TransferFunction.from_rational_expression(10, s)
  349. >>> tf3
  350. TransferFunction(10, 1, s)
  351. """
  352. expr = _sympify(expr)
  353. if var is None:
  354. _free_symbols = expr.free_symbols
  355. _len_free_symbols = len(_free_symbols)
  356. if _len_free_symbols == 1:
  357. var = list(_free_symbols)[0]
  358. elif _len_free_symbols == 0:
  359. raise ValueError("Positional argument `var` not found in the TransferFunction defined. Specify it manually.")
  360. else:
  361. raise ValueError("Conflicting values found for positional argument `var` ({}). Specify it manually.".format(_free_symbols))
  362. _num, _den = expr.as_numer_denom()
  363. if _den == 0 or _num.has(S.ComplexInfinity):
  364. raise ZeroDivisionError("TransferFunction cannot have a zero denominator.")
  365. return cls(_num, _den, var)
  366. @property
  367. def num(self):
  368. """
  369. Returns the numerator polynomial of the transfer function.
  370. Examples
  371. ========
  372. >>> from sympy.abc import s, p
  373. >>> from sympy.physics.control.lti import TransferFunction
  374. >>> G1 = TransferFunction(s**2 + p*s + 3, s - 4, s)
  375. >>> G1.num
  376. p*s + s**2 + 3
  377. >>> G2 = TransferFunction((p + 5)*(p - 3), (p - 3)*(p + 1), p)
  378. >>> G2.num
  379. (p - 3)*(p + 5)
  380. """
  381. return self._num
  382. @property
  383. def den(self):
  384. """
  385. Returns the denominator polynomial of the transfer function.
  386. Examples
  387. ========
  388. >>> from sympy.abc import s, p
  389. >>> from sympy.physics.control.lti import TransferFunction
  390. >>> G1 = TransferFunction(s + 4, p**3 - 2*p + 4, s)
  391. >>> G1.den
  392. p**3 - 2*p + 4
  393. >>> G2 = TransferFunction(3, 4, s)
  394. >>> G2.den
  395. 4
  396. """
  397. return self._den
  398. @property
  399. def var(self):
  400. """
  401. Returns the complex variable of the Laplace transform used by the polynomials of
  402. the transfer function.
  403. Examples
  404. ========
  405. >>> from sympy.abc import s, p
  406. >>> from sympy.physics.control.lti import TransferFunction
  407. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  408. >>> G1.var
  409. p
  410. >>> G2 = TransferFunction(0, s - 5, s)
  411. >>> G2.var
  412. s
  413. """
  414. return self._var
  415. def _eval_subs(self, old, new):
  416. arg_num = self.num.subs(old, new)
  417. arg_den = self.den.subs(old, new)
  418. argnew = TransferFunction(arg_num, arg_den, self.var)
  419. return self if old == self.var else argnew
  420. def _eval_evalf(self, prec):
  421. return TransferFunction(
  422. self.num._eval_evalf(prec),
  423. self.den._eval_evalf(prec),
  424. self.var)
  425. def _eval_simplify(self, **kwargs):
  426. tf = cancel(Mul(self.num, 1/self.den, evaluate=False), expand=False).as_numer_denom()
  427. num_, den_ = tf[0], tf[1]
  428. return TransferFunction(num_, den_, self.var)
  429. def expand(self):
  430. """
  431. Returns the transfer function with numerator and denominator
  432. in expanded form.
  433. Examples
  434. ========
  435. >>> from sympy.abc import s, p, a, b
  436. >>> from sympy.physics.control.lti import TransferFunction
  437. >>> G1 = TransferFunction((a - s)**2, (s**2 + a)**2, s)
  438. >>> G1.expand()
  439. TransferFunction(a**2 - 2*a*s + s**2, a**2 + 2*a*s**2 + s**4, s)
  440. >>> G2 = TransferFunction((p + 3*b)*(p - b), (p - b)*(p + 2*b), p)
  441. >>> G2.expand()
  442. TransferFunction(-3*b**2 + 2*b*p + p**2, -2*b**2 + b*p + p**2, p)
  443. """
  444. return TransferFunction(expand(self.num), expand(self.den), self.var)
  445. def dc_gain(self):
  446. """
  447. Computes the gain of the response as the frequency approaches zero.
  448. The DC gain is infinite for systems with pure integrators.
  449. Examples
  450. ========
  451. >>> from sympy.abc import s, p, a, b
  452. >>> from sympy.physics.control.lti import TransferFunction
  453. >>> tf1 = TransferFunction(s + 3, s**2 - 9, s)
  454. >>> tf1.dc_gain()
  455. -1/3
  456. >>> tf2 = TransferFunction(p**2, p - 3 + p**3, p)
  457. >>> tf2.dc_gain()
  458. 0
  459. >>> tf3 = TransferFunction(a*p**2 - b, s + b, s)
  460. >>> tf3.dc_gain()
  461. (a*p**2 - b)/b
  462. >>> tf4 = TransferFunction(1, s, s)
  463. >>> tf4.dc_gain()
  464. oo
  465. """
  466. m = Mul(self.num, Pow(self.den, -1, evaluate=False), evaluate=False)
  467. return limit(m, self.var, 0)
  468. def poles(self):
  469. """
  470. Returns the poles of a transfer function.
  471. Examples
  472. ========
  473. >>> from sympy.abc import s, p, a
  474. >>> from sympy.physics.control.lti import TransferFunction
  475. >>> tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  476. >>> tf1.poles()
  477. [-5, 1]
  478. >>> tf2 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  479. >>> tf2.poles()
  480. [I, I, -I, -I]
  481. >>> tf3 = TransferFunction(s**2, a*s + p, s)
  482. >>> tf3.poles()
  483. [-p/a]
  484. """
  485. return _roots(Poly(self.den, self.var), self.var)
  486. def zeros(self):
  487. """
  488. Returns the zeros of a transfer function.
  489. Examples
  490. ========
  491. >>> from sympy.abc import s, p, a
  492. >>> from sympy.physics.control.lti import TransferFunction
  493. >>> tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  494. >>> tf1.zeros()
  495. [-3, 1]
  496. >>> tf2 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  497. >>> tf2.zeros()
  498. [1, 1]
  499. >>> tf3 = TransferFunction(s**2, a*s + p, s)
  500. >>> tf3.zeros()
  501. [0, 0]
  502. """
  503. return _roots(Poly(self.num, self.var), self.var)
  504. def is_stable(self):
  505. """
  506. Returns True if the transfer function is asymptotically stable; else False.
  507. This would not check the marginal or conditional stability of the system.
  508. Examples
  509. ========
  510. >>> from sympy.abc import s, p, a
  511. >>> from sympy import symbols
  512. >>> from sympy.physics.control.lti import TransferFunction
  513. >>> q, r = symbols('q, r', negative=True)
  514. >>> tf1 = TransferFunction((1 - s)**2, (s + 1)**2, s)
  515. >>> tf1.is_stable()
  516. True
  517. >>> tf2 = TransferFunction((1 - p)**2, (s**2 + 1)**2, s)
  518. >>> tf2.is_stable()
  519. False
  520. >>> tf3 = TransferFunction(4, q*s - r, s)
  521. >>> tf3.is_stable()
  522. False
  523. >>> tf4 = TransferFunction(p + 1, a*p - s**2, p)
  524. >>> tf4.is_stable() is None # Not enough info about the symbols to determine stability
  525. True
  526. """
  527. return fuzzy_and(pole.as_real_imag()[0].is_negative for pole in self.poles())
  528. def __add__(self, other):
  529. if isinstance(other, (TransferFunction, Series)):
  530. if not self.var == other.var:
  531. raise ValueError("All the transfer functions should use the same complex variable "
  532. "of the Laplace transform.")
  533. return Parallel(self, other)
  534. elif isinstance(other, Parallel):
  535. if not self.var == other.var:
  536. raise ValueError("All the transfer functions should use the same complex variable "
  537. "of the Laplace transform.")
  538. arg_list = list(other.args)
  539. return Parallel(self, *arg_list)
  540. else:
  541. raise ValueError("TransferFunction cannot be added with {}.".
  542. format(type(other)))
  543. def __radd__(self, other):
  544. return self + other
  545. def __sub__(self, other):
  546. if isinstance(other, (TransferFunction, Series)):
  547. if not self.var == other.var:
  548. raise ValueError("All the transfer functions should use the same complex variable "
  549. "of the Laplace transform.")
  550. return Parallel(self, -other)
  551. elif isinstance(other, Parallel):
  552. if not self.var == other.var:
  553. raise ValueError("All the transfer functions should use the same complex variable "
  554. "of the Laplace transform.")
  555. arg_list = [-i for i in list(other.args)]
  556. return Parallel(self, *arg_list)
  557. else:
  558. raise ValueError("{} cannot be subtracted from a TransferFunction."
  559. .format(type(other)))
  560. def __rsub__(self, other):
  561. return -self + other
  562. def __mul__(self, other):
  563. if isinstance(other, (TransferFunction, Parallel)):
  564. if not self.var == other.var:
  565. raise ValueError("All the transfer functions should use the same complex variable "
  566. "of the Laplace transform.")
  567. return Series(self, other)
  568. elif isinstance(other, Series):
  569. if not self.var == other.var:
  570. raise ValueError("All the transfer functions should use the same complex variable "
  571. "of the Laplace transform.")
  572. arg_list = list(other.args)
  573. return Series(self, *arg_list)
  574. else:
  575. raise ValueError("TransferFunction cannot be multiplied with {}."
  576. .format(type(other)))
  577. __rmul__ = __mul__
  578. def __truediv__(self, other):
  579. if (isinstance(other, Parallel) and len(other.args) == 2 and isinstance(other.args[0], TransferFunction)
  580. and isinstance(other.args[1], (Series, TransferFunction))):
  581. if not self.var == other.var:
  582. raise ValueError("Both TransferFunction and Parallel should use the"
  583. " same complex variable of the Laplace transform.")
  584. if other.args[1] == self:
  585. # plant and controller with unit feedback.
  586. return Feedback(self, other.args[0])
  587. other_arg_list = list(other.args[1].args) if isinstance(other.args[1], Series) else other.args[1]
  588. if other_arg_list == other.args[1]:
  589. return Feedback(self, other_arg_list)
  590. elif self in other_arg_list:
  591. other_arg_list.remove(self)
  592. else:
  593. return Feedback(self, Series(*other_arg_list))
  594. if len(other_arg_list) == 1:
  595. return Feedback(self, *other_arg_list)
  596. else:
  597. return Feedback(self, Series(*other_arg_list))
  598. else:
  599. raise ValueError("TransferFunction cannot be divided by {}.".
  600. format(type(other)))
  601. __rtruediv__ = __truediv__
  602. def __pow__(self, p):
  603. p = sympify(p)
  604. if not p.is_Integer:
  605. raise ValueError("Exponent must be an integer.")
  606. if p is S.Zero:
  607. return TransferFunction(1, 1, self.var)
  608. elif p > 0:
  609. num_, den_ = self.num**p, self.den**p
  610. else:
  611. p = abs(p)
  612. num_, den_ = self.den**p, self.num**p
  613. return TransferFunction(num_, den_, self.var)
  614. def __neg__(self):
  615. return TransferFunction(-self.num, self.den, self.var)
  616. @property
  617. def is_proper(self):
  618. """
  619. Returns True if degree of the numerator polynomial is less than
  620. or equal to degree of the denominator polynomial, else False.
  621. Examples
  622. ========
  623. >>> from sympy.abc import s, p, a, b
  624. >>> from sympy.physics.control.lti import TransferFunction
  625. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  626. >>> tf1.is_proper
  627. False
  628. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*p + 2, p)
  629. >>> tf2.is_proper
  630. True
  631. """
  632. return degree(self.num, self.var) <= degree(self.den, self.var)
  633. @property
  634. def is_strictly_proper(self):
  635. """
  636. Returns True if degree of the numerator polynomial is strictly less
  637. than degree of the denominator polynomial, else False.
  638. Examples
  639. ========
  640. >>> from sympy.abc import s, p, a, b
  641. >>> from sympy.physics.control.lti import TransferFunction
  642. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  643. >>> tf1.is_strictly_proper
  644. False
  645. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  646. >>> tf2.is_strictly_proper
  647. True
  648. """
  649. return degree(self.num, self.var) < degree(self.den, self.var)
  650. @property
  651. def is_biproper(self):
  652. """
  653. Returns True if degree of the numerator polynomial is equal to
  654. degree of the denominator polynomial, else False.
  655. Examples
  656. ========
  657. >>> from sympy.abc import s, p, a, b
  658. >>> from sympy.physics.control.lti import TransferFunction
  659. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  660. >>> tf1.is_biproper
  661. True
  662. >>> tf2 = TransferFunction(p**2, p + a, p)
  663. >>> tf2.is_biproper
  664. False
  665. """
  666. return degree(self.num, self.var) == degree(self.den, self.var)
  667. def to_expr(self):
  668. """
  669. Converts a ``TransferFunction`` object to SymPy Expr.
  670. Examples
  671. ========
  672. >>> from sympy.abc import s, p, a, b
  673. >>> from sympy.physics.control.lti import TransferFunction
  674. >>> from sympy import Expr
  675. >>> tf1 = TransferFunction(s, a*s**2 + 1, s)
  676. >>> tf1.to_expr()
  677. s/(a*s**2 + 1)
  678. >>> isinstance(_, Expr)
  679. True
  680. >>> tf2 = TransferFunction(1, (p + 3*b)*(b - p), p)
  681. >>> tf2.to_expr()
  682. 1/((b - p)*(3*b + p))
  683. >>> tf3 = TransferFunction((s - 2)*(s - 3), (s - 1)*(s - 2)*(s - 3), s)
  684. >>> tf3.to_expr()
  685. ((s - 3)*(s - 2))/(((s - 3)*(s - 2)*(s - 1)))
  686. """
  687. if self.num != 1:
  688. return Mul(self.num, Pow(self.den, -1, evaluate=False), evaluate=False)
  689. else:
  690. return Pow(self.den, -1, evaluate=False)
  691. def _flatten_args(args, _cls):
  692. temp_args = []
  693. for arg in args:
  694. if isinstance(arg, _cls):
  695. temp_args.extend(arg.args)
  696. else:
  697. temp_args.append(arg)
  698. return tuple(temp_args)
  699. def _dummify_args(_arg, var):
  700. dummy_dict = {}
  701. dummy_arg_list = []
  702. for arg in _arg:
  703. _s = Dummy()
  704. dummy_dict[_s] = var
  705. dummy_arg = arg.subs({var: _s})
  706. dummy_arg_list.append(dummy_arg)
  707. return dummy_arg_list, dummy_dict
  708. class Series(SISOLinearTimeInvariant):
  709. r"""
  710. A class for representing a series configuration of SISO systems.
  711. Parameters
  712. ==========
  713. args : SISOLinearTimeInvariant
  714. SISO systems in a series configuration.
  715. evaluate : Boolean, Keyword
  716. When passed ``True``, returns the equivalent
  717. ``Series(*args).doit()``. Set to ``False`` by default.
  718. Raises
  719. ======
  720. ValueError
  721. When no argument is passed.
  722. ``var`` attribute is not same for every system.
  723. TypeError
  724. Any of the passed ``*args`` has unsupported type
  725. A combination of SISO and MIMO systems is
  726. passed. There should be homogeneity in the
  727. type of systems passed, SISO in this case.
  728. Examples
  729. ========
  730. >>> from sympy.abc import s, p, a, b
  731. >>> from sympy.physics.control.lti import TransferFunction, Series, Parallel
  732. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  733. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  734. >>> tf3 = TransferFunction(p**2, p + s, s)
  735. >>> S1 = Series(tf1, tf2)
  736. >>> S1
  737. Series(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s))
  738. >>> S1.var
  739. s
  740. >>> S2 = Series(tf2, Parallel(tf3, -tf1))
  741. >>> S2
  742. Series(TransferFunction(s**3 - 2, s**4 + 5*s + 6, s), Parallel(TransferFunction(p**2, p + s, s), TransferFunction(-a*p**2 - b*s, -p + s, s)))
  743. >>> S2.var
  744. s
  745. >>> S3 = Series(Parallel(tf1, tf2), Parallel(tf2, tf3))
  746. >>> S3
  747. Series(Parallel(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)), Parallel(TransferFunction(s**3 - 2, s**4 + 5*s + 6, s), TransferFunction(p**2, p + s, s)))
  748. >>> S3.var
  749. s
  750. You can get the resultant transfer function by using ``.doit()`` method:
  751. >>> S3 = Series(tf1, tf2, -tf3)
  752. >>> S3.doit()
  753. TransferFunction(-p**2*(s**3 - 2)*(a*p**2 + b*s), (-p + s)*(p + s)*(s**4 + 5*s + 6), s)
  754. >>> S4 = Series(tf2, Parallel(tf1, -tf3))
  755. >>> S4.doit()
  756. TransferFunction((s**3 - 2)*(-p**2*(-p + s) + (p + s)*(a*p**2 + b*s)), (-p + s)*(p + s)*(s**4 + 5*s + 6), s)
  757. Notes
  758. =====
  759. All the transfer functions should use the same complex variable
  760. ``var`` of the Laplace transform.
  761. See Also
  762. ========
  763. MIMOSeries, Parallel, TransferFunction, Feedback
  764. """
  765. def __new__(cls, *args, evaluate=False):
  766. args = _flatten_args(args, Series)
  767. cls._check_args(args)
  768. obj = super().__new__(cls, *args)
  769. return obj.doit() if evaluate else obj
  770. @property
  771. def var(self):
  772. """
  773. Returns the complex variable used by all the transfer functions.
  774. Examples
  775. ========
  776. >>> from sympy.abc import p
  777. >>> from sympy.physics.control.lti import TransferFunction, Series, Parallel
  778. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  779. >>> G2 = TransferFunction(p, 4 - p, p)
  780. >>> G3 = TransferFunction(0, p**4 - 1, p)
  781. >>> Series(G1, G2).var
  782. p
  783. >>> Series(-G3, Parallel(G1, G2)).var
  784. p
  785. """
  786. return self.args[0].var
  787. def doit(self, **hints):
  788. """
  789. Returns the resultant transfer function obtained after evaluating
  790. the transfer functions in series configuration.
  791. Examples
  792. ========
  793. >>> from sympy.abc import s, p, a, b
  794. >>> from sympy.physics.control.lti import TransferFunction, Series
  795. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  796. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  797. >>> Series(tf2, tf1).doit()
  798. TransferFunction((s**3 - 2)*(a*p**2 + b*s), (-p + s)*(s**4 + 5*s + 6), s)
  799. >>> Series(-tf1, -tf2).doit()
  800. TransferFunction((2 - s**3)*(-a*p**2 - b*s), (-p + s)*(s**4 + 5*s + 6), s)
  801. """
  802. _num_arg = (arg.doit().num for arg in self.args)
  803. _den_arg = (arg.doit().den for arg in self.args)
  804. res_num = Mul(*_num_arg, evaluate=True)
  805. res_den = Mul(*_den_arg, evaluate=True)
  806. return TransferFunction(res_num, res_den, self.var)
  807. def _eval_rewrite_as_TransferFunction(self, *args, **kwargs):
  808. return self.doit()
  809. @_check_other_SISO
  810. def __add__(self, other):
  811. if isinstance(other, Parallel):
  812. arg_list = list(other.args)
  813. return Parallel(self, *arg_list)
  814. return Parallel(self, other)
  815. __radd__ = __add__
  816. @_check_other_SISO
  817. def __sub__(self, other):
  818. return self + (-other)
  819. def __rsub__(self, other):
  820. return -self + other
  821. @_check_other_SISO
  822. def __mul__(self, other):
  823. arg_list = list(self.args)
  824. return Series(*arg_list, other)
  825. def __truediv__(self, other):
  826. if (isinstance(other, Parallel) and len(other.args) == 2
  827. and isinstance(other.args[0], TransferFunction) and isinstance(other.args[1], Series)):
  828. if not self.var == other.var:
  829. raise ValueError("All the transfer functions should use the same complex variable "
  830. "of the Laplace transform.")
  831. self_arg_list = set(self.args)
  832. other_arg_list = set(other.args[1].args)
  833. res = list(self_arg_list ^ other_arg_list)
  834. if len(res) == 0:
  835. return Feedback(self, other.args[0])
  836. elif len(res) == 1:
  837. return Feedback(self, *res)
  838. else:
  839. return Feedback(self, Series(*res))
  840. else:
  841. raise ValueError("This transfer function expression is invalid.")
  842. def __neg__(self):
  843. return Series(TransferFunction(-1, 1, self.var), self)
  844. def to_expr(self):
  845. """Returns the equivalent ``Expr`` object."""
  846. return Mul(*(arg.to_expr() for arg in self.args), evaluate=False)
  847. @property
  848. def is_proper(self):
  849. """
  850. Returns True if degree of the numerator polynomial of the resultant transfer
  851. function is less than or equal to degree of the denominator polynomial of
  852. the same, else False.
  853. Examples
  854. ========
  855. >>> from sympy.abc import s, p, a, b
  856. >>> from sympy.physics.control.lti import TransferFunction, Series
  857. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  858. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*s + 2, s)
  859. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  860. >>> S1 = Series(-tf2, tf1)
  861. >>> S1.is_proper
  862. False
  863. >>> S2 = Series(tf1, tf2, tf3)
  864. >>> S2.is_proper
  865. True
  866. """
  867. return self.doit().is_proper
  868. @property
  869. def is_strictly_proper(self):
  870. """
  871. Returns True if degree of the numerator polynomial of the resultant transfer
  872. function is strictly less than degree of the denominator polynomial of
  873. the same, else False.
  874. Examples
  875. ========
  876. >>> from sympy.abc import s, p, a, b
  877. >>> from sympy.physics.control.lti import TransferFunction, Series
  878. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  879. >>> tf2 = TransferFunction(s**3 - 2, s**2 + 5*s + 6, s)
  880. >>> tf3 = TransferFunction(1, s**2 + s + 1, s)
  881. >>> S1 = Series(tf1, tf2)
  882. >>> S1.is_strictly_proper
  883. False
  884. >>> S2 = Series(tf1, tf2, tf3)
  885. >>> S2.is_strictly_proper
  886. True
  887. """
  888. return self.doit().is_strictly_proper
  889. @property
  890. def is_biproper(self):
  891. r"""
  892. Returns True if degree of the numerator polynomial of the resultant transfer
  893. function is equal to degree of the denominator polynomial of
  894. the same, else False.
  895. Examples
  896. ========
  897. >>> from sympy.abc import s, p, a, b
  898. >>> from sympy.physics.control.lti import TransferFunction, Series
  899. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  900. >>> tf2 = TransferFunction(p, s**2, s)
  901. >>> tf3 = TransferFunction(s**2, 1, s)
  902. >>> S1 = Series(tf1, -tf2)
  903. >>> S1.is_biproper
  904. False
  905. >>> S2 = Series(tf2, tf3)
  906. >>> S2.is_biproper
  907. True
  908. """
  909. return self.doit().is_biproper
  910. def _mat_mul_compatible(*args):
  911. """To check whether shapes are compatible for matrix mul."""
  912. return all(args[i].num_outputs == args[i+1].num_inputs for i in range(len(args)-1))
  913. class MIMOSeries(MIMOLinearTimeInvariant):
  914. r"""
  915. A class for representing a series configuration of MIMO systems.
  916. Parameters
  917. ==========
  918. args : MIMOLinearTimeInvariant
  919. MIMO systems in a series configuration.
  920. evaluate : Boolean, Keyword
  921. When passed ``True``, returns the equivalent
  922. ``MIMOSeries(*args).doit()``. Set to ``False`` by default.
  923. Raises
  924. ======
  925. ValueError
  926. When no argument is passed.
  927. ``var`` attribute is not same for every system.
  928. ``num_outputs`` of the MIMO system is not equal to the
  929. ``num_inputs`` of its adjacent MIMO system. (Matrix
  930. multiplication constraint, basically)
  931. TypeError
  932. Any of the passed ``*args`` has unsupported type
  933. A combination of SISO and MIMO systems is
  934. passed. There should be homogeneity in the
  935. type of systems passed, MIMO in this case.
  936. Examples
  937. ========
  938. >>> from sympy.abc import s
  939. >>> from sympy.physics.control.lti import MIMOSeries, TransferFunctionMatrix
  940. >>> from sympy import Matrix, pprint
  941. >>> mat_a = Matrix([[5*s], [5]]) # 2 Outputs 1 Input
  942. >>> mat_b = Matrix([[5, 1/(6*s**2)]]) # 1 Output 2 Inputs
  943. >>> mat_c = Matrix([[1, s], [5/s, 1]]) # 2 Outputs 2 Inputs
  944. >>> tfm_a = TransferFunctionMatrix.from_Matrix(mat_a, s)
  945. >>> tfm_b = TransferFunctionMatrix.from_Matrix(mat_b, s)
  946. >>> tfm_c = TransferFunctionMatrix.from_Matrix(mat_c, s)
  947. >>> MIMOSeries(tfm_c, tfm_b, tfm_a)
  948. MIMOSeries(TransferFunctionMatrix(((TransferFunction(1, 1, s), TransferFunction(s, 1, s)), (TransferFunction(5, s, s), TransferFunction(1, 1, s)))), TransferFunctionMatrix(((TransferFunction(5, 1, s), TransferFunction(1, 6*s**2, s)),)), TransferFunctionMatrix(((TransferFunction(5*s, 1, s),), (TransferFunction(5, 1, s),))))
  949. >>> pprint(_, use_unicode=False) # For Better Visualization
  950. [5*s] [1 s]
  951. [---] [5 1 ] [- -]
  952. [ 1 ] [- ----] [1 1]
  953. [ ] *[1 2] *[ ]
  954. [ 5 ] [ 6*s ]{t} [5 1]
  955. [ - ] [- -]
  956. [ 1 ]{t} [s 1]{t}
  957. >>> MIMOSeries(tfm_c, tfm_b, tfm_a).doit()
  958. TransferFunctionMatrix(((TransferFunction(150*s**4 + 25*s, 6*s**3, s), TransferFunction(150*s**4 + 5*s, 6*s**2, s)), (TransferFunction(150*s**3 + 25, 6*s**3, s), TransferFunction(150*s**3 + 5, 6*s**2, s))))
  959. >>> pprint(_, use_unicode=False) # (2 Inputs -A-> 2 Outputs) -> (2 Inputs -B-> 1 Output) -> (1 Input -C-> 2 Outputs) is equivalent to (2 Inputs -Series Equivalent-> 2 Outputs).
  960. [ 4 4 ]
  961. [150*s + 25*s 150*s + 5*s]
  962. [------------- ------------]
  963. [ 3 2 ]
  964. [ 6*s 6*s ]
  965. [ ]
  966. [ 3 3 ]
  967. [ 150*s + 25 150*s + 5 ]
  968. [ ----------- ---------- ]
  969. [ 3 2 ]
  970. [ 6*s 6*s ]{t}
  971. Notes
  972. =====
  973. All the transfer function matrices should use the same complex variable ``var`` of the Laplace transform.
  974. ``MIMOSeries(A, B)`` is not equivalent to ``A*B``. It is always in the reverse order, that is ``B*A``.
  975. See Also
  976. ========
  977. Series, MIMOParallel
  978. """
  979. def __new__(cls, *args, evaluate=False):
  980. cls._check_args(args)
  981. if _mat_mul_compatible(*args):
  982. obj = super().__new__(cls, *args)
  983. else:
  984. raise ValueError("Number of input signals do not match the number"
  985. " of output signals of adjacent systems for some args.")
  986. return obj.doit() if evaluate else obj
  987. @property
  988. def var(self):
  989. """
  990. Returns the complex variable used by all the transfer functions.
  991. Examples
  992. ========
  993. >>> from sympy.abc import p
  994. >>> from sympy.physics.control.lti import TransferFunction, MIMOSeries, TransferFunctionMatrix
  995. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  996. >>> G2 = TransferFunction(p, 4 - p, p)
  997. >>> G3 = TransferFunction(0, p**4 - 1, p)
  998. >>> tfm_1 = TransferFunctionMatrix([[G1, G2, G3]])
  999. >>> tfm_2 = TransferFunctionMatrix([[G1], [G2], [G3]])
  1000. >>> MIMOSeries(tfm_2, tfm_1).var
  1001. p
  1002. """
  1003. return self.args[0].var
  1004. @property
  1005. def num_inputs(self):
  1006. """Returns the number of input signals of the series system."""
  1007. return self.args[0].num_inputs
  1008. @property
  1009. def num_outputs(self):
  1010. """Returns the number of output signals of the series system."""
  1011. return self.args[-1].num_outputs
  1012. @property
  1013. def shape(self):
  1014. """Returns the shape of the equivalent MIMO system."""
  1015. return self.num_outputs, self.num_inputs
  1016. def doit(self, cancel=False, **kwargs):
  1017. """
  1018. Returns the resultant transfer function matrix obtained after evaluating
  1019. the MIMO systems arranged in a series configuration.
  1020. Examples
  1021. ========
  1022. >>> from sympy.abc import s, p, a, b
  1023. >>> from sympy.physics.control.lti import TransferFunction, MIMOSeries, TransferFunctionMatrix
  1024. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1025. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1026. >>> tfm1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf2]])
  1027. >>> tfm2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf1]])
  1028. >>> MIMOSeries(tfm2, tfm1).doit()
  1029. TransferFunctionMatrix(((TransferFunction(2*(-p + s)*(s**3 - 2)*(a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)**2*(s**4 + 5*s + 6)**2, s), TransferFunction((-p + s)**2*(s**3 - 2)*(a*p**2 + b*s) + (-p + s)*(a*p**2 + b*s)**2*(s**4 + 5*s + 6), (-p + s)**3*(s**4 + 5*s + 6), s)), (TransferFunction((-p + s)*(s**3 - 2)**2*(s**4 + 5*s + 6) + (s**3 - 2)*(a*p**2 + b*s)*(s**4 + 5*s + 6)**2, (-p + s)*(s**4 + 5*s + 6)**3, s), TransferFunction(2*(s**3 - 2)*(a*p**2 + b*s), (-p + s)*(s**4 + 5*s + 6), s))))
  1030. """
  1031. _arg = (arg.doit()._expr_mat for arg in reversed(self.args))
  1032. if cancel:
  1033. res = MatMul(*_arg, evaluate=True)
  1034. return TransferFunctionMatrix.from_Matrix(res, self.var)
  1035. _dummy_args, _dummy_dict = _dummify_args(_arg, self.var)
  1036. res = MatMul(*_dummy_args, evaluate=True)
  1037. temp_tfm = TransferFunctionMatrix.from_Matrix(res, self.var)
  1038. return temp_tfm.subs(_dummy_dict)
  1039. def _eval_rewrite_as_TransferFunctionMatrix(self, *args, **kwargs):
  1040. return self.doit()
  1041. @_check_other_MIMO
  1042. def __add__(self, other):
  1043. if isinstance(other, MIMOParallel):
  1044. arg_list = list(other.args)
  1045. return MIMOParallel(self, *arg_list)
  1046. return MIMOParallel(self, other)
  1047. __radd__ = __add__
  1048. @_check_other_MIMO
  1049. def __sub__(self, other):
  1050. return self + (-other)
  1051. def __rsub__(self, other):
  1052. return -self + other
  1053. @_check_other_MIMO
  1054. def __mul__(self, other):
  1055. if isinstance(other, MIMOSeries):
  1056. self_arg_list = list(self.args)
  1057. other_arg_list = list(other.args)
  1058. return MIMOSeries(*other_arg_list, *self_arg_list) # A*B = MIMOSeries(B, A)
  1059. arg_list = list(self.args)
  1060. return MIMOSeries(other, *arg_list)
  1061. def __neg__(self):
  1062. arg_list = list(self.args)
  1063. arg_list[0] = -arg_list[0]
  1064. return MIMOSeries(*arg_list)
  1065. class Parallel(SISOLinearTimeInvariant):
  1066. r"""
  1067. A class for representing a parallel configuration of SISO systems.
  1068. Parameters
  1069. ==========
  1070. args : SISOLinearTimeInvariant
  1071. SISO systems in a parallel arrangement.
  1072. evaluate : Boolean, Keyword
  1073. When passed ``True``, returns the equivalent
  1074. ``Parallel(*args).doit()``. Set to ``False`` by default.
  1075. Raises
  1076. ======
  1077. ValueError
  1078. When no argument is passed.
  1079. ``var`` attribute is not same for every system.
  1080. TypeError
  1081. Any of the passed ``*args`` has unsupported type
  1082. A combination of SISO and MIMO systems is
  1083. passed. There should be homogeneity in the
  1084. type of systems passed.
  1085. Examples
  1086. ========
  1087. >>> from sympy.abc import s, p, a, b
  1088. >>> from sympy.physics.control.lti import TransferFunction, Parallel, Series
  1089. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1090. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1091. >>> tf3 = TransferFunction(p**2, p + s, s)
  1092. >>> P1 = Parallel(tf1, tf2)
  1093. >>> P1
  1094. Parallel(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s))
  1095. >>> P1.var
  1096. s
  1097. >>> P2 = Parallel(tf2, Series(tf3, -tf1))
  1098. >>> P2
  1099. Parallel(TransferFunction(s**3 - 2, s**4 + 5*s + 6, s), Series(TransferFunction(p**2, p + s, s), TransferFunction(-a*p**2 - b*s, -p + s, s)))
  1100. >>> P2.var
  1101. s
  1102. >>> P3 = Parallel(Series(tf1, tf2), Series(tf2, tf3))
  1103. >>> P3
  1104. Parallel(Series(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)), Series(TransferFunction(s**3 - 2, s**4 + 5*s + 6, s), TransferFunction(p**2, p + s, s)))
  1105. >>> P3.var
  1106. s
  1107. You can get the resultant transfer function by using ``.doit()`` method:
  1108. >>> Parallel(tf1, tf2, -tf3).doit()
  1109. TransferFunction(-p**2*(-p + s)*(s**4 + 5*s + 6) + (-p + s)*(p + s)*(s**3 - 2) + (p + s)*(a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(p + s)*(s**4 + 5*s + 6), s)
  1110. >>> Parallel(tf2, Series(tf1, -tf3)).doit()
  1111. TransferFunction(-p**2*(a*p**2 + b*s)*(s**4 + 5*s + 6) + (-p + s)*(p + s)*(s**3 - 2), (-p + s)*(p + s)*(s**4 + 5*s + 6), s)
  1112. Notes
  1113. =====
  1114. All the transfer functions should use the same complex variable
  1115. ``var`` of the Laplace transform.
  1116. See Also
  1117. ========
  1118. Series, TransferFunction, Feedback
  1119. """
  1120. def __new__(cls, *args, evaluate=False):
  1121. args = _flatten_args(args, Parallel)
  1122. cls._check_args(args)
  1123. obj = super().__new__(cls, *args)
  1124. return obj.doit() if evaluate else obj
  1125. @property
  1126. def var(self):
  1127. """
  1128. Returns the complex variable used by all the transfer functions.
  1129. Examples
  1130. ========
  1131. >>> from sympy.abc import p
  1132. >>> from sympy.physics.control.lti import TransferFunction, Parallel, Series
  1133. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  1134. >>> G2 = TransferFunction(p, 4 - p, p)
  1135. >>> G3 = TransferFunction(0, p**4 - 1, p)
  1136. >>> Parallel(G1, G2).var
  1137. p
  1138. >>> Parallel(-G3, Series(G1, G2)).var
  1139. p
  1140. """
  1141. return self.args[0].var
  1142. def doit(self, **hints):
  1143. """
  1144. Returns the resultant transfer function obtained after evaluating
  1145. the transfer functions in parallel configuration.
  1146. Examples
  1147. ========
  1148. >>> from sympy.abc import s, p, a, b
  1149. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1150. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1151. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1152. >>> Parallel(tf2, tf1).doit()
  1153. TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s)
  1154. >>> Parallel(-tf1, -tf2).doit()
  1155. TransferFunction((2 - s**3)*(-p + s) + (-a*p**2 - b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s)
  1156. """
  1157. _arg = (arg.doit().to_expr() for arg in self.args)
  1158. res = Add(*_arg).as_numer_denom()
  1159. return TransferFunction(*res, self.var)
  1160. def _eval_rewrite_as_TransferFunction(self, *args, **kwargs):
  1161. return self.doit()
  1162. @_check_other_SISO
  1163. def __add__(self, other):
  1164. self_arg_list = list(self.args)
  1165. return Parallel(*self_arg_list, other)
  1166. __radd__ = __add__
  1167. @_check_other_SISO
  1168. def __sub__(self, other):
  1169. return self + (-other)
  1170. def __rsub__(self, other):
  1171. return -self + other
  1172. @_check_other_SISO
  1173. def __mul__(self, other):
  1174. if isinstance(other, Series):
  1175. arg_list = list(other.args)
  1176. return Series(self, *arg_list)
  1177. return Series(self, other)
  1178. def __neg__(self):
  1179. return Series(TransferFunction(-1, 1, self.var), self)
  1180. def to_expr(self):
  1181. """Returns the equivalent ``Expr`` object."""
  1182. return Add(*(arg.to_expr() for arg in self.args), evaluate=False)
  1183. @property
  1184. def is_proper(self):
  1185. """
  1186. Returns True if degree of the numerator polynomial of the resultant transfer
  1187. function is less than or equal to degree of the denominator polynomial of
  1188. the same, else False.
  1189. Examples
  1190. ========
  1191. >>> from sympy.abc import s, p, a, b
  1192. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1193. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  1194. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*s + 2, s)
  1195. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1196. >>> P1 = Parallel(-tf2, tf1)
  1197. >>> P1.is_proper
  1198. False
  1199. >>> P2 = Parallel(tf2, tf3)
  1200. >>> P2.is_proper
  1201. True
  1202. """
  1203. return self.doit().is_proper
  1204. @property
  1205. def is_strictly_proper(self):
  1206. """
  1207. Returns True if degree of the numerator polynomial of the resultant transfer
  1208. function is strictly less than degree of the denominator polynomial of
  1209. the same, else False.
  1210. Examples
  1211. ========
  1212. >>> from sympy.abc import s, p, a, b
  1213. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1214. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1215. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1216. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1217. >>> P1 = Parallel(tf1, tf2)
  1218. >>> P1.is_strictly_proper
  1219. False
  1220. >>> P2 = Parallel(tf2, tf3)
  1221. >>> P2.is_strictly_proper
  1222. True
  1223. """
  1224. return self.doit().is_strictly_proper
  1225. @property
  1226. def is_biproper(self):
  1227. """
  1228. Returns True if degree of the numerator polynomial of the resultant transfer
  1229. function is equal to degree of the denominator polynomial of
  1230. the same, else False.
  1231. Examples
  1232. ========
  1233. >>> from sympy.abc import s, p, a, b
  1234. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1235. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1236. >>> tf2 = TransferFunction(p**2, p + s, s)
  1237. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1238. >>> P1 = Parallel(tf1, -tf2)
  1239. >>> P1.is_biproper
  1240. True
  1241. >>> P2 = Parallel(tf2, tf3)
  1242. >>> P2.is_biproper
  1243. False
  1244. """
  1245. return self.doit().is_biproper
  1246. class MIMOParallel(MIMOLinearTimeInvariant):
  1247. r"""
  1248. A class for representing a parallel configuration of MIMO systems.
  1249. Parameters
  1250. ==========
  1251. args : MIMOLinearTimeInvariant
  1252. MIMO Systems in a parallel arrangement.
  1253. evaluate : Boolean, Keyword
  1254. When passed ``True``, returns the equivalent
  1255. ``MIMOParallel(*args).doit()``. Set to ``False`` by default.
  1256. Raises
  1257. ======
  1258. ValueError
  1259. When no argument is passed.
  1260. ``var`` attribute is not same for every system.
  1261. All MIMO systems passed do not have same shape.
  1262. TypeError
  1263. Any of the passed ``*args`` has unsupported type
  1264. A combination of SISO and MIMO systems is
  1265. passed. There should be homogeneity in the
  1266. type of systems passed, MIMO in this case.
  1267. Examples
  1268. ========
  1269. >>> from sympy.abc import s
  1270. >>> from sympy.physics.control.lti import TransferFunctionMatrix, MIMOParallel
  1271. >>> from sympy import Matrix, pprint
  1272. >>> expr_1 = 1/s
  1273. >>> expr_2 = s/(s**2-1)
  1274. >>> expr_3 = (2 + s)/(s**2 - 1)
  1275. >>> expr_4 = 5
  1276. >>> tfm_a = TransferFunctionMatrix.from_Matrix(Matrix([[expr_1, expr_2], [expr_3, expr_4]]), s)
  1277. >>> tfm_b = TransferFunctionMatrix.from_Matrix(Matrix([[expr_2, expr_1], [expr_4, expr_3]]), s)
  1278. >>> tfm_c = TransferFunctionMatrix.from_Matrix(Matrix([[expr_3, expr_4], [expr_1, expr_2]]), s)
  1279. >>> MIMOParallel(tfm_a, tfm_b, tfm_c)
  1280. MIMOParallel(TransferFunctionMatrix(((TransferFunction(1, s, s), TransferFunction(s, s**2 - 1, s)), (TransferFunction(s + 2, s**2 - 1, s), TransferFunction(5, 1, s)))), TransferFunctionMatrix(((TransferFunction(s, s**2 - 1, s), TransferFunction(1, s, s)), (TransferFunction(5, 1, s), TransferFunction(s + 2, s**2 - 1, s)))), TransferFunctionMatrix(((TransferFunction(s + 2, s**2 - 1, s), TransferFunction(5, 1, s)), (TransferFunction(1, s, s), TransferFunction(s, s**2 - 1, s)))))
  1281. >>> pprint(_, use_unicode=False) # For Better Visualization
  1282. [ 1 s ] [ s 1 ] [s + 2 5 ]
  1283. [ - ------] [------ - ] [------ - ]
  1284. [ s 2 ] [ 2 s ] [ 2 1 ]
  1285. [ s - 1] [s - 1 ] [s - 1 ]
  1286. [ ] + [ ] + [ ]
  1287. [s + 2 5 ] [ 5 s + 2 ] [ 1 s ]
  1288. [------ - ] [ - ------] [ - ------]
  1289. [ 2 1 ] [ 1 2 ] [ s 2 ]
  1290. [s - 1 ]{t} [ s - 1]{t} [ s - 1]{t}
  1291. >>> MIMOParallel(tfm_a, tfm_b, tfm_c).doit()
  1292. TransferFunctionMatrix(((TransferFunction(s**2 + s*(2*s + 2) - 1, s*(s**2 - 1), s), TransferFunction(2*s**2 + 5*s*(s**2 - 1) - 1, s*(s**2 - 1), s)), (TransferFunction(s**2 + s*(s + 2) + 5*s*(s**2 - 1) - 1, s*(s**2 - 1), s), TransferFunction(5*s**2 + 2*s - 3, s**2 - 1, s))))
  1293. >>> pprint(_, use_unicode=False)
  1294. [ 2 2 / 2 \ ]
  1295. [ s + s*(2*s + 2) - 1 2*s + 5*s*\s - 1/ - 1]
  1296. [ -------------------- -----------------------]
  1297. [ / 2 \ / 2 \ ]
  1298. [ s*\s - 1/ s*\s - 1/ ]
  1299. [ ]
  1300. [ 2 / 2 \ 2 ]
  1301. [s + s*(s + 2) + 5*s*\s - 1/ - 1 5*s + 2*s - 3 ]
  1302. [--------------------------------- -------------- ]
  1303. [ / 2 \ 2 ]
  1304. [ s*\s - 1/ s - 1 ]{t}
  1305. Notes
  1306. =====
  1307. All the transfer function matrices should use the same complex variable
  1308. ``var`` of the Laplace transform.
  1309. See Also
  1310. ========
  1311. Parallel, MIMOSeries
  1312. """
  1313. def __new__(cls, *args, evaluate=False):
  1314. args = _flatten_args(args, MIMOParallel)
  1315. cls._check_args(args)
  1316. if any(arg.shape != args[0].shape for arg in args):
  1317. raise TypeError("Shape of all the args is not equal.")
  1318. obj = super().__new__(cls, *args)
  1319. return obj.doit() if evaluate else obj
  1320. @property
  1321. def var(self):
  1322. """
  1323. Returns the complex variable used by all the systems.
  1324. Examples
  1325. ========
  1326. >>> from sympy.abc import p
  1327. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOParallel
  1328. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  1329. >>> G2 = TransferFunction(p, 4 - p, p)
  1330. >>> G3 = TransferFunction(0, p**4 - 1, p)
  1331. >>> G4 = TransferFunction(p**2, p**2 - 1, p)
  1332. >>> tfm_a = TransferFunctionMatrix([[G1, G2], [G3, G4]])
  1333. >>> tfm_b = TransferFunctionMatrix([[G2, G1], [G4, G3]])
  1334. >>> MIMOParallel(tfm_a, tfm_b).var
  1335. p
  1336. """
  1337. return self.args[0].var
  1338. @property
  1339. def num_inputs(self):
  1340. """Returns the number of input signals of the parallel system."""
  1341. return self.args[0].num_inputs
  1342. @property
  1343. def num_outputs(self):
  1344. """Returns the number of output signals of the parallel system."""
  1345. return self.args[0].num_outputs
  1346. @property
  1347. def shape(self):
  1348. """Returns the shape of the equivalent MIMO system."""
  1349. return self.num_outputs, self.num_inputs
  1350. def doit(self, **hints):
  1351. """
  1352. Returns the resultant transfer function matrix obtained after evaluating
  1353. the MIMO systems arranged in a parallel configuration.
  1354. Examples
  1355. ========
  1356. >>> from sympy.abc import s, p, a, b
  1357. >>> from sympy.physics.control.lti import TransferFunction, MIMOParallel, TransferFunctionMatrix
  1358. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1359. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1360. >>> tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  1361. >>> tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]])
  1362. >>> MIMOParallel(tfm_1, tfm_2).doit()
  1363. TransferFunctionMatrix(((TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s), TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s)), (TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s), TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s))))
  1364. """
  1365. _arg = (arg.doit()._expr_mat for arg in self.args)
  1366. res = MatAdd(*_arg, evaluate=True)
  1367. return TransferFunctionMatrix.from_Matrix(res, self.var)
  1368. def _eval_rewrite_as_TransferFunctionMatrix(self, *args, **kwargs):
  1369. return self.doit()
  1370. @_check_other_MIMO
  1371. def __add__(self, other):
  1372. self_arg_list = list(self.args)
  1373. return MIMOParallel(*self_arg_list, other)
  1374. __radd__ = __add__
  1375. @_check_other_MIMO
  1376. def __sub__(self, other):
  1377. return self + (-other)
  1378. def __rsub__(self, other):
  1379. return -self + other
  1380. @_check_other_MIMO
  1381. def __mul__(self, other):
  1382. if isinstance(other, MIMOSeries):
  1383. arg_list = list(other.args)
  1384. return MIMOSeries(*arg_list, self)
  1385. return MIMOSeries(other, self)
  1386. def __neg__(self):
  1387. arg_list = [-arg for arg in list(self.args)]
  1388. return MIMOParallel(*arg_list)
  1389. class Feedback(SISOLinearTimeInvariant):
  1390. r"""
  1391. A class for representing closed-loop feedback interconnection between two
  1392. SISO input/output systems.
  1393. The first argument, ``sys1``, is the feedforward part of the closed-loop
  1394. system or in simple words, the dynamical model representing the process
  1395. to be controlled. The second argument, ``sys2``, is the feedback system
  1396. and controls the fed back signal to ``sys1``. Both ``sys1`` and ``sys2``
  1397. can either be ``Series`` or ``TransferFunction`` objects.
  1398. Parameters
  1399. ==========
  1400. sys1 : Series, TransferFunction
  1401. The feedforward path system.
  1402. sys2 : Series, TransferFunction, optional
  1403. The feedback path system (often a feedback controller).
  1404. It is the model sitting on the feedback path.
  1405. If not specified explicitly, the sys2 is
  1406. assumed to be unit (1.0) transfer function.
  1407. sign : int, optional
  1408. The sign of feedback. Can either be ``1``
  1409. (for positive feedback) or ``-1`` (for negative feedback).
  1410. Default value is `-1`.
  1411. Raises
  1412. ======
  1413. ValueError
  1414. When ``sys1`` and ``sys2`` are not using the
  1415. same complex variable of the Laplace transform.
  1416. When a combination of ``sys1`` and ``sys2`` yields
  1417. zero denominator.
  1418. TypeError
  1419. When either ``sys1`` or ``sys2`` is not a ``Series`` or a
  1420. ``TransferFunction`` object.
  1421. Examples
  1422. ========
  1423. >>> from sympy.abc import s
  1424. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1425. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1426. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  1427. >>> F1 = Feedback(plant, controller)
  1428. >>> F1
  1429. Feedback(TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s), TransferFunction(5*s - 10, s + 7, s), -1)
  1430. >>> F1.var
  1431. s
  1432. >>> F1.args
  1433. (TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s), TransferFunction(5*s - 10, s + 7, s), -1)
  1434. You can get the feedforward and feedback path systems by using ``.sys1`` and ``.sys2`` respectively.
  1435. >>> F1.sys1
  1436. TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1437. >>> F1.sys2
  1438. TransferFunction(5*s - 10, s + 7, s)
  1439. You can get the resultant closed loop transfer function obtained by negative feedback
  1440. interconnection using ``.doit()`` method.
  1441. >>> F1.doit()
  1442. TransferFunction((s + 7)*(s**2 - 4*s + 2)*(3*s**2 + 7*s - 3), ((s + 7)*(s**2 - 4*s + 2) + (5*s - 10)*(3*s**2 + 7*s - 3))*(s**2 - 4*s + 2), s)
  1443. >>> G = TransferFunction(2*s**2 + 5*s + 1, s**2 + 2*s + 3, s)
  1444. >>> C = TransferFunction(5*s + 10, s + 10, s)
  1445. >>> F2 = Feedback(G*C, TransferFunction(1, 1, s))
  1446. >>> F2.doit()
  1447. TransferFunction((s + 10)*(5*s + 10)*(s**2 + 2*s + 3)*(2*s**2 + 5*s + 1), (s + 10)*((s + 10)*(s**2 + 2*s + 3) + (5*s + 10)*(2*s**2 + 5*s + 1))*(s**2 + 2*s + 3), s)
  1448. To negate a ``Feedback`` object, the ``-`` operator can be prepended:
  1449. >>> -F1
  1450. Feedback(TransferFunction(-3*s**2 - 7*s + 3, s**2 - 4*s + 2, s), TransferFunction(10 - 5*s, s + 7, s), -1)
  1451. >>> -F2
  1452. Feedback(Series(TransferFunction(-1, 1, s), TransferFunction(2*s**2 + 5*s + 1, s**2 + 2*s + 3, s), TransferFunction(5*s + 10, s + 10, s)), TransferFunction(-1, 1, s), -1)
  1453. See Also
  1454. ========
  1455. MIMOFeedback, Series, Parallel
  1456. """
  1457. def __new__(cls, sys1, sys2=None, sign=-1):
  1458. if not sys2:
  1459. sys2 = TransferFunction(1, 1, sys1.var)
  1460. if not (isinstance(sys1, (TransferFunction, Series))
  1461. and isinstance(sys2, (TransferFunction, Series))):
  1462. raise TypeError("Unsupported type for `sys1` or `sys2` of Feedback.")
  1463. if sign not in [-1, 1]:
  1464. raise ValueError("Unsupported type for feedback. `sign` arg should "
  1465. "either be 1 (positive feedback loop) or -1 (negative feedback loop).")
  1466. if Mul(sys1.to_expr(), sys2.to_expr()).simplify() == sign:
  1467. raise ValueError("The equivalent system will have zero denominator.")
  1468. if sys1.var != sys2.var:
  1469. raise ValueError("Both `sys1` and `sys2` should be using the"
  1470. " same complex variable.")
  1471. return super().__new__(cls, sys1, sys2, _sympify(sign))
  1472. @property
  1473. def sys1(self):
  1474. """
  1475. Returns the feedforward system of the feedback interconnection.
  1476. Examples
  1477. ========
  1478. >>> from sympy.abc import s, p
  1479. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1480. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1481. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  1482. >>> F1 = Feedback(plant, controller)
  1483. >>> F1.sys1
  1484. TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1485. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  1486. >>> C = TransferFunction(5*p + 10, p + 10, p)
  1487. >>> P = TransferFunction(1 - s, p + 2, p)
  1488. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  1489. >>> F2.sys1
  1490. TransferFunction(1, 1, p)
  1491. """
  1492. return self.args[0]
  1493. @property
  1494. def sys2(self):
  1495. """
  1496. Returns the feedback controller of the feedback interconnection.
  1497. Examples
  1498. ========
  1499. >>> from sympy.abc import s, p
  1500. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1501. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1502. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  1503. >>> F1 = Feedback(plant, controller)
  1504. >>> F1.sys2
  1505. TransferFunction(5*s - 10, s + 7, s)
  1506. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  1507. >>> C = TransferFunction(5*p + 10, p + 10, p)
  1508. >>> P = TransferFunction(1 - s, p + 2, p)
  1509. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  1510. >>> F2.sys2
  1511. Series(TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p), TransferFunction(5*p + 10, p + 10, p), TransferFunction(1 - s, p + 2, p))
  1512. """
  1513. return self.args[1]
  1514. @property
  1515. def var(self):
  1516. """
  1517. Returns the complex variable of the Laplace transform used by all
  1518. the transfer functions involved in the feedback interconnection.
  1519. Examples
  1520. ========
  1521. >>> from sympy.abc import s, p
  1522. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1523. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1524. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  1525. >>> F1 = Feedback(plant, controller)
  1526. >>> F1.var
  1527. s
  1528. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  1529. >>> C = TransferFunction(5*p + 10, p + 10, p)
  1530. >>> P = TransferFunction(1 - s, p + 2, p)
  1531. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  1532. >>> F2.var
  1533. p
  1534. """
  1535. return self.sys1.var
  1536. @property
  1537. def sign(self):
  1538. """
  1539. Returns the type of MIMO Feedback model. ``1``
  1540. for Positive and ``-1`` for Negative.
  1541. """
  1542. return self.args[2]
  1543. @property
  1544. def sensitivity(self):
  1545. """
  1546. Returns the sensitivity function of the feedback loop.
  1547. Sensitivity of a Feedback system is the ratio
  1548. of change in the open loop gain to the change in
  1549. the closed loop gain.
  1550. .. note::
  1551. This method would not return the complementary
  1552. sensitivity function.
  1553. Examples
  1554. ========
  1555. >>> from sympy.abc import p
  1556. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1557. >>> C = TransferFunction(5*p + 10, p + 10, p)
  1558. >>> P = TransferFunction(1 - p, p + 2, p)
  1559. >>> F_1 = Feedback(P, C)
  1560. >>> F_1.sensitivity
  1561. 1/((1 - p)*(5*p + 10)/((p + 2)*(p + 10)) + 1)
  1562. """
  1563. return 1/(1 - self.sign*self.sys1.to_expr()*self.sys2.to_expr())
  1564. def doit(self, cancel=False, expand=False, **hints):
  1565. """
  1566. Returns the resultant transfer function obtained by the
  1567. feedback interconnection.
  1568. Examples
  1569. ========
  1570. >>> from sympy.abc import s
  1571. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  1572. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  1573. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  1574. >>> F1 = Feedback(plant, controller)
  1575. >>> F1.doit()
  1576. TransferFunction((s + 7)*(s**2 - 4*s + 2)*(3*s**2 + 7*s - 3), ((s + 7)*(s**2 - 4*s + 2) + (5*s - 10)*(3*s**2 + 7*s - 3))*(s**2 - 4*s + 2), s)
  1577. >>> G = TransferFunction(2*s**2 + 5*s + 1, s**2 + 2*s + 3, s)
  1578. >>> F2 = Feedback(G, TransferFunction(1, 1, s))
  1579. >>> F2.doit()
  1580. TransferFunction((s**2 + 2*s + 3)*(2*s**2 + 5*s + 1), (s**2 + 2*s + 3)*(3*s**2 + 7*s + 4), s)
  1581. Use kwarg ``expand=True`` to expand the resultant transfer function.
  1582. Use ``cancel=True`` to cancel out the common terms in numerator and
  1583. denominator.
  1584. >>> F2.doit(cancel=True, expand=True)
  1585. TransferFunction(2*s**2 + 5*s + 1, 3*s**2 + 7*s + 4, s)
  1586. >>> F2.doit(expand=True)
  1587. TransferFunction(2*s**4 + 9*s**3 + 17*s**2 + 17*s + 3, 3*s**4 + 13*s**3 + 27*s**2 + 29*s + 12, s)
  1588. """
  1589. arg_list = list(self.sys1.args) if isinstance(self.sys1, Series) else [self.sys1]
  1590. # F_n and F_d are resultant TFs of num and den of Feedback.
  1591. F_n, unit = self.sys1.doit(), TransferFunction(1, 1, self.sys1.var)
  1592. if self.sign == -1:
  1593. F_d = Parallel(unit, Series(self.sys2, *arg_list)).doit()
  1594. else:
  1595. F_d = Parallel(unit, -Series(self.sys2, *arg_list)).doit()
  1596. _resultant_tf = TransferFunction(F_n.num * F_d.den, F_n.den * F_d.num, F_n.var)
  1597. if cancel:
  1598. _resultant_tf = _resultant_tf.simplify()
  1599. if expand:
  1600. _resultant_tf = _resultant_tf.expand()
  1601. return _resultant_tf
  1602. def _eval_rewrite_as_TransferFunction(self, num, den, sign, **kwargs):
  1603. return self.doit()
  1604. def __neg__(self):
  1605. return Feedback(-self.sys1, -self.sys2, self.sign)
  1606. def _is_invertible(a, b, sign):
  1607. """
  1608. Checks whether a given pair of MIMO
  1609. systems passed is invertible or not.
  1610. """
  1611. _mat = eye(a.num_outputs) - sign*(a.doit()._expr_mat)*(b.doit()._expr_mat)
  1612. _det = _mat.det()
  1613. return _det != 0
  1614. class MIMOFeedback(MIMOLinearTimeInvariant):
  1615. r"""
  1616. A class for representing closed-loop feedback interconnection between two
  1617. MIMO input/output systems.
  1618. Parameters
  1619. ==========
  1620. sys1 : MIMOSeries, TransferFunctionMatrix
  1621. The MIMO system placed on the feedforward path.
  1622. sys2 : MIMOSeries, TransferFunctionMatrix
  1623. The system placed on the feedback path
  1624. (often a feedback controller).
  1625. sign : int, optional
  1626. The sign of feedback. Can either be ``1``
  1627. (for positive feedback) or ``-1`` (for negative feedback).
  1628. Default value is `-1`.
  1629. Raises
  1630. ======
  1631. ValueError
  1632. When ``sys1`` and ``sys2`` are not using the
  1633. same complex variable of the Laplace transform.
  1634. Forward path model should have an equal number of inputs/outputs
  1635. to the feedback path outputs/inputs.
  1636. When product of ``sys1`` and ``sys2`` is not a square matrix.
  1637. When the equivalent MIMO system is not invertible.
  1638. TypeError
  1639. When either ``sys1`` or ``sys2`` is not a ``MIMOSeries`` or a
  1640. ``TransferFunctionMatrix`` object.
  1641. Examples
  1642. ========
  1643. >>> from sympy import Matrix, pprint
  1644. >>> from sympy.abc import s
  1645. >>> from sympy.physics.control.lti import TransferFunctionMatrix, MIMOFeedback
  1646. >>> plant_mat = Matrix([[1, 1/s], [0, 1]])
  1647. >>> controller_mat = Matrix([[10, 0], [0, 10]]) # Constant Gain
  1648. >>> plant = TransferFunctionMatrix.from_Matrix(plant_mat, s)
  1649. >>> controller = TransferFunctionMatrix.from_Matrix(controller_mat, s)
  1650. >>> feedback = MIMOFeedback(plant, controller) # Negative Feedback (default)
  1651. >>> pprint(feedback, use_unicode=False)
  1652. / [1 1] [10 0 ] \-1 [1 1]
  1653. | [- -] [-- - ] | [- -]
  1654. | [1 s] [1 1 ] | [1 s]
  1655. |I + [ ] *[ ] | * [ ]
  1656. | [0 1] [0 10] | [0 1]
  1657. | [- -] [- --] | [- -]
  1658. \ [1 1]{t} [1 1 ]{t}/ [1 1]{t}
  1659. To get the equivalent system matrix, use either ``doit`` or ``rewrite`` method.
  1660. >>> pprint(feedback.doit(), use_unicode=False)
  1661. [1 1 ]
  1662. [-- -----]
  1663. [11 121*s]
  1664. [ ]
  1665. [0 1 ]
  1666. [- -- ]
  1667. [1 11 ]{t}
  1668. To negate the ``MIMOFeedback`` object, use ``-`` operator.
  1669. >>> neg_feedback = -feedback
  1670. >>> pprint(neg_feedback.doit(), use_unicode=False)
  1671. [-1 -1 ]
  1672. [--- -----]
  1673. [ 11 121*s]
  1674. [ ]
  1675. [ 0 -1 ]
  1676. [ - --- ]
  1677. [ 1 11 ]{t}
  1678. See Also
  1679. ========
  1680. Feedback, MIMOSeries, MIMOParallel
  1681. """
  1682. def __new__(cls, sys1, sys2, sign=-1):
  1683. if not (isinstance(sys1, (TransferFunctionMatrix, MIMOSeries))
  1684. and isinstance(sys2, (TransferFunctionMatrix, MIMOSeries))):
  1685. raise TypeError("Unsupported type for `sys1` or `sys2` of MIMO Feedback.")
  1686. if sys1.num_inputs != sys2.num_outputs or \
  1687. sys1.num_outputs != sys2.num_inputs:
  1688. raise ValueError("Product of `sys1` and `sys2` "
  1689. "must yield a square matrix.")
  1690. if sign not in (-1, 1):
  1691. raise ValueError("Unsupported type for feedback. `sign` arg should "
  1692. "either be 1 (positive feedback loop) or -1 (negative feedback loop).")
  1693. if not _is_invertible(sys1, sys2, sign):
  1694. raise ValueError("Non-Invertible system inputted.")
  1695. if sys1.var != sys2.var:
  1696. raise ValueError("Both `sys1` and `sys2` should be using the"
  1697. " same complex variable.")
  1698. return super().__new__(cls, sys1, sys2, _sympify(sign))
  1699. @property
  1700. def sys1(self):
  1701. r"""
  1702. Returns the system placed on the feedforward path of the MIMO feedback interconnection.
  1703. Examples
  1704. ========
  1705. >>> from sympy import pprint
  1706. >>> from sympy.abc import s
  1707. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  1708. >>> tf1 = TransferFunction(s**2 + s + 1, s**2 - s + 1, s)
  1709. >>> tf2 = TransferFunction(1, s, s)
  1710. >>> tf3 = TransferFunction(1, 1, s)
  1711. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  1712. >>> sys2 = TransferFunctionMatrix([[tf3, tf3], [tf3, tf2]])
  1713. >>> F_1 = MIMOFeedback(sys1, sys2, 1)
  1714. >>> F_1.sys1
  1715. TransferFunctionMatrix(((TransferFunction(s**2 + s + 1, s**2 - s + 1, s), TransferFunction(1, s, s)), (TransferFunction(1, s, s), TransferFunction(s**2 + s + 1, s**2 - s + 1, s))))
  1716. >>> pprint(_, use_unicode=False)
  1717. [ 2 ]
  1718. [s + s + 1 1 ]
  1719. [---------- - ]
  1720. [ 2 s ]
  1721. [s - s + 1 ]
  1722. [ ]
  1723. [ 2 ]
  1724. [ 1 s + s + 1]
  1725. [ - ----------]
  1726. [ s 2 ]
  1727. [ s - s + 1]{t}
  1728. """
  1729. return self.args[0]
  1730. @property
  1731. def sys2(self):
  1732. r"""
  1733. Returns the feedback controller of the MIMO feedback interconnection.
  1734. Examples
  1735. ========
  1736. >>> from sympy import pprint
  1737. >>> from sympy.abc import s
  1738. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  1739. >>> tf1 = TransferFunction(s**2, s**3 - s + 1, s)
  1740. >>> tf2 = TransferFunction(1, s, s)
  1741. >>> tf3 = TransferFunction(1, 1, s)
  1742. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  1743. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  1744. >>> F_1 = MIMOFeedback(sys1, sys2)
  1745. >>> F_1.sys2
  1746. TransferFunctionMatrix(((TransferFunction(s**2, s**3 - s + 1, s), TransferFunction(1, 1, s)), (TransferFunction(1, 1, s), TransferFunction(1, s, s))))
  1747. >>> pprint(_, use_unicode=False)
  1748. [ 2 ]
  1749. [ s 1]
  1750. [---------- -]
  1751. [ 3 1]
  1752. [s - s + 1 ]
  1753. [ ]
  1754. [ 1 1]
  1755. [ - -]
  1756. [ 1 s]{t}
  1757. """
  1758. return self.args[1]
  1759. @property
  1760. def var(self):
  1761. r"""
  1762. Returns the complex variable of the Laplace transform used by all
  1763. the transfer functions involved in the MIMO feedback loop.
  1764. Examples
  1765. ========
  1766. >>> from sympy.abc import p
  1767. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  1768. >>> tf1 = TransferFunction(p, 1 - p, p)
  1769. >>> tf2 = TransferFunction(1, p, p)
  1770. >>> tf3 = TransferFunction(1, 1, p)
  1771. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  1772. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  1773. >>> F_1 = MIMOFeedback(sys1, sys2, 1) # Positive feedback
  1774. >>> F_1.var
  1775. p
  1776. """
  1777. return self.sys1.var
  1778. @property
  1779. def sign(self):
  1780. r"""
  1781. Returns the type of feedback interconnection of two models. ``1``
  1782. for Positive and ``-1`` for Negative.
  1783. """
  1784. return self.args[2]
  1785. @property
  1786. def sensitivity(self):
  1787. r"""
  1788. Returns the sensitivity function matrix of the feedback loop.
  1789. Sensitivity of a closed-loop system is the ratio of change
  1790. in the open loop gain to the change in the closed loop gain.
  1791. .. note::
  1792. This method would not return the complementary
  1793. sensitivity function.
  1794. Examples
  1795. ========
  1796. >>> from sympy import pprint
  1797. >>> from sympy.abc import p
  1798. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  1799. >>> tf1 = TransferFunction(p, 1 - p, p)
  1800. >>> tf2 = TransferFunction(1, p, p)
  1801. >>> tf3 = TransferFunction(1, 1, p)
  1802. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  1803. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  1804. >>> F_1 = MIMOFeedback(sys1, sys2, 1) # Positive feedback
  1805. >>> F_2 = MIMOFeedback(sys1, sys2) # Negative feedback
  1806. >>> pprint(F_1.sensitivity, use_unicode=False)
  1807. [ 4 3 2 5 4 2 ]
  1808. [- p + 3*p - 4*p + 3*p - 1 p - 2*p + 3*p - 3*p + 1 ]
  1809. [---------------------------- -----------------------------]
  1810. [ 4 3 2 5 4 3 2 ]
  1811. [ p + 3*p - 8*p + 8*p - 3 p + 3*p - 8*p + 8*p - 3*p]
  1812. [ ]
  1813. [ 4 3 2 3 2 ]
  1814. [ p - p - p + p 3*p - 6*p + 4*p - 1 ]
  1815. [ -------------------------- -------------------------- ]
  1816. [ 4 3 2 4 3 2 ]
  1817. [ p + 3*p - 8*p + 8*p - 3 p + 3*p - 8*p + 8*p - 3 ]
  1818. >>> pprint(F_2.sensitivity, use_unicode=False)
  1819. [ 4 3 2 5 4 2 ]
  1820. [p - 3*p + 2*p + p - 1 p - 2*p + 3*p - 3*p + 1]
  1821. [------------------------ --------------------------]
  1822. [ 4 3 5 4 2 ]
  1823. [ p - 3*p + 2*p - 1 p - 3*p + 2*p - p ]
  1824. [ ]
  1825. [ 4 3 2 4 3 ]
  1826. [ p - p - p + p 2*p - 3*p + 2*p - 1 ]
  1827. [ ------------------- --------------------- ]
  1828. [ 4 3 4 3 ]
  1829. [ p - 3*p + 2*p - 1 p - 3*p + 2*p - 1 ]
  1830. """
  1831. _sys1_mat = self.sys1.doit()._expr_mat
  1832. _sys2_mat = self.sys2.doit()._expr_mat
  1833. return (eye(self.sys1.num_inputs) - \
  1834. self.sign*_sys1_mat*_sys2_mat).inv()
  1835. def doit(self, cancel=True, expand=False, **hints):
  1836. r"""
  1837. Returns the resultant transfer function matrix obtained by the
  1838. feedback interconnection.
  1839. Examples
  1840. ========
  1841. >>> from sympy import pprint
  1842. >>> from sympy.abc import s
  1843. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  1844. >>> tf1 = TransferFunction(s, 1 - s, s)
  1845. >>> tf2 = TransferFunction(1, s, s)
  1846. >>> tf3 = TransferFunction(5, 1, s)
  1847. >>> tf4 = TransferFunction(s - 1, s, s)
  1848. >>> tf5 = TransferFunction(0, 1, s)
  1849. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  1850. >>> sys2 = TransferFunctionMatrix([[tf3, tf5], [tf5, tf5]])
  1851. >>> F_1 = MIMOFeedback(sys1, sys2, 1)
  1852. >>> pprint(F_1, use_unicode=False)
  1853. / [ s 1 ] [5 0] \-1 [ s 1 ]
  1854. | [----- - ] [- -] | [----- - ]
  1855. | [1 - s s ] [1 1] | [1 - s s ]
  1856. |I - [ ] *[ ] | * [ ]
  1857. | [ 5 s - 1] [0 0] | [ 5 s - 1]
  1858. | [ - -----] [- -] | [ - -----]
  1859. \ [ 1 s ]{t} [1 1]{t}/ [ 1 s ]{t}
  1860. >>> pprint(F_1.doit(), use_unicode=False)
  1861. [ -s s - 1 ]
  1862. [------- ----------- ]
  1863. [6*s - 1 s*(6*s - 1) ]
  1864. [ ]
  1865. [5*s - 5 (s - 1)*(6*s + 24)]
  1866. [------- ------------------]
  1867. [6*s - 1 s*(6*s - 1) ]{t}
  1868. If the user wants the resultant ``TransferFunctionMatrix`` object without
  1869. canceling the common factors then the ``cancel`` kwarg should be passed ``False``.
  1870. >>> pprint(F_1.doit(cancel=False), use_unicode=False)
  1871. [ 25*s*(1 - s) 25 - 25*s ]
  1872. [ -------------------- -------------- ]
  1873. [ 25*(1 - 6*s)*(1 - s) 25*s*(1 - 6*s) ]
  1874. [ ]
  1875. [s*(25*s - 25) + 5*(1 - s)*(6*s - 1) s*(s - 1)*(6*s - 1) + s*(25*s - 25)]
  1876. [----------------------------------- -----------------------------------]
  1877. [ (1 - s)*(6*s - 1) 2 ]
  1878. [ s *(6*s - 1) ]{t}
  1879. If the user wants the expanded form of the resultant transfer function matrix,
  1880. the ``expand`` kwarg should be passed as ``True``.
  1881. >>> pprint(F_1.doit(expand=True), use_unicode=False)
  1882. [ -s s - 1 ]
  1883. [------- -------- ]
  1884. [6*s - 1 2 ]
  1885. [ 6*s - s ]
  1886. [ ]
  1887. [ 2 ]
  1888. [5*s - 5 6*s + 18*s - 24]
  1889. [------- ----------------]
  1890. [6*s - 1 2 ]
  1891. [ 6*s - s ]{t}
  1892. """
  1893. _mat = self.sensitivity * self.sys1.doit()._expr_mat
  1894. _resultant_tfm = _to_TFM(_mat, self.var)
  1895. if cancel:
  1896. _resultant_tfm = _resultant_tfm.simplify()
  1897. if expand:
  1898. _resultant_tfm = _resultant_tfm.expand()
  1899. return _resultant_tfm
  1900. def _eval_rewrite_as_TransferFunctionMatrix(self, sys1, sys2, sign, **kwargs):
  1901. return self.doit()
  1902. def __neg__(self):
  1903. return MIMOFeedback(-self.sys1, -self.sys2, self.sign)
  1904. def _to_TFM(mat, var):
  1905. """Private method to convert ImmutableMatrix to TransferFunctionMatrix efficiently"""
  1906. to_tf = lambda expr: TransferFunction.from_rational_expression(expr, var)
  1907. arg = [[to_tf(expr) for expr in row] for row in mat.tolist()]
  1908. return TransferFunctionMatrix(arg)
  1909. class TransferFunctionMatrix(MIMOLinearTimeInvariant):
  1910. r"""
  1911. A class for representing the MIMO (multiple-input and multiple-output)
  1912. generalization of the SISO (single-input and single-output) transfer function.
  1913. It is a matrix of transfer functions (``TransferFunction``, SISO-``Series`` or SISO-``Parallel``).
  1914. There is only one argument, ``arg`` which is also the compulsory argument.
  1915. ``arg`` is expected to be strictly of the type list of lists
  1916. which holds the transfer functions or reducible to transfer functions.
  1917. Parameters
  1918. ==========
  1919. arg : Nested ``List`` (strictly).
  1920. Users are expected to input a nested list of ``TransferFunction``, ``Series``
  1921. and/or ``Parallel`` objects.
  1922. Examples
  1923. ========
  1924. .. note::
  1925. ``pprint()`` can be used for better visualization of ``TransferFunctionMatrix`` objects.
  1926. >>> from sympy.abc import s, p, a
  1927. >>> from sympy import pprint
  1928. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, Series, Parallel
  1929. >>> tf_1 = TransferFunction(s + a, s**2 + s + 1, s)
  1930. >>> tf_2 = TransferFunction(p**4 - 3*p + 2, s + p, s)
  1931. >>> tf_3 = TransferFunction(3, s + 2, s)
  1932. >>> tf_4 = TransferFunction(-a + p, 9*s - 9, s)
  1933. >>> tfm_1 = TransferFunctionMatrix([[tf_1], [tf_2], [tf_3]])
  1934. >>> tfm_1
  1935. TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(3, s + 2, s),)))
  1936. >>> tfm_1.var
  1937. s
  1938. >>> tfm_1.num_inputs
  1939. 1
  1940. >>> tfm_1.num_outputs
  1941. 3
  1942. >>> tfm_1.shape
  1943. (3, 1)
  1944. >>> tfm_1.args
  1945. (((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(3, s + 2, s),)),)
  1946. >>> tfm_2 = TransferFunctionMatrix([[tf_1, -tf_3], [tf_2, -tf_1], [tf_3, -tf_2]])
  1947. >>> tfm_2
  1948. TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s), TransferFunction(-3, s + 2, s)), (TransferFunction(p**4 - 3*p + 2, p + s, s), TransferFunction(-a - s, s**2 + s + 1, s)), (TransferFunction(3, s + 2, s), TransferFunction(-p**4 + 3*p - 2, p + s, s))))
  1949. >>> pprint(tfm_2, use_unicode=False) # pretty-printing for better visualization
  1950. [ a + s -3 ]
  1951. [ ---------- ----- ]
  1952. [ 2 s + 2 ]
  1953. [ s + s + 1 ]
  1954. [ ]
  1955. [ 4 ]
  1956. [p - 3*p + 2 -a - s ]
  1957. [------------ ---------- ]
  1958. [ p + s 2 ]
  1959. [ s + s + 1 ]
  1960. [ ]
  1961. [ 4 ]
  1962. [ 3 - p + 3*p - 2]
  1963. [ ----- --------------]
  1964. [ s + 2 p + s ]{t}
  1965. TransferFunctionMatrix can be transposed, if user wants to switch the input and output transfer functions
  1966. >>> tfm_2.transpose()
  1967. TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s), TransferFunction(p**4 - 3*p + 2, p + s, s), TransferFunction(3, s + 2, s)), (TransferFunction(-3, s + 2, s), TransferFunction(-a - s, s**2 + s + 1, s), TransferFunction(-p**4 + 3*p - 2, p + s, s))))
  1968. >>> pprint(_, use_unicode=False)
  1969. [ 4 ]
  1970. [ a + s p - 3*p + 2 3 ]
  1971. [---------- ------------ ----- ]
  1972. [ 2 p + s s + 2 ]
  1973. [s + s + 1 ]
  1974. [ ]
  1975. [ 4 ]
  1976. [ -3 -a - s - p + 3*p - 2]
  1977. [ ----- ---------- --------------]
  1978. [ s + 2 2 p + s ]
  1979. [ s + s + 1 ]{t}
  1980. >>> tf_5 = TransferFunction(5, s, s)
  1981. >>> tf_6 = TransferFunction(5*s, (2 + s**2), s)
  1982. >>> tf_7 = TransferFunction(5, (s*(2 + s**2)), s)
  1983. >>> tf_8 = TransferFunction(5, 1, s)
  1984. >>> tfm_3 = TransferFunctionMatrix([[tf_5, tf_6], [tf_7, tf_8]])
  1985. >>> tfm_3
  1986. TransferFunctionMatrix(((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)), (TransferFunction(5, s*(s**2 + 2), s), TransferFunction(5, 1, s))))
  1987. >>> pprint(tfm_3, use_unicode=False)
  1988. [ 5 5*s ]
  1989. [ - ------]
  1990. [ s 2 ]
  1991. [ s + 2]
  1992. [ ]
  1993. [ 5 5 ]
  1994. [---------- - ]
  1995. [ / 2 \ 1 ]
  1996. [s*\s + 2/ ]{t}
  1997. >>> tfm_3.var
  1998. s
  1999. >>> tfm_3.shape
  2000. (2, 2)
  2001. >>> tfm_3.num_outputs
  2002. 2
  2003. >>> tfm_3.num_inputs
  2004. 2
  2005. >>> tfm_3.args
  2006. (((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)), (TransferFunction(5, s*(s**2 + 2), s), TransferFunction(5, 1, s))),)
  2007. To access the ``TransferFunction`` at any index in the ``TransferFunctionMatrix``, use the index notation.
  2008. >>> tfm_3[1, 0] # gives the TransferFunction present at 2nd Row and 1st Col. Similar to that in Matrix classes
  2009. TransferFunction(5, s*(s**2 + 2), s)
  2010. >>> tfm_3[0, 0] # gives the TransferFunction present at 1st Row and 1st Col.
  2011. TransferFunction(5, s, s)
  2012. >>> tfm_3[:, 0] # gives the first column
  2013. TransferFunctionMatrix(((TransferFunction(5, s, s),), (TransferFunction(5, s*(s**2 + 2), s),)))
  2014. >>> pprint(_, use_unicode=False)
  2015. [ 5 ]
  2016. [ - ]
  2017. [ s ]
  2018. [ ]
  2019. [ 5 ]
  2020. [----------]
  2021. [ / 2 \]
  2022. [s*\s + 2/]{t}
  2023. >>> tfm_3[0, :] # gives the first row
  2024. TransferFunctionMatrix(((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)),))
  2025. >>> pprint(_, use_unicode=False)
  2026. [5 5*s ]
  2027. [- ------]
  2028. [s 2 ]
  2029. [ s + 2]{t}
  2030. To negate a transfer function matrix, ``-`` operator can be prepended:
  2031. >>> tfm_4 = TransferFunctionMatrix([[tf_2], [-tf_1], [tf_3]])
  2032. >>> -tfm_4
  2033. TransferFunctionMatrix(((TransferFunction(-p**4 + 3*p - 2, p + s, s),), (TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(-3, s + 2, s),)))
  2034. >>> tfm_5 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, -tf_1]])
  2035. >>> -tfm_5
  2036. TransferFunctionMatrix(((TransferFunction(-a - s, s**2 + s + 1, s), TransferFunction(-p**4 + 3*p - 2, p + s, s)), (TransferFunction(-3, s + 2, s), TransferFunction(a + s, s**2 + s + 1, s))))
  2037. ``subs()`` returns the ``TransferFunctionMatrix`` object with the value substituted in the expression. This will not
  2038. mutate your original ``TransferFunctionMatrix``.
  2039. >>> tfm_2.subs(p, 2) # substituting p everywhere in tfm_2 with 2.
  2040. TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s), TransferFunction(-3, s + 2, s)), (TransferFunction(12, s + 2, s), TransferFunction(-a - s, s**2 + s + 1, s)), (TransferFunction(3, s + 2, s), TransferFunction(-12, s + 2, s))))
  2041. >>> pprint(_, use_unicode=False)
  2042. [ a + s -3 ]
  2043. [---------- ----- ]
  2044. [ 2 s + 2 ]
  2045. [s + s + 1 ]
  2046. [ ]
  2047. [ 12 -a - s ]
  2048. [ ----- ----------]
  2049. [ s + 2 2 ]
  2050. [ s + s + 1]
  2051. [ ]
  2052. [ 3 -12 ]
  2053. [ ----- ----- ]
  2054. [ s + 2 s + 2 ]{t}
  2055. >>> pprint(tfm_2, use_unicode=False) # State of tfm_2 is unchanged after substitution
  2056. [ a + s -3 ]
  2057. [ ---------- ----- ]
  2058. [ 2 s + 2 ]
  2059. [ s + s + 1 ]
  2060. [ ]
  2061. [ 4 ]
  2062. [p - 3*p + 2 -a - s ]
  2063. [------------ ---------- ]
  2064. [ p + s 2 ]
  2065. [ s + s + 1 ]
  2066. [ ]
  2067. [ 4 ]
  2068. [ 3 - p + 3*p - 2]
  2069. [ ----- --------------]
  2070. [ s + 2 p + s ]{t}
  2071. ``subs()`` also supports multiple substitutions.
  2072. >>> tfm_2.subs({p: 2, a: 1}) # substituting p with 2 and a with 1
  2073. TransferFunctionMatrix(((TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(-3, s + 2, s)), (TransferFunction(12, s + 2, s), TransferFunction(-s - 1, s**2 + s + 1, s)), (TransferFunction(3, s + 2, s), TransferFunction(-12, s + 2, s))))
  2074. >>> pprint(_, use_unicode=False)
  2075. [ s + 1 -3 ]
  2076. [---------- ----- ]
  2077. [ 2 s + 2 ]
  2078. [s + s + 1 ]
  2079. [ ]
  2080. [ 12 -s - 1 ]
  2081. [ ----- ----------]
  2082. [ s + 2 2 ]
  2083. [ s + s + 1]
  2084. [ ]
  2085. [ 3 -12 ]
  2086. [ ----- ----- ]
  2087. [ s + 2 s + 2 ]{t}
  2088. Users can reduce the ``Series`` and ``Parallel`` elements of the matrix to ``TransferFunction`` by using
  2089. ``doit()``.
  2090. >>> tfm_6 = TransferFunctionMatrix([[Series(tf_3, tf_4), Parallel(tf_3, tf_4)]])
  2091. >>> tfm_6
  2092. TransferFunctionMatrix(((Series(TransferFunction(3, s + 2, s), TransferFunction(-a + p, 9*s - 9, s)), Parallel(TransferFunction(3, s + 2, s), TransferFunction(-a + p, 9*s - 9, s))),))
  2093. >>> pprint(tfm_6, use_unicode=False)
  2094. [ -a + p 3 -a + p 3 ]
  2095. [-------*----- ------- + -----]
  2096. [9*s - 9 s + 2 9*s - 9 s + 2]{t}
  2097. >>> tfm_6.doit()
  2098. TransferFunctionMatrix(((TransferFunction(-3*a + 3*p, (s + 2)*(9*s - 9), s), TransferFunction(27*s + (-a + p)*(s + 2) - 27, (s + 2)*(9*s - 9), s)),))
  2099. >>> pprint(_, use_unicode=False)
  2100. [ -3*a + 3*p 27*s + (-a + p)*(s + 2) - 27]
  2101. [----------------- ----------------------------]
  2102. [(s + 2)*(9*s - 9) (s + 2)*(9*s - 9) ]{t}
  2103. >>> tf_9 = TransferFunction(1, s, s)
  2104. >>> tf_10 = TransferFunction(1, s**2, s)
  2105. >>> tfm_7 = TransferFunctionMatrix([[Series(tf_9, tf_10), tf_9], [tf_10, Parallel(tf_9, tf_10)]])
  2106. >>> tfm_7
  2107. TransferFunctionMatrix(((Series(TransferFunction(1, s, s), TransferFunction(1, s**2, s)), TransferFunction(1, s, s)), (TransferFunction(1, s**2, s), Parallel(TransferFunction(1, s, s), TransferFunction(1, s**2, s)))))
  2108. >>> pprint(tfm_7, use_unicode=False)
  2109. [ 1 1 ]
  2110. [---- - ]
  2111. [ 2 s ]
  2112. [s*s ]
  2113. [ ]
  2114. [ 1 1 1]
  2115. [ -- -- + -]
  2116. [ 2 2 s]
  2117. [ s s ]{t}
  2118. >>> tfm_7.doit()
  2119. TransferFunctionMatrix(((TransferFunction(1, s**3, s), TransferFunction(1, s, s)), (TransferFunction(1, s**2, s), TransferFunction(s**2 + s, s**3, s))))
  2120. >>> pprint(_, use_unicode=False)
  2121. [1 1 ]
  2122. [-- - ]
  2123. [ 3 s ]
  2124. [s ]
  2125. [ ]
  2126. [ 2 ]
  2127. [1 s + s]
  2128. [-- ------]
  2129. [ 2 3 ]
  2130. [s s ]{t}
  2131. Addition, subtraction, and multiplication of transfer function matrices can form
  2132. unevaluated ``Series`` or ``Parallel`` objects.
  2133. - For addition and subtraction:
  2134. All the transfer function matrices must have the same shape.
  2135. - For multiplication (C = A * B):
  2136. The number of inputs of the first transfer function matrix (A) must be equal to the
  2137. number of outputs of the second transfer function matrix (B).
  2138. Also, use pretty-printing (``pprint``) to analyse better.
  2139. >>> tfm_8 = TransferFunctionMatrix([[tf_3], [tf_2], [-tf_1]])
  2140. >>> tfm_9 = TransferFunctionMatrix([[-tf_3]])
  2141. >>> tfm_10 = TransferFunctionMatrix([[tf_1], [tf_2], [tf_4]])
  2142. >>> tfm_11 = TransferFunctionMatrix([[tf_4], [-tf_1]])
  2143. >>> tfm_12 = TransferFunctionMatrix([[tf_4, -tf_1, tf_3], [-tf_2, -tf_4, -tf_3]])
  2144. >>> tfm_8 + tfm_10
  2145. MIMOParallel(TransferFunctionMatrix(((TransferFunction(3, s + 2, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a - s, s**2 + s + 1, s),))), TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a + p, 9*s - 9, s),))))
  2146. >>> pprint(_, use_unicode=False)
  2147. [ 3 ] [ a + s ]
  2148. [ ----- ] [ ---------- ]
  2149. [ s + 2 ] [ 2 ]
  2150. [ ] [ s + s + 1 ]
  2151. [ 4 ] [ ]
  2152. [p - 3*p + 2] [ 4 ]
  2153. [------------] + [p - 3*p + 2]
  2154. [ p + s ] [------------]
  2155. [ ] [ p + s ]
  2156. [ -a - s ] [ ]
  2157. [ ---------- ] [ -a + p ]
  2158. [ 2 ] [ ------- ]
  2159. [ s + s + 1 ]{t} [ 9*s - 9 ]{t}
  2160. >>> -tfm_10 - tfm_8
  2161. MIMOParallel(TransferFunctionMatrix(((TransferFunction(-a - s, s**2 + s + 1, s),), (TransferFunction(-p**4 + 3*p - 2, p + s, s),), (TransferFunction(a - p, 9*s - 9, s),))), TransferFunctionMatrix(((TransferFunction(-3, s + 2, s),), (TransferFunction(-p**4 + 3*p - 2, p + s, s),), (TransferFunction(a + s, s**2 + s + 1, s),))))
  2162. >>> pprint(_, use_unicode=False)
  2163. [ -a - s ] [ -3 ]
  2164. [ ---------- ] [ ----- ]
  2165. [ 2 ] [ s + 2 ]
  2166. [ s + s + 1 ] [ ]
  2167. [ ] [ 4 ]
  2168. [ 4 ] [- p + 3*p - 2]
  2169. [- p + 3*p - 2] + [--------------]
  2170. [--------------] [ p + s ]
  2171. [ p + s ] [ ]
  2172. [ ] [ a + s ]
  2173. [ a - p ] [ ---------- ]
  2174. [ ------- ] [ 2 ]
  2175. [ 9*s - 9 ]{t} [ s + s + 1 ]{t}
  2176. >>> tfm_12 * tfm_8
  2177. MIMOSeries(TransferFunctionMatrix(((TransferFunction(3, s + 2, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a - s, s**2 + s + 1, s),))), TransferFunctionMatrix(((TransferFunction(-a + p, 9*s - 9, s), TransferFunction(-a - s, s**2 + s + 1, s), TransferFunction(3, s + 2, s)), (TransferFunction(-p**4 + 3*p - 2, p + s, s), TransferFunction(a - p, 9*s - 9, s), TransferFunction(-3, s + 2, s)))))
  2178. >>> pprint(_, use_unicode=False)
  2179. [ 3 ]
  2180. [ ----- ]
  2181. [ -a + p -a - s 3 ] [ s + 2 ]
  2182. [ ------- ---------- -----] [ ]
  2183. [ 9*s - 9 2 s + 2] [ 4 ]
  2184. [ s + s + 1 ] [p - 3*p + 2]
  2185. [ ] *[------------]
  2186. [ 4 ] [ p + s ]
  2187. [- p + 3*p - 2 a - p -3 ] [ ]
  2188. [-------------- ------- -----] [ -a - s ]
  2189. [ p + s 9*s - 9 s + 2]{t} [ ---------- ]
  2190. [ 2 ]
  2191. [ s + s + 1 ]{t}
  2192. >>> tfm_12 * tfm_8 * tfm_9
  2193. MIMOSeries(TransferFunctionMatrix(((TransferFunction(-3, s + 2, s),),)), TransferFunctionMatrix(((TransferFunction(3, s + 2, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a - s, s**2 + s + 1, s),))), TransferFunctionMatrix(((TransferFunction(-a + p, 9*s - 9, s), TransferFunction(-a - s, s**2 + s + 1, s), TransferFunction(3, s + 2, s)), (TransferFunction(-p**4 + 3*p - 2, p + s, s), TransferFunction(a - p, 9*s - 9, s), TransferFunction(-3, s + 2, s)))))
  2194. >>> pprint(_, use_unicode=False)
  2195. [ 3 ]
  2196. [ ----- ]
  2197. [ -a + p -a - s 3 ] [ s + 2 ]
  2198. [ ------- ---------- -----] [ ]
  2199. [ 9*s - 9 2 s + 2] [ 4 ]
  2200. [ s + s + 1 ] [p - 3*p + 2] [ -3 ]
  2201. [ ] *[------------] *[-----]
  2202. [ 4 ] [ p + s ] [s + 2]{t}
  2203. [- p + 3*p - 2 a - p -3 ] [ ]
  2204. [-------------- ------- -----] [ -a - s ]
  2205. [ p + s 9*s - 9 s + 2]{t} [ ---------- ]
  2206. [ 2 ]
  2207. [ s + s + 1 ]{t}
  2208. >>> tfm_10 + tfm_8*tfm_9
  2209. MIMOParallel(TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a + p, 9*s - 9, s),))), MIMOSeries(TransferFunctionMatrix(((TransferFunction(-3, s + 2, s),),)), TransferFunctionMatrix(((TransferFunction(3, s + 2, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(-a - s, s**2 + s + 1, s),)))))
  2210. >>> pprint(_, use_unicode=False)
  2211. [ a + s ] [ 3 ]
  2212. [ ---------- ] [ ----- ]
  2213. [ 2 ] [ s + 2 ]
  2214. [ s + s + 1 ] [ ]
  2215. [ ] [ 4 ]
  2216. [ 4 ] [p - 3*p + 2] [ -3 ]
  2217. [p - 3*p + 2] + [------------] *[-----]
  2218. [------------] [ p + s ] [s + 2]{t}
  2219. [ p + s ] [ ]
  2220. [ ] [ -a - s ]
  2221. [ -a + p ] [ ---------- ]
  2222. [ ------- ] [ 2 ]
  2223. [ 9*s - 9 ]{t} [ s + s + 1 ]{t}
  2224. These unevaluated ``Series`` or ``Parallel`` objects can convert into the
  2225. resultant transfer function matrix using ``.doit()`` method or by
  2226. ``.rewrite(TransferFunctionMatrix)``.
  2227. >>> (-tfm_8 + tfm_10 + tfm_8*tfm_9).doit()
  2228. TransferFunctionMatrix(((TransferFunction((a + s)*(s + 2)**3 - 3*(s + 2)**2*(s**2 + s + 1) - 9*(s + 2)*(s**2 + s + 1), (s + 2)**3*(s**2 + s + 1), s),), (TransferFunction((p + s)*(-3*p**4 + 9*p - 6), (p + s)**2*(s + 2), s),), (TransferFunction((-a + p)*(s + 2)*(s**2 + s + 1)**2 + (a + s)*(s + 2)*(9*s - 9)*(s**2 + s + 1) + (3*a + 3*s)*(9*s - 9)*(s**2 + s + 1), (s + 2)*(9*s - 9)*(s**2 + s + 1)**2, s),)))
  2229. >>> (-tfm_12 * -tfm_8 * -tfm_9).rewrite(TransferFunctionMatrix)
  2230. TransferFunctionMatrix(((TransferFunction(3*(-3*a + 3*p)*(p + s)*(s + 2)*(s**2 + s + 1)**2 + 3*(-3*a - 3*s)*(p + s)*(s + 2)*(9*s - 9)*(s**2 + s + 1) + 3*(a + s)*(s + 2)**2*(9*s - 9)*(-p**4 + 3*p - 2)*(s**2 + s + 1), (p + s)*(s + 2)**3*(9*s - 9)*(s**2 + s + 1)**2, s),), (TransferFunction(3*(-a + p)*(p + s)*(s + 2)**2*(-p**4 + 3*p - 2)*(s**2 + s + 1) + 3*(3*a + 3*s)*(p + s)**2*(s + 2)*(9*s - 9) + 3*(p + s)*(s + 2)*(9*s - 9)*(-3*p**4 + 9*p - 6)*(s**2 + s + 1), (p + s)**2*(s + 2)**3*(9*s - 9)*(s**2 + s + 1), s),)))
  2231. See Also
  2232. ========
  2233. TransferFunction, MIMOSeries, MIMOParallel, Feedback
  2234. """
  2235. def __new__(cls, arg):
  2236. expr_mat_arg = []
  2237. try:
  2238. var = arg[0][0].var
  2239. except TypeError:
  2240. raise ValueError("`arg` param in TransferFunctionMatrix should "
  2241. "strictly be a nested list containing TransferFunction objects.")
  2242. for row_index, row in enumerate(arg):
  2243. temp = []
  2244. for col_index, element in enumerate(row):
  2245. if not isinstance(element, SISOLinearTimeInvariant):
  2246. raise TypeError("Each element is expected to be of type `SISOLinearTimeInvariant`.")
  2247. if var != element.var:
  2248. raise ValueError("Conflicting value(s) found for `var`. All TransferFunction instances in "
  2249. "TransferFunctionMatrix should use the same complex variable in Laplace domain.")
  2250. temp.append(element.to_expr())
  2251. expr_mat_arg.append(temp)
  2252. if isinstance(arg, (tuple, list, Tuple)):
  2253. # Making nested Tuple (sympy.core.containers.Tuple) from nested list or nested Python tuple
  2254. arg = Tuple(*(Tuple(*r, sympify=False) for r in arg), sympify=False)
  2255. obj = super(TransferFunctionMatrix, cls).__new__(cls, arg)
  2256. obj._expr_mat = ImmutableMatrix(expr_mat_arg)
  2257. return obj
  2258. @classmethod
  2259. def from_Matrix(cls, matrix, var):
  2260. """
  2261. Creates a new ``TransferFunctionMatrix`` efficiently from a SymPy Matrix of ``Expr`` objects.
  2262. Parameters
  2263. ==========
  2264. matrix : ``ImmutableMatrix`` having ``Expr``/``Number`` elements.
  2265. var : Symbol
  2266. Complex variable of the Laplace transform which will be used by the
  2267. all the ``TransferFunction`` objects in the ``TransferFunctionMatrix``.
  2268. Examples
  2269. ========
  2270. >>> from sympy.abc import s
  2271. >>> from sympy.physics.control.lti import TransferFunctionMatrix
  2272. >>> from sympy import Matrix, pprint
  2273. >>> M = Matrix([[s, 1/s], [1/(s+1), s]])
  2274. >>> M_tf = TransferFunctionMatrix.from_Matrix(M, s)
  2275. >>> pprint(M_tf, use_unicode=False)
  2276. [ s 1]
  2277. [ - -]
  2278. [ 1 s]
  2279. [ ]
  2280. [ 1 s]
  2281. [----- -]
  2282. [s + 1 1]{t}
  2283. >>> M_tf.elem_poles()
  2284. [[[], [0]], [[-1], []]]
  2285. >>> M_tf.elem_zeros()
  2286. [[[0], []], [[], [0]]]
  2287. """
  2288. return _to_TFM(matrix, var)
  2289. @property
  2290. def var(self):
  2291. """
  2292. Returns the complex variable used by all the transfer functions or
  2293. ``Series``/``Parallel`` objects in a transfer function matrix.
  2294. Examples
  2295. ========
  2296. >>> from sympy.abc import p, s
  2297. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, Series, Parallel
  2298. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  2299. >>> G2 = TransferFunction(p, 4 - p, p)
  2300. >>> G3 = TransferFunction(0, p**4 - 1, p)
  2301. >>> G4 = TransferFunction(s + 1, s**2 + s + 1, s)
  2302. >>> S1 = Series(G1, G2)
  2303. >>> S2 = Series(-G3, Parallel(G2, -G1))
  2304. >>> tfm1 = TransferFunctionMatrix([[G1], [G2], [G3]])
  2305. >>> tfm1.var
  2306. p
  2307. >>> tfm2 = TransferFunctionMatrix([[-S1, -S2], [S1, S2]])
  2308. >>> tfm2.var
  2309. p
  2310. >>> tfm3 = TransferFunctionMatrix([[G4]])
  2311. >>> tfm3.var
  2312. s
  2313. """
  2314. return self.args[0][0][0].var
  2315. @property
  2316. def num_inputs(self):
  2317. """
  2318. Returns the number of inputs of the system.
  2319. Examples
  2320. ========
  2321. >>> from sympy.abc import s, p
  2322. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  2323. >>> G1 = TransferFunction(s + 3, s**2 - 3, s)
  2324. >>> G2 = TransferFunction(4, s**2, s)
  2325. >>> G3 = TransferFunction(p**2 + s**2, p - 3, s)
  2326. >>> tfm_1 = TransferFunctionMatrix([[G2, -G1, G3], [-G2, -G1, -G3]])
  2327. >>> tfm_1.num_inputs
  2328. 3
  2329. See Also
  2330. ========
  2331. num_outputs
  2332. """
  2333. return self._expr_mat.shape[1]
  2334. @property
  2335. def num_outputs(self):
  2336. """
  2337. Returns the number of outputs of the system.
  2338. Examples
  2339. ========
  2340. >>> from sympy.abc import s
  2341. >>> from sympy.physics.control.lti import TransferFunctionMatrix
  2342. >>> from sympy import Matrix
  2343. >>> M_1 = Matrix([[s], [1/s]])
  2344. >>> TFM = TransferFunctionMatrix.from_Matrix(M_1, s)
  2345. >>> print(TFM)
  2346. TransferFunctionMatrix(((TransferFunction(s, 1, s),), (TransferFunction(1, s, s),)))
  2347. >>> TFM.num_outputs
  2348. 2
  2349. See Also
  2350. ========
  2351. num_inputs
  2352. """
  2353. return self._expr_mat.shape[0]
  2354. @property
  2355. def shape(self):
  2356. """
  2357. Returns the shape of the transfer function matrix, that is, ``(# of outputs, # of inputs)``.
  2358. Examples
  2359. ========
  2360. >>> from sympy.abc import s, p
  2361. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  2362. >>> tf1 = TransferFunction(p**2 - 1, s**4 + s**3 - p, p)
  2363. >>> tf2 = TransferFunction(1 - p, p**2 - 3*p + 7, p)
  2364. >>> tf3 = TransferFunction(3, 4, p)
  2365. >>> tfm1 = TransferFunctionMatrix([[tf1, -tf2]])
  2366. >>> tfm1.shape
  2367. (1, 2)
  2368. >>> tfm2 = TransferFunctionMatrix([[-tf2, tf3], [tf1, -tf1]])
  2369. >>> tfm2.shape
  2370. (2, 2)
  2371. """
  2372. return self._expr_mat.shape
  2373. def __neg__(self):
  2374. neg = -self._expr_mat
  2375. return _to_TFM(neg, self.var)
  2376. @_check_other_MIMO
  2377. def __add__(self, other):
  2378. if not isinstance(other, MIMOParallel):
  2379. return MIMOParallel(self, other)
  2380. other_arg_list = list(other.args)
  2381. return MIMOParallel(self, *other_arg_list)
  2382. @_check_other_MIMO
  2383. def __sub__(self, other):
  2384. return self + (-other)
  2385. @_check_other_MIMO
  2386. def __mul__(self, other):
  2387. if not isinstance(other, MIMOSeries):
  2388. return MIMOSeries(other, self)
  2389. other_arg_list = list(other.args)
  2390. return MIMOSeries(*other_arg_list, self)
  2391. def __getitem__(self, key):
  2392. trunc = self._expr_mat.__getitem__(key)
  2393. if isinstance(trunc, ImmutableMatrix):
  2394. return _to_TFM(trunc, self.var)
  2395. return TransferFunction.from_rational_expression(trunc, self.var)
  2396. def transpose(self):
  2397. """Returns the transpose of the ``TransferFunctionMatrix`` (switched input and output layers)."""
  2398. transposed_mat = self._expr_mat.transpose()
  2399. return _to_TFM(transposed_mat, self.var)
  2400. def elem_poles(self):
  2401. """
  2402. Returns the poles of each element of the ``TransferFunctionMatrix``.
  2403. .. note::
  2404. Actual poles of a MIMO system are NOT the poles of individual elements.
  2405. Examples
  2406. ========
  2407. >>> from sympy.abc import s
  2408. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  2409. >>> tf_1 = TransferFunction(3, (s + 1), s)
  2410. >>> tf_2 = TransferFunction(s + 6, (s + 1)*(s + 2), s)
  2411. >>> tf_3 = TransferFunction(s + 3, s**2 + 3*s + 2, s)
  2412. >>> tf_4 = TransferFunction(s + 2, s**2 + 5*s - 10, s)
  2413. >>> tfm_1 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, tf_4]])
  2414. >>> tfm_1
  2415. TransferFunctionMatrix(((TransferFunction(3, s + 1, s), TransferFunction(s + 6, (s + 1)*(s + 2), s)), (TransferFunction(s + 3, s**2 + 3*s + 2, s), TransferFunction(s + 2, s**2 + 5*s - 10, s))))
  2416. >>> tfm_1.elem_poles()
  2417. [[[-1], [-2, -1]], [[-2, -1], [-5/2 + sqrt(65)/2, -sqrt(65)/2 - 5/2]]]
  2418. See Also
  2419. ========
  2420. elem_zeros
  2421. """
  2422. return [[element.poles() for element in row] for row in self.doit().args[0]]
  2423. def elem_zeros(self):
  2424. """
  2425. Returns the zeros of each element of the ``TransferFunctionMatrix``.
  2426. .. note::
  2427. Actual zeros of a MIMO system are NOT the zeros of individual elements.
  2428. Examples
  2429. ========
  2430. >>> from sympy.abc import s
  2431. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  2432. >>> tf_1 = TransferFunction(3, (s + 1), s)
  2433. >>> tf_2 = TransferFunction(s + 6, (s + 1)*(s + 2), s)
  2434. >>> tf_3 = TransferFunction(s + 3, s**2 + 3*s + 2, s)
  2435. >>> tf_4 = TransferFunction(s**2 - 9*s + 20, s**2 + 5*s - 10, s)
  2436. >>> tfm_1 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, tf_4]])
  2437. >>> tfm_1
  2438. TransferFunctionMatrix(((TransferFunction(3, s + 1, s), TransferFunction(s + 6, (s + 1)*(s + 2), s)), (TransferFunction(s + 3, s**2 + 3*s + 2, s), TransferFunction(s**2 - 9*s + 20, s**2 + 5*s - 10, s))))
  2439. >>> tfm_1.elem_zeros()
  2440. [[[], [-6]], [[-3], [4, 5]]]
  2441. See Also
  2442. ========
  2443. elem_poles
  2444. """
  2445. return [[element.zeros() for element in row] for row in self.doit().args[0]]
  2446. def _flat(self):
  2447. """Returns flattened list of args in TransferFunctionMatrix"""
  2448. return [elem for tup in self.args[0] for elem in tup]
  2449. def _eval_evalf(self, prec):
  2450. """Calls evalf() on each transfer function in the transfer function matrix"""
  2451. dps = prec_to_dps(prec)
  2452. mat = self._expr_mat.applyfunc(lambda a: a.evalf(n=dps))
  2453. return _to_TFM(mat, self.var)
  2454. def _eval_simplify(self, **kwargs):
  2455. """Simplifies the transfer function matrix"""
  2456. simp_mat = self._expr_mat.applyfunc(lambda a: cancel(a, expand=False))
  2457. return _to_TFM(simp_mat, self.var)
  2458. def expand(self, **hints):
  2459. """Expands the transfer function matrix"""
  2460. expand_mat = self._expr_mat.expand(**hints)
  2461. return _to_TFM(expand_mat, self.var)