Skip to content

Commit d9b4dbf

Browse files
committed
add iosys_repr and set default __repr__ to short form
1 parent a4a5b55 commit d9b4dbf

File tree

8 files changed

+145
-21
lines changed

8 files changed

+145
-21
lines changed

control/frdata.py

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

393393
def __repr__(self):
394-
"""Loadable string representation,
394+
return self.iosys_repr(format=self.repr_format)
395+
396+
def iosys_repr(self, format='loadable'):
397+
"""Return representation of a transfer function.
398+
399+
Parameters
400+
----------
401+
format : str
402+
Format to use in creating the representation:
403+
404+
* 'iosys' : <FrequencyResponseData:sysname:[inputs]->[outputs]
405+
* 'loadable' : FrequencyResponseData(response, omega[, dt[, ...]])
406+
407+
Returns
408+
-------
409+
str
410+
String representing the transfer function.
411+
412+
Notes
413+
-----
414+
By default, the representation for a frequency response is set to
415+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
416+
I/O systems or set the `repr_format` attribute for a single system.
395417
396-
limited for number of data points.
397418
"""
419+
if format == 'iosys':
420+
return super().__repr__()
421+
elif format != 'loadable':
422+
raise ValueError(f"unknown format '{format}'")
423+
424+
# Loadable format
398425
out = "FrequencyResponseData(\n{d},\n{w}{smooth}".format(
399426
d=repr(self.fresp), w=repr(self.omega),
400427
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

@@ -189,6 +190,8 @@ def __init__(
189190
# Process timebase: if not given use default, but allow None as value
190191
self.dt = _process_dt_keyword(kwargs)
191192

193+
self._repr_format = kwargs.pop('repr_format', None)
194+
192195
# Make sure there were no other keywords
193196
if kwargs:
194197
raise TypeError("unrecognized keywords: ", str(kwargs))
@@ -247,6 +250,20 @@ def __repr__(self):
247250
return f'<{self.__class__.__name__}:{self.name}:' + \
248251
f'{list(self.input_labels)}->{list(self.output_labels)}>'
249252

253+
def iosys_repr(self, format=None):
254+
raise NotImplementedError(
255+
f"`iosys_repr` is not implemented for {self.__class__}")
256+
257+
@property
258+
def repr_format(self):
259+
"""Set the string representation format ('iosys' or 'loadable')."""
260+
return self._repr_format if self._repr_format is not None \
261+
else config.defaults['iosys.repr_format']
262+
263+
@repr_format.setter
264+
def repr_format(self, value):
265+
self._repr_format = value
266+
250267
def __str__(self):
251268
"""String representation of an input/output object"""
252269
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
@@ -428,9 +428,38 @@ def __str__(self):
428428
string += f"\ndt = {self.dt}\n"
429429
return string
430430

431-
# represent to implement a re-loadable version
432431
def __repr__(self):
433-
"""Print state-space system in loadable form."""
432+
return self.iosys_repr(format=self.repr_format)
433+
434+
def iosys_repr(self, format='loadable'):
435+
"""Return representation of a state sapce system.
436+
437+
Parameters
438+
----------
439+
format : str
440+
Format to use in creating the representation:
441+
442+
* 'iosys' : <TransferFunction:sysname:[inputs]->[outputs]
443+
* 'loadable' : StateSpace(A, B, C, D[, dt[, ...]])
444+
445+
Returns
446+
-------
447+
str
448+
String representing the transfer function.
449+
450+
Notes
451+
-----
452+
By default, the representation for a state space system is set to
453+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
454+
I/O systems or set the `repr_format` attribute for a single system.
455+
456+
"""
457+
if format == 'iosys':
458+
return super().__repr__()
459+
elif format != 'loadable':
460+
raise ValueError(f"unknown format '{format}'")
461+
462+
# Loadable format
434463
out = "StateSpace(\n{A},\n{B},\n{C},\n{D}".format(
435464
A=self.A.__repr__(), B=self.B.__repr__(),
436465
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
@@ -2314,7 +2314,8 @@ def test_signal_indexing():
23142314
'long_state_1', 'long_state_2', 'long_state_3']},
23152315
r"[\n]name='.*', states=\[.*\],[\n]outputs=3, inputs=2\)", r"dt"),
23162316
])
2317-
def test_system_repr(fcn, spec, expected, missing):
2317+
@pytest.mark.parametrize("format", ['iosys', 'loadable'])
2318+
def test_loadable_system_repr(fcn, spec, expected, missing, format):
23182319
spec['outputs'] = spec.get('outputs', 3)
23192320
spec['inputs'] = spec.get('inputs', 2)
23202321
if fcn is ct.ss:
@@ -2327,11 +2328,31 @@ def test_system_repr(fcn, spec, expected, missing):
23272328
sys = fcn(sys, omega, name=spec.get('name'))
23282329
case ct.tf:
23292330
sys = fcn(sys, name=spec.get('name'))
2330-
23312331
assert sys.shape == (sys.noutputs, sys.ninputs)
23322332

2333+
# Construct the 'iosys' format
2334+
iosys_expected = f"<{sys.__class__.__name__}:{sys.name}:" \
2335+
f"{sys.input_labels}->{sys.output_labels}>"
2336+
2337+
# Make sure the default format is OK
2338+
out = repr(sys)
2339+
if ct.config.defaults['iosys.repr_format'] == 'iosys':
2340+
assert out == iosys_expected
2341+
else:
2342+
assert re.search(expected, out) != None
2343+
2344+
# Now set the format to the given type and make sure things look right
2345+
sys.repr_format = format
23332346
out = repr(sys)
2334-
assert re.search(expected, out) != None
2347+
if format == 'loadable':
2348+
assert re.search(expected, out) != None
23352349

2336-
if missing is not None:
2337-
assert re.search(missing, out) is None
2350+
if missing is not None:
2351+
assert re.search(missing, out) is None
2352+
2353+
# Make sure we can change the default format back to 'iosys'
2354+
sys.repr_format = None
2355+
2356+
# Test 'iosys', either set explicitly or via config.defaults
2357+
out = repr(sys)
2358+
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
@@ -1121,15 +1121,16 @@ def test_latex_repr(self):
11211121
"dt=0.5,\n"
11221122
"outputs=2, inputs=2)"),
11231123
])
1124-
def test_repr(self, Hargs, ref):
1124+
def test_loadable_repr(self, Hargs, ref):
11251125
"""Test __repr__ printout."""
11261126
H = TransferFunction(*Hargs)
11271127

1128-
assert repr(H) == ref
1128+
rep = H.iosys_repr(format='loadable')
1129+
assert rep == ref
11291130

11301131
# and reading back
11311132
array = np.array # noqa
1132-
H2 = eval(H.__repr__())
1133+
H2 = eval(rep)
11331134
for p in range(len(H.num)):
11341135
for m in range(len(H.num[0])):
11351136
np.testing.assert_array_almost_equal(

control/xferfcn.py

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

461461
return outstr
462462

463-
# represent to implement a re-loadable version
464463
def __repr__(self):
465-
"""Print transfer function in loadable form."""
464+
return self.iosys_repr(format=self.repr_format)
465+
466+
def iosys_repr(self, format='loadable'):
467+
"""Return representation of a transfer function.
468+
469+
Parameters
470+
----------
471+
format : str
472+
Format to use in creating the representation:
473+
474+
* 'iosys' : <TransferFunction:sysname:[inputs]->[outputs]
475+
* 'loadable' : TransferFunction(num, den[, dt[, ...]])
476+
477+
Returns
478+
-------
479+
str
480+
String representing the transfer function.
481+
482+
Notes
483+
-----
484+
By default, the representation for a transfer function is set to
485+
'iosys'. Set config.defaults['iosys.repr_format'] to change for all
486+
I/O systems or set the `repr_format` attribute for a single system.
487+
488+
"""
489+
if format == 'iosys':
490+
return super().__repr__()
491+
elif format != 'loadable':
492+
raise ValueError(f"unknown format '{format}'")
493+
494+
# Loadable format
466495
if self.issiso():
467496
out = "TransferFunction(\n{num},\n{den}".format(
468497
num=self._num[0, 0].__repr__(), den=self._den[0, 0].__repr__())

0 commit comments

Comments
 (0)