From 0a7cd533fbc5712acac2c10fa22f97be755b3d17 Mon Sep 17 00:00:00 2001 From: Ben Greiner Date: Sun, 11 Apr 2021 18:39:46 +0200 Subject: [PATCH 1/3] discrete time LaTeX repr of StateSpace systems --- control/statesp.py | 30 +++++++++++++++++++++--------- control/tests/statesp_test.py | 21 ++++++++++++++------- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index c12583111..b7d4bca7a 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -418,8 +418,8 @@ def _latex_partitioned_stateless(self): """ lines = [ r'\[', - r'\left(', - (r'\begin{array}' + (r'\left(' + + r'\begin{array}' + r'{' + 'rll' * self.ninputs + '}') ] @@ -429,7 +429,8 @@ def _latex_partitioned_stateless(self): lines.extend([ r'\end{array}' - r'\right)', + r'\right)' + + self._latex_dt(), r'\]']) return '\n'.join(lines) @@ -449,8 +450,8 @@ def _latex_partitioned(self): lines = [ r'\[', - r'\left(', - (r'\begin{array}' + (r'\left(' + + r'\begin{array}' + r'{' + 'rll' * self.nstates + '|' + 'rll' * self.ninputs + '}') ] @@ -466,7 +467,8 @@ def _latex_partitioned(self): lines.extend([ r'\end{array}' - r'\right)', + + r'\right)' + + self._latex_dt(), r'\]']) return '\n'.join(lines) @@ -509,11 +511,21 @@ def fmt_matrix(matrix, name): lines.extend(fmt_matrix(self.D, 'D')) lines.extend([ - r'\end{array}', + r'\end{array}' + + self._latex_dt(), r'\]']) return '\n'.join(lines) + def _latex_dt(self): + if self.isdtime(strict=True): + if self.dt is True: + return r"~,~dt~\mathrm{unspecified}" + else: + fmt = config.defaults['statesp.latex_num_format'] + return f"~,~dt={self.dt:{fmt}}" + return "" + def _repr_latex_(self): """LaTeX representation of state-space model @@ -534,9 +546,9 @@ def _repr_latex_(self): elif config.defaults['statesp.latex_repr_type'] == 'separate': return self._latex_separate() else: - cfg = config.defaults['statesp.latex_repr_type'] raise ValueError( - "Unknown statesp.latex_repr_type '{cfg}'".format(cfg=cfg)) + "Unknown statesp.latex_repr_type '{cfg}'".format( + cfg=config.defaults['statesp.latex_repr_type'])) # Negation of a system def __neg__(self): diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 71e7cc4bc..a63013113 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -997,9 +997,9 @@ def test_statespace_defaults(self, matarrayout): [[1.2345, -2e-200], [-1, 0]]) LTX_G1_REF = { - 'p3_p' : '\\[\n\\left(\n\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}14&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}88&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}00123&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', + 'p3_p' : '\\[\n\\left(\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}14&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}88&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}00123&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', - 'p5_p' : '\\[\n\\left(\n\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}1416&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}2346&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}8765&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}001234&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', + 'p5_p' : '\\[\n\\left(\\begin{array}{rllrll|rll}\n3.&\\hspace{-1em}1416&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n-1.&\\hspace{-1em}2346&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\hline\n9.&\\hspace{-1em}8765&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}001234&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', 'p3_s' : '\\[\n\\begin{array}{ll}\nA = \\left(\\begin{array}{rllrll}\n3.&\\hspace{-1em}14&\\hspace{-1em}\\phantom{\\cdot}&1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{100}\\\\\n-1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-23}\\\\\n\\end{array}\\right)\n&\nB = \\left(\\begin{array}{rll}\n0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\\\\nC = \\left(\\begin{array}{rllrll}\n9.&\\hspace{-1em}88&\\hspace{-1em}\\cdot10^{8}&0.&\\hspace{-1em}00123&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n&\nD = \\left(\\begin{array}{rll}\n5\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n\\]', @@ -1007,9 +1007,9 @@ def test_statespace_defaults(self, matarrayout): } LTX_G2_REF = { - 'p3_p' : '\\[\n\\left(\n\\begin{array}{rllrll}\n1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', + 'p3_p' : '\\[\n\\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', - 'p5_p' : '\\[\n\\left(\n\\begin{array}{rllrll}\n1.&\\hspace{-1em}2345&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', + 'p5_p' : '\\[\n\\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}2345&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\]', 'p3_s' : '\\[\n\\begin{array}{ll}\nD = \\left(\\begin{array}{rllrll}\n1.&\\hspace{-1em}23&\\hspace{-1em}\\phantom{\\cdot}&-2\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\cdot10^{-200}\\\\\n-1\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}&0\\phantom{.}&\\hspace{-1em}&\\hspace{-1em}\\phantom{\\cdot}\\\\\n\\end{array}\\right)\n\\end{array}\n\\]', @@ -1022,9 +1022,14 @@ def test_statespace_defaults(self, matarrayout): @pytest.mark.parametrize(" gmats, ref", [(LTX_G1, LTX_G1_REF), (LTX_G2, LTX_G2_REF)]) +@pytest.mark.parametrize("dt, dtref", + [(0, ""), + (None, ""), + (True, r"~,~dt~\mathrm{{unspecified}}"), + (0.1, r"~,~dt={dt:{fmt}}")]) @pytest.mark.parametrize("repr_type", [None, "partitioned", "separate"]) @pytest.mark.parametrize("num_format", [None, ".3g", ".5g"]) -def test_latex_repr(gmats, ref, repr_type, num_format, editsdefaults): +def test_latex_repr(gmats, ref, dt, dtref, repr_type, num_format, editsdefaults): """Test `._latex_repr_` with different config values This is a 'gold image' test, so if you change behaviour, @@ -1040,9 +1045,11 @@ def test_latex_repr(gmats, ref, repr_type, num_format, editsdefaults): if repr_type is not None: set_defaults('statesp', latex_repr_type=repr_type) - g = StateSpace(*gmats) + g = StateSpace(*(gmats+(dt,))) refkey = "{}_{}".format(refkey_n[num_format], refkey_r[repr_type]) - assert g._repr_latex_() == ref[refkey] + dt_latex = dtref.format(dt=dt, fmt=defaults['statesp.latex_num_format']) + ref_latex = ref[refkey][:-3] + dt_latex + ref[refkey][-3:] + assert g._repr_latex_() == ref_latex @pytest.mark.parametrize( From 89c22c160a6db38ec971d538c4413f72838e0514 Mon Sep 17 00:00:00 2001 From: Ben Greiner Date: Sun, 11 Apr 2021 18:51:29 +0200 Subject: [PATCH 2/3] use isdtime in StateSpace.__str__() --- control/statesp.py | 10 +++++----- control/tests/statesp_test.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index b7d4bca7a..82039975d 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -391,11 +391,11 @@ def __str__(self): "\n ".join(str(M).splitlines())) for Mvar, M in zip(["A", "B", "C", "D"], [self.A, self.B, self.C, self.D])]) - # TODO: replace with standard calls to lti functions - if (type(self.dt) == bool and self.dt is True): - string += "\ndt unspecified\n" - elif (not (self.dt is None) and type(self.dt) != bool and self.dt > 0): - string += "\ndt = " + self.dt.__str__() + "\n" + if self.isdtime(strict=True): + if self.dt is True: + string += "\ndt unspecified\n" + else: + string += f"\ndt = {self.dt}\n" return string # represent to implement a re-loadable version diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index a63013113..94c82280d 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -745,7 +745,7 @@ def test_str(self, sys322): tsysdtunspec = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, True) assert str(tsysdtunspec) == tref + "\ndt unspecified\n" sysdt1 = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, 1.) - assert str(sysdt1) == tref + "\ndt = 1.0\n" + assert str(sysdt1) == tref + "\ndt = {}\n".format(1.) def test_pole_static(self): """Regression: pole() of static gain is empty array.""" From 460859368661c0903fcf41040df8bd446e9cac2f Mon Sep 17 00:00:00 2001 From: Ben Greiner Date: Sun, 11 Apr 2021 19:56:12 +0200 Subject: [PATCH 3/3] print dt=True instead of unspecified --- control/statesp.py | 7 ++----- control/tests/statesp_test.py | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/control/statesp.py b/control/statesp.py index 82039975d..bfa55b357 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -392,10 +392,7 @@ def __str__(self): for Mvar, M in zip(["A", "B", "C", "D"], [self.A, self.B, self.C, self.D])]) if self.isdtime(strict=True): - if self.dt is True: - string += "\ndt unspecified\n" - else: - string += f"\ndt = {self.dt}\n" + string += f"\ndt = {self.dt}\n" return string # represent to implement a re-loadable version @@ -520,7 +517,7 @@ def fmt_matrix(matrix, name): def _latex_dt(self): if self.isdtime(strict=True): if self.dt is True: - return r"~,~dt~\mathrm{unspecified}" + return r"~,~dt=~\mathrm{True}" else: fmt = config.defaults['statesp.latex_num_format'] return f"~,~dt={self.dt:{fmt}}" diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 94c82280d..ab62bc1b6 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -743,7 +743,7 @@ def test_str(self, sys322): " [ 0. 1.]]\n") assert str(tsys) == tref tsysdtunspec = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, True) - assert str(tsysdtunspec) == tref + "\ndt unspecified\n" + assert str(tsysdtunspec) == tref + "\ndt = True\n" sysdt1 = StateSpace(tsys.A, tsys.B, tsys.C, tsys.D, 1.) assert str(sysdt1) == tref + "\ndt = {}\n".format(1.) @@ -1025,7 +1025,7 @@ def test_statespace_defaults(self, matarrayout): @pytest.mark.parametrize("dt, dtref", [(0, ""), (None, ""), - (True, r"~,~dt~\mathrm{{unspecified}}"), + (True, r"~,~dt=~\mathrm{{True}}"), (0.1, r"~,~dt={dt:{fmt}}")]) @pytest.mark.parametrize("repr_type", [None, "partitioned", "separate"]) @pytest.mark.parametrize("num_format", [None, ".3g", ".5g"])