From ae4915c4ece5f417fa514dadeb0d30ab412a28aa Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 10:09:58 -0400 Subject: [PATCH 1/6] change unsupported "shape =" to reshape() to avoid warnings --- control/descfcn.py | 3 +-- control/statesp.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/control/descfcn.py b/control/descfcn.py index 6f3f5169d..460b22601 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -188,8 +188,7 @@ def describing_function( # Go through all of the amplitudes we were given retdf = np.empty(np.shape(A), dtype=complex) - df = retdf # Access to the return array - df.shape = (-1, ) # as a 1D array + df = retdf.reshape((-1, )) # Access to the return array as a 1D array for i, a in enumerate(np.atleast_1d(A)): # Make sure we got a valid argument if a == 0: diff --git a/control/statesp.py b/control/statesp.py index 89698bb50..8091e29ed 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -261,9 +261,9 @@ def __init__(self, *args, **kwargs): # Reset shapes if the system is static if static: - A.shape = (0, 0) - B.shape = (0, self.ninputs) - C.shape = (self.noutputs, 0) + A = A.reshape((0, 0)) + B = B.reshape((0, self.ninputs)) + C = C.reshape((self.noutputs, 0)) # Check to make sure everything is consistent _check_shape(A, self.nstates, self.nstates, name="A") From 3b70cb41b5d41ed4aa4da9f2db2eb3d83c3ffdcd Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 10:10:36 -0400 Subject: [PATCH 2/6] remove unneeded warning check in ctrlplot_test() --- control/tests/ctrlplot_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/control/tests/ctrlplot_test.py b/control/tests/ctrlplot_test.py index bf8a075ae..a6d248bc0 100644 --- a/control/tests/ctrlplot_test.py +++ b/control/tests/ctrlplot_test.py @@ -748,8 +748,7 @@ def test_pole_zero_subplots(savefig=False): sys2 = ct.tf([1, 0.2], [1, 1, 3, 1, 1], name='sys2') ct.root_locus_plot([sys1, sys2], ax=ax_array[0, 0]) cplt = ct.root_locus_plot([sys1, sys2], ax=ax_array[1, 0]) - with pytest.warns(UserWarning, match="Tight layout not applied"): - cplt.set_plot_title("Root locus plots (w/ specified axes)") + cplt.set_plot_title("Root locus plots (w/ specified axes)") if savefig: plt.savefig("ctrlplot-pole_zero_subplots.png") From 40860d566dcd2763ea2dd587f647a411fd09958a Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 11:44:55 -0400 Subject: [PATCH 3/6] place near imaginary poles in nyquist_test to fix signs --- control/tests/nyquist_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/tests/nyquist_test.py b/control/tests/nyquist_test.py index 8f42e7430..6beb68ef2 100644 --- a/control/tests/nyquist_test.py +++ b/control/tests/nyquist_test.py @@ -374,7 +374,7 @@ def test_nyquist_indent_im(): def test_nyquist_indent_near_imaginary_axis(): """Test indent direction for poles near the imaginary axis.""" - sys = ct.tf([1, 11, 10], [0.01, 1, 0.01, 1]) + sys = ct.zpk([-10, -1], [-100, 0.001+1j, 0.001-1j], 10) omega = np.linspace(0, 2, 21) _, contour_default = ct.nyquist_response( From 84b0d0fc155a1e49580a5dd4099c01646fceff2f Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 11:45:19 -0400 Subject: [PATCH 4/6] cast eigenvectors to real to avoid imaginary initial conditions --- control/phaseplot.py | 1 + 1 file changed, 1 insertion(+) diff --git a/control/phaseplot.py b/control/phaseplot.py index deaa65636..0fdb9922f 100644 --- a/control/phaseplot.py +++ b/control/phaseplot.py @@ -892,6 +892,7 @@ def separatrices( timepts = np.linspace(0, timescale) # Run the trajectory starting in eigenvector directions + dir = dir.real # use real components only for eps in [-radius, radius]: x0 = xeq + dir * eps if evals[j].real < 0: From c4f248cfd6ba05400cb94abec631eae27258b63e Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 11:54:44 -0400 Subject: [PATCH 5/6] cast singular values to real (fix doctest error) --- control/modelsimp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/control/modelsimp.py b/control/modelsimp.py index 3352cc156..a2eb065b3 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -73,7 +73,7 @@ def hankel_singular_values(sys): WoWc = Wo @ Wc w, v = np.linalg.eig(WoWc) - hsv = np.sqrt(w) + hsv = np.sqrt(w.real) hsv = np.array(hsv) hsv = np.sort(hsv) # Return the Hankel singular values, high to low From 65d1d26d9e3566a0b7add83cdcd073f484fd29fa Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 8 Aug 2026 12:13:21 -0400 Subject: [PATCH 6/6] use doctest ELLIPSIS for bandwidth() values to avoid ULP failures bandwidth() gets its value from a scipy.optimize.root_scalar bisection, so the last digit or two of the repr differs across scipy/BLAS builds. The full-precision literals matched locally but failed on GitHub. Truncate to ~14 significant digits with # doctest: +ELLIPSIS. This keeps the examples verified (unlike +SKIP, used elsewhere in the repo for platform-dependent output), and trim_doctest_flags (on by default) strips the flag comment from the rendered HTML. Also applied to the G1*G2 example, which has the same origin and had not failed yet. Verified with "make doctest" in doc/: 728 tests, 0 failures. Co-Authored-By: Claude Opus 5 --- control/lti.py | 8 ++++---- doc/intro.rst | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/control/lti.py b/control/lti.py index 90188f556..7d4c98548 100644 --- a/control/lti.py +++ b/control/lti.py @@ -706,15 +706,15 @@ def bandwidth(sys, dbdrop=-3): Examples -------- >>> G = ct.tf([1], [1, 1]) - >>> ct.bandwidth(G) - np.float64(0.9976283451102316) + >>> ct.bandwidth(G) # doctest: +ELLIPSIS + np.float64(0.99762834511023...) >>> G1 = ct.tf(0.1, [1, 0.1]) >>> wn2 = 1 >>> zeta2 = 0.001 >>> G2 = ct.tf(wn2**2, [1, 2*zeta2*wn2, wn2**2]) - >>> ct.bandwidth(G1*G2) - np.float64(0.10184838823897456) + >>> ct.bandwidth(G1*G2) # doctest: +ELLIPSIS + np.float64(0.10184838823897...) """ if not isinstance(sys, LTI): diff --git a/doc/intro.rst b/doc/intro.rst index 0054bb668..22eb6d5df 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -190,5 +190,5 @@ This documentation has a number of notional conventions and functionality: .. doctest:: >>> sys = ct.tf([1], [1, 0.5, 1]) - >>> ct.bandwidth(sys) - np.float64(1.4839084518312828) + >>> ct.bandwidth(sys) # doctest: +ELLIPSIS + np.float64(1.4839084518312...)