From 9acbfbbbf3b4e2c9e8453c0cc18d21df88d6230b Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 6 Dec 2024 17:36:37 -0800 Subject: [PATCH 1/4] fix issue with multiplying MIMO LTI system by scalar --- control/frdata.py | 15 +++++++++++++-- control/tests/lti_test.py | 18 ++++++++++++++++++ control/xferfcn.py | 17 ++++++++--------- 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/control/frdata.py b/control/frdata.py index 1bdf28528..ac032d3f7 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -261,6 +261,11 @@ def __init__(self, *args, **kwargs): # create interpolation functions if smooth: + # Set the order of the fit + if self.omega.size < 2: + raise ValueError("can't smooth with only 1 frequency") + degree = 3 if self.omega.size > 3 else self.omega.size - 1 + self.ifunc = empty((self.fresp.shape[0], self.fresp.shape[1]), dtype=tuple) for i in range(self.fresp.shape[0]): @@ -268,7 +273,8 @@ def __init__(self, *args, **kwargs): self.ifunc[i, j], u = splprep( u=self.omega, x=[real(self.fresp[i, j, :]), imag(self.fresp[i, j, :])], - w=1.0/(absolute(self.fresp[i, j, :]) + 0.001), s=0.0) + w=1.0/(absolute(self.fresp[i, j, :]) + 0.001), + s=0.0, k=degree) else: self.ifunc = None @@ -392,7 +398,12 @@ def __add__(self, other): # Convert the second argument to a frequency response function. # or re-base the frd to the current omega (if needed) - other = _convert_to_frd(other, omega=self.omega) + if isinstance(other, (int, float, complex, np.number)): + other = _convert_to_frd( + other, omega=self.omega, + inputs=self.ninputs, outputs=self.noutputs) + else: + other = _convert_to_frd(other, omega=self.omega) # Check that the input-output sizes are consistent. if self.ninputs != other.ninputs: diff --git a/control/tests/lti_test.py b/control/tests/lti_test.py index 3f001c17b..5359ceea3 100644 --- a/control/tests/lti_test.py +++ b/control/tests/lti_test.py @@ -350,3 +350,21 @@ def test_subsys_indexing(fcn, outdx, inpdx, key): np.testing.assert_almost_equal( subsys_fcn.frequency_response(omega).response, subsys_chk.frequency_response(omega).response) + + +@slycotonly +@pytest.mark.parametrize("op", [ + '__mul__', '__rmul__', '__add__', '__radd__', '__sub__', '__rsub__']) +@pytest.mark.parametrize("fcn", [ct.ss, ct.tf, ct.frd]) +def test_scalar_algebra(op, fcn): + sys_ss = ct.rss(4, 2, 2) + match fcn: + case ct.ss: + sys = sys_ss + case ct.tf: + sys = ct.tf(sys_ss) + case ct.frd: + sys = ct.frd(sys_ss, [0.1, 1, 10]) + + scaled = getattr(sys, op)(2) + np.testing.assert_almost_equal(getattr(sys(1j), op)(2), scaled(1j)) diff --git a/control/xferfcn.py b/control/xferfcn.py index 56ec7395f..b7daa9a2d 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -634,11 +634,11 @@ def __mul__(self, other): from .statesp import StateSpace # Convert the second argument to a transfer function. - if isinstance(other, StateSpace): + if isinstance(other, (StateSpace, np.ndarray)): other = _convert_to_transfer_function(other) - elif isinstance(other, (int, float, complex, np.number, np.ndarray)): - other = _convert_to_transfer_function(other, inputs=self.ninputs, - outputs=self.noutputs) + elif isinstance(other, (int, float, complex, np.number)): + # Multiply by a scaled identify matrix (transfer function) + other = _convert_to_transfer_function(np.eye(self.ninputs) * other) if not isinstance(other, TransferFunction): return NotImplemented @@ -681,8 +681,8 @@ def __rmul__(self, other): # Convert the second argument to a transfer function. if isinstance(other, (int, float, complex, np.number)): - other = _convert_to_transfer_function(other, inputs=self.ninputs, - outputs=self.ninputs) + # Multiply by a scaled identify matrix (transfer function) + other = _convert_to_transfer_function(np.eye(self.noutputs) * other) else: other = _convert_to_transfer_function(other) @@ -723,9 +723,8 @@ def __truediv__(self, other): """Divide two LTI objects.""" if isinstance(other, (int, float, complex, np.number)): - other = _convert_to_transfer_function( - other, inputs=self.ninputs, - outputs=self.ninputs) + # Multiply by a scaled identify matrix (transfer function) + other = _convert_to_transfer_function(np.eye(self.ninputs) * other) else: other = _convert_to_transfer_function(other) From 0b9be7a845fc6fcc62dd667d4c2d932debe363db Mon Sep 17 00:00:00 2001 From: "Scott C. Livingston" Date: Tue, 10 Dec 2024 16:03:04 -0800 Subject: [PATCH 2/4] fix misprint --- control/xferfcn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/xferfcn.py b/control/xferfcn.py index b7daa9a2d..ba8712aea 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -681,7 +681,7 @@ def __rmul__(self, other): # Convert the second argument to a transfer function. if isinstance(other, (int, float, complex, np.number)): - # Multiply by a scaled identify matrix (transfer function) + # Multiply by a scaled identity matrix (transfer function) other = _convert_to_transfer_function(np.eye(self.noutputs) * other) else: other = _convert_to_transfer_function(other) From f514f7c0064f214aa1be0917086f01833f6af490 Mon Sep 17 00:00:00 2001 From: "Scott C. Livingston" Date: Tue, 10 Dec 2024 16:03:11 -0800 Subject: [PATCH 3/4] fix misprint --- control/xferfcn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/xferfcn.py b/control/xferfcn.py index ba8712aea..43b99cc07 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -637,7 +637,7 @@ def __mul__(self, other): if isinstance(other, (StateSpace, np.ndarray)): other = _convert_to_transfer_function(other) elif isinstance(other, (int, float, complex, np.number)): - # Multiply by a scaled identify matrix (transfer function) + # Multiply by a scaled identity matrix (transfer function) other = _convert_to_transfer_function(np.eye(self.ninputs) * other) if not isinstance(other, TransferFunction): return NotImplemented From 3165881f23f917a9ed1c211b4b3f1497ac0e8c0d Mon Sep 17 00:00:00 2001 From: "Scott C. Livingston" Date: Tue, 10 Dec 2024 16:03:19 -0800 Subject: [PATCH 4/4] fix misprint --- control/xferfcn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/xferfcn.py b/control/xferfcn.py index 43b99cc07..5304ea636 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -723,7 +723,7 @@ def __truediv__(self, other): """Divide two LTI objects.""" if isinstance(other, (int, float, complex, np.number)): - # Multiply by a scaled identify matrix (transfer function) + # Multiply by a scaled identity matrix (transfer function) other = _convert_to_transfer_function(np.eye(self.ninputs) * other) else: other = _convert_to_transfer_function(other)