diff --git a/control/statesp.py b/control/statesp.py index a1fd84b20..a2fa3dd73 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -326,7 +326,7 @@ def __init__(self, *args, init_namedio=True, **kwargs): D = np.zeros((C.shape[0], B.shape[1])) D = _ssmatrix(D) - # Matrices definining the linear system + # Matrices defining the linear system self.A = A self.B = B self.C = C @@ -346,9 +346,8 @@ def __init__(self, *args, init_namedio=True, **kwargs): defaults = args[0] if len(args) == 1 else \ {'inputs': D.shape[1], 'outputs': D.shape[0], 'states': A.shape[0]} - static = (A.size == 0) name, inputs, outputs, states, dt = _process_namedio_keywords( - kwargs, defaults, static=static, end=True) + kwargs, defaults, static=(A.size == 0), end=True) # Initialize LTI (NamedIOSystem) object super().__init__( @@ -1484,11 +1483,6 @@ def output(self, t, x, u=None, params=None): return (self.C @ x).reshape((-1,)) \ + (self.D @ u).reshape((-1,)) # return as row vector - def _isstatic(self): - """True if and only if the system has no dynamics, that is, - if A and B are zero. """ - return not np.any(self.A) and not np.any(self.B) - # TODO: add discrete time check def _convert_to_statespace(sys): diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 693be979e..8a6ed8165 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -41,6 +41,9 @@ class TSys: [[-1, 1], [0, -2]], [[0, 1], [1, 0]], [[1, 0], [0, 1]], np.zeros((2, 2))) + # Create a static gain linear system + T.staticgain = ct.StateSpace([], [], [], 1) + # Create simulation parameters T.T = np.linspace(0, 10, 100) T.U = np.sin(T.T) @@ -51,7 +54,7 @@ class TSys: def test_linear_iosys(self, tsys): # Create an input/output system from the linear system linsys = tsys.siso_linsys - iosys = ios.LinearIOSystem(linsys) + iosys = ios.LinearIOSystem(linsys).copy() # Make sure that the right hand side matches linear system for x, u in (([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([0, 0], 1)): @@ -66,6 +69,11 @@ def test_linear_iosys(self, tsys): np.testing.assert_array_almost_equal(lti_t, ios_t) np.testing.assert_allclose(lti_y, ios_y, atol=0.002, rtol=0.) + # Make sure that a static linear system has dt=None + # and otherwise dt is as specified + assert ios.LinearIOSystem(tsys.staticgain).dt is None + assert ios.LinearIOSystem(tsys.staticgain, dt=.1).dt == .1 + def test_tf2io(self, tsys): # Create a transfer function from the state space system linsys = tsys.siso_linsys diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index e97584fbb..6fcdade22 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -399,25 +399,6 @@ def test_freq_resp(self): mag, phase, omega = sys.freqresp(true_omega) np.testing.assert_almost_equal(mag, true_mag) - def test__isstatic(self): - A0 = np.zeros((2,2)) - A1 = A0.copy() - A1[0,1] = 1.1 - B0 = np.zeros((2,1)) - B1 = B0.copy() - B1[0,0] = 1.3 - C0 = A0 - C1 = np.eye(2) - D0 = 0 - D1 = np.ones((2,1)) - assert StateSpace(A0, B0, C1, D1)._isstatic() - assert not StateSpace(A1, B0, C1, D1)._isstatic() - assert not StateSpace(A0, B1, C1, D1)._isstatic() - assert not StateSpace(A1, B1, C1, D1)._isstatic() - assert StateSpace(A0, B0, C0, D0)._isstatic() - assert StateSpace(A0, B0, C0, D1)._isstatic() - assert StateSpace(A0, B0, C1, D0)._isstatic() - @slycotonly def test_minreal(self): """Test a minreal model reduction.""" @@ -1160,6 +1141,20 @@ def test_linfnorm_ct_mimo(self, ct_siso): np.testing.assert_allclose(fpeak, reffpeak) +@pytest.mark.parametrize("args, static", [ + (([], [], [], 1), True), # ctime, empty state + (([], [], [], 1, 1), True), # dtime, empty state + ((0, 0, 0, 1), False), # ctime, unused state + ((-1, 0, 0, 1), False), # ctime, exponential decay + ((-1, 0, 0, 0), False), # ctime, no input, no output + ((0, 0, 0, 1, 1), False), # dtime, integrator + ((1, 0, 0, 1, 1), False), # dtime, unused state + ((0, 0, 0, 1, None), False), # unspecified, unused state +]) +def test_isstatic(args, static): + sys = ct.StateSpace(*args) + assert sys._isstatic() == static + # Make sure that using params for StateSpace objects generates a warning def test_params_warning(): sys = StateSpace(-1, 1, 1, 0) @@ -1169,4 +1164,3 @@ def test_params_warning(): with pytest.warns(UserWarning, match="params keyword ignored"): sys.output(0, [0], [0], {'k': 5}) -