图片解析应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2952 lines
109 KiB

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