Repository: sympy/sympy

Issue:
Control test fails with numpy 2.0
CC @akshanshbhatt @faze-geek 

This is the second time I have asked to fix these tests (gh-23128).

This test fails with numpy 2.0:
```console
$ pytest sympy/physics/control/tests/test_control_plots.py::test_pole_zero
================================================================================================ test session starts =================================================================================================
platform darwin -- Python 3.12.2, pytest-8.1.1, pluggy-1.4.0
architecture: 64-bit
cache:        yes
ground types: flint (python-flint==0.6.0)

rootdir: /Users/enojb/work/dev/sympy
configfile: pyproject.toml
plugins: instafail-0.5.0, doctestplus-1.2.2.dev5+g5176f32, hypothesis-6.99.9, xdist-3.5.0, timeout-2.3.1, split-0.8.2
collected 1 item

sympy/physics/control/tests/test_control_plots.py F                                                                                                                                                            [100%]

====================================================================================================== FAILURES ======================================================================================================
___________________________________________________________________________________________________ test_pole_zero ___________________________________________________________________________________________________

    def test_pole_zero():
        if not numpy:
            skip("NumPy is required for this test")

        def pz_tester(sys, expected_value):
            z, p = pole_zero_numerical_data(sys)
            z_check = numpy.allclose(z, expected_value[0])
            p_check = numpy.allclose(p, expected_value[1])
            return p_check and z_check

        exp1 = [[], [-0.24999999999999994+1.3919410907075054j, -0.24999999999999994-1.3919410907075054j]]
        exp2 = [[0.0], [-0.25+0.3227486121839514j, -0.25-0.3227486121839514j]]
        exp3 = [[0.0], [-0.5000000000000004+0.8660254037844395j,
            -0.5000000000000004-0.8660254037844395j, 0.9999999999999998+0j]]
        exp4 = [[], [5.0, 0.0, 0.0, 0.0]]
        exp5 = [[-5.645751311064592, -0.5000000000000008, -0.3542486889354093],
            [-0.24999999999999986+1.3919410907075052j,
            -0.24999999999999986-1.3919410907075052j, -0.2499999999999998+0.32274861218395134j,
            -0.2499999999999998-0.32274861218395134j]]
        exp6 = [[], [-1.1641600331447917-3.545808351896439j,
              -0.8358399668552097+2.5458083518964383j]]

>       assert pz_tester(tf1, exp1)
E       assert False
E        +  where False = <function test_pole_zero.<locals>.pz_tester at 0x1148e6c00>(TransferFunction(1, p**2 + 0.5*p + 2, p), [[], [(-0.24999999999999994+1.3919410907075054j), (-0.24999999999999994-1.3919410907075054j)]])

sympy/physics/control/tests/test_control_plots.py:121: AssertionError
                                                                                                   DO *NOT* COMMIT!
============================================================================================== short test summary info ===============================================================================================
FAILED sympy/physics/control/tests/test_control_plots.py::test_pole_zero - assert False
================================================================================================= 1 failed in 0.29s ==================================================================================================
```

Discussion:

Patch:
diff --git a/sympy/physics/control/control_plots.py b/sympy/physics/control/control_plots.py
index 3742de329e..e233d5d0a4 100644
--- a/sympy/physics/control/control_plots.py
+++ b/sympy/physics/control/control_plots.py
@@ -2,12 +2,14 @@
 from sympy.functions.elementary.exponential import (exp, log)
 from sympy.polys.partfrac import apart
 from sympy.core.symbol import Dummy
+from sympy.core.sympify import _sympify
 from sympy.external import import_module
 from sympy.functions import arg, Abs
 from sympy.integrals.laplace import _fast_inverse_laplace
 from sympy.physics.control.lti import SISOLinearTimeInvariant
 from sympy.plotting.series import LineOver1DRangeSeries
 from sympy.polys.polytools import Poly
+from sympy.polys.polyutils import _nsort
 from sympy.printing.latex import latex
 
 __all__ = ['pole_zero_numerical_data', 'pole_zero_plot',
@@ -88,8 +90,8 @@ def pole_zero_numerical_data(system):
     >>> from sympy.physics.control.lti import TransferFunction
     >>> from sympy.physics.control.control_plots import pole_zero_numerical_data
     >>> tf1 = TransferFunction(s**2 + 1, s**4 + 4*s**3 + 6*s**2 + 5*s + 2, s)
-    >>> pole_zero_numerical_data(tf1)   # doctest: +SKIP
-    ([-0.+1.j  0.-1.j], [-2. +0.j        -0.5+0.8660254j -0.5-0.8660254j -1. +0.j       ])
+    >>> pole_zero_numerical_data(tf1)  # doctest: +SKIP
+    ([-1j, 1j], [-2.0, (-1.5-0.8660254j), (-0.5+0.8660254j)])
 
     See Also
     ========
@@ -109,7 +111,12 @@ def pole_zero_numerical_data(system):
     zeros = np.roots(num_poly)
     poles = np.roots(den_poly)
 
-    return zeros, poles
+    # make ordering canonical
+    def _sort(l):
+        return [float(i) if i.is_real else complex(i) for i in
+                _nsort([_sympify(i) for i in l])]
+
+    return _sort(zeros), _sort(poles)
 
 
 def pole_zero_plot(system, pole_color='blue', pole_markersize=10,
diff --git a/sympy/polys/polyutils.py b/sympy/polys/polyutils.py
index b5c49fcc9d..6a2019d3b1 100644
--- a/sympy/polys/polyutils.py
+++ b/sympy/polys/polyutils.py
@@ -39,6 +39,8 @@ def _nsort(roots, separated=False):
     """
     if not all(r.is_number for r in roots):
         raise NotImplementedError
+    if not len(roots):
+        return [] if not separated else ([], [])
     # see issue 6137:
     # get the real part of the evaluated real and imaginary parts of each root
     key = [[i.n(2).as_real_imag()[0] for i in r.as_real_imag()] for r in roots]


Test patch:
diff --git a/sympy/physics/control/tests/test_control_plots.py b/sympy/physics/control/tests/test_control_plots.py
index 673fcee6cf..dbb9126762 100644
--- a/sympy/physics/control/tests/test_control_plots.py
+++ b/sympy/physics/control/tests/test_control_plots.py
@@ -1,8 +1,10 @@
 from math import isclose
-from sympy.core.numbers import I
+from sympy.core.numbers import I, all_close
+from sympy.core.sympify import _sympify
 from sympy.core.symbol import Dummy
 from sympy.functions.elementary.complexes import (Abs, arg)
 from sympy.functions.elementary.exponential import log
+from sympy.polys.polyutils import _nsort
 from sympy.abc import s, p, a
 from sympy.external import import_module
 from sympy.physics.control.control_plots import \
@@ -101,20 +103,28 @@ def test_pole_zero():
         skip("NumPy is required for this test")
 
     def pz_tester(sys, expected_value):
-        z, p = pole_zero_numerical_data(sys)
-        z_check = numpy.allclose(z, expected_value[0])
-        p_check = numpy.allclose(p, expected_value[1])
+        zp = pole_zero_numerical_data(sys)
+        zp = [[_sympify(i).n(chop=1e-10) for i in j] for j in zp]
+        z, p = [_nsort(i) for i in zp]
+        def check(a, b):
+            if isinstance(a, (list, tuple)):
+                return all(check(i, j) for i,j in zip(a, b))
+            if not b:
+                return all_close(a, b, atol=1e-10, rtol=0)
+            return all_close(a, b, atol=0, rtol=1e-6)
+        z_check = check(z, expected_value[0])
+        p_check = check(p, expected_value[1])
         return p_check and z_check
 
-    exp1 = [[], [-0.24999999999999994+1.3919410907075054j, -0.24999999999999994-1.3919410907075054j]]
-    exp2 = [[0.0], [-0.25+0.3227486121839514j, -0.25-0.3227486121839514j]]
-    exp3 = [[0.0], [-0.5000000000000004+0.8660254037844395j,
-        -0.5000000000000004-0.8660254037844395j, 0.9999999999999998+0j]]
-    exp4 = [[], [5.0, 0.0, 0.0, 0.0]]
+    exp1 = [[], [-0.24999999999999994-1.3919410907075054j, -0.24999999999999994+1.3919410907075054j]]
+    exp2 = [[0.0], [-0.25-0.3227486121839514j, -0.25+0.3227486121839514j]]
+    exp3 = [[0.0], [0.9999999999999998+0j, -0.5000000000000004-0.8660254037844395j,
+        -0.5000000000000004+0.8660254037844395j]]
+    exp4 = [[], [0.0, 0.0, 0.0, 5.0]]
     exp5 = [[-5.645751311064592, -0.5000000000000008, -0.3542486889354093],
-        [-0.24999999999999986+1.3919410907075052j,
-        -0.24999999999999986-1.3919410907075052j, -0.2499999999999998+0.32274861218395134j,
-        -0.2499999999999998-0.32274861218395134j]]
+        [-0.24999999999999986-1.3919410907075052j,
+        -0.24999999999999986-0.32274861218395134j, -0.2499999999999998+0.32274861218395134j,
+        -0.2499999999999998+1.3919410907075052j]]
     exp6 = [[], [-1.1641600331447917-3.545808351896439j,
           -0.8358399668552097+2.5458083518964383j]]