_ltisys.py 126 KB

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