Skip to content

Commit 85772f5

Browse files
committed
add iosys_repr and set default __repr__ to short form
1 parent b0397e0 commit 85772f5

8 files changed

Lines changed: 145 additions & 21 deletions

File tree

control/frdata.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,37 @@ def __str__(self):
416416
return '\n'.join(outstr)
417417

418418
def __repr__(self):
419-
"""Loadable string representation,
419+
return self.iosys_repr(format=self.repr_format)
420+
421+
def iosys_repr(self, format='loadable'):
422+
"""Return representation of a transfer function.
423+
424+
Parameters
425+
----------
426+
format : str
427+
Format to use in creating the representation:
428+
429+
* 'iosys' : <FrequencyResponseData:sysname:[inputs]->[outputs]
430+
* 'loadable' : FrequencyResponseData(response, omega[, dt[, ...]])
431+
432+
Returns
433+
-------
434+
str
435+
String representing the transfer function.
436+
437+
Notes
438+
-----
439+
By default, the representation for a frequency response is set to
440+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
441+
I/O systems or set the `repr_format` attribute for a single system.
420442
421-
limited for number of data points.
422443
"""
444+
if format == 'iosys':
445+
return super().__repr__()
446+
elif format != 'loadable':
447+
raise ValueError(f"unknown format '{format}'")
448+
449+
# Loadable format
423450
out = "FrequencyResponseData(\n{d},\n{w}{smooth}".format(
424451
d=repr(self.fresp), w=repr(self.omega),
425452
smooth=(self._ifunc and ", smooth=True") or "")

control/iosys.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
'iosys.indexed_system_name_suffix': '$indexed',
3232
'iosys.converted_system_name_prefix': '',
3333
'iosys.converted_system_name_suffix': '$converted',
34+
'iosys.repr_format': 'iosys',
3435
}
3536

3637

@@ -183,6 +184,8 @@ def __init__(
183184
# Process timebase: if not given use default, but allow None as value
184185
self.dt = _process_dt_keyword(kwargs)
185186

187+
self._repr_format = kwargs.pop('repr_format', None)
188+
186189
# Make sure there were no other keywords
187190
if kwargs:
188191
raise TypeError("unrecognized keywords: ", str(kwargs))
@@ -241,6 +244,20 @@ def __repr__(self):
241244
return f'<{self.__class__.__name__}:{self.name}:' + \
242245
f'{list(self.input_labels)}->{list(self.output_labels)}>'
243246

247+
def iosys_repr(self, format=None):
248+
raise NotImplementedError(
249+
f"`iosys_repr` is not implemented for {self.__class__}")
250+
251+
@property
252+
def repr_format(self):
253+
"""Set the string representation format ('iosys' or 'loadable')."""
254+
return self._repr_format if self._repr_format is not None \
255+
else config.defaults['iosys.repr_format']
256+
257+
@repr_format.setter
258+
def repr_format(self, value):
259+
self._repr_format = value
260+
244261
def __str__(self):
245262
"""String representation of an input/output object"""
246263
str = f"<{self.__class__.__name__}>: {self.name}\n"

control/statesp.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,9 +389,38 @@ def __str__(self):
389389
string += f"\ndt = {self.dt}\n"
390390
return string
391391

392-
# represent to implement a re-loadable version
393392
def __repr__(self):
394-
"""Print state-space system in loadable form."""
393+
return self.iosys_repr(format=self.repr_format)
394+
395+
def iosys_repr(self, format='loadable'):
396+
"""Return representation of a state sapce system.
397+
398+
Parameters
399+
----------
400+
format : str
401+
Format to use in creating the representation:
402+
403+
* 'iosys' : <TransferFunction:sysname:[inputs]->[outputs]
404+
* 'loadable' : StateSpace(A, B, C, D[, dt[, ...]])
405+
406+
Returns
407+
-------
408+
str
409+
String representing the transfer function.
410+
411+
Notes
412+
-----
413+
By default, the representation for a state space system is set to
414+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
415+
I/O systems or set the `repr_format` attribute for a single system.
416+
417+
"""
418+
if format == 'iosys':
419+
return super().__repr__()
420+
elif format != 'loadable':
421+
raise ValueError(f"unknown format '{format}'")
422+
423+
# Loadable format
395424
out = "StateSpace(\n{A},\n{B},\n{C},\n{D}".format(
396425
A=self.A.__repr__(), B=self.B.__repr__(),
397426
C=self.C.__repr__(), D=self.D.__repr__())

control/tests/frd_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -474,14 +474,14 @@ def test_repr_str(self):
474474
sysm = ct.frd(
475475
np.matmul(array([[1], [2]]), sys0.fresp), sys0.omega, name='sysm')
476476

477-
assert repr(sys0) == ref0
478-
assert repr(sys1) == ref1
477+
assert sys0.iosys_repr(format='loadable') == ref0
478+
assert sys1.iosys_repr(format='loadable') == ref1
479479

480-
sys0r = eval(repr(sys0))
480+
sys0r = eval(sys0.iosys_repr(format='loadable'))
481481
np.testing.assert_array_almost_equal(sys0r.fresp, sys0.fresp)
482482
np.testing.assert_array_almost_equal(sys0r.omega, sys0.omega)
483483

484-
sys1r = eval(repr(sys1))
484+
sys1r = eval(sys1.iosys_repr(format='loadable'))
485485
np.testing.assert_array_almost_equal(sys1r.fresp, sys1.fresp)
486486
np.testing.assert_array_almost_equal(sys1r.omega, sys1.omega)
487487
assert(sys1._ifunc is not None)

control/tests/iosys_test.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2369,7 +2369,8 @@ def test_signal_prefixing(fcn):
23692369
'long_state_1', 'long_state_2', 'long_state_3']},
23702370
r"[\n]name='.*', states=\[.*\],[\n]outputs=3, inputs=2\)", r"dt"),
23712371
])
2372-
def test_system_repr(fcn, spec, expected, missing):
2372+
@pytest.mark.parametrize("format", ['iosys', 'loadable'])
2373+
def test_loadable_system_repr(fcn, spec, expected, missing, format):
23732374
spec['outputs'] = spec.get('outputs', 3)
23742375
spec['inputs'] = spec.get('inputs', 2)
23752376
if fcn is ct.ss:
@@ -2382,11 +2383,31 @@ def test_system_repr(fcn, spec, expected, missing):
23822383
sys = fcn(sys, omega, name=spec.get('name'))
23832384
case ct.tf:
23842385
sys = fcn(sys, name=spec.get('name'))
2385-
23862386
assert sys.shape == (sys.noutputs, sys.ninputs)
23872387

2388+
# Construct the 'iosys' format
2389+
iosys_expected = f"<{sys.__class__.__name__}:{sys.name}:" \
2390+
f"{sys.input_labels}->{sys.output_labels}>"
2391+
2392+
# Make sure the default format is OK
2393+
out = repr(sys)
2394+
if ct.config.defaults['iosys.repr_format'] == 'iosys':
2395+
assert out == iosys_expected
2396+
else:
2397+
assert re.search(expected, out) != None
2398+
2399+
# Now set the format to the given type and make sure things look right
2400+
sys.repr_format = format
23882401
out = repr(sys)
2389-
assert re.search(expected, out) != None
2402+
if format == 'loadable':
2403+
assert re.search(expected, out) != None
23902404

2391-
if missing is not None:
2392-
assert re.search(missing, out) is None
2405+
if missing is not None:
2406+
assert re.search(missing, out) is None
2407+
2408+
# Make sure we can change the default format back to 'iosys'
2409+
sys.repr_format = None
2410+
2411+
# Test 'iosys', either set explicitly or via config.defaults
2412+
out = repr(sys)
2413+
assert out == iosys_expected

control/tests/statesp_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,12 +748,12 @@ def test_repr(self, sys322):
748748
"array([[-2., 4.],",
749749
" [ 0., 1.]]){dt},",
750750
"name='sys322', states=3, outputs=2, inputs=2)"])
751-
assert repr(sys322) == ref322.format(dt='')
751+
assert sys322.iosys_repr(format='loadable') == ref322.format(dt='')
752752
sysd = StateSpace(sys322.A, sys322.B,
753753
sys322.C, sys322.D, 0.4)
754-
assert repr(sysd), ref322.format(dt="\ndt=0.4")
754+
assert sysd.iosys_repr(format='loadable'), ref322.format(dt="\ndt=0.4")
755755
array = np.array # noqa
756-
sysd2 = eval(repr(sysd))
756+
sysd2 = eval(sysd.iosys_repr(format='loadable'))
757757
np.testing.assert_allclose(sysd.A, sysd2.A)
758758
np.testing.assert_allclose(sysd.B, sysd2.B)
759759
np.testing.assert_allclose(sysd.C, sysd2.C)

control/tests/xferfcn_test.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,15 +1115,16 @@ def test_latex_repr(self):
11151115
"dt=0.5,\n"
11161116
"outputs=2, inputs=2)"),
11171117
])
1118-
def test_repr(self, Hargs, ref):
1118+
def test_loadable_repr(self, Hargs, ref):
11191119
"""Test __repr__ printout."""
11201120
H = TransferFunction(*Hargs)
11211121

1122-
assert repr(H) == ref
1122+
rep = H.iosys_repr(format='loadable')
1123+
assert rep == ref
11231124

11241125
# and reading back
11251126
array = np.array # noqa
1126-
H2 = eval(H.__repr__())
1127+
H2 = eval(rep)
11271128
for p in range(len(H.num)):
11281129
for m in range(len(H.num[0])):
11291130
np.testing.assert_array_almost_equal(

control/xferfcn.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -488,9 +488,38 @@ def __str__(self, var=None):
488488

489489
return outstr
490490

491-
# represent to implement a re-loadable version
492491
def __repr__(self):
493-
"""Print transfer function in loadable form."""
492+
return self.iosys_repr(format=self.repr_format)
493+
494+
def iosys_repr(self, format='loadable'):
495+
"""Return representation of a transfer function.
496+
497+
Parameters
498+
----------
499+
format : str
500+
Format to use in creating the representation:
501+
502+
* 'iosys' : <TransferFunction:sysname:[inputs]->[outputs]
503+
* 'loadable' : TransferFunction(num, den[, dt[, ...]])
504+
505+
Returns
506+
-------
507+
str
508+
String representing the transfer function.
509+
510+
Notes
511+
-----
512+
By default, the representation for a transfer function is set to
513+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
514+
I/O systems or set the `repr_format` attribute for a single system.
515+
516+
"""
517+
if format == 'iosys':
518+
return super().__repr__()
519+
elif format != 'loadable':
520+
raise ValueError(f"unknown format '{format}'")
521+
522+
# Loadable format
494523
if self.issiso():
495524
out = "TransferFunction(\n{num},\n{den}".format(
496525
num=self.num[0][0].__repr__(), den=self.den[0][0].__repr__())

0 commit comments

Comments
 (0)