diff --git a/control/statesp.py b/control/statesp.py index 65529b99d..cd39b8096 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -555,7 +555,10 @@ def fmt_matrix(matrix, name): # Negation of a system def __neg__(self): """Negate a state space system.""" - return StateSpace(self.A, self.B, -self.C, -self.D, self.dt) + return StateSpace( + self.A, self.B, -self.C, -self.D, self.dt, + inputs=self.input_labels, outputs=self.output_labels, + states=self.state_labels) # Addition of two state space systems (parallel interconnection) def __add__(self, other): @@ -645,7 +648,10 @@ def __mul__(self, other): A, C = self.A, self.C B = self.B * other D = self.D * other - dt = self.dt + return StateSpace( + A, B, C, D, self.dt, + inputs=self.input_labels, outputs=self.output_labels, + states=self.state_labels) elif isinstance(other, np.ndarray): other = np.atleast_2d(other) @@ -706,7 +712,10 @@ def __rmul__(self, other): # Just multiplying by a scalar; change the input B = other * self.B D = other * self.D - return StateSpace(self.A, B, self.C, D, self.dt) + return StateSpace( + self.A, B, self.C, D, self.dt, + inputs=self.input_labels, outputs=self.output_labels, + states=self.state_labels) elif isinstance(other, np.ndarray): other = np.atleast_2d(other) diff --git a/control/tests/namedio_test.py b/control/tests/namedio_test.py index 8c44f5980..119caacf8 100644 --- a/control/tests/namedio_test.py +++ b/control/tests/namedio_test.py @@ -59,6 +59,40 @@ def test_named_ss(): " ['y1', 'y2']>" +@pytest.mark.parametrize( + "sys", + [ + ct.ss( + [[-1, 0], [0, -2]], [[1, 0], [0, 1]], + [[1, 0], [0, 1]], [[0, 0], [0, 0]], + inputs=['e1', 'e2'], outputs=['u1', 'u2'], + states=['x1', 'x2'], name='plant'), + ct.tf( + [[[1], [2]], [[3], [4]]], + [[[1, 1], [1, 2]], [[1, 3], [1, 4]]], + inputs=['e1', 'e2'], outputs=['u1', 'u2'], name='plant'), + ], +) +@pytest.mark.parametrize( + "operation", + [ + lambda sys: -sys, + lambda sys: sys * 2, + lambda sys: 2 * sys, + lambda sys: ct.negate(sys), + lambda sys: ct.series(sys, 2), + lambda sys: ct.series(2, sys), + ], +) +def test_named_scalar_operations_preserve_signal_names(sys, operation): + result = operation(sys) + + assert result.input_labels == sys.input_labels + assert result.output_labels == sys.output_labels + if isinstance(sys, ct.StateSpace): + assert result.state_labels == sys.state_labels + + # List of classes that are expected fun_instance = { ct.rss: (ct.NonlinearIOSystem, ct.StateSpace, ct.StateSpace), diff --git a/control/xferfcn.py b/control/xferfcn.py index 8e51534d7..21323894c 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -554,7 +554,9 @@ def __neg__(self): for i in range(self.noutputs): for j in range(self.ninputs): num[i, j] *= -1 - return TransferFunction(num, self.den, self.dt) + return TransferFunction( + num, self.den, self.dt, + inputs=self.input_labels, outputs=self.output_labels) def __add__(self, other): """Add two LTI objects (parallel connection).""" @@ -620,8 +622,13 @@ 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 identity matrix (transfer function) - other = _convert_to_transfer_function(np.eye(self.ninputs) * other) + num = deepcopy(self.num_array) + for i in range(self.noutputs): + for j in range(self.ninputs): + num[i, j] *= other + return TransferFunction( + num, self.den, self.dt, + inputs=self.input_labels, outputs=self.output_labels) if not isinstance(other, TransferFunction): return NotImplemented @@ -669,8 +676,13 @@ def __rmul__(self, other): # Convert the second argument to a transfer function. if isinstance(other, (int, float, complex, np.number)): - # Multiply by a scaled identity matrix (transfer function) - other = _convert_to_transfer_function(np.eye(self.noutputs) * other) + num = deepcopy(self.num_array) + for i in range(self.noutputs): + for j in range(self.ninputs): + num[i, j] *= other + return TransferFunction( + num, self.den, self.dt, + inputs=self.input_labels, outputs=self.output_labels) else: other = _convert_to_transfer_function(other)