69
69
70
70
71
71
# Define module default parameter values
72
- _xferfcn_defaults = {}
72
+ _xferfcn_defaults = {
73
+ 'xferfcn.display_format' : 'poly' ,
74
+ 'xferfcn.floating_point_format' : '.4g'
75
+ }
76
+
77
+ def _float2str (value ):
78
+ _num_format = config .defaults .get ('xferfcn.floating_point_format' , ':.4g' )
79
+ return f"{ value :{_num_format }} "
73
80
74
81
75
82
class TransferFunction (LTI ):
@@ -92,6 +99,10 @@ class TransferFunction(LTI):
92
99
time, positive number is discrete time with specified
93
100
sampling time, None indicates unspecified timebase (either
94
101
continuous or discrete time).
102
+ display_format: None, 'poly' or 'zpk'
103
+ Set the display format used in printing the TransferFunction object.
104
+ Default behavior is polynomial display and can be changed by
105
+ changing config.defaults['xferfcn.display_format'].
95
106
96
107
Attributes
97
108
----------
@@ -198,6 +209,17 @@ def __init__(self, *args, **kwargs):
198
209
#
199
210
# Process keyword arguments
200
211
#
212
+ # During module init, TransferFunction.s and TransferFunction.z
213
+ # get initialized when defaults are not fully initialized yet.
214
+ # Use 'poly' in these cases.
215
+
216
+ self .display_format = kwargs .pop (
217
+ 'display_format' ,
218
+ config .defaults .get ('xferfcn.display_format' , 'poly' ))
219
+
220
+ if self .display_format not in ('poly' , 'zpk' ):
221
+ raise ValueError ("display_format must be 'poly' or 'zpk',"
222
+ " got '%s'" % self .display_format )
201
223
202
224
# Determine if the transfer function is static (needed for dt)
203
225
static = True
@@ -432,22 +454,29 @@ def _truncatecoeff(self):
432
454
[self .num , self .den ] = data
433
455
434
456
def __str__ (self , var = None ):
435
- """String representation of the transfer function."""
457
+ """String representation of the transfer function.
436
458
437
- mimo = self .ninputs > 1 or self .noutputs > 1
459
+ Based on the display_format property, the output will be formatted as
460
+ either polynomials or in zpk form.
461
+ """
462
+ mimo = not self .issiso ()
438
463
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'
464
+ var = 's' if self .isctime () else 'z'
441
465
outstr = ""
442
466
443
- for i in range (self .ninputs ):
444
- for j in range (self .noutputs ):
467
+ for ni in range (self .ninputs ):
468
+ for no in range (self .noutputs ):
445
469
if mimo :
446
- outstr += "\n Input %i to output %i:" % (i + 1 , j + 1 )
470
+ outstr += "\n Input %i to output %i:" % (ni + 1 , no + 1 )
447
471
448
472
# 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 )
473
+ if self .display_format == 'poly' :
474
+ numstr = _tf_polynomial_to_string (self .num [no ][ni ], var = var )
475
+ denstr = _tf_polynomial_to_string (self .den [no ][ni ], var = var )
476
+ elif self .display_format == 'zpk' :
477
+ z , p , k = tf2zpk (self .num [no ][ni ], self .den [no ][ni ])
478
+ numstr = _tf_factorized_polynomial_to_string (z , gain = k , var = var )
479
+ denstr = _tf_factorized_polynomial_to_string (p , var = var )
451
480
452
481
# Figure out the length of the separating line
453
482
dashcount = max (len (numstr ), len (denstr ))
@@ -461,10 +490,9 @@ def __str__(self, var=None):
461
490
462
491
outstr += "\n " + numstr + "\n " + dashes + "\n " + denstr + "\n "
463
492
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 += "\n dt = " + self .dt .__str__ () + "\n "
493
+ # If this is a strict discrete time system, print the sampling time
494
+ if type (self .dt ) != bool and self .isdtime (strict = True ):
495
+ outstr += "\n dt = " + str (self .dt ) + "\n "
468
496
469
497
return outstr
470
498
@@ -485,7 +513,7 @@ def __repr__(self):
485
513
def _repr_latex_ (self , var = None ):
486
514
"""LaTeX representation of transfer function, for Jupyter notebook"""
487
515
488
- mimo = self . ninputs > 1 or self .noutputs > 1
516
+ mimo = not self .issiso ()
489
517
490
518
if var is None :
491
519
# ! TODO: replace with standard calls to lti functions
@@ -496,18 +524,23 @@ def _repr_latex_(self, var=None):
496
524
if mimo :
497
525
out .append (r"\begin{bmatrix}" )
498
526
499
- for i in range (self .noutputs ):
500
- for j in range (self .ninputs ):
527
+ for no in range (self .noutputs ):
528
+ for ni in range (self .ninputs ):
501
529
# Convert the numerator and denominator polynomials to strings.
502
- numstr = _tf_polynomial_to_string (self .num [i ][j ], var = var )
503
- denstr = _tf_polynomial_to_string (self .den [i ][j ], var = var )
530
+ if self .display_format == 'poly' :
531
+ numstr = _tf_polynomial_to_string (self .num [no ][ni ], var = var )
532
+ denstr = _tf_polynomial_to_string (self .den [no ][ni ], var = var )
533
+ elif self .display_format == 'zpk' :
534
+ z , p , k = tf2zpk (self .num [no ][ni ], self .den [no ][ni ])
535
+ numstr = _tf_factorized_polynomial_to_string (z , gain = k , var = var )
536
+ denstr = _tf_factorized_polynomial_to_string (p , var = var )
504
537
505
538
numstr = _tf_string_to_latex (numstr , var = var )
506
539
denstr = _tf_string_to_latex (denstr , var = var )
507
540
508
541
out += [r"\frac{" , numstr , "}{" , denstr , "}" ]
509
542
510
- if mimo and j < self .noutputs - 1 :
543
+ if mimo and ni < self .ninputs - 1 :
511
544
out .append ("&" )
512
545
513
546
if mimo :
@@ -1285,7 +1318,7 @@ def _tf_polynomial_to_string(coeffs, var='s'):
1285
1318
N = len (coeffs ) - 1
1286
1319
1287
1320
for k in range (len (coeffs )):
1288
- coefstr = '%.4g' % abs (coeffs [k ])
1321
+ coefstr = _float2str ( abs (coeffs [k ]) )
1289
1322
power = (N - k )
1290
1323
if power == 0 :
1291
1324
if coefstr != '0' :
@@ -1323,6 +1356,48 @@ def _tf_polynomial_to_string(coeffs, var='s'):
1323
1356
return thestr
1324
1357
1325
1358
1359
+ def _tf_factorized_polynomial_to_string (roots , gain = 1 , var = 's' ):
1360
+ """Convert a factorized polynomial to a string"""
1361
+
1362
+ if roots .size == 0 :
1363
+ return _float2str (gain )
1364
+
1365
+ factors = []
1366
+ for root in sorted (roots , reverse = True ):
1367
+ if np .isreal (root ):
1368
+ if root == 0 :
1369
+ factor = f"{ var } "
1370
+ factors .append (factor )
1371
+ elif root > 0 :
1372
+ factor = f"{ var } - { _float2str (np .abs (root ))} "
1373
+ factors .append (factor )
1374
+ else :
1375
+ factor = f"{ var } + { _float2str (np .abs (root ))} "
1376
+ factors .append (factor )
1377
+ elif np .isreal (root * 1j ):
1378
+ if root .imag > 0 :
1379
+ factor = f"{ var } - { _float2str (np .abs (root ))} j"
1380
+ factors .append (factor )
1381
+ else :
1382
+ factor = f"{ var } + { _float2str (np .abs (root ))} j"
1383
+ factors .append (factor )
1384
+ else :
1385
+ if root .real > 0 :
1386
+ factor = f"{ var } - ({ _float2str (root )} )"
1387
+ factors .append (factor )
1388
+ else :
1389
+ factor = f"{ var } + ({ _float2str (- root )} )"
1390
+ factors .append (factor )
1391
+
1392
+ multiplier = ''
1393
+ if round (gain , 4 ) != 1.0 :
1394
+ multiplier = _float2str (gain ) + " "
1395
+
1396
+ if len (factors ) > 1 or multiplier :
1397
+ factors = [f"({ factor } )" for factor in factors ]
1398
+
1399
+ return multiplier + " " .join (factors )
1400
+
1326
1401
def _tf_string_to_latex (thestr , var = 's' ):
1327
1402
""" make sure to superscript all digits in a polynomial string
1328
1403
and convert float coefficients in scientific notation
@@ -1486,6 +1561,10 @@ def tf(*args, **kwargs):
1486
1561
Polynomial coefficients of the numerator
1487
1562
den: array_like, or list of list of array_like
1488
1563
Polynomial coefficients of the denominator
1564
+ display_format: None, 'poly' or 'zpk'
1565
+ Set the display format used in printing the TransferFunction object.
1566
+ Default behavior is polynomial display and can be changed by
1567
+ changing config.defaults['xferfcn.display_format']..
1489
1568
1490
1569
Returns
1491
1570
-------
@@ -1538,7 +1617,7 @@ def tf(*args, **kwargs):
1538
1617
1539
1618
>>> # Create a variable 's' to allow algebra operations for SISO systems
1540
1619
>>> s = tf('s')
1541
- >>> G = (s + 1)/ (s**2 + 2*s + 1)
1620
+ >>> G = (s + 1) / (s**2 + 2*s + 1)
1542
1621
1543
1622
>>> # Convert a StateSpace to a TransferFunction object.
1544
1623
>>> sys_ss = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.")
@@ -1609,12 +1688,24 @@ def zpk(zeros, poles, gain, *args, **kwargs):
1609
1688
name : string, optional
1610
1689
System name (used for specifying signals). If unspecified, a generic
1611
1690
name <sys[id]> is generated with a unique integer id.
1691
+ display_format: None, 'poly' or 'zpk'
1692
+ Set the display format used in printing the TransferFunction object.
1693
+ Default behavior is polynomial display and can be changed by
1694
+ changing config.defaults['xferfcn.display_format'].
1612
1695
1613
1696
Returns
1614
1697
-------
1615
1698
out: :class:`TransferFunction`
1616
1699
Transfer function with given zeros, poles, and gain.
1617
1700
1701
+ Examples
1702
+ --------
1703
+ >>> from control import tf
1704
+ >>> G = zpk([1],[2, 3], gain=1, display_format='zpk')
1705
+ >>> G
1706
+ s - 1
1707
+ ---------------
1708
+ (s - 2) (s - 3)
1618
1709
"""
1619
1710
num , den = zpk2tf (zeros , poles , gain )
1620
1711
return TransferFunction (num , den , * args , ** kwargs )
0 commit comments