Skip to content

Commit 93f7b46

Browse files
committed
Rework after review
1 parent 07a859d commit 93f7b46

3 files changed

Lines changed: 53 additions & 94 deletions

File tree

control/tests/xferfcn_test.py

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ def test_printing_mimo(self):
967967
def test_printing_zpk(self, zeros, poles, gain, output):
968968
"""Test _tf_polynomial_to_string for constant systems"""
969969
G = zpk(zeros, poles, gain)
970-
res = G.to_zpk()
970+
res = str(G)
971971
assert res == output
972972

973973
@pytest.mark.parametrize(
@@ -996,15 +996,10 @@ def test_printing_zpk(self, zeros, poles, gain, output):
996996
'(s - 5) (s + 4)\n'))])
997997
def test_printing_zpk_mimo(self, num, den, output):
998998
"""Test _tf_polynomial_to_string for constant systems"""
999-
G = tf(num, den)
1000-
res = G.to_zpk()
999+
G = tf(num, den, display_format='zpk')
1000+
res = str(G)
10011001
assert res == output
10021002

1003-
def test_printing_zpk_invalid(self):
1004-
G = tf([1], [1 + 1j])
1005-
with pytest.raises(ValueError, match='complex valued'):
1006-
G.to_zpk()
1007-
10081003
@slycotonly
10091004
def test_size_mismatch(self):
10101005
"""Test size mismacht"""

control/xferfcn.py

Lines changed: 49 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ class TransferFunction(LTI):
9292
time, positive number is discrete time with specified
9393
sampling time, None indicates unspecified timebase (either
9494
continuous or discrete time).
95+
display_format: None, 'poly' or 'zpk'
96+
Set the display format used in printing the TransferFunction object.
97+
Default behavior is polynomial display.
9598
9699
Attributes
97100
----------
@@ -149,7 +152,7 @@ class TransferFunction(LTI):
149152
# Give TransferFunction._rmul_() priority for ndarray * TransferFunction
150153
__array_priority__ = 11 # override ndarray and matrix types
151154

152-
def __init__(self, *args, **kwargs):
155+
def __init__(self, *args, display_format=None, **kwargs):
153156
"""TransferFunction(num, den[, dt])
154157
155158
Construct a transfer function.
@@ -198,6 +201,14 @@ def __init__(self, *args, **kwargs):
198201
#
199202
# Process keyword arguments
200203
#
204+
if display_format is None:
205+
display_format = 'poly'
206+
207+
if display_format not in ('poly', 'zpk'):
208+
raise ValueError("display_format must be 'poly' or 'zpk',"
209+
" got '%s'" % display_format)
210+
211+
self.display_format = display_format
201212

202213
# Determine if the transfer function is static (needed for dt)
203214
static = True
@@ -432,61 +443,16 @@ def _truncatecoeff(self):
432443
[self.num, self.den] = data
433444

434445
def __str__(self, var=None):
435-
"""String representation of the transfer function."""
436-
437-
mimo = self.ninputs > 1 or self.noutputs > 1
438-
if var is None:
439-
# TODO: replace with standard calls to lti functions
440-
var = 's' if self.dt is None or self.dt == 0 else 'z'
441-
outstr = ""
442-
443-
for i in range(self.ninputs):
444-
for j in range(self.noutputs):
445-
if mimo:
446-
outstr += "\nInput %i to output %i:" % (i + 1, j + 1)
447-
448-
# Convert the numerator and denominator polynomials to strings.
449-
numstr = _tf_polynomial_to_string(self.num[j][i], var=var)
450-
denstr = _tf_polynomial_to_string(self.den[j][i], var=var)
446+
"""String representation of the transfer function.
451447
452-
# Figure out the length of the separating line
453-
dashcount = max(len(numstr), len(denstr))
454-
dashes = '-' * dashcount
455-
456-
# Center the numerator or denominator
457-
if len(numstr) < dashcount:
458-
numstr = ' ' * ((dashcount - len(numstr)) // 2) + numstr
459-
if len(denstr) < dashcount:
460-
denstr = ' ' * ((dashcount - len(denstr)) // 2) + denstr
461-
462-
outstr += "\n" + numstr + "\n" + dashes + "\n" + denstr + "\n"
463-
464-
# See if this is a discrete time system with specific sampling time
465-
if not (self.dt is None) and type(self.dt) != bool and self.dt > 0:
466-
# TODO: replace with standard calls to lti functions
467-
outstr += "\ndt = " + self.dt.__str__() + "\n"
468-
469-
return outstr
470-
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)
448+
Based on the display_format property, the output will be formatted as
449+
either polynomials or in zpk form.
483450
484451
"""
485452

486-
mimo = self.ninputs > 1 or self.noutputs > 1
453+
mimo = not self.issiso()
487454
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'
455+
var = 's' if self.isctime() else 'z'
490456
outstr = ""
491457

492458
for i in range(self.ninputs):
@@ -495,34 +461,13 @@ def to_zpk(self, var=None):
495461
outstr += "\nInput %i to output %i:" % (i + 1, j + 1)
496462

497463
# 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-
# Round imaginary part down to zero for values close to
506-
# precision to prevent small errors to mess up things.
507-
polygain = complex(polygain.real,
508-
round(polygain.imag, 12))
509-
510-
if abs(polygain) == 0 and abs(dcgain) == 0:
511-
k = 1
512-
else:
513-
if abs(polygain) == 0:
514-
raise ValueError(
515-
f"Transfer function has infinite gain. "
516-
"Please check polynomials.")
517-
k = dcgain/polygain
518-
if not np.isreal(k):
519-
raise ValueError(f"Transfer function has complex valued gain (k = {k}). "
520-
"Please check polynomials for non-complimentary poles.")
521-
522-
k = np.abs(k)
523-
524-
numstr = _tf_factorized_polynomial_to_string(num_roots, gain=k, var=var)
525-
denstr = _tf_factorized_polynomial_to_string(den_roots, var=var)
464+
if self.display_format == 'poly':
465+
numstr = _tf_polynomial_to_string(self.num[j][i], var=var)
466+
denstr = _tf_polynomial_to_string(self.den[j][i], var=var)
467+
elif self.display_format == 'zpk':
468+
z, p, k = tf2zpk(self.num[j][i], self.den[j][i])
469+
numstr = _tf_factorized_polynomial_to_string(z, gain=k, var=var)
470+
denstr = _tf_factorized_polynomial_to_string(p, var=var)
526471

527472
# Figure out the length of the separating line
528473
dashcount = max(len(numstr), len(denstr))
@@ -538,8 +483,7 @@ def to_zpk(self, var=None):
538483

539484
# See if this is a discrete time system with specific sampling time
540485
if not (self.dt is None) and type(self.dt) != bool and self.dt > 0:
541-
# TODO: replace with standard calls to lti functions
542-
outstr += "\ndt = " + self.dt.__str__() + "\n"
486+
outstr += "\ndt = " + str(self.dt) + "\n"
543487

544488
return outstr
545489

@@ -575,8 +519,13 @@ def _repr_latex_(self, var=None):
575519
for i in range(self.noutputs):
576520
for j in range(self.ninputs):
577521
# Convert the numerator and denominator polynomials to strings.
578-
numstr = _tf_polynomial_to_string(self.num[i][j], var=var)
579-
denstr = _tf_polynomial_to_string(self.den[i][j], var=var)
522+
if self.display_format == 'poly':
523+
numstr = _tf_polynomial_to_string(self.num[j][i], var=var)
524+
denstr = _tf_polynomial_to_string(self.den[j][i], var=var)
525+
elif self.display_format == 'zpk':
526+
z, p, k = tf2zpk(self.num[j][i], self.den[j][i])
527+
numstr = _tf_factorized_polynomial_to_string(z, gain=k, var=var)
528+
denstr = _tf_factorized_polynomial_to_string(p, var=var)
580529

581530
numstr = _tf_string_to_latex(numstr, var=var)
582531
denstr = _tf_string_to_latex(denstr, var=var)
@@ -1432,7 +1381,6 @@ def _tf_factorized_polynomial_to_string(roots, gain=1, var='s'):
14321381
factor = f"{var} + ({-root:.4g})"
14331382
factors.append(factor)
14341383

1435-
14361384
multiplier = ''
14371385
if round(gain, 4) != 1.0:
14381386
multiplier = f"{gain:.4g} "
@@ -1605,6 +1553,9 @@ def tf(*args, **kwargs):
16051553
Polynomial coefficients of the numerator
16061554
den: array_like, or list of list of array_like
16071555
Polynomial coefficients of the denominator
1556+
display_format: None, 'poly' or 'zpk'
1557+
Set the display format used in printing the TransferFunction object.
1558+
Default behavior is polynomial display.
16081559
16091560
Returns
16101561
-------
@@ -1657,7 +1608,7 @@ def tf(*args, **kwargs):
16571608
16581609
>>> # Create a variable 's' to allow algebra operations for SISO systems
16591610
>>> s = tf('s')
1660-
>>> G = (s + 1)/(s**2 + 2*s + 1)
1611+
>>> G = (s + 1) / (s**2 + 2*s + 1)
16611612
16621613
>>> # Convert a StateSpace to a TransferFunction object.
16631614
>>> sys_ss = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
@@ -1728,14 +1679,27 @@ def zpk(zeros, poles, gain, *args, **kwargs):
17281679
name : string, optional
17291680
System name (used for specifying signals). If unspecified, a generic
17301681
name <sys[id]> is generated with a unique integer id.
1682+
display_format: None, 'poly' or 'zpk'
1683+
Set the display format used in printing the TransferFunction object.
1684+
Default behavior is zpk display.
17311685
17321686
Returns
17331687
-------
17341688
out: :class:`TransferFunction`
17351689
Transfer function with given zeros, poles, and gain.
17361690
1691+
Examples
1692+
--------
1693+
>>> from control import tf
1694+
>>> G = zpk([1],[2, 3], gain=6)
1695+
>>> G
1696+
s - 1
1697+
---------------
1698+
(s - 2) (s - )
17371699
"""
17381700
num, den = zpk2tf(zeros, poles, gain)
1701+
if 'display_format' not in kwargs:
1702+
kwargs['display_format'] = 'zpk'
17391703
return TransferFunction(num, den, *args, **kwargs)
17401704

17411705

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ source = "https://github.com/python-control/python-control"
5353
write_to = "control/_version.py"
5454

5555
[tool.pytest.ini_options]
56-
addopts = "-ra"
56+
addopts = "-ra --ff -x"
5757
filterwarnings = [
5858
"error:.*matrix subclass:PendingDeprecationWarning",
5959
]

0 commit comments

Comments
 (0)