From f81b6d2617c14c0ca18b317bd92916b6ded5a71f Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Sat, 10 Dec 2022 11:59:25 +0200 Subject: [PATCH 1/3] Allow division by scalar for StateSpace & InputOutputSystem Attempt division by any non-LTI and non-NameIOSystem by translating G / k to G * (1/k). Division of StateSpace by TransferFunction, etc., unchanged. --- control/iosys.py | 11 +++++++++++ control/statesp.py | 19 ++++++++----------- control/tests/statesp_test.py | 11 +++++++++++ control/tests/type_conversion_test.py | 6 +++--- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index cbbd4cecc..b6b665030 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -346,6 +346,17 @@ def __neg__(sys): # Return the newly created system return newsys + def __truediv__(sys2, sys1): + """Multiply two input/output systems (series interconnection)""" + # Note: order of arguments is flipped so that self = sys2, + # corresponding to the ordering convention of sys2 * sys1 + + if not isinstance(sys1, (LTI, NamedIOSystem)): + return sys2 * (1/sys1) + else: + return NotImplemented + + # Update parameters used for _rhs, _out (used by subclasses) def _update_params(self, params, warning=False): if warning: diff --git a/control/statesp.py b/control/statesp.py index 7843cb33f..6203eda66 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -64,7 +64,7 @@ from .frdata import FrequencyResponseData from .lti import LTI, _process_frequency_response from .namedio import common_timebase, isdtime, _process_namedio_keywords, \ - _process_dt_keyword + _process_dt_keyword, NamedIOSystem from . import config from copy import deepcopy @@ -794,17 +794,14 @@ def __rmul__(self, other): pass raise TypeError("can't interconnect systems") - # TODO: __div__ and __rdiv__ are not written yet. - def __div__(self, other): - """Divide two LTI systems.""" - - raise NotImplementedError("StateSpace.__div__ is not implemented yet.") - - def __rdiv__(self, other): - """Right divide two LTI systems.""" + # TODO: general __truediv__, and __rtruediv__; requires descriptor system support + def __truediv__(self, other): + """Divide a StateSpace object; only division by scalars is supported""" + if not isinstance(other, (LTI, NamedIOSystem)): + return self * (1/other) + else: + return NotImplemented - raise NotImplementedError( - "StateSpace.__rdiv__ is not implemented yet.") def __call__(self, x, squeeze=None, warn_infinite=True): """Evaluate system's transfer function at complex frequency. diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index 41f0c893a..fa837f30d 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -333,6 +333,17 @@ def test_multiply_ss(self, sys222, sys322): np.testing.assert_array_almost_equal(sys.C, C) np.testing.assert_array_almost_equal(sys.D, D) + @pytest.mark.parametrize("k", [2, -3.141, np.float32(2.718), np.array([[4.321], [5.678]])]) + def test_truediv_ss_scalar(self, sys322, k): + """Divide SS by scalar.""" + sys = sys322 / k + syscheck = sys322 * (1/k) + + np.testing.assert_array_almost_equal(sys.A, syscheck.A) + np.testing.assert_array_almost_equal(sys.B, syscheck.B) + np.testing.assert_array_almost_equal(sys.C, syscheck.C) + np.testing.assert_array_almost_equal(sys.D, syscheck.D) + @pytest.mark.parametrize("omega, resp", [(1., np.array([[ 4.37636761e-05-0.01522976j, diff --git a/control/tests/type_conversion_test.py b/control/tests/type_conversion_test.py index 7163f7097..0deb68f88 100644 --- a/control/tests/type_conversion_test.py +++ b/control/tests/type_conversion_test.py @@ -88,11 +88,11 @@ def sys_dict(): ('mul', 'flt', ['ss', 'tf', 'frd', 'lio', 'ios', 'arr', 'flt']), # op left ss tf frd lio ios arr flt - ('truediv', 'ss', ['xs', 'tf', 'frd', 'xio', 'xos', 'xs', 'xs' ]), + ('truediv', 'ss', ['xs', 'tf', 'frd', 'xio', 'xos', 'ss', 'ss' ]), ('truediv', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]), ('truediv', 'frd', ['frd', 'frd', 'frd', 'frd', 'E', 'frd', 'frd']), - ('truediv', 'lio', ['xio', 'tf', 'frd', 'xio', 'xio', 'xio', 'xio']), - ('truediv', 'ios', ['xos', 'xos', 'E', 'xos', 'xos' 'xos', 'xos']), + ('truediv', 'lio', ['xio', 'tf', 'frd', 'xio', 'xio', 'lio', 'lio']), + ('truediv', 'ios', ['xos', 'xos', 'E', 'xos', 'xos', 'ios', 'ios']), ('truediv', 'arr', ['xs', 'tf', 'frd', 'xio', 'xos', 'arr', 'arr']), ('truediv', 'flt', ['xs', 'tf', 'frd', 'xio', 'xos', 'arr', 'flt'])] From 0efae636cb831ecba53274e61a33f828d529b5b3 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Sat, 10 Dec 2022 12:08:07 +0200 Subject: [PATCH 2/3] Remove __div__ and __rdiv__ methods These aren't used in Python 3. --- control/frdata.py | 8 -------- control/xferfcn.py | 8 -------- 2 files changed, 16 deletions(-) diff --git a/control/frdata.py b/control/frdata.py index a33775afb..c78607a07 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -408,10 +408,6 @@ def __truediv__(self, other): smooth=(self.ifunc is not None) and (other.ifunc is not None)) - # TODO: Remove when transition to python3 complete - def __div__(self, other): - return self.__truediv__(other) - # TODO: Division of MIMO transfer function objects is not written yet. def __rtruediv__(self, other): """Right divide two LTI objects.""" @@ -429,10 +425,6 @@ def __rtruediv__(self, other): return other / self - # TODO: Remove when transition to python3 complete - def __rdiv__(self, other): - return self.__rtruediv__(other) - def __pow__(self, other): if not type(other) == int: raise ValueError("Exponent must be an integer") diff --git a/control/xferfcn.py b/control/xferfcn.py index 84188a63f..a27b46623 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -702,10 +702,6 @@ def __truediv__(self, other): return TransferFunction(num, den, dt) - # TODO: Remove when transition to python3 complete - def __div__(self, other): - return TransferFunction.__truediv__(self, other) - # TODO: Division of MIMO transfer function objects is not written yet. def __rtruediv__(self, other): """Right divide two LTI objects.""" @@ -724,10 +720,6 @@ def __rtruediv__(self, other): return other / self - # TODO: Remove when transition to python3 complete - def __rdiv__(self, other): - return TransferFunction.__rtruediv__(self, other) - def __pow__(self, other): if not type(other) == int: raise ValueError("Exponent must be an integer") From cc3fb2fd903275ade892898b8c5b6779b6b05807 Mon Sep 17 00:00:00 2001 From: Rory Yorke Date: Fri, 16 Dec 2022 06:00:47 +0200 Subject: [PATCH 3/3] Update __truediv__ docstring for InputOutputSystem and StateSpace --- control/iosys.py | 4 +++- control/statesp.py | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index b6b665030..67cca74de 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -347,7 +347,9 @@ def __neg__(sys): return newsys def __truediv__(sys2, sys1): - """Multiply two input/output systems (series interconnection)""" + """Division of input/output systems + + Only division by scalars and arrays of scalars is supported""" # Note: order of arguments is flipped so that self = sys2, # corresponding to the ordering convention of sys2 * sys1 diff --git a/control/statesp.py b/control/statesp.py index 6203eda66..249c6f5e0 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -796,7 +796,11 @@ def __rmul__(self, other): # TODO: general __truediv__, and __rtruediv__; requires descriptor system support def __truediv__(self, other): - """Divide a StateSpace object; only division by scalars is supported""" + """Division of StateSpace systems + + Only division by TFs, FRDs, scalars, and arrays of scalars is + supported. + """ if not isinstance(other, (LTI, NamedIOSystem)): return self * (1/other) else: