Skip to content

Commit 5005159

Browse files
authored
implement __repr__ for tf, ss, and frd (#416)
* implement __repr__ for tf, ss, and frd * use np.matmul for compatibility with python 2.7
1 parent 8366806 commit 5005159

6 files changed

Lines changed: 143 additions & 8 deletions

File tree

control/frdata.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,14 @@ def __str__(self):
161161
"""String representation of the transfer function."""
162162

163163
mimo = self.inputs > 1 or self.outputs > 1
164-
outstr = ['frequency response data ']
164+
outstr = ['Frequency response data']
165165

166166
mt, pt, wt = self.freqresp(self.omega)
167167
for i in range(self.inputs):
168168
for j in range(self.outputs):
169169
if mimo:
170170
outstr.append("Input %i to output %i:" % (i + 1, j + 1))
171-
outstr.append('Freq [rad/s] Response ')
171+
outstr.append('Freq [rad/s] Response')
172172
outstr.append('------------ ---------------------')
173173
outstr.extend(
174174
['%12.3f %10.4g%+10.4gj' % (w, m, p)
@@ -177,6 +177,15 @@ def __str__(self):
177177

178178
return '\n'.join(outstr)
179179

180+
def __repr__(self):
181+
"""Loadable string representation,
182+
183+
limited for number of data points.
184+
"""
185+
return "FrequencyResponseData({d}, {w}{smooth})".format(
186+
d=repr(self.fresp), w=repr(self.omega),
187+
smooth=(self.ifunc and ", smooth=True") or "")
188+
180189
def __neg__(self):
181190
"""Negate a transfer function."""
182191

control/statesp.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,8 +283,14 @@ def __str__(self):
283283
string += "\ndt = " + self.dt.__str__() + "\n"
284284
return string
285285

286-
# represent as string, makes display work for IPython
287-
__repr__ = __str__
286+
# represent to implement a re-loadable version
287+
# TODO: remove the conversion to array when matrix is no longer used
288+
def __repr__(self):
289+
"""Print state-space system in loadable form."""
290+
return "StateSpace({A}, {B}, {C}, {D}{dt})".format(
291+
A=asarray(self.A).__repr__(), B=asarray(self.B).__repr__(),
292+
C=asarray(self.C).__repr__(), D=asarray(self.D).__repr__(),
293+
dt=(isdtime(self, strict=True) and ", {}".format(self.dt)) or '')
288294

289295
# Negation of a system
290296
def __neg__(self):

control/tests/frd_test.py

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import control as ct
1111
from control.statesp import StateSpace
1212
from control.xferfcn import TransferFunction
13-
from control.frdata import FRD, _convertToFRD
13+
from control.frdata import FRD, _convertToFRD, FrequencyResponseData
1414
from control import bdalg
1515
from control import freqplot
1616
from control.exception import slycot_check
@@ -414,6 +414,56 @@ def test_evalfr_deprecated(self):
414414
# Make sure that we get a pending deprecation warning
415415
self.assertRaises(PendingDeprecationWarning, frd_tf.evalfr, 1.)
416416

417-
417+
def test_repr_str(self):
418+
# repr printing
419+
array = np.array
420+
sys0 = FrequencyResponseData([1.0, 0.9+0.1j, 0.1+2j, 0.05+3j],
421+
[0.1, 1.0, 10.0, 100.0])
422+
sys1 = FrequencyResponseData(sys0.fresp, sys0.omega, smooth=True)
423+
ref0 = "FrequencyResponseData(" \
424+
"array([[[1. +0.j , 0.9 +0.1j, 0.1 +2.j , 0.05+3.j ]]])," \
425+
" array([ 0.1, 1. , 10. , 100. ]))"
426+
ref1 = ref0[:-1] + ", smooth=True)"
427+
sysm = FrequencyResponseData(
428+
np.matmul(array([[1],[2]]), sys0.fresp), sys0.omega)
429+
430+
self.assertEqual(repr(sys0), ref0)
431+
self.assertEqual(repr(sys1), ref1)
432+
sys0r = eval(repr(sys0))
433+
np.testing.assert_array_almost_equal(sys0r.fresp, sys0.fresp)
434+
np.testing.assert_array_almost_equal(sys0r.omega, sys0.omega)
435+
sys1r = eval(repr(sys1))
436+
np.testing.assert_array_almost_equal(sys1r.fresp, sys1.fresp)
437+
np.testing.assert_array_almost_equal(sys1r.omega, sys1.omega)
438+
assert(sys1.ifunc is not None)
439+
440+
refs = """Frequency response data
441+
Freq [rad/s] Response
442+
------------ ---------------------
443+
0.100 1 +0j
444+
1.000 0.9 +0.1j
445+
10.000 0.1 +2j
446+
100.000 0.05 +3j"""
447+
self.assertEqual(str(sys0), refs)
448+
self.assertEqual(str(sys1), refs)
449+
450+
# print multi-input system
451+
refm = """Frequency response data
452+
Input 1 to output 1:
453+
Freq [rad/s] Response
454+
------------ ---------------------
455+
0.100 1 +0j
456+
1.000 0.9 +0.1j
457+
10.000 0.1 +2j
458+
100.000 0.05 +3j
459+
Input 2 to output 1:
460+
Freq [rad/s] Response
461+
------------ ---------------------
462+
0.100 2 +0j
463+
1.000 1.8 +0.2j
464+
10.000 0.2 +4j
465+
100.000 0.1 +6j"""
466+
self.assertEqual(str(sysm), refm)
467+
418468
if __name__ == "__main__":
419469
unittest.main()

control/tests/statesp_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,25 @@ def test_lft(self):
519519
np.testing.assert_allclose(np.array(pk.C).reshape(-1), Cmatlab)
520520
np.testing.assert_allclose(np.array(pk.D).reshape(-1), Dmatlab)
521521

522+
def test_repr(self):
523+
ref322 = """StateSpace(array([[-3., 4., 2.],
524+
[-1., -3., 0.],
525+
[ 2., 5., 3.]]), array([[ 1., 4.],
526+
[-3., -3.],
527+
[-2., 1.]]), array([[ 4., 2., -3.],
528+
[ 1., 4., 3.]]), array([[-2., 4.],
529+
[ 0., 1.]]){dt})"""
530+
self.assertEqual(repr(self.sys322), ref322.format(dt=''))
531+
sysd = StateSpace(self.sys322.A, self.sys322.B,
532+
self.sys322.C, self.sys322.D, 0.4)
533+
self.assertEqual(repr(sysd), ref322.format(dt=", 0.4"))
534+
array = np.array
535+
sysd2 = eval(repr(sysd))
536+
np.testing.assert_allclose(sysd.A, sysd2.A)
537+
np.testing.assert_allclose(sysd.B, sysd2.B)
538+
np.testing.assert_allclose(sysd.C, sysd2.C)
539+
np.testing.assert_allclose(sysd.D, sysd2.D)
540+
522541
def test_str(self):
523542
"""Test that printing the system works"""
524543
tsys = self.sys322
@@ -653,5 +672,6 @@ def test_sample_system_prewarping(self):
653672
evalfr(plant_d_warped, np.exp(wwarp*1j*Ts)),
654673
decimal=4)
655674

675+
656676
if __name__ == "__main__":
657677
unittest.main()

control/tests/xferfcn_test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,6 +872,46 @@ def test_latex_repr(self):
872872
r'+ 2.3 ' + expmul + ' 10^{-45}'
873873
r'}' + suffix + '$$')
874874
self.assertEqual(H._repr_latex_(), ref)
875+
876+
def test_repr(self):
877+
"""Test __repr__ printout."""
878+
Hc = TransferFunction([-1., 4.], [1., 3., 5.])
879+
Hd = TransferFunction([2., 3., 0.], [1., -3., 4., 0], 2.0)
880+
Hcm = TransferFunction(
881+
[ [[0, 1], [2, 3]], [[4, 5], [6, 7]] ],
882+
[ [[6, 7], [4, 5]], [[2, 3], [0, 1]] ])
883+
Hdm = TransferFunction(
884+
[ [[0, 1], [2, 3]], [[4, 5], [6, 7]] ],
885+
[ [[6, 7], [4, 5]], [[2, 3], [0, 1]] ], 0.5)
886+
887+
refs = [
888+
"TransferFunction(array([-1., 4.]), array([1., 3., 5.]))",
889+
"TransferFunction(array([2., 3., 0.]),"
890+
" array([ 1., -3., 4., 0.]), 2.0)",
891+
"TransferFunction([[array([1]), array([2, 3])],"
892+
" [array([4, 5]), array([6, 7])]],"
893+
" [[array([6, 7]), array([4, 5])],"
894+
" [array([2, 3]), array([1])]])",
895+
"TransferFunction([[array([1]), array([2, 3])],"
896+
" [array([4, 5]), array([6, 7])]],"
897+
" [[array([6, 7]), array([4, 5])],"
898+
" [array([2, 3]), array([1])]], 0.5)" ]
899+
self.assertEqual(repr(Hc), refs[0])
900+
self.assertEqual(repr(Hd), refs[1])
901+
self.assertEqual(repr(Hcm), refs[2])
902+
self.assertEqual(repr(Hdm), refs[3])
903+
904+
# and reading back
905+
array = np.array
906+
for H in (Hc, Hd, Hcm, Hdm):
907+
H2 = eval(H.__repr__())
908+
for p in range(len(H.num)):
909+
for m in range(len(H.num[0])):
910+
np.testing.assert_array_almost_equal(
911+
H.num[p][m], H2.num[p][m])
912+
np.testing.assert_array_almost_equal(
913+
H.den[p][m], H2.den[p][m])
914+
self.assertEqual(H.dt, H2.dt)
875915

876916
def test_sample_system_prewarping(self):
877917
"""test that prewarping works when converting from cont to discrete time system"""
@@ -893,5 +933,6 @@ def test_sample_system_prewarping(self):
893933
evalfr(plant_d_warped, np.exp(wwarp*1j*Ts)),
894934
decimal=4)
895935

936+
896937
if __name__ == "__main__":
897938
unittest.main()

control/xferfcn.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,17 @@ def __str__(self, var=None):
284284

285285
return outstr
286286

287-
# represent as string, makes display work for IPython
288-
__repr__ = __str__
287+
# represent to implement a re-loadable version
288+
def __repr__(self):
289+
"""Print transfer function in loadable form"""
290+
if self.issiso():
291+
return "TransferFunction({num}, {den}{dt})".format(
292+
num=self.num[0][0].__repr__(), den=self.den[0][0].__repr__(),
293+
dt=(isdtime(self, strict=True) and ', {}'.format(self.dt)) or '')
294+
else:
295+
return "TransferFunction({num}, {den}{dt})".format(
296+
num=self.num.__repr__(), den=self.den.__repr__(),
297+
dt=(isdtime(self, strict=True) and ', {}'.format(self.dt)) or '')
289298

290299
def _repr_latex_(self, var=None):
291300
"""LaTeX representation of transfer function, for Jupyter notebook"""

0 commit comments

Comments
 (0)