lti.py 173 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001
  1. from typing import Type
  2. from sympy import Interval, numer, Rational, solveset
  3. from sympy.core.add import Add
  4. from sympy.core.basic import Basic
  5. from sympy.core.containers import Tuple
  6. from sympy.core.evalf import EvalfMixin
  7. from sympy.core.expr import Expr
  8. from sympy.core.function import expand
  9. from sympy.core.logic import fuzzy_and
  10. from sympy.core.mul import Mul
  11. from sympy.core.numbers import I, pi, oo
  12. from sympy.core.power import Pow
  13. from sympy.core.singleton import S
  14. from sympy.core.symbol import Dummy, Symbol
  15. from sympy.functions import Abs
  16. from sympy.core.sympify import sympify, _sympify
  17. from sympy.matrices import Matrix, ImmutableMatrix, ImmutableDenseMatrix, eye, ShapeError, zeros
  18. from sympy.functions.elementary.exponential import (exp, log)
  19. from sympy.matrices.expressions import MatMul, MatAdd
  20. from sympy.polys import Poly, rootof
  21. from sympy.polys.polyroots import roots
  22. from sympy.polys.polytools import (cancel, degree)
  23. from sympy.series import limit
  24. from sympy.utilities.misc import filldedent
  25. from sympy.solvers.ode.systems import linodesolve
  26. from sympy.solvers.solveset import linsolve, linear_eq_to_matrix
  27. from mpmath.libmp.libmpf import prec_to_dps
  28. __all__ = ['TransferFunction', 'PIDController', 'Series', 'MIMOSeries', 'Parallel', 'MIMOParallel',
  29. 'Feedback', 'MIMOFeedback', 'TransferFunctionMatrix', 'StateSpace', 'gbt', 'bilinear', 'forward_diff', 'backward_diff',
  30. 'phase_margin', 'gain_margin']
  31. def _roots(poly, var):
  32. """ like roots, but works on higher-order polynomials. """
  33. r = roots(poly, var, multiple=True)
  34. n = degree(poly)
  35. if len(r) != n:
  36. r = [rootof(poly, var, k) for k in range(n)]
  37. return r
  38. def gbt(tf, sample_per, alpha):
  39. r"""
  40. Returns falling coefficients of H(z) from numerator and denominator.
  41. Explanation
  42. ===========
  43. Where H(z) is the corresponding discretized transfer function,
  44. discretized with the generalised bilinear transformation method.
  45. H(z) is obtained from the continuous transfer function H(s)
  46. by substituting $s(z) = \frac{z-1}{T(\alpha z + (1-\alpha))}$ into H(s), where T is the
  47. sample period.
  48. Coefficients are falling, i.e. $H(z) = \frac{az+b}{cz+d}$ is returned
  49. as [a, b], [c, d].
  50. Examples
  51. ========
  52. >>> from sympy.physics.control.lti import TransferFunction, gbt
  53. >>> from sympy.abc import s, L, R, T
  54. >>> tf = TransferFunction(1, s*L + R, s)
  55. >>> numZ, denZ = gbt(tf, T, 0.5)
  56. >>> numZ
  57. [T/(2*(L + R*T/2)), T/(2*(L + R*T/2))]
  58. >>> denZ
  59. [1, (-L + R*T/2)/(L + R*T/2)]
  60. >>> numZ, denZ = gbt(tf, T, 0)
  61. >>> numZ
  62. [T/L]
  63. >>> denZ
  64. [1, (-L + R*T)/L]
  65. >>> numZ, denZ = gbt(tf, T, 1)
  66. >>> numZ
  67. [T/(L + R*T), 0]
  68. >>> denZ
  69. [1, -L/(L + R*T)]
  70. >>> numZ, denZ = gbt(tf, T, 0.3)
  71. >>> numZ
  72. [3*T/(10*(L + 3*R*T/10)), 7*T/(10*(L + 3*R*T/10))]
  73. >>> denZ
  74. [1, (-L + 7*R*T/10)/(L + 3*R*T/10)]
  75. References
  76. ==========
  77. .. [1] https://www.polyu.edu.hk/ama/profile/gfzhang/Research/ZCC09_IJC.pdf
  78. """
  79. if not tf.is_SISO:
  80. raise NotImplementedError("Not implemented for MIMO systems.")
  81. T = sample_per # and sample period T
  82. s = tf.var
  83. z = s # dummy discrete variable z
  84. np = tf.num.as_poly(s).all_coeffs()
  85. dp = tf.den.as_poly(s).all_coeffs()
  86. alpha = Rational(alpha).limit_denominator(1000)
  87. # The next line results from multiplying H(z) with z^N/z^N
  88. N = max(len(np), len(dp)) - 1
  89. num = Add(*[ T**(N-i) * c * (z-1)**i * (alpha * z + 1 - alpha)**(N-i) for c, i in zip(np[::-1], range(len(np))) ])
  90. den = Add(*[ T**(N-i) * c * (z-1)**i * (alpha * z + 1 - alpha)**(N-i) for c, i in zip(dp[::-1], range(len(dp))) ])
  91. num_coefs = num.as_poly(z).all_coeffs()
  92. den_coefs = den.as_poly(z).all_coeffs()
  93. para = den_coefs[0]
  94. num_coefs = [coef/para for coef in num_coefs]
  95. den_coefs = [coef/para for coef in den_coefs]
  96. return num_coefs, den_coefs
  97. def bilinear(tf, sample_per):
  98. r"""
  99. Returns falling coefficients of H(z) from numerator and denominator.
  100. Explanation
  101. ===========
  102. Where H(z) is the corresponding discretized transfer function,
  103. discretized with the bilinear transform method.
  104. H(z) is obtained from the continuous transfer function H(s)
  105. by substituting $s(z) = \frac{2}{T}\frac{z-1}{z+1}$ into H(s), where T is the
  106. sample period.
  107. Coefficients are falling, i.e. $H(z) = \frac{az+b}{cz+d}$ is returned
  108. as [a, b], [c, d].
  109. Examples
  110. ========
  111. >>> from sympy.physics.control.lti import TransferFunction, bilinear
  112. >>> from sympy.abc import s, L, R, T
  113. >>> tf = TransferFunction(1, s*L + R, s)
  114. >>> numZ, denZ = bilinear(tf, T)
  115. >>> numZ
  116. [T/(2*(L + R*T/2)), T/(2*(L + R*T/2))]
  117. >>> denZ
  118. [1, (-L + R*T/2)/(L + R*T/2)]
  119. """
  120. return gbt(tf, sample_per, S.Half)
  121. def forward_diff(tf, sample_per):
  122. r"""
  123. Returns falling coefficients of H(z) from numerator and denominator.
  124. Explanation
  125. ===========
  126. Where H(z) is the corresponding discretized transfer function,
  127. discretized with the forward difference transform method.
  128. H(z) is obtained from the continuous transfer function H(s)
  129. by substituting $s(z) = \frac{z-1}{T}$ into H(s), where T is the
  130. sample period.
  131. Coefficients are falling, i.e. $H(z) = \frac{az+b}{cz+d}$ is returned
  132. as [a, b], [c, d].
  133. Examples
  134. ========
  135. >>> from sympy.physics.control.lti import TransferFunction, forward_diff
  136. >>> from sympy.abc import s, L, R, T
  137. >>> tf = TransferFunction(1, s*L + R, s)
  138. >>> numZ, denZ = forward_diff(tf, T)
  139. >>> numZ
  140. [T/L]
  141. >>> denZ
  142. [1, (-L + R*T)/L]
  143. """
  144. return gbt(tf, sample_per, S.Zero)
  145. def backward_diff(tf, sample_per):
  146. r"""
  147. Returns falling coefficients of H(z) from numerator and denominator.
  148. Explanation
  149. ===========
  150. Where H(z) is the corresponding discretized transfer function,
  151. discretized with the backward difference transform method.
  152. H(z) is obtained from the continuous transfer function H(s)
  153. by substituting $s(z) = \frac{z-1}{Tz}$ into H(s), where T is the
  154. sample period.
  155. Coefficients are falling, i.e. $H(z) = \frac{az+b}{cz+d}$ is returned
  156. as [a, b], [c, d].
  157. Examples
  158. ========
  159. >>> from sympy.physics.control.lti import TransferFunction, backward_diff
  160. >>> from sympy.abc import s, L, R, T
  161. >>> tf = TransferFunction(1, s*L + R, s)
  162. >>> numZ, denZ = backward_diff(tf, T)
  163. >>> numZ
  164. [T/(L + R*T), 0]
  165. >>> denZ
  166. [1, -L/(L + R*T)]
  167. """
  168. return gbt(tf, sample_per, S.One)
  169. def phase_margin(system):
  170. r"""
  171. Returns the phase margin of a continuous time system.
  172. Only applicable to Transfer Functions which can generate valid bode plots.
  173. Raises
  174. ======
  175. NotImplementedError
  176. When time delay terms are present in the system.
  177. ValueError
  178. When a SISO LTI system is not passed.
  179. When more than one free symbol is present in the system.
  180. The only variable in the transfer function should be
  181. the variable of the Laplace transform.
  182. Examples
  183. ========
  184. >>> from sympy.physics.control import TransferFunction, phase_margin
  185. >>> from sympy.abc import s
  186. >>> tf = TransferFunction(1, s**3 + 2*s**2 + s, s)
  187. >>> phase_margin(tf)
  188. 180*(-pi + atan((-1 + (-2*18**(1/3)/(9 + sqrt(93))**(1/3) + 12**(1/3)*(9 + sqrt(93))**(1/3))**2/36)/(-12**(1/3)*(9 + sqrt(93))**(1/3)/3 + 2*18**(1/3)/(3*(9 + sqrt(93))**(1/3)))))/pi + 180
  189. >>> phase_margin(tf).n()
  190. 21.3863897518751
  191. >>> tf1 = TransferFunction(s**3, s**2 + 5*s, s)
  192. >>> phase_margin(tf1)
  193. -180 + 180*(atan(sqrt(2)*(-51/10 - sqrt(101)/10)*sqrt(1 + sqrt(101))/(2*(sqrt(101)/2 + 51/2))) + pi)/pi
  194. >>> phase_margin(tf1).n()
  195. -25.1783920627277
  196. >>> tf2 = TransferFunction(1, s + 1, s)
  197. >>> phase_margin(tf2)
  198. -180
  199. See Also
  200. ========
  201. gain_margin
  202. References
  203. ==========
  204. .. [1] https://en.wikipedia.org/wiki/Phase_margin
  205. """
  206. from sympy.functions import arg
  207. if not isinstance(system, SISOLinearTimeInvariant):
  208. raise ValueError("Margins are only applicable for SISO LTI systems.")
  209. _w = Dummy("w", real=True)
  210. repl = I*_w
  211. expr = system.to_expr()
  212. len_free_symbols = len(expr.free_symbols)
  213. if expr.has(exp):
  214. raise NotImplementedError("Margins for systems with Time delay terms are not supported.")
  215. elif len_free_symbols > 1:
  216. raise ValueError("Extra degree of freedom found. Make sure"
  217. " that there are no free symbols in the dynamical system other"
  218. " than the variable of Laplace transform.")
  219. w_expr = expr.subs({system.var: repl})
  220. mag = 20*log(Abs(w_expr), 10)
  221. mag_sol = list(solveset(mag, _w, Interval(0, oo, left_open=True)))
  222. if (len(mag_sol) == 0):
  223. pm = S(-180)
  224. else:
  225. wcp = mag_sol[0]
  226. pm = ((arg(w_expr)*S(180)/pi).subs({_w:wcp}) + S(180)) % 360
  227. if(pm >= 180):
  228. pm = pm - 360
  229. return pm
  230. def gain_margin(system):
  231. r"""
  232. Returns the gain margin of a continuous time system.
  233. Only applicable to Transfer Functions which can generate valid bode plots.
  234. Raises
  235. ======
  236. NotImplementedError
  237. When time delay terms are present in the system.
  238. ValueError
  239. When a SISO LTI system is not passed.
  240. When more than one free symbol is present in the system.
  241. The only variable in the transfer function should be
  242. the variable of the Laplace transform.
  243. Examples
  244. ========
  245. >>> from sympy.physics.control import TransferFunction, gain_margin
  246. >>> from sympy.abc import s
  247. >>> tf = TransferFunction(1, s**3 + 2*s**2 + s, s)
  248. >>> gain_margin(tf)
  249. 20*log(2)/log(10)
  250. >>> gain_margin(tf).n()
  251. 6.02059991327962
  252. >>> tf1 = TransferFunction(s**3, s**2 + 5*s, s)
  253. >>> gain_margin(tf1)
  254. oo
  255. See Also
  256. ========
  257. phase_margin
  258. References
  259. ==========
  260. https://en.wikipedia.org/wiki/Bode_plot
  261. """
  262. if not isinstance(system, SISOLinearTimeInvariant):
  263. raise ValueError("Margins are only applicable for SISO LTI systems.")
  264. _w = Dummy("w", real=True)
  265. repl = I*_w
  266. expr = system.to_expr()
  267. len_free_symbols = len(expr.free_symbols)
  268. if expr.has(exp):
  269. raise NotImplementedError("Margins for systems with Time delay terms are not supported.")
  270. elif len_free_symbols > 1:
  271. raise ValueError("Extra degree of freedom found. Make sure"
  272. " that there are no free symbols in the dynamical system other"
  273. " than the variable of Laplace transform.")
  274. w_expr = expr.subs({system.var: repl})
  275. mag = 20*log(Abs(w_expr), 10)
  276. phase = w_expr
  277. phase_sol = list(solveset(numer(phase.as_real_imag()[1].cancel()),_w, Interval(0, oo, left_open = True)))
  278. if (len(phase_sol) == 0):
  279. gm = oo
  280. else:
  281. wcg = phase_sol[0]
  282. gm = -mag.subs({_w:wcg})
  283. return gm
  284. class LinearTimeInvariant(Basic, EvalfMixin):
  285. """A common class for all the Linear Time-Invariant Dynamical Systems."""
  286. _clstype: Type
  287. # Users should not directly interact with this class.
  288. def __new__(cls, *system, **kwargs):
  289. if cls is LinearTimeInvariant:
  290. raise NotImplementedError('The LTICommon class is not meant to be used directly.')
  291. return super(LinearTimeInvariant, cls).__new__(cls, *system, **kwargs)
  292. @classmethod
  293. def _check_args(cls, args):
  294. if not args:
  295. raise ValueError("At least 1 argument must be passed.")
  296. if not all(isinstance(arg, cls._clstype) for arg in args):
  297. raise TypeError(f"All arguments must be of type {cls._clstype}.")
  298. var_set = {arg.var for arg in args}
  299. if len(var_set) != 1:
  300. raise ValueError(filldedent(f"""
  301. All transfer functions should use the same complex variable
  302. of the Laplace transform. {len(var_set)} different
  303. values found."""))
  304. @property
  305. def is_SISO(self):
  306. """Returns `True` if the passed LTI system is SISO else returns False."""
  307. return self._is_SISO
  308. class SISOLinearTimeInvariant(LinearTimeInvariant):
  309. """A common class for all the SISO Linear Time-Invariant Dynamical Systems."""
  310. # Users should not directly interact with this class.
  311. @property
  312. def num_inputs(self):
  313. """Return the number of inputs for SISOLinearTimeInvariant."""
  314. return 1
  315. @property
  316. def num_outputs(self):
  317. """Return the number of outputs for SISOLinearTimeInvariant."""
  318. return 1
  319. _is_SISO = True
  320. class MIMOLinearTimeInvariant(LinearTimeInvariant):
  321. """A common class for all the MIMO Linear Time-Invariant Dynamical Systems."""
  322. # Users should not directly interact with this class.
  323. _is_SISO = False
  324. SISOLinearTimeInvariant._clstype = SISOLinearTimeInvariant
  325. MIMOLinearTimeInvariant._clstype = MIMOLinearTimeInvariant
  326. def _check_other_SISO(func):
  327. def wrapper(*args, **kwargs):
  328. if not isinstance(args[-1], SISOLinearTimeInvariant):
  329. return NotImplemented
  330. else:
  331. return func(*args, **kwargs)
  332. return wrapper
  333. def _check_other_MIMO(func):
  334. def wrapper(*args, **kwargs):
  335. if not isinstance(args[-1], MIMOLinearTimeInvariant):
  336. return NotImplemented
  337. else:
  338. return func(*args, **kwargs)
  339. return wrapper
  340. class TransferFunction(SISOLinearTimeInvariant):
  341. r"""
  342. A class for representing LTI (Linear, time-invariant) systems that can be strictly described
  343. by ratio of polynomials in the Laplace transform complex variable. The arguments
  344. are ``num``, ``den``, and ``var``, where ``num`` and ``den`` are numerator and
  345. denominator polynomials of the ``TransferFunction`` respectively, and the third argument is
  346. a complex variable of the Laplace transform used by these polynomials of the transfer function.
  347. ``num`` and ``den`` can be either polynomials or numbers, whereas ``var``
  348. has to be a :py:class:`~.Symbol`.
  349. Explanation
  350. ===========
  351. Generally, a dynamical system representing a physical model can be described in terms of Linear
  352. Ordinary Differential Equations like -
  353. $b_{m}y^{\left(m\right)}+b_{m-1}y^{\left(m-1\right)}+\dots+b_{1}y^{\left(1\right)}+b_{0}y=
  354. a_{n}x^{\left(n\right)}+a_{n-1}x^{\left(n-1\right)}+\dots+a_{1}x^{\left(1\right)}+a_{0}x$
  355. Here, $x$ is the input signal and $y$ is the output signal and superscript on both is the order of derivative
  356. (not exponent). Derivative is taken with respect to the independent variable, $t$. Also, generally $m$ is greater
  357. than $n$.
  358. It is not feasible to analyse the properties of such systems in their native form therefore, we use
  359. mathematical tools like Laplace transform to get a better perspective. Taking the Laplace transform
  360. of both the sides in the equation (at zero initial conditions), we get -
  361. $\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]=
  362. \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]$
  363. Using the linearity property of Laplace transform and also considering zero initial conditions
  364. (i.e. $y(0^{-}) = 0$, $y'(0^{-}) = 0$ and so on), the equation
  365. above gets translated to -
  366. $b_{m}\mathcal{L}[y^{\left(m\right)}]+\dots+b_{1}\mathcal{L}[y^{\left(1\right)}]+b_{0}\mathcal{L}[y]=
  367. a_{n}\mathcal{L}[x^{\left(n\right)}]+\dots+a_{1}\mathcal{L}[x^{\left(1\right)}]+a_{0}\mathcal{L}[x]$
  368. Now, applying Derivative property of Laplace transform,
  369. $b_{m}s^{m}\mathcal{L}[y]+\dots+b_{1}s\mathcal{L}[y]+b_{0}\mathcal{L}[y]=
  370. a_{n}s^{n}\mathcal{L}[x]+\dots+a_{1}s\mathcal{L}[x]+a_{0}\mathcal{L}[x]$
  371. Here, the superscript on $s$ is **exponent**. Note that the zero initial conditions assumption, mentioned above, is very important
  372. and cannot be ignored otherwise the dynamical system cannot be considered time-independent and the simplified equation above
  373. cannot be reached.
  374. Collecting $\mathcal{L}[y]$ and $\mathcal{L}[x]$ terms from both the sides and taking the ratio
  375. $\frac{ \mathcal{L}\left\{y\right\} }{ \mathcal{L}\left\{x\right\} }$, we get the typical rational form of transfer
  376. function.
  377. The numerator of the transfer function is, therefore, the Laplace transform of the output signal
  378. (The signals are represented as functions of time) and similarly, the denominator
  379. of the transfer function is the Laplace transform of the input signal. It is also a convention
  380. to denote the input and output signal's Laplace transform with capital alphabets like shown below.
  381. $H(s) = \frac{Y(s)}{X(s)} = \frac{ \mathcal{L}\left\{y(t)\right\} }{ \mathcal{L}\left\{x(t)\right\} }$
  382. $s$, also known as complex frequency, is a complex variable in the Laplace domain. It corresponds to the
  383. equivalent variable $t$, in the time domain. Transfer functions are sometimes also referred to as the Laplace
  384. transform of the system's impulse response. Transfer function, $H$, is represented as a rational
  385. function in $s$ like,
  386. $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}}$
  387. Parameters
  388. ==========
  389. num : Expr, Number
  390. The numerator polynomial of the transfer function.
  391. den : Expr, Number
  392. The denominator polynomial of the transfer function.
  393. var : Symbol
  394. Complex variable of the Laplace transform used by the
  395. polynomials of the transfer function.
  396. Raises
  397. ======
  398. TypeError
  399. When ``var`` is not a Symbol or when ``num`` or ``den`` is not a
  400. number or a polynomial.
  401. ValueError
  402. When ``den`` is zero.
  403. Examples
  404. ========
  405. >>> from sympy.abc import s, p, a
  406. >>> from sympy.physics.control.lti import TransferFunction
  407. >>> tf1 = TransferFunction(s + a, s**2 + s + 1, s)
  408. >>> tf1
  409. TransferFunction(a + s, s**2 + s + 1, s)
  410. >>> tf1.num
  411. a + s
  412. >>> tf1.den
  413. s**2 + s + 1
  414. >>> tf1.var
  415. s
  416. >>> tf1.args
  417. (a + s, s**2 + s + 1, s)
  418. Any complex variable can be used for ``var``.
  419. >>> tf2 = TransferFunction(a*p**3 - a*p**2 + s*p, p + a**2, p)
  420. >>> tf2
  421. TransferFunction(a*p**3 - a*p**2 + p*s, a**2 + p, p)
  422. >>> tf3 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  423. >>> tf3
  424. TransferFunction((p - 1)*(p + 3), (p - 1)*(p + 5), p)
  425. To negate a transfer function the ``-`` operator can be prepended:
  426. >>> tf4 = TransferFunction(-a + s, p**2 + s, p)
  427. >>> -tf4
  428. TransferFunction(a - s, p**2 + s, p)
  429. >>> tf5 = TransferFunction(s**4 - 2*s**3 + 5*s + 4, s + 4, s)
  430. >>> -tf5
  431. TransferFunction(-s**4 + 2*s**3 - 5*s - 4, s + 4, s)
  432. You can use a float or an integer (or other constants) as numerator and denominator:
  433. >>> tf6 = TransferFunction(1/2, 4, s)
  434. >>> tf6.num
  435. 0.500000000000000
  436. >>> tf6.den
  437. 4
  438. >>> tf6.var
  439. s
  440. >>> tf6.args
  441. (0.5, 4, s)
  442. You can take the integer power of a transfer function using the ``**`` operator:
  443. >>> tf7 = TransferFunction(s + a, s - a, s)
  444. >>> tf7**3
  445. TransferFunction((a + s)**3, (-a + s)**3, s)
  446. >>> tf7**0
  447. TransferFunction(1, 1, s)
  448. >>> tf8 = TransferFunction(p + 4, p - 3, p)
  449. >>> tf8**-1
  450. TransferFunction(p - 3, p + 4, p)
  451. Addition, subtraction, and multiplication of transfer functions can form
  452. unevaluated ``Series`` or ``Parallel`` objects.
  453. >>> tf9 = TransferFunction(s + 1, s**2 + s + 1, s)
  454. >>> tf10 = TransferFunction(s - p, s + 3, s)
  455. >>> tf11 = TransferFunction(4*s**2 + 2*s - 4, s - 1, s)
  456. >>> tf12 = TransferFunction(1 - s, s**2 + 4, s)
  457. >>> tf9 + tf10
  458. Parallel(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(-p + s, s + 3, s))
  459. >>> tf10 - tf11
  460. Parallel(TransferFunction(-p + s, s + 3, s), TransferFunction(-4*s**2 - 2*s + 4, s - 1, s))
  461. >>> tf9 * tf10
  462. Series(TransferFunction(s + 1, s**2 + s + 1, s), TransferFunction(-p + s, s + 3, s))
  463. >>> tf10 - (tf9 + tf12)
  464. Parallel(TransferFunction(-p + s, s + 3, s), TransferFunction(-s - 1, s**2 + s + 1, s), TransferFunction(s - 1, s**2 + 4, s))
  465. >>> tf10 - (tf9 * tf12)
  466. 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)))
  467. >>> tf11 * tf10 * tf9
  468. 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))
  469. >>> tf9 * tf11 + tf10 * tf12
  470. 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)))
  471. >>> (tf9 + tf12) * (tf10 + tf11)
  472. 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)))
  473. These unevaluated ``Series`` or ``Parallel`` objects can convert into the
  474. resultant transfer function using ``.doit()`` method or by ``.rewrite(TransferFunction)``.
  475. >>> ((tf9 + tf10) * tf12).doit()
  476. TransferFunction((1 - s)*((-p + s)*(s**2 + s + 1) + (s + 1)*(s + 3)), (s + 3)*(s**2 + 4)*(s**2 + s + 1), s)
  477. >>> (tf9 * tf10 - tf11 * tf12).rewrite(TransferFunction)
  478. 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)
  479. See Also
  480. ========
  481. Feedback, Series, Parallel
  482. References
  483. ==========
  484. .. [1] https://en.wikipedia.org/wiki/Transfer_function
  485. .. [2] https://en.wikipedia.org/wiki/Laplace_transform
  486. """
  487. def __new__(cls, num, den, var):
  488. num, den = _sympify(num), _sympify(den)
  489. if not isinstance(var, Symbol):
  490. raise TypeError("Variable input must be a Symbol.")
  491. if den == 0:
  492. raise ValueError("TransferFunction cannot have a zero denominator.")
  493. if (((isinstance(num, (Expr, TransferFunction, Series, Parallel)) and num.has(Symbol)) or num.is_number) and
  494. ((isinstance(den, (Expr, TransferFunction, Series, Parallel)) and den.has(Symbol)) or den.is_number)):
  495. cls.is_StateSpace_object = False
  496. return super(TransferFunction, cls).__new__(cls, num, den, var)
  497. else:
  498. raise TypeError("Unsupported type for numerator or denominator of TransferFunction.")
  499. @classmethod
  500. def from_rational_expression(cls, expr, var=None):
  501. r"""
  502. Creates a new ``TransferFunction`` efficiently from a rational expression.
  503. Parameters
  504. ==========
  505. expr : Expr, Number
  506. The rational expression representing the ``TransferFunction``.
  507. var : Symbol, optional
  508. Complex variable of the Laplace transform used by the
  509. polynomials of the transfer function.
  510. Raises
  511. ======
  512. ValueError
  513. When ``expr`` is of type ``Number`` and optional parameter ``var``
  514. is not passed.
  515. When ``expr`` has more than one variables and an optional parameter
  516. ``var`` is not passed.
  517. ZeroDivisionError
  518. When denominator of ``expr`` is zero or it has ``ComplexInfinity``
  519. in its numerator.
  520. Examples
  521. ========
  522. >>> from sympy.abc import s, p, a
  523. >>> from sympy.physics.control.lti import TransferFunction
  524. >>> expr1 = (s + 5)/(3*s**2 + 2*s + 1)
  525. >>> tf1 = TransferFunction.from_rational_expression(expr1)
  526. >>> tf1
  527. TransferFunction(s + 5, 3*s**2 + 2*s + 1, s)
  528. >>> expr2 = (a*p**3 - a*p**2 + s*p)/(p + a**2) # Expr with more than one variables
  529. >>> tf2 = TransferFunction.from_rational_expression(expr2, p)
  530. >>> tf2
  531. TransferFunction(a*p**3 - a*p**2 + p*s, a**2 + p, p)
  532. In case of conflict between two or more variables in a expression, SymPy will
  533. raise a ``ValueError``, if ``var`` is not passed by the user.
  534. >>> tf = TransferFunction.from_rational_expression((a + a*s)/(s**2 + s + 1))
  535. Traceback (most recent call last):
  536. ...
  537. ValueError: Conflicting values found for positional argument `var` ({a, s}). Specify it manually.
  538. This can be corrected by specifying the ``var`` parameter manually.
  539. >>> tf = TransferFunction.from_rational_expression((a + a*s)/(s**2 + s + 1), s)
  540. >>> tf
  541. TransferFunction(a*s + a, s**2 + s + 1, s)
  542. ``var`` also need to be specified when ``expr`` is a ``Number``
  543. >>> tf3 = TransferFunction.from_rational_expression(10, s)
  544. >>> tf3
  545. TransferFunction(10, 1, s)
  546. """
  547. expr = _sympify(expr)
  548. if var is None:
  549. _free_symbols = expr.free_symbols
  550. _len_free_symbols = len(_free_symbols)
  551. if _len_free_symbols == 1:
  552. var = list(_free_symbols)[0]
  553. elif _len_free_symbols == 0:
  554. raise ValueError(filldedent("""
  555. Positional argument `var` not found in the
  556. TransferFunction defined. Specify it manually."""))
  557. else:
  558. raise ValueError(filldedent("""
  559. Conflicting values found for positional argument `var` ({}).
  560. Specify it manually.""".format(_free_symbols)))
  561. _num, _den = expr.as_numer_denom()
  562. if _den == 0 or _num.has(S.ComplexInfinity):
  563. raise ZeroDivisionError("TransferFunction cannot have a zero denominator.")
  564. return cls(_num, _den, var)
  565. @classmethod
  566. def from_coeff_lists(cls, num_list, den_list, var):
  567. r"""
  568. Creates a new ``TransferFunction`` efficiently from a list of coefficients.
  569. Parameters
  570. ==========
  571. num_list : Sequence
  572. Sequence comprising of numerator coefficients.
  573. den_list : Sequence
  574. Sequence comprising of denominator coefficients.
  575. var : Symbol
  576. Complex variable of the Laplace transform used by the
  577. polynomials of the transfer function.
  578. Raises
  579. ======
  580. ZeroDivisionError
  581. When the constructed denominator is zero.
  582. Examples
  583. ========
  584. >>> from sympy.abc import s, p
  585. >>> from sympy.physics.control.lti import TransferFunction
  586. >>> num = [1, 0, 2]
  587. >>> den = [3, 2, 2, 1]
  588. >>> tf = TransferFunction.from_coeff_lists(num, den, s)
  589. >>> tf
  590. TransferFunction(s**2 + 2, 3*s**3 + 2*s**2 + 2*s + 1, s)
  591. >>> #Create a Transfer Function with more than one variable
  592. >>> tf1 = TransferFunction.from_coeff_lists([p, 1], [2*p, 0, 4], s)
  593. >>> tf1
  594. TransferFunction(p*s + 1, 2*p*s**2 + 4, s)
  595. """
  596. num_list = num_list[::-1]
  597. den_list = den_list[::-1]
  598. num_var_powers = [var**i for i in range(len(num_list))]
  599. den_var_powers = [var**i for i in range(len(den_list))]
  600. _num = sum(coeff * var_power for coeff, var_power in zip(num_list, num_var_powers))
  601. _den = sum(coeff * var_power for coeff, var_power in zip(den_list, den_var_powers))
  602. if _den == 0:
  603. raise ZeroDivisionError("TransferFunction cannot have a zero denominator.")
  604. return cls(_num, _den, var)
  605. @classmethod
  606. def from_zpk(cls, zeros, poles, gain, var):
  607. r"""
  608. Creates a new ``TransferFunction`` from given zeros, poles and gain.
  609. Parameters
  610. ==========
  611. zeros : Sequence
  612. Sequence comprising of zeros of transfer function.
  613. poles : Sequence
  614. Sequence comprising of poles of transfer function.
  615. gain : Number, Symbol, Expression
  616. A scalar value specifying gain of the model.
  617. var : Symbol
  618. Complex variable of the Laplace transform used by the
  619. polynomials of the transfer function.
  620. Examples
  621. ========
  622. >>> from sympy.abc import s, p, k
  623. >>> from sympy.physics.control.lti import TransferFunction
  624. >>> zeros = [1, 2, 3]
  625. >>> poles = [6, 5, 4]
  626. >>> gain = 7
  627. >>> tf = TransferFunction.from_zpk(zeros, poles, gain, s)
  628. >>> tf
  629. TransferFunction(7*(s - 3)*(s - 2)*(s - 1), (s - 6)*(s - 5)*(s - 4), s)
  630. >>> #Create a Transfer Function with variable poles and zeros
  631. >>> tf1 = TransferFunction.from_zpk([p, k], [p + k, p - k], 2, s)
  632. >>> tf1
  633. TransferFunction(2*(-k + s)*(-p + s), (-k - p + s)*(k - p + s), s)
  634. >>> #Complex poles or zeros are acceptable
  635. >>> tf2 = TransferFunction.from_zpk([0], [1-1j, 1+1j, 2], -2, s)
  636. >>> tf2
  637. TransferFunction(-2*s, (s - 2)*(s - 1.0 - 1.0*I)*(s - 1.0 + 1.0*I), s)
  638. """
  639. num_poly = 1
  640. den_poly = 1
  641. for zero in zeros:
  642. num_poly *= var - zero
  643. for pole in poles:
  644. den_poly *= var - pole
  645. return cls(gain*num_poly, den_poly, var)
  646. @property
  647. def num(self):
  648. """
  649. Returns the numerator polynomial of the transfer function.
  650. Examples
  651. ========
  652. >>> from sympy.abc import s, p
  653. >>> from sympy.physics.control.lti import TransferFunction
  654. >>> G1 = TransferFunction(s**2 + p*s + 3, s - 4, s)
  655. >>> G1.num
  656. p*s + s**2 + 3
  657. >>> G2 = TransferFunction((p + 5)*(p - 3), (p - 3)*(p + 1), p)
  658. >>> G2.num
  659. (p - 3)*(p + 5)
  660. """
  661. return self.args[0]
  662. @property
  663. def den(self):
  664. """
  665. Returns the denominator polynomial of the transfer function.
  666. Examples
  667. ========
  668. >>> from sympy.abc import s, p
  669. >>> from sympy.physics.control.lti import TransferFunction
  670. >>> G1 = TransferFunction(s + 4, p**3 - 2*p + 4, s)
  671. >>> G1.den
  672. p**3 - 2*p + 4
  673. >>> G2 = TransferFunction(3, 4, s)
  674. >>> G2.den
  675. 4
  676. """
  677. return self.args[1]
  678. @property
  679. def var(self):
  680. """
  681. Returns the complex variable of the Laplace transform used by the polynomials of
  682. the transfer function.
  683. Examples
  684. ========
  685. >>> from sympy.abc import s, p
  686. >>> from sympy.physics.control.lti import TransferFunction
  687. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  688. >>> G1.var
  689. p
  690. >>> G2 = TransferFunction(0, s - 5, s)
  691. >>> G2.var
  692. s
  693. """
  694. return self.args[2]
  695. def _eval_subs(self, old, new):
  696. arg_num = self.num.subs(old, new)
  697. arg_den = self.den.subs(old, new)
  698. argnew = TransferFunction(arg_num, arg_den, self.var)
  699. return self if old == self.var else argnew
  700. def _eval_evalf(self, prec):
  701. return TransferFunction(
  702. self.num._eval_evalf(prec),
  703. self.den._eval_evalf(prec),
  704. self.var)
  705. def _eval_simplify(self, **kwargs):
  706. tf = cancel(Mul(self.num, 1/self.den, evaluate=False), expand=False).as_numer_denom()
  707. num_, den_ = tf[0], tf[1]
  708. return TransferFunction(num_, den_, self.var)
  709. def _eval_rewrite_as_StateSpace(self, *args):
  710. """
  711. Returns the equivalent space model of the transfer function model.
  712. The state space model will be returned in the controllable canonical form.
  713. Unlike the space state to transfer function model conversion, the transfer function
  714. to state space model conversion is not unique. There can be multiple state space
  715. representations of a given transfer function model.
  716. Examples
  717. ========
  718. >>> from sympy.abc import s
  719. >>> from sympy.physics.control import TransferFunction, StateSpace
  720. >>> tf = TransferFunction(s**2 + 1, s**3 + 2*s + 10, s)
  721. >>> tf.rewrite(StateSpace)
  722. StateSpace(Matrix([
  723. [ 0, 1, 0],
  724. [ 0, 0, 1],
  725. [-10, -2, 0]]), Matrix([
  726. [0],
  727. [0],
  728. [1]]), Matrix([[1, 0, 1]]), Matrix([[0]]))
  729. """
  730. if not self.is_proper:
  731. raise ValueError("Transfer Function must be proper.")
  732. num_poly = Poly(self.num, self.var)
  733. den_poly = Poly(self.den, self.var)
  734. n = den_poly.degree()
  735. num_coeffs = num_poly.all_coeffs()
  736. den_coeffs = den_poly.all_coeffs()
  737. diff = n - num_poly.degree()
  738. num_coeffs = [0]*diff + num_coeffs
  739. a = den_coeffs[1:]
  740. a_mat = Matrix([[(-1)*coefficient/den_coeffs[0] for coefficient in reversed(a)]])
  741. vert = zeros(n-1, 1)
  742. mat = eye(n-1)
  743. A = vert.row_join(mat)
  744. A = A.col_join(a_mat)
  745. B = zeros(n, 1)
  746. B[n-1] = 1
  747. i = n
  748. C = []
  749. while(i > 0):
  750. C.append(num_coeffs[i] - den_coeffs[i]*num_coeffs[0])
  751. i -= 1
  752. C = Matrix([C])
  753. D = Matrix([num_coeffs[0]])
  754. return StateSpace(A, B, C, D)
  755. def expand(self):
  756. """
  757. Returns the transfer function with numerator and denominator
  758. in expanded form.
  759. Examples
  760. ========
  761. >>> from sympy.abc import s, p, a, b
  762. >>> from sympy.physics.control.lti import TransferFunction
  763. >>> G1 = TransferFunction((a - s)**2, (s**2 + a)**2, s)
  764. >>> G1.expand()
  765. TransferFunction(a**2 - 2*a*s + s**2, a**2 + 2*a*s**2 + s**4, s)
  766. >>> G2 = TransferFunction((p + 3*b)*(p - b), (p - b)*(p + 2*b), p)
  767. >>> G2.expand()
  768. TransferFunction(-3*b**2 + 2*b*p + p**2, -2*b**2 + b*p + p**2, p)
  769. """
  770. return TransferFunction(expand(self.num), expand(self.den), self.var)
  771. def dc_gain(self):
  772. """
  773. Computes the gain of the response as the frequency approaches zero.
  774. The DC gain is infinite for systems with pure integrators.
  775. Examples
  776. ========
  777. >>> from sympy.abc import s, p, a, b
  778. >>> from sympy.physics.control.lti import TransferFunction
  779. >>> tf1 = TransferFunction(s + 3, s**2 - 9, s)
  780. >>> tf1.dc_gain()
  781. -1/3
  782. >>> tf2 = TransferFunction(p**2, p - 3 + p**3, p)
  783. >>> tf2.dc_gain()
  784. 0
  785. >>> tf3 = TransferFunction(a*p**2 - b, s + b, s)
  786. >>> tf3.dc_gain()
  787. (a*p**2 - b)/b
  788. >>> tf4 = TransferFunction(1, s, s)
  789. >>> tf4.dc_gain()
  790. oo
  791. """
  792. m = Mul(self.num, Pow(self.den, -1, evaluate=False), evaluate=False)
  793. return limit(m, self.var, 0)
  794. def poles(self):
  795. """
  796. Returns the poles of a transfer function.
  797. Examples
  798. ========
  799. >>> from sympy.abc import s, p, a
  800. >>> from sympy.physics.control.lti import TransferFunction
  801. >>> tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  802. >>> tf1.poles()
  803. [-5, 1]
  804. >>> tf2 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  805. >>> tf2.poles()
  806. [I, I, -I, -I]
  807. >>> tf3 = TransferFunction(s**2, a*s + p, s)
  808. >>> tf3.poles()
  809. [-p/a]
  810. """
  811. return _roots(Poly(self.den, self.var), self.var)
  812. def zeros(self):
  813. """
  814. Returns the zeros of a transfer function.
  815. Examples
  816. ========
  817. >>> from sympy.abc import s, p, a
  818. >>> from sympy.physics.control.lti import TransferFunction
  819. >>> tf1 = TransferFunction((p + 3)*(p - 1), (p - 1)*(p + 5), p)
  820. >>> tf1.zeros()
  821. [-3, 1]
  822. >>> tf2 = TransferFunction((1 - s)**2, (s**2 + 1)**2, s)
  823. >>> tf2.zeros()
  824. [1, 1]
  825. >>> tf3 = TransferFunction(s**2, a*s + p, s)
  826. >>> tf3.zeros()
  827. [0, 0]
  828. """
  829. return _roots(Poly(self.num, self.var), self.var)
  830. def eval_frequency(self, other):
  831. """
  832. Returns the system response at any point in the real or complex plane.
  833. Examples
  834. ========
  835. >>> from sympy.abc import s, p, a
  836. >>> from sympy.physics.control.lti import TransferFunction
  837. >>> from sympy import I
  838. >>> tf1 = TransferFunction(1, s**2 + 2*s + 1, s)
  839. >>> omega = 0.1
  840. >>> tf1.eval_frequency(I*omega)
  841. 1/(0.99 + 0.2*I)
  842. >>> tf2 = TransferFunction(s**2, a*s + p, s)
  843. >>> tf2.eval_frequency(2)
  844. 4/(2*a + p)
  845. >>> tf2.eval_frequency(I*2)
  846. -4/(2*I*a + p)
  847. """
  848. arg_num = self.num.subs(self.var, other)
  849. arg_den = self.den.subs(self.var, other)
  850. argnew = TransferFunction(arg_num, arg_den, self.var).to_expr()
  851. return argnew.expand()
  852. def is_stable(self):
  853. """
  854. Returns True if the transfer function is asymptotically stable; else False.
  855. This would not check the marginal or conditional stability of the system.
  856. Examples
  857. ========
  858. >>> from sympy.abc import s, p, a
  859. >>> from sympy import symbols
  860. >>> from sympy.physics.control.lti import TransferFunction
  861. >>> q, r = symbols('q, r', negative=True)
  862. >>> tf1 = TransferFunction((1 - s)**2, (s + 1)**2, s)
  863. >>> tf1.is_stable()
  864. True
  865. >>> tf2 = TransferFunction((1 - p)**2, (s**2 + 1)**2, s)
  866. >>> tf2.is_stable()
  867. False
  868. >>> tf3 = TransferFunction(4, q*s - r, s)
  869. >>> tf3.is_stable()
  870. False
  871. >>> tf4 = TransferFunction(p + 1, a*p - s**2, p)
  872. >>> tf4.is_stable() is None # Not enough info about the symbols to determine stability
  873. True
  874. """
  875. return fuzzy_and(pole.as_real_imag()[0].is_negative for pole in self.poles())
  876. def __add__(self, other):
  877. if hasattr(other, "is_StateSpace_object") and other.is_StateSpace_object:
  878. return Parallel(self, other)
  879. elif isinstance(other, (TransferFunction, Series, Feedback)):
  880. if not self.var == other.var:
  881. raise ValueError(filldedent("""
  882. All the transfer functions should use the same complex variable
  883. of the Laplace transform."""))
  884. return Parallel(self, other)
  885. elif isinstance(other, Parallel):
  886. if not self.var == other.var:
  887. raise ValueError(filldedent("""
  888. All the transfer functions should use the same complex variable
  889. of the Laplace transform."""))
  890. arg_list = list(other.args)
  891. return Parallel(self, *arg_list)
  892. else:
  893. raise ValueError("TransferFunction cannot be added with {}.".
  894. format(type(other)))
  895. def __radd__(self, other):
  896. return self + other
  897. def __sub__(self, other):
  898. if hasattr(other, "is_StateSpace_object") and other.is_StateSpace_object:
  899. return Parallel(self, -other)
  900. elif isinstance(other, (TransferFunction, Series)):
  901. if not self.var == other.var:
  902. raise ValueError(filldedent("""
  903. All the transfer functions should use the same complex variable
  904. of the Laplace transform."""))
  905. return Parallel(self, -other)
  906. elif isinstance(other, Parallel):
  907. if not self.var == other.var:
  908. raise ValueError(filldedent("""
  909. All the transfer functions should use the same complex variable
  910. of the Laplace transform."""))
  911. arg_list = [-i for i in list(other.args)]
  912. return Parallel(self, *arg_list)
  913. else:
  914. raise ValueError("{} cannot be subtracted from a TransferFunction."
  915. .format(type(other)))
  916. def __rsub__(self, other):
  917. return -self + other
  918. def __mul__(self, other):
  919. if hasattr(other, "is_StateSpace_object") and other.is_StateSpace_object:
  920. return Series(self, other)
  921. elif isinstance(other, (TransferFunction, Parallel, Feedback)):
  922. if not self.var == other.var:
  923. raise ValueError(filldedent("""
  924. All the transfer functions should use the same complex variable
  925. of the Laplace transform."""))
  926. return Series(self, other)
  927. elif isinstance(other, Series):
  928. if not self.var == other.var:
  929. raise ValueError(filldedent("""
  930. All the transfer functions should use the same complex variable
  931. of the Laplace transform."""))
  932. arg_list = list(other.args)
  933. return Series(self, *arg_list)
  934. else:
  935. raise ValueError("TransferFunction cannot be multiplied with {}."
  936. .format(type(other)))
  937. __rmul__ = __mul__
  938. def __truediv__(self, other):
  939. if isinstance(other, TransferFunction):
  940. if not self.var == other.var:
  941. raise ValueError(filldedent("""
  942. All the transfer functions should use the same complex variable
  943. of the Laplace transform."""))
  944. return Series(self, TransferFunction(other.den, other.num, self.var))
  945. elif (isinstance(other, Parallel) and len(other.args
  946. ) == 2 and isinstance(other.args[0], TransferFunction)
  947. and isinstance(other.args[1], (Series, TransferFunction))):
  948. if not self.var == other.var:
  949. raise ValueError(filldedent("""
  950. Both TransferFunction and Parallel should use the
  951. same complex variable of the Laplace transform."""))
  952. if other.args[1] == self:
  953. # plant and controller with unit feedback.
  954. return Feedback(self, other.args[0])
  955. other_arg_list = list(other.args[1].args) if isinstance(
  956. other.args[1], Series) else other.args[1]
  957. if other_arg_list == other.args[1]:
  958. return Feedback(self, other_arg_list)
  959. elif self in other_arg_list:
  960. other_arg_list.remove(self)
  961. else:
  962. return Feedback(self, Series(*other_arg_list))
  963. if len(other_arg_list) == 1:
  964. return Feedback(self, *other_arg_list)
  965. else:
  966. return Feedback(self, Series(*other_arg_list))
  967. else:
  968. raise ValueError("TransferFunction cannot be divided by {}.".
  969. format(type(other)))
  970. __rtruediv__ = __truediv__
  971. def __pow__(self, p):
  972. p = sympify(p)
  973. if not p.is_Integer:
  974. raise ValueError("Exponent must be an integer.")
  975. if p is S.Zero:
  976. return TransferFunction(1, 1, self.var)
  977. elif p > 0:
  978. num_, den_ = self.num**p, self.den**p
  979. else:
  980. p = abs(p)
  981. num_, den_ = self.den**p, self.num**p
  982. return TransferFunction(num_, den_, self.var)
  983. def __neg__(self):
  984. return TransferFunction(-self.num, self.den, self.var)
  985. @property
  986. def is_proper(self):
  987. """
  988. Returns True if degree of the numerator polynomial is less than
  989. or equal to degree of the denominator polynomial, else False.
  990. Examples
  991. ========
  992. >>> from sympy.abc import s, p, a, b
  993. >>> from sympy.physics.control.lti import TransferFunction
  994. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  995. >>> tf1.is_proper
  996. False
  997. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*p + 2, p)
  998. >>> tf2.is_proper
  999. True
  1000. """
  1001. return degree(self.num, self.var) <= degree(self.den, self.var)
  1002. @property
  1003. def is_strictly_proper(self):
  1004. """
  1005. Returns True if degree of the numerator polynomial is strictly less
  1006. than degree of the denominator polynomial, else False.
  1007. Examples
  1008. ========
  1009. >>> from sympy.abc import s, p, a, b
  1010. >>> from sympy.physics.control.lti import TransferFunction
  1011. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1012. >>> tf1.is_strictly_proper
  1013. False
  1014. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1015. >>> tf2.is_strictly_proper
  1016. True
  1017. """
  1018. return degree(self.num, self.var) < degree(self.den, self.var)
  1019. @property
  1020. def is_biproper(self):
  1021. """
  1022. Returns True if degree of the numerator polynomial is equal to
  1023. degree of the denominator polynomial, else False.
  1024. Examples
  1025. ========
  1026. >>> from sympy.abc import s, p, a, b
  1027. >>> from sympy.physics.control.lti import TransferFunction
  1028. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1029. >>> tf1.is_biproper
  1030. True
  1031. >>> tf2 = TransferFunction(p**2, p + a, p)
  1032. >>> tf2.is_biproper
  1033. False
  1034. """
  1035. return degree(self.num, self.var) == degree(self.den, self.var)
  1036. def to_expr(self):
  1037. """
  1038. Converts a ``TransferFunction`` object to SymPy Expr.
  1039. Examples
  1040. ========
  1041. >>> from sympy.abc import s, p, a, b
  1042. >>> from sympy.physics.control.lti import TransferFunction
  1043. >>> from sympy import Expr
  1044. >>> tf1 = TransferFunction(s, a*s**2 + 1, s)
  1045. >>> tf1.to_expr()
  1046. s/(a*s**2 + 1)
  1047. >>> isinstance(_, Expr)
  1048. True
  1049. >>> tf2 = TransferFunction(1, (p + 3*b)*(b - p), p)
  1050. >>> tf2.to_expr()
  1051. 1/((b - p)*(3*b + p))
  1052. >>> tf3 = TransferFunction((s - 2)*(s - 3), (s - 1)*(s - 2)*(s - 3), s)
  1053. >>> tf3.to_expr()
  1054. ((s - 3)*(s - 2))/(((s - 3)*(s - 2)*(s - 1)))
  1055. """
  1056. if self.num != 1:
  1057. return Mul(self.num, Pow(self.den, -1, evaluate=False), evaluate=False)
  1058. else:
  1059. return Pow(self.den, -1, evaluate=False)
  1060. class PIDController(TransferFunction):
  1061. r"""
  1062. A class for representing PID (Proportional-Integral-Derivative)
  1063. controllers in control systems. The PIDController class is a subclass
  1064. of TransferFunction, representing the controller's transfer function
  1065. in the Laplace domain. The arguments are ``kp``, ``ki``, ``kd``,
  1066. ``tf``, and ``var``, where ``kp``, ``ki``, and ``kd`` are the
  1067. proportional, integral, and derivative gains respectively.``tf``
  1068. is the derivative filter time constant, which can be used to
  1069. filter out the noise and ``var`` is the complex variable used in
  1070. the transfer function.
  1071. Parameters
  1072. ==========
  1073. kp : Expr, Number
  1074. Proportional gain. Defaults to ``Symbol('kp')`` if not specified.
  1075. ki : Expr, Number
  1076. Integral gain. Defaults to ``Symbol('ki')`` if not specified.
  1077. kd : Expr, Number
  1078. Derivative gain. Defaults to ``Symbol('kd')`` if not specified.
  1079. tf : Expr, Number
  1080. Derivative filter time constant. Defaults to ``0`` if not specified.
  1081. var : Symbol
  1082. The complex frequency variable. Defaults to ``s`` if not specified.
  1083. Examples
  1084. ========
  1085. >>> from sympy import symbols
  1086. >>> from sympy.physics.control.lti import PIDController
  1087. >>> kp, ki, kd = symbols('kp ki kd')
  1088. >>> p1 = PIDController(kp, ki, kd)
  1089. >>> print(p1)
  1090. PIDController(kp, ki, kd, 0, s)
  1091. >>> p1.doit()
  1092. TransferFunction(kd*s**2 + ki + kp*s, s, s)
  1093. >>> p1.kp
  1094. kp
  1095. >>> p1.ki
  1096. ki
  1097. >>> p1.kd
  1098. kd
  1099. >>> p1.tf
  1100. 0
  1101. >>> p1.var
  1102. s
  1103. >>> p1.to_expr()
  1104. (kd*s**2 + ki + kp*s)/s
  1105. See Also
  1106. ========
  1107. TransferFunction
  1108. References
  1109. ==========
  1110. .. [1] https://en.wikipedia.org/wiki/PID_controller
  1111. .. [2] https://in.mathworks.com/help/control/ug/proportional-integral-derivative-pid-controllers.html
  1112. """
  1113. def __new__(cls, kp=Symbol('kp'), ki=Symbol('ki'), kd=Symbol('kd'), tf=0, var=Symbol('s')):
  1114. kp, ki, kd, tf = _sympify(kp), _sympify(ki), _sympify(kd), _sympify(tf)
  1115. num = kp*tf*var**2 + kp*var + ki*tf*var + ki + kd*var**2
  1116. den = tf*var**2 + var
  1117. obj = TransferFunction.__new__(cls, num, den, var)
  1118. obj._kp, obj._ki, obj._kd, obj._tf = kp, ki, kd, tf
  1119. return obj
  1120. def __repr__(self):
  1121. return f"PIDController({self.kp}, {self.ki}, {self.kd}, {self.tf}, {self.var})"
  1122. __str__ = __repr__
  1123. @property
  1124. def kp(self):
  1125. """
  1126. Returns the Proportional gain (kp) of the PIDController.
  1127. """
  1128. return self._kp
  1129. @property
  1130. def ki(self):
  1131. """
  1132. Returns the Integral gain (ki) of the PIDController.
  1133. """
  1134. return self._ki
  1135. @property
  1136. def kd(self):
  1137. """
  1138. Returns the Derivative gain (kd) of the PIDController.
  1139. """
  1140. return self._kd
  1141. @property
  1142. def tf(self):
  1143. """
  1144. Returns the Derivative filter time constant (tf) of the PIDController.
  1145. """
  1146. return self._tf
  1147. def doit(self):
  1148. """
  1149. Convert the PIDController into TransferFunction.
  1150. """
  1151. return TransferFunction(self.num, self.den, self.var)
  1152. def _flatten_args(args, _cls):
  1153. temp_args = []
  1154. for arg in args:
  1155. if isinstance(arg, _cls):
  1156. temp_args.extend(arg.args)
  1157. else:
  1158. temp_args.append(arg)
  1159. return tuple(temp_args)
  1160. def _dummify_args(_arg, var):
  1161. dummy_dict = {}
  1162. dummy_arg_list = []
  1163. for arg in _arg:
  1164. _s = Dummy()
  1165. dummy_dict[_s] = var
  1166. dummy_arg = arg.subs({var: _s})
  1167. dummy_arg_list.append(dummy_arg)
  1168. return dummy_arg_list, dummy_dict
  1169. class Series(SISOLinearTimeInvariant):
  1170. r"""
  1171. A class for representing a series configuration of SISO systems.
  1172. Parameters
  1173. ==========
  1174. args : SISOLinearTimeInvariant
  1175. SISO systems in a series configuration.
  1176. evaluate : Boolean, Keyword
  1177. When passed ``True``, returns the equivalent
  1178. ``Series(*args).doit()``. Set to ``False`` by default.
  1179. Raises
  1180. ======
  1181. ValueError
  1182. When no argument is passed.
  1183. ``var`` attribute is not same for every system.
  1184. TypeError
  1185. Any of the passed ``*args`` has unsupported type
  1186. A combination of SISO and MIMO systems is
  1187. passed. There should be homogeneity in the
  1188. type of systems passed, SISO in this case.
  1189. Examples
  1190. ========
  1191. >>> from sympy.abc import s, p, a, b
  1192. >>> from sympy import Matrix
  1193. >>> from sympy.physics.control.lti import TransferFunction, Series, Parallel, StateSpace
  1194. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1195. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1196. >>> tf3 = TransferFunction(p**2, p + s, s)
  1197. >>> S1 = Series(tf1, tf2)
  1198. >>> S1
  1199. Series(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s))
  1200. >>> S1.var
  1201. s
  1202. >>> S2 = Series(tf2, Parallel(tf3, -tf1))
  1203. >>> S2
  1204. 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)))
  1205. >>> S2.var
  1206. s
  1207. >>> S3 = Series(Parallel(tf1, tf2), Parallel(tf2, tf3))
  1208. >>> S3
  1209. 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)))
  1210. >>> S3.var
  1211. s
  1212. You can get the resultant transfer function by using ``.doit()`` method:
  1213. >>> S3 = Series(tf1, tf2, -tf3)
  1214. >>> S3.doit()
  1215. TransferFunction(-p**2*(s**3 - 2)*(a*p**2 + b*s), (-p + s)*(p + s)*(s**4 + 5*s + 6), s)
  1216. >>> S4 = Series(tf2, Parallel(tf1, -tf3))
  1217. >>> S4.doit()
  1218. 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)
  1219. You can also connect StateSpace which results in SISO
  1220. >>> A1 = Matrix([[-1]])
  1221. >>> B1 = Matrix([[1]])
  1222. >>> C1 = Matrix([[-1]])
  1223. >>> D1 = Matrix([1])
  1224. >>> A2 = Matrix([[0]])
  1225. >>> B2 = Matrix([[1]])
  1226. >>> C2 = Matrix([[1]])
  1227. >>> D2 = Matrix([[0]])
  1228. >>> ss1 = StateSpace(A1, B1, C1, D1)
  1229. >>> ss2 = StateSpace(A2, B2, C2, D2)
  1230. >>> S5 = Series(ss1, ss2)
  1231. >>> S5
  1232. Series(StateSpace(Matrix([[-1]]), Matrix([[1]]), Matrix([[-1]]), Matrix([[1]])), StateSpace(Matrix([[0]]), Matrix([[1]]), Matrix([[1]]), Matrix([[0]])))
  1233. >>> S5.doit()
  1234. StateSpace(Matrix([
  1235. [-1, 0],
  1236. [-1, 0]]), Matrix([
  1237. [1],
  1238. [1]]), Matrix([[0, 1]]), Matrix([[0]]))
  1239. Notes
  1240. =====
  1241. All the transfer functions should use the same complex variable
  1242. ``var`` of the Laplace transform.
  1243. See Also
  1244. ========
  1245. MIMOSeries, Parallel, TransferFunction, Feedback
  1246. """
  1247. def __new__(cls, *args, evaluate=False):
  1248. args = _flatten_args(args, Series)
  1249. # For StateSpace series connection
  1250. if args and any(isinstance(arg, StateSpace) or (hasattr(arg, 'is_StateSpace_object')
  1251. and arg.is_StateSpace_object)for arg in args):
  1252. # Check for SISO
  1253. if (args[0].num_inputs == 1) and (args[-1].num_outputs == 1):
  1254. # Check the interconnection
  1255. for i in range(1, len(args)):
  1256. if args[i].num_inputs != args[i-1].num_outputs:
  1257. raise ValueError(filldedent("""Systems with incompatible inputs and outputs
  1258. cannot be connected in Series."""))
  1259. cls._is_series_StateSpace = True
  1260. else:
  1261. raise ValueError("To use Series connection for MIMO systems use MIMOSeries instead.")
  1262. else:
  1263. cls._is_series_StateSpace = False
  1264. cls._check_args(args)
  1265. obj = super().__new__(cls, *args)
  1266. return obj.doit() if evaluate else obj
  1267. def __repr__(self):
  1268. systems_repr = ', '.join(repr(system) for system in self.args)
  1269. return f"Series({systems_repr})"
  1270. __str__ = __repr__
  1271. @property
  1272. def var(self):
  1273. """
  1274. Returns the complex variable used by all the transfer functions.
  1275. Examples
  1276. ========
  1277. >>> from sympy.abc import p
  1278. >>> from sympy.physics.control.lti import TransferFunction, Series, Parallel
  1279. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  1280. >>> G2 = TransferFunction(p, 4 - p, p)
  1281. >>> G3 = TransferFunction(0, p**4 - 1, p)
  1282. >>> Series(G1, G2).var
  1283. p
  1284. >>> Series(-G3, Parallel(G1, G2)).var
  1285. p
  1286. """
  1287. return self.args[0].var
  1288. def doit(self, **hints):
  1289. """
  1290. Returns the resultant transfer function or StateSpace obtained after evaluating
  1291. the series interconnection.
  1292. Examples
  1293. ========
  1294. >>> from sympy.abc import s, p, a, b
  1295. >>> from sympy.physics.control.lti import TransferFunction, Series
  1296. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1297. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1298. >>> Series(tf2, tf1).doit()
  1299. TransferFunction((s**3 - 2)*(a*p**2 + b*s), (-p + s)*(s**4 + 5*s + 6), s)
  1300. >>> Series(-tf1, -tf2).doit()
  1301. TransferFunction((2 - s**3)*(-a*p**2 - b*s), (-p + s)*(s**4 + 5*s + 6), s)
  1302. Notes
  1303. =====
  1304. If a series connection contains only TransferFunction components, the equivalent system returned
  1305. will be a TransferFunction. However, if a StateSpace object is used in any of the arguments,
  1306. the output will be a StateSpace object.
  1307. """
  1308. # Check if the system is a StateSpace
  1309. if self._is_series_StateSpace:
  1310. # Return the equivalent StateSpace model
  1311. res = self.args[0]
  1312. if not isinstance(res, StateSpace):
  1313. res = res.doit().rewrite(StateSpace)
  1314. for arg in self.args[1:]:
  1315. if not isinstance(arg, StateSpace):
  1316. arg = arg.doit().rewrite(StateSpace)
  1317. else:
  1318. arg = arg.doit()
  1319. arg = arg.doit()
  1320. res = arg * res
  1321. return res
  1322. _num_arg = (arg.doit().num for arg in self.args)
  1323. _den_arg = (arg.doit().den for arg in self.args)
  1324. res_num = Mul(*_num_arg, evaluate=True)
  1325. res_den = Mul(*_den_arg, evaluate=True)
  1326. return TransferFunction(res_num, res_den, self.var)
  1327. def _eval_rewrite_as_TransferFunction(self, *args, **kwargs):
  1328. if self._is_series_StateSpace:
  1329. return self.doit().rewrite(TransferFunction)[0][0]
  1330. return self.doit()
  1331. @_check_other_SISO
  1332. def __add__(self, other):
  1333. if isinstance(other, Parallel):
  1334. arg_list = list(other.args)
  1335. return Parallel(self, *arg_list)
  1336. return Parallel(self, other)
  1337. __radd__ = __add__
  1338. @_check_other_SISO
  1339. def __sub__(self, other):
  1340. return self + (-other)
  1341. def __rsub__(self, other):
  1342. return -self + other
  1343. @_check_other_SISO
  1344. def __mul__(self, other):
  1345. arg_list = list(self.args)
  1346. return Series(*arg_list, other)
  1347. def __truediv__(self, other):
  1348. if isinstance(other, TransferFunction):
  1349. return Series(*self.args, TransferFunction(other.den, other.num, other.var))
  1350. elif isinstance(other, Series):
  1351. tf_self = self.rewrite(TransferFunction)
  1352. tf_other = other.rewrite(TransferFunction)
  1353. return tf_self / tf_other
  1354. elif (isinstance(other, Parallel) and len(other.args) == 2
  1355. and isinstance(other.args[0], TransferFunction) and isinstance(other.args[1], Series)):
  1356. if not self.var == other.var:
  1357. raise ValueError(filldedent("""
  1358. All the transfer functions should use the same complex variable
  1359. of the Laplace transform."""))
  1360. self_arg_list = set(self.args)
  1361. other_arg_list = set(other.args[1].args)
  1362. res = list(self_arg_list ^ other_arg_list)
  1363. if len(res) == 0:
  1364. return Feedback(self, other.args[0])
  1365. elif len(res) == 1:
  1366. return Feedback(self, *res)
  1367. else:
  1368. return Feedback(self, Series(*res))
  1369. else:
  1370. raise ValueError("This transfer function expression is invalid.")
  1371. def __neg__(self):
  1372. return Series(TransferFunction(-1, 1, self.var), self)
  1373. def to_expr(self):
  1374. """Returns the equivalent ``Expr`` object."""
  1375. return Mul(*(arg.to_expr() for arg in self.args), evaluate=False)
  1376. @property
  1377. def is_proper(self):
  1378. """
  1379. Returns True if degree of the numerator polynomial of the resultant transfer
  1380. function is less than or equal to degree of the denominator polynomial of
  1381. the same, else False.
  1382. Examples
  1383. ========
  1384. >>> from sympy.abc import s, p, a, b
  1385. >>> from sympy.physics.control.lti import TransferFunction, Series
  1386. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  1387. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*s + 2, s)
  1388. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1389. >>> S1 = Series(-tf2, tf1)
  1390. >>> S1.is_proper
  1391. False
  1392. >>> S2 = Series(tf1, tf2, tf3)
  1393. >>> S2.is_proper
  1394. True
  1395. """
  1396. return self.doit().is_proper
  1397. @property
  1398. def is_strictly_proper(self):
  1399. """
  1400. Returns True if degree of the numerator polynomial of the resultant transfer
  1401. function is strictly less than degree of the denominator polynomial of
  1402. the same, else False.
  1403. Examples
  1404. ========
  1405. >>> from sympy.abc import s, p, a, b
  1406. >>> from sympy.physics.control.lti import TransferFunction, Series
  1407. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1408. >>> tf2 = TransferFunction(s**3 - 2, s**2 + 5*s + 6, s)
  1409. >>> tf3 = TransferFunction(1, s**2 + s + 1, s)
  1410. >>> S1 = Series(tf1, tf2)
  1411. >>> S1.is_strictly_proper
  1412. False
  1413. >>> S2 = Series(tf1, tf2, tf3)
  1414. >>> S2.is_strictly_proper
  1415. True
  1416. """
  1417. return self.doit().is_strictly_proper
  1418. @property
  1419. def is_biproper(self):
  1420. r"""
  1421. Returns True if degree of the numerator polynomial of the resultant transfer
  1422. function is equal to degree of the denominator polynomial of
  1423. the same, else False.
  1424. Examples
  1425. ========
  1426. >>> from sympy.abc import s, p, a, b
  1427. >>> from sympy.physics.control.lti import TransferFunction, Series
  1428. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1429. >>> tf2 = TransferFunction(p, s**2, s)
  1430. >>> tf3 = TransferFunction(s**2, 1, s)
  1431. >>> S1 = Series(tf1, -tf2)
  1432. >>> S1.is_biproper
  1433. False
  1434. >>> S2 = Series(tf2, tf3)
  1435. >>> S2.is_biproper
  1436. True
  1437. """
  1438. return self.doit().is_biproper
  1439. @property
  1440. def is_StateSpace_object(self):
  1441. return self._is_series_StateSpace
  1442. def _mat_mul_compatible(*args):
  1443. """To check whether shapes are compatible for matrix mul."""
  1444. return all(args[i].num_outputs == args[i+1].num_inputs for i in range(len(args)-1))
  1445. class MIMOSeries(MIMOLinearTimeInvariant):
  1446. r"""
  1447. A class for representing a series configuration of MIMO systems.
  1448. Parameters
  1449. ==========
  1450. args : MIMOLinearTimeInvariant
  1451. MIMO systems in a series configuration.
  1452. evaluate : Boolean, Keyword
  1453. When passed ``True``, returns the equivalent
  1454. ``MIMOSeries(*args).doit()``. Set to ``False`` by default.
  1455. Raises
  1456. ======
  1457. ValueError
  1458. When no argument is passed.
  1459. ``var`` attribute is not same for every system.
  1460. ``num_outputs`` of the MIMO system is not equal to the
  1461. ``num_inputs`` of its adjacent MIMO system. (Matrix
  1462. multiplication constraint, basically)
  1463. TypeError
  1464. Any of the passed ``*args`` has unsupported type
  1465. A combination of SISO and MIMO systems is
  1466. passed. There should be homogeneity in the
  1467. type of systems passed, MIMO in this case.
  1468. Examples
  1469. ========
  1470. >>> from sympy.abc import s
  1471. >>> from sympy.physics.control.lti import MIMOSeries, TransferFunctionMatrix, StateSpace
  1472. >>> from sympy import Matrix, pprint
  1473. >>> mat_a = Matrix([[5*s], [5]]) # 2 Outputs 1 Input
  1474. >>> mat_b = Matrix([[5, 1/(6*s**2)]]) # 1 Output 2 Inputs
  1475. >>> mat_c = Matrix([[1, s], [5/s, 1]]) # 2 Outputs 2 Inputs
  1476. >>> tfm_a = TransferFunctionMatrix.from_Matrix(mat_a, s)
  1477. >>> tfm_b = TransferFunctionMatrix.from_Matrix(mat_b, s)
  1478. >>> tfm_c = TransferFunctionMatrix.from_Matrix(mat_c, s)
  1479. >>> MIMOSeries(tfm_c, tfm_b, tfm_a)
  1480. 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),))))
  1481. >>> pprint(_, use_unicode=False) # For Better Visualization
  1482. [5*s] [1 s]
  1483. [---] [5 1 ] [- -]
  1484. [ 1 ] [- ----] [1 1]
  1485. [ ] *[1 2] *[ ]
  1486. [ 5 ] [ 6*s ]{t} [5 1]
  1487. [ - ] [- -]
  1488. [ 1 ]{t} [s 1]{t}
  1489. >>> MIMOSeries(tfm_c, tfm_b, tfm_a).doit()
  1490. 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))))
  1491. >>> 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).
  1492. [ 4 4 ]
  1493. [150*s + 25*s 150*s + 5*s]
  1494. [------------- ------------]
  1495. [ 3 2 ]
  1496. [ 6*s 6*s ]
  1497. [ ]
  1498. [ 3 3 ]
  1499. [ 150*s + 25 150*s + 5 ]
  1500. [ ----------- ---------- ]
  1501. [ 3 2 ]
  1502. [ 6*s 6*s ]{t}
  1503. >>> a1 = Matrix([[4, 1], [2, -3]])
  1504. >>> b1 = Matrix([[5, 2], [-3, -3]])
  1505. >>> c1 = Matrix([[2, -4], [0, 1]])
  1506. >>> d1 = Matrix([[3, 2], [1, -1]])
  1507. >>> a2 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  1508. >>> b2 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  1509. >>> c2 = Matrix([[4, 2, -3], [1, 4, 3]])
  1510. >>> d2 = Matrix([[-2, 4], [0, 1]])
  1511. >>> ss1 = StateSpace(a1, b1, c1, d1) #2 inputs, 2 outputs
  1512. >>> ss2 = StateSpace(a2, b2, c2, d2) #2 inputs, 2 outputs
  1513. >>> S1 = MIMOSeries(ss1, ss2) #(2 inputs, 2 outputs) -> (2 inputs, 2 outputs)
  1514. >>> S1
  1515. MIMOSeries(StateSpace(Matrix([
  1516. [4, 1],
  1517. [2, -3]]), Matrix([
  1518. [ 5, 2],
  1519. [-3, -3]]), Matrix([
  1520. [2, -4],
  1521. [0, 1]]), Matrix([
  1522. [3, 2],
  1523. [1, -1]])), StateSpace(Matrix([
  1524. [-3, 4, 2],
  1525. [-1, -3, 0],
  1526. [ 2, 5, 3]]), Matrix([
  1527. [ 1, 4],
  1528. [-3, -3],
  1529. [-2, 1]]), Matrix([
  1530. [4, 2, -3],
  1531. [1, 4, 3]]), Matrix([
  1532. [-2, 4],
  1533. [ 0, 1]])))
  1534. >>> S1.doit()
  1535. StateSpace(Matrix([
  1536. [ 4, 1, 0, 0, 0],
  1537. [ 2, -3, 0, 0, 0],
  1538. [ 2, 0, -3, 4, 2],
  1539. [-6, 9, -1, -3, 0],
  1540. [-4, 9, 2, 5, 3]]), Matrix([
  1541. [ 5, 2],
  1542. [ -3, -3],
  1543. [ 7, -2],
  1544. [-12, -3],
  1545. [ -5, -5]]), Matrix([
  1546. [-4, 12, 4, 2, -3],
  1547. [ 0, 1, 1, 4, 3]]), Matrix([
  1548. [-2, -8],
  1549. [ 1, -1]]))
  1550. Notes
  1551. =====
  1552. All the transfer function matrices should use the same complex variable ``var`` of the Laplace transform.
  1553. ``MIMOSeries(A, B)`` is not equivalent to ``A*B``. It is always in the reverse order, that is ``B*A``.
  1554. See Also
  1555. ========
  1556. Series, MIMOParallel
  1557. """
  1558. def __new__(cls, *args, evaluate=False):
  1559. if args and any(isinstance(arg, StateSpace) or (hasattr(arg, 'is_StateSpace_object')
  1560. and arg.is_StateSpace_object) for arg in args):
  1561. # Check compatibility
  1562. for i in range(1, len(args)):
  1563. if args[i].num_inputs != args[i - 1].num_outputs:
  1564. raise ValueError(filldedent("""Systems with incompatible inputs and outputs
  1565. cannot be connected in MIMOSeries."""))
  1566. obj = super().__new__(cls, *args)
  1567. cls._is_series_StateSpace = True
  1568. else:
  1569. cls._check_args(args)
  1570. cls._is_series_StateSpace = False
  1571. if _mat_mul_compatible(*args):
  1572. obj = super().__new__(cls, *args)
  1573. else:
  1574. raise ValueError(filldedent("""
  1575. Number of input signals do not match the number
  1576. of output signals of adjacent systems for some args."""))
  1577. return obj.doit() if evaluate else obj
  1578. @property
  1579. def var(self):
  1580. """
  1581. Returns the complex variable used by all the transfer functions.
  1582. Examples
  1583. ========
  1584. >>> from sympy.abc import p
  1585. >>> from sympy.physics.control.lti import TransferFunction, MIMOSeries, TransferFunctionMatrix
  1586. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  1587. >>> G2 = TransferFunction(p, 4 - p, p)
  1588. >>> G3 = TransferFunction(0, p**4 - 1, p)
  1589. >>> tfm_1 = TransferFunctionMatrix([[G1, G2, G3]])
  1590. >>> tfm_2 = TransferFunctionMatrix([[G1], [G2], [G3]])
  1591. >>> MIMOSeries(tfm_2, tfm_1).var
  1592. p
  1593. """
  1594. return self.args[0].var
  1595. @property
  1596. def num_inputs(self):
  1597. """Returns the number of input signals of the series system."""
  1598. return self.args[0].num_inputs
  1599. @property
  1600. def num_outputs(self):
  1601. """Returns the number of output signals of the series system."""
  1602. return self.args[-1].num_outputs
  1603. @property
  1604. def shape(self):
  1605. """Returns the shape of the equivalent MIMO system."""
  1606. return self.num_outputs, self.num_inputs
  1607. @property
  1608. def is_StateSpace_object(self):
  1609. return self._is_series_StateSpace
  1610. def doit(self, cancel=False, **kwargs):
  1611. """
  1612. Returns the resultant obtained after evaluating the MIMO systems arranged
  1613. in a series configuration. For TransferFunction systems it returns a TransferFunctionMatrix
  1614. and for StateSpace systems it returns the resultant StateSpace system.
  1615. Examples
  1616. ========
  1617. >>> from sympy.abc import s, p, a, b
  1618. >>> from sympy.physics.control.lti import TransferFunction, MIMOSeries, TransferFunctionMatrix
  1619. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1620. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1621. >>> tfm1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf2]])
  1622. >>> tfm2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf1]])
  1623. >>> MIMOSeries(tfm2, tfm1).doit()
  1624. 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))))
  1625. """
  1626. if self._is_series_StateSpace:
  1627. # Return the equivalent StateSpace model
  1628. res = self.args[0]
  1629. if not isinstance(res, StateSpace):
  1630. res = res.doit().rewrite(StateSpace)
  1631. for arg in self.args[1:]:
  1632. if not isinstance(arg, StateSpace):
  1633. arg = arg.doit().rewrite(StateSpace)
  1634. else:
  1635. arg = arg.doit()
  1636. res = arg * res
  1637. return res
  1638. _arg = (arg.doit()._expr_mat for arg in reversed(self.args))
  1639. if cancel:
  1640. res = MatMul(*_arg, evaluate=True)
  1641. return TransferFunctionMatrix.from_Matrix(res, self.var)
  1642. _dummy_args, _dummy_dict = _dummify_args(_arg, self.var)
  1643. res = MatMul(*_dummy_args, evaluate=True)
  1644. temp_tfm = TransferFunctionMatrix.from_Matrix(res, self.var)
  1645. return temp_tfm.subs(_dummy_dict)
  1646. def _eval_rewrite_as_TransferFunctionMatrix(self, *args, **kwargs):
  1647. if self._is_series_StateSpace:
  1648. return self.doit().rewrite(TransferFunction)
  1649. return self.doit()
  1650. @_check_other_MIMO
  1651. def __add__(self, other):
  1652. if isinstance(other, MIMOParallel):
  1653. arg_list = list(other.args)
  1654. return MIMOParallel(self, *arg_list)
  1655. return MIMOParallel(self, other)
  1656. __radd__ = __add__
  1657. @_check_other_MIMO
  1658. def __sub__(self, other):
  1659. return self + (-other)
  1660. def __rsub__(self, other):
  1661. return -self + other
  1662. @_check_other_MIMO
  1663. def __mul__(self, other):
  1664. if isinstance(other, MIMOSeries):
  1665. self_arg_list = list(self.args)
  1666. other_arg_list = list(other.args)
  1667. return MIMOSeries(*other_arg_list, *self_arg_list) # A*B = MIMOSeries(B, A)
  1668. arg_list = list(self.args)
  1669. return MIMOSeries(other, *arg_list)
  1670. def __neg__(self):
  1671. arg_list = list(self.args)
  1672. arg_list[0] = -arg_list[0]
  1673. return MIMOSeries(*arg_list)
  1674. class Parallel(SISOLinearTimeInvariant):
  1675. r"""
  1676. A class for representing a parallel configuration of SISO systems.
  1677. Parameters
  1678. ==========
  1679. args : SISOLinearTimeInvariant
  1680. SISO systems in a parallel arrangement.
  1681. evaluate : Boolean, Keyword
  1682. When passed ``True``, returns the equivalent
  1683. ``Parallel(*args).doit()``. Set to ``False`` by default.
  1684. Raises
  1685. ======
  1686. ValueError
  1687. When no argument is passed.
  1688. ``var`` attribute is not same for every system.
  1689. TypeError
  1690. Any of the passed ``*args`` has unsupported type
  1691. A combination of SISO and MIMO systems is
  1692. passed. There should be homogeneity in the
  1693. type of systems passed.
  1694. Examples
  1695. ========
  1696. >>> from sympy import Matrix
  1697. >>> from sympy.abc import s, p, a, b
  1698. >>> from sympy.physics.control.lti import TransferFunction, Parallel, Series, StateSpace
  1699. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1700. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1701. >>> tf3 = TransferFunction(p**2, p + s, s)
  1702. >>> P1 = Parallel(tf1, tf2)
  1703. >>> P1
  1704. Parallel(TransferFunction(a*p**2 + b*s, -p + s, s), TransferFunction(s**3 - 2, s**4 + 5*s + 6, s))
  1705. >>> P1.var
  1706. s
  1707. >>> P2 = Parallel(tf2, Series(tf3, -tf1))
  1708. >>> P2
  1709. 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)))
  1710. >>> P2.var
  1711. s
  1712. >>> P3 = Parallel(Series(tf1, tf2), Series(tf2, tf3))
  1713. >>> P3
  1714. 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)))
  1715. >>> P3.var
  1716. s
  1717. You can get the resultant transfer function by using ``.doit()`` method:
  1718. >>> Parallel(tf1, tf2, -tf3).doit()
  1719. 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)
  1720. >>> Parallel(tf2, Series(tf1, -tf3)).doit()
  1721. 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)
  1722. Parallel can be used to connect SISO ``StateSpace`` systems together.
  1723. >>> A1 = Matrix([[-1]])
  1724. >>> B1 = Matrix([[1]])
  1725. >>> C1 = Matrix([[-1]])
  1726. >>> D1 = Matrix([1])
  1727. >>> A2 = Matrix([[0]])
  1728. >>> B2 = Matrix([[1]])
  1729. >>> C2 = Matrix([[1]])
  1730. >>> D2 = Matrix([[0]])
  1731. >>> ss1 = StateSpace(A1, B1, C1, D1)
  1732. >>> ss2 = StateSpace(A2, B2, C2, D2)
  1733. >>> P4 = Parallel(ss1, ss2)
  1734. >>> P4
  1735. Parallel(StateSpace(Matrix([[-1]]), Matrix([[1]]), Matrix([[-1]]), Matrix([[1]])), StateSpace(Matrix([[0]]), Matrix([[1]]), Matrix([[1]]), Matrix([[0]])))
  1736. ``doit()`` can be used to find ``StateSpace`` equivalent for the system containing ``StateSpace`` objects.
  1737. >>> P4.doit()
  1738. StateSpace(Matrix([
  1739. [-1, 0],
  1740. [ 0, 0]]), Matrix([
  1741. [1],
  1742. [1]]), Matrix([[-1, 1]]), Matrix([[1]]))
  1743. >>> P4.rewrite(TransferFunction)
  1744. TransferFunction(s*(s + 1) + 1, s*(s + 1), s)
  1745. Notes
  1746. =====
  1747. All the transfer functions should use the same complex variable
  1748. ``var`` of the Laplace transform.
  1749. See Also
  1750. ========
  1751. Series, TransferFunction, Feedback
  1752. """
  1753. def __new__(cls, *args, evaluate=False):
  1754. args = _flatten_args(args, Parallel)
  1755. # For StateSpace parallel connection
  1756. if args and any(isinstance(arg, StateSpace) or (hasattr(arg, 'is_StateSpace_object')
  1757. and arg.is_StateSpace_object) for arg in args):
  1758. # Check for SISO
  1759. if all(arg.is_SISO for arg in args):
  1760. cls._is_parallel_StateSpace = True
  1761. else:
  1762. raise ValueError("To use Parallel connection for MIMO systems use MIMOParallel instead.")
  1763. else:
  1764. cls._is_parallel_StateSpace = False
  1765. cls._check_args(args)
  1766. obj = super().__new__(cls, *args)
  1767. return obj.doit() if evaluate else obj
  1768. def __repr__(self):
  1769. systems_repr = ', '.join(repr(system) for system in self.args)
  1770. return f"Parallel({systems_repr})"
  1771. __str__ = __repr__
  1772. @property
  1773. def var(self):
  1774. """
  1775. Returns the complex variable used by all the transfer functions.
  1776. Examples
  1777. ========
  1778. >>> from sympy.abc import p
  1779. >>> from sympy.physics.control.lti import TransferFunction, Parallel, Series
  1780. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  1781. >>> G2 = TransferFunction(p, 4 - p, p)
  1782. >>> G3 = TransferFunction(0, p**4 - 1, p)
  1783. >>> Parallel(G1, G2).var
  1784. p
  1785. >>> Parallel(-G3, Series(G1, G2)).var
  1786. p
  1787. """
  1788. return self.args[0].var
  1789. def doit(self, **hints):
  1790. """
  1791. Returns the resultant transfer function or state space obtained by
  1792. parallel connection of transfer functions or state space objects.
  1793. Examples
  1794. ========
  1795. >>> from sympy.abc import s, p, a, b
  1796. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1797. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1798. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1799. >>> Parallel(tf2, tf1).doit()
  1800. TransferFunction((-p + s)*(s**3 - 2) + (a*p**2 + b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s)
  1801. >>> Parallel(-tf1, -tf2).doit()
  1802. TransferFunction((2 - s**3)*(-p + s) + (-a*p**2 - b*s)*(s**4 + 5*s + 6), (-p + s)*(s**4 + 5*s + 6), s)
  1803. """
  1804. if self._is_parallel_StateSpace:
  1805. # Return the equivalent StateSpace model
  1806. res = self.args[0].doit()
  1807. if not isinstance(res, StateSpace):
  1808. res = res.rewrite(StateSpace)
  1809. for arg in self.args[1:]:
  1810. if not isinstance(arg, StateSpace):
  1811. arg = arg.doit().rewrite(StateSpace)
  1812. res += arg
  1813. return res
  1814. _arg = (arg.doit().to_expr() for arg in self.args)
  1815. res = Add(*_arg).as_numer_denom()
  1816. return TransferFunction(*res, self.var)
  1817. def _eval_rewrite_as_TransferFunction(self, *args, **kwargs):
  1818. if self._is_parallel_StateSpace:
  1819. return self.doit().rewrite(TransferFunction)[0][0]
  1820. return self.doit()
  1821. @_check_other_SISO
  1822. def __add__(self, other):
  1823. self_arg_list = list(self.args)
  1824. return Parallel(*self_arg_list, other)
  1825. __radd__ = __add__
  1826. @_check_other_SISO
  1827. def __sub__(self, other):
  1828. return self + (-other)
  1829. def __rsub__(self, other):
  1830. return -self + other
  1831. @_check_other_SISO
  1832. def __mul__(self, other):
  1833. if isinstance(other, Series):
  1834. arg_list = list(other.args)
  1835. return Series(self, *arg_list)
  1836. return Series(self, other)
  1837. def __neg__(self):
  1838. return Series(TransferFunction(-1, 1, self.var), self)
  1839. def to_expr(self):
  1840. """Returns the equivalent ``Expr`` object."""
  1841. return Add(*(arg.to_expr() for arg in self.args), evaluate=False)
  1842. @property
  1843. def is_proper(self):
  1844. """
  1845. Returns True if degree of the numerator polynomial of the resultant transfer
  1846. function is less than or equal to degree of the denominator polynomial of
  1847. the same, else False.
  1848. Examples
  1849. ========
  1850. >>> from sympy.abc import s, p, a, b
  1851. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1852. >>> tf1 = TransferFunction(b*s**2 + p**2 - a*p + s, b - p**2, s)
  1853. >>> tf2 = TransferFunction(p**2 - 4*p, p**3 + 3*s + 2, s)
  1854. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1855. >>> P1 = Parallel(-tf2, tf1)
  1856. >>> P1.is_proper
  1857. False
  1858. >>> P2 = Parallel(tf2, tf3)
  1859. >>> P2.is_proper
  1860. True
  1861. """
  1862. return self.doit().is_proper
  1863. @property
  1864. def is_strictly_proper(self):
  1865. """
  1866. Returns True if degree of the numerator polynomial of the resultant transfer
  1867. function is strictly less than degree of the denominator polynomial of
  1868. the same, else False.
  1869. Examples
  1870. ========
  1871. >>> from sympy.abc import s, p, a, b
  1872. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1873. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1874. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  1875. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1876. >>> P1 = Parallel(tf1, tf2)
  1877. >>> P1.is_strictly_proper
  1878. False
  1879. >>> P2 = Parallel(tf2, tf3)
  1880. >>> P2.is_strictly_proper
  1881. True
  1882. """
  1883. return self.doit().is_strictly_proper
  1884. @property
  1885. def is_biproper(self):
  1886. """
  1887. Returns True if degree of the numerator polynomial of the resultant transfer
  1888. function is equal to degree of the denominator polynomial of
  1889. the same, else False.
  1890. Examples
  1891. ========
  1892. >>> from sympy.abc import s, p, a, b
  1893. >>> from sympy.physics.control.lti import TransferFunction, Parallel
  1894. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  1895. >>> tf2 = TransferFunction(p**2, p + s, s)
  1896. >>> tf3 = TransferFunction(s, s**2 + s + 1, s)
  1897. >>> P1 = Parallel(tf1, -tf2)
  1898. >>> P1.is_biproper
  1899. True
  1900. >>> P2 = Parallel(tf2, tf3)
  1901. >>> P2.is_biproper
  1902. False
  1903. """
  1904. return self.doit().is_biproper
  1905. @property
  1906. def is_StateSpace_object(self):
  1907. return self._is_parallel_StateSpace
  1908. class MIMOParallel(MIMOLinearTimeInvariant):
  1909. r"""
  1910. A class for representing a parallel configuration of MIMO systems.
  1911. Parameters
  1912. ==========
  1913. args : MIMOLinearTimeInvariant
  1914. MIMO Systems in a parallel arrangement.
  1915. evaluate : Boolean, Keyword
  1916. When passed ``True``, returns the equivalent
  1917. ``MIMOParallel(*args).doit()``. Set to ``False`` by default.
  1918. Raises
  1919. ======
  1920. ValueError
  1921. When no argument is passed.
  1922. ``var`` attribute is not same for every system.
  1923. All MIMO systems passed do not have same shape.
  1924. TypeError
  1925. Any of the passed ``*args`` has unsupported type
  1926. A combination of SISO and MIMO systems is
  1927. passed. There should be homogeneity in the
  1928. type of systems passed, MIMO in this case.
  1929. Examples
  1930. ========
  1931. >>> from sympy.abc import s
  1932. >>> from sympy.physics.control.lti import TransferFunctionMatrix, MIMOParallel, StateSpace
  1933. >>> from sympy import Matrix, pprint
  1934. >>> expr_1 = 1/s
  1935. >>> expr_2 = s/(s**2-1)
  1936. >>> expr_3 = (2 + s)/(s**2 - 1)
  1937. >>> expr_4 = 5
  1938. >>> tfm_a = TransferFunctionMatrix.from_Matrix(Matrix([[expr_1, expr_2], [expr_3, expr_4]]), s)
  1939. >>> tfm_b = TransferFunctionMatrix.from_Matrix(Matrix([[expr_2, expr_1], [expr_4, expr_3]]), s)
  1940. >>> tfm_c = TransferFunctionMatrix.from_Matrix(Matrix([[expr_3, expr_4], [expr_1, expr_2]]), s)
  1941. >>> MIMOParallel(tfm_a, tfm_b, tfm_c)
  1942. 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)))))
  1943. >>> pprint(_, use_unicode=False) # For Better Visualization
  1944. [ 1 s ] [ s 1 ] [s + 2 5 ]
  1945. [ - ------] [------ - ] [------ - ]
  1946. [ s 2 ] [ 2 s ] [ 2 1 ]
  1947. [ s - 1] [s - 1 ] [s - 1 ]
  1948. [ ] + [ ] + [ ]
  1949. [s + 2 5 ] [ 5 s + 2 ] [ 1 s ]
  1950. [------ - ] [ - ------] [ - ------]
  1951. [ 2 1 ] [ 1 2 ] [ s 2 ]
  1952. [s - 1 ]{t} [ s - 1]{t} [ s - 1]{t}
  1953. >>> MIMOParallel(tfm_a, tfm_b, tfm_c).doit()
  1954. 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))))
  1955. >>> pprint(_, use_unicode=False)
  1956. [ 2 2 / 2 \ ]
  1957. [ s + s*(2*s + 2) - 1 2*s + 5*s*\s - 1/ - 1]
  1958. [ -------------------- -----------------------]
  1959. [ / 2 \ / 2 \ ]
  1960. [ s*\s - 1/ s*\s - 1/ ]
  1961. [ ]
  1962. [ 2 / 2 \ 2 ]
  1963. [s + s*(s + 2) + 5*s*\s - 1/ - 1 5*s + 2*s - 3 ]
  1964. [--------------------------------- -------------- ]
  1965. [ / 2 \ 2 ]
  1966. [ s*\s - 1/ s - 1 ]{t}
  1967. ``MIMOParallel`` can also be used to connect MIMO ``StateSpace`` systems.
  1968. >>> A1 = Matrix([[4, 1], [2, -3]])
  1969. >>> B1 = Matrix([[5, 2], [-3, -3]])
  1970. >>> C1 = Matrix([[2, -4], [0, 1]])
  1971. >>> D1 = Matrix([[3, 2], [1, -1]])
  1972. >>> A2 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  1973. >>> B2 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  1974. >>> C2 = Matrix([[4, 2, -3], [1, 4, 3]])
  1975. >>> D2 = Matrix([[-2, 4], [0, 1]])
  1976. >>> ss1 = StateSpace(A1, B1, C1, D1)
  1977. >>> ss2 = StateSpace(A2, B2, C2, D2)
  1978. >>> p1 = MIMOParallel(ss1, ss2)
  1979. >>> p1
  1980. MIMOParallel(StateSpace(Matrix([
  1981. [4, 1],
  1982. [2, -3]]), Matrix([
  1983. [ 5, 2],
  1984. [-3, -3]]), Matrix([
  1985. [2, -4],
  1986. [0, 1]]), Matrix([
  1987. [3, 2],
  1988. [1, -1]])), StateSpace(Matrix([
  1989. [-3, 4, 2],
  1990. [-1, -3, 0],
  1991. [ 2, 5, 3]]), Matrix([
  1992. [ 1, 4],
  1993. [-3, -3],
  1994. [-2, 1]]), Matrix([
  1995. [4, 2, -3],
  1996. [1, 4, 3]]), Matrix([
  1997. [-2, 4],
  1998. [ 0, 1]])))
  1999. ``doit()`` can be used to find ``StateSpace`` equivalent for the system containing ``StateSpace`` objects.
  2000. >>> p1.doit()
  2001. StateSpace(Matrix([
  2002. [4, 1, 0, 0, 0],
  2003. [2, -3, 0, 0, 0],
  2004. [0, 0, -3, 4, 2],
  2005. [0, 0, -1, -3, 0],
  2006. [0, 0, 2, 5, 3]]), Matrix([
  2007. [ 5, 2],
  2008. [-3, -3],
  2009. [ 1, 4],
  2010. [-3, -3],
  2011. [-2, 1]]), Matrix([
  2012. [2, -4, 4, 2, -3],
  2013. [0, 1, 1, 4, 3]]), Matrix([
  2014. [1, 6],
  2015. [1, 0]]))
  2016. Notes
  2017. =====
  2018. All the transfer function matrices should use the same complex variable
  2019. ``var`` of the Laplace transform.
  2020. See Also
  2021. ========
  2022. Parallel, MIMOSeries
  2023. """
  2024. def __new__(cls, *args, evaluate=False):
  2025. args = _flatten_args(args, MIMOParallel)
  2026. # For StateSpace Parallel connection
  2027. if args and any(isinstance(arg, StateSpace) or (hasattr(arg, 'is_StateSpace_object')
  2028. and arg.is_StateSpace_object) for arg in args):
  2029. if any(arg.num_inputs != args[0].num_inputs or arg.num_outputs != args[0].num_outputs
  2030. for arg in args[1:]):
  2031. raise ShapeError("Systems with incompatible inputs and outputs cannot be "
  2032. "connected in MIMOParallel.")
  2033. cls._is_parallel_StateSpace = True
  2034. else:
  2035. cls._check_args(args)
  2036. if any(arg.shape != args[0].shape for arg in args):
  2037. raise TypeError("Shape of all the args is not equal.")
  2038. cls._is_parallel_StateSpace = False
  2039. obj = super().__new__(cls, *args)
  2040. return obj.doit() if evaluate else obj
  2041. @property
  2042. def var(self):
  2043. """
  2044. Returns the complex variable used by all the systems.
  2045. Examples
  2046. ========
  2047. >>> from sympy.abc import p
  2048. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOParallel
  2049. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  2050. >>> G2 = TransferFunction(p, 4 - p, p)
  2051. >>> G3 = TransferFunction(0, p**4 - 1, p)
  2052. >>> G4 = TransferFunction(p**2, p**2 - 1, p)
  2053. >>> tfm_a = TransferFunctionMatrix([[G1, G2], [G3, G4]])
  2054. >>> tfm_b = TransferFunctionMatrix([[G2, G1], [G4, G3]])
  2055. >>> MIMOParallel(tfm_a, tfm_b).var
  2056. p
  2057. """
  2058. return self.args[0].var
  2059. @property
  2060. def num_inputs(self):
  2061. """Returns the number of input signals of the parallel system."""
  2062. return self.args[0].num_inputs
  2063. @property
  2064. def num_outputs(self):
  2065. """Returns the number of output signals of the parallel system."""
  2066. return self.args[0].num_outputs
  2067. @property
  2068. def shape(self):
  2069. """Returns the shape of the equivalent MIMO system."""
  2070. return self.num_outputs, self.num_inputs
  2071. @property
  2072. def is_StateSpace_object(self):
  2073. return self._is_parallel_StateSpace
  2074. def doit(self, **hints):
  2075. """
  2076. Returns the resultant transfer function matrix or StateSpace obtained after evaluating
  2077. the MIMO systems arranged in a parallel configuration.
  2078. Examples
  2079. ========
  2080. >>> from sympy.abc import s, p, a, b
  2081. >>> from sympy.physics.control.lti import TransferFunction, MIMOParallel, TransferFunctionMatrix
  2082. >>> tf1 = TransferFunction(a*p**2 + b*s, s - p, s)
  2083. >>> tf2 = TransferFunction(s**3 - 2, s**4 + 5*s + 6, s)
  2084. >>> tfm_1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  2085. >>> tfm_2 = TransferFunctionMatrix([[tf2, tf1], [tf1, tf2]])
  2086. >>> MIMOParallel(tfm_1, tfm_2).doit()
  2087. 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))))
  2088. """
  2089. if self._is_parallel_StateSpace:
  2090. # Return the equivalent StateSpace model.
  2091. res = self.args[0]
  2092. if not isinstance(res, StateSpace):
  2093. res = res.doit().rewrite(StateSpace)
  2094. for arg in self.args[1:]:
  2095. if not isinstance(arg, StateSpace):
  2096. arg = arg.doit().rewrite(StateSpace)
  2097. else:
  2098. arg = arg.doit()
  2099. res += arg
  2100. return res
  2101. _arg = (arg.doit()._expr_mat for arg in self.args)
  2102. res = MatAdd(*_arg, evaluate=True)
  2103. return TransferFunctionMatrix.from_Matrix(res, self.var)
  2104. def _eval_rewrite_as_TransferFunctionMatrix(self, *args, **kwargs):
  2105. if self._is_parallel_StateSpace:
  2106. return self.doit().rewrite(TransferFunction)
  2107. return self.doit()
  2108. @_check_other_MIMO
  2109. def __add__(self, other):
  2110. self_arg_list = list(self.args)
  2111. return MIMOParallel(*self_arg_list, other)
  2112. __radd__ = __add__
  2113. @_check_other_MIMO
  2114. def __sub__(self, other):
  2115. return self + (-other)
  2116. def __rsub__(self, other):
  2117. return -self + other
  2118. @_check_other_MIMO
  2119. def __mul__(self, other):
  2120. if isinstance(other, MIMOSeries):
  2121. arg_list = list(other.args)
  2122. return MIMOSeries(*arg_list, self)
  2123. return MIMOSeries(other, self)
  2124. def __neg__(self):
  2125. arg_list = [-arg for arg in list(self.args)]
  2126. return MIMOParallel(*arg_list)
  2127. class Feedback(SISOLinearTimeInvariant):
  2128. r"""
  2129. A class for representing closed-loop feedback interconnection between two
  2130. SISO input/output systems.
  2131. The first argument, ``sys1``, is the feedforward part of the closed-loop
  2132. system or in simple words, the dynamical model representing the process
  2133. to be controlled. The second argument, ``sys2``, is the feedback system
  2134. and controls the fed back signal to ``sys1``. Both ``sys1`` and ``sys2``
  2135. can either be ``Series``, ``StateSpace`` or ``TransferFunction`` objects.
  2136. Parameters
  2137. ==========
  2138. sys1 : Series, StateSpace, TransferFunction
  2139. The feedforward path system.
  2140. sys2 : Series, StateSpace, TransferFunction, optional
  2141. The feedback path system (often a feedback controller).
  2142. It is the model sitting on the feedback path.
  2143. If not specified explicitly, the sys2 is
  2144. assumed to be unit (1.0) transfer function.
  2145. sign : int, optional
  2146. The sign of feedback. Can either be ``1``
  2147. (for positive feedback) or ``-1`` (for negative feedback).
  2148. Default value is `-1`.
  2149. Raises
  2150. ======
  2151. ValueError
  2152. When ``sys1`` and ``sys2`` are not using the
  2153. same complex variable of the Laplace transform.
  2154. When a combination of ``sys1`` and ``sys2`` yields
  2155. zero denominator.
  2156. TypeError
  2157. When either ``sys1`` or ``sys2`` is not a ``Series``, ``StateSpace`` or
  2158. ``TransferFunction`` object.
  2159. Examples
  2160. ========
  2161. >>> from sympy import Matrix
  2162. >>> from sympy.abc import s
  2163. >>> from sympy.physics.control.lti import StateSpace, TransferFunction, Feedback
  2164. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2165. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  2166. >>> F1 = Feedback(plant, controller)
  2167. >>> F1
  2168. Feedback(TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s), TransferFunction(5*s - 10, s + 7, s), -1)
  2169. >>> F1.var
  2170. s
  2171. >>> F1.args
  2172. (TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s), TransferFunction(5*s - 10, s + 7, s), -1)
  2173. You can get the feedforward and feedback path systems by using ``.sys1`` and ``.sys2`` respectively.
  2174. >>> F1.sys1
  2175. TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2176. >>> F1.sys2
  2177. TransferFunction(5*s - 10, s + 7, s)
  2178. You can get the resultant closed loop transfer function obtained by negative feedback
  2179. interconnection using ``.doit()`` method.
  2180. >>> F1.doit()
  2181. 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)
  2182. >>> G = TransferFunction(2*s**2 + 5*s + 1, s**2 + 2*s + 3, s)
  2183. >>> C = TransferFunction(5*s + 10, s + 10, s)
  2184. >>> F2 = Feedback(G*C, TransferFunction(1, 1, s))
  2185. >>> F2.doit()
  2186. 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)
  2187. To negate a ``Feedback`` object, the ``-`` operator can be prepended:
  2188. >>> -F1
  2189. Feedback(TransferFunction(-3*s**2 - 7*s + 3, s**2 - 4*s + 2, s), TransferFunction(10 - 5*s, s + 7, s), -1)
  2190. >>> -F2
  2191. 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)
  2192. ``Feedback`` can also be used to connect SISO ``StateSpace`` systems together.
  2193. >>> A1 = Matrix([[-1]])
  2194. >>> B1 = Matrix([[1]])
  2195. >>> C1 = Matrix([[-1]])
  2196. >>> D1 = Matrix([1])
  2197. >>> A2 = Matrix([[0]])
  2198. >>> B2 = Matrix([[1]])
  2199. >>> C2 = Matrix([[1]])
  2200. >>> D2 = Matrix([[0]])
  2201. >>> ss1 = StateSpace(A1, B1, C1, D1)
  2202. >>> ss2 = StateSpace(A2, B2, C2, D2)
  2203. >>> F3 = Feedback(ss1, ss2)
  2204. >>> F3
  2205. Feedback(StateSpace(Matrix([[-1]]), Matrix([[1]]), Matrix([[-1]]), Matrix([[1]])), StateSpace(Matrix([[0]]), Matrix([[1]]), Matrix([[1]]), Matrix([[0]])), -1)
  2206. ``doit()`` can be used to find ``StateSpace`` equivalent for the system containing ``StateSpace`` objects.
  2207. >>> F3.doit()
  2208. StateSpace(Matrix([
  2209. [-1, -1],
  2210. [-1, -1]]), Matrix([
  2211. [1],
  2212. [1]]), Matrix([[-1, -1]]), Matrix([[1]]))
  2213. We can also find the equivalent ``TransferFunction`` by using ``rewrite(TransferFunction)`` method.
  2214. >>> F3.rewrite(TransferFunction)
  2215. TransferFunction(s, s + 2, s)
  2216. See Also
  2217. ========
  2218. MIMOFeedback, Series, Parallel
  2219. """
  2220. def __new__(cls, sys1, sys2=None, sign=-1):
  2221. if not sys2:
  2222. sys2 = TransferFunction(1, 1, sys1.var)
  2223. if not isinstance(sys1, (TransferFunction, Series, StateSpace, Feedback)):
  2224. raise TypeError("Unsupported type for `sys1` in Feedback.")
  2225. if not isinstance(sys2, (TransferFunction, Series, StateSpace, Feedback)):
  2226. raise TypeError("Unsupported type for `sys2` in Feedback.")
  2227. if not (sys1.num_inputs == sys1.num_outputs == sys2.num_inputs ==
  2228. sys2.num_outputs == 1):
  2229. raise ValueError("""To use Feedback connection for MIMO systems
  2230. use MIMOFeedback instead.""")
  2231. if sign not in [-1, 1]:
  2232. raise ValueError(filldedent("""
  2233. Unsupported type for feedback. `sign` arg should
  2234. either be 1 (positive feedback loop) or -1
  2235. (negative feedback loop)."""))
  2236. if sys1.is_StateSpace_object or sys2.is_StateSpace_object:
  2237. cls.is_StateSpace_object = True
  2238. else:
  2239. if Mul(sys1.to_expr(), sys2.to_expr()).simplify() == sign:
  2240. raise ValueError("The equivalent system will have zero denominator.")
  2241. if sys1.var != sys2.var:
  2242. raise ValueError(filldedent("""Both `sys1` and `sys2` should be using the
  2243. same complex variable."""))
  2244. cls.is_StateSpace_object = False
  2245. return super(SISOLinearTimeInvariant, cls).__new__(cls, sys1, sys2, _sympify(sign))
  2246. def __repr__(self):
  2247. return f"Feedback({self.sys1}, {self.sys2}, {self.sign})"
  2248. __str__ = __repr__
  2249. @property
  2250. def sys1(self):
  2251. """
  2252. Returns the feedforward system of the feedback interconnection.
  2253. Examples
  2254. ========
  2255. >>> from sympy.abc import s, p
  2256. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  2257. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2258. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  2259. >>> F1 = Feedback(plant, controller)
  2260. >>> F1.sys1
  2261. TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2262. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  2263. >>> C = TransferFunction(5*p + 10, p + 10, p)
  2264. >>> P = TransferFunction(1 - s, p + 2, p)
  2265. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  2266. >>> F2.sys1
  2267. TransferFunction(1, 1, p)
  2268. """
  2269. return self.args[0]
  2270. @property
  2271. def sys2(self):
  2272. """
  2273. Returns the feedback controller of the feedback interconnection.
  2274. Examples
  2275. ========
  2276. >>> from sympy.abc import s, p
  2277. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  2278. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2279. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  2280. >>> F1 = Feedback(plant, controller)
  2281. >>> F1.sys2
  2282. TransferFunction(5*s - 10, s + 7, s)
  2283. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  2284. >>> C = TransferFunction(5*p + 10, p + 10, p)
  2285. >>> P = TransferFunction(1 - s, p + 2, p)
  2286. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  2287. >>> F2.sys2
  2288. 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))
  2289. """
  2290. return self.args[1]
  2291. @property
  2292. def var(self):
  2293. """
  2294. Returns the complex variable of the Laplace transform used by all
  2295. the transfer functions involved in the feedback interconnection.
  2296. Examples
  2297. ========
  2298. >>> from sympy.abc import s, p
  2299. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  2300. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2301. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  2302. >>> F1 = Feedback(plant, controller)
  2303. >>> F1.var
  2304. s
  2305. >>> G = TransferFunction(2*s**2 + 5*s + 1, p**2 + 2*p + 3, p)
  2306. >>> C = TransferFunction(5*p + 10, p + 10, p)
  2307. >>> P = TransferFunction(1 - s, p + 2, p)
  2308. >>> F2 = Feedback(TransferFunction(1, 1, p), G*C*P)
  2309. >>> F2.var
  2310. p
  2311. """
  2312. return self.sys1.var
  2313. @property
  2314. def sign(self):
  2315. """
  2316. Returns the type of MIMO Feedback model. ``1``
  2317. for Positive and ``-1`` for Negative.
  2318. """
  2319. return self.args[2]
  2320. @property
  2321. def num(self):
  2322. """
  2323. Returns the numerator of the closed loop feedback system.
  2324. """
  2325. return self.sys1
  2326. @property
  2327. def den(self):
  2328. """
  2329. Returns the denominator of the closed loop feedback model.
  2330. """
  2331. unit = TransferFunction(1, 1, self.var)
  2332. arg_list = list(self.sys1.args) if isinstance(self.sys1, Series) else [self.sys1]
  2333. if self.sign == 1:
  2334. return Parallel(unit, -Series(self.sys2, *arg_list))
  2335. return Parallel(unit, Series(self.sys2, *arg_list))
  2336. @property
  2337. def sensitivity(self):
  2338. """
  2339. Returns the sensitivity function of the feedback loop.
  2340. Sensitivity of a Feedback system is the ratio
  2341. of change in the open loop gain to the change in
  2342. the closed loop gain.
  2343. .. note::
  2344. This method would not return the complementary
  2345. sensitivity function.
  2346. Examples
  2347. ========
  2348. >>> from sympy.abc import p
  2349. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  2350. >>> C = TransferFunction(5*p + 10, p + 10, p)
  2351. >>> P = TransferFunction(1 - p, p + 2, p)
  2352. >>> F_1 = Feedback(P, C)
  2353. >>> F_1.sensitivity
  2354. 1/((1 - p)*(5*p + 10)/((p + 2)*(p + 10)) + 1)
  2355. """
  2356. return 1/(1 - self.sign*self.sys1.to_expr()*self.sys2.to_expr())
  2357. def doit(self, cancel=False, expand=False, **hints):
  2358. """
  2359. Returns the resultant transfer function or state space obtained by
  2360. feedback connection of transfer functions or state space objects.
  2361. Examples
  2362. ========
  2363. >>> from sympy.abc import s
  2364. >>> from sympy import Matrix
  2365. >>> from sympy.physics.control.lti import TransferFunction, Feedback, StateSpace
  2366. >>> plant = TransferFunction(3*s**2 + 7*s - 3, s**2 - 4*s + 2, s)
  2367. >>> controller = TransferFunction(5*s - 10, s + 7, s)
  2368. >>> F1 = Feedback(plant, controller)
  2369. >>> F1.doit()
  2370. 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)
  2371. >>> G = TransferFunction(2*s**2 + 5*s + 1, s**2 + 2*s + 3, s)
  2372. >>> F2 = Feedback(G, TransferFunction(1, 1, s))
  2373. >>> F2.doit()
  2374. TransferFunction((s**2 + 2*s + 3)*(2*s**2 + 5*s + 1), (s**2 + 2*s + 3)*(3*s**2 + 7*s + 4), s)
  2375. Use kwarg ``expand=True`` to expand the resultant transfer function.
  2376. Use ``cancel=True`` to cancel out the common terms in numerator and
  2377. denominator.
  2378. >>> F2.doit(cancel=True, expand=True)
  2379. TransferFunction(2*s**2 + 5*s + 1, 3*s**2 + 7*s + 4, s)
  2380. >>> F2.doit(expand=True)
  2381. 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)
  2382. If the connection contain any ``StateSpace`` object then ``doit()``
  2383. will return the equivalent ``StateSpace`` object.
  2384. >>> A1 = Matrix([[-1.5, -2], [1, 0]])
  2385. >>> B1 = Matrix([0.5, 0])
  2386. >>> C1 = Matrix([[0, 1]])
  2387. >>> A2 = Matrix([[0, 1], [-5, -2]])
  2388. >>> B2 = Matrix([0, 3])
  2389. >>> C2 = Matrix([[0, 1]])
  2390. >>> ss1 = StateSpace(A1, B1, C1)
  2391. >>> ss2 = StateSpace(A2, B2, C2)
  2392. >>> F3 = Feedback(ss1, ss2)
  2393. >>> F3.doit()
  2394. StateSpace(Matrix([
  2395. [-1.5, -2, 0, -0.5],
  2396. [ 1, 0, 0, 0],
  2397. [ 0, 0, 0, 1],
  2398. [ 0, 3, -5, -2]]), Matrix([
  2399. [0.5],
  2400. [ 0],
  2401. [ 0],
  2402. [ 0]]), Matrix([[0, 1, 0, 0]]), Matrix([[0]]))
  2403. """
  2404. if self.is_StateSpace_object:
  2405. sys1_ss = self.sys1.doit().rewrite(StateSpace)
  2406. sys2_ss = self.sys2.doit().rewrite(StateSpace)
  2407. A1, B1, C1, D1 = sys1_ss.A, sys1_ss.B, sys1_ss.C, sys1_ss.D
  2408. A2, B2, C2, D2 = sys2_ss.A, sys2_ss.B, sys2_ss.C, sys2_ss.D
  2409. # Create identity matrices
  2410. I_inputs = eye(self.num_inputs)
  2411. I_outputs = eye(self.num_outputs)
  2412. # Compute F and its inverse
  2413. F = I_inputs - self.sign * D2 * D1
  2414. E = F.inv()
  2415. # Compute intermediate matrices
  2416. E_D2 = E * D2
  2417. E_C2 = E * C2
  2418. T1 = I_outputs + self.sign * D1 * E_D2
  2419. T2 = I_inputs + self.sign * E_D2 * D1
  2420. A = Matrix.vstack(
  2421. Matrix.hstack(A1 + self.sign * B1 * E_D2 * C1, self.sign * B1 * E_C2),
  2422. Matrix.hstack(B2 * T1 * C1, A2 + self.sign * B2 * D1 * E_C2)
  2423. )
  2424. B = Matrix.vstack(B1 * T2, B2 * D1 * T2)
  2425. C = Matrix.hstack(T1 * C1, self.sign * D1 * E_C2)
  2426. D = D1 * T2
  2427. return StateSpace(A, B, C, D)
  2428. arg_list = list(self.sys1.args) if isinstance(self.sys1, Series) else [self.sys1]
  2429. # F_n and F_d are resultant TFs of num and den of Feedback.
  2430. F_n, unit = self.sys1.doit(), TransferFunction(1, 1, self.sys1.var)
  2431. if self.sign == -1:
  2432. F_d = Parallel(unit, Series(self.sys2, *arg_list)).doit()
  2433. else:
  2434. F_d = Parallel(unit, -Series(self.sys2, *arg_list)).doit()
  2435. _resultant_tf = TransferFunction(F_n.num * F_d.den, F_n.den * F_d.num, F_n.var)
  2436. if cancel:
  2437. _resultant_tf = _resultant_tf.simplify()
  2438. if expand:
  2439. _resultant_tf = _resultant_tf.expand()
  2440. return _resultant_tf
  2441. def _eval_rewrite_as_TransferFunction(self, num, den, sign, **kwargs):
  2442. if self.is_StateSpace_object:
  2443. return self.doit().rewrite(TransferFunction)[0][0]
  2444. return self.doit()
  2445. def to_expr(self):
  2446. """
  2447. Converts a ``Feedback`` object to SymPy Expr.
  2448. Examples
  2449. ========
  2450. >>> from sympy.abc import s, a, b
  2451. >>> from sympy.physics.control.lti import TransferFunction, Feedback
  2452. >>> from sympy import Expr
  2453. >>> tf1 = TransferFunction(a+s, 1, s)
  2454. >>> tf2 = TransferFunction(b+s, 1, s)
  2455. >>> fd1 = Feedback(tf1, tf2)
  2456. >>> fd1.to_expr()
  2457. (a + s)/((a + s)*(b + s) + 1)
  2458. >>> isinstance(_, Expr)
  2459. True
  2460. """
  2461. return self.doit().to_expr()
  2462. def __neg__(self):
  2463. return Feedback(-self.sys1, -self.sys2, self.sign)
  2464. def _is_invertible(a, b, sign):
  2465. """
  2466. Checks whether a given pair of MIMO
  2467. systems passed is invertible or not.
  2468. """
  2469. _mat = eye(a.num_outputs) - sign*(a.doit()._expr_mat)*(b.doit()._expr_mat)
  2470. _det = _mat.det()
  2471. return _det != 0
  2472. class MIMOFeedback(MIMOLinearTimeInvariant):
  2473. r"""
  2474. A class for representing closed-loop feedback interconnection between two
  2475. MIMO input/output systems.
  2476. Parameters
  2477. ==========
  2478. sys1 : MIMOSeries, TransferFunctionMatrix, StateSpace
  2479. The MIMO system placed on the feedforward path.
  2480. sys2 : MIMOSeries, TransferFunctionMatrix, StateSpace
  2481. The system placed on the feedback path
  2482. (often a feedback controller).
  2483. sign : int, optional
  2484. The sign of feedback. Can either be ``1``
  2485. (for positive feedback) or ``-1`` (for negative feedback).
  2486. Default value is `-1`.
  2487. Raises
  2488. ======
  2489. ValueError
  2490. When ``sys1`` and ``sys2`` are not using the
  2491. same complex variable of the Laplace transform.
  2492. Forward path model should have an equal number of inputs/outputs
  2493. to the feedback path outputs/inputs.
  2494. When product of ``sys1`` and ``sys2`` is not a square matrix.
  2495. When the equivalent MIMO system is not invertible.
  2496. TypeError
  2497. When either ``sys1`` or ``sys2`` is not a ``MIMOSeries``,
  2498. ``TransferFunctionMatrix`` or a ``StateSpace`` object.
  2499. Examples
  2500. ========
  2501. >>> from sympy import Matrix, pprint
  2502. >>> from sympy.abc import s
  2503. >>> from sympy.physics.control.lti import StateSpace, TransferFunctionMatrix, MIMOFeedback
  2504. >>> plant_mat = Matrix([[1, 1/s], [0, 1]])
  2505. >>> controller_mat = Matrix([[10, 0], [0, 10]]) # Constant Gain
  2506. >>> plant = TransferFunctionMatrix.from_Matrix(plant_mat, s)
  2507. >>> controller = TransferFunctionMatrix.from_Matrix(controller_mat, s)
  2508. >>> feedback = MIMOFeedback(plant, controller) # Negative Feedback (default)
  2509. >>> pprint(feedback, use_unicode=False)
  2510. / [1 1] [10 0 ] \-1 [1 1]
  2511. | [- -] [-- - ] | [- -]
  2512. | [1 s] [1 1 ] | [1 s]
  2513. |I + [ ] *[ ] | * [ ]
  2514. | [0 1] [0 10] | [0 1]
  2515. | [- -] [- --] | [- -]
  2516. \ [1 1]{t} [1 1 ]{t}/ [1 1]{t}
  2517. To get the equivalent system matrix, use either ``doit`` or ``rewrite`` method.
  2518. >>> pprint(feedback.doit(), use_unicode=False)
  2519. [1 1 ]
  2520. [-- -----]
  2521. [11 121*s]
  2522. [ ]
  2523. [0 1 ]
  2524. [- -- ]
  2525. [1 11 ]{t}
  2526. To negate the ``MIMOFeedback`` object, use ``-`` operator.
  2527. >>> neg_feedback = -feedback
  2528. >>> pprint(neg_feedback.doit(), use_unicode=False)
  2529. [-1 -1 ]
  2530. [--- -----]
  2531. [11 121*s]
  2532. [ ]
  2533. [ 0 -1 ]
  2534. [ - --- ]
  2535. [ 1 11 ]{t}
  2536. ``MIMOFeedback`` can also be used to connect MIMO ``StateSpace`` systems.
  2537. >>> A1 = Matrix([[4, 1], [2, -3]])
  2538. >>> B1 = Matrix([[5, 2], [-3, -3]])
  2539. >>> C1 = Matrix([[2, -4], [0, 1]])
  2540. >>> D1 = Matrix([[3, 2], [1, -1]])
  2541. >>> A2 = Matrix([[-3, 4, 2], [-1, -3, 0], [2, 5, 3]])
  2542. >>> B2 = Matrix([[1, 4], [-3, -3], [-2, 1]])
  2543. >>> C2 = Matrix([[4, 2, -3], [1, 4, 3]])
  2544. >>> D2 = Matrix([[-2, 4], [0, 1]])
  2545. >>> ss1 = StateSpace(A1, B1, C1, D1)
  2546. >>> ss2 = StateSpace(A2, B2, C2, D2)
  2547. >>> F1 = MIMOFeedback(ss1, ss2)
  2548. >>> F1
  2549. MIMOFeedback(StateSpace(Matrix([
  2550. [4, 1],
  2551. [2, -3]]), Matrix([
  2552. [ 5, 2],
  2553. [-3, -3]]), Matrix([
  2554. [2, -4],
  2555. [0, 1]]), Matrix([
  2556. [3, 2],
  2557. [1, -1]])), StateSpace(Matrix([
  2558. [-3, 4, 2],
  2559. [-1, -3, 0],
  2560. [ 2, 5, 3]]), Matrix([
  2561. [ 1, 4],
  2562. [-3, -3],
  2563. [-2, 1]]), Matrix([
  2564. [4, 2, -3],
  2565. [1, 4, 3]]), Matrix([
  2566. [-2, 4],
  2567. [ 0, 1]])), -1)
  2568. ``doit()`` can be used to find ``StateSpace`` equivalent for the system containing ``StateSpace`` objects.
  2569. >>> F1.doit()
  2570. StateSpace(Matrix([
  2571. [ 3, -3/4, -15/4, -37/2, -15],
  2572. [ 7/2, -39/8, 9/8, 39/4, 9],
  2573. [ 3, -41/4, -45/4, -51/2, -19],
  2574. [-9/2, 129/8, 73/8, 171/4, 36],
  2575. [-3/2, 47/8, 31/8, 85/4, 18]]), Matrix([
  2576. [-1/4, 19/4],
  2577. [ 3/8, -21/8],
  2578. [ 1/4, 29/4],
  2579. [ 3/8, -93/8],
  2580. [ 5/8, -35/8]]), Matrix([
  2581. [ 1, -15/4, -7/4, -21/2, -9],
  2582. [1/2, -13/8, -13/8, -19/4, -3]]), Matrix([
  2583. [-1/4, 11/4],
  2584. [ 1/8, 9/8]]))
  2585. See Also
  2586. ========
  2587. Feedback, MIMOSeries, MIMOParallel
  2588. """
  2589. def __new__(cls, sys1, sys2, sign=-1):
  2590. if not isinstance(sys1, (TransferFunctionMatrix, MIMOSeries, StateSpace)):
  2591. raise TypeError("Unsupported type for `sys1` in MIMO Feedback.")
  2592. if not isinstance(sys2, (TransferFunctionMatrix, MIMOSeries, StateSpace)):
  2593. raise TypeError("Unsupported type for `sys2` in MIMO Feedback.")
  2594. if sys1.num_inputs != sys2.num_outputs or \
  2595. sys1.num_outputs != sys2.num_inputs:
  2596. raise ValueError(filldedent("""
  2597. Product of `sys1` and `sys2` must
  2598. yield a square matrix."""))
  2599. if sign not in (-1, 1):
  2600. raise ValueError(filldedent("""
  2601. Unsupported type for feedback. `sign` arg should
  2602. either be 1 (positive feedback loop) or -1
  2603. (negative feedback loop)."""))
  2604. if sys1.is_StateSpace_object or sys2.is_StateSpace_object:
  2605. cls.is_StateSpace_object = True
  2606. else:
  2607. if not _is_invertible(sys1, sys2, sign):
  2608. raise ValueError("Non-Invertible system inputted.")
  2609. cls.is_StateSpace_object = False
  2610. if not cls.is_StateSpace_object and sys1.var != sys2.var:
  2611. raise ValueError(filldedent("""
  2612. Both `sys1` and `sys2` should be using the
  2613. same complex variable."""))
  2614. return super().__new__(cls, sys1, sys2, _sympify(sign))
  2615. @property
  2616. def sys1(self):
  2617. r"""
  2618. Returns the system placed on the feedforward path of the MIMO feedback interconnection.
  2619. Examples
  2620. ========
  2621. >>> from sympy import pprint
  2622. >>> from sympy.abc import s
  2623. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  2624. >>> tf1 = TransferFunction(s**2 + s + 1, s**2 - s + 1, s)
  2625. >>> tf2 = TransferFunction(1, s, s)
  2626. >>> tf3 = TransferFunction(1, 1, s)
  2627. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  2628. >>> sys2 = TransferFunctionMatrix([[tf3, tf3], [tf3, tf2]])
  2629. >>> F_1 = MIMOFeedback(sys1, sys2, 1)
  2630. >>> F_1.sys1
  2631. 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))))
  2632. >>> pprint(_, use_unicode=False)
  2633. [ 2 ]
  2634. [s + s + 1 1 ]
  2635. [---------- - ]
  2636. [ 2 s ]
  2637. [s - s + 1 ]
  2638. [ ]
  2639. [ 2 ]
  2640. [ 1 s + s + 1]
  2641. [ - ----------]
  2642. [ s 2 ]
  2643. [ s - s + 1]{t}
  2644. """
  2645. return self.args[0]
  2646. @property
  2647. def sys2(self):
  2648. r"""
  2649. Returns the feedback controller of the MIMO feedback interconnection.
  2650. Examples
  2651. ========
  2652. >>> from sympy import pprint
  2653. >>> from sympy.abc import s
  2654. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  2655. >>> tf1 = TransferFunction(s**2, s**3 - s + 1, s)
  2656. >>> tf2 = TransferFunction(1, s, s)
  2657. >>> tf3 = TransferFunction(1, 1, s)
  2658. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  2659. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  2660. >>> F_1 = MIMOFeedback(sys1, sys2)
  2661. >>> F_1.sys2
  2662. TransferFunctionMatrix(((TransferFunction(s**2, s**3 - s + 1, s), TransferFunction(1, 1, s)), (TransferFunction(1, 1, s), TransferFunction(1, s, s))))
  2663. >>> pprint(_, use_unicode=False)
  2664. [ 2 ]
  2665. [ s 1]
  2666. [---------- -]
  2667. [ 3 1]
  2668. [s - s + 1 ]
  2669. [ ]
  2670. [ 1 1]
  2671. [ - -]
  2672. [ 1 s]{t}
  2673. """
  2674. return self.args[1]
  2675. @property
  2676. def var(self):
  2677. r"""
  2678. Returns the complex variable of the Laplace transform used by all
  2679. the transfer functions involved in the MIMO feedback loop.
  2680. Examples
  2681. ========
  2682. >>> from sympy.abc import p
  2683. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  2684. >>> tf1 = TransferFunction(p, 1 - p, p)
  2685. >>> tf2 = TransferFunction(1, p, p)
  2686. >>> tf3 = TransferFunction(1, 1, p)
  2687. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  2688. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  2689. >>> F_1 = MIMOFeedback(sys1, sys2, 1) # Positive feedback
  2690. >>> F_1.var
  2691. p
  2692. """
  2693. return self.sys1.var
  2694. @property
  2695. def sign(self):
  2696. r"""
  2697. Returns the type of feedback interconnection of two models. ``1``
  2698. for Positive and ``-1`` for Negative.
  2699. """
  2700. return self.args[2]
  2701. @property
  2702. def sensitivity(self):
  2703. r"""
  2704. Returns the sensitivity function matrix of the feedback loop.
  2705. Sensitivity of a closed-loop system is the ratio of change
  2706. in the open loop gain to the change in the closed loop gain.
  2707. .. note::
  2708. This method would not return the complementary
  2709. sensitivity function.
  2710. Examples
  2711. ========
  2712. >>> from sympy import pprint
  2713. >>> from sympy.abc import p
  2714. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  2715. >>> tf1 = TransferFunction(p, 1 - p, p)
  2716. >>> tf2 = TransferFunction(1, p, p)
  2717. >>> tf3 = TransferFunction(1, 1, p)
  2718. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf2, tf1]])
  2719. >>> sys2 = TransferFunctionMatrix([[tf1, tf3], [tf3, tf2]])
  2720. >>> F_1 = MIMOFeedback(sys1, sys2, 1) # Positive feedback
  2721. >>> F_2 = MIMOFeedback(sys1, sys2) # Negative feedback
  2722. >>> pprint(F_1.sensitivity, use_unicode=False)
  2723. [ 4 3 2 5 4 2 ]
  2724. [- p + 3*p - 4*p + 3*p - 1 p - 2*p + 3*p - 3*p + 1 ]
  2725. [---------------------------- -----------------------------]
  2726. [ 4 3 2 5 4 3 2 ]
  2727. [ p + 3*p - 8*p + 8*p - 3 p + 3*p - 8*p + 8*p - 3*p]
  2728. [ ]
  2729. [ 4 3 2 3 2 ]
  2730. [ p - p - p + p 3*p - 6*p + 4*p - 1 ]
  2731. [ -------------------------- -------------------------- ]
  2732. [ 4 3 2 4 3 2 ]
  2733. [ p + 3*p - 8*p + 8*p - 3 p + 3*p - 8*p + 8*p - 3 ]
  2734. >>> pprint(F_2.sensitivity, use_unicode=False)
  2735. [ 4 3 2 5 4 2 ]
  2736. [p - 3*p + 2*p + p - 1 p - 2*p + 3*p - 3*p + 1]
  2737. [------------------------ --------------------------]
  2738. [ 4 3 5 4 2 ]
  2739. [ p - 3*p + 2*p - 1 p - 3*p + 2*p - p ]
  2740. [ ]
  2741. [ 4 3 2 4 3 ]
  2742. [ p - p - p + p 2*p - 3*p + 2*p - 1 ]
  2743. [ ------------------- --------------------- ]
  2744. [ 4 3 4 3 ]
  2745. [ p - 3*p + 2*p - 1 p - 3*p + 2*p - 1 ]
  2746. """
  2747. _sys1_mat = self.sys1.doit()._expr_mat
  2748. _sys2_mat = self.sys2.doit()._expr_mat
  2749. return (eye(self.sys1.num_inputs) - \
  2750. self.sign*_sys1_mat*_sys2_mat).inv()
  2751. @property
  2752. def num_inputs(self):
  2753. """Returns the number of inputs of the system."""
  2754. return self.sys1.num_inputs
  2755. @property
  2756. def num_outputs(self):
  2757. """Returns the number of outputs of the system."""
  2758. return self.sys1.num_outputs
  2759. def doit(self, cancel=True, expand=False, **hints):
  2760. r"""
  2761. Returns the resultant transfer function matrix obtained by the
  2762. feedback interconnection.
  2763. Examples
  2764. ========
  2765. >>> from sympy import pprint
  2766. >>> from sympy.abc import s
  2767. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, MIMOFeedback
  2768. >>> tf1 = TransferFunction(s, 1 - s, s)
  2769. >>> tf2 = TransferFunction(1, s, s)
  2770. >>> tf3 = TransferFunction(5, 1, s)
  2771. >>> tf4 = TransferFunction(s - 1, s, s)
  2772. >>> tf5 = TransferFunction(0, 1, s)
  2773. >>> sys1 = TransferFunctionMatrix([[tf1, tf2], [tf3, tf4]])
  2774. >>> sys2 = TransferFunctionMatrix([[tf3, tf5], [tf5, tf5]])
  2775. >>> F_1 = MIMOFeedback(sys1, sys2, 1)
  2776. >>> pprint(F_1, use_unicode=False)
  2777. / [ s 1 ] [5 0] \-1 [ s 1 ]
  2778. | [----- - ] [- -] | [----- - ]
  2779. | [1 - s s ] [1 1] | [1 - s s ]
  2780. |I - [ ] *[ ] | * [ ]
  2781. | [ 5 s - 1] [0 0] | [ 5 s - 1]
  2782. | [ - -----] [- -] | [ - -----]
  2783. \ [ 1 s ]{t} [1 1]{t}/ [ 1 s ]{t}
  2784. >>> pprint(F_1.doit(), use_unicode=False)
  2785. [ -s s - 1 ]
  2786. [------- ----------- ]
  2787. [6*s - 1 s*(6*s - 1) ]
  2788. [ ]
  2789. [5*s - 5 (s - 1)*(6*s + 24)]
  2790. [------- ------------------]
  2791. [6*s - 1 s*(6*s - 1) ]{t}
  2792. If the user wants the resultant ``TransferFunctionMatrix`` object without
  2793. canceling the common factors then the ``cancel`` kwarg should be passed ``False``.
  2794. >>> pprint(F_1.doit(cancel=False), use_unicode=False)
  2795. [ s*(s - 1) s - 1 ]
  2796. [ ----------------- ----------- ]
  2797. [ (1 - s)*(6*s - 1) s*(6*s - 1) ]
  2798. [ ]
  2799. [s*(25*s - 25) + 5*(1 - s)*(6*s - 1) s*(s - 1)*(6*s - 1) + s*(25*s - 25)]
  2800. [----------------------------------- -----------------------------------]
  2801. [ (1 - s)*(6*s - 1) 2 ]
  2802. [ s *(6*s - 1) ]{t}
  2803. If the user wants the expanded form of the resultant transfer function matrix,
  2804. the ``expand`` kwarg should be passed as ``True``.
  2805. >>> pprint(F_1.doit(expand=True), use_unicode=False)
  2806. [ -s s - 1 ]
  2807. [------- -------- ]
  2808. [6*s - 1 2 ]
  2809. [ 6*s - s ]
  2810. [ ]
  2811. [ 2 ]
  2812. [5*s - 5 6*s + 18*s - 24]
  2813. [------- ----------------]
  2814. [6*s - 1 2 ]
  2815. [ 6*s - s ]{t}
  2816. """
  2817. if self.is_StateSpace_object:
  2818. sys1_ss = self.sys1.doit().rewrite(StateSpace)
  2819. sys2_ss = self.sys2.doit().rewrite(StateSpace)
  2820. A1, B1, C1, D1 = sys1_ss.A, sys1_ss.B, sys1_ss.C, sys1_ss.D
  2821. A2, B2, C2, D2 = sys2_ss.A, sys2_ss.B, sys2_ss.C, sys2_ss.D
  2822. # Create identity matrices
  2823. I_inputs = eye(self.num_inputs)
  2824. I_outputs = eye(self.num_outputs)
  2825. # Compute F and its inverse
  2826. F = I_inputs - self.sign * D2 * D1
  2827. E = F.inv()
  2828. # Compute intermediate matrices
  2829. E_D2 = E * D2
  2830. E_C2 = E * C2
  2831. T1 = I_outputs + self.sign * D1 * E_D2
  2832. T2 = I_inputs + self.sign * E_D2 * D1
  2833. A = Matrix.vstack(
  2834. Matrix.hstack(A1 + self.sign * B1 * E_D2 * C1, self.sign * B1 * E_C2),
  2835. Matrix.hstack(B2 * T1 * C1, A2 + self.sign * B2 * D1 * E_C2)
  2836. )
  2837. B = Matrix.vstack(B1 * T2, B2 * D1 * T2)
  2838. C = Matrix.hstack(T1 * C1, self.sign * D1 * E_C2)
  2839. D = D1 * T2
  2840. return StateSpace(A, B, C, D)
  2841. _mat = self.sensitivity * self.sys1.doit()._expr_mat
  2842. _resultant_tfm = _to_TFM(_mat, self.var)
  2843. if cancel:
  2844. _resultant_tfm = _resultant_tfm.simplify()
  2845. if expand:
  2846. _resultant_tfm = _resultant_tfm.expand()
  2847. return _resultant_tfm
  2848. def _eval_rewrite_as_TransferFunctionMatrix(self, sys1, sys2, sign, **kwargs):
  2849. return self.doit()
  2850. def __neg__(self):
  2851. return MIMOFeedback(-self.sys1, -self.sys2, self.sign)
  2852. def _to_TFM(mat, var):
  2853. """Private method to convert ImmutableMatrix to TransferFunctionMatrix efficiently"""
  2854. to_tf = lambda expr: TransferFunction.from_rational_expression(expr, var)
  2855. arg = [[to_tf(expr) for expr in row] for row in mat.tolist()]
  2856. return TransferFunctionMatrix(arg)
  2857. class TransferFunctionMatrix(MIMOLinearTimeInvariant):
  2858. r"""
  2859. A class for representing the MIMO (multiple-input and multiple-output)
  2860. generalization of the SISO (single-input and single-output) transfer function.
  2861. It is a matrix of transfer functions (``TransferFunction``, SISO-``Series`` or SISO-``Parallel``).
  2862. There is only one argument, ``arg`` which is also the compulsory argument.
  2863. ``arg`` is expected to be strictly of the type list of lists
  2864. which holds the transfer functions or reducible to transfer functions.
  2865. Parameters
  2866. ==========
  2867. arg : Nested ``List`` (strictly).
  2868. Users are expected to input a nested list of ``TransferFunction``, ``Series``
  2869. and/or ``Parallel`` objects.
  2870. Examples
  2871. ========
  2872. .. note::
  2873. ``pprint()`` can be used for better visualization of ``TransferFunctionMatrix`` objects.
  2874. >>> from sympy.abc import s, p, a
  2875. >>> from sympy import pprint
  2876. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, Series, Parallel
  2877. >>> tf_1 = TransferFunction(s + a, s**2 + s + 1, s)
  2878. >>> tf_2 = TransferFunction(p**4 - 3*p + 2, s + p, s)
  2879. >>> tf_3 = TransferFunction(3, s + 2, s)
  2880. >>> tf_4 = TransferFunction(-a + p, 9*s - 9, s)
  2881. >>> tfm_1 = TransferFunctionMatrix([[tf_1], [tf_2], [tf_3]])
  2882. >>> tfm_1
  2883. TransferFunctionMatrix(((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(3, s + 2, s),)))
  2884. >>> tfm_1.var
  2885. s
  2886. >>> tfm_1.num_inputs
  2887. 1
  2888. >>> tfm_1.num_outputs
  2889. 3
  2890. >>> tfm_1.shape
  2891. (3, 1)
  2892. >>> tfm_1.args
  2893. (((TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(p**4 - 3*p + 2, p + s, s),), (TransferFunction(3, s + 2, s),)),)
  2894. >>> tfm_2 = TransferFunctionMatrix([[tf_1, -tf_3], [tf_2, -tf_1], [tf_3, -tf_2]])
  2895. >>> tfm_2
  2896. 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))))
  2897. >>> pprint(tfm_2, use_unicode=False) # pretty-printing for better visualization
  2898. [ a + s -3 ]
  2899. [ ---------- ----- ]
  2900. [ 2 s + 2 ]
  2901. [ s + s + 1 ]
  2902. [ ]
  2903. [ 4 ]
  2904. [p - 3*p + 2 -a - s ]
  2905. [------------ ---------- ]
  2906. [ p + s 2 ]
  2907. [ s + s + 1 ]
  2908. [ ]
  2909. [ 4 ]
  2910. [ 3 - p + 3*p - 2]
  2911. [ ----- --------------]
  2912. [ s + 2 p + s ]{t}
  2913. TransferFunctionMatrix can be transposed, if user wants to switch the input and output transfer functions
  2914. >>> tfm_2.transpose()
  2915. 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))))
  2916. >>> pprint(_, use_unicode=False)
  2917. [ 4 ]
  2918. [ a + s p - 3*p + 2 3 ]
  2919. [---------- ------------ ----- ]
  2920. [ 2 p + s s + 2 ]
  2921. [s + s + 1 ]
  2922. [ ]
  2923. [ 4 ]
  2924. [ -3 -a - s - p + 3*p - 2]
  2925. [ ----- ---------- --------------]
  2926. [ s + 2 2 p + s ]
  2927. [ s + s + 1 ]{t}
  2928. >>> tf_5 = TransferFunction(5, s, s)
  2929. >>> tf_6 = TransferFunction(5*s, (2 + s**2), s)
  2930. >>> tf_7 = TransferFunction(5, (s*(2 + s**2)), s)
  2931. >>> tf_8 = TransferFunction(5, 1, s)
  2932. >>> tfm_3 = TransferFunctionMatrix([[tf_5, tf_6], [tf_7, tf_8]])
  2933. >>> tfm_3
  2934. TransferFunctionMatrix(((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)), (TransferFunction(5, s*(s**2 + 2), s), TransferFunction(5, 1, s))))
  2935. >>> pprint(tfm_3, use_unicode=False)
  2936. [ 5 5*s ]
  2937. [ - ------]
  2938. [ s 2 ]
  2939. [ s + 2]
  2940. [ ]
  2941. [ 5 5 ]
  2942. [---------- - ]
  2943. [ / 2 \ 1 ]
  2944. [s*\s + 2/ ]{t}
  2945. >>> tfm_3.var
  2946. s
  2947. >>> tfm_3.shape
  2948. (2, 2)
  2949. >>> tfm_3.num_outputs
  2950. 2
  2951. >>> tfm_3.num_inputs
  2952. 2
  2953. >>> tfm_3.args
  2954. (((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)), (TransferFunction(5, s*(s**2 + 2), s), TransferFunction(5, 1, s))),)
  2955. To access the ``TransferFunction`` at any index in the ``TransferFunctionMatrix``, use the index notation.
  2956. >>> tfm_3[1, 0] # gives the TransferFunction present at 2nd Row and 1st Col. Similar to that in Matrix classes
  2957. TransferFunction(5, s*(s**2 + 2), s)
  2958. >>> tfm_3[0, 0] # gives the TransferFunction present at 1st Row and 1st Col.
  2959. TransferFunction(5, s, s)
  2960. >>> tfm_3[:, 0] # gives the first column
  2961. TransferFunctionMatrix(((TransferFunction(5, s, s),), (TransferFunction(5, s*(s**2 + 2), s),)))
  2962. >>> pprint(_, use_unicode=False)
  2963. [ 5 ]
  2964. [ - ]
  2965. [ s ]
  2966. [ ]
  2967. [ 5 ]
  2968. [----------]
  2969. [ / 2 \]
  2970. [s*\s + 2/]{t}
  2971. >>> tfm_3[0, :] # gives the first row
  2972. TransferFunctionMatrix(((TransferFunction(5, s, s), TransferFunction(5*s, s**2 + 2, s)),))
  2973. >>> pprint(_, use_unicode=False)
  2974. [5 5*s ]
  2975. [- ------]
  2976. [s 2 ]
  2977. [ s + 2]{t}
  2978. To negate a transfer function matrix, ``-`` operator can be prepended:
  2979. >>> tfm_4 = TransferFunctionMatrix([[tf_2], [-tf_1], [tf_3]])
  2980. >>> -tfm_4
  2981. TransferFunctionMatrix(((TransferFunction(-p**4 + 3*p - 2, p + s, s),), (TransferFunction(a + s, s**2 + s + 1, s),), (TransferFunction(-3, s + 2, s),)))
  2982. >>> tfm_5 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, -tf_1]])
  2983. >>> -tfm_5
  2984. 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))))
  2985. ``subs()`` returns the ``TransferFunctionMatrix`` object with the value substituted in the expression. This will not
  2986. mutate your original ``TransferFunctionMatrix``.
  2987. >>> tfm_2.subs(p, 2) # substituting p everywhere in tfm_2 with 2.
  2988. 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))))
  2989. >>> pprint(_, use_unicode=False)
  2990. [ a + s -3 ]
  2991. [---------- ----- ]
  2992. [ 2 s + 2 ]
  2993. [s + s + 1 ]
  2994. [ ]
  2995. [ 12 -a - s ]
  2996. [ ----- ----------]
  2997. [ s + 2 2 ]
  2998. [ s + s + 1]
  2999. [ ]
  3000. [ 3 -12 ]
  3001. [ ----- ----- ]
  3002. [ s + 2 s + 2 ]{t}
  3003. >>> pprint(tfm_2, use_unicode=False) # State of tfm_2 is unchanged after substitution
  3004. [ a + s -3 ]
  3005. [ ---------- ----- ]
  3006. [ 2 s + 2 ]
  3007. [ s + s + 1 ]
  3008. [ ]
  3009. [ 4 ]
  3010. [p - 3*p + 2 -a - s ]
  3011. [------------ ---------- ]
  3012. [ p + s 2 ]
  3013. [ s + s + 1 ]
  3014. [ ]
  3015. [ 4 ]
  3016. [ 3 - p + 3*p - 2]
  3017. [ ----- --------------]
  3018. [ s + 2 p + s ]{t}
  3019. ``subs()`` also supports multiple substitutions.
  3020. >>> tfm_2.subs({p: 2, a: 1}) # substituting p with 2 and a with 1
  3021. 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))))
  3022. >>> pprint(_, use_unicode=False)
  3023. [ s + 1 -3 ]
  3024. [---------- ----- ]
  3025. [ 2 s + 2 ]
  3026. [s + s + 1 ]
  3027. [ ]
  3028. [ 12 -s - 1 ]
  3029. [ ----- ----------]
  3030. [ s + 2 2 ]
  3031. [ s + s + 1]
  3032. [ ]
  3033. [ 3 -12 ]
  3034. [ ----- ----- ]
  3035. [ s + 2 s + 2 ]{t}
  3036. Users can reduce the ``Series`` and ``Parallel`` elements of the matrix to ``TransferFunction`` by using
  3037. ``doit()``.
  3038. >>> tfm_6 = TransferFunctionMatrix([[Series(tf_3, tf_4), Parallel(tf_3, tf_4)]])
  3039. >>> tfm_6
  3040. 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))),))
  3041. >>> pprint(tfm_6, use_unicode=False)
  3042. [-a + p 3 -a + p 3 ]
  3043. [-------*----- ------- + -----]
  3044. [9*s - 9 s + 2 9*s - 9 s + 2]{t}
  3045. >>> tfm_6.doit()
  3046. 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)),))
  3047. >>> pprint(_, use_unicode=False)
  3048. [ -3*a + 3*p 27*s + (-a + p)*(s + 2) - 27]
  3049. [----------------- ----------------------------]
  3050. [(s + 2)*(9*s - 9) (s + 2)*(9*s - 9) ]{t}
  3051. >>> tf_9 = TransferFunction(1, s, s)
  3052. >>> tf_10 = TransferFunction(1, s**2, s)
  3053. >>> tfm_7 = TransferFunctionMatrix([[Series(tf_9, tf_10), tf_9], [tf_10, Parallel(tf_9, tf_10)]])
  3054. >>> tfm_7
  3055. 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)))))
  3056. >>> pprint(tfm_7, use_unicode=False)
  3057. [ 1 1 ]
  3058. [---- - ]
  3059. [ 2 s ]
  3060. [s*s ]
  3061. [ ]
  3062. [ 1 1 1]
  3063. [ -- -- + -]
  3064. [ 2 2 s]
  3065. [ s s ]{t}
  3066. >>> tfm_7.doit()
  3067. TransferFunctionMatrix(((TransferFunction(1, s**3, s), TransferFunction(1, s, s)), (TransferFunction(1, s**2, s), TransferFunction(s**2 + s, s**3, s))))
  3068. >>> pprint(_, use_unicode=False)
  3069. [1 1 ]
  3070. [-- - ]
  3071. [ 3 s ]
  3072. [s ]
  3073. [ ]
  3074. [ 2 ]
  3075. [1 s + s]
  3076. [-- ------]
  3077. [ 2 3 ]
  3078. [s s ]{t}
  3079. Addition, subtraction, and multiplication of transfer function matrices can form
  3080. unevaluated ``Series`` or ``Parallel`` objects.
  3081. - For addition and subtraction:
  3082. All the transfer function matrices must have the same shape.
  3083. - For multiplication (C = A * B):
  3084. The number of inputs of the first transfer function matrix (A) must be equal to the
  3085. number of outputs of the second transfer function matrix (B).
  3086. Also, use pretty-printing (``pprint``) to analyse better.
  3087. >>> tfm_8 = TransferFunctionMatrix([[tf_3], [tf_2], [-tf_1]])
  3088. >>> tfm_9 = TransferFunctionMatrix([[-tf_3]])
  3089. >>> tfm_10 = TransferFunctionMatrix([[tf_1], [tf_2], [tf_4]])
  3090. >>> tfm_11 = TransferFunctionMatrix([[tf_4], [-tf_1]])
  3091. >>> tfm_12 = TransferFunctionMatrix([[tf_4, -tf_1, tf_3], [-tf_2, -tf_4, -tf_3]])
  3092. >>> tfm_8 + tfm_10
  3093. 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),))))
  3094. >>> pprint(_, use_unicode=False)
  3095. [ 3 ] [ a + s ]
  3096. [ ----- ] [ ---------- ]
  3097. [ s + 2 ] [ 2 ]
  3098. [ ] [ s + s + 1 ]
  3099. [ 4 ] [ ]
  3100. [p - 3*p + 2] [ 4 ]
  3101. [------------] + [p - 3*p + 2]
  3102. [ p + s ] [------------]
  3103. [ ] [ p + s ]
  3104. [ -a - s ] [ ]
  3105. [ ---------- ] [ -a + p ]
  3106. [ 2 ] [ ------- ]
  3107. [ s + s + 1 ]{t} [ 9*s - 9 ]{t}
  3108. >>> -tfm_10 - tfm_8
  3109. 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),))))
  3110. >>> pprint(_, use_unicode=False)
  3111. [ -a - s ] [ -3 ]
  3112. [ ---------- ] [ ----- ]
  3113. [ 2 ] [ s + 2 ]
  3114. [ s + s + 1 ] [ ]
  3115. [ ] [ 4 ]
  3116. [ 4 ] [- p + 3*p - 2]
  3117. [- p + 3*p - 2] + [--------------]
  3118. [--------------] [ p + s ]
  3119. [ p + s ] [ ]
  3120. [ ] [ a + s ]
  3121. [ a - p ] [ ---------- ]
  3122. [ ------- ] [ 2 ]
  3123. [ 9*s - 9 ]{t} [ s + s + 1 ]{t}
  3124. >>> tfm_12 * tfm_8
  3125. 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)))))
  3126. >>> pprint(_, use_unicode=False)
  3127. [ 3 ]
  3128. [ ----- ]
  3129. [ -a + p -a - s 3 ] [ s + 2 ]
  3130. [ ------- ---------- -----] [ ]
  3131. [ 9*s - 9 2 s + 2] [ 4 ]
  3132. [ s + s + 1 ] [p - 3*p + 2]
  3133. [ ] *[------------]
  3134. [ 4 ] [ p + s ]
  3135. [- p + 3*p - 2 a - p -3 ] [ ]
  3136. [-------------- ------- -----] [ -a - s ]
  3137. [ p + s 9*s - 9 s + 2]{t} [ ---------- ]
  3138. [ 2 ]
  3139. [ s + s + 1 ]{t}
  3140. >>> tfm_12 * tfm_8 * tfm_9
  3141. 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)))))
  3142. >>> pprint(_, use_unicode=False)
  3143. [ 3 ]
  3144. [ ----- ]
  3145. [ -a + p -a - s 3 ] [ s + 2 ]
  3146. [ ------- ---------- -----] [ ]
  3147. [ 9*s - 9 2 s + 2] [ 4 ]
  3148. [ s + s + 1 ] [p - 3*p + 2] [ -3 ]
  3149. [ ] *[------------] *[-----]
  3150. [ 4 ] [ p + s ] [s + 2]{t}
  3151. [- p + 3*p - 2 a - p -3 ] [ ]
  3152. [-------------- ------- -----] [ -a - s ]
  3153. [ p + s 9*s - 9 s + 2]{t} [ ---------- ]
  3154. [ 2 ]
  3155. [ s + s + 1 ]{t}
  3156. >>> tfm_10 + tfm_8*tfm_9
  3157. 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),)))))
  3158. >>> pprint(_, use_unicode=False)
  3159. [ a + s ] [ 3 ]
  3160. [ ---------- ] [ ----- ]
  3161. [ 2 ] [ s + 2 ]
  3162. [ s + s + 1 ] [ ]
  3163. [ ] [ 4 ]
  3164. [ 4 ] [p - 3*p + 2] [ -3 ]
  3165. [p - 3*p + 2] + [------------] *[-----]
  3166. [------------] [ p + s ] [s + 2]{t}
  3167. [ p + s ] [ ]
  3168. [ ] [ -a - s ]
  3169. [ -a + p ] [ ---------- ]
  3170. [ ------- ] [ 2 ]
  3171. [ 9*s - 9 ]{t} [ s + s + 1 ]{t}
  3172. These unevaluated ``Series`` or ``Parallel`` objects can convert into the
  3173. resultant transfer function matrix using ``.doit()`` method or by
  3174. ``.rewrite(TransferFunctionMatrix)``.
  3175. >>> (-tfm_8 + tfm_10 + tfm_8*tfm_9).doit()
  3176. 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),)))
  3177. >>> (-tfm_12 * -tfm_8 * -tfm_9).rewrite(TransferFunctionMatrix)
  3178. 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),)))
  3179. See Also
  3180. ========
  3181. TransferFunction, MIMOSeries, MIMOParallel, Feedback
  3182. """
  3183. def __new__(cls, arg):
  3184. expr_mat_arg = []
  3185. try:
  3186. var = arg[0][0].var
  3187. except TypeError:
  3188. raise ValueError(filldedent("""
  3189. `arg` param in TransferFunctionMatrix should
  3190. strictly be a nested list containing TransferFunction
  3191. objects."""))
  3192. for row in arg:
  3193. temp = []
  3194. for element in row:
  3195. if not isinstance(element, SISOLinearTimeInvariant):
  3196. raise TypeError(filldedent("""
  3197. Each element is expected to be of
  3198. type `SISOLinearTimeInvariant`."""))
  3199. if var != element.var:
  3200. raise ValueError(filldedent("""
  3201. Conflicting value(s) found for `var`. All TransferFunction
  3202. instances in TransferFunctionMatrix should use the same
  3203. complex variable in Laplace domain."""))
  3204. temp.append(element.to_expr())
  3205. expr_mat_arg.append(temp)
  3206. if isinstance(arg, (tuple, list, Tuple)):
  3207. # Making nested Tuple (sympy.core.containers.Tuple) from nested list or nested Python tuple
  3208. arg = Tuple(*(Tuple(*r, sympify=False) for r in arg), sympify=False)
  3209. obj = super(TransferFunctionMatrix, cls).__new__(cls, arg)
  3210. obj._expr_mat = ImmutableMatrix(expr_mat_arg)
  3211. obj.is_StateSpace_object = False
  3212. return obj
  3213. @classmethod
  3214. def from_Matrix(cls, matrix, var):
  3215. """
  3216. Creates a new ``TransferFunctionMatrix`` efficiently from a SymPy Matrix of ``Expr`` objects.
  3217. Parameters
  3218. ==========
  3219. matrix : ``ImmutableMatrix`` having ``Expr``/``Number`` elements.
  3220. var : Symbol
  3221. Complex variable of the Laplace transform which will be used by the
  3222. all the ``TransferFunction`` objects in the ``TransferFunctionMatrix``.
  3223. Examples
  3224. ========
  3225. >>> from sympy.abc import s
  3226. >>> from sympy.physics.control.lti import TransferFunctionMatrix
  3227. >>> from sympy import Matrix, pprint
  3228. >>> M = Matrix([[s, 1/s], [1/(s+1), s]])
  3229. >>> M_tf = TransferFunctionMatrix.from_Matrix(M, s)
  3230. >>> pprint(M_tf, use_unicode=False)
  3231. [ s 1]
  3232. [ - -]
  3233. [ 1 s]
  3234. [ ]
  3235. [ 1 s]
  3236. [----- -]
  3237. [s + 1 1]{t}
  3238. >>> M_tf.elem_poles()
  3239. [[[], [0]], [[-1], []]]
  3240. >>> M_tf.elem_zeros()
  3241. [[[0], []], [[], [0]]]
  3242. """
  3243. return _to_TFM(matrix, var)
  3244. @property
  3245. def var(self):
  3246. """
  3247. Returns the complex variable used by all the transfer functions or
  3248. ``Series``/``Parallel`` objects in a transfer function matrix.
  3249. Examples
  3250. ========
  3251. >>> from sympy.abc import p, s
  3252. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix, Series, Parallel
  3253. >>> G1 = TransferFunction(p**2 + 2*p + 4, p - 6, p)
  3254. >>> G2 = TransferFunction(p, 4 - p, p)
  3255. >>> G3 = TransferFunction(0, p**4 - 1, p)
  3256. >>> G4 = TransferFunction(s + 1, s**2 + s + 1, s)
  3257. >>> S1 = Series(G1, G2)
  3258. >>> S2 = Series(-G3, Parallel(G2, -G1))
  3259. >>> tfm1 = TransferFunctionMatrix([[G1], [G2], [G3]])
  3260. >>> tfm1.var
  3261. p
  3262. >>> tfm2 = TransferFunctionMatrix([[-S1, -S2], [S1, S2]])
  3263. >>> tfm2.var
  3264. p
  3265. >>> tfm3 = TransferFunctionMatrix([[G4]])
  3266. >>> tfm3.var
  3267. s
  3268. """
  3269. return self.args[0][0][0].var
  3270. @property
  3271. def num_inputs(self):
  3272. """
  3273. Returns the number of inputs of the system.
  3274. Examples
  3275. ========
  3276. >>> from sympy.abc import s, p
  3277. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  3278. >>> G1 = TransferFunction(s + 3, s**2 - 3, s)
  3279. >>> G2 = TransferFunction(4, s**2, s)
  3280. >>> G3 = TransferFunction(p**2 + s**2, p - 3, s)
  3281. >>> tfm_1 = TransferFunctionMatrix([[G2, -G1, G3], [-G2, -G1, -G3]])
  3282. >>> tfm_1.num_inputs
  3283. 3
  3284. See Also
  3285. ========
  3286. num_outputs
  3287. """
  3288. return self._expr_mat.shape[1]
  3289. @property
  3290. def num_outputs(self):
  3291. """
  3292. Returns the number of outputs of the system.
  3293. Examples
  3294. ========
  3295. >>> from sympy.abc import s
  3296. >>> from sympy.physics.control.lti import TransferFunctionMatrix
  3297. >>> from sympy import Matrix
  3298. >>> M_1 = Matrix([[s], [1/s]])
  3299. >>> TFM = TransferFunctionMatrix.from_Matrix(M_1, s)
  3300. >>> print(TFM)
  3301. TransferFunctionMatrix(((TransferFunction(s, 1, s),), (TransferFunction(1, s, s),)))
  3302. >>> TFM.num_outputs
  3303. 2
  3304. See Also
  3305. ========
  3306. num_inputs
  3307. """
  3308. return self._expr_mat.shape[0]
  3309. @property
  3310. def shape(self):
  3311. """
  3312. Returns the shape of the transfer function matrix, that is, ``(# of outputs, # of inputs)``.
  3313. Examples
  3314. ========
  3315. >>> from sympy.abc import s, p
  3316. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  3317. >>> tf1 = TransferFunction(p**2 - 1, s**4 + s**3 - p, p)
  3318. >>> tf2 = TransferFunction(1 - p, p**2 - 3*p + 7, p)
  3319. >>> tf3 = TransferFunction(3, 4, p)
  3320. >>> tfm1 = TransferFunctionMatrix([[tf1, -tf2]])
  3321. >>> tfm1.shape
  3322. (1, 2)
  3323. >>> tfm2 = TransferFunctionMatrix([[-tf2, tf3], [tf1, -tf1]])
  3324. >>> tfm2.shape
  3325. (2, 2)
  3326. """
  3327. return self._expr_mat.shape
  3328. def __neg__(self):
  3329. neg = -self._expr_mat
  3330. return _to_TFM(neg, self.var)
  3331. @_check_other_MIMO
  3332. def __add__(self, other):
  3333. if not isinstance(other, MIMOParallel):
  3334. return MIMOParallel(self, other)
  3335. other_arg_list = list(other.args)
  3336. return MIMOParallel(self, *other_arg_list)
  3337. @_check_other_MIMO
  3338. def __sub__(self, other):
  3339. return self + (-other)
  3340. @_check_other_MIMO
  3341. def __mul__(self, other):
  3342. if not isinstance(other, MIMOSeries):
  3343. return MIMOSeries(other, self)
  3344. other_arg_list = list(other.args)
  3345. return MIMOSeries(*other_arg_list, self)
  3346. def __getitem__(self, key):
  3347. trunc = self._expr_mat.__getitem__(key)
  3348. if isinstance(trunc, ImmutableMatrix):
  3349. return _to_TFM(trunc, self.var)
  3350. return TransferFunction.from_rational_expression(trunc, self.var)
  3351. def transpose(self):
  3352. """Returns the transpose of the ``TransferFunctionMatrix`` (switched input and output layers)."""
  3353. transposed_mat = self._expr_mat.transpose()
  3354. return _to_TFM(transposed_mat, self.var)
  3355. def elem_poles(self):
  3356. """
  3357. Returns the poles of each element of the ``TransferFunctionMatrix``.
  3358. .. note::
  3359. Actual poles of a MIMO system are NOT the poles of individual elements.
  3360. Examples
  3361. ========
  3362. >>> from sympy.abc import s
  3363. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  3364. >>> tf_1 = TransferFunction(3, (s + 1), s)
  3365. >>> tf_2 = TransferFunction(s + 6, (s + 1)*(s + 2), s)
  3366. >>> tf_3 = TransferFunction(s + 3, s**2 + 3*s + 2, s)
  3367. >>> tf_4 = TransferFunction(s + 2, s**2 + 5*s - 10, s)
  3368. >>> tfm_1 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, tf_4]])
  3369. >>> tfm_1
  3370. 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))))
  3371. >>> tfm_1.elem_poles()
  3372. [[[-1], [-2, -1]], [[-2, -1], [-5/2 + sqrt(65)/2, -sqrt(65)/2 - 5/2]]]
  3373. See Also
  3374. ========
  3375. elem_zeros
  3376. """
  3377. return [[element.poles() for element in row] for row in self.doit().args[0]]
  3378. def elem_zeros(self):
  3379. """
  3380. Returns the zeros of each element of the ``TransferFunctionMatrix``.
  3381. .. note::
  3382. Actual zeros of a MIMO system are NOT the zeros of individual elements.
  3383. Examples
  3384. ========
  3385. >>> from sympy.abc import s
  3386. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  3387. >>> tf_1 = TransferFunction(3, (s + 1), s)
  3388. >>> tf_2 = TransferFunction(s + 6, (s + 1)*(s + 2), s)
  3389. >>> tf_3 = TransferFunction(s + 3, s**2 + 3*s + 2, s)
  3390. >>> tf_4 = TransferFunction(s**2 - 9*s + 20, s**2 + 5*s - 10, s)
  3391. >>> tfm_1 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, tf_4]])
  3392. >>> tfm_1
  3393. 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))))
  3394. >>> tfm_1.elem_zeros()
  3395. [[[], [-6]], [[-3], [4, 5]]]
  3396. See Also
  3397. ========
  3398. elem_poles
  3399. """
  3400. return [[element.zeros() for element in row] for row in self.doit().args[0]]
  3401. def eval_frequency(self, other):
  3402. """
  3403. Evaluates system response of each transfer function in the ``TransferFunctionMatrix`` at any point in the real or complex plane.
  3404. Examples
  3405. ========
  3406. >>> from sympy.abc import s
  3407. >>> from sympy.physics.control.lti import TransferFunction, TransferFunctionMatrix
  3408. >>> from sympy import I
  3409. >>> tf_1 = TransferFunction(3, (s + 1), s)
  3410. >>> tf_2 = TransferFunction(s + 6, (s + 1)*(s + 2), s)
  3411. >>> tf_3 = TransferFunction(s + 3, s**2 + 3*s + 2, s)
  3412. >>> tf_4 = TransferFunction(s**2 - 9*s + 20, s**2 + 5*s - 10, s)
  3413. >>> tfm_1 = TransferFunctionMatrix([[tf_1, tf_2], [tf_3, tf_4]])
  3414. >>> tfm_1
  3415. 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))))
  3416. >>> tfm_1.eval_frequency(2)
  3417. Matrix([
  3418. [ 1, 2/3],
  3419. [5/12, 3/2]])
  3420. >>> tfm_1.eval_frequency(I*2)
  3421. Matrix([
  3422. [ 3/5 - 6*I/5, -I],
  3423. [3/20 - 11*I/20, -101/74 + 23*I/74]])
  3424. """
  3425. mat = self._expr_mat.subs(self.var, other)
  3426. return mat.expand()
  3427. def _flat(self):
  3428. """Returns flattened list of args in TransferFunctionMatrix"""
  3429. return [elem for tup in self.args[0] for elem in tup]
  3430. def _eval_evalf(self, prec):
  3431. """Calls evalf() on each transfer function in the transfer function matrix"""
  3432. dps = prec_to_dps(prec)
  3433. mat = self._expr_mat.applyfunc(lambda a: a.evalf(n=dps))
  3434. return _to_TFM(mat, self.var)
  3435. def _eval_simplify(self, **kwargs):
  3436. """Simplifies the transfer function matrix"""
  3437. simp_mat = self._expr_mat.applyfunc(lambda a: cancel(a, expand=False))
  3438. return _to_TFM(simp_mat, self.var)
  3439. def expand(self, **hints):
  3440. """Expands the transfer function matrix"""
  3441. expand_mat = self._expr_mat.expand(**hints)
  3442. return _to_TFM(expand_mat, self.var)
  3443. class StateSpace(LinearTimeInvariant):
  3444. r"""
  3445. State space model (ssm) of a linear, time invariant control system.
  3446. Represents the standard state-space model with A, B, C, D as state-space matrices.
  3447. This makes the linear control system:
  3448. (1) x'(t) = A * x(t) + B * u(t); x in R^n , u in R^k
  3449. (2) y(t) = C * x(t) + D * u(t); y in R^m
  3450. where u(t) is any input signal, y(t) the corresponding output, and x(t) the system's state.
  3451. Parameters
  3452. ==========
  3453. A : Matrix
  3454. The State matrix of the state space model.
  3455. B : Matrix
  3456. The Input-to-State matrix of the state space model.
  3457. C : Matrix
  3458. The State-to-Output matrix of the state space model.
  3459. D : Matrix
  3460. The Feedthrough matrix of the state space model.
  3461. Examples
  3462. ========
  3463. >>> from sympy import Matrix
  3464. >>> from sympy.physics.control import StateSpace
  3465. The easiest way to create a StateSpaceModel is via four matrices:
  3466. >>> A = Matrix([[1, 2], [1, 0]])
  3467. >>> B = Matrix([1, 1])
  3468. >>> C = Matrix([[0, 1]])
  3469. >>> D = Matrix([0])
  3470. >>> StateSpace(A, B, C, D)
  3471. StateSpace(Matrix([
  3472. [1, 2],
  3473. [1, 0]]), Matrix([
  3474. [1],
  3475. [1]]), Matrix([[0, 1]]), Matrix([[0]]))
  3476. One can use less matrices. The rest will be filled with a minimum of zeros:
  3477. >>> StateSpace(A, B)
  3478. StateSpace(Matrix([
  3479. [1, 2],
  3480. [1, 0]]), Matrix([
  3481. [1],
  3482. [1]]), Matrix([[0, 0]]), Matrix([[0]]))
  3483. See Also
  3484. ========
  3485. TransferFunction, TransferFunctionMatrix
  3486. References
  3487. ==========
  3488. .. [1] https://en.wikipedia.org/wiki/State-space_representation
  3489. .. [2] https://in.mathworks.com/help/control/ref/ss.html
  3490. """
  3491. def __new__(cls, A=None, B=None, C=None, D=None):
  3492. if A is None:
  3493. A = zeros(1)
  3494. if B is None:
  3495. B = zeros(A.rows, 1)
  3496. if C is None:
  3497. C = zeros(1, A.cols)
  3498. if D is None:
  3499. D = zeros(C.rows, B.cols)
  3500. A = _sympify(A)
  3501. B = _sympify(B)
  3502. C = _sympify(C)
  3503. D = _sympify(D)
  3504. if (isinstance(A, ImmutableDenseMatrix) and isinstance(B, ImmutableDenseMatrix) and
  3505. isinstance(C, ImmutableDenseMatrix) and isinstance(D, ImmutableDenseMatrix)):
  3506. # Check State Matrix is square
  3507. if A.rows != A.cols:
  3508. raise ShapeError("Matrix A must be a square matrix.")
  3509. # Check State and Input matrices have same rows
  3510. if A.rows != B.rows:
  3511. raise ShapeError("Matrices A and B must have the same number of rows.")
  3512. # Check Output and Feedthrough matrices have same rows
  3513. if C.rows != D.rows:
  3514. raise ShapeError("Matrices C and D must have the same number of rows.")
  3515. # Check State and Output matrices have same columns
  3516. if A.cols != C.cols:
  3517. raise ShapeError("Matrices A and C must have the same number of columns.")
  3518. # Check Input and Feedthrough matrices have same columns
  3519. if B.cols != D.cols:
  3520. raise ShapeError("Matrices B and D must have the same number of columns.")
  3521. obj = super(StateSpace, cls).__new__(cls, A, B, C, D)
  3522. obj._A = A
  3523. obj._B = B
  3524. obj._C = C
  3525. obj._D = D
  3526. # Determine if the system is SISO or MIMO
  3527. num_outputs = D.rows
  3528. num_inputs = D.cols
  3529. if num_inputs == 1 and num_outputs == 1:
  3530. obj._is_SISO = True
  3531. obj._clstype = SISOLinearTimeInvariant
  3532. else:
  3533. obj._is_SISO = False
  3534. obj._clstype = MIMOLinearTimeInvariant
  3535. obj.is_StateSpace_object = True
  3536. return obj
  3537. else:
  3538. raise TypeError("A, B, C and D inputs must all be sympy Matrices.")
  3539. @property
  3540. def state_matrix(self):
  3541. """
  3542. Returns the state matrix of the model.
  3543. Examples
  3544. ========
  3545. >>> from sympy import Matrix
  3546. >>> from sympy.physics.control import StateSpace
  3547. >>> A = Matrix([[1, 2], [1, 0]])
  3548. >>> B = Matrix([1, 1])
  3549. >>> C = Matrix([[0, 1]])
  3550. >>> D = Matrix([0])
  3551. >>> ss = StateSpace(A, B, C, D)
  3552. >>> ss.state_matrix
  3553. Matrix([
  3554. [1, 2],
  3555. [1, 0]])
  3556. """
  3557. return self._A
  3558. @property
  3559. def input_matrix(self):
  3560. """
  3561. Returns the input matrix of the model.
  3562. Examples
  3563. ========
  3564. >>> from sympy import Matrix
  3565. >>> from sympy.physics.control import StateSpace
  3566. >>> A = Matrix([[1, 2], [1, 0]])
  3567. >>> B = Matrix([1, 1])
  3568. >>> C = Matrix([[0, 1]])
  3569. >>> D = Matrix([0])
  3570. >>> ss = StateSpace(A, B, C, D)
  3571. >>> ss.input_matrix
  3572. Matrix([
  3573. [1],
  3574. [1]])
  3575. """
  3576. return self._B
  3577. @property
  3578. def output_matrix(self):
  3579. """
  3580. Returns the output matrix of the model.
  3581. Examples
  3582. ========
  3583. >>> from sympy import Matrix
  3584. >>> from sympy.physics.control import StateSpace
  3585. >>> A = Matrix([[1, 2], [1, 0]])
  3586. >>> B = Matrix([1, 1])
  3587. >>> C = Matrix([[0, 1]])
  3588. >>> D = Matrix([0])
  3589. >>> ss = StateSpace(A, B, C, D)
  3590. >>> ss.output_matrix
  3591. Matrix([[0, 1]])
  3592. """
  3593. return self._C
  3594. @property
  3595. def feedforward_matrix(self):
  3596. """
  3597. Returns the feedforward matrix of the model.
  3598. Examples
  3599. ========
  3600. >>> from sympy import Matrix
  3601. >>> from sympy.physics.control import StateSpace
  3602. >>> A = Matrix([[1, 2], [1, 0]])
  3603. >>> B = Matrix([1, 1])
  3604. >>> C = Matrix([[0, 1]])
  3605. >>> D = Matrix([0])
  3606. >>> ss = StateSpace(A, B, C, D)
  3607. >>> ss.feedforward_matrix
  3608. Matrix([[0]])
  3609. """
  3610. return self._D
  3611. A = state_matrix
  3612. B = input_matrix
  3613. C = output_matrix
  3614. D = feedforward_matrix
  3615. @property
  3616. def num_states(self):
  3617. """
  3618. Returns the number of states of the model.
  3619. Examples
  3620. ========
  3621. >>> from sympy import Matrix
  3622. >>> from sympy.physics.control import StateSpace
  3623. >>> A = Matrix([[1, 2], [1, 0]])
  3624. >>> B = Matrix([1, 1])
  3625. >>> C = Matrix([[0, 1]])
  3626. >>> D = Matrix([0])
  3627. >>> ss = StateSpace(A, B, C, D)
  3628. >>> ss.num_states
  3629. 2
  3630. """
  3631. return self._A.rows
  3632. @property
  3633. def num_inputs(self):
  3634. """
  3635. Returns the number of inputs of the model.
  3636. Examples
  3637. ========
  3638. >>> from sympy import Matrix
  3639. >>> from sympy.physics.control import StateSpace
  3640. >>> A = Matrix([[1, 2], [1, 0]])
  3641. >>> B = Matrix([1, 1])
  3642. >>> C = Matrix([[0, 1]])
  3643. >>> D = Matrix([0])
  3644. >>> ss = StateSpace(A, B, C, D)
  3645. >>> ss.num_inputs
  3646. 1
  3647. """
  3648. return self._D.cols
  3649. @property
  3650. def num_outputs(self):
  3651. """
  3652. Returns the number of outputs of the model.
  3653. Examples
  3654. ========
  3655. >>> from sympy import Matrix
  3656. >>> from sympy.physics.control import StateSpace
  3657. >>> A = Matrix([[1, 2], [1, 0]])
  3658. >>> B = Matrix([1, 1])
  3659. >>> C = Matrix([[0, 1]])
  3660. >>> D = Matrix([0])
  3661. >>> ss = StateSpace(A, B, C, D)
  3662. >>> ss.num_outputs
  3663. 1
  3664. """
  3665. return self._D.rows
  3666. @property
  3667. def shape(self):
  3668. """Returns the shape of the equivalent StateSpace system."""
  3669. return self.num_outputs, self.num_inputs
  3670. def dsolve(self, initial_conditions=None, input_vector=None, var=Symbol('t')):
  3671. r"""
  3672. Returns `y(t)` or output of StateSpace given by the solution of equations:
  3673. x'(t) = A * x(t) + B * u(t)
  3674. y(t) = C * x(t) + D * u(t)
  3675. Parameters
  3676. ============
  3677. initial_conditions : Matrix
  3678. The initial conditions of `x` state vector. If not provided, it defaults to a zero vector.
  3679. input_vector : Matrix
  3680. The input vector for state space. If not provided, it defaults to a zero vector.
  3681. var : Symbol
  3682. The symbol representing time. If not provided, it defaults to `t`.
  3683. Examples
  3684. ==========
  3685. >>> from sympy import Matrix
  3686. >>> from sympy.physics.control import StateSpace
  3687. >>> A = Matrix([[-2, 0], [1, -1]])
  3688. >>> B = Matrix([[1], [0]])
  3689. >>> C = Matrix([[2, 1]])
  3690. >>> ip = Matrix([5])
  3691. >>> i = Matrix([0, 0])
  3692. >>> ss = StateSpace(A, B, C)
  3693. >>> ss.dsolve(input_vector=ip, initial_conditions=i).simplify()
  3694. Matrix([[15/2 - 5*exp(-t) - 5*exp(-2*t)/2]])
  3695. If no input is provided it defaults to solving the system with zero initial conditions and zero input.
  3696. >>> ss.dsolve()
  3697. Matrix([[0]])
  3698. References
  3699. ==========
  3700. .. [1] https://web.mit.edu/2.14/www/Handouts/StateSpaceResponse.pdf
  3701. .. [2] https://docs.sympy.org/latest/modules/solvers/ode.html#sympy.solvers.ode.systems.linodesolve
  3702. """
  3703. if not isinstance(var, Symbol):
  3704. raise ValueError("Variable for representing time must be a Symbol.")
  3705. if not initial_conditions:
  3706. initial_conditions = zeros(self._A.shape[0], 1)
  3707. elif initial_conditions.shape != (self._A.shape[0], 1):
  3708. raise ShapeError("Initial condition vector should have the same number of "
  3709. "rows as the state matrix.")
  3710. if not input_vector:
  3711. input_vector = zeros(self._B.shape[1], 1)
  3712. elif input_vector.shape != (self._B.shape[1], 1):
  3713. raise ShapeError("Input vector should have the same number of "
  3714. "columns as the input matrix.")
  3715. sol = linodesolve(A=self._A, t=var, b=self._B*input_vector, type='type2', doit=True)
  3716. mat1 = Matrix(sol)
  3717. mat2 = mat1.replace(var, 0)
  3718. free1 = self._A.free_symbols | self._B.free_symbols | input_vector.free_symbols
  3719. free2 = mat2.free_symbols
  3720. # Get all the free symbols form the matrix
  3721. dummy_symbols = list(free2-free1)
  3722. # Convert the matrix to a Coefficient matrix
  3723. r1, r2 = linear_eq_to_matrix(mat2, dummy_symbols)
  3724. s = linsolve((r1, initial_conditions+r2))
  3725. res_tuple = next(iter(s))
  3726. for ind, v in enumerate(res_tuple):
  3727. mat1 = mat1.replace(dummy_symbols[ind], v)
  3728. res = self._C*mat1 + self._D*input_vector
  3729. return res
  3730. def _eval_evalf(self, prec):
  3731. """
  3732. Returns state space model where numerical expressions are evaluated into floating point numbers.
  3733. """
  3734. dps = prec_to_dps(prec)
  3735. return StateSpace(
  3736. self._A.evalf(n = dps),
  3737. self._B.evalf(n = dps),
  3738. self._C.evalf(n = dps),
  3739. self._D.evalf(n = dps))
  3740. def _eval_rewrite_as_TransferFunction(self, *args):
  3741. """
  3742. Returns the equivalent Transfer Function of the state space model.
  3743. Examples
  3744. ========
  3745. >>> from sympy import Matrix
  3746. >>> from sympy.physics.control import TransferFunction, StateSpace
  3747. >>> A = Matrix([[-5, -1], [3, -1]])
  3748. >>> B = Matrix([2, 5])
  3749. >>> C = Matrix([[1, 2]])
  3750. >>> D = Matrix([0])
  3751. >>> ss = StateSpace(A, B, C, D)
  3752. >>> ss.rewrite(TransferFunction)
  3753. [[TransferFunction(12*s + 59, s**2 + 6*s + 8, s)]]
  3754. """
  3755. s = Symbol('s')
  3756. n = self._A.shape[0]
  3757. I = eye(n)
  3758. G = self._C*(s*I - self._A).solve(self._B) + self._D
  3759. G = G.simplify()
  3760. to_tf = lambda expr: TransferFunction.from_rational_expression(expr, s)
  3761. tf_mat = [[to_tf(expr) for expr in sublist] for sublist in G.tolist()]
  3762. return tf_mat
  3763. def __add__(self, other):
  3764. """
  3765. Add two State Space systems (parallel connection).
  3766. Examples
  3767. ========
  3768. >>> from sympy import Matrix
  3769. >>> from sympy.physics.control import StateSpace
  3770. >>> A1 = Matrix([[1]])
  3771. >>> B1 = Matrix([[2]])
  3772. >>> C1 = Matrix([[-1]])
  3773. >>> D1 = Matrix([[-2]])
  3774. >>> A2 = Matrix([[-1]])
  3775. >>> B2 = Matrix([[-2]])
  3776. >>> C2 = Matrix([[1]])
  3777. >>> D2 = Matrix([[2]])
  3778. >>> ss1 = StateSpace(A1, B1, C1, D1)
  3779. >>> ss2 = StateSpace(A2, B2, C2, D2)
  3780. >>> ss1 + ss2
  3781. StateSpace(Matrix([
  3782. [1, 0],
  3783. [0, -1]]), Matrix([
  3784. [ 2],
  3785. [-2]]), Matrix([[-1, 1]]), Matrix([[0]]))
  3786. """
  3787. # Check for scalars
  3788. if isinstance(other, (int, float, complex, Symbol)):
  3789. A = self._A
  3790. B = self._B
  3791. C = self._C
  3792. D = self._D.applyfunc(lambda element: element + other)
  3793. else:
  3794. # Check nature of system
  3795. if not isinstance(other, StateSpace):
  3796. raise ValueError("Addition is only supported for 2 State Space models.")
  3797. # Check dimensions of system
  3798. elif ((self.num_inputs != other.num_inputs) or (self.num_outputs != other.num_outputs)):
  3799. raise ShapeError("Systems with incompatible inputs and outputs cannot be added.")
  3800. m1 = (self._A).row_join(zeros(self._A.shape[0], other._A.shape[-1]))
  3801. m2 = zeros(other._A.shape[0], self._A.shape[-1]).row_join(other._A)
  3802. A = m1.col_join(m2)
  3803. B = self._B.col_join(other._B)
  3804. C = self._C.row_join(other._C)
  3805. D = self._D + other._D
  3806. return StateSpace(A, B, C, D)
  3807. def __radd__(self, other):
  3808. """
  3809. Right add two State Space systems.
  3810. Examples
  3811. ========
  3812. >>> from sympy.physics.control import StateSpace
  3813. >>> s = StateSpace()
  3814. >>> 5 + s
  3815. StateSpace(Matrix([[0]]), Matrix([[0]]), Matrix([[0]]), Matrix([[5]]))
  3816. """
  3817. return self + other
  3818. def __sub__(self, other):
  3819. """
  3820. Subtract two State Space systems.
  3821. Examples
  3822. ========
  3823. >>> from sympy import Matrix
  3824. >>> from sympy.physics.control import StateSpace
  3825. >>> A1 = Matrix([[1]])
  3826. >>> B1 = Matrix([[2]])
  3827. >>> C1 = Matrix([[-1]])
  3828. >>> D1 = Matrix([[-2]])
  3829. >>> A2 = Matrix([[-1]])
  3830. >>> B2 = Matrix([[-2]])
  3831. >>> C2 = Matrix([[1]])
  3832. >>> D2 = Matrix([[2]])
  3833. >>> ss1 = StateSpace(A1, B1, C1, D1)
  3834. >>> ss2 = StateSpace(A2, B2, C2, D2)
  3835. >>> ss1 - ss2
  3836. StateSpace(Matrix([
  3837. [1, 0],
  3838. [0, -1]]), Matrix([
  3839. [ 2],
  3840. [-2]]), Matrix([[-1, -1]]), Matrix([[-4]]))
  3841. """
  3842. return self + (-other)
  3843. def __rsub__(self, other):
  3844. """
  3845. Right subtract two tate Space systems.
  3846. Examples
  3847. ========
  3848. >>> from sympy.physics.control import StateSpace
  3849. >>> s = StateSpace()
  3850. >>> 5 - s
  3851. StateSpace(Matrix([[0]]), Matrix([[0]]), Matrix([[0]]), Matrix([[5]]))
  3852. """
  3853. return other + (-self)
  3854. def __neg__(self):
  3855. """
  3856. Returns the negation of the state space model.
  3857. Examples
  3858. ========
  3859. >>> from sympy import Matrix
  3860. >>> from sympy.physics.control import StateSpace
  3861. >>> A = Matrix([[-5, -1], [3, -1]])
  3862. >>> B = Matrix([2, 5])
  3863. >>> C = Matrix([[1, 2]])
  3864. >>> D = Matrix([0])
  3865. >>> ss = StateSpace(A, B, C, D)
  3866. >>> -ss
  3867. StateSpace(Matrix([
  3868. [-5, -1],
  3869. [ 3, -1]]), Matrix([
  3870. [2],
  3871. [5]]), Matrix([[-1, -2]]), Matrix([[0]]))
  3872. """
  3873. return StateSpace(self._A, self._B, -self._C, -self._D)
  3874. def __mul__(self, other):
  3875. """
  3876. Multiplication of two State Space systems (serial connection).
  3877. Examples
  3878. ========
  3879. >>> from sympy import Matrix
  3880. >>> from sympy.physics.control import StateSpace
  3881. >>> A = Matrix([[-5, -1], [3, -1]])
  3882. >>> B = Matrix([2, 5])
  3883. >>> C = Matrix([[1, 2]])
  3884. >>> D = Matrix([0])
  3885. >>> ss = StateSpace(A, B, C, D)
  3886. >>> ss*5
  3887. StateSpace(Matrix([
  3888. [-5, -1],
  3889. [ 3, -1]]), Matrix([
  3890. [2],
  3891. [5]]), Matrix([[5, 10]]), Matrix([[0]]))
  3892. """
  3893. # Check for scalars
  3894. if isinstance(other, (int, float, complex, Symbol)):
  3895. A = self._A
  3896. B = self._B
  3897. C = self._C.applyfunc(lambda element: element*other)
  3898. D = self._D.applyfunc(lambda element: element*other)
  3899. else:
  3900. # Check nature of system
  3901. if not isinstance(other, StateSpace):
  3902. raise ValueError("Multiplication is only supported for 2 State Space models.")
  3903. # Check dimensions of system
  3904. elif self.num_inputs != other.num_outputs:
  3905. raise ShapeError("Systems with incompatible inputs and outputs cannot be multiplied.")
  3906. m1 = (other._A).row_join(zeros(other._A.shape[0], self._A.shape[1]))
  3907. m2 = (self._B * other._C).row_join(self._A)
  3908. A = m1.col_join(m2)
  3909. B = (other._B).col_join(self._B * other._D)
  3910. C = (self._D * other._C).row_join(self._C)
  3911. D = self._D * other._D
  3912. return StateSpace(A, B, C, D)
  3913. def __rmul__(self, other):
  3914. """
  3915. Right multiply two tate Space systems.
  3916. Examples
  3917. ========
  3918. >>> from sympy import Matrix
  3919. >>> from sympy.physics.control import StateSpace
  3920. >>> A = Matrix([[-5, -1], [3, -1]])
  3921. >>> B = Matrix([2, 5])
  3922. >>> C = Matrix([[1, 2]])
  3923. >>> D = Matrix([0])
  3924. >>> ss = StateSpace(A, B, C, D)
  3925. >>> 5*ss
  3926. StateSpace(Matrix([
  3927. [-5, -1],
  3928. [ 3, -1]]), Matrix([
  3929. [10],
  3930. [25]]), Matrix([[1, 2]]), Matrix([[0]]))
  3931. """
  3932. if isinstance(other, (int, float, complex, Symbol)):
  3933. A = self._A
  3934. C = self._C
  3935. B = self._B.applyfunc(lambda element: element*other)
  3936. D = self._D.applyfunc(lambda element: element*other)
  3937. return StateSpace(A, B, C, D)
  3938. else:
  3939. return self*other
  3940. def __repr__(self):
  3941. A_str = self._A.__repr__()
  3942. B_str = self._B.__repr__()
  3943. C_str = self._C.__repr__()
  3944. D_str = self._D.__repr__()
  3945. return f"StateSpace(\n{A_str},\n\n{B_str},\n\n{C_str},\n\n{D_str})"
  3946. def append(self, other):
  3947. """
  3948. Returns the first model appended with the second model. The order is preserved.
  3949. Examples
  3950. ========
  3951. >>> from sympy import Matrix
  3952. >>> from sympy.physics.control import StateSpace
  3953. >>> A1 = Matrix([[1]])
  3954. >>> B1 = Matrix([[2]])
  3955. >>> C1 = Matrix([[-1]])
  3956. >>> D1 = Matrix([[-2]])
  3957. >>> A2 = Matrix([[-1]])
  3958. >>> B2 = Matrix([[-2]])
  3959. >>> C2 = Matrix([[1]])
  3960. >>> D2 = Matrix([[2]])
  3961. >>> ss1 = StateSpace(A1, B1, C1, D1)
  3962. >>> ss2 = StateSpace(A2, B2, C2, D2)
  3963. >>> ss1.append(ss2)
  3964. StateSpace(Matrix([
  3965. [1, 0],
  3966. [0, -1]]), Matrix([
  3967. [2, 0],
  3968. [0, -2]]), Matrix([
  3969. [-1, 0],
  3970. [ 0, 1]]), Matrix([
  3971. [-2, 0],
  3972. [ 0, 2]]))
  3973. """
  3974. n = self.num_states + other.num_states
  3975. m = self.num_inputs + other.num_inputs
  3976. p = self.num_outputs + other.num_outputs
  3977. A = zeros(n, n)
  3978. B = zeros(n, m)
  3979. C = zeros(p, n)
  3980. D = zeros(p, m)
  3981. A[:self.num_states, :self.num_states] = self._A
  3982. A[self.num_states:, self.num_states:] = other._A
  3983. B[:self.num_states, :self.num_inputs] = self._B
  3984. B[self.num_states:, self.num_inputs:] = other._B
  3985. C[:self.num_outputs, :self.num_states] = self._C
  3986. C[self.num_outputs:, self.num_states:] = other._C
  3987. D[:self.num_outputs, :self.num_inputs] = self._D
  3988. D[self.num_outputs:, self.num_inputs:] = other._D
  3989. return StateSpace(A, B, C, D)
  3990. def observability_matrix(self):
  3991. """
  3992. Returns the observability matrix of the state space model:
  3993. [C, C * A^1, C * A^2, .. , C * A^(n-1)]; A in R^(n x n), C in R^(m x k)
  3994. Examples
  3995. ========
  3996. >>> from sympy import Matrix
  3997. >>> from sympy.physics.control import StateSpace
  3998. >>> A = Matrix([[-1.5, -2], [1, 0]])
  3999. >>> B = Matrix([0.5, 0])
  4000. >>> C = Matrix([[0, 1]])
  4001. >>> D = Matrix([1])
  4002. >>> ss = StateSpace(A, B, C, D)
  4003. >>> ob = ss.observability_matrix()
  4004. >>> ob
  4005. Matrix([
  4006. [0, 1],
  4007. [1, 0]])
  4008. References
  4009. ==========
  4010. .. [1] https://in.mathworks.com/help/control/ref/statespacemodel.obsv.html
  4011. """
  4012. n = self.num_states
  4013. ob = self._C
  4014. for i in range(1,n):
  4015. ob = ob.col_join(self._C * self._A**i)
  4016. return ob
  4017. def observable_subspace(self):
  4018. """
  4019. Returns the observable subspace of the state space model.
  4020. Examples
  4021. ========
  4022. >>> from sympy import Matrix
  4023. >>> from sympy.physics.control import StateSpace
  4024. >>> A = Matrix([[-1.5, -2], [1, 0]])
  4025. >>> B = Matrix([0.5, 0])
  4026. >>> C = Matrix([[0, 1]])
  4027. >>> D = Matrix([1])
  4028. >>> ss = StateSpace(A, B, C, D)
  4029. >>> ob_subspace = ss.observable_subspace()
  4030. >>> ob_subspace
  4031. [Matrix([
  4032. [0],
  4033. [1]]), Matrix([
  4034. [1],
  4035. [0]])]
  4036. """
  4037. return self.observability_matrix().columnspace()
  4038. def is_observable(self):
  4039. """
  4040. Returns if the state space model is observable.
  4041. Examples
  4042. ========
  4043. >>> from sympy import Matrix
  4044. >>> from sympy.physics.control import StateSpace
  4045. >>> A = Matrix([[-1.5, -2], [1, 0]])
  4046. >>> B = Matrix([0.5, 0])
  4047. >>> C = Matrix([[0, 1]])
  4048. >>> D = Matrix([1])
  4049. >>> ss = StateSpace(A, B, C, D)
  4050. >>> ss.is_observable()
  4051. True
  4052. """
  4053. return self.observability_matrix().rank() == self.num_states
  4054. def controllability_matrix(self):
  4055. """
  4056. Returns the controllability matrix of the system:
  4057. [B, A * B, A^2 * B, .. , A^(n-1) * B]; A in R^(n x n), B in R^(n x m)
  4058. Examples
  4059. ========
  4060. >>> from sympy import Matrix
  4061. >>> from sympy.physics.control import StateSpace
  4062. >>> A = Matrix([[-1.5, -2], [1, 0]])
  4063. >>> B = Matrix([0.5, 0])
  4064. >>> C = Matrix([[0, 1]])
  4065. >>> D = Matrix([1])
  4066. >>> ss = StateSpace(A, B, C, D)
  4067. >>> ss.controllability_matrix()
  4068. Matrix([
  4069. [0.5, -0.75],
  4070. [ 0, 0.5]])
  4071. References
  4072. ==========
  4073. .. [1] https://in.mathworks.com/help/control/ref/statespacemodel.ctrb.html
  4074. """
  4075. co = self._B
  4076. n = self._A.shape[0]
  4077. for i in range(1, n):
  4078. co = co.row_join(((self._A)**i) * self._B)
  4079. return co
  4080. def controllable_subspace(self):
  4081. """
  4082. Returns the controllable subspace of the state space model.
  4083. Examples
  4084. ========
  4085. >>> from sympy import Matrix
  4086. >>> from sympy.physics.control import StateSpace
  4087. >>> A = Matrix([[-1.5, -2], [1, 0]])
  4088. >>> B = Matrix([0.5, 0])
  4089. >>> C = Matrix([[0, 1]])
  4090. >>> D = Matrix([1])
  4091. >>> ss = StateSpace(A, B, C, D)
  4092. >>> co_subspace = ss.controllable_subspace()
  4093. >>> co_subspace
  4094. [Matrix([
  4095. [0.5],
  4096. [ 0]]), Matrix([
  4097. [-0.75],
  4098. [ 0.5]])]
  4099. """
  4100. return self.controllability_matrix().columnspace()
  4101. def is_controllable(self):
  4102. """
  4103. Returns if the state space model is controllable.
  4104. Examples
  4105. ========
  4106. >>> from sympy import Matrix
  4107. >>> from sympy.physics.control import StateSpace
  4108. >>> A = Matrix([[-1.5, -2], [1, 0]])
  4109. >>> B = Matrix([0.5, 0])
  4110. >>> C = Matrix([[0, 1]])
  4111. >>> D = Matrix([1])
  4112. >>> ss = StateSpace(A, B, C, D)
  4113. >>> ss.is_controllable()
  4114. True
  4115. """
  4116. return self.controllability_matrix().rank() == self.num_states