ltisys.py 120 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664
  1. """
  2. ltisys -- a collection of classes and functions for modeling linear
  3. time invariant systems.
  4. """
  5. from __future__ import division, print_function, absolute_import
  6. #
  7. # Author: Travis Oliphant 2001
  8. #
  9. # Feb 2010: Warren Weckesser
  10. # Rewrote lsim2 and added impulse2.
  11. # Apr 2011: Jeffrey Armstrong <jeff@approximatrix.com>
  12. # Added dlsim, dstep, dimpulse, cont2discrete
  13. # Aug 2013: Juan Luis Cano
  14. # Rewrote abcd_normalize.
  15. # Jan 2015: Irvin Probst irvin DOT probst AT ensta-bretagne DOT fr
  16. # Added pole placement
  17. # Mar 2015: Clancy Rowley
  18. # Rewrote lsim
  19. # May 2015: Felix Berkenkamp
  20. # Split lti class into subclasses
  21. # Merged discrete systems and added dlti
  22. import warnings
  23. # np.linalg.qr fails on some tests with LinAlgError: zgeqrf returns -7
  24. # use scipy's qr until this is solved
  25. import scipy._lib.six as six
  26. from scipy.linalg import qr as s_qr
  27. from scipy import integrate, interpolate, linalg
  28. from scipy.interpolate import interp1d
  29. from scipy._lib.six import xrange
  30. from .filter_design import (tf2zpk, zpk2tf, normalize, freqs, freqz, freqs_zpk,
  31. freqz_zpk)
  32. from .lti_conversion import (tf2ss, abcd_normalize, ss2tf, zpk2ss, ss2zpk,
  33. cont2discrete)
  34. import numpy
  35. import numpy as np
  36. from numpy import (real, atleast_1d, atleast_2d, squeeze, asarray, zeros,
  37. dot, transpose, ones, zeros_like, linspace, nan_to_num)
  38. import copy
  39. __all__ = ['lti', 'dlti', 'TransferFunction', 'ZerosPolesGain', 'StateSpace',
  40. 'lsim', 'lsim2', 'impulse', 'impulse2', 'step', 'step2', 'bode',
  41. 'freqresp', 'place_poles', 'dlsim', 'dstep', 'dimpulse',
  42. 'dfreqresp', 'dbode']
  43. class LinearTimeInvariant(object):
  44. def __new__(cls, *system, **kwargs):
  45. """Create a new object, don't allow direct instances."""
  46. if cls is LinearTimeInvariant:
  47. raise NotImplementedError('The LinearTimeInvariant class is not '
  48. 'meant to be used directly, use `lti` '
  49. 'or `dlti` instead.')
  50. return super(LinearTimeInvariant, cls).__new__(cls)
  51. def __init__(self):
  52. """
  53. Initialize the `lti` baseclass.
  54. The heavy lifting is done by the subclasses.
  55. """
  56. super(LinearTimeInvariant, self).__init__()
  57. self.inputs = None
  58. self.outputs = None
  59. self._dt = None
  60. @property
  61. def dt(self):
  62. """Return the sampling time of the system, `None` for `lti` systems."""
  63. return self._dt
  64. @property
  65. def _dt_dict(self):
  66. if self.dt is None:
  67. return {}
  68. else:
  69. return {'dt': self.dt}
  70. @property
  71. def zeros(self):
  72. """Zeros of the system."""
  73. return self.to_zpk().zeros
  74. @property
  75. def poles(self):
  76. """Poles of the system."""
  77. return self.to_zpk().poles
  78. def _as_ss(self):
  79. """Convert to `StateSpace` system, without copying.
  80. Returns
  81. -------
  82. sys: StateSpace
  83. The `StateSpace` system. If the class is already an instance of
  84. `StateSpace` then this instance is returned.
  85. """
  86. if isinstance(self, StateSpace):
  87. return self
  88. else:
  89. return self.to_ss()
  90. def _as_zpk(self):
  91. """Convert to `ZerosPolesGain` system, without copying.
  92. Returns
  93. -------
  94. sys: ZerosPolesGain
  95. The `ZerosPolesGain` system. If the class is already an instance of
  96. `ZerosPolesGain` then this instance is returned.
  97. """
  98. if isinstance(self, ZerosPolesGain):
  99. return self
  100. else:
  101. return self.to_zpk()
  102. def _as_tf(self):
  103. """Convert to `TransferFunction` system, without copying.
  104. Returns
  105. -------
  106. sys: ZerosPolesGain
  107. The `TransferFunction` system. If the class is already an instance of
  108. `TransferFunction` then this instance is returned.
  109. """
  110. if isinstance(self, TransferFunction):
  111. return self
  112. else:
  113. return self.to_tf()
  114. class lti(LinearTimeInvariant):
  115. """
  116. Continuous-time linear time invariant system base class.
  117. Parameters
  118. ----------
  119. *system : arguments
  120. The `lti` class can be instantiated with either 2, 3 or 4 arguments.
  121. The following gives the number of arguments and the corresponding
  122. continuous-time subclass that is created:
  123. * 2: `TransferFunction`: (numerator, denominator)
  124. * 3: `ZerosPolesGain`: (zeros, poles, gain)
  125. * 4: `StateSpace`: (A, B, C, D)
  126. Each argument can be an array or a sequence.
  127. See Also
  128. --------
  129. ZerosPolesGain, StateSpace, TransferFunction, dlti
  130. Notes
  131. -----
  132. `lti` instances do not exist directly. Instead, `lti` creates an instance
  133. of one of its subclasses: `StateSpace`, `TransferFunction` or
  134. `ZerosPolesGain`.
  135. If (numerator, denominator) is passed in for ``*system``, coefficients for
  136. both the numerator and denominator should be specified in descending
  137. exponent order (e.g., ``s^2 + 3s + 5`` would be represented as ``[1, 3,
  138. 5]``).
  139. Changing the value of properties that are not directly part of the current
  140. system representation (such as the `zeros` of a `StateSpace` system) is
  141. very inefficient and may lead to numerical inaccuracies. It is better to
  142. convert to the specific system representation first. For example, call
  143. ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain.
  144. Examples
  145. --------
  146. >>> from scipy import signal
  147. >>> signal.lti(1, 2, 3, 4)
  148. StateSpaceContinuous(
  149. array([[1]]),
  150. array([[2]]),
  151. array([[3]]),
  152. array([[4]]),
  153. dt: None
  154. )
  155. >>> signal.lti([1, 2], [3, 4], 5)
  156. ZerosPolesGainContinuous(
  157. array([1, 2]),
  158. array([3, 4]),
  159. 5,
  160. dt: None
  161. )
  162. >>> signal.lti([3, 4], [1, 2])
  163. TransferFunctionContinuous(
  164. array([3., 4.]),
  165. array([1., 2.]),
  166. dt: None
  167. )
  168. """
  169. def __new__(cls, *system):
  170. """Create an instance of the appropriate subclass."""
  171. if cls is lti:
  172. N = len(system)
  173. if N == 2:
  174. return TransferFunctionContinuous.__new__(
  175. TransferFunctionContinuous, *system)
  176. elif N == 3:
  177. return ZerosPolesGainContinuous.__new__(
  178. ZerosPolesGainContinuous, *system)
  179. elif N == 4:
  180. return StateSpaceContinuous.__new__(StateSpaceContinuous,
  181. *system)
  182. else:
  183. raise ValueError("`system` needs to be an instance of `lti` "
  184. "or have 2, 3 or 4 arguments.")
  185. # __new__ was called from a subclass, let it call its own functions
  186. return super(lti, cls).__new__(cls)
  187. def __init__(self, *system):
  188. """
  189. Initialize the `lti` baseclass.
  190. The heavy lifting is done by the subclasses.
  191. """
  192. super(lti, self).__init__(*system)
  193. def impulse(self, X0=None, T=None, N=None):
  194. """
  195. Return the impulse response of a continuous-time system.
  196. See `impulse` for details.
  197. """
  198. return impulse(self, X0=X0, T=T, N=N)
  199. def step(self, X0=None, T=None, N=None):
  200. """
  201. Return the step response of a continuous-time system.
  202. See `step` for details.
  203. """
  204. return step(self, X0=X0, T=T, N=N)
  205. def output(self, U, T, X0=None):
  206. """
  207. Return the response of a continuous-time system to input `U`.
  208. See `lsim` for details.
  209. """
  210. return lsim(self, U, T, X0=X0)
  211. def bode(self, w=None, n=100):
  212. """
  213. Calculate Bode magnitude and phase data of a continuous-time system.
  214. Returns a 3-tuple containing arrays of frequencies [rad/s], magnitude
  215. [dB] and phase [deg]. See `bode` for details.
  216. Examples
  217. --------
  218. >>> from scipy import signal
  219. >>> import matplotlib.pyplot as plt
  220. >>> sys = signal.TransferFunction([1], [1, 1])
  221. >>> w, mag, phase = sys.bode()
  222. >>> plt.figure()
  223. >>> plt.semilogx(w, mag) # Bode magnitude plot
  224. >>> plt.figure()
  225. >>> plt.semilogx(w, phase) # Bode phase plot
  226. >>> plt.show()
  227. """
  228. return bode(self, w=w, n=n)
  229. def freqresp(self, w=None, n=10000):
  230. """
  231. Calculate the frequency response of a continuous-time system.
  232. Returns a 2-tuple containing arrays of frequencies [rad/s] and
  233. complex magnitude.
  234. See `freqresp` for details.
  235. """
  236. return freqresp(self, w=w, n=n)
  237. def to_discrete(self, dt, method='zoh', alpha=None):
  238. """Return a discretized version of the current system.
  239. Parameters: See `cont2discrete` for details.
  240. Returns
  241. -------
  242. sys: instance of `dlti`
  243. """
  244. raise NotImplementedError('to_discrete is not implemented for this '
  245. 'system class.')
  246. class dlti(LinearTimeInvariant):
  247. """
  248. Discrete-time linear time invariant system base class.
  249. Parameters
  250. ----------
  251. *system: arguments
  252. The `dlti` class can be instantiated with either 2, 3 or 4 arguments.
  253. The following gives the number of arguments and the corresponding
  254. discrete-time subclass that is created:
  255. * 2: `TransferFunction`: (numerator, denominator)
  256. * 3: `ZerosPolesGain`: (zeros, poles, gain)
  257. * 4: `StateSpace`: (A, B, C, D)
  258. Each argument can be an array or a sequence.
  259. dt: float, optional
  260. Sampling time [s] of the discrete-time systems. Defaults to ``True``
  261. (unspecified sampling time). Must be specified as a keyword argument,
  262. for example, ``dt=0.1``.
  263. See Also
  264. --------
  265. ZerosPolesGain, StateSpace, TransferFunction, lti
  266. Notes
  267. -----
  268. `dlti` instances do not exist directly. Instead, `dlti` creates an instance
  269. of one of its subclasses: `StateSpace`, `TransferFunction` or
  270. `ZerosPolesGain`.
  271. Changing the value of properties that are not directly part of the current
  272. system representation (such as the `zeros` of a `StateSpace` system) is
  273. very inefficient and may lead to numerical inaccuracies. It is better to
  274. convert to the specific system representation first. For example, call
  275. ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain.
  276. If (numerator, denominator) is passed in for ``*system``, coefficients for
  277. both the numerator and denominator should be specified in descending
  278. exponent order (e.g., ``z^2 + 3z + 5`` would be represented as ``[1, 3,
  279. 5]``).
  280. .. versionadded:: 0.18.0
  281. Examples
  282. --------
  283. >>> from scipy import signal
  284. >>> signal.dlti(1, 2, 3, 4)
  285. StateSpaceDiscrete(
  286. array([[1]]),
  287. array([[2]]),
  288. array([[3]]),
  289. array([[4]]),
  290. dt: True
  291. )
  292. >>> signal.dlti(1, 2, 3, 4, dt=0.1)
  293. StateSpaceDiscrete(
  294. array([[1]]),
  295. array([[2]]),
  296. array([[3]]),
  297. array([[4]]),
  298. dt: 0.1
  299. )
  300. >>> signal.dlti([1, 2], [3, 4], 5, dt=0.1)
  301. ZerosPolesGainDiscrete(
  302. array([1, 2]),
  303. array([3, 4]),
  304. 5,
  305. dt: 0.1
  306. )
  307. >>> signal.dlti([3, 4], [1, 2], dt=0.1)
  308. TransferFunctionDiscrete(
  309. array([3., 4.]),
  310. array([1., 2.]),
  311. dt: 0.1
  312. )
  313. """
  314. def __new__(cls, *system, **kwargs):
  315. """Create an instance of the appropriate subclass."""
  316. if cls is dlti:
  317. N = len(system)
  318. if N == 2:
  319. return TransferFunctionDiscrete.__new__(
  320. TransferFunctionDiscrete, *system, **kwargs)
  321. elif N == 3:
  322. return ZerosPolesGainDiscrete.__new__(ZerosPolesGainDiscrete,
  323. *system, **kwargs)
  324. elif N == 4:
  325. return StateSpaceDiscrete.__new__(StateSpaceDiscrete, *system,
  326. **kwargs)
  327. else:
  328. raise ValueError("`system` needs to be an instance of `dlti` "
  329. "or have 2, 3 or 4 arguments.")
  330. # __new__ was called from a subclass, let it call its own functions
  331. return super(dlti, cls).__new__(cls)
  332. def __init__(self, *system, **kwargs):
  333. """
  334. Initialize the `lti` baseclass.
  335. The heavy lifting is done by the subclasses.
  336. """
  337. dt = kwargs.pop('dt', True)
  338. super(dlti, self).__init__(*system, **kwargs)
  339. self.dt = dt
  340. @property
  341. def dt(self):
  342. """Return the sampling time of the system."""
  343. return self._dt
  344. @dt.setter
  345. def dt(self, dt):
  346. self._dt = dt
  347. def impulse(self, x0=None, t=None, n=None):
  348. """
  349. Return the impulse response of the discrete-time `dlti` system.
  350. See `dimpulse` for details.
  351. """
  352. return dimpulse(self, x0=x0, t=t, n=n)
  353. def step(self, x0=None, t=None, n=None):
  354. """
  355. Return the step response of the discrete-time `dlti` system.
  356. See `dstep` for details.
  357. """
  358. return dstep(self, x0=x0, t=t, n=n)
  359. def output(self, u, t, x0=None):
  360. """
  361. Return the response of the discrete-time system to input `u`.
  362. See `dlsim` for details.
  363. """
  364. return dlsim(self, u, t, x0=x0)
  365. def bode(self, w=None, n=100):
  366. """
  367. Calculate Bode magnitude and phase data of a discrete-time system.
  368. Returns a 3-tuple containing arrays of frequencies [rad/s], magnitude
  369. [dB] and phase [deg]. See `dbode` for details.
  370. Examples
  371. --------
  372. >>> from scipy import signal
  373. >>> import matplotlib.pyplot as plt
  374. Transfer function: H(z) = 1 / (z^2 + 2z + 3) with sampling time 0.5s
  375. >>> sys = signal.TransferFunction([1], [1, 2, 3], dt=0.5)
  376. Equivalent: signal.dbode(sys)
  377. >>> w, mag, phase = sys.bode()
  378. >>> plt.figure()
  379. >>> plt.semilogx(w, mag) # Bode magnitude plot
  380. >>> plt.figure()
  381. >>> plt.semilogx(w, phase) # Bode phase plot
  382. >>> plt.show()
  383. """
  384. return dbode(self, w=w, n=n)
  385. def freqresp(self, w=None, n=10000, whole=False):
  386. """
  387. Calculate the frequency response of a discrete-time system.
  388. Returns a 2-tuple containing arrays of frequencies [rad/s] and
  389. complex magnitude.
  390. See `dfreqresp` for details.
  391. """
  392. return dfreqresp(self, w=w, n=n, whole=whole)
  393. class TransferFunction(LinearTimeInvariant):
  394. r"""Linear Time Invariant system class in transfer function form.
  395. Represents the system as the continuous-time transfer function
  396. :math:`H(s)=\sum_{i=0}^N b[N-i] s^i / \sum_{j=0}^M a[M-j] s^j` or the
  397. discrete-time transfer function
  398. :math:`H(s)=\sum_{i=0}^N b[N-i] z^i / \sum_{j=0}^M a[M-j] z^j`, where
  399. :math:`b` are elements of the numerator `num`, :math:`a` are elements of
  400. the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``.
  401. `TransferFunction` systems inherit additional
  402. functionality from the `lti`, respectively the `dlti` classes, depending on
  403. which system representation is used.
  404. Parameters
  405. ----------
  406. *system: arguments
  407. The `TransferFunction` class can be instantiated with 1 or 2
  408. arguments. The following gives the number of input arguments and their
  409. interpretation:
  410. * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or
  411. `ZerosPolesGain`)
  412. * 2: array_like: (numerator, denominator)
  413. dt: float, optional
  414. Sampling time [s] of the discrete-time systems. Defaults to `None`
  415. (continuous-time). Must be specified as a keyword argument, for
  416. example, ``dt=0.1``.
  417. See Also
  418. --------
  419. ZerosPolesGain, StateSpace, lti, dlti
  420. tf2ss, tf2zpk, tf2sos
  421. Notes
  422. -----
  423. Changing the value of properties that are not part of the
  424. `TransferFunction` system representation (such as the `A`, `B`, `C`, `D`
  425. state-space matrices) is very inefficient and may lead to numerical
  426. inaccuracies. It is better to convert to the specific system
  427. representation first. For example, call ``sys = sys.to_ss()`` before
  428. accessing/changing the A, B, C, D system matrices.
  429. If (numerator, denominator) is passed in for ``*system``, coefficients
  430. for both the numerator and denominator should be specified in descending
  431. exponent order (e.g. ``s^2 + 3s + 5`` or ``z^2 + 3z + 5`` would be
  432. represented as ``[1, 3, 5]``)
  433. Examples
  434. --------
  435. Construct the transfer function:
  436. .. math:: H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1}
  437. >>> from scipy import signal
  438. >>> num = [1, 3, 3]
  439. >>> den = [1, 2, 1]
  440. >>> signal.TransferFunction(num, den)
  441. TransferFunctionContinuous(
  442. array([1., 3., 3.]),
  443. array([1., 2., 1.]),
  444. dt: None
  445. )
  446. Construct the transfer function with a sampling time of 0.1 seconds:
  447. .. math:: H(z) = \frac{z^2 + 3z + 3}{z^2 + 2z + 1}
  448. >>> signal.TransferFunction(num, den, dt=0.1)
  449. TransferFunctionDiscrete(
  450. array([1., 3., 3.]),
  451. array([1., 2., 1.]),
  452. dt: 0.1
  453. )
  454. """
  455. def __new__(cls, *system, **kwargs):
  456. """Handle object conversion if input is an instance of lti."""
  457. if len(system) == 1 and isinstance(system[0], LinearTimeInvariant):
  458. return system[0].to_tf()
  459. # Choose whether to inherit from `lti` or from `dlti`
  460. if cls is TransferFunction:
  461. if kwargs.get('dt') is None:
  462. return TransferFunctionContinuous.__new__(
  463. TransferFunctionContinuous,
  464. *system,
  465. **kwargs)
  466. else:
  467. return TransferFunctionDiscrete.__new__(
  468. TransferFunctionDiscrete,
  469. *system,
  470. **kwargs)
  471. # No special conversion needed
  472. return super(TransferFunction, cls).__new__(cls)
  473. def __init__(self, *system, **kwargs):
  474. """Initialize the state space LTI system."""
  475. # Conversion of lti instances is handled in __new__
  476. if isinstance(system[0], LinearTimeInvariant):
  477. return
  478. # Remove system arguments, not needed by parents anymore
  479. super(TransferFunction, self).__init__(**kwargs)
  480. self._num = None
  481. self._den = None
  482. self.num, self.den = normalize(*system)
  483. def __repr__(self):
  484. """Return representation of the system's transfer function"""
  485. return '{0}(\n{1},\n{2},\ndt: {3}\n)'.format(
  486. self.__class__.__name__,
  487. repr(self.num),
  488. repr(self.den),
  489. repr(self.dt),
  490. )
  491. @property
  492. def num(self):
  493. """Numerator of the `TransferFunction` system."""
  494. return self._num
  495. @num.setter
  496. def num(self, num):
  497. self._num = atleast_1d(num)
  498. # Update dimensions
  499. if len(self.num.shape) > 1:
  500. self.outputs, self.inputs = self.num.shape
  501. else:
  502. self.outputs = 1
  503. self.inputs = 1
  504. @property
  505. def den(self):
  506. """Denominator of the `TransferFunction` system."""
  507. return self._den
  508. @den.setter
  509. def den(self, den):
  510. self._den = atleast_1d(den)
  511. def _copy(self, system):
  512. """
  513. Copy the parameters of another `TransferFunction` object
  514. Parameters
  515. ----------
  516. system : `TransferFunction`
  517. The `StateSpace` system that is to be copied
  518. """
  519. self.num = system.num
  520. self.den = system.den
  521. def to_tf(self):
  522. """
  523. Return a copy of the current `TransferFunction` system.
  524. Returns
  525. -------
  526. sys : instance of `TransferFunction`
  527. The current system (copy)
  528. """
  529. return copy.deepcopy(self)
  530. def to_zpk(self):
  531. """
  532. Convert system representation to `ZerosPolesGain`.
  533. Returns
  534. -------
  535. sys : instance of `ZerosPolesGain`
  536. Zeros, poles, gain representation of the current system
  537. """
  538. return ZerosPolesGain(*tf2zpk(self.num, self.den),
  539. **self._dt_dict)
  540. def to_ss(self):
  541. """
  542. Convert system representation to `StateSpace`.
  543. Returns
  544. -------
  545. sys : instance of `StateSpace`
  546. State space model of the current system
  547. """
  548. return StateSpace(*tf2ss(self.num, self.den),
  549. **self._dt_dict)
  550. @staticmethod
  551. def _z_to_zinv(num, den):
  552. """Change a transfer function from the variable `z` to `z**-1`.
  553. Parameters
  554. ----------
  555. num, den: 1d array_like
  556. Sequences representing the coefficients of the numerator and
  557. denominator polynomials, in order of descending degree of 'z'.
  558. That is, ``5z**2 + 3z + 2`` is presented as ``[5, 3, 2]``.
  559. Returns
  560. -------
  561. num, den: 1d array_like
  562. Sequences representing the coefficients of the numerator and
  563. denominator polynomials, in order of ascending degree of 'z**-1'.
  564. That is, ``5 + 3 z**-1 + 2 z**-2`` is presented as ``[5, 3, 2]``.
  565. """
  566. diff = len(num) - len(den)
  567. if diff > 0:
  568. den = np.hstack((np.zeros(diff), den))
  569. elif diff < 0:
  570. num = np.hstack((np.zeros(-diff), num))
  571. return num, den
  572. @staticmethod
  573. def _zinv_to_z(num, den):
  574. """Change a transfer function from the variable `z` to `z**-1`.
  575. Parameters
  576. ----------
  577. num, den: 1d array_like
  578. Sequences representing the coefficients of the numerator and
  579. denominator polynomials, in order of ascending degree of 'z**-1'.
  580. That is, ``5 + 3 z**-1 + 2 z**-2`` is presented as ``[5, 3, 2]``.
  581. Returns
  582. -------
  583. num, den: 1d array_like
  584. Sequences representing the coefficients of the numerator and
  585. denominator polynomials, in order of descending degree of 'z'.
  586. That is, ``5z**2 + 3z + 2`` is presented as ``[5, 3, 2]``.
  587. """
  588. diff = len(num) - len(den)
  589. if diff > 0:
  590. den = np.hstack((den, np.zeros(diff)))
  591. elif diff < 0:
  592. num = np.hstack((num, np.zeros(-diff)))
  593. return num, den
  594. class TransferFunctionContinuous(TransferFunction, lti):
  595. r"""
  596. Continuous-time Linear Time Invariant system in transfer function form.
  597. Represents the system as the transfer function
  598. :math:`H(s)=\sum_{i=0}^N b[N-i] s^i / \sum_{j=0}^M a[M-j] s^j`, where
  599. :math:`b` are elements of the numerator `num`, :math:`a` are elements of
  600. the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``.
  601. Continuous-time `TransferFunction` systems inherit additional
  602. functionality from the `lti` class.
  603. Parameters
  604. ----------
  605. *system: arguments
  606. The `TransferFunction` class can be instantiated with 1 or 2
  607. arguments. The following gives the number of input arguments and their
  608. interpretation:
  609. * 1: `lti` system: (`StateSpace`, `TransferFunction` or
  610. `ZerosPolesGain`)
  611. * 2: array_like: (numerator, denominator)
  612. See Also
  613. --------
  614. ZerosPolesGain, StateSpace, lti
  615. tf2ss, tf2zpk, tf2sos
  616. Notes
  617. -----
  618. Changing the value of properties that are not part of the
  619. `TransferFunction` system representation (such as the `A`, `B`, `C`, `D`
  620. state-space matrices) is very inefficient and may lead to numerical
  621. inaccuracies. It is better to convert to the specific system
  622. representation first. For example, call ``sys = sys.to_ss()`` before
  623. accessing/changing the A, B, C, D system matrices.
  624. If (numerator, denominator) is passed in for ``*system``, coefficients
  625. for both the numerator and denominator should be specified in descending
  626. exponent order (e.g. ``s^2 + 3s + 5`` would be represented as
  627. ``[1, 3, 5]``)
  628. Examples
  629. --------
  630. Construct the transfer function:
  631. .. math:: H(s) = \frac{s^2 + 3s + 3}{s^2 + 2s + 1}
  632. >>> from scipy import signal
  633. >>> num = [1, 3, 3]
  634. >>> den = [1, 2, 1]
  635. >>> signal.TransferFunction(num, den)
  636. TransferFunctionContinuous(
  637. array([ 1., 3., 3.]),
  638. array([ 1., 2., 1.]),
  639. dt: None
  640. )
  641. """
  642. def to_discrete(self, dt, method='zoh', alpha=None):
  643. """
  644. Returns the discretized `TransferFunction` system.
  645. Parameters: See `cont2discrete` for details.
  646. Returns
  647. -------
  648. sys: instance of `dlti` and `StateSpace`
  649. """
  650. return TransferFunction(*cont2discrete((self.num, self.den),
  651. dt,
  652. method=method,
  653. alpha=alpha)[:-1],
  654. dt=dt)
  655. class TransferFunctionDiscrete(TransferFunction, dlti):
  656. r"""
  657. Discrete-time Linear Time Invariant system in transfer function form.
  658. Represents the system as the transfer function
  659. :math:`H(z)=\sum_{i=0}^N b[N-i] z^i / \sum_{j=0}^M a[M-j] z^j`, where
  660. :math:`b` are elements of the numerator `num`, :math:`a` are elements of
  661. the denominator `den`, and ``N == len(b) - 1``, ``M == len(a) - 1``.
  662. Discrete-time `TransferFunction` systems inherit additional functionality
  663. from the `dlti` class.
  664. Parameters
  665. ----------
  666. *system: arguments
  667. The `TransferFunction` class can be instantiated with 1 or 2
  668. arguments. The following gives the number of input arguments and their
  669. interpretation:
  670. * 1: `dlti` system: (`StateSpace`, `TransferFunction` or
  671. `ZerosPolesGain`)
  672. * 2: array_like: (numerator, denominator)
  673. dt: float, optional
  674. Sampling time [s] of the discrete-time systems. Defaults to `True`
  675. (unspecified sampling time). Must be specified as a keyword argument,
  676. for example, ``dt=0.1``.
  677. See Also
  678. --------
  679. ZerosPolesGain, StateSpace, dlti
  680. tf2ss, tf2zpk, tf2sos
  681. Notes
  682. -----
  683. Changing the value of properties that are not part of the
  684. `TransferFunction` system representation (such as the `A`, `B`, `C`, `D`
  685. state-space matrices) is very inefficient and may lead to numerical
  686. inaccuracies.
  687. If (numerator, denominator) is passed in for ``*system``, coefficients
  688. for both the numerator and denominator should be specified in descending
  689. exponent order (e.g., ``z^2 + 3z + 5`` would be represented as
  690. ``[1, 3, 5]``).
  691. Examples
  692. --------
  693. Construct the transfer function with a sampling time of 0.5 seconds:
  694. .. math:: H(z) = \frac{z^2 + 3z + 3}{z^2 + 2z + 1}
  695. >>> from scipy import signal
  696. >>> num = [1, 3, 3]
  697. >>> den = [1, 2, 1]
  698. >>> signal.TransferFunction(num, den, 0.5)
  699. TransferFunctionDiscrete(
  700. array([ 1., 3., 3.]),
  701. array([ 1., 2., 1.]),
  702. dt: 0.5
  703. )
  704. """
  705. pass
  706. class ZerosPolesGain(LinearTimeInvariant):
  707. r"""
  708. Linear Time Invariant system class in zeros, poles, gain form.
  709. Represents the system as the continuous- or discrete-time transfer function
  710. :math:`H(s)=k \prod_i (s - z[i]) / \prod_j (s - p[j])`, where :math:`k` is
  711. the `gain`, :math:`z` are the `zeros` and :math:`p` are the `poles`.
  712. `ZerosPolesGain` systems inherit additional functionality from the `lti`,
  713. respectively the `dlti` classes, depending on which system representation
  714. is used.
  715. Parameters
  716. ----------
  717. *system : arguments
  718. The `ZerosPolesGain` class can be instantiated with 1 or 3
  719. arguments. The following gives the number of input arguments and their
  720. interpretation:
  721. * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or
  722. `ZerosPolesGain`)
  723. * 3: array_like: (zeros, poles, gain)
  724. dt: float, optional
  725. Sampling time [s] of the discrete-time systems. Defaults to `None`
  726. (continuous-time). Must be specified as a keyword argument, for
  727. example, ``dt=0.1``.
  728. See Also
  729. --------
  730. TransferFunction, StateSpace, lti, dlti
  731. zpk2ss, zpk2tf, zpk2sos
  732. Notes
  733. -----
  734. Changing the value of properties that are not part of the
  735. `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D`
  736. state-space matrices) is very inefficient and may lead to numerical
  737. inaccuracies. It is better to convert to the specific system
  738. representation first. For example, call ``sys = sys.to_ss()`` before
  739. accessing/changing the A, B, C, D system matrices.
  740. Examples
  741. --------
  742. >>> from scipy import signal
  743. Transfer function: H(s) = 5(s - 1)(s - 2) / (s - 3)(s - 4)
  744. >>> signal.ZerosPolesGain([1, 2], [3, 4], 5)
  745. ZerosPolesGainContinuous(
  746. array([1, 2]),
  747. array([3, 4]),
  748. 5,
  749. dt: None
  750. )
  751. Transfer function: H(z) = 5(z - 1)(z - 2) / (z - 3)(z - 4)
  752. >>> signal.ZerosPolesGain([1, 2], [3, 4], 5, dt=0.1)
  753. ZerosPolesGainDiscrete(
  754. array([1, 2]),
  755. array([3, 4]),
  756. 5,
  757. dt: 0.1
  758. )
  759. """
  760. def __new__(cls, *system, **kwargs):
  761. """Handle object conversion if input is an instance of `lti`"""
  762. if len(system) == 1 and isinstance(system[0], LinearTimeInvariant):
  763. return system[0].to_zpk()
  764. # Choose whether to inherit from `lti` or from `dlti`
  765. if cls is ZerosPolesGain:
  766. if kwargs.get('dt') is None:
  767. return ZerosPolesGainContinuous.__new__(
  768. ZerosPolesGainContinuous,
  769. *system,
  770. **kwargs)
  771. else:
  772. return ZerosPolesGainDiscrete.__new__(
  773. ZerosPolesGainDiscrete,
  774. *system,
  775. **kwargs
  776. )
  777. # No special conversion needed
  778. return super(ZerosPolesGain, cls).__new__(cls)
  779. def __init__(self, *system, **kwargs):
  780. """Initialize the zeros, poles, gain system."""
  781. # Conversion of lti instances is handled in __new__
  782. if isinstance(system[0], LinearTimeInvariant):
  783. return
  784. super(ZerosPolesGain, self).__init__(**kwargs)
  785. self._zeros = None
  786. self._poles = None
  787. self._gain = None
  788. self.zeros, self.poles, self.gain = system
  789. def __repr__(self):
  790. """Return representation of the `ZerosPolesGain` system."""
  791. return '{0}(\n{1},\n{2},\n{3},\ndt: {4}\n)'.format(
  792. self.__class__.__name__,
  793. repr(self.zeros),
  794. repr(self.poles),
  795. repr(self.gain),
  796. repr(self.dt),
  797. )
  798. @property
  799. def zeros(self):
  800. """Zeros of the `ZerosPolesGain` system."""
  801. return self._zeros
  802. @zeros.setter
  803. def zeros(self, zeros):
  804. self._zeros = atleast_1d(zeros)
  805. # Update dimensions
  806. if len(self.zeros.shape) > 1:
  807. self.outputs, self.inputs = self.zeros.shape
  808. else:
  809. self.outputs = 1
  810. self.inputs = 1
  811. @property
  812. def poles(self):
  813. """Poles of the `ZerosPolesGain` system."""
  814. return self._poles
  815. @poles.setter
  816. def poles(self, poles):
  817. self._poles = atleast_1d(poles)
  818. @property
  819. def gain(self):
  820. """Gain of the `ZerosPolesGain` system."""
  821. return self._gain
  822. @gain.setter
  823. def gain(self, gain):
  824. self._gain = gain
  825. def _copy(self, system):
  826. """
  827. Copy the parameters of another `ZerosPolesGain` system.
  828. Parameters
  829. ----------
  830. system : instance of `ZerosPolesGain`
  831. The zeros, poles gain system that is to be copied
  832. """
  833. self.poles = system.poles
  834. self.zeros = system.zeros
  835. self.gain = system.gain
  836. def to_tf(self):
  837. """
  838. Convert system representation to `TransferFunction`.
  839. Returns
  840. -------
  841. sys : instance of `TransferFunction`
  842. Transfer function of the current system
  843. """
  844. return TransferFunction(*zpk2tf(self.zeros, self.poles, self.gain),
  845. **self._dt_dict)
  846. def to_zpk(self):
  847. """
  848. Return a copy of the current 'ZerosPolesGain' system.
  849. Returns
  850. -------
  851. sys : instance of `ZerosPolesGain`
  852. The current system (copy)
  853. """
  854. return copy.deepcopy(self)
  855. def to_ss(self):
  856. """
  857. Convert system representation to `StateSpace`.
  858. Returns
  859. -------
  860. sys : instance of `StateSpace`
  861. State space model of the current system
  862. """
  863. return StateSpace(*zpk2ss(self.zeros, self.poles, self.gain),
  864. **self._dt_dict)
  865. class ZerosPolesGainContinuous(ZerosPolesGain, lti):
  866. r"""
  867. Continuous-time Linear Time Invariant system in zeros, poles, gain form.
  868. Represents the system as the continuous time transfer function
  869. :math:`H(s)=k \prod_i (s - z[i]) / \prod_j (s - p[j])`, where :math:`k` is
  870. the `gain`, :math:`z` are the `zeros` and :math:`p` are the `poles`.
  871. Continuous-time `ZerosPolesGain` systems inherit additional functionality
  872. from the `lti` class.
  873. Parameters
  874. ----------
  875. *system : arguments
  876. The `ZerosPolesGain` class can be instantiated with 1 or 3
  877. arguments. The following gives the number of input arguments and their
  878. interpretation:
  879. * 1: `lti` system: (`StateSpace`, `TransferFunction` or
  880. `ZerosPolesGain`)
  881. * 3: array_like: (zeros, poles, gain)
  882. See Also
  883. --------
  884. TransferFunction, StateSpace, lti
  885. zpk2ss, zpk2tf, zpk2sos
  886. Notes
  887. -----
  888. Changing the value of properties that are not part of the
  889. `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D`
  890. state-space matrices) is very inefficient and may lead to numerical
  891. inaccuracies. It is better to convert to the specific system
  892. representation first. For example, call ``sys = sys.to_ss()`` before
  893. accessing/changing the A, B, C, D system matrices.
  894. Examples
  895. --------
  896. >>> from scipy import signal
  897. Transfer function: H(s) = 5(s - 1)(s - 2) / (s - 3)(s - 4)
  898. >>> signal.ZerosPolesGain([1, 2], [3, 4], 5)
  899. ZerosPolesGainContinuous(
  900. array([1, 2]),
  901. array([3, 4]),
  902. 5,
  903. dt: None
  904. )
  905. """
  906. def to_discrete(self, dt, method='zoh', alpha=None):
  907. """
  908. Returns the discretized `ZerosPolesGain` system.
  909. Parameters: See `cont2discrete` for details.
  910. Returns
  911. -------
  912. sys: instance of `dlti` and `ZerosPolesGain`
  913. """
  914. return ZerosPolesGain(
  915. *cont2discrete((self.zeros, self.poles, self.gain),
  916. dt,
  917. method=method,
  918. alpha=alpha)[:-1],
  919. dt=dt)
  920. class ZerosPolesGainDiscrete(ZerosPolesGain, dlti):
  921. r"""
  922. Discrete-time Linear Time Invariant system in zeros, poles, gain form.
  923. Represents the system as the discrete-time transfer function
  924. :math:`H(s)=k \prod_i (s - z[i]) / \prod_j (s - p[j])`, where :math:`k` is
  925. the `gain`, :math:`z` are the `zeros` and :math:`p` are the `poles`.
  926. Discrete-time `ZerosPolesGain` systems inherit additional functionality
  927. from the `dlti` class.
  928. Parameters
  929. ----------
  930. *system : arguments
  931. The `ZerosPolesGain` class can be instantiated with 1 or 3
  932. arguments. The following gives the number of input arguments and their
  933. interpretation:
  934. * 1: `dlti` system: (`StateSpace`, `TransferFunction` or
  935. `ZerosPolesGain`)
  936. * 3: array_like: (zeros, poles, gain)
  937. dt: float, optional
  938. Sampling time [s] of the discrete-time systems. Defaults to `True`
  939. (unspecified sampling time). Must be specified as a keyword argument,
  940. for example, ``dt=0.1``.
  941. See Also
  942. --------
  943. TransferFunction, StateSpace, dlti
  944. zpk2ss, zpk2tf, zpk2sos
  945. Notes
  946. -----
  947. Changing the value of properties that are not part of the
  948. `ZerosPolesGain` system representation (such as the `A`, `B`, `C`, `D`
  949. state-space matrices) is very inefficient and may lead to numerical
  950. inaccuracies. It is better to convert to the specific system
  951. representation first. For example, call ``sys = sys.to_ss()`` before
  952. accessing/changing the A, B, C, D system matrices.
  953. Examples
  954. --------
  955. >>> from scipy import signal
  956. Transfer function: H(s) = 5(s - 1)(s - 2) / (s - 3)(s - 4)
  957. >>> signal.ZerosPolesGain([1, 2], [3, 4], 5)
  958. ZerosPolesGainContinuous(
  959. array([1, 2]),
  960. array([3, 4]),
  961. 5,
  962. dt: None
  963. )
  964. Transfer function: H(z) = 5(z - 1)(z - 2) / (z - 3)(z - 4)
  965. >>> signal.ZerosPolesGain([1, 2], [3, 4], 5, dt=0.1)
  966. ZerosPolesGainDiscrete(
  967. array([1, 2]),
  968. array([3, 4]),
  969. 5,
  970. dt: 0.1
  971. )
  972. """
  973. pass
  974. def _atleast_2d_or_none(arg):
  975. if arg is not None:
  976. return atleast_2d(arg)
  977. class StateSpace(LinearTimeInvariant):
  978. r"""
  979. Linear Time Invariant system in state-space form.
  980. Represents the system as the continuous-time, first order differential
  981. equation :math:`\dot{x} = A x + B u` or the discrete-time difference
  982. equation :math:`x[k+1] = A x[k] + B u[k]`. `StateSpace` systems
  983. inherit additional functionality from the `lti`, respectively the `dlti`
  984. classes, depending on which system representation is used.
  985. Parameters
  986. ----------
  987. *system: arguments
  988. The `StateSpace` class can be instantiated with 1 or 3 arguments.
  989. The following gives the number of input arguments and their
  990. interpretation:
  991. * 1: `lti` or `dlti` system: (`StateSpace`, `TransferFunction` or
  992. `ZerosPolesGain`)
  993. * 4: array_like: (A, B, C, D)
  994. dt: float, optional
  995. Sampling time [s] of the discrete-time systems. Defaults to `None`
  996. (continuous-time). Must be specified as a keyword argument, for
  997. example, ``dt=0.1``.
  998. See Also
  999. --------
  1000. TransferFunction, ZerosPolesGain, lti, dlti
  1001. ss2zpk, ss2tf, zpk2sos
  1002. Notes
  1003. -----
  1004. Changing the value of properties that are not part of the
  1005. `StateSpace` system representation (such as `zeros` or `poles`) is very
  1006. inefficient and may lead to numerical inaccuracies. It is better to
  1007. convert to the specific system representation first. For example, call
  1008. ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain.
  1009. Examples
  1010. --------
  1011. >>> from scipy import signal
  1012. >>> a = np.array([[0, 1], [0, 0]])
  1013. >>> b = np.array([[0], [1]])
  1014. >>> c = np.array([[1, 0]])
  1015. >>> d = np.array([[0]])
  1016. >>> sys = signal.StateSpace(a, b, c, d)
  1017. >>> print(sys)
  1018. StateSpaceContinuous(
  1019. array([[0, 1],
  1020. [0, 0]]),
  1021. array([[0],
  1022. [1]]),
  1023. array([[1, 0]]),
  1024. array([[0]]),
  1025. dt: None
  1026. )
  1027. >>> sys.to_discrete(0.1)
  1028. StateSpaceDiscrete(
  1029. array([[1. , 0.1],
  1030. [0. , 1. ]]),
  1031. array([[0.005],
  1032. [0.1 ]]),
  1033. array([[1, 0]]),
  1034. array([[0]]),
  1035. dt: 0.1
  1036. )
  1037. >>> a = np.array([[1, 0.1], [0, 1]])
  1038. >>> b = np.array([[0.005], [0.1]])
  1039. >>> signal.StateSpace(a, b, c, d, dt=0.1)
  1040. StateSpaceDiscrete(
  1041. array([[1. , 0.1],
  1042. [0. , 1. ]]),
  1043. array([[0.005],
  1044. [0.1 ]]),
  1045. array([[1, 0]]),
  1046. array([[0]]),
  1047. dt: 0.1
  1048. )
  1049. """
  1050. # Override Numpy binary operations and ufuncs
  1051. __array_priority__ = 100.0
  1052. __array_ufunc__ = None
  1053. def __new__(cls, *system, **kwargs):
  1054. """Create new StateSpace object and settle inheritance."""
  1055. # Handle object conversion if input is an instance of `lti`
  1056. if len(system) == 1 and isinstance(system[0], LinearTimeInvariant):
  1057. return system[0].to_ss()
  1058. # Choose whether to inherit from `lti` or from `dlti`
  1059. if cls is StateSpace:
  1060. if kwargs.get('dt') is None:
  1061. return StateSpaceContinuous.__new__(StateSpaceContinuous,
  1062. *system, **kwargs)
  1063. else:
  1064. return StateSpaceDiscrete.__new__(StateSpaceDiscrete,
  1065. *system, **kwargs)
  1066. # No special conversion needed
  1067. return super(StateSpace, cls).__new__(cls)
  1068. def __init__(self, *system, **kwargs):
  1069. """Initialize the state space lti/dlti system."""
  1070. # Conversion of lti instances is handled in __new__
  1071. if isinstance(system[0], LinearTimeInvariant):
  1072. return
  1073. # Remove system arguments, not needed by parents anymore
  1074. super(StateSpace, self).__init__(**kwargs)
  1075. self._A = None
  1076. self._B = None
  1077. self._C = None
  1078. self._D = None
  1079. self.A, self.B, self.C, self.D = abcd_normalize(*system)
  1080. def __repr__(self):
  1081. """Return representation of the `StateSpace` system."""
  1082. return '{0}(\n{1},\n{2},\n{3},\n{4},\ndt: {5}\n)'.format(
  1083. self.__class__.__name__,
  1084. repr(self.A),
  1085. repr(self.B),
  1086. repr(self.C),
  1087. repr(self.D),
  1088. repr(self.dt),
  1089. )
  1090. def _check_binop_other(self, other):
  1091. return isinstance(other, (StateSpace, np.ndarray, float, complex,
  1092. np.number) + six.integer_types)
  1093. def __mul__(self, other):
  1094. """
  1095. Post-multiply another system or a scalar
  1096. Handles multiplication of systems in the sense of a frequency domain
  1097. multiplication. That means, given two systems E1(s) and E2(s), their
  1098. multiplication, H(s) = E1(s) * E2(s), means that applying H(s) to U(s)
  1099. is equivalent to first applying E2(s), and then E1(s).
  1100. Notes
  1101. -----
  1102. For SISO systems the order of system application does not matter.
  1103. However, for MIMO systems, where the two systems are matrices, the
  1104. order above ensures standard Matrix multiplication rules apply.
  1105. """
  1106. if not self._check_binop_other(other):
  1107. return NotImplemented
  1108. if isinstance(other, StateSpace):
  1109. # Disallow mix of discrete and continuous systems.
  1110. if type(other) is not type(self):
  1111. return NotImplemented
  1112. if self.dt != other.dt:
  1113. raise TypeError('Cannot multiply systems with different `dt`.')
  1114. n1 = self.A.shape[0]
  1115. n2 = other.A.shape[0]
  1116. # Interconnection of systems
  1117. # x1' = A1 x1 + B1 u1
  1118. # y1 = C1 x1 + D1 u1
  1119. # x2' = A2 x2 + B2 y1
  1120. # y2 = C2 x2 + D2 y1
  1121. #
  1122. # Plugging in with u1 = y2 yields
  1123. # [x1'] [A1 B1*C2 ] [x1] [B1*D2]
  1124. # [x2'] = [0 A2 ] [x2] + [B2 ] u2
  1125. # [x1]
  1126. # y2 = [C1 D1*C2] [x2] + D1*D2 u2
  1127. a = np.vstack((np.hstack((self.A, np.dot(self.B, other.C))),
  1128. np.hstack((zeros((n2, n1)), other.A))))
  1129. b = np.vstack((np.dot(self.B, other.D), other.B))
  1130. c = np.hstack((self.C, np.dot(self.D, other.C)))
  1131. d = np.dot(self.D, other.D)
  1132. else:
  1133. # Assume that other is a scalar / matrix
  1134. # For post multiplication the input gets scaled
  1135. a = self.A
  1136. b = np.dot(self.B, other)
  1137. c = self.C
  1138. d = np.dot(self.D, other)
  1139. common_dtype = np.find_common_type((a.dtype, b.dtype, c.dtype, d.dtype), ())
  1140. return StateSpace(np.asarray(a, dtype=common_dtype),
  1141. np.asarray(b, dtype=common_dtype),
  1142. np.asarray(c, dtype=common_dtype),
  1143. np.asarray(d, dtype=common_dtype))
  1144. def __rmul__(self, other):
  1145. """Pre-multiply a scalar or matrix (but not StateSpace)"""
  1146. if not self._check_binop_other(other) or isinstance(other, StateSpace):
  1147. return NotImplemented
  1148. # For pre-multiplication only the output gets scaled
  1149. a = self.A
  1150. b = self.B
  1151. c = np.dot(other, self.C)
  1152. d = np.dot(other, self.D)
  1153. common_dtype = np.find_common_type((a.dtype, b.dtype, c.dtype, d.dtype), ())
  1154. return StateSpace(np.asarray(a, dtype=common_dtype),
  1155. np.asarray(b, dtype=common_dtype),
  1156. np.asarray(c, dtype=common_dtype),
  1157. np.asarray(d, dtype=common_dtype))
  1158. def __neg__(self):
  1159. """Negate the system (equivalent to pre-multiplying by -1)."""
  1160. return StateSpace(self.A, self.B, -self.C, -self.D)
  1161. def __add__(self, other):
  1162. """
  1163. Adds two systems in the sense of frequency domain addition.
  1164. """
  1165. if not self._check_binop_other(other):
  1166. return NotImplemented
  1167. if isinstance(other, StateSpace):
  1168. # Disallow mix of discrete and continuous systems.
  1169. if type(other) is not type(self):
  1170. raise TypeError('Cannot add {} and {}'.format(type(self),
  1171. type(other)))
  1172. if self.dt != other.dt:
  1173. raise TypeError('Cannot add systems with different `dt`.')
  1174. # Interconnection of systems
  1175. # x1' = A1 x1 + B1 u
  1176. # y1 = C1 x1 + D1 u
  1177. # x2' = A2 x2 + B2 u
  1178. # y2 = C2 x2 + D2 u
  1179. # y = y1 + y2
  1180. #
  1181. # Plugging in yields
  1182. # [x1'] [A1 0 ] [x1] [B1]
  1183. # [x2'] = [0 A2] [x2] + [B2] u
  1184. # [x1]
  1185. # y = [C1 C2] [x2] + [D1 + D2] u
  1186. a = linalg.block_diag(self.A, other.A)
  1187. b = np.vstack((self.B, other.B))
  1188. c = np.hstack((self.C, other.C))
  1189. d = self.D + other.D
  1190. else:
  1191. other = np.atleast_2d(other)
  1192. if self.D.shape == other.shape:
  1193. # A scalar/matrix is really just a static system (A=0, B=0, C=0)
  1194. a = self.A
  1195. b = self.B
  1196. c = self.C
  1197. d = self.D + other
  1198. else:
  1199. raise ValueError("Cannot add systems with incompatible dimensions")
  1200. common_dtype = np.find_common_type((a.dtype, b.dtype, c.dtype, d.dtype), ())
  1201. return StateSpace(np.asarray(a, dtype=common_dtype),
  1202. np.asarray(b, dtype=common_dtype),
  1203. np.asarray(c, dtype=common_dtype),
  1204. np.asarray(d, dtype=common_dtype))
  1205. def __sub__(self, other):
  1206. if not self._check_binop_other(other):
  1207. return NotImplemented
  1208. return self.__add__(-other)
  1209. def __radd__(self, other):
  1210. if not self._check_binop_other(other):
  1211. return NotImplemented
  1212. return self.__add__(other)
  1213. def __rsub__(self, other):
  1214. if not self._check_binop_other(other):
  1215. return NotImplemented
  1216. return (-self).__add__(other)
  1217. def __truediv__(self, other):
  1218. """
  1219. Divide by a scalar
  1220. """
  1221. # Division by non-StateSpace scalars
  1222. if not self._check_binop_other(other) or isinstance(other, StateSpace):
  1223. return NotImplemented
  1224. if isinstance(other, np.ndarray) and other.ndim > 0:
  1225. # It's ambiguous what this means, so disallow it
  1226. raise ValueError("Cannot divide StateSpace by non-scalar numpy arrays")
  1227. return self.__mul__(1/other)
  1228. @property
  1229. def A(self):
  1230. """State matrix of the `StateSpace` system."""
  1231. return self._A
  1232. @A.setter
  1233. def A(self, A):
  1234. self._A = _atleast_2d_or_none(A)
  1235. @property
  1236. def B(self):
  1237. """Input matrix of the `StateSpace` system."""
  1238. return self._B
  1239. @B.setter
  1240. def B(self, B):
  1241. self._B = _atleast_2d_or_none(B)
  1242. self.inputs = self.B.shape[-1]
  1243. @property
  1244. def C(self):
  1245. """Output matrix of the `StateSpace` system."""
  1246. return self._C
  1247. @C.setter
  1248. def C(self, C):
  1249. self._C = _atleast_2d_or_none(C)
  1250. self.outputs = self.C.shape[0]
  1251. @property
  1252. def D(self):
  1253. """Feedthrough matrix of the `StateSpace` system."""
  1254. return self._D
  1255. @D.setter
  1256. def D(self, D):
  1257. self._D = _atleast_2d_or_none(D)
  1258. def _copy(self, system):
  1259. """
  1260. Copy the parameters of another `StateSpace` system.
  1261. Parameters
  1262. ----------
  1263. system : instance of `StateSpace`
  1264. The state-space system that is to be copied
  1265. """
  1266. self.A = system.A
  1267. self.B = system.B
  1268. self.C = system.C
  1269. self.D = system.D
  1270. def to_tf(self, **kwargs):
  1271. """
  1272. Convert system representation to `TransferFunction`.
  1273. Parameters
  1274. ----------
  1275. kwargs : dict, optional
  1276. Additional keywords passed to `ss2zpk`
  1277. Returns
  1278. -------
  1279. sys : instance of `TransferFunction`
  1280. Transfer function of the current system
  1281. """
  1282. return TransferFunction(*ss2tf(self._A, self._B, self._C, self._D,
  1283. **kwargs), **self._dt_dict)
  1284. def to_zpk(self, **kwargs):
  1285. """
  1286. Convert system representation to `ZerosPolesGain`.
  1287. Parameters
  1288. ----------
  1289. kwargs : dict, optional
  1290. Additional keywords passed to `ss2zpk`
  1291. Returns
  1292. -------
  1293. sys : instance of `ZerosPolesGain`
  1294. Zeros, poles, gain representation of the current system
  1295. """
  1296. return ZerosPolesGain(*ss2zpk(self._A, self._B, self._C, self._D,
  1297. **kwargs), **self._dt_dict)
  1298. def to_ss(self):
  1299. """
  1300. Return a copy of the current `StateSpace` system.
  1301. Returns
  1302. -------
  1303. sys : instance of `StateSpace`
  1304. The current system (copy)
  1305. """
  1306. return copy.deepcopy(self)
  1307. class StateSpaceContinuous(StateSpace, lti):
  1308. r"""
  1309. Continuous-time Linear Time Invariant system in state-space form.
  1310. Represents the system as the continuous-time, first order differential
  1311. equation :math:`\dot{x} = A x + B u`.
  1312. Continuous-time `StateSpace` systems inherit additional functionality
  1313. from the `lti` class.
  1314. Parameters
  1315. ----------
  1316. *system: arguments
  1317. The `StateSpace` class can be instantiated with 1 or 3 arguments.
  1318. The following gives the number of input arguments and their
  1319. interpretation:
  1320. * 1: `lti` system: (`StateSpace`, `TransferFunction` or
  1321. `ZerosPolesGain`)
  1322. * 4: array_like: (A, B, C, D)
  1323. See Also
  1324. --------
  1325. TransferFunction, ZerosPolesGain, lti
  1326. ss2zpk, ss2tf, zpk2sos
  1327. Notes
  1328. -----
  1329. Changing the value of properties that are not part of the
  1330. `StateSpace` system representation (such as `zeros` or `poles`) is very
  1331. inefficient and may lead to numerical inaccuracies. It is better to
  1332. convert to the specific system representation first. For example, call
  1333. ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain.
  1334. Examples
  1335. --------
  1336. >>> from scipy import signal
  1337. >>> a = np.array([[0, 1], [0, 0]])
  1338. >>> b = np.array([[0], [1]])
  1339. >>> c = np.array([[1, 0]])
  1340. >>> d = np.array([[0]])
  1341. >>> sys = signal.StateSpace(a, b, c, d)
  1342. >>> print(sys)
  1343. StateSpaceContinuous(
  1344. array([[0, 1],
  1345. [0, 0]]),
  1346. array([[0],
  1347. [1]]),
  1348. array([[1, 0]]),
  1349. array([[0]]),
  1350. dt: None
  1351. )
  1352. """
  1353. def to_discrete(self, dt, method='zoh', alpha=None):
  1354. """
  1355. Returns the discretized `StateSpace` system.
  1356. Parameters: See `cont2discrete` for details.
  1357. Returns
  1358. -------
  1359. sys: instance of `dlti` and `StateSpace`
  1360. """
  1361. return StateSpace(*cont2discrete((self.A, self.B, self.C, self.D),
  1362. dt,
  1363. method=method,
  1364. alpha=alpha)[:-1],
  1365. dt=dt)
  1366. class StateSpaceDiscrete(StateSpace, dlti):
  1367. r"""
  1368. Discrete-time Linear Time Invariant system in state-space form.
  1369. Represents the system as the discrete-time difference equation
  1370. :math:`x[k+1] = A x[k] + B u[k]`.
  1371. `StateSpace` systems inherit additional functionality from the `dlti`
  1372. class.
  1373. Parameters
  1374. ----------
  1375. *system: arguments
  1376. The `StateSpace` class can be instantiated with 1 or 3 arguments.
  1377. The following gives the number of input arguments and their
  1378. interpretation:
  1379. * 1: `dlti` system: (`StateSpace`, `TransferFunction` or
  1380. `ZerosPolesGain`)
  1381. * 4: array_like: (A, B, C, D)
  1382. dt: float, optional
  1383. Sampling time [s] of the discrete-time systems. Defaults to `True`
  1384. (unspecified sampling time). Must be specified as a keyword argument,
  1385. for example, ``dt=0.1``.
  1386. See Also
  1387. --------
  1388. TransferFunction, ZerosPolesGain, dlti
  1389. ss2zpk, ss2tf, zpk2sos
  1390. Notes
  1391. -----
  1392. Changing the value of properties that are not part of the
  1393. `StateSpace` system representation (such as `zeros` or `poles`) is very
  1394. inefficient and may lead to numerical inaccuracies. It is better to
  1395. convert to the specific system representation first. For example, call
  1396. ``sys = sys.to_zpk()`` before accessing/changing the zeros, poles or gain.
  1397. Examples
  1398. --------
  1399. >>> from scipy import signal
  1400. >>> a = np.array([[1, 0.1], [0, 1]])
  1401. >>> b = np.array([[0.005], [0.1]])
  1402. >>> c = np.array([[1, 0]])
  1403. >>> d = np.array([[0]])
  1404. >>> signal.StateSpace(a, b, c, d, dt=0.1)
  1405. StateSpaceDiscrete(
  1406. array([[ 1. , 0.1],
  1407. [ 0. , 1. ]]),
  1408. array([[ 0.005],
  1409. [ 0.1 ]]),
  1410. array([[1, 0]]),
  1411. array([[0]]),
  1412. dt: 0.1
  1413. )
  1414. """
  1415. pass
  1416. def lsim2(system, U=None, T=None, X0=None, **kwargs):
  1417. """
  1418. Simulate output of a continuous-time linear system, by using
  1419. the ODE solver `scipy.integrate.odeint`.
  1420. Parameters
  1421. ----------
  1422. system : an instance of the `lti` class or a tuple describing the system.
  1423. The following gives the number of elements in the tuple and
  1424. the interpretation:
  1425. * 1: (instance of `lti`)
  1426. * 2: (num, den)
  1427. * 3: (zeros, poles, gain)
  1428. * 4: (A, B, C, D)
  1429. U : array_like (1D or 2D), optional
  1430. An input array describing the input at each time T. Linear
  1431. interpolation is used between given times. If there are
  1432. multiple inputs, then each column of the rank-2 array
  1433. represents an input. If U is not given, the input is assumed
  1434. to be zero.
  1435. T : array_like (1D or 2D), optional
  1436. The time steps at which the input is defined and at which the
  1437. output is desired. The default is 101 evenly spaced points on
  1438. the interval [0,10.0].
  1439. X0 : array_like (1D), optional
  1440. The initial condition of the state vector. If `X0` is not
  1441. given, the initial conditions are assumed to be 0.
  1442. kwargs : dict
  1443. Additional keyword arguments are passed on to the function
  1444. `odeint`. See the notes below for more details.
  1445. Returns
  1446. -------
  1447. T : 1D ndarray
  1448. The time values for the output.
  1449. yout : ndarray
  1450. The response of the system.
  1451. xout : ndarray
  1452. The time-evolution of the state-vector.
  1453. Notes
  1454. -----
  1455. This function uses `scipy.integrate.odeint` to solve the
  1456. system's differential equations. Additional keyword arguments
  1457. given to `lsim2` are passed on to `odeint`. See the documentation
  1458. for `scipy.integrate.odeint` for the full list of arguments.
  1459. If (num, den) is passed in for ``system``, coefficients for both the
  1460. numerator and denominator should be specified in descending exponent
  1461. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1462. """
  1463. if isinstance(system, lti):
  1464. sys = system._as_ss()
  1465. elif isinstance(system, dlti):
  1466. raise AttributeError('lsim2 can only be used with continuous-time '
  1467. 'systems.')
  1468. else:
  1469. sys = lti(*system)._as_ss()
  1470. if X0 is None:
  1471. X0 = zeros(sys.B.shape[0], sys.A.dtype)
  1472. if T is None:
  1473. # XXX T should really be a required argument, but U was
  1474. # changed from a required positional argument to a keyword,
  1475. # and T is after U in the argument list. So we either: change
  1476. # the API and move T in front of U; check here for T being
  1477. # None and raise an exception; or assign a default value to T
  1478. # here. This code implements the latter.
  1479. T = linspace(0, 10.0, 101)
  1480. T = atleast_1d(T)
  1481. if len(T.shape) != 1:
  1482. raise ValueError("T must be a rank-1 array.")
  1483. if U is not None:
  1484. U = atleast_1d(U)
  1485. if len(U.shape) == 1:
  1486. U = U.reshape(-1, 1)
  1487. sU = U.shape
  1488. if sU[0] != len(T):
  1489. raise ValueError("U must have the same number of rows "
  1490. "as elements in T.")
  1491. if sU[1] != sys.inputs:
  1492. raise ValueError("The number of inputs in U (%d) is not "
  1493. "compatible with the number of system "
  1494. "inputs (%d)" % (sU[1], sys.inputs))
  1495. # Create a callable that uses linear interpolation to
  1496. # calculate the input at any time.
  1497. ufunc = interpolate.interp1d(T, U, kind='linear',
  1498. axis=0, bounds_error=False)
  1499. def fprime(x, t, sys, ufunc):
  1500. """The vector field of the linear system."""
  1501. return dot(sys.A, x) + squeeze(dot(sys.B, nan_to_num(ufunc([t]))))
  1502. xout = integrate.odeint(fprime, X0, T, args=(sys, ufunc), **kwargs)
  1503. yout = dot(sys.C, transpose(xout)) + dot(sys.D, transpose(U))
  1504. else:
  1505. def fprime(x, t, sys):
  1506. """The vector field of the linear system."""
  1507. return dot(sys.A, x)
  1508. xout = integrate.odeint(fprime, X0, T, args=(sys,), **kwargs)
  1509. yout = dot(sys.C, transpose(xout))
  1510. return T, squeeze(transpose(yout)), xout
  1511. def _cast_to_array_dtype(in1, in2):
  1512. """Cast array to dtype of other array, while avoiding ComplexWarning.
  1513. Those can be raised when casting complex to real.
  1514. """
  1515. if numpy.issubdtype(in2.dtype, numpy.float):
  1516. # dtype to cast to is not complex, so use .real
  1517. in1 = in1.real.astype(in2.dtype)
  1518. else:
  1519. in1 = in1.astype(in2.dtype)
  1520. return in1
  1521. def lsim(system, U, T, X0=None, interp=True):
  1522. """
  1523. Simulate output of a continuous-time linear system.
  1524. Parameters
  1525. ----------
  1526. system : an instance of the LTI class or a tuple describing the system.
  1527. The following gives the number of elements in the tuple and
  1528. the interpretation:
  1529. * 1: (instance of `lti`)
  1530. * 2: (num, den)
  1531. * 3: (zeros, poles, gain)
  1532. * 4: (A, B, C, D)
  1533. U : array_like
  1534. An input array describing the input at each time `T`
  1535. (interpolation is assumed between given times). If there are
  1536. multiple inputs, then each column of the rank-2 array
  1537. represents an input. If U = 0 or None, a zero input is used.
  1538. T : array_like
  1539. The time steps at which the input is defined and at which the
  1540. output is desired. Must be nonnegative, increasing, and equally spaced.
  1541. X0 : array_like, optional
  1542. The initial conditions on the state vector (zero by default).
  1543. interp : bool, optional
  1544. Whether to use linear (True, the default) or zero-order-hold (False)
  1545. interpolation for the input array.
  1546. Returns
  1547. -------
  1548. T : 1D ndarray
  1549. Time values for the output.
  1550. yout : 1D ndarray
  1551. System response.
  1552. xout : ndarray
  1553. Time evolution of the state vector.
  1554. Notes
  1555. -----
  1556. If (num, den) is passed in for ``system``, coefficients for both the
  1557. numerator and denominator should be specified in descending exponent
  1558. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1559. Examples
  1560. --------
  1561. Simulate a double integrator y'' = u, with a constant input u = 1
  1562. >>> from scipy import signal
  1563. >>> system = signal.lti([[0., 1.], [0., 0.]], [[0.], [1.]], [[1., 0.]], 0.)
  1564. >>> t = np.linspace(0, 5)
  1565. >>> u = np.ones_like(t)
  1566. >>> tout, y, x = signal.lsim(system, u, t)
  1567. >>> import matplotlib.pyplot as plt
  1568. >>> plt.plot(t, y)
  1569. """
  1570. if isinstance(system, lti):
  1571. sys = system._as_ss()
  1572. elif isinstance(system, dlti):
  1573. raise AttributeError('lsim can only be used with continuous-time '
  1574. 'systems.')
  1575. else:
  1576. sys = lti(*system)._as_ss()
  1577. T = atleast_1d(T)
  1578. if len(T.shape) != 1:
  1579. raise ValueError("T must be a rank-1 array.")
  1580. A, B, C, D = map(np.asarray, (sys.A, sys.B, sys.C, sys.D))
  1581. n_states = A.shape[0]
  1582. n_inputs = B.shape[1]
  1583. n_steps = T.size
  1584. if X0 is None:
  1585. X0 = zeros(n_states, sys.A.dtype)
  1586. xout = zeros((n_steps, n_states), sys.A.dtype)
  1587. if T[0] == 0:
  1588. xout[0] = X0
  1589. elif T[0] > 0:
  1590. # step forward to initial time, with zero input
  1591. xout[0] = dot(X0, linalg.expm(transpose(A) * T[0]))
  1592. else:
  1593. raise ValueError("Initial time must be nonnegative")
  1594. no_input = (U is None or
  1595. (isinstance(U, (int, float)) and U == 0.) or
  1596. not np.any(U))
  1597. if n_steps == 1:
  1598. yout = squeeze(dot(xout, transpose(C)))
  1599. if not no_input:
  1600. yout += squeeze(dot(U, transpose(D)))
  1601. return T, squeeze(yout), squeeze(xout)
  1602. dt = T[1] - T[0]
  1603. if not np.allclose((T[1:] - T[:-1]) / dt, 1.0):
  1604. warnings.warn("Non-uniform timesteps are deprecated. Results may be "
  1605. "slow and/or inaccurate.", DeprecationWarning)
  1606. return lsim2(system, U, T, X0)
  1607. if no_input:
  1608. # Zero input: just use matrix exponential
  1609. # take transpose because state is a row vector
  1610. expAT_dt = linalg.expm(transpose(A) * dt)
  1611. for i in xrange(1, n_steps):
  1612. xout[i] = dot(xout[i-1], expAT_dt)
  1613. yout = squeeze(dot(xout, transpose(C)))
  1614. return T, squeeze(yout), squeeze(xout)
  1615. # Nonzero input
  1616. U = atleast_1d(U)
  1617. if U.ndim == 1:
  1618. U = U[:, np.newaxis]
  1619. if U.shape[0] != n_steps:
  1620. raise ValueError("U must have the same number of rows "
  1621. "as elements in T.")
  1622. if U.shape[1] != n_inputs:
  1623. raise ValueError("System does not define that many inputs.")
  1624. if not interp:
  1625. # Zero-order hold
  1626. # Algorithm: to integrate from time 0 to time dt, we solve
  1627. # xdot = A x + B u, x(0) = x0
  1628. # udot = 0, u(0) = u0.
  1629. #
  1630. # Solution is
  1631. # [ x(dt) ] [ A*dt B*dt ] [ x0 ]
  1632. # [ u(dt) ] = exp [ 0 0 ] [ u0 ]
  1633. M = np.vstack([np.hstack([A * dt, B * dt]),
  1634. np.zeros((n_inputs, n_states + n_inputs))])
  1635. # transpose everything because the state and input are row vectors
  1636. expMT = linalg.expm(transpose(M))
  1637. Ad = expMT[:n_states, :n_states]
  1638. Bd = expMT[n_states:, :n_states]
  1639. for i in xrange(1, n_steps):
  1640. xout[i] = dot(xout[i-1], Ad) + dot(U[i-1], Bd)
  1641. else:
  1642. # Linear interpolation between steps
  1643. # Algorithm: to integrate from time 0 to time dt, with linear
  1644. # interpolation between inputs u(0) = u0 and u(dt) = u1, we solve
  1645. # xdot = A x + B u, x(0) = x0
  1646. # udot = (u1 - u0) / dt, u(0) = u0.
  1647. #
  1648. # Solution is
  1649. # [ x(dt) ] [ A*dt B*dt 0 ] [ x0 ]
  1650. # [ u(dt) ] = exp [ 0 0 I ] [ u0 ]
  1651. # [u1 - u0] [ 0 0 0 ] [u1 - u0]
  1652. M = np.vstack([np.hstack([A * dt, B * dt,
  1653. np.zeros((n_states, n_inputs))]),
  1654. np.hstack([np.zeros((n_inputs, n_states + n_inputs)),
  1655. np.identity(n_inputs)]),
  1656. np.zeros((n_inputs, n_states + 2 * n_inputs))])
  1657. expMT = linalg.expm(transpose(M))
  1658. Ad = expMT[:n_states, :n_states]
  1659. Bd1 = expMT[n_states+n_inputs:, :n_states]
  1660. Bd0 = expMT[n_states:n_states + n_inputs, :n_states] - Bd1
  1661. for i in xrange(1, n_steps):
  1662. xout[i] = (dot(xout[i-1], Ad) + dot(U[i-1], Bd0) + dot(U[i], Bd1))
  1663. yout = (squeeze(dot(xout, transpose(C))) + squeeze(dot(U, transpose(D))))
  1664. return T, squeeze(yout), squeeze(xout)
  1665. def _default_response_times(A, n):
  1666. """Compute a reasonable set of time samples for the response time.
  1667. This function is used by `impulse`, `impulse2`, `step` and `step2`
  1668. to compute the response time when the `T` argument to the function
  1669. is None.
  1670. Parameters
  1671. ----------
  1672. A : array_like
  1673. The system matrix, which is square.
  1674. n : int
  1675. The number of time samples to generate.
  1676. Returns
  1677. -------
  1678. t : ndarray
  1679. The 1-D array of length `n` of time samples at which the response
  1680. is to be computed.
  1681. """
  1682. # Create a reasonable time interval.
  1683. # TODO: This could use some more work.
  1684. # For example, what is expected when the system is unstable?
  1685. vals = linalg.eigvals(A)
  1686. r = min(abs(real(vals)))
  1687. if r == 0.0:
  1688. r = 1.0
  1689. tc = 1.0 / r
  1690. t = linspace(0.0, 7 * tc, n)
  1691. return t
  1692. def impulse(system, X0=None, T=None, N=None):
  1693. """Impulse response of continuous-time system.
  1694. Parameters
  1695. ----------
  1696. system : an instance of the LTI class or a tuple of array_like
  1697. describing the system.
  1698. The following gives the number of elements in the tuple and
  1699. the interpretation:
  1700. * 1 (instance of `lti`)
  1701. * 2 (num, den)
  1702. * 3 (zeros, poles, gain)
  1703. * 4 (A, B, C, D)
  1704. X0 : array_like, optional
  1705. Initial state-vector. Defaults to zero.
  1706. T : array_like, optional
  1707. Time points. Computed if not given.
  1708. N : int, optional
  1709. The number of time points to compute (if `T` is not given).
  1710. Returns
  1711. -------
  1712. T : ndarray
  1713. A 1-D array of time points.
  1714. yout : ndarray
  1715. A 1-D array containing the impulse response of the system (except for
  1716. singularities at zero).
  1717. Notes
  1718. -----
  1719. If (num, den) is passed in for ``system``, coefficients for both the
  1720. numerator and denominator should be specified in descending exponent
  1721. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1722. """
  1723. if isinstance(system, lti):
  1724. sys = system._as_ss()
  1725. elif isinstance(system, dlti):
  1726. raise AttributeError('impulse can only be used with continuous-time '
  1727. 'systems.')
  1728. else:
  1729. sys = lti(*system)._as_ss()
  1730. if X0 is None:
  1731. X = squeeze(sys.B)
  1732. else:
  1733. X = squeeze(sys.B + X0)
  1734. if N is None:
  1735. N = 100
  1736. if T is None:
  1737. T = _default_response_times(sys.A, N)
  1738. else:
  1739. T = asarray(T)
  1740. _, h, _ = lsim(sys, 0., T, X, interp=False)
  1741. return T, h
  1742. def impulse2(system, X0=None, T=None, N=None, **kwargs):
  1743. """
  1744. Impulse response of a single-input, continuous-time linear system.
  1745. Parameters
  1746. ----------
  1747. system : an instance of the LTI class or a tuple of array_like
  1748. describing the system.
  1749. The following gives the number of elements in the tuple and
  1750. the interpretation:
  1751. * 1 (instance of `lti`)
  1752. * 2 (num, den)
  1753. * 3 (zeros, poles, gain)
  1754. * 4 (A, B, C, D)
  1755. X0 : 1-D array_like, optional
  1756. The initial condition of the state vector. Default: 0 (the
  1757. zero vector).
  1758. T : 1-D array_like, optional
  1759. The time steps at which the input is defined and at which the
  1760. output is desired. If `T` is not given, the function will
  1761. generate a set of time samples automatically.
  1762. N : int, optional
  1763. Number of time points to compute. Default: 100.
  1764. kwargs : various types
  1765. Additional keyword arguments are passed on to the function
  1766. `scipy.signal.lsim2`, which in turn passes them on to
  1767. `scipy.integrate.odeint`; see the latter's documentation for
  1768. information about these arguments.
  1769. Returns
  1770. -------
  1771. T : ndarray
  1772. The time values for the output.
  1773. yout : ndarray
  1774. The output response of the system.
  1775. See Also
  1776. --------
  1777. impulse, lsim2, integrate.odeint
  1778. Notes
  1779. -----
  1780. The solution is generated by calling `scipy.signal.lsim2`, which uses
  1781. the differential equation solver `scipy.integrate.odeint`.
  1782. If (num, den) is passed in for ``system``, coefficients for both the
  1783. numerator and denominator should be specified in descending exponent
  1784. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1785. .. versionadded:: 0.8.0
  1786. Examples
  1787. --------
  1788. Second order system with a repeated root: x''(t) + 2*x(t) + x(t) = u(t)
  1789. >>> from scipy import signal
  1790. >>> system = ([1.0], [1.0, 2.0, 1.0])
  1791. >>> t, y = signal.impulse2(system)
  1792. >>> import matplotlib.pyplot as plt
  1793. >>> plt.plot(t, y)
  1794. """
  1795. if isinstance(system, lti):
  1796. sys = system._as_ss()
  1797. elif isinstance(system, dlti):
  1798. raise AttributeError('impulse2 can only be used with continuous-time '
  1799. 'systems.')
  1800. else:
  1801. sys = lti(*system)._as_ss()
  1802. B = sys.B
  1803. if B.shape[-1] != 1:
  1804. raise ValueError("impulse2() requires a single-input system.")
  1805. B = B.squeeze()
  1806. if X0 is None:
  1807. X0 = zeros_like(B)
  1808. if N is None:
  1809. N = 100
  1810. if T is None:
  1811. T = _default_response_times(sys.A, N)
  1812. # Move the impulse in the input to the initial conditions, and then
  1813. # solve using lsim2().
  1814. ic = B + X0
  1815. Tr, Yr, Xr = lsim2(sys, T=T, X0=ic, **kwargs)
  1816. return Tr, Yr
  1817. def step(system, X0=None, T=None, N=None):
  1818. """Step response of continuous-time system.
  1819. Parameters
  1820. ----------
  1821. system : an instance of the LTI class or a tuple of array_like
  1822. describing the system.
  1823. The following gives the number of elements in the tuple and
  1824. the interpretation:
  1825. * 1 (instance of `lti`)
  1826. * 2 (num, den)
  1827. * 3 (zeros, poles, gain)
  1828. * 4 (A, B, C, D)
  1829. X0 : array_like, optional
  1830. Initial state-vector (default is zero).
  1831. T : array_like, optional
  1832. Time points (computed if not given).
  1833. N : int, optional
  1834. Number of time points to compute if `T` is not given.
  1835. Returns
  1836. -------
  1837. T : 1D ndarray
  1838. Output time points.
  1839. yout : 1D ndarray
  1840. Step response of system.
  1841. See also
  1842. --------
  1843. scipy.signal.step2
  1844. Notes
  1845. -----
  1846. If (num, den) is passed in for ``system``, coefficients for both the
  1847. numerator and denominator should be specified in descending exponent
  1848. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1849. """
  1850. if isinstance(system, lti):
  1851. sys = system._as_ss()
  1852. elif isinstance(system, dlti):
  1853. raise AttributeError('step can only be used with continuous-time '
  1854. 'systems.')
  1855. else:
  1856. sys = lti(*system)._as_ss()
  1857. if N is None:
  1858. N = 100
  1859. if T is None:
  1860. T = _default_response_times(sys.A, N)
  1861. else:
  1862. T = asarray(T)
  1863. U = ones(T.shape, sys.A.dtype)
  1864. vals = lsim(sys, U, T, X0=X0, interp=False)
  1865. return vals[0], vals[1]
  1866. def step2(system, X0=None, T=None, N=None, **kwargs):
  1867. """Step response of continuous-time system.
  1868. This function is functionally the same as `scipy.signal.step`, but
  1869. it uses the function `scipy.signal.lsim2` to compute the step
  1870. response.
  1871. Parameters
  1872. ----------
  1873. system : an instance of the LTI class or a tuple of array_like
  1874. describing the system.
  1875. The following gives the number of elements in the tuple and
  1876. the interpretation:
  1877. * 1 (instance of `lti`)
  1878. * 2 (num, den)
  1879. * 3 (zeros, poles, gain)
  1880. * 4 (A, B, C, D)
  1881. X0 : array_like, optional
  1882. Initial state-vector (default is zero).
  1883. T : array_like, optional
  1884. Time points (computed if not given).
  1885. N : int, optional
  1886. Number of time points to compute if `T` is not given.
  1887. kwargs : various types
  1888. Additional keyword arguments are passed on the function
  1889. `scipy.signal.lsim2`, which in turn passes them on to
  1890. `scipy.integrate.odeint`. See the documentation for
  1891. `scipy.integrate.odeint` for information about these arguments.
  1892. Returns
  1893. -------
  1894. T : 1D ndarray
  1895. Output time points.
  1896. yout : 1D ndarray
  1897. Step response of system.
  1898. See also
  1899. --------
  1900. scipy.signal.step
  1901. Notes
  1902. -----
  1903. If (num, den) is passed in for ``system``, coefficients for both the
  1904. numerator and denominator should be specified in descending exponent
  1905. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1906. .. versionadded:: 0.8.0
  1907. """
  1908. if isinstance(system, lti):
  1909. sys = system._as_ss()
  1910. elif isinstance(system, dlti):
  1911. raise AttributeError('step2 can only be used with continuous-time '
  1912. 'systems.')
  1913. else:
  1914. sys = lti(*system)._as_ss()
  1915. if N is None:
  1916. N = 100
  1917. if T is None:
  1918. T = _default_response_times(sys.A, N)
  1919. else:
  1920. T = asarray(T)
  1921. U = ones(T.shape, sys.A.dtype)
  1922. vals = lsim2(sys, U, T, X0=X0, **kwargs)
  1923. return vals[0], vals[1]
  1924. def bode(system, w=None, n=100):
  1925. """
  1926. Calculate Bode magnitude and phase data of a continuous-time system.
  1927. Parameters
  1928. ----------
  1929. system : an instance of the LTI class or a tuple describing the system.
  1930. The following gives the number of elements in the tuple and
  1931. the interpretation:
  1932. * 1 (instance of `lti`)
  1933. * 2 (num, den)
  1934. * 3 (zeros, poles, gain)
  1935. * 4 (A, B, C, D)
  1936. w : array_like, optional
  1937. Array of frequencies (in rad/s). Magnitude and phase data is calculated
  1938. for every value in this array. If not given a reasonable set will be
  1939. calculated.
  1940. n : int, optional
  1941. Number of frequency points to compute if `w` is not given. The `n`
  1942. frequencies are logarithmically spaced in an interval chosen to
  1943. include the influence of the poles and zeros of the system.
  1944. Returns
  1945. -------
  1946. w : 1D ndarray
  1947. Frequency array [rad/s]
  1948. mag : 1D ndarray
  1949. Magnitude array [dB]
  1950. phase : 1D ndarray
  1951. Phase array [deg]
  1952. Notes
  1953. -----
  1954. If (num, den) is passed in for ``system``, coefficients for both the
  1955. numerator and denominator should be specified in descending exponent
  1956. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  1957. .. versionadded:: 0.11.0
  1958. Examples
  1959. --------
  1960. >>> from scipy import signal
  1961. >>> import matplotlib.pyplot as plt
  1962. >>> sys = signal.TransferFunction([1], [1, 1])
  1963. >>> w, mag, phase = signal.bode(sys)
  1964. >>> plt.figure()
  1965. >>> plt.semilogx(w, mag) # Bode magnitude plot
  1966. >>> plt.figure()
  1967. >>> plt.semilogx(w, phase) # Bode phase plot
  1968. >>> plt.show()
  1969. """
  1970. w, y = freqresp(system, w=w, n=n)
  1971. mag = 20.0 * numpy.log10(abs(y))
  1972. phase = numpy.unwrap(numpy.arctan2(y.imag, y.real)) * 180.0 / numpy.pi
  1973. return w, mag, phase
  1974. def freqresp(system, w=None, n=10000):
  1975. """Calculate the frequency response of a continuous-time system.
  1976. Parameters
  1977. ----------
  1978. system : an instance of the `lti` class or a tuple describing the system.
  1979. The following gives the number of elements in the tuple and
  1980. the interpretation:
  1981. * 1 (instance of `lti`)
  1982. * 2 (num, den)
  1983. * 3 (zeros, poles, gain)
  1984. * 4 (A, B, C, D)
  1985. w : array_like, optional
  1986. Array of frequencies (in rad/s). Magnitude and phase data is
  1987. calculated for every value in this array. If not given, a reasonable
  1988. set will be calculated.
  1989. n : int, optional
  1990. Number of frequency points to compute if `w` is not given. The `n`
  1991. frequencies are logarithmically spaced in an interval chosen to
  1992. include the influence of the poles and zeros of the system.
  1993. Returns
  1994. -------
  1995. w : 1D ndarray
  1996. Frequency array [rad/s]
  1997. H : 1D ndarray
  1998. Array of complex magnitude values
  1999. Notes
  2000. -----
  2001. If (num, den) is passed in for ``system``, coefficients for both the
  2002. numerator and denominator should be specified in descending exponent
  2003. order (e.g. ``s^2 + 3s + 5`` would be represented as ``[1, 3, 5]``).
  2004. Examples
  2005. --------
  2006. Generating the Nyquist plot of a transfer function
  2007. >>> from scipy import signal
  2008. >>> import matplotlib.pyplot as plt
  2009. Transfer function: H(s) = 5 / (s-1)^3
  2010. >>> s1 = signal.ZerosPolesGain([], [1, 1, 1], [5])
  2011. >>> w, H = signal.freqresp(s1)
  2012. >>> plt.figure()
  2013. >>> plt.plot(H.real, H.imag, "b")
  2014. >>> plt.plot(H.real, -H.imag, "r")
  2015. >>> plt.show()
  2016. """
  2017. if isinstance(system, lti):
  2018. if isinstance(system, (TransferFunction, ZerosPolesGain)):
  2019. sys = system
  2020. else:
  2021. sys = system._as_zpk()
  2022. elif isinstance(system, dlti):
  2023. raise AttributeError('freqresp can only be used with continuous-time '
  2024. 'systems.')
  2025. else:
  2026. sys = lti(*system)._as_zpk()
  2027. if sys.inputs != 1 or sys.outputs != 1:
  2028. raise ValueError("freqresp() requires a SISO (single input, single "
  2029. "output) system.")
  2030. if w is not None:
  2031. worN = w
  2032. else:
  2033. worN = n
  2034. if isinstance(sys, TransferFunction):
  2035. # In the call to freqs(), sys.num.ravel() is used because there are
  2036. # cases where sys.num is a 2-D array with a single row.
  2037. w, h = freqs(sys.num.ravel(), sys.den, worN=worN)
  2038. elif isinstance(sys, ZerosPolesGain):
  2039. w, h = freqs_zpk(sys.zeros, sys.poles, sys.gain, worN=worN)
  2040. return w, h
  2041. # This class will be used by place_poles to return its results
  2042. # see https://code.activestate.com/recipes/52308/
  2043. class Bunch:
  2044. def __init__(self, **kwds):
  2045. self.__dict__.update(kwds)
  2046. def _valid_inputs(A, B, poles, method, rtol, maxiter):
  2047. """
  2048. Check the poles come in complex conjugage pairs
  2049. Check shapes of A, B and poles are compatible.
  2050. Check the method chosen is compatible with provided poles
  2051. Return update method to use and ordered poles
  2052. """
  2053. poles = np.asarray(poles)
  2054. if poles.ndim > 1:
  2055. raise ValueError("Poles must be a 1D array like.")
  2056. # Will raise ValueError if poles do not come in complex conjugates pairs
  2057. poles = _order_complex_poles(poles)
  2058. if A.ndim > 2:
  2059. raise ValueError("A must be a 2D array/matrix.")
  2060. if B.ndim > 2:
  2061. raise ValueError("B must be a 2D array/matrix")
  2062. if A.shape[0] != A.shape[1]:
  2063. raise ValueError("A must be square")
  2064. if len(poles) > A.shape[0]:
  2065. raise ValueError("maximum number of poles is %d but you asked for %d" %
  2066. (A.shape[0], len(poles)))
  2067. if len(poles) < A.shape[0]:
  2068. raise ValueError("number of poles is %d but you should provide %d" %
  2069. (len(poles), A.shape[0]))
  2070. r = np.linalg.matrix_rank(B)
  2071. for p in poles:
  2072. if sum(p == poles) > r:
  2073. raise ValueError("at least one of the requested pole is repeated "
  2074. "more than rank(B) times")
  2075. # Choose update method
  2076. update_loop = _YT_loop
  2077. if method not in ('KNV0','YT'):
  2078. raise ValueError("The method keyword must be one of 'YT' or 'KNV0'")
  2079. if method == "KNV0":
  2080. update_loop = _KNV0_loop
  2081. if not all(np.isreal(poles)):
  2082. raise ValueError("Complex poles are not supported by KNV0")
  2083. if maxiter < 1:
  2084. raise ValueError("maxiter must be at least equal to 1")
  2085. # We do not check rtol <= 0 as the user can use a negative rtol to
  2086. # force maxiter iterations
  2087. if rtol > 1:
  2088. raise ValueError("rtol can not be greater than 1")
  2089. return update_loop, poles
  2090. def _order_complex_poles(poles):
  2091. """
  2092. Check we have complex conjugates pairs and reorder P according to YT, ie
  2093. real_poles, complex_i, conjugate complex_i, ....
  2094. The lexicographic sort on the complex poles is added to help the user to
  2095. compare sets of poles.
  2096. """
  2097. ordered_poles = np.sort(poles[np.isreal(poles)])
  2098. im_poles = []
  2099. for p in np.sort(poles[np.imag(poles) < 0]):
  2100. if np.conj(p) in poles:
  2101. im_poles.extend((p, np.conj(p)))
  2102. ordered_poles = np.hstack((ordered_poles, im_poles))
  2103. if poles.shape[0] != len(ordered_poles):
  2104. raise ValueError("Complex poles must come with their conjugates")
  2105. return ordered_poles
  2106. def _KNV0(B, ker_pole, transfer_matrix, j, poles):
  2107. """
  2108. Algorithm "KNV0" Kautsky et Al. Robust pole
  2109. assignment in linear state feedback, Int journal of Control
  2110. 1985, vol 41 p 1129->1155
  2111. https://la.epfl.ch/files/content/sites/la/files/
  2112. users/105941/public/KautskyNicholsDooren
  2113. """
  2114. # Remove xj form the base
  2115. transfer_matrix_not_j = np.delete(transfer_matrix, j, axis=1)
  2116. # If we QR this matrix in full mode Q=Q0|Q1
  2117. # then Q1 will be a single column orthogonnal to
  2118. # Q0, that's what we are looking for !
  2119. # After merge of gh-4249 great speed improvements could be achieved
  2120. # using QR updates instead of full QR in the line below
  2121. # To debug with numpy qr uncomment the line below
  2122. # Q, R = np.linalg.qr(transfer_matrix_not_j, mode="complete")
  2123. Q, R = s_qr(transfer_matrix_not_j, mode="full")
  2124. mat_ker_pj = np.dot(ker_pole[j], ker_pole[j].T)
  2125. yj = np.dot(mat_ker_pj, Q[:, -1])
  2126. # If Q[:, -1] is "almost" orthogonal to ker_pole[j] its
  2127. # projection into ker_pole[j] will yield a vector
  2128. # close to 0. As we are looking for a vector in ker_pole[j]
  2129. # simply stick with transfer_matrix[:, j] (unless someone provides me with
  2130. # a better choice ?)
  2131. if not np.allclose(yj, 0):
  2132. xj = yj/np.linalg.norm(yj)
  2133. transfer_matrix[:, j] = xj
  2134. # KNV does not support complex poles, using YT technique the two lines
  2135. # below seem to work 9 out of 10 times but it is not reliable enough:
  2136. # transfer_matrix[:, j]=real(xj)
  2137. # transfer_matrix[:, j+1]=imag(xj)
  2138. # Add this at the beginning of this function if you wish to test
  2139. # complex support:
  2140. # if ~np.isreal(P[j]) and (j>=B.shape[0]-1 or P[j]!=np.conj(P[j+1])):
  2141. # return
  2142. # Problems arise when imag(xj)=>0 I have no idea on how to fix this
  2143. def _YT_real(ker_pole, Q, transfer_matrix, i, j):
  2144. """
  2145. Applies algorithm from YT section 6.1 page 19 related to real pairs
  2146. """
  2147. # step 1 page 19
  2148. u = Q[:, -2, np.newaxis]
  2149. v = Q[:, -1, np.newaxis]
  2150. # step 2 page 19
  2151. m = np.dot(np.dot(ker_pole[i].T, np.dot(u, v.T) -
  2152. np.dot(v, u.T)), ker_pole[j])
  2153. # step 3 page 19
  2154. um, sm, vm = np.linalg.svd(m)
  2155. # mu1, mu2 two first columns of U => 2 first lines of U.T
  2156. mu1, mu2 = um.T[:2, :, np.newaxis]
  2157. # VM is V.T with numpy we want the first two lines of V.T
  2158. nu1, nu2 = vm[:2, :, np.newaxis]
  2159. # what follows is a rough python translation of the formulas
  2160. # in section 6.2 page 20 (step 4)
  2161. transfer_matrix_j_mo_transfer_matrix_j = np.vstack((
  2162. transfer_matrix[:, i, np.newaxis],
  2163. transfer_matrix[:, j, np.newaxis]))
  2164. if not np.allclose(sm[0], sm[1]):
  2165. ker_pole_imo_mu1 = np.dot(ker_pole[i], mu1)
  2166. ker_pole_i_nu1 = np.dot(ker_pole[j], nu1)
  2167. ker_pole_mu_nu = np.vstack((ker_pole_imo_mu1, ker_pole_i_nu1))
  2168. else:
  2169. ker_pole_ij = np.vstack((
  2170. np.hstack((ker_pole[i],
  2171. np.zeros(ker_pole[i].shape))),
  2172. np.hstack((np.zeros(ker_pole[j].shape),
  2173. ker_pole[j]))
  2174. ))
  2175. mu_nu_matrix = np.vstack(
  2176. (np.hstack((mu1, mu2)), np.hstack((nu1, nu2)))
  2177. )
  2178. ker_pole_mu_nu = np.dot(ker_pole_ij, mu_nu_matrix)
  2179. transfer_matrix_ij = np.dot(np.dot(ker_pole_mu_nu, ker_pole_mu_nu.T),
  2180. transfer_matrix_j_mo_transfer_matrix_j)
  2181. if not np.allclose(transfer_matrix_ij, 0):
  2182. transfer_matrix_ij = (np.sqrt(2)*transfer_matrix_ij /
  2183. np.linalg.norm(transfer_matrix_ij))
  2184. transfer_matrix[:, i] = transfer_matrix_ij[
  2185. :transfer_matrix[:, i].shape[0], 0
  2186. ]
  2187. transfer_matrix[:, j] = transfer_matrix_ij[
  2188. transfer_matrix[:, i].shape[0]:, 0
  2189. ]
  2190. else:
  2191. # As in knv0 if transfer_matrix_j_mo_transfer_matrix_j is orthogonal to
  2192. # Vect{ker_pole_mu_nu} assign transfer_matrixi/transfer_matrix_j to
  2193. # ker_pole_mu_nu and iterate. As we are looking for a vector in
  2194. # Vect{Matker_pole_MU_NU} (see section 6.1 page 19) this might help
  2195. # (that's a guess, not a claim !)
  2196. transfer_matrix[:, i] = ker_pole_mu_nu[
  2197. :transfer_matrix[:, i].shape[0], 0
  2198. ]
  2199. transfer_matrix[:, j] = ker_pole_mu_nu[
  2200. transfer_matrix[:, i].shape[0]:, 0
  2201. ]
  2202. def _YT_complex(ker_pole, Q, transfer_matrix, i, j):
  2203. """
  2204. Applies algorithm from YT section 6.2 page 20 related to complex pairs
  2205. """
  2206. # step 1 page 20
  2207. ur = np.sqrt(2)*Q[:, -2, np.newaxis]
  2208. ui = np.sqrt(2)*Q[:, -1, np.newaxis]
  2209. u = ur + 1j*ui
  2210. # step 2 page 20
  2211. ker_pole_ij = ker_pole[i]
  2212. m = np.dot(np.dot(np.conj(ker_pole_ij.T), np.dot(u, np.conj(u).T) -
  2213. np.dot(np.conj(u), u.T)), ker_pole_ij)
  2214. # step 3 page 20
  2215. e_val, e_vec = np.linalg.eig(m)
  2216. # sort eigenvalues according to their module
  2217. e_val_idx = np.argsort(np.abs(e_val))
  2218. mu1 = e_vec[:, e_val_idx[-1], np.newaxis]
  2219. mu2 = e_vec[:, e_val_idx[-2], np.newaxis]
  2220. # what follows is a rough python translation of the formulas
  2221. # in section 6.2 page 20 (step 4)
  2222. # remember transfer_matrix_i has been split as
  2223. # transfer_matrix[i]=real(transfer_matrix_i) and
  2224. # transfer_matrix[j]=imag(transfer_matrix_i)
  2225. transfer_matrix_j_mo_transfer_matrix_j = (
  2226. transfer_matrix[:, i, np.newaxis] +
  2227. 1j*transfer_matrix[:, j, np.newaxis]
  2228. )
  2229. if not np.allclose(np.abs(e_val[e_val_idx[-1]]),
  2230. np.abs(e_val[e_val_idx[-2]])):
  2231. ker_pole_mu = np.dot(ker_pole_ij, mu1)
  2232. else:
  2233. mu1_mu2_matrix = np.hstack((mu1, mu2))
  2234. ker_pole_mu = np.dot(ker_pole_ij, mu1_mu2_matrix)
  2235. transfer_matrix_i_j = np.dot(np.dot(ker_pole_mu, np.conj(ker_pole_mu.T)),
  2236. transfer_matrix_j_mo_transfer_matrix_j)
  2237. if not np.allclose(transfer_matrix_i_j, 0):
  2238. transfer_matrix_i_j = (transfer_matrix_i_j /
  2239. np.linalg.norm(transfer_matrix_i_j))
  2240. transfer_matrix[:, i] = np.real(transfer_matrix_i_j[:, 0])
  2241. transfer_matrix[:, j] = np.imag(transfer_matrix_i_j[:, 0])
  2242. else:
  2243. # same idea as in YT_real
  2244. transfer_matrix[:, i] = np.real(ker_pole_mu[:, 0])
  2245. transfer_matrix[:, j] = np.imag(ker_pole_mu[:, 0])
  2246. def _YT_loop(ker_pole, transfer_matrix, poles, B, maxiter, rtol):
  2247. """
  2248. Algorithm "YT" Tits, Yang. Globally Convergent
  2249. Algorithms for Robust Pole Assignment by State Feedback
  2250. https://hdl.handle.net/1903/5598
  2251. The poles P have to be sorted accordingly to section 6.2 page 20
  2252. """
  2253. # The IEEE edition of the YT paper gives useful information on the
  2254. # optimal update order for the real poles in order to minimize the number
  2255. # of times we have to loop over all poles, see page 1442
  2256. nb_real = poles[np.isreal(poles)].shape[0]
  2257. # hnb => Half Nb Real
  2258. hnb = nb_real // 2
  2259. # Stick to the indices in the paper and then remove one to get numpy array
  2260. # index it is a bit easier to link the code to the paper this way even if it
  2261. # is not very clean. The paper is unclear about what should be done when
  2262. # there is only one real pole => use KNV0 on this real pole seem to work
  2263. if nb_real > 0:
  2264. #update the biggest real pole with the smallest one
  2265. update_order = [[nb_real], [1]]
  2266. else:
  2267. update_order = [[],[]]
  2268. r_comp = np.arange(nb_real+1, len(poles)+1, 2)
  2269. # step 1.a
  2270. r_p = np.arange(1, hnb+nb_real % 2)
  2271. update_order[0].extend(2*r_p)
  2272. update_order[1].extend(2*r_p+1)
  2273. # step 1.b
  2274. update_order[0].extend(r_comp)
  2275. update_order[1].extend(r_comp+1)
  2276. # step 1.c
  2277. r_p = np.arange(1, hnb+1)
  2278. update_order[0].extend(2*r_p-1)
  2279. update_order[1].extend(2*r_p)
  2280. # step 1.d
  2281. if hnb == 0 and np.isreal(poles[0]):
  2282. update_order[0].append(1)
  2283. update_order[1].append(1)
  2284. update_order[0].extend(r_comp)
  2285. update_order[1].extend(r_comp+1)
  2286. # step 2.a
  2287. r_j = np.arange(2, hnb+nb_real % 2)
  2288. for j in r_j:
  2289. for i in range(1, hnb+1):
  2290. update_order[0].append(i)
  2291. update_order[1].append(i+j)
  2292. # step 2.b
  2293. if hnb == 0 and np.isreal(poles[0]):
  2294. update_order[0].append(1)
  2295. update_order[1].append(1)
  2296. update_order[0].extend(r_comp)
  2297. update_order[1].extend(r_comp+1)
  2298. # step 2.c
  2299. r_j = np.arange(2, hnb+nb_real % 2)
  2300. for j in r_j:
  2301. for i in range(hnb+1, nb_real+1):
  2302. idx_1 = i+j
  2303. if idx_1 > nb_real:
  2304. idx_1 = i+j-nb_real
  2305. update_order[0].append(i)
  2306. update_order[1].append(idx_1)
  2307. # step 2.d
  2308. if hnb == 0 and np.isreal(poles[0]):
  2309. update_order[0].append(1)
  2310. update_order[1].append(1)
  2311. update_order[0].extend(r_comp)
  2312. update_order[1].extend(r_comp+1)
  2313. # step 3.a
  2314. for i in range(1, hnb+1):
  2315. update_order[0].append(i)
  2316. update_order[1].append(i+hnb)
  2317. # step 3.b
  2318. if hnb == 0 and np.isreal(poles[0]):
  2319. update_order[0].append(1)
  2320. update_order[1].append(1)
  2321. update_order[0].extend(r_comp)
  2322. update_order[1].extend(r_comp+1)
  2323. update_order = np.array(update_order).T-1
  2324. stop = False
  2325. nb_try = 0
  2326. while nb_try < maxiter and not stop:
  2327. det_transfer_matrixb = np.abs(np.linalg.det(transfer_matrix))
  2328. for i, j in update_order:
  2329. if i == j:
  2330. assert i == 0, "i!=0 for KNV call in YT"
  2331. assert np.isreal(poles[i]), "calling KNV on a complex pole"
  2332. _KNV0(B, ker_pole, transfer_matrix, i, poles)
  2333. else:
  2334. transfer_matrix_not_i_j = np.delete(transfer_matrix, (i, j),
  2335. axis=1)
  2336. # after merge of gh-4249 great speed improvements could be
  2337. # achieved using QR updates instead of full QR in the line below
  2338. #to debug with numpy qr uncomment the line below
  2339. #Q, _ = np.linalg.qr(transfer_matrix_not_i_j, mode="complete")
  2340. Q, _ = s_qr(transfer_matrix_not_i_j, mode="full")
  2341. if np.isreal(poles[i]):
  2342. assert np.isreal(poles[j]), "mixing real and complex " + \
  2343. "in YT_real" + str(poles)
  2344. _YT_real(ker_pole, Q, transfer_matrix, i, j)
  2345. else:
  2346. assert ~np.isreal(poles[i]), "mixing real and complex " + \
  2347. "in YT_real" + str(poles)
  2348. _YT_complex(ker_pole, Q, transfer_matrix, i, j)
  2349. det_transfer_matrix = np.max((np.sqrt(np.spacing(1)),
  2350. np.abs(np.linalg.det(transfer_matrix))))
  2351. cur_rtol = np.abs(
  2352. (det_transfer_matrix -
  2353. det_transfer_matrixb) /
  2354. det_transfer_matrix)
  2355. if cur_rtol < rtol and det_transfer_matrix > np.sqrt(np.spacing(1)):
  2356. # Convergence test from YT page 21
  2357. stop = True
  2358. nb_try += 1
  2359. return stop, cur_rtol, nb_try
  2360. def _KNV0_loop(ker_pole, transfer_matrix, poles, B, maxiter, rtol):
  2361. """
  2362. Loop over all poles one by one and apply KNV method 0 algorithm
  2363. """
  2364. # This method is useful only because we need to be able to call
  2365. # _KNV0 from YT without looping over all poles, otherwise it would
  2366. # have been fine to mix _KNV0_loop and _KNV0 in a single function
  2367. stop = False
  2368. nb_try = 0
  2369. while nb_try < maxiter and not stop:
  2370. det_transfer_matrixb = np.abs(np.linalg.det(transfer_matrix))
  2371. for j in range(B.shape[0]):
  2372. _KNV0(B, ker_pole, transfer_matrix, j, poles)
  2373. det_transfer_matrix = np.max((np.sqrt(np.spacing(1)),
  2374. np.abs(np.linalg.det(transfer_matrix))))
  2375. cur_rtol = np.abs((det_transfer_matrix - det_transfer_matrixb) /
  2376. det_transfer_matrix)
  2377. if cur_rtol < rtol and det_transfer_matrix > np.sqrt(np.spacing(1)):
  2378. # Convergence test from YT page 21
  2379. stop = True
  2380. nb_try += 1
  2381. return stop, cur_rtol, nb_try
  2382. def place_poles(A, B, poles, method="YT", rtol=1e-3, maxiter=30):
  2383. """
  2384. Compute K such that eigenvalues (A - dot(B, K))=poles.
  2385. K is the gain matrix such as the plant described by the linear system
  2386. ``AX+BU`` will have its closed-loop poles, i.e the eigenvalues ``A - B*K``,
  2387. as close as possible to those asked for in poles.
  2388. SISO, MISO and MIMO systems are supported.
  2389. Parameters
  2390. ----------
  2391. A, B : ndarray
  2392. State-space representation of linear system ``AX + BU``.
  2393. poles : array_like
  2394. Desired real poles and/or complex conjugates poles.
  2395. Complex poles are only supported with ``method="YT"`` (default).
  2396. method: {'YT', 'KNV0'}, optional
  2397. Which method to choose to find the gain matrix K. One of:
  2398. - 'YT': Yang Tits
  2399. - 'KNV0': Kautsky, Nichols, Van Dooren update method 0
  2400. See References and Notes for details on the algorithms.
  2401. rtol: float, optional
  2402. After each iteration the determinant of the eigenvectors of
  2403. ``A - B*K`` is compared to its previous value, when the relative
  2404. error between these two values becomes lower than `rtol` the algorithm
  2405. stops. Default is 1e-3.
  2406. maxiter: int, optional
  2407. Maximum number of iterations to compute the gain matrix.
  2408. Default is 30.
  2409. Returns
  2410. -------
  2411. full_state_feedback : Bunch object
  2412. full_state_feedback is composed of:
  2413. gain_matrix : 1-D ndarray
  2414. The closed loop matrix K such as the eigenvalues of ``A-BK``
  2415. are as close as possible to the requested poles.
  2416. computed_poles : 1-D ndarray
  2417. The poles corresponding to ``A-BK`` sorted as first the real
  2418. poles in increasing order, then the complex congugates in
  2419. lexicographic order.
  2420. requested_poles : 1-D ndarray
  2421. The poles the algorithm was asked to place sorted as above,
  2422. they may differ from what was achieved.
  2423. X : 2-D ndarray
  2424. The transfer matrix such as ``X * diag(poles) = (A - B*K)*X``
  2425. (see Notes)
  2426. rtol : float
  2427. The relative tolerance achieved on ``det(X)`` (see Notes).
  2428. `rtol` will be NaN if it is possible to solve the system
  2429. ``diag(poles) = (A - B*K)``, or 0 when the optimization
  2430. algorithms can't do anything i.e when ``B.shape[1] == 1``.
  2431. nb_iter : int
  2432. The number of iterations performed before converging.
  2433. `nb_iter` will be NaN if it is possible to solve the system
  2434. ``diag(poles) = (A - B*K)``, or 0 when the optimization
  2435. algorithms can't do anything i.e when ``B.shape[1] == 1``.
  2436. Notes
  2437. -----
  2438. The Tits and Yang (YT), [2]_ paper is an update of the original Kautsky et
  2439. al. (KNV) paper [1]_. KNV relies on rank-1 updates to find the transfer
  2440. matrix X such that ``X * diag(poles) = (A - B*K)*X``, whereas YT uses
  2441. rank-2 updates. This yields on average more robust solutions (see [2]_
  2442. pp 21-22), furthermore the YT algorithm supports complex poles whereas KNV
  2443. does not in its original version. Only update method 0 proposed by KNV has
  2444. been implemented here, hence the name ``'KNV0'``.
  2445. KNV extended to complex poles is used in Matlab's ``place`` function, YT is
  2446. distributed under a non-free licence by Slicot under the name ``robpole``.
  2447. It is unclear and undocumented how KNV0 has been extended to complex poles
  2448. (Tits and Yang claim on page 14 of their paper that their method can not be
  2449. used to extend KNV to complex poles), therefore only YT supports them in
  2450. this implementation.
  2451. As the solution to the problem of pole placement is not unique for MIMO
  2452. systems, both methods start with a tentative transfer matrix which is
  2453. altered in various way to increase its determinant. Both methods have been
  2454. proven to converge to a stable solution, however depending on the way the
  2455. initial transfer matrix is chosen they will converge to different
  2456. solutions and therefore there is absolutely no guarantee that using
  2457. ``'KNV0'`` will yield results similar to Matlab's or any other
  2458. implementation of these algorithms.
  2459. Using the default method ``'YT'`` should be fine in most cases; ``'KNV0'``
  2460. is only provided because it is needed by ``'YT'`` in some specific cases.
  2461. Furthermore ``'YT'`` gives on average more robust results than ``'KNV0'``
  2462. when ``abs(det(X))`` is used as a robustness indicator.
  2463. [2]_ is available as a technical report on the following URL:
  2464. https://hdl.handle.net/1903/5598
  2465. References
  2466. ----------
  2467. .. [1] J. Kautsky, N.K. Nichols and P. van Dooren, "Robust pole assignment
  2468. in linear state feedback", International Journal of Control, Vol. 41
  2469. pp. 1129-1155, 1985.
  2470. .. [2] A.L. Tits and Y. Yang, "Globally convergent algorithms for robust
  2471. pole assignment by state feedback, IEEE Transactions on Automatic
  2472. Control, Vol. 41, pp. 1432-1452, 1996.
  2473. Examples
  2474. --------
  2475. A simple example demonstrating real pole placement using both KNV and YT
  2476. algorithms. This is example number 1 from section 4 of the reference KNV
  2477. publication ([1]_):
  2478. >>> from scipy import signal
  2479. >>> import matplotlib.pyplot as plt
  2480. >>> A = np.array([[ 1.380, -0.2077, 6.715, -5.676 ],
  2481. ... [-0.5814, -4.290, 0, 0.6750 ],
  2482. ... [ 1.067, 4.273, -6.654, 5.893 ],
  2483. ... [ 0.0480, 4.273, 1.343, -2.104 ]])
  2484. >>> B = np.array([[ 0, 5.679 ],
  2485. ... [ 1.136, 1.136 ],
  2486. ... [ 0, 0, ],
  2487. ... [-3.146, 0 ]])
  2488. >>> P = np.array([-0.2, -0.5, -5.0566, -8.6659])
  2489. Now compute K with KNV method 0, with the default YT method and with the YT
  2490. method while forcing 100 iterations of the algorithm and print some results
  2491. after each call.
  2492. >>> fsf1 = signal.place_poles(A, B, P, method='KNV0')
  2493. >>> fsf1.gain_matrix
  2494. array([[ 0.20071427, -0.96665799, 0.24066128, -0.10279785],
  2495. [ 0.50587268, 0.57779091, 0.51795763, -0.41991442]])
  2496. >>> fsf2 = signal.place_poles(A, B, P) # uses YT method
  2497. >>> fsf2.computed_poles
  2498. array([-8.6659, -5.0566, -0.5 , -0.2 ])
  2499. >>> fsf3 = signal.place_poles(A, B, P, rtol=-1, maxiter=100)
  2500. >>> fsf3.X
  2501. array([[ 0.52072442+0.j, -0.08409372+0.j, -0.56847937+0.j, 0.74823657+0.j],
  2502. [-0.04977751+0.j, -0.80872954+0.j, 0.13566234+0.j, -0.29322906+0.j],
  2503. [-0.82266932+0.j, -0.19168026+0.j, -0.56348322+0.j, -0.43815060+0.j],
  2504. [ 0.22267347+0.j, 0.54967577+0.j, -0.58387806+0.j, -0.40271926+0.j]])
  2505. The absolute value of the determinant of X is a good indicator to check the
  2506. robustness of the results, both ``'KNV0'`` and ``'YT'`` aim at maximizing
  2507. it. Below a comparison of the robustness of the results above:
  2508. >>> abs(np.linalg.det(fsf1.X)) < abs(np.linalg.det(fsf2.X))
  2509. True
  2510. >>> abs(np.linalg.det(fsf2.X)) < abs(np.linalg.det(fsf3.X))
  2511. True
  2512. Now a simple example for complex poles:
  2513. >>> A = np.array([[ 0, 7/3., 0, 0 ],
  2514. ... [ 0, 0, 0, 7/9. ],
  2515. ... [ 0, 0, 0, 0 ],
  2516. ... [ 0, 0, 0, 0 ]])
  2517. >>> B = np.array([[ 0, 0 ],
  2518. ... [ 0, 0 ],
  2519. ... [ 1, 0 ],
  2520. ... [ 0, 1 ]])
  2521. >>> P = np.array([-3, -1, -2-1j, -2+1j]) / 3.
  2522. >>> fsf = signal.place_poles(A, B, P, method='YT')
  2523. We can plot the desired and computed poles in the complex plane:
  2524. >>> t = np.linspace(0, 2*np.pi, 401)
  2525. >>> plt.plot(np.cos(t), np.sin(t), 'k--') # unit circle
  2526. >>> plt.plot(fsf.requested_poles.real, fsf.requested_poles.imag,
  2527. ... 'wo', label='Desired')
  2528. >>> plt.plot(fsf.computed_poles.real, fsf.computed_poles.imag, 'bx',
  2529. ... label='Placed')
  2530. >>> plt.grid()
  2531. >>> plt.axis('image')
  2532. >>> plt.axis([-1.1, 1.1, -1.1, 1.1])
  2533. >>> plt.legend(bbox_to_anchor=(1.05, 1), loc=2, numpoints=1)
  2534. """
  2535. # Move away all the inputs checking, it only adds noise to the code
  2536. update_loop, poles = _valid_inputs(A, B, poles, method, rtol, maxiter)
  2537. # The current value of the relative tolerance we achieved
  2538. cur_rtol = 0
  2539. # The number of iterations needed before converging
  2540. nb_iter = 0
  2541. # Step A: QR decomposition of B page 1132 KN
  2542. # to debug with numpy qr uncomment the line below
  2543. # u, z = np.linalg.qr(B, mode="complete")
  2544. u, z = s_qr(B, mode="full")
  2545. rankB = np.linalg.matrix_rank(B)
  2546. u0 = u[:, :rankB]
  2547. u1 = u[:, rankB:]
  2548. z = z[:rankB, :]
  2549. # If we can use the identity matrix as X the solution is obvious
  2550. if B.shape[0] == rankB:
  2551. # if B is square and full rank there is only one solution
  2552. # such as (A+BK)=inv(X)*diag(P)*X with X=eye(A.shape[0])
  2553. # i.e K=inv(B)*(diag(P)-A)
  2554. # if B has as many lines as its rank (but not square) there are many
  2555. # solutions and we can choose one using least squares
  2556. # => use lstsq in both cases.
  2557. # In both cases the transfer matrix X will be eye(A.shape[0]) and I
  2558. # can hardly think of a better one so there is nothing to optimize
  2559. #
  2560. # for complex poles we use the following trick
  2561. #
  2562. # |a -b| has for eigenvalues a+b and a-b
  2563. # |b a|
  2564. #
  2565. # |a+bi 0| has the obvious eigenvalues a+bi and a-bi
  2566. # |0 a-bi|
  2567. #
  2568. # e.g solving the first one in R gives the solution
  2569. # for the second one in C
  2570. diag_poles = np.zeros(A.shape)
  2571. idx = 0
  2572. while idx < poles.shape[0]:
  2573. p = poles[idx]
  2574. diag_poles[idx, idx] = np.real(p)
  2575. if ~np.isreal(p):
  2576. diag_poles[idx, idx+1] = -np.imag(p)
  2577. diag_poles[idx+1, idx+1] = np.real(p)
  2578. diag_poles[idx+1, idx] = np.imag(p)
  2579. idx += 1 # skip next one
  2580. idx += 1
  2581. gain_matrix = np.linalg.lstsq(B, diag_poles-A, rcond=-1)[0]
  2582. transfer_matrix = np.eye(A.shape[0])
  2583. cur_rtol = np.nan
  2584. nb_iter = np.nan
  2585. else:
  2586. # step A (p1144 KNV) and beginning of step F: decompose
  2587. # dot(U1.T, A-P[i]*I).T and build our set of transfer_matrix vectors
  2588. # in the same loop
  2589. ker_pole = []
  2590. # flag to skip the conjugate of a complex pole
  2591. skip_conjugate = False
  2592. # select orthonormal base ker_pole for each Pole and vectors for
  2593. # transfer_matrix
  2594. for j in range(B.shape[0]):
  2595. if skip_conjugate:
  2596. skip_conjugate = False
  2597. continue
  2598. pole_space_j = np.dot(u1.T, A-poles[j]*np.eye(B.shape[0])).T
  2599. # after QR Q=Q0|Q1
  2600. # only Q0 is used to reconstruct the qr'ed (dot Q, R) matrix.
  2601. # Q1 is orthogonnal to Q0 and will be multiplied by the zeros in
  2602. # R when using mode "complete". In default mode Q1 and the zeros
  2603. # in R are not computed
  2604. # To debug with numpy qr uncomment the line below
  2605. # Q, _ = np.linalg.qr(pole_space_j, mode="complete")
  2606. Q, _ = s_qr(pole_space_j, mode="full")
  2607. ker_pole_j = Q[:, pole_space_j.shape[1]:]
  2608. # We want to select one vector in ker_pole_j to build the transfer
  2609. # matrix, however qr returns sometimes vectors with zeros on the
  2610. # same line for each pole and this yields very long convergence
  2611. # times.
  2612. # Or some other times a set of vectors, one with zero imaginary
  2613. # part and one (or several) with imaginary parts. After trying
  2614. # many ways to select the best possible one (eg ditch vectors
  2615. # with zero imaginary part for complex poles) I ended up summing
  2616. # all vectors in ker_pole_j, this solves 100% of the problems and
  2617. # is a valid choice for transfer_matrix.
  2618. # This way for complex poles we are sure to have a non zero
  2619. # imaginary part that way, and the problem of lines full of zeros
  2620. # in transfer_matrix is solved too as when a vector from
  2621. # ker_pole_j has a zero the other one(s) when
  2622. # ker_pole_j.shape[1]>1) for sure won't have a zero there.
  2623. transfer_matrix_j = np.sum(ker_pole_j, axis=1)[:, np.newaxis]
  2624. transfer_matrix_j = (transfer_matrix_j /
  2625. np.linalg.norm(transfer_matrix_j))
  2626. if ~np.isreal(poles[j]): # complex pole
  2627. transfer_matrix_j = np.hstack([np.real(transfer_matrix_j),
  2628. np.imag(transfer_matrix_j)])
  2629. ker_pole.extend([ker_pole_j, ker_pole_j])
  2630. # Skip next pole as it is the conjugate
  2631. skip_conjugate = True
  2632. else: # real pole, nothing to do
  2633. ker_pole.append(ker_pole_j)
  2634. if j == 0:
  2635. transfer_matrix = transfer_matrix_j
  2636. else:
  2637. transfer_matrix = np.hstack((transfer_matrix, transfer_matrix_j))
  2638. if rankB > 1: # otherwise there is nothing we can optimize
  2639. stop, cur_rtol, nb_iter = update_loop(ker_pole, transfer_matrix,
  2640. poles, B, maxiter, rtol)
  2641. if not stop and rtol > 0:
  2642. # if rtol<=0 the user has probably done that on purpose,
  2643. # don't annoy him
  2644. err_msg = (
  2645. "Convergence was not reached after maxiter iterations.\n"
  2646. "You asked for a relative tolerance of %f we got %f" %
  2647. (rtol, cur_rtol)
  2648. )
  2649. warnings.warn(err_msg)
  2650. # reconstruct transfer_matrix to match complex conjugate pairs,
  2651. # ie transfer_matrix_j/transfer_matrix_j+1 are
  2652. # Re(Complex_pole), Im(Complex_pole) now and will be Re-Im/Re+Im after
  2653. transfer_matrix = transfer_matrix.astype(complex)
  2654. idx = 0
  2655. while idx < poles.shape[0]-1:
  2656. if ~np.isreal(poles[idx]):
  2657. rel = transfer_matrix[:, idx].copy()
  2658. img = transfer_matrix[:, idx+1]
  2659. # rel will be an array referencing a column of transfer_matrix
  2660. # if we don't copy() it will changer after the next line and
  2661. # and the line after will not yield the correct value
  2662. transfer_matrix[:, idx] = rel-1j*img
  2663. transfer_matrix[:, idx+1] = rel+1j*img
  2664. idx += 1 # skip next one
  2665. idx += 1
  2666. try:
  2667. m = np.linalg.solve(transfer_matrix.T, np.dot(np.diag(poles),
  2668. transfer_matrix.T)).T
  2669. gain_matrix = np.linalg.solve(z, np.dot(u0.T, m-A))
  2670. except np.linalg.LinAlgError:
  2671. raise ValueError("The poles you've chosen can't be placed. "
  2672. "Check the controllability matrix and try "
  2673. "another set of poles")
  2674. # Beware: Kautsky solves A+BK but the usual form is A-BK
  2675. gain_matrix = -gain_matrix
  2676. # K still contains complex with ~=0j imaginary parts, get rid of them
  2677. gain_matrix = np.real(gain_matrix)
  2678. full_state_feedback = Bunch()
  2679. full_state_feedback.gain_matrix = gain_matrix
  2680. full_state_feedback.computed_poles = _order_complex_poles(
  2681. np.linalg.eig(A - np.dot(B, gain_matrix))[0]
  2682. )
  2683. full_state_feedback.requested_poles = poles
  2684. full_state_feedback.X = transfer_matrix
  2685. full_state_feedback.rtol = cur_rtol
  2686. full_state_feedback.nb_iter = nb_iter
  2687. return full_state_feedback
  2688. def dlsim(system, u, t=None, x0=None):
  2689. """
  2690. Simulate output of a discrete-time linear system.
  2691. Parameters
  2692. ----------
  2693. system : tuple of array_like or instance of `dlti`
  2694. A tuple describing the system.
  2695. The following gives the number of elements in the tuple and
  2696. the interpretation:
  2697. * 1: (instance of `dlti`)
  2698. * 3: (num, den, dt)
  2699. * 4: (zeros, poles, gain, dt)
  2700. * 5: (A, B, C, D, dt)
  2701. u : array_like
  2702. An input array describing the input at each time `t` (interpolation is
  2703. assumed between given times). If there are multiple inputs, then each
  2704. column of the rank-2 array represents an input.
  2705. t : array_like, optional
  2706. The time steps at which the input is defined. If `t` is given, it
  2707. must be the same length as `u`, and the final value in `t` determines
  2708. the number of steps returned in the output.
  2709. x0 : array_like, optional
  2710. The initial conditions on the state vector (zero by default).
  2711. Returns
  2712. -------
  2713. tout : ndarray
  2714. Time values for the output, as a 1-D array.
  2715. yout : ndarray
  2716. System response, as a 1-D array.
  2717. xout : ndarray, optional
  2718. Time-evolution of the state-vector. Only generated if the input is a
  2719. `StateSpace` system.
  2720. See Also
  2721. --------
  2722. lsim, dstep, dimpulse, cont2discrete
  2723. Examples
  2724. --------
  2725. A simple integrator transfer function with a discrete time step of 1.0
  2726. could be implemented as:
  2727. >>> from scipy import signal
  2728. >>> tf = ([1.0,], [1.0, -1.0], 1.0)
  2729. >>> t_in = [0.0, 1.0, 2.0, 3.0]
  2730. >>> u = np.asarray([0.0, 0.0, 1.0, 1.0])
  2731. >>> t_out, y = signal.dlsim(tf, u, t=t_in)
  2732. >>> y.T
  2733. array([[ 0., 0., 0., 1.]])
  2734. """
  2735. # Convert system to dlti-StateSpace
  2736. if isinstance(system, lti):
  2737. raise AttributeError('dlsim can only be used with discrete-time dlti '
  2738. 'systems.')
  2739. elif not isinstance(system, dlti):
  2740. system = dlti(*system[:-1], dt=system[-1])
  2741. # Condition needed to ensure output remains compatible
  2742. is_ss_input = isinstance(system, StateSpace)
  2743. system = system._as_ss()
  2744. u = np.atleast_1d(u)
  2745. if u.ndim == 1:
  2746. u = np.atleast_2d(u).T
  2747. if t is None:
  2748. out_samples = len(u)
  2749. stoptime = (out_samples - 1) * system.dt
  2750. else:
  2751. stoptime = t[-1]
  2752. out_samples = int(np.floor(stoptime / system.dt)) + 1
  2753. # Pre-build output arrays
  2754. xout = np.zeros((out_samples, system.A.shape[0]))
  2755. yout = np.zeros((out_samples, system.C.shape[0]))
  2756. tout = np.linspace(0.0, stoptime, num=out_samples)
  2757. # Check initial condition
  2758. if x0 is None:
  2759. xout[0, :] = np.zeros((system.A.shape[1],))
  2760. else:
  2761. xout[0, :] = np.asarray(x0)
  2762. # Pre-interpolate inputs into the desired time steps
  2763. if t is None:
  2764. u_dt = u
  2765. else:
  2766. if len(u.shape) == 1:
  2767. u = u[:, np.newaxis]
  2768. u_dt_interp = interp1d(t, u.transpose(), copy=False, bounds_error=True)
  2769. u_dt = u_dt_interp(tout).transpose()
  2770. # Simulate the system
  2771. for i in range(0, out_samples - 1):
  2772. xout[i+1, :] = (np.dot(system.A, xout[i, :]) +
  2773. np.dot(system.B, u_dt[i, :]))
  2774. yout[i, :] = (np.dot(system.C, xout[i, :]) +
  2775. np.dot(system.D, u_dt[i, :]))
  2776. # Last point
  2777. yout[out_samples-1, :] = (np.dot(system.C, xout[out_samples-1, :]) +
  2778. np.dot(system.D, u_dt[out_samples-1, :]))
  2779. if is_ss_input:
  2780. return tout, yout, xout
  2781. else:
  2782. return tout, yout
  2783. def dimpulse(system, x0=None, t=None, n=None):
  2784. """
  2785. Impulse response of discrete-time system.
  2786. Parameters
  2787. ----------
  2788. system : tuple of array_like or instance of `dlti`
  2789. A tuple describing the system.
  2790. The following gives the number of elements in the tuple and
  2791. the interpretation:
  2792. * 1: (instance of `dlti`)
  2793. * 3: (num, den, dt)
  2794. * 4: (zeros, poles, gain, dt)
  2795. * 5: (A, B, C, D, dt)
  2796. x0 : array_like, optional
  2797. Initial state-vector. Defaults to zero.
  2798. t : array_like, optional
  2799. Time points. Computed if not given.
  2800. n : int, optional
  2801. The number of time points to compute (if `t` is not given).
  2802. Returns
  2803. -------
  2804. tout : ndarray
  2805. Time values for the output, as a 1-D array.
  2806. yout : tuple of ndarray
  2807. Impulse response of system. Each element of the tuple represents
  2808. the output of the system based on an impulse in each input.
  2809. See Also
  2810. --------
  2811. impulse, dstep, dlsim, cont2discrete
  2812. """
  2813. # Convert system to dlti-StateSpace
  2814. if isinstance(system, dlti):
  2815. system = system._as_ss()
  2816. elif isinstance(system, lti):
  2817. raise AttributeError('dimpulse can only be used with discrete-time '
  2818. 'dlti systems.')
  2819. else:
  2820. system = dlti(*system[:-1], dt=system[-1])._as_ss()
  2821. # Default to 100 samples if unspecified
  2822. if n is None:
  2823. n = 100
  2824. # If time is not specified, use the number of samples
  2825. # and system dt
  2826. if t is None:
  2827. t = np.linspace(0, n * system.dt, n, endpoint=False)
  2828. else:
  2829. t = np.asarray(t)
  2830. # For each input, implement a step change
  2831. yout = None
  2832. for i in range(0, system.inputs):
  2833. u = np.zeros((t.shape[0], system.inputs))
  2834. u[0, i] = 1.0
  2835. one_output = dlsim(system, u, t=t, x0=x0)
  2836. if yout is None:
  2837. yout = (one_output[1],)
  2838. else:
  2839. yout = yout + (one_output[1],)
  2840. tout = one_output[0]
  2841. return tout, yout
  2842. def dstep(system, x0=None, t=None, n=None):
  2843. """
  2844. Step response of discrete-time system.
  2845. Parameters
  2846. ----------
  2847. system : tuple of array_like
  2848. A tuple describing the system.
  2849. The following gives the number of elements in the tuple and
  2850. the interpretation:
  2851. * 1: (instance of `dlti`)
  2852. * 3: (num, den, dt)
  2853. * 4: (zeros, poles, gain, dt)
  2854. * 5: (A, B, C, D, dt)
  2855. x0 : array_like, optional
  2856. Initial state-vector. Defaults to zero.
  2857. t : array_like, optional
  2858. Time points. Computed if not given.
  2859. n : int, optional
  2860. The number of time points to compute (if `t` is not given).
  2861. Returns
  2862. -------
  2863. tout : ndarray
  2864. Output time points, as a 1-D array.
  2865. yout : tuple of ndarray
  2866. Step response of system. Each element of the tuple represents
  2867. the output of the system based on a step response to each input.
  2868. See Also
  2869. --------
  2870. step, dimpulse, dlsim, cont2discrete
  2871. """
  2872. # Convert system to dlti-StateSpace
  2873. if isinstance(system, dlti):
  2874. system = system._as_ss()
  2875. elif isinstance(system, lti):
  2876. raise AttributeError('dstep can only be used with discrete-time dlti '
  2877. 'systems.')
  2878. else:
  2879. system = dlti(*system[:-1], dt=system[-1])._as_ss()
  2880. # Default to 100 samples if unspecified
  2881. if n is None:
  2882. n = 100
  2883. # If time is not specified, use the number of samples
  2884. # and system dt
  2885. if t is None:
  2886. t = np.linspace(0, n * system.dt, n, endpoint=False)
  2887. else:
  2888. t = np.asarray(t)
  2889. # For each input, implement a step change
  2890. yout = None
  2891. for i in range(0, system.inputs):
  2892. u = np.zeros((t.shape[0], system.inputs))
  2893. u[:, i] = np.ones((t.shape[0],))
  2894. one_output = dlsim(system, u, t=t, x0=x0)
  2895. if yout is None:
  2896. yout = (one_output[1],)
  2897. else:
  2898. yout = yout + (one_output[1],)
  2899. tout = one_output[0]
  2900. return tout, yout
  2901. def dfreqresp(system, w=None, n=10000, whole=False):
  2902. """
  2903. Calculate the frequency response of a discrete-time system.
  2904. Parameters
  2905. ----------
  2906. system : an instance of the `dlti` class or a tuple describing the system.
  2907. The following gives the number of elements in the tuple and
  2908. the interpretation:
  2909. * 1 (instance of `dlti`)
  2910. * 2 (numerator, denominator, dt)
  2911. * 3 (zeros, poles, gain, dt)
  2912. * 4 (A, B, C, D, dt)
  2913. w : array_like, optional
  2914. Array of frequencies (in radians/sample). Magnitude and phase data is
  2915. calculated for every value in this array. If not given a reasonable
  2916. set will be calculated.
  2917. n : int, optional
  2918. Number of frequency points to compute if `w` is not given. The `n`
  2919. frequencies are logarithmically spaced in an interval chosen to
  2920. include the influence of the poles and zeros of the system.
  2921. whole : bool, optional
  2922. Normally, if 'w' is not given, frequencies are computed from 0 to the
  2923. Nyquist frequency, pi radians/sample (upper-half of unit-circle). If
  2924. `whole` is True, compute frequencies from 0 to 2*pi radians/sample.
  2925. Returns
  2926. -------
  2927. w : 1D ndarray
  2928. Frequency array [radians/sample]
  2929. H : 1D ndarray
  2930. Array of complex magnitude values
  2931. Notes
  2932. -----
  2933. If (num, den) is passed in for ``system``, coefficients for both the
  2934. numerator and denominator should be specified in descending exponent
  2935. order (e.g. ``z^2 + 3z + 5`` would be represented as ``[1, 3, 5]``).
  2936. .. versionadded:: 0.18.0
  2937. Examples
  2938. --------
  2939. Generating the Nyquist plot of a transfer function
  2940. >>> from scipy import signal
  2941. >>> import matplotlib.pyplot as plt
  2942. Transfer function: H(z) = 1 / (z^2 + 2z + 3)
  2943. >>> sys = signal.TransferFunction([1], [1, 2, 3], dt=0.05)
  2944. >>> w, H = signal.dfreqresp(sys)
  2945. >>> plt.figure()
  2946. >>> plt.plot(H.real, H.imag, "b")
  2947. >>> plt.plot(H.real, -H.imag, "r")
  2948. >>> plt.show()
  2949. """
  2950. if not isinstance(system, dlti):
  2951. if isinstance(system, lti):
  2952. raise AttributeError('dfreqresp can only be used with '
  2953. 'discrete-time systems.')
  2954. system = dlti(*system[:-1], dt=system[-1])
  2955. if isinstance(system, StateSpace):
  2956. # No SS->ZPK code exists right now, just SS->TF->ZPK
  2957. system = system._as_tf()
  2958. if not isinstance(system, (TransferFunction, ZerosPolesGain)):
  2959. raise ValueError('Unknown system type')
  2960. if system.inputs != 1 or system.outputs != 1:
  2961. raise ValueError("dfreqresp requires a SISO (single input, single "
  2962. "output) system.")
  2963. if w is not None:
  2964. worN = w
  2965. else:
  2966. worN = n
  2967. if isinstance(system, TransferFunction):
  2968. # Convert numerator and denominator from polynomials in the variable
  2969. # 'z' to polynomials in the variable 'z^-1', as freqz expects.
  2970. num, den = TransferFunction._z_to_zinv(system.num.ravel(), system.den)
  2971. w, h = freqz(num, den, worN=worN, whole=whole)
  2972. elif isinstance(system, ZerosPolesGain):
  2973. w, h = freqz_zpk(system.zeros, system.poles, system.gain, worN=worN,
  2974. whole=whole)
  2975. return w, h
  2976. def dbode(system, w=None, n=100):
  2977. """
  2978. Calculate Bode magnitude and phase data of a discrete-time system.
  2979. Parameters
  2980. ----------
  2981. system : an instance of the LTI class or a tuple describing the system.
  2982. The following gives the number of elements in the tuple and
  2983. the interpretation:
  2984. * 1 (instance of `dlti`)
  2985. * 2 (num, den, dt)
  2986. * 3 (zeros, poles, gain, dt)
  2987. * 4 (A, B, C, D, dt)
  2988. w : array_like, optional
  2989. Array of frequencies (in radians/sample). Magnitude and phase data is
  2990. calculated for every value in this array. If not given a reasonable
  2991. set will be calculated.
  2992. n : int, optional
  2993. Number of frequency points to compute if `w` is not given. The `n`
  2994. frequencies are logarithmically spaced in an interval chosen to
  2995. include the influence of the poles and zeros of the system.
  2996. Returns
  2997. -------
  2998. w : 1D ndarray
  2999. Frequency array [rad/time_unit]
  3000. mag : 1D ndarray
  3001. Magnitude array [dB]
  3002. phase : 1D ndarray
  3003. Phase array [deg]
  3004. Notes
  3005. -----
  3006. If (num, den) is passed in for ``system``, coefficients for both the
  3007. numerator and denominator should be specified in descending exponent
  3008. order (e.g. ``z^2 + 3z + 5`` would be represented as ``[1, 3, 5]``).
  3009. .. versionadded:: 0.18.0
  3010. Examples
  3011. --------
  3012. >>> from scipy import signal
  3013. >>> import matplotlib.pyplot as plt
  3014. Transfer function: H(z) = 1 / (z^2 + 2z + 3)
  3015. >>> sys = signal.TransferFunction([1], [1, 2, 3], dt=0.05)
  3016. Equivalent: sys.bode()
  3017. >>> w, mag, phase = signal.dbode(sys)
  3018. >>> plt.figure()
  3019. >>> plt.semilogx(w, mag) # Bode magnitude plot
  3020. >>> plt.figure()
  3021. >>> plt.semilogx(w, phase) # Bode phase plot
  3022. >>> plt.show()
  3023. """
  3024. w, y = dfreqresp(system, w=w, n=n)
  3025. if isinstance(system, dlti):
  3026. dt = system.dt
  3027. else:
  3028. dt = system[-1]
  3029. mag = 20.0 * numpy.log10(abs(y))
  3030. phase = numpy.rad2deg(numpy.unwrap(numpy.angle(y)))
  3031. return w / dt, mag, phase