Repository: python-control/python-control
Pull request #765 (2022-08-25 04:41:16)

fix control.matlab.lsim bug for discrete time system
This PR fixes issue #764.  There was a dimension check in `forced_response` for discrete time systems that occurred before processing of the `transpose` parameter.  Removing the dimension check (which gets done later, after `transpose` processing) fixes the bug.  Add a unit test to catch the error and also turned on some tests that were `@slycotonly` but that didn't rely on slycot.



Patch:
diff --git a/control/timeresp.py b/control/timeresp.py
index aa1261cc..e029f2a4 100644
--- a/control/timeresp.py
+++ b/control/timeresp.py
@@ -972,11 +972,6 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False,
             dt = 1. if sys.dt in [True, None] else sys.dt
             T = np.array(range(n_steps)) * dt
         else:
-            # Make sure the input vector and time vector have same length
-            if (U.ndim == 1 and U.shape[0] != T.shape[0]) or \
-                    (U.ndim > 1 and U.shape[1] != T.shape[0]):
-                raise ValueError('Parameter ``T`` must have same elements as'
-                                 ' the number of columns in input array ``U``')
             if U.ndim == 0:
                 U = np.full((n_inputs, T.shape[0]), U)
     else:


Test patch:
diff --git a/control/tests/matlab_test.py b/control/tests/matlab_test.py
index a379ce7f..abf86ce4 100644
--- a/control/tests/matlab_test.py
+++ b/control/tests/matlab_test.py
@@ -208,7 +208,6 @@ class TestMatlab:
         np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
         np.testing.assert_array_almost_equal(tout, t)
 
-    @slycotonly
     def testStep_mimo(self, mimo):
         """Test step for MIMO system"""
         sys = mimo.ss1
@@ -267,7 +266,6 @@ class TestMatlab:
             np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
             np.testing.assert_array_almost_equal(tout, t)
 
-    @slycotonly
     def testImpulse_mimo(self, mimo):
         """Test impulse() for MIMO system"""
         t = np.linspace(0, 1, 10)
@@ -296,7 +294,6 @@ class TestMatlab:
         np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
         np.testing.assert_array_almost_equal(tout, t)
 
-    @slycotonly
     def testInitial_mimo(self, mimo):
         """Test initial() for MIMO system"""
         t = np.linspace(0, 1, 10)
@@ -333,7 +330,6 @@ class TestMatlab:
         yout, _t, _xout = lsim(siso.ss1, u, t, x0)
         np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
 
-    @slycotonly
     def testLsim_mimo(self, mimo):
         """Test lsim() for MIMO system.
 
@@ -352,6 +348,25 @@ class TestMatlab:
         yout, _t, _xout = lsim(mimo.ss1, u, t, x0)
         np.testing.assert_array_almost_equal(yout, youttrue, decimal=4)
 
+    def test_lsim_mimo_dtime(self):
+        # https://github.com/python-control/python-control/issues/764
+        time = np.linspace(0.0, 511.0e-6, 512)
+        DAC = np.sin(time)
+        ADC = np.cos(time)
+
+        input_Kalman = np.transpose(
+            np.concatenate(([[DAC]], [[ADC]]), axis=1)[0])
+        Af = [[0.45768416, -0.42025511], [-0.43354791, 0.51961178]]
+        Bf = [[2.84368641, 52.05922305], [-1.47286557, -19.94861943]]
+        Cf = [[1.0, 0.0], [0.0, 1.0]]
+        Df = [[0.0, 0.0], [0.0, 0.0]]
+
+        ss_Kalman = ss(Af, Bf, Cf, Df, 1.0e-6)
+        y_est, t, x_est = lsim(ss_Kalman, input_Kalman, time)
+        assert y_est.shape == (time.size, ss_Kalman.ninputs)
+        assert t.shape == (time.size, )
+        assert x_est.shape == (time.size, ss_Kalman.nstates)
+
     def testMargin(self, siso):
         """Test margin()"""
         #! TODO: check results to make sure they are OK
@@ -582,7 +597,6 @@ class TestMatlab:
         for i in range(len(ssdata_1)):
             np.testing.assert_array_almost_equal(ssdata_1[i], ssdata_2[i])
 
-    @slycotonly
     def testMIMOssdata(self, mimo):
         """Test ssdata() MIMO"""
         m = (mimo.ss1.A, mimo.ss1.B, mimo.ss1.C, mimo.ss1.D)
diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py
index ff712f84..124e16c1 100644
--- a/control/tests/timeresp_test.py
+++ b/control/tests/timeresp_test.py
@@ -698,10 +698,10 @@ class TestTimeresp:
         """Test invalid parameters dtime with sys.dt > 0."""
         with pytest.raises(ValueError, match="can't both be zero"):
             forced_response(tsystem.sys)
-        with pytest.raises(ValueError, match="must have same elements"):
+        with pytest.raises(ValueError, match="Parameter ``U``: Wrong shape"):
             forced_response(tsystem.sys,
                             T=tsystem.t, U=np.random.randn(1, 12))
-        with pytest.raises(ValueError, match="must have same elements"):
+        with pytest.raises(ValueError, match="Parameter ``U``: Wrong shape"):
             forced_response(tsystem.sys,
                             T=tsystem.t, U=np.random.randn(12))
         with pytest.raises(ValueError, match="must match sampling time"):