Repository: python-control/python-control
Pull request #1142 (2025-03-24 03:14:16)

Add type error checks, unit tests, documentation for real-valued systems
This PR addresses #1141 which suggested putting in checks for unsupported coefficient types in transfer functions (specifically complex numbers).  This PR does that, including some small additions to the documentation and unit tests.

Note that attempting to pass a complex coefficient for a state space system was already caught as an error.



Patch:
diff --git a/control/xferfcn.py b/control/xferfcn.py
index 16d7c505..02ba72df 100644
--- a/control/xferfcn.py
+++ b/control/xferfcn.py
@@ -1954,6 +1954,7 @@ def _clean_part(data, name="<unknown>"):
 
     """
     valid_types = (int, float, complex, np.number)
+    unsupported_types = (complex, np.complexfloating)
     valid_collection = (list, tuple, ndarray)
 
     if isinstance(data, np.ndarray) and data.ndim == 2 and \
@@ -1998,8 +1999,11 @@ def _clean_part(data, name="<unknown>"):
     for i in range(out.shape[0]):
         for j in range(out.shape[1]):
             for k in range(len(out[i, j])):
-                if isinstance(out[i, j][k], (int, np.int32, np.int64)):
+                if isinstance(out[i, j][k], (int, np.integer)):
                     out[i, j][k] = float(out[i, j][k])
+                elif isinstance(out[i, j][k], unsupported_types):
+                    raise TypeError(
+                        f"unsupported data type: {type(out[i, j][k])}")
     return out
 
 
diff --git a/doc/linear.rst b/doc/linear.rst
index b7b8f713..a9960fec 100644
--- a/doc/linear.rst
+++ b/doc/linear.rst
@@ -39,7 +39,7 @@ of linear time-invariant (LTI) systems:
   y &= C x + D u
 
 where :math:`u` is the input, :math:`y` is the output, and :math:`x`
-is the state.
+is the state.  All vectors and matrices must be real-valued.
 
 To create a state space system, use the :func:`ss` function:
 
@@ -94,7 +94,8 @@ transfer functions
               {b_0 s^n + b_1 s^{n-1} + \cdots + b_n},
 
 where :math:`n` is greater than or equal to :math:`m` for a proper
-transfer function.  Improper transfer functions are also allowed.
+transfer function.  Improper transfer functions are also allowed.  All
+coefficients must be real-valued.
 
 To create a transfer function, use the :func:`tf` function::
 
diff --git a/examples/cruise.ipynb b/examples/cruise.ipynb
index 16935b15..08a1583a 100644
--- a/examples/cruise.ipynb
+++ b/examples/cruise.ipynb
@@ -420,8 +420,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "system: a =  (0.010124405669387215-0j) , b =  (1.3203061238159202+0j)\n",
-      "pzcancel: kp = 0.5 , ki = (0.005062202834693608+0j) , 1/(kp b) =  (1.5148002148317266+0j)\n",
+      "system: a =  0.010124405669387215 , b =  1.3203061238159202\n",
+      "pzcancel: kp = 0.5 , ki = 0.005062202834693608 , 1/(kp b) =  1.5148002148317266\n",
       "sfb_int: K =  0.5 , ki = 0.1\n"
      ]
     },
@@ -442,7 +442,7 @@
     "\n",
     "# Construction a controller that cancels the pole\n",
     "kp = 0.5\n",
-    "a = -P.poles()[0]\n",
+    "a = -P.poles()[0].real\n",
     "b = np.real(P(0)) * a\n",
     "ki = a * kp\n",
     "control_pz = ct.TransferFunction(\n",


Test patch:
diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py
index e3d78bbd..3c1411f0 100644
--- a/control/tests/statesp_test.py
+++ b/control/tests/statesp_test.py
@@ -136,6 +136,7 @@ class TestStateSpace:
          ((np.ones((3, 3)), np.ones((3, 2)),
            np.ones((2, 3)), np.ones((2, 3))), ValueError,
           r"Incompatible dimensions of D matrix; expected \(2, 2\)"),
+         (([1j], 2, 3, 0), TypeError, "real number, not 'complex'"),
         ])
     def test_constructor_invalid(self, args, exc, errmsg):
         """Test invalid input to StateSpace() constructor"""
diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py
index 87c85239..d3db08ef 100644
--- a/control/tests/xferfcn_test.py
+++ b/control/tests/xferfcn_test.py
@@ -49,6 +49,10 @@ class TestXferFcn:
                               [[4, 5], [6, 7]]],
                              [[[6, 7], [4, 5]],
                               [[2, 3]]])
+
+        with pytest.raises(TypeError, match="unsupported data type"):
+            ct.tf([1j], [1, 2, 3])
+
         # good input
         TransferFunction([[[0, 1], [2, 3]],
                           [[4, 5], [6, 7]]],