Skip to content

Commit 5c85459

Browse files
committed
Add print zpk form
1 parent 346bc40 commit 5c85459

2 files changed

Lines changed: 138 additions & 1 deletion

File tree

control/tests/xferfcn_test.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import control as ct
1111
from control import StateSpace, TransferFunction, rss, evalfr
12-
from control import ss, ss2tf, tf, tf2ss
12+
from control import ss, ss2tf, tf, tf2ss, zpk
1313
from control import isctime, isdtime, sample_system, defaults
1414
from control.statesp import _convert_to_statespace
1515
from control.xferfcn import _convert_to_transfer_function
@@ -906,6 +906,33 @@ def test_printing_mimo(self):
906906
assert isinstance(str(sys), str)
907907
assert isinstance(sys._repr_latex_(), str)
908908

909+
@pytest.mark.parametrize(
910+
"zeros, poles, gain, output",
911+
[([0], [-1], 1, "\n s\n-----\ns + 1\n"),
912+
([-1], [-1], 1, "\ns + 1\n-----\ns + 1\n"),
913+
([-1], [1], 1, "\ns + 1\n-----\ns - 1\n"),
914+
([1], [-1], 1, "\ns - 1\n-----\ns + 1\n"),
915+
([-1], [-1], 2, "\n2 (s + 1)\n---------\n s + 1\n"),
916+
([-1], [-1], 0, "\n0\n-\n1\n"),
917+
([-1], [1j, -1j], 1, "\n s + 1\n-----------------\n(s - 1j) (s + 1j)\n"),
918+
([4j, -4j], [2j, -2j], 2, "\n2 (s - 4j) (s + 4j)\n-------------------\n (s - 2j) (s + 2j)\n"),
919+
([1j, -1j], [-1, -4], 2, "\n2 (s - 1j) (s + 1j)\n-------------------\n (s + 1) (s + 4)\n"),
920+
([1], [-1 + 1j, -1 - 1j], 1, "\n s - 1\n-------------------------\n(s + (1-1j)) (s + (1+1j))\n"),
921+
([1], [1 + 1j, 1 - 1j], 1, "\n s - 1\n-------------------------\n(s - (1+1j)) (s - (1-1j))\n"),
922+
])
923+
def test_printing_zpk(self, zeros, poles, gain, output):
924+
"""Test _tf_polynomial_to_string for constant systems"""
925+
G = zpk(zeros, poles, gain)
926+
print(G)
927+
res = G.to_zpk()
928+
print(res)
929+
assert res == output
930+
931+
def test_printing_zpk_invalid(self):
932+
G = tf([1], [1 + 1j])
933+
with pytest.raises(ValueError, match='complex valued'):
934+
G.to_zpk()
935+
909936
@slycotonly
910937
def test_size_mismatch(self):
911938
"""Test size mismacht"""

control/xferfcn.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -468,6 +468,73 @@ def __str__(self, var=None):
468468

469469
return outstr
470470

471+
def to_zpk(self, var=None):
472+
"""Return string representation of the transfer function as factorized
473+
polynomials.
474+
475+
Examples
476+
--------
477+
>>> from control import tf
478+
>>> G = tf([1],[1, -2, 1])
479+
>>> G.to_zpk()
480+
1
481+
---------------
482+
(s - 1) (s - 1)
483+
484+
"""
485+
486+
mimo = self.ninputs > 1 or self.noutputs > 1
487+
if var is None:
488+
# TODO: replace with standard calls to lti functions
489+
var = 's' if self.dt is None or self.dt == 0 else 'z'
490+
outstr = ""
491+
492+
for i in range(self.ninputs):
493+
for j in range(self.noutputs):
494+
if mimo:
495+
outstr += "\nInput %i to output %i:" % (i + 1, j + 1)
496+
497+
# Convert the numerator and denominator polynomials to strings.
498+
dcgain = self.num[j][i][-1] / self.den[j][i][-1]
499+
500+
num_roots = roots(self.num[j][i]).astype(complex)
501+
den_roots = roots(self.den[j][i]).astype(complex)
502+
503+
polygain = np.prod(num_roots) / np.prod(den_roots)
504+
505+
if abs(polygain) == 0 and abs(dcgain) == 0:
506+
k = 1
507+
else:
508+
k = dcgain/polygain
509+
if not np.isreal(k):
510+
raise ValueError("Transfer function has complex valued gain. "
511+
"Please check polynomials for non-complimentary poles.")
512+
513+
k = np.abs(k)
514+
515+
numstr = _tf_factorized_polynomial_to_string(num_roots, gain=k, var=var)
516+
denstr = _tf_factorized_polynomial_to_string(den_roots, var=var)
517+
518+
# Figure out the length of the separating line
519+
dashcount = max(len(numstr), len(denstr))
520+
dashes = '-' * dashcount
521+
522+
# Center the numerator or denominator
523+
if len(numstr) < dashcount:
524+
numstr = ' ' * ((dashcount - len(numstr)) // 2) + numstr
525+
if len(denstr) < dashcount:
526+
denstr = ' ' * ((dashcount - len(denstr)) // 2) + denstr
527+
528+
outstr += "\n" + numstr + "\n" + dashes + "\n" + denstr + "\n"
529+
530+
# See if this is a discrete time system with specific sampling time
531+
if not (self.dt is None) and type(self.dt) != bool and self.dt > 0:
532+
# TODO: replace with standard calls to lti functions
533+
outstr += "\ndt = " + self.dt.__str__() + "\n"
534+
535+
return outstr
536+
537+
471538
# represent to implement a re-loadable version
472539
def __repr__(self):
473540
"""Print transfer function in loadable form"""
@@ -1323,6 +1390,49 @@ def _tf_polynomial_to_string(coeffs, var='s'):
13231390
return thestr
13241391

13251392

1393+
def _tf_factorized_polynomial_to_string(roots, gain=1, var='s'):
1394+
"""Convert a factorized polynomial to a string"""
1395+
1396+
if roots.size == 0:
1397+
return f"{gain:.4g}"
1398+
1399+
factors = []
1400+
for root in sorted(roots, reverse=True):
1401+
if np.isreal(root):
1402+
if root == 0:
1403+
factor = f"{var}"
1404+
factors.append(factor)
1405+
elif root > 0:
1406+
factor = f"{var} - {np.abs(root):.4g}"
1407+
factors.append(factor)
1408+
else:
1409+
factor = f"{var} + {np.abs(root):.4g}"
1410+
factors.append(factor)
1411+
elif np.isreal(root * 1j):
1412+
if root.imag > 0:
1413+
factor = f"{var} - {np.abs(root):.4g}j"
1414+
factors.append(factor)
1415+
else:
1416+
factor = f"{var} + {np.abs(root):.4g}j"
1417+
factors.append(factor)
1418+
else:
1419+
if root.real > 0:
1420+
factor = f"{var} - ({root:.4g})"
1421+
factors.append(factor)
1422+
else:
1423+
factor = f"{var} + ({-root:.4g})"
1424+
factors.append(factor)
1425+
1426+
1427+
multiplier = ''
1428+
if round(gain, 4) != 1.0:
1429+
multiplier = f"{gain:.4g} "
1430+
1431+
if len(factors) > 1 or multiplier:
1432+
factors = [f"({factor})" for factor in factors]
1433+
1434+
return multiplier + " ".join(factors)
1435+
13261436
def _tf_string_to_latex(thestr, var='s'):
13271437
""" make sure to superscript all digits in a polynomial string
13281438
and convert float coefficients in scientific notation

0 commit comments

Comments
 (0)