diff --git a/control/iosys.py b/control/iosys.py index 29f5bfefb..42cf4094d 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -46,6 +46,24 @@ class NamedSignal(np.ndarray): This class modifies the `numpy.ndarray` class and allows signals to be accessed using the signal name in addition to indices and slices. + Signals can be either a 2D array, index by signal and time, or a 3D + array, indexed by signal, trace, and time. + + Attributes + ---------- + signal_labels : list of str + Label names for each of the signal elements in the signal. + trace_labels : list of str, optional + Label names for each of the traces in the signal (if multi-trace). + + Examples + -------- + >>> sys = ct.rss( + ... states=['p1', 'p2', 'p3'], inputs=['u1', 'u2'], outputs=['y']) + >>> resp = ct.step_response(sys) + >>> resp.states['p1', 'u1'] # Step response from u1 to p1 + NamedSignal(...) + """ def __new__(cls, input_array, signal_labels=None, trace_labels=None): # See https://numpy.org/doc/stable/user/basics.subclassing.html @@ -314,7 +332,7 @@ def _repr_latex_(self): def _repr_html_(self): # Defaults to using __repr__; override in subclasses return None - + def _repr_markdown_(self): return self._repr_html_() diff --git a/control/tests/phaseplot_test.py b/control/tests/phaseplot_test.py index fc4edcbea..ac5249948 100644 --- a/control/tests/phaseplot_test.py +++ b/control/tests/phaseplot_test.py @@ -167,7 +167,7 @@ def invpend_ode(t, x, m=0, l=0, b=0, g=0): ct.phase_plane_plot( invpend_ode, [-5, 5, 2, 2], params={'stuff': (1, 1, 0.2, 1)}, plot_streamlines=True) - + with pytest.raises(ValueError, match="gridtype must be 'meshgrid' when using streamplot"): ct.phase_plane_plot(ct.rss(2, 1, 1), plot_streamlines=False, plot_streamplot=True, gridtype='boxgrid') @@ -190,7 +190,7 @@ def invpend_ode(t, x, m=0, l=0, b=0, g=0): sys, [-12, 12, -10, 10], 15, gridspec=[2, 9], plot_streamlines=True, plot_separatrices=False, suppress_warnings=True) - + @pytest.mark.usefixtures('mplcleanup') def test_phase_plot_zorder(): # some of these tests are a bit akward since the streamlines and separatrices @@ -211,7 +211,7 @@ def get_zorders(cplt): assert cplt.lines[3] == None or isinstance(cplt.lines[3], mpl.streamplot.StreamplotSet) streamplot = max(cplt.lines[3].lines.get_zorder(), cplt.lines[3].arrows.get_zorder()) if cplt.lines[3] else None return streamlines, quiver, streamplot, separatrices, equilpoints - + def assert_orders(streamlines, quiver, streamplot, separatrices, equilpoints): print(streamlines, quiver, streamplot, separatrices, equilpoints) if streamlines is not None: @@ -261,8 +261,6 @@ def sys(t, x): # make sure changing the norm at least doesn't throw an error ct.phase_plane_plot(sys, plot_streamplot=dict(vary_color=True, norm=mpl.colors.LogNorm())) - - @pytest.mark.usefixtures('mplcleanup') def test_basic_phase_plots(savefigs=False): diff --git a/control/timeresp.py b/control/timeresp.py index bd549589a..3c49d213e 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -551,10 +551,10 @@ def outputs(self): def states(self): """Time response state vector. - Time evolution of the state vector, indexed indexed by either the - state and time (if only a single trace is given) or the state, trace, - and time (for multiple traces). See `TimeResponseData.squeeze` - for a description of how this can be modified using the `squeeze` + Time evolution of the state vector, indexed by either the state and + time (if only a single trace is given) or the state, trace, and + time (for multiple traces). See `TimeResponseData.squeeze` for a + description of how this can be modified using the `squeeze` keyword. Input and output signal names can be used to index the data in @@ -616,9 +616,9 @@ def inputs(self): def _legacy_states(self): """Time response state vector (legacy version). - Time evolution of the state vector, indexed indexed by either the - state and time (if only a single trace is given) or the state, - trace, and time (for multiple traces). + Time evolution of the state vector, indexed by either the state and + time (if only a single trace is given) or the state, trace, and + time (for multiple traces). The `legacy_states` property is not affected by the `squeeze` keyword and hence it will always have these dimensions. diff --git a/doc/Makefile b/doc/Makefile index 493fd7da5..4029dd70f 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,7 +1,7 @@ # Makefile for python-control Sphinx documentation # RMM, 15 Jan 2025 -FIGS = figures/classes.pdf +FIGS = figures/classes.svg RST_FIGS = figures/flatsys-steering-compare.png \ figures/iosys-predprey-open.png \ figures/timeplot-servomech-combined.png \ diff --git a/doc/_templates/extended-class-template.rst b/doc/_templates/extended-class-template.rst new file mode 100644 index 000000000..6e1e4ccd7 --- /dev/null +++ b/doc/_templates/extended-class-template.rst @@ -0,0 +1,9 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :no-members: + :show-inheritance: + :no-inherited-members: + :no-special-members: diff --git a/doc/classes.rst b/doc/classes.rst index 0ab508a3a..000761724 100644 --- a/doc/classes.rst +++ b/doc/classes.rst @@ -14,10 +14,10 @@ systems (both linear time-invariant and nonlinear). They are usually created from factory functions such as :func:`tf` and :func:`ss`, so the user should normally not need to instantiate these directly. -The following figure illustrates the relationship between the classes. +The following figure illustrates the relationship between the classes: -.. image:: figures/classes.pdf - :width: 800 +.. figure:: figures/classes.svg + :width: 640 :align: center .. autosummary:: @@ -34,6 +34,17 @@ The following figure illustrates the relationship between the classes. InterconnectedSystem LinearICSystem +The time response of an input/output system is represented using a +special :class:`NamedSignal` class that allows the individual signal +elements to be access using signal names in place of integer offsets: + +.. autosummary:: + :toctree: generated/ + :template: extended-class-template.rst + :nosignatures: + + NamedSignal + Response and Plotting Classes ============================= @@ -95,5 +106,5 @@ operations: optimal.OptimalEstimationProblem optimal.OptimalEstimationResult -More informaton on the functions used to create these classes can be +More information on the functions used to create these classes can be found in the :ref:`nonlinear-systems` chapter. diff --git a/doc/figures/Makefile b/doc/figures/Makefile index 1ca54b372..26bdf22c2 100644 --- a/doc/figures/Makefile +++ b/doc/figures/Makefile @@ -2,7 +2,7 @@ # RMM, 26 Dec 2024 # List of figures that need to be created (first figure generated is OK) -FIGS = classes.pdf +FIGS = classes.svg # Location of the control package SRCDIR = ../.. @@ -12,5 +12,5 @@ all: $(FIGS) clean: /bin/rm -f $(FIGS) -classes.pdf: classes.fig - fig2dev -Lpdf $< $@ +classes.svg: classes.fig + fig2dev -Lsvg $< $@ diff --git a/doc/figures/classes.fig b/doc/figures/classes.fig index 4e63b8bff..17c112cc7 100644 --- a/doc/figures/classes.fig +++ b/doc/figures/classes.fig @@ -1,4 +1,5 @@ -#FIG 3.2 Produced by xfig version 3.2.8b +#FIG 3.2 Produced by xfig version 3.2.9 +#encoding: UTF-8 Landscape Center Inches @@ -37,12 +38,13 @@ Single 2 1 0 2 1 7 50 -1 -1 0.000 0 0 7 0 1 2 1 0 1.00 60.00 90.00 6525 1950 7050 2550 -4 1 1 50 -1 16 12 0.0000 4 210 2115 4050 3675 InterconnectedSystem\001 -4 1 1 50 -1 16 12 0.0000 4 165 1605 7950 3675 TransferFunction\001 -4 1 1 50 -1 0 12 0.0000 4 150 345 7050 2775 LTI\001 -4 1 1 50 -1 16 12 0.0000 4 210 1830 5175 2775 NonlinearIOSystem\001 -4 1 1 50 -1 16 12 0.0000 4 210 1095 6150 3675 StateSpace\001 -4 1 1 50 -1 16 12 0.0000 4 210 1500 5175 4575 LinearICSystem\001 -4 2 1 50 -1 16 12 0.0000 4 210 1035 3375 3225 FlatSystem\001 -4 0 1 50 -1 16 12 0.0000 4 165 420 8400 3225 FRD\001 -4 1 1 50 -1 16 12 0.0000 4 210 1770 6300 1875 InputOutputSystem\001 +4 1 1 50 -1 16 12 0.0000 4 191 1958 4050 3675 InterconnectedSystem\001 +4 1 1 50 -1 16 12 0.0000 4 151 1496 7950 3675 TransferFunction\001 +4 1 1 50 -1 0 12 0.0000 4 133 305 7050 2775 LTI\001 +4 1 1 50 -1 16 12 0.0000 4 191 1705 5175 2775 NonlinearIOSystem\001 +4 1 1 50 -1 16 12 0.0000 4 190 1016 6150 3675 StateSpace\001 +4 1 1 50 -1 16 12 0.0000 4 191 1394 5175 4575 LinearICSystem\001 +4 2 1 50 -1 16 12 0.0000 4 191 970 3375 3225 FlatSystem\001 +4 0 1 50 -1 16 12 0.0000 4 145 384 8400 3225 FRD\001 +4 1 1 50 -1 16 12 0.0000 4 191 1681 6300 1875 InputOutputSystem\001 +4 1 7 50 -1 16 12 0.0000 4 22 21 5175 4800 .\001 diff --git a/doc/figures/classes.pdf b/doc/figures/classes.pdf deleted file mode 100644 index 2c51b0193..000000000 Binary files a/doc/figures/classes.pdf and /dev/null differ diff --git a/doc/figures/classes.svg b/doc/figures/classes.svg new file mode 100644 index 000000000..98fedb596 --- /dev/null +++ b/doc/figures/classes.svg @@ -0,0 +1,151 @@ + + + + + + + +. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +InterconnectedSystem + +TransferFunction + +LTI + +NonlinearIOSystem + +StateSpace + +LinearICSystem + +FlatSystem + +FRD + +InputOutputSystem + + + + + + + + + + + diff --git a/doc/linear.rst b/doc/linear.rst index a9960feca..200260303 100644 --- a/doc/linear.rst +++ b/doc/linear.rst @@ -531,6 +531,8 @@ equilibrium values (thereby keeping the input/output gain unchanged at zero frequency ["DC"]). +.. _displaying-lti-system-information: + Displaying LTI System Information ================================= diff --git a/doc/phaseplot.rst b/doc/phaseplot.rst index d2a3e6353..8c014415f 100644 --- a/doc/phaseplot.rst +++ b/doc/phaseplot.rst @@ -43,7 +43,7 @@ on a grid, equilibrium points and separatrices if they exist. A variety of options are available to modify the information that is plotted, including plotting a grid of vectors instead of streamlines, plotting streamlines from arbitrary starting points and turning on and off -various features of the plot. +various features of the plot. To illustrate some of these possibilities, consider a phase plane plot for an inverted pendulum system, which is created using a mesh grid: diff --git a/doc/releases.rst b/doc/releases.rst index 88a76775a..7bc5c0f46 100644 --- a/doc/releases.rst +++ b/doc/releases.rst @@ -27,6 +27,7 @@ the ability to index systems and signal using signal labels. .. toctree:: :maxdepth: 1 + releases/0.10.2-notes releases/0.10.1-notes releases/0.10.0-notes diff --git a/doc/releases/0.10.1-notes.rst b/doc/releases/0.10.1-notes.rst index dd0939021..8b99100f2 100644 --- a/doc/releases/0.10.1-notes.rst +++ b/doc/releases/0.10.1-notes.rst @@ -2,8 +2,8 @@ .. _version-0.10.1: -Version 0.10.1 Release Notes (current) --------------------------------------- +Version 0.10.1 Release Notes +---------------------------- * Released: 17 Aug 2024 * `GitHub release page diff --git a/doc/releases/0.10.2-notes.rst b/doc/releases/0.10.2-notes.rst new file mode 100644 index 000000000..3e13239aa --- /dev/null +++ b/doc/releases/0.10.2-notes.rst @@ -0,0 +1,239 @@ +.. currentmodule:: control + +.. _version-0.10.2: + +Version 0.10.2 Release Notes (current) +-------------------------------------- + +* Released: date of release +* `GitHub release page + `_ + +This release contains numerous bug fixes and improvements, including +substantial updates to the documentation, including refactoring of the +online manual into a User Guide and a Reference Manual, as well as +more consistent and complete docstrings. In addition, signals and +systems can now be referenced using signal labels in addition to +offsets, and phase plane plots make use of the matplotlib +`~matplotlib.pyplot.streamplot` function. Numerous other changes have +been made to improve consistency of keyword arguments and function +names, with legacy aliases available. + +This version of `python-control` requires Python 3.10 or higher, NumPy +1.23 or higher (2.x recommended), and SciPy 1.8 or higher. + + +New classes, functions, and methods +................................... + +The following new classes, functions, and methods have been added in +this release: + +* `find_operating_point`: this function replaces (with a legacy alias) + the `find_eqpt` function and now returns an `OperatingPoint` object + containing the information about the operating point. + +* `combine_tf` and `split_tf`: these two new functions allow you to + create an MIMO transfer function from SISO transfer functions and + vice versa. + +* `create_statefbk_iosystem` now allows the creation of state feedback + controllers using a "reference gain" pattern (:math:`u = k_\text{f}\, + r - K x`) in addition to the default "trajectory generation" pattern + (:math:`u = u_\text{d} - K(x - x_\text{d})`). + +* `disk_margins`: compute disk-based stability margins for SISO and + MIMO systems. + +* `model_reduction`: allow specific states, inputs, or outputs to be + either eliminated or retained. + +* `place_acker`: renamed version of `acker` (which is still accessible + via an alias). + + +Bug fixes +......... + +The following bugs have been fixed in this release: + +* `phase_plane_plot`: fixed a bug in which the return value was + returning a sublist of lines rather than just a list of lines in + `cplt.lines`. + +* Processing of the timebase parameter (`dt`) for I/O systems is now + handled uniformly across all I/O system factory functions. This + affected the `zpk` function, which was defaulting to a discrete time + system to have timebase None instead of 0. + +* Multiplying (*), adding (+), or subtracting (-) a constant from any + (MIMO) LTI object now acts element-wise (same as ndarray's). This + fixes a bug where multiplying a MIMO LTI system by a constant was + multiplying by a matrix filled with the constant rather than a + diagonal matrix (scaled identity). + +* Fixed a bug where specifying an FRD system with fewer than 4 + frequency points was generating an error because the default + settings try to set up a smooth (interpolating) response and the + default degree of the fit was 3. + +* Fixed some bugs where computing poles and zeros of transfer + functions could generate spurious error messages about unsafe + casting of complex numbers to real numbers. + +* `TimeResponseData.to_pandas`: multi-trace data (e.g., the output + from a MIMO step response) was not being processed correctly. A new + column 'trace' is now generated for multi-trace responses. + +* Fixed a bug where where some arguments to `nyquist_plot` were not + being processed correctly and generated errors about unrecognized + keywords. + +* Updated `ctrb` and `obsv` to handle 1D `B` or `C` matrix correctly. + +* `bode_plot`: Fixed missing plot title when `display_margin` keyword + was used. + +* `singular_values_plot`: color cycling was not working correctly when + a list of systems or responses was provided. + +* `nyquist_plot`: The `lines` parameter of the `ControlPlot` object + now matches the documentation. A 2D array is returned with the + first index corresponding to the response (system) index and the + second index corresponding to the segment type (primary, mirror x + unscaled, scaled). + +* Fix some internal bugs that cropped up when using NumPy 2.3.1 but + were latent prior to that. + + +Improvements +............ + +The following additional improvements and changes in functionality +were implemented in this release: + +* User documentation is now divided into a User Guide that provides a + description of the main functionality of the python-control package, + along with a Reference Manual describing classes, functions, and + parameters in more detail. + +* Signal responses and I/O subsystem specifications can now use signal + names in addition to indices to get the desired inputs, outputs, and + states (e.g., `response.outputs['y0', 'y1']`). This is implemented + via a new `NamedSignal` object, which generalizes `numpy.ndarray`. + +* `find_operating_point` (legacy `find_eqpt`): accepts new parameters + `root_method` and `root_kwargs` to set the root finding algorithm + that is used. + +* `root_locus_map` now correctly handles the degenerate case of being + passed a single gain. + +* The `PoleZeroData` object now takes a `sort_loci` parameter when it + is created, with a default value of True. This is useful if you + create a `PoleZeroData` object by hand (e.g., for producing stability + diagrams). + +* Factory functions for I/O system creation are now consistent in + terms of copying signal/system names, overriding system/signal + names, and converting between classes. + +* The `tf` factory function to allow a 2D list of SISO transfer + functions to be given as a means of creating a MIMO transfer + function (use the new `combine_tf` function). + +* The `nlsys` factory function can now create a `NonlinearIOSystem` + representation of a `StateSpace` system (passed as the first + argument to `nlsys`). + +* LTI systems now have member functions for computing the various time + responses and generating frequency domain plots. See `LTI.to_ss`, + `LTI.to_tf`, `LTI.bode_plot`, `LTI.nyquist_plot`, `LTI.nichols_plot` + and `LTI.forced_response`, `LTI.impulse_response`, + `LTI.initial_response`, `LTI.step_response`. + +* String representations of I/O systems (accessed via `repr`, `print`, + and `str`) have been updated to create a more consistent form and + provide more useful information. See + :ref:`displaying-lti-system-information` for more information. + +* Binary operations between MIMO and SISO functions are now supported, + with the SISO system being converted to a MIMO system as if it were + a scalar. + +* `nyquist_response`: generates an error if you force the system to + evaluate the dynamics at a pole. + +* `phase_crossover_frequencies`: turned off spurious warning messages. + +* `ss2tf`: added new `method=scipy` capability, allowing `ss2tf` to + work on MIMO systems even if Slycot is not present. + +* `flatsys.solve_flat_optimal` (legacy `flatsys.solve_flat_ocp`): + allows scalar time vector. + +* Improved checking of matrix shapes and better error messages in + state space factory functions and other operations where matrices + are passed as arguments. + +* `FrequencyResponseData`: use `~FrequencyResponseData.complex` to + access the (squeeze processed) complex frequency response (instead + of the legacy `response` property) and + `~FrequencyResponseData.frdata ` to access + the 3D frequency response data array (instead of the legacy `fresp` + attribute). + +* Time response and optimization function keywords have been + regularized to allow consistent use of keywords across related + functions: + + - Parameters specifying the inputs, outputs, and states are referred + to as `inputs`, `outputs`, and `states` consistently throughout the + functions. + + - Variables associated with inputs, outputs, states and time use + those words plus an appropriate modifier: `initial_state`, + `final_output`, `input_indices`, etc. + + - Aliases are used both to maintain backward compatibility and to + allow shorthand descriptions: e.g., `U`, `Y`, `X0`. Short form + aliases are documented in docstrings by listing the parameter as + ``long_form (or sf) : type``. + + - Existing legacy keywords are allowed and generate a + `PendingDeprecationWarning`. Specifying a parameter value in two + different ways (e.g., via long form and an alias) generates a + `TypeError`. + +* `phase_plane_plot`: makes use of the matplotlib + `~matplotlib.pyplot.streamplot` function to provide better default + phase plane diagrams. + +* `root_locus_plot`: added by the ability to recompute the root locus + when zooming in on portions of the root locus diagram. + +* `nyquist_plot`: updated the rescaling algorithm to use a more + gradual change in the magnitude of the Nyquist curve. The + `blend_fraction` parameter can be used to start the rescaling prior + to reaching `max_curve_magnitude`, giving less confusing plots. Some + default parameter values have been adjusted to improve Nyquist + plots. + + +Deprecations +............ + +The following functions have been newly deprecated in this release and +generate a warning message when used: + +* `FrequencyResponseData.response`: use + `FrequencyResponseData.complex` to return the complex value of the + frequency response. + +* `FrequencyResponseData.fresp`: use `FrequencyResponseData.frdata + ` to access the raw 3D frequency response + data array. + +The listed items are slated to be removed in future releases (usually +the next major or minor version update). diff --git a/doc/requirements.txt b/doc/requirements.txt index 5fdf9113d..ded0c7087 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -7,4 +7,4 @@ sphinx-copybutton numpydoc ipykernel nbsphinx -docutils==0.16 # pin until sphinx_rtd_theme is compatible with 0.17 or later +docutils diff --git a/doc/test_sphinxdocs.py b/doc/test_sphinxdocs.py index 1a49f357c..80024c23d 100644 --- a/doc/test_sphinxdocs.py +++ b/doc/test_sphinxdocs.py @@ -48,7 +48,6 @@ # Functons that we can skip object_skiplist = [ - control.NamedSignal, # np.ndarray members cause errors control.FrequencyResponseList, # Use FrequencyResponseData control.TimeResponseList, # Use TimeResponseData control.common_timebase, # mainly internal use