Repository: python-control/python-control

Issue:
Change installation instructions to recommend Conda, warn about compiling Slycot
See python-control/Slycot#191

The python-control installation docs should recommend conda first, and, when suggesting `pip install slycot`, warning that compilation can be difficult, especially on Windows.

Tangentially, on the conda instructions: is it safe to install numpy & scipy from the conda default repos, and slycot from conda-forge?  See, e.g., the somewhat old https://github.com/conda/conda/issues/7273#issuecomment-387782788 which says that one should not mix these (though requests for a reference for that go unanswered.)  FWIW, I nowadays use [mambaforge](https://github.com/conda-forge/miniforge#mambaforge) , which defaults to conda-forge, and seems to generally work well on Windows and Linux.

Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/58244402/badge)](https://coveralls.io/builds/58244402)

Coverage: 94.867% (+0.001%) from 94.866% when pulling **ee173f21df9370622e1365ca7e5245260bff4b62 on murrayrm:smallfixes-26Mar2023** into **2c5c2f01302372e9743e4a95a51194a144fa5737 on python-control:main**.


Patch:
diff --git a/README.rst b/README.rst
index f3e3a13f..ebcf77c4 100644
--- a/README.rst
+++ b/README.rst
@@ -22,14 +22,17 @@ Python Control Systems Library
 The Python Control Systems Library is a Python module that implements basic
 operations for analysis and design of feedback control systems.
 
-
 Have a go now!
-==============
+--------------
 Try out the examples in the examples folder using the binder service.
 
 .. image:: https://mybinder.org/badge_logo.svg
  :target: https://mybinder.org/v2/gh/python-control/python-control/HEAD
 
+The package can also be installed on Google Colab using the commands::
+
+  !pip install control
+  import control as ct
 
 Features
 --------
@@ -44,7 +47,7 @@ Features
 - Nonlinear systems: optimization-based control, describing functions, differential flatness
 
 Links
-=====
+-----
 
 - Project home page: http://python-control.org
 - Source code repository: https://github.com/python-control/python-control
@@ -52,9 +55,8 @@ Links
 - Issue tracker: https://github.com/python-control/python-control/issues
 - Mailing list: http://sourceforge.net/p/python-control/mailman/
 
-
 Dependencies
-============
+------------
 
 The package requires numpy, scipy, and matplotlib.  In addition, some routines
 use a module called slycot, that is a Python wrapper around some FORTRAN
@@ -64,6 +66,7 @@ functionality is limited or absent, and installation of slycot is recommended
 
 https://github.com/python-control/Slycot
 
+
 Installation
 ============
 
@@ -73,7 +76,7 @@ Conda and conda-forge
 The easiest way to get started with the Control Systems library is
 using `Conda <https://conda.io>`_.
 
-The Control Systems library has been packages for the `conda-forge
+The Control Systems library has packages available using the `conda-forge
 <https://conda-forge.org>`_ Conda channel, and as of Slycot version
 0.3.4, binaries for that package are available for 64-bit Windows,
 OSX, and Linux.
@@ -83,6 +86,10 @@ conda environment, run::
 
   conda install -c conda-forge control slycot
 
+Mixing packages from conda-forge and the default conda channel can
+sometimes cause problems with dependencies, so it is usually best to
+instally NumPy, SciPy, and Matplotlib from conda-forge as well.
+
 Pip
 ---
 
@@ -92,7 +99,8 @@ To install using pip::
   pip install control
 
 If you install Slycot using pip you'll need a development environment
-(e.g., Python development files, C and Fortran compilers).
+(e.g., Python development files, C and Fortran compilers).  Pip
+installation can be particularly complicated for Windows.
 
 Installing from source
 ----------------------
@@ -106,11 +114,13 @@ toplevel `python-control` directory::
 Article and Citation Information
 ================================
 
-An `article <https://ieeexplore.ieee.org/abstract/document/9683368>`_ about the library is available on IEEE Explore. If the Python Control Systems Library helped you in your research, please cite::
+An `article <https://ieeexplore.ieee.org/abstract/document/9683368>`_ about
+the library is available on IEEE Explore. If the Python Control Systems Library helped you in your research, please cite::
 
   @inproceedings{python-control2021,
     title={The Python Control Systems Library (python-control)},
-    author={Fuller, Sawyer and Greiner, Ben and Moore, Jason and Murray, Richard and van Paassen, Ren{\'e} and Yorke, Rory},
+    author={Fuller, Sawyer and Greiner, Ben and Moore, Jason and
+            Murray, Richard and van Paassen, Ren{\'e} and Yorke, Rory},
     booktitle={60th IEEE Conference on Decision and Control (CDC)},
     pages={4875--4881},
     year={2021},
diff --git a/control/config.py b/control/config.py
index f7f449bb..f75bd52d 100644
--- a/control/config.py
+++ b/control/config.py
@@ -76,7 +76,6 @@ def set_defaults(module, **keywords):
     >>> ct.defaults['freqplot.number_of_samples']
     100
     >>> # do some customized freqplotting
-    >>> ct.reset_defaults()
 
     """
     if not isinstance(module, str):
@@ -209,7 +208,6 @@ def use_matlab_defaults():
     --------
     >>> ct.use_matlab_defaults()
     >>> # do some matlab style plotting
-    >>> ct.reset_defaults()
 
     """
     set_defaults('freqplot', dB=True, deg=True, Hz=False, grid=True)
@@ -229,7 +227,6 @@ def use_fbs_defaults():
     --------
     >>> ct.use_fbs_defaults()
     >>> # do some FBS style plotting
-    >>> ct.reset_defaults()
 
     """
     set_defaults('freqplot', dB=False, deg=True, Hz=False, grid=False)
@@ -263,7 +260,6 @@ def use_numpy_matrix(flag=True, warn=True):
     --------
     >>> ct.use_numpy_matrix(True, False)
     >>> # do some legacy calculations using np.matrix
-    >>> ct.reset_defaults()
 
     """
     if flag and warn:
@@ -285,7 +281,6 @@ def use_legacy_defaults(version):
     >>> ct.use_legacy_defaults("0.9.0")
     (0, 9, 0)
     >>> # do some legacy style plotting
-    >>> ct.reset_defaults()
 
     """
     import re
diff --git a/control/ctrlutil.py b/control/ctrlutil.py
index fa7b91ee..425812dc 100644
--- a/control/ctrlutil.py
+++ b/control/ctrlutil.py
@@ -44,6 +44,7 @@
 from . import lti
 import numpy as np
 import math
+import warnings
 
 __all__ = ['unwrap', 'issys', 'db2mag', 'mag2db']
 
@@ -99,6 +100,8 @@ def issys(obj):
     False
 
     """
+    warnings.warn("issys() is deprecated; use isinstance(obj, ct.LTI)",
+                  FutureWarning, stacklevel=2)
     return isinstance(obj, lti.LTI)
 
 def db2mag(db):
diff --git a/control/freqplot.py b/control/freqplot.py
index c20fb189..25de3c11 100644
--- a/control/freqplot.py
+++ b/control/freqplot.py
@@ -558,21 +558,21 @@ def nyquist_plot(
         List of linear input/output systems (single system is OK). Nyquist
         curves for each system are plotted on the same graph.
 
-    plot : boolean
-        If True, plot magnitude
-
-    omega : array_like
+    omega : array_like, optional
         Set of frequencies to be evaluated, in rad/sec.
 
-    omega_limits : array_like of two values
+    omega_limits : array_like of two values, optional
         Limits to the range of frequencies. Ignored if omega is provided, and
         auto-generated if omitted.
 
-    omega_num : int
+    omega_num : int, optional
         Number of frequency samples to plot.  Defaults to
         config.defaults['freqplot.number_of_samples'].
 
-    color : string
+    plot : boolean, optional
+        If True (default), plot the Nyquist plot.
+
+    color : string, optional
         Used to specify the color of the line and arrowhead.
 
     return_contour : bool, optional
@@ -690,6 +690,13 @@ def nyquist_plot(
        to `none` will turn off indentation.  If `return_contour` is True, the
        exact contour used for evaluation is returned.
 
+    3. For those portions of the Nyquist plot in which the contour is
+       indented to avoid poles, resuling in a scaling of the Nyquist plot,
+       the line styles are according to the settings of the `primary_style`
+       and `mirror_style` keywords.  By default the scaled portions of the
+       primary curve use a dotted line style and the scaled portion of the
+       mirror image use a dashdot line style.
+
     Examples
     --------
     >>> G = ct.zpk([], [-1, -2, -3], gain=100)
diff --git a/control/matlab/wrappers.py b/control/matlab/wrappers.py
index 1c4ee053..e7d75724 100644
--- a/control/matlab/wrappers.py
+++ b/control/matlab/wrappers.py
@@ -5,7 +5,7 @@ Wrappers for the MATLAB compatibility module
 import numpy as np
 from ..iosys import ss
 from ..xferfcn import tf
-from ..ctrlutil import issys
+from ..lti import LTI
 from ..exception import ControlArgument
 from scipy.signal import zpk2tf
 from warnings import warn
@@ -124,7 +124,7 @@ def _parse_freqplot_args(*args):
     i = 0
     while i < len(args):
         # Check to see if this is a system of some sort
-        if issys(args[i]):
+        if isinstance(args[i], LTI):
             # Append the system to our list of systems
             syslist.append(args[i])
             i += 1
diff --git a/doc/conf.py b/doc/conf.py
index a76b1c48..5fb7342f 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -37,11 +37,12 @@ author = u'Python Control Developers'
 import re
 import control
 
+# Get the version number for this commmit (including alpha/beta/rc tags)
+release = re.sub('^v', '', os.popen('git describe').read().strip())
+
 # The short X.Y.Z version
-version = re.sub(r'(\d+\.\d+\.\d+)(.*)', r'\1', control.__version__)
+version = re.sub(r'(\d+\.\d+\.\d+(.post\d+)?)(.*)', r'\1', release)
 
-# The full version, including alpha/beta/rc tags
-release = control.__version__
 print("version %s, release %s" % (version, release))
 
 # -- General configuration ---------------------------------------------------
@@ -206,11 +207,10 @@ def linkcode_resolve(domain, info):
         linespec = ""
 
     base_url = "https://github.com/python-control/python-control/blob/"
-    if 'dev' in control.__version__:
+    if release != version:      # development release
         return base_url + "main/control/%s%s" % (fn, linespec)
-    else:
-        return base_url + "%s/control/%s%s" % (
-           control.__version__, fn, linespec)
+    else:                       # specific version
+        return base_url + "%s/control/%s%s" % (version, fn, linespec)
 
 # Don't automaticall show all members of class in Methods & Attributes section
 numpydoc_show_class_members = False
@@ -282,4 +282,5 @@ import numpy as np
 import control as ct
 import control.optimal as obc
 import control.flatsys as fs
+ct.reset_defaults()
 """
diff --git a/doc/conventions.rst b/doc/conventions.rst
index 47636671..5dc2e3d7 100644
--- a/doc/conventions.rst
+++ b/doc/conventions.rst
@@ -6,8 +6,11 @@
 Library conventions
 *******************
 
-The python-control library uses a set of standard conventions for the way
-that different types of standard information used by the library.
+The python-control library uses a set of standard conventions for the
+way that different types of standard information used by the library.
+Throughout this manual, we assume the `control` package has been
+imported as `ct`.
+
 
 LTI system representation
 =========================
@@ -29,7 +32,7 @@ of linear time-invariant (LTI) systems:
 
 where u is the input, y is the output, and x is the state.
 
-To create a state space system, use the :func:`ss` function:
+To create a state space system, use the :func:`ss` function::
 
   sys = ct.ss(A, B, C, D)
 
@@ -51,7 +54,7 @@ transfer functions
 where n is generally greater than or equal to m (for a proper transfer
 function).
 
-To create a transfer function, use the :func:`tf` function:
+To create a transfer function, use the :func:`tf` function::
 
   sys = ct.tf(num, den)
 
@@ -77,7 +80,7 @@ performed.
 The FRD class is also used as the return type for the
 :func:`frequency_response` function (and the equivalent method for the
 :class:`StateSpace` and :class:`TransferFunction` classes).  This
-object can be assigned to a tuple using
+object can be assigned to a tuple using::
 
     mag, phase, omega = response
 
@@ -91,7 +94,7 @@ is not SISO or `squeeze` is False, the array is 3D, indexed by the
 output, input, and frequency.  If `squeeze` is True then
 single-dimensional axes are removed.  The processing of the `squeeze`
 keyword can be changed by calling the response function with a new
-argument:
+argument::
 
     mag, phase, omega = response(squeeze=False)
 
@@ -101,10 +104,10 @@ Discrete time systems
 A discrete time system is created by specifying a nonzero 'timebase', dt.
 The timebase argument can be given when a system is constructed:
 
-* dt = 0: continuous time system (default)
-* dt > 0: discrete time system with sampling period 'dt'
-* dt = True: discrete time with unspecified sampling period
-* dt = None: no timebase specified
+* `dt = 0`: continuous time system (default)
+* `dt > 0`: discrete time system with sampling period 'dt'
+* `dt = True`: discrete time with unspecified sampling period
+* `dt = None`: no timebase specified
 
 Only the :class:`StateSpace`, :class:`TransferFunction`, and
 :class:`InputOutputSystem` classes allow explicit representation of
@@ -119,8 +122,8 @@ result will have the timebase of the latter system. For continuous time
 systems, the :func:`sample_system` function or the :meth:`StateSpace.sample`
 and :meth:`TransferFunction.sample` methods can be used to create a discrete
 time system from a continuous time system.  See
-:ref:`utility-and-conversions`. The default value of 'dt' can be changed by
-changing the value of ``control.config.defaults['control.default_dt']``.
+:ref:`utility-and-conversions`. The default value of `dt` can be changed by
+changing the value of `control.config.defaults['control.default_dt']`.
 
 Conversion between representations
 ----------------------------------
@@ -165,10 +168,9 @@ points in time, rows are different components::
            ...
            [ui(t1), ui(t2), ui(t3), ..., ui(tn)]]
 
-      Same for X, Y
-
-So, U[:,2] is the system's input at the third point in time; and U[1] or U[1,:]
-is the sequence of values for the system's second input.
+(and similarly for `X`, `Y`).  So, `U[:, 2]` is the system's input at the
+third point in time; and `U[1]` or `U[1, :]` is the sequence of values for
+the system's second input.
 
 When there is only one row, a 1D object is accepted or returned, which adds
 convenience for SISO systems:
@@ -185,8 +187,10 @@ Functions that return time responses (e.g., :func:`forced_response`,
 :func:`impulse_response`, :func:`input_output_response`,
 :func:`initial_response`, and :func:`step_response`) return a
 :class:`TimeResponseData` object that contains the data for the time
-response.  These data can be accessed via the ``time``, ``outputs``,
-``states`` and ``inputs`` properties::
+response.  These data can be accessed via the
+:attr:`~TimeResponseData.time`, :attr:`~TimeResponseData.outputs`,
+:attr:`~TimeResponseData.states` and :attr:`~TimeResponseData.inputs`
+properties::
 
     sys = ct.rss(4, 1, 1)
     response = ct.step_response(sys)
@@ -213,13 +217,13 @@ The output of a MIMO LTI system can be plotted like this::
     plot(t, y[1], label='y_1')
 
 The convention also works well with the state space form of linear
-systems. If ``D`` is the feedthrough matrix (2D array) of a linear system,
-and ``U`` is its input (array), then the feedthrough part of the system's
+systems. If `D` is the feedthrough matrix (2D array) of a linear system,
+and `U` is its input (array), then the feedthrough part of the system's
 response, can be computed like this::
 
     ft = D @ U
 
-Finally, the `to_pandas()` function can be used to create a pandas dataframe:
+Finally, the `to_pandas()` function can be used to create a pandas dataframe::
 
     df = response.to_pandas()
 
@@ -242,16 +246,12 @@ for various types of plots and establishing the underlying representation for
 state space matrices.
 
 To set the default value of a configuration variable, set the appropriate
-element of the `control.config.defaults` dictionary:
-
-.. code-block:: python
+element of the `control.config.defaults` dictionary::
 
     ct.config.defaults['module.parameter'] = value
 
 The `~control.config.set_defaults` function can also be used to set multiple
-configuration parameters at the same time:
-
-.. code-block:: python
+configuration parameters at the same time::
 
     ct.config.set_defaults('module', param1=val1, param2=val2, ...]
 
diff --git a/doc/intro.rst b/doc/intro.rst
index ce01aca1..9d4198c5 100644
--- a/doc/intro.rst
+++ b/doc/intro.rst
@@ -31,23 +31,43 @@ some thing to keep in mind:
 * You must include commas in vectors.  So [1 2 3] must be [1, 2, 3].
 * Functions that return multiple arguments use tuples.  
 * You cannot use braces for collections; use tuples instead.
+* Time series data have time as the final index (see
+  :ref:`time-series-convention`).
 
 Installation
 ============
 
-The `python-control` package can be installed using pip, conda or the
-standard setuptools mechanisms.  The package requires `numpy`_ and
-`scipy`_, and the plotting routines require `matplotlib
-<https://matplotlib.org>`_.  In addition, some routines require the `slycot
-<https://github.com/python-control/Slycot>`_ library in order to implement
-more advanced features (including some MIMO functionality).
+The `python-control` package can be installed using conda or pip.  The
+package requires `NumPy`_ and `SciPy`_, and the plotting routines
+require `Matplotlib <https://matplotlib.org>`_.  In addition, some
+routines require the `Slycot
+<https://github.com/python-control/Slycot>`_ library in order to
+implement more advanced features (including some MIMO functionality).
 
+For users with the Anaconda distribution of Python, the following
+command can be used::
+
+  conda install -c conda-forge control slycot
+
+This installs `slycot` and `python-control` from conda-forge, including the
+`openblas` package.  NumPy, SciPy, and Matplotlib will also be installed if
+they are not already present.
+
+.. note::
+   Mixing packages from conda-forge and the default conda channel
+   can sometimes cause problems with dependencies, so it is usually best to
+   instally NumPy, SciPy, and Matplotlib from conda-forge as well.)
 
 To install using pip::
 
   pip install slycot   # optional
   pip install control
 
+.. note::
+   If you install Slycot using pip you'll need a development
+   environment (e.g., Python development files, C and Fortran compilers).
+   Pip installation can be particularly complicated for Windows.
+
 Many parts of `python-control` will work without `slycot`, but some
 functionality is limited or absent, and installation of `slycot` is
 recommended. Users can check to insure that slycot is installed
@@ -56,28 +76,14 @@ correctly by running the command::
   python -c "import slycot"
 
 and verifying that no error message appears. More information on the 
-slycot package can be obtained from the `slycot project page
+Slycot package can be obtained from the `Slycot project page
 <https://github.com/python-control/Slycot>`_.
 
-For users with the Anaconda distribution of Python, the following
-commands can be used::
-
-  conda install numpy scipy matplotlib    # if not yet installed
-  conda install -c conda-forge control slycot
-
-This installs `slycot` and `python-control` from conda-forge, including the
-`openblas` package.
-
-Alternatively, to use setuptools, first `download the source
+Alternatively, to install from source, first `download the source
 <https://github.com/python-control/python-control/releases>`_ and unpack it.
 To install in your home directory, use::
 
-  python setup.py install --user
-
-or to install for all users (on Linux or Mac OS)::
-
-  python setup.py build
-  sudo python setup.py install
+  pip install .
 
 Getting started
 ===============
@@ -85,7 +91,7 @@ Getting started
 There are two different ways to use the package.  For the default interface
 described in :ref:`function-ref`, simply import the control package as follows::
 
-    >>> import control
+    >>> import control as ct
 
 If you want to have a MATLAB-like environment, use the :ref:`matlab-module`::
 
diff --git a/examples/kincar-flatsys.py b/examples/kincar-flatsys.py
index 2ebee313..b61a9e1c 100644
--- a/examples/kincar-flatsys.py
+++ b/examples/kincar-flatsys.py
@@ -10,7 +10,7 @@ import numpy as np
 import matplotlib.pyplot as plt
 import control as ct
 import control.flatsys as fs
-import control.optimal as opt
+import control.optimal as obc
 
 #
 # System model and utility functions
@@ -147,7 +147,7 @@ timepts = np.linspace(0, Tf, 10)
 basis = fs.PolyFamily(8)
 
 # Define the cost function (penalize lateral error and steering)
-traj_cost = opt.quadratic_cost(
+traj_cost = obc.quadratic_cost(
     vehicle_flat, np.diag([0, 0.1, 0]), np.diag([0.1, 1]), x0=xf, u0=uf)
 
 # Solve for an optimal solution
@@ -168,7 +168,7 @@ plot_results(T, xd, ud)
 
 # Constraint the input values
 constraints = [
-    opt.input_range_constraint(vehicle_flat, [8, -0.1], [12, 0.1]) ]
+    obc.input_range_constraint(vehicle_flat, [8, -0.1], [12, 0.1]) ]
 
 # TEST: Change the basis to use B-splines
 basis = fs.BSplineFamily([0, Tf/2, Tf], 6)
@@ -198,11 +198,11 @@ if 'PYCONTROL_TEST_EXAMPLES' not in os.environ:
 #
 
 # Define the cost function (mainly penalize steering angle)
-traj_cost = opt.quadratic_cost(
+traj_cost = obc.quadratic_cost(
     vehicle_flat, None, np.diag([0.1, 10]), x0=xf, u0=uf)
 
 # Set terminal cost to bring us close to xf
-terminal_cost = opt.quadratic_cost(vehicle_flat, 1e3 * np.eye(3), None, x0=xf)
+terminal_cost = obc.quadratic_cost(vehicle_flat, 1e3 * np.eye(3), None, x0=xf)
 
 # Change the basis to use B-splines
 basis = fs.BSplineFamily([0, Tf/2, Tf], [4, 6], vars=2)
diff --git a/examples/mpc_aircraft.ipynb b/examples/mpc_aircraft.ipynb
index 5da812eb..a1edf3eb 100644
--- a/examples/mpc_aircraft.ipynb
+++ b/examples/mpc_aircraft.ipynb
@@ -19,7 +19,7 @@
    "source": [
     "import control as ct\n",
     "import numpy as np\n",
-    "import control.optimal as opt\n",
+    "import control.optimal as obc\n",
     "import matplotlib.pyplot as plt"
    ]
   },
@@ -70,15 +70,15 @@
     "# model.y.reference = ys;\n",
     "\n",
     "# provide constraints on the system signals\n",
-    "constraints = [opt.input_range_constraint(sys, [-5, -6], [5, 6])]\n",
+    "constraints = [obc.input_range_constraint(sys, [-5, -6], [5, 6])]\n",
     "\n",
     "# provide penalties on the system signals\n",
     "Q = model.C.transpose() @ np.diag([10, 10, 10, 10]) @ model.C\n",
     "R = np.diag([3, 2])\n",
-    "cost = opt.quadratic_cost(model, Q, R, x0=xd, u0=ud)\n",
+    "cost = obc.quadratic_cost(model, Q, R, x0=xd, u0=ud)\n",
     "\n",
     "# online MPC controller object is constructed with a horizon 6\n",
-    "ctrl = opt.create_mpc_iosystem(model, np.arange(0, 6) * 0.2, cost, constraints)"
+    "ctrl = obc.create_mpc_iosystem(model, np.arange(0, 6) * 0.2, cost, constraints)"
    ]
   },
   {
diff --git a/examples/steering-optimal.py b/examples/steering-optimal.py
index 778ac3c2..d9bad608 100644
--- a/examples/steering-optimal.py
+++ b/examples/steering-optimal.py
@@ -8,7 +8,7 @@
 import numpy as np
 import math
 import control as ct
-import control.optimal as opt
+import control.optimal as obc
 import matplotlib.pyplot as plt
 import logging
 import time
@@ -106,7 +106,7 @@ print("Approach 1: standard quadratic cost")
 # Set up the cost functions
 Q = np.diag([.1, 10, .1])       # keep lateral error low
 R = np.diag([.1, 1])            # minimize applied inputs
-quad_cost = opt.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf)
+quad_cost = obc.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf)
 
 # Define the time horizon (and spacing) for the optimization
 timepts = np.linspace(0, Tf, 20, endpoint=True)
@@ -124,7 +124,7 @@ logging.basicConfig(
 
 # Compute the optimal control, setting step size for gradient calculation (eps)
 start_time = time.process_time()
-result1 = opt.solve_ocp(
+result1 = obc.solve_ocp(
     vehicle, timepts, x0, quad_cost, initial_guess=straight_line, log=True,
     # minimize_method='trust-constr',
     # minimize_options={'finite_diff_rel_step': 0.01},
@@ -158,9 +158,9 @@ print("Final simulated state:", y1[:,-1])
 print("\nApproach 2: input cost and constraints plus terminal cost")
 
 # Add input constraint, input cost, terminal cost
-constraints = [ opt.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
-traj_cost = opt.quadratic_cost(vehicle, None, np.diag([0.1, 1]), u0=uf)
-term_cost = opt.quadratic_cost(vehicle, np.diag([1, 10, 10]), None, x0=xf)
+constraints = [ obc.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
+traj_cost = obc.quadratic_cost(vehicle, None, np.diag([0.1, 1]), u0=uf)
+term_cost = obc.quadratic_cost(vehicle, np.diag([1, 10, 10]), None, x0=xf)
 
 # Change logging to keep less information
 logging.basicConfig(
@@ -175,7 +175,7 @@ straight_line = (state_guess, input_guess)
 
 # Compute the optimal control
 start_time = time.process_time()
-result2 = opt.solve_ocp(
+result2 = obc.solve_ocp(
     vehicle, timepts, x0, traj_cost, constraints, terminal_cost=term_cost,
     initial_guess=straight_line, log=True,
     # minimize_method='SLSQP', minimize_options={'eps': 0.01}
@@ -207,10 +207,10 @@ print("\nApproach 3: terminal constraints")
 
 # Input cost and terminal constraints
 R = np.diag([1, 1])                 # minimize applied inputs
-cost3 = opt.quadratic_cost(vehicle, np.zeros((3,3)), R, u0=uf)
+cost3 = obc.quadratic_cost(vehicle, np.zeros((3,3)), R, u0=uf)
 constraints = [
-    opt.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
-terminal = [ opt.state_range_constraint(vehicle, xf, xf) ]
+    obc.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ]
+terminal = [ obc.state_range_constraint(vehicle, xf, xf) ]
 
 # Reset logging to its default values
 logging.basicConfig(
@@ -219,7 +219,7 @@ logging.basicConfig(
 
 # Compute the optimal control
 start_time = time.process_time()
-result3 = opt.solve_ocp(
+result3 = obc.solve_ocp(
     vehicle, timepts, x0, cost3, constraints,
     terminal_constraints=terminal, initial_guess=straight_line, log=False,
     # solve_ivp_kwargs={'atol': 1e-3, 'rtol': 1e-2},
@@ -254,7 +254,7 @@ import control.flatsys as flat
 
 # Compute the optimal control
 start_time = time.process_time()
-result4 = opt.solve_ocp(
+result4 = obc.solve_ocp(
     vehicle, timepts, x0, quad_cost,
     constraints,
     terminal_constraints=terminal,


Test patch:
diff --git a/control/tests/ctrlutil_test.py b/control/tests/ctrlutil_test.py
index 460ff601..758c98b6 100644
--- a/control/tests/ctrlutil_test.py
+++ b/control/tests/ctrlutil_test.py
@@ -1,7 +1,8 @@
 """ctrlutil_test.py"""
 
 import numpy as np
-
+import pytest
+import control as ct
 from control.ctrlutil import db2mag, mag2db, unwrap
 
 class TestUtils:
@@ -58,3 +59,8 @@ class TestUtils:
     def test_mag2db_array(self):
         db_array = mag2db(self.mag)
         np.testing.assert_array_almost_equal(db_array, self.db)
+
+    def test_issys(self):
+        sys = ct.rss(2, 1, 1)
+        with pytest.warns(FutureWarning, match="deprecated; use isinstance"):
+            ct.issys(sys)
diff --git a/control/tests/flatsys_test.py b/control/tests/flatsys_test.py
index 95fb8cf7..7f480f43 100644
--- a/control/tests/flatsys_test.py
+++ b/control/tests/flatsys_test.py
@@ -212,7 +212,7 @@ class TestFlatSys:
             elif re.match("Iteration limit.*", traj_ocp.message) and \
                  re.match(
                      "conda ubuntu-3.* Generic", os.getenv('JOBNAME', '')) and \
-                 re.match("1.24.[01]", np.__version__):
+                 re.match("1.24.[012]", np.__version__):
                 pytest.xfail("gh820: iteration limit exceeded")
 
             else: