From 562701cfdf610aa3f926503432f14e2aefc5bb75 Mon Sep 17 00:00:00 2001 From: Henk van der Laak Date: Tue, 21 Feb 2023 12:44:37 +0100 Subject: [PATCH 1/9] Enable doctest --- .github/conda-env/doctest-env.yml | 19 +++++++++++++ .github/workflows/doctest.yml | 47 +++++++++++++++++++++++++++++++ doc/Makefile | 7 +++-- doc/conf.py | 3 +- 4 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 .github/conda-env/doctest-env.yml create mode 100644 .github/workflows/doctest.yml diff --git a/.github/conda-env/doctest-env.yml b/.github/conda-env/doctest-env.yml new file mode 100644 index 000000000..f46b239cd --- /dev/null +++ b/.github/conda-env/doctest-env.yml @@ -0,0 +1,19 @@ +name: test-env +dependencies: + - conda-build # for conda index + - pip + - coverage + - coveralls + - pytest + - pytest-cov + - pytest-timeout + - pytest-xvfb + - numpy + - matplotlib + - scipy + - sphinx + - sphinx_rtd_theme + - ipykernel + - nbsphinx + - docutils + - numpydoc diff --git a/.github/workflows/doctest.yml b/.github/workflows/doctest.yml new file mode 100644 index 000000000..62638d104 --- /dev/null +++ b/.github/workflows/doctest.yml @@ -0,0 +1,47 @@ +name: Doctest + +on: [push, pull_request] + +jobs: + doctest-linux: + # doctest needs to run only on + # latest-greatest platform with full options + runs-on: ubuntu-latest + + steps: + - name: Checkout python-control + uses: actions/checkout@v3 + + - name: Setup Conda + uses: conda-incubator/setup-miniconda@v2 + with: + python-version: 3.11 + activate-environment: test-env + environment-file: .github/conda-env/doctest-env.yml + miniforge-version: latest + miniforge-variant: Mambaforge + channels: conda-forge + channel-priority: strict + auto-update-conda: false + auto-activate-base: false + + - name: Install full dependencies + shell: bash -l {0} + run: | + mamba install cvxopt pandas slycot + + - name: Run doctest + shell: bash -l {0} + env: + PYTHON_CONTROL_ARRAY_AND_MATRIX: ${{ matrix.array-and-matrix }} + MPLBACKEND: ${{ matrix.mplbackend }} + working-directory: doc + run: | + make html + make doctest + + - name: Archive results + uses: actions/upload-artifact@v3 + with: + name: doctest-output + path: doc/_build/doctest/output.txt diff --git a/doc/Makefile b/doc/Makefile index b2f9eaeed..a38e94b17 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -15,10 +15,11 @@ help: .PHONY: help Makefile # Rules to create figures -FIGS = classes.pdf -classes.pdf: classes.fig; fig2dev -Lpdf $< $@ +FIGS = classes.png +classes.png: classes.fig + fig2dev -Lpng $< $@ # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -html pdf clean: Makefile $(FIGS) +html pdf clean doctest: Makefile $(FIGS) @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/conf.py b/doc/conf.py index e2e420104..9e78a47b7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -56,7 +56,8 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.napoleon', 'sphinx.ext.intersphinx', 'sphinx.ext.imgmath', - 'sphinx.ext.autosummary', 'nbsphinx', 'numpydoc', 'sphinx.ext.linkcode' + 'sphinx.ext.autosummary', 'nbsphinx', 'numpydoc', + 'sphinx.ext.linkcode', 'sphinx.ext.doctest' ] # scan documents for autosummary directives and generate stub pages for each. From c913c8f02bf75ed87108bba221f01e70aade446e Mon Sep 17 00:00:00 2001 From: Henk van der Laak Date: Tue, 21 Feb 2023 13:08:43 +0100 Subject: [PATCH 2/9] Docs update --- control/bdalg.py | 76 +++++++++++++++++++---- control/bench/time_freqresp.py | 20 +++--- control/canonical.py | 90 +++++++++++++++++++++++++++ control/config.py | 71 +++++++++++++++++++++- control/ctrlutil.py | 56 +++++++++++++++-- control/delay.py | 14 +++++ control/descfcn.py | 72 ++++++++++++++++++++-- control/dtime.py | 22 +++++-- control/exception.py | 28 +++++++++ control/frdata.py | 28 ++++++++- control/freqplot.py | 49 +++++++++------ control/grid.py | 2 +- control/iosys.py | 73 +++++++++++++++------- control/lti.py | 31 +++++++--- control/margins.py | 14 +++-- control/matlab/timeresp.py | 31 ++++++++-- control/matlab/wrappers.py | 22 ++++--- control/modelsimp.py | 33 +++++++--- control/robust.py | 107 ++++++++++++++++++++++++--------- control/sisotool.py | 6 +- control/statefbk.py | 39 ++++++++---- control/statesp.py | 21 +++---- control/stochsys.py | 8 +-- control/timeresp.py | 24 ++++++-- control/xferfcn.py | 34 ++++++++--- doc/classes.png | Bin 0 -> 9026 bytes doc/classes.rst | 2 +- doc/control.rst | 5 ++ 28 files changed, 789 insertions(+), 189 deletions(-) create mode 100644 doc/classes.png diff --git a/control/bdalg.py b/control/bdalg.py index d1baaa410..f93e7d89c 100644 --- a/control/bdalg.py +++ b/control/bdalg.py @@ -99,9 +99,19 @@ def series(sys1, *sysn): Examples -------- - >>> sys3 = series(sys1, sys2) # Same as sys3 = sys2 * sys1 + >>> from control import rss, series - >>> sys5 = series(sys1, sys2, sys3, sys4) # More systems + >>> G1 = rss(3) + >>> G2 = rss(4) + >>> G = series(G1, G2) # Same as sys3 = sys2 * sys1 + >>> G.ninputs, G.noutputs, G.nstates + (1, 1, 7) + + >>> G1 = rss(2, inputs=2, outputs=3) + >>> G2 = rss(3, inputs=3, outputs=1) + >>> G = series(G1, G2) # Same as sys3 = sys2 * sys1 + >>> G.ninputs, G.noutputs, G.nstates + (2, 1, 5) """ from functools import reduce @@ -146,9 +156,19 @@ def parallel(sys1, *sysn): Examples -------- - >>> sys3 = parallel(sys1, sys2) # Same as sys3 = sys1 + sys2 + >>> from control import parallel, rss + + >>> G1 = rss(3) + >>> G2 = rss(4) + >>> G = parallel(G1, G2) # Same as sys3 = sys1 + sys2 + >>> G.ninputs, G.noutputs, G.nstates + (1, 1, 7) - >>> sys5 = parallel(sys1, sys2, sys3, sys4) # More systems + >>> G1 = rss(3, inputs=3, outputs=4) + >>> G2 = rss(4, inputs=3, outputs=4) + >>> G = parallel(G1, G2) # Add another system + >>> G.ninputs, G.noutputs, G.nstates + (3, 4, 7) """ from functools import reduce @@ -174,7 +194,15 @@ def negate(sys): Examples -------- - >>> sys2 = negate(sys1) # Same as sys2 = -sys1. + >>> from control import negate, tf + + >>> G = tf([2],[1, 1]) + >>> G.dcgain() > 0 + True + + >>> Gn = negate(G) # Same as sys2 = -sys1. + >>> Gn.dcgain() < 0 + True """ return -sys @@ -222,6 +250,16 @@ def feedback(sys1, sys2=1, sign=-1): the corresponding feedback function is used. If `sys1` and `sys2` are both scalars, then TransferFunction.feedback is used. + Examples + -------- + >>> from control import feedback, rss + + >>> G = rss(3, inputs=2, outputs=5) + >>> C = rss(4, inputs=5, outputs=2) + >>> T = feedback(G, C, sign=1) + >>> T.ninputs, T.noutputs, T.nstates + (2, 5, 7) + """ # Allow anything with a feedback function to call that function try: @@ -278,9 +316,19 @@ def append(*sys): Examples -------- - >>> sys1 = ss([[1., -2], [3., -4]], [[5.], [7]], [[6., 8]], [[9.]]) - >>> sys2 = ss([[-1.]], [[1.]], [[1.]], [[0.]]) - >>> sys = append(sys1, sys2) + >>> from control import append, rss + >>> G1 = rss(3) + + >>> G2 = rss(4) + >>> G = append(G1, G2) + >>> G.ninputs, G.noutputs, G.nstates + (2, 2, 7) + + >>> G1 = rss(3, inputs=2, outputs=4) + >>> G2 = rss(4, inputs=1, outputs=4) + >>> G = append(G1, G2) + >>> G.ninputs, G.noutputs, G.nstates + (3, 8, 7) """ s1 = ss._convert_to_statespace(sys[0]) @@ -323,11 +371,13 @@ def connect(sys, Q, inputv, outputv): Examples -------- - >>> sys1 = ss([[1., -2], [3., -4]], [[5.], [7]], [[6, 8]], [[9.]]) - >>> sys2 = ss([[-1.]], [[1.]], [[1.]], [[0.]]) - >>> sys = append(sys1, sys2) - >>> Q = [[1, 2], [2, -1]] # negative feedback interconnection - >>> sysc = connect(sys, Q, [2], [1, 2]) + >>> from control import append, connect, rss + + >>> G = rss(7, inputs=2, outputs=2) + >>> K = [[1, 2], [2, -1]] # negative feedback interconnection + >>> T = connect(G, K, [2], [1, 2]) + >>> T.ninputs, T.noutputs, T.nstates + (1, 2, 7) Notes ----- diff --git a/control/bench/time_freqresp.py b/control/bench/time_freqresp.py index 3ae837082..4da2bcdc4 100644 --- a/control/bench/time_freqresp.py +++ b/control/bench/time_freqresp.py @@ -3,12 +3,14 @@ from numpy import logspace from timeit import timeit -nstates = 10 -sys = rss(nstates) -sys_tf = tf(sys) -w = logspace(-1,1,50) -ntimes = 1000 -time_ss = timeit("sys.freqquency_response(w)", setup="from __main__ import sys, w", number=ntimes) -time_tf = timeit("sys_tf.frequency_response(w)", setup="from __main__ import sys_tf, w", number=ntimes) -print("State-space model on %d states: %f" % (nstates, time_ss)) -print("Transfer-function model on %d states: %f" % (nstates, time_tf)) + +if __name__ == '__main__': + nstates = 10 + sys = rss(nstates) + sys_tf = tf(sys) + w = logspace(-1,1,50) + ntimes = 1000 + time_ss = timeit("sys.freqquency_response(w)", setup="from __main__ import sys, w", number=ntimes) + time_tf = timeit("sys_tf.frequency_response(w)", setup="from __main__ import sys_tf, w", number=ntimes) + print("State-space model on %d states: %f" % (nstates, time_ss)) + print("Transfer-function model on %d states: %f" % (nstates, time_tf)) diff --git a/control/canonical.py b/control/canonical.py index e714e5b8d..f8aa0ff2e 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -36,6 +36,28 @@ def canonical_form(xsys, form='reachable'): System in desired canonical form, with state 'z' T : (M, M) real ndarray Coordinate transformation matrix, z = T * x + + Examples + -------- + >>> from control import canonical_form, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss(G) + + >>> Gc, T = canonical_form(Gs) # default reachable + >>> Gc.B # doctest: +SKIP + matrix([[1.], + [0.]]) + + >>> Gc, T = canonical_form(Gs, 'observable') + >>> Gc.C # doctest: +SKIP + matrix([[1., 0.]]) + + >>> Gc, T = canonical_form(Gs, 'modal') + >>> Gc.A # doctest: +SKIP + matrix([[-2., 0.], + [ 0., -1.]]) + """ # Call the appropriate tranformation function @@ -65,6 +87,19 @@ def reachable_form(xsys): System in reachable canonical form, with state `z` T : (M, M) real ndarray Coordinate transformation: z = T * x + + Examples + -------- + >>> from control import reachable_form, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss(G) + + >>> Gc, T = reachable_form(Gs) # default reachable + >>> Gc.B # doctest: +SKIP + matrix([[1.], + [0.]]) + """ # Check to make sure we have a SISO system if not issiso(xsys): @@ -119,6 +154,18 @@ def observable_form(xsys): System in observable canonical form, with state `z` T : (M, M) real ndarray Coordinate transformation: z = T * x + + Examples + -------- + >>> from control import observable_form, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss(G) + + >>> Gc, T = observable_form(Gs) + >>> Gc.C # doctest: +SKIP + matrix([[1., 0.]]) + """ # Check to make sure we have a SISO system if not issiso(xsys): @@ -177,6 +224,24 @@ def similarity_transform(xsys, T, timescale=1, inverse=False): zsys : StateSpace object System in transformed coordinates, with state 'z' + + Examples + -------- + >>> import numpy as np + >>> from control import similarity_transform, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss(G) + >>> Gs.A # doctest: +SKIP + matrix([[-3., -2.], + [ 1., 0.]]) + + >>> T = np.array([[0,1],[1,0]]) + >>> Gt = similarity_transform(Gs, T) + >>> Gt.A # doctest: +SKIP + matrix([[ 0., 1.], + [-2., -3.]]) + """ # Create a new system, starting with a copy of the old one zsys = StateSpace(xsys) @@ -370,6 +435,18 @@ def bdschur(a, condmax=None, sort=None): If `sort` is 'discrete', the blocks are sorted as for 'continuous', but applied to log of eigenvalues (i.e., continuous-equivalent eigenvalues). + + Examples + -------- + >>> from control import bdschur, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss(G) + >>> amodal, tmodal, blksizes = bdschur(Gs.A) + >>> amodal #doctest: +SKIP + array([[-2., 0.], + [ 0., -1.]]) + """ if condmax is None: condmax = np.finfo(np.float64).eps ** -0.5 @@ -436,6 +513,19 @@ def modal_form(xsys, condmax=None, sort=False): System in modal canonical form, with state `z` T : (M, M) ndarray Coordinate transformation: z = T * x + + Examples + -------- + >>> from control import modal_form, tf, tf2ss + + >>> G = tf([1],[1, 3, 2]) + >>> Gs = tf2ss((G)) + + >>> Gc, T = modal_form(Gs) # default reachable + >>> Gc.A # doctest: +SKIP + matrix([[-2., 0.], + [ 0., -1.]]) + """ if sort: diff --git a/control/config.py b/control/config.py index 37763a6b8..4bb7648a9 100644 --- a/control/config.py +++ b/control/config.py @@ -65,9 +65,22 @@ def set_defaults(module, **keywords): """Set default values of parameters for a module. The set_defaults() function can be used to modify multiple parameter - values for a module at the same time, using keyword arguments: + values for a module at the same time, using keyword arguments. - control.set_defaults('module', param1=val, param2=val) + Examples + -------- + >>> from control import defaults, reset_defaults, set_defaults + + >>> defaults['freqplot.number_of_samples'] + 1000 + >>> set_defaults('freqplot', number_of_samples=100) + >>> defaults['freqplot.number_of_samples'] + 100 + + >>> # do some customized freqplotting + >>> reset_defaults() + >>> defaults['freqplot.number_of_samples'] + 1000 """ if not isinstance(module, str): @@ -80,7 +93,24 @@ def set_defaults(module, **keywords): def reset_defaults(): - """Reset configuration values to their default (initial) values.""" + """Reset configuration values to their default (initial) values. + + Examples + -------- + >>> from control import defaults, reset_defaults, set_defaults + + >>> defaults['freqplot.number_of_samples'] + 1000 + >>> set_defaults('freqplot', number_of_samples=100) + >>> defaults['freqplot.number_of_samples'] + 100 + + >>> # do some customized freqplotting + >>> reset_defaults() + >>> defaults['freqplot.number_of_samples'] + 1000 + + """ # System level defaults defaults.update(_control_defaults) @@ -181,6 +211,14 @@ def use_matlab_defaults(): rad/sec, with grids * State space class and functions use Numpy matrix objects + Examples + -------- + >>> from control import use_matlab_defaults, reset_defaults + + >>> use_matlab_defaults() + >>> # do some matlab style plotting + >>> reset_defaults() + """ set_defaults('freqplot', dB=True, deg=True, Hz=False, grid=True) set_defaults('statesp', use_numpy_matrix=True) @@ -195,6 +233,14 @@ def use_fbs_defaults(): frequency in rad/sec, no grid * Nyquist plots use dashed lines for mirror image of Nyquist curve + Examples + -------- + >>> from control import use_fbs_defaults, reset_defaults + + >>> use_fbs_defaults() + >>> # do some FBS style plotting + >>> reset_defaults() + """ set_defaults('freqplot', dB=False, deg=True, Hz=False, grid=False) set_defaults('nyquist', mirror_style='--') @@ -222,6 +268,15 @@ class and functions. If flat is `False`, then matrices are Prior to release 0.9.x, the default type for 2D arrays is the Numpy `matrix` class. Starting in release 0.9.0, the default type for state space operations is a 2D array. + + Examples + -------- + >>> from control import use_numpy_matrix, reset_defaults + + >>> use_numpy_matrix(True, False) + >>> # do some legacy calculations using np.matrix + >>> reset_defaults() + """ if flag and warn: warnings.warn("Return type numpy.matrix is deprecated.", @@ -236,6 +291,16 @@ def use_legacy_defaults(version): ---------- version : string Version number of the defaults desired. Ranges from '0.1' to '0.8.4'. + + Examples + -------- + >>> from control import use_legacy_defaults, reset_defaults + + >>> use_legacy_defaults("0.9.0") + (0, 9, 0) + >>> # do some legacy style plotting + >>> reset_defaults() + """ import re (major, minor, patch) = (None, None, None) # default values diff --git a/control/ctrlutil.py b/control/ctrlutil.py index 35035c7e9..ddd75812b 100644 --- a/control/ctrlutil.py +++ b/control/ctrlutil.py @@ -66,9 +66,20 @@ def unwrap(angle, period=2*math.pi): Examples -------- >>> import numpy as np - >>> theta = [5.74, 5.97, 6.19, 0.13, 0.35, 0.57] - >>> unwrap(theta, period=2 * np.pi) - [5.74, 5.97, 6.19, 6.413185307179586, 6.633185307179586, 6.8531853071795865] + >>> from control import unwrap + >>> from pprint import pprint + + >>> # Already continuous + >>> theta1 = np.array([1.0, 1.5, 2.0, 2.5, 3.0]) * np.pi + >>> theta2 = unwrap(theta1) + >>> theta2/np.pi # doctest: +SKIP + array([1. , 1.5, 2. , 2.5, 3. ]) + + >>> # Wrapped, discontinuous + >>> theta1 = np.array([1.0, 1.5, 0.0, 0.5, 1.0]) * np.pi + >>> theta2 = unwrap(theta1) + >>> theta2/np.pi # doctest: +SKIP + array([1. , 1.5, 2. , 2.5, 3. ]) """ dangle = np.diff(angle) @@ -78,7 +89,22 @@ def unwrap(angle, period=2*math.pi): return angle def issys(obj): - """Return True if an object is a system, otherwise False""" + """Return True if an object is a Linear Time Invariant (LTI) system, + otherwise False + + Examples + -------- + >>> from control import issys, tf, InputOutputSystem, LinearIOSystem + + >>> G = tf([1],[1, 1]) + >>> issys(G) + True + + >>> K = InputOutputSystem() # Not necessarily LTI! + >>> issys(K) + False + + """ return isinstance(obj, lti.LTI) def db2mag(db): @@ -98,6 +124,17 @@ def db2mag(db): mag : float or ndarray corresponding magnitudes + Examples + -------- + >>> import numpy as np + >>> from control import db2mag + + >>> db2mag(-40.0) # doctest: +SKIP + 0.01 + + >>> db2mag(np.array([0, -20])) # doctest: +SKIP + array([1. , 0.1]) + """ return 10. ** (db / 20.) @@ -118,5 +155,16 @@ def mag2db(mag): db : float or ndarray corresponding values in decibels + Examples + -------- + >>> import numpy as np + >>> from control import mag2db + + >>> mag2db(10.0) # doctest: +SKIP + 20.0 + + >>> mag2db(np.array([1, 0.01])) # doctest: +SKIP + array([ 0., -40.]) + """ return 20. * np.log10(mag) diff --git a/control/delay.py b/control/delay.py index b5867ada8..42110c1ff 100644 --- a/control/delay.py +++ b/control/delay.py @@ -74,6 +74,20 @@ def pade(T, n=1, numdeg=None): Ed. pp. 572-574 2. M. Vajta, "Some remarks on Padé-approximations", 3rd TEMPUS-INTCOM Symposium + + Examples + -------- + >>> from control import pade + + >>> delay = 1 + >>> num, den = pade(delay, 5) + >>> len(num), len(den) + (6, 6) + + >>> num, den = pade(delay, 5, -2) + >>> len(num), len(den) + (4, 6) + """ if numdeg is None: numdeg = n diff --git a/control/descfcn.py b/control/descfcn.py index 41d273f38..dfd2c2d4b 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -120,6 +120,17 @@ def describing_function( TypeError If A[i] < 0 or if A[i] = 0 and the function F(0) is non-zero. + Examples + -------- + >>> import numpy as np + >>> from control import describing_function + + >>> F = lambda x: np.exp(-x) # Basic diode description + >>> A = np.logspace(-1, 1, 20) # Amplitudes from 0.1 to 10.0 + >>> df_values = describing_function(F, A) + >>> len(df_values) + 20 + """ # If there is an analytical solution, trying using that first if try_method and hasattr(F, 'describing_function'): @@ -238,11 +249,15 @@ def describing_function_plot( Examples -------- - >>> H_simple = ct.tf([8], [1, 2, 2, 1]) - >>> F_saturation = ct.descfcn.saturation_nonlinearity(1) + >>> import numpy as np + >>> from control import describing_function_plot, saturation_nonlinearity, tf + + >>> H_simple = tf([8], [1, 2, 2, 1]) + >>> F_saturation = saturation_nonlinearity(1) >>> amp = np.linspace(1, 4, 10) - >>> ct.describing_function_plot(H_simple, F_saturation, amp) - [(3.344008947853124, 1.414213099755523)] + >>> points = describing_function_plot(H_simple, F_saturation, amp) + >>> len(points) + 1 """ # Decide whether to turn on warnings or not @@ -354,6 +369,19 @@ class saturation_nonlinearity(DescribingFunctionNonlinearity): functions will not have zero bias and hence care must be taken in using the nonlinearity for analysis. + Examples + -------- + >>> from control import saturation_nonlinearity + + >>> nl = saturation_nonlinearity(5) + >>> f = lambda x: round(nl(x)) + >>> f(1) + 1 + >>> f(10) + 5 + >>> f(-10) + -5 + """ def __init__(self, ub=1, lb=None): # Create the describing function nonlinearity object @@ -407,6 +435,23 @@ class relay_hysteresis_nonlinearity(DescribingFunctionNonlinearity): `-c <= x <= c`, the value depends on the branch of the hysteresis loop (as illustrated in Figure 10.20 of FBS2e). + Examples + -------- + >>> from control import relay_hysteresis_nonlinearity + + >>> nl = relay_hysteresis_nonlinearity(1, 2) + >>> f = lambda x: round(nl(x)) + >>> f(0) + -1 + >>> f(1) # not enough for switching on + -1 + >>> f(5) + 1 + >>> f(-1) # not enough for switching off + 1 + >>> f(-5) + -1 + """ def __init__(self, b, c): # Create the describing function nonlinearity object @@ -462,6 +507,25 @@ class friction_backlash_nonlinearity(DescribingFunctionNonlinearity): center, the output is unchanged. Otherwise, the output is given by the input shifted by `b/2`. + Examples + -------- + >>> from control import friction_backlash_nonlinearity + + >>> nl = friction_backlash_nonlinearity(2) # backlash of +/- 1 + >>> f = lambda x: round(nl(x)) + >>> f(0) + 0 + >>> f(1) # not enough to overcome backlash + 0 + >>> f(2) + 1 + >>> f(1) + 1 + >>> f(0) # not enough to overcome backlash + 1 + >>> f(-1) + 0 + """ def __init__(self, b): diff --git a/control/dtime.py b/control/dtime.py index 724eafb76..164cc6874 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -109,8 +109,15 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> sysc = TransferFunction([1], [1, 2, 1]) - >>> sysd = sample_system(sysc, 1, method='bilinear') + >>> from control import sample_system, tf + + >>> Gc = tf([1], [1, 2, 1]) + >>> Gc.isdtime() + False + >>> Gd = sample_system(Gc, 1, method='bilinear') + >>> Gd.isdtime() + True + """ # Make sure we have a continuous time system @@ -150,8 +157,15 @@ def c2d(sysc, Ts, method='zoh', prewarp_frequency=None): Examples -------- - >>> sysc = TransferFunction([1], [1, 2, 1]) - >>> sysd = sample_system(sysc, 1, method='bilinear') + >>> from control import sample_system, tf + + >>> Gc = tf([1], [1, 2, 1]) + >>> Gc.isdtime() + False + >>> Gd = sample_system(Gc, 1, method='bilinear') + >>> Gd.isdtime() + True + """ # Call the sample_system() function to do the work diff --git a/control/exception.py b/control/exception.py index 575c78c0a..338de06f6 100644 --- a/control/exception.py +++ b/control/exception.py @@ -63,6 +63,15 @@ class ControlNotImplemented(NotImplementedError): # Utility function to see if slycot is installed slycot_installed = None def slycot_check(): + """Return True if slycot is installed, otherwise False. + + Examples + -------- + >>> from control import slycot_check + >>> slycot_check() + True + + """ global slycot_installed if slycot_installed is None: try: @@ -76,6 +85,16 @@ def slycot_check(): # Utility function to see if pandas is installed pandas_installed = None def pandas_check(): + """Return True if pandas is installed, otherwise False. + + Examples + -------- + >>> from control import pandas_check + >>> pandas_check() + True + + """ + global pandas_installed if pandas_installed is None: try: @@ -88,6 +107,15 @@ def pandas_check(): # Utility function to see if cvxopt is installed cvxopt_installed = None def cvxopt_check(): + """Return True if cvxopt is installed, otherwise False. + + Examples + -------- + >>> from control import cvxopt_check + >>> cvxopt_check() + True + + """ global cvxopt_installed if cvxopt_installed is None: try: diff --git a/control/frdata.py b/control/frdata.py index c78607a07..22f3b121e 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -102,7 +102,7 @@ class FrequencyResponseData(LTI): second dimension corresponding to the input index, and the 3rd dimension corresponding to the frequency points in omega. For example, - >>> frdata[2,5,:] = numpy.array([1., 0.8-0.2j, 0.2-0.8j]) + >>> frdata[2,5,:] = numpy.array([1., 0.8-0.2j, 0.2-0.8j]) # doctest: +SKIP means that the frequency response from the 6th input to the 3rd output at the frequencies defined in omega is set to the array above, i.e. the rows @@ -672,9 +672,18 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1): a frequency response data at the specified omega. If sys is a scalar, then the number of inputs and outputs can be specified manually, as in: ++ + >>> import numpy as np + >>> from control.frdata import _convert_to_FRD + >>> omega = np.logspace(-1, 1) >>> frd = _convert_to_FRD(3., omega) # Assumes inputs = outputs = 1 - >>> frd = _convert_to_FRD(1., omegs, inputs=3, outputs=2) + >>> frd.ninputs, frd.noutputs + (1, 1) + + >>> frd = _convert_to_FRD(1., omega, inputs=3, outputs=2) + >>> frd.ninputs, frd.noutputs + (3, 2) In the latter example, sys's matrix transfer function is [[1., 1., 1.] [1., 1., 1.]]. @@ -755,5 +764,20 @@ def frd(*args): See Also -------- FRD, ss, tf + + Examples + -------- + >>> from control import frd, tf + + >>> # Create from measurements + >>> response = [1.0, 1.0, 0.5] + >>> freqs = [1, 10, 100] + >>> F = frd(response, freqs) + + >>> G = tf([1], [1, 1]) + >>> freqs = [1, 10, 100] + >>> F = frd(G, freqs) + + """ return FRD(*args) diff --git a/control/freqplot.py b/control/freqplot.py index a749c3f6d..56b946aa3 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -174,8 +174,10 @@ def bode_plot(syslist, omega=None, Examples -------- - >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> mag, phase, omega = bode(sys) + >>> from control import bode_plot, ss + + >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> Gmag, Gphase, Gomega = bode_plot(G) """ # Make a copy of the kwargs dictionary since we will modify it @@ -692,8 +694,11 @@ def nyquist_plot( Examples -------- - >>> sys = ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) - >>> count = nyquist_plot(sys) + >>> from control import nyquist_plot, zpk + + >>> G = zpk([],[-1,-2,-3], gain=100) + >>> nyquist_plot(G) + 2 """ # Check to see if legacy 'Plot' keyword was used @@ -1258,6 +1263,14 @@ def gangof4_plot(P, C, omega=None, **kwargs): Returns ------- None + + Examples + -------- + >>> from control import gangof4_plot, tf + >>> P = tf([1],[1, 1]) + >>> C = tf([2],[1]) + >>> gangof4_plot(P, C) + """ if not P.issiso() or not C.issiso(): # TODO: Add MIMO go4 plots. @@ -1402,14 +1415,14 @@ def singular_values_plot(syslist, omega=None, Examples -------- >>> import numpy as np + >>> from control import tf, singular_values_plot + + >>> omegas = np.logspace(-4, 1, 1000) >>> den = [75, 1] - >>> sys = TransferFunction( - [[[87.8], [-86.4]], [[108.2], [-109.6]]], [[den, den], [den, den]]) - >>> omega = np.logspace(-4, 1, 1000) - >>> sigma, omega = singular_values_plot(sys, plot=True) - >>> singular_values_plot(sys, 0.0, plot=False) - (array([[197.20868123], - [ 1.39141948]]), array([0.])) + >>> G = tf([[[87.8], [-86.4]], [[108.2], [-109.6]]], [[den, den], [den, den]]) + >>> sigmas, omegas = singular_values_plot(G, omega=omegas, plot=False) + + >>> sigmas, omegas = singular_values_plot(G, 0.0, plot=False) """ @@ -1625,9 +1638,12 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None, Examples -------- - >>> from matlab import ss - >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> omega = _default_frequency_range(sys) + >>> from control import ss + + >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> omega = _default_frequency_range(G) + >>> omega.min(), omega.max() + (0.1, 100.0) """ # Set default values for options @@ -1755,11 +1771,6 @@ def gen_prefix(pow1000): 'y'][8 - pow1000] # yocto (10^-24) -def find_nearest_omega(omega_list, omega): - omega_list = np.asarray(omega_list) - return omega_list[(np.abs(omega_list - omega)).argmin()] - - # Function aliases bode = bode_plot nyquist = nyquist_plot diff --git a/control/grid.py b/control/grid.py index 07ca4a59d..785ec2743 100644 --- a/control/grid.py +++ b/control/grid.py @@ -156,7 +156,7 @@ def nogrid(): def zgrid(zetas=None, wns=None, ax=None): - '''Draws discrete damping and frequency grid''' + """Draws discrete damping and frequency grid""" fig = plt.gcf() if ax is None: diff --git a/control/iosys.py b/control/iosys.py index 6b0f6cfaa..410fa5c62 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -2371,10 +2371,14 @@ def ss(*args, **kwargs): Examples -------- - >>> # Create a Linear I/O system object from from for matrices - >>> sys1 = ss([[1, -2], [3 -4]], [[5], [7]], [[6, 8]], [[9]]) + Create a Linear I/O system object from matrices. + + >>> from control import ss, tf + + >>> G = ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) + + Convert a TransferFunction to a StateSpace object. - >>> # Convert a TransferFunction to a StateSpace object. >>> sys_tf = tf([2.], [1., 3]) >>> sys2 = ss(sys_tf) @@ -2495,6 +2499,16 @@ def drss(*args, **kwargs): function calls :func:`rss` using either the `dt` keyword provided by the user or `dt=True` if not specified. + Examples + -------- + >>> from control import drss + >>> G = drss(states=4, outputs=2, inputs=1) + >>> G.ninputs, G.noutputs, G.nstates + (1, 2, 4) + >>> G.isdtime() + True + + """ # Make sure the timebase makes sense if 'dt' in kwargs: @@ -2583,12 +2597,16 @@ def tf2io(*args, **kwargs): Examples -------- + >>> from control import tf2ss, tf + >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] >>> sys1 = tf2ss(num, den) >>> sys_tf = tf(num, den) - >>> sys2 = tf2ss(sys_tf) + >>> G = tf2ss(sys_tf) + >>> G.ninputs, G.noutputs, G.nstates + (2, 2, 8) """ # Convert the system to a state space system @@ -2765,26 +2783,31 @@ def interconnect( Examples -------- - >>> P = control.LinearIOSystem( - >>> control.rss(2, 2, 2, strictly_proper=True), name='P') - >>> C = control.LinearIOSystem(control.rss(2, 2, 2), name='C') - >>> T = control.interconnect( - >>> [P, C], - >>> connections = [ - >>> ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'], - >>> ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']], - >>> inplist = ['C.u[0]', 'C.u[1]'], - >>> outlist = ['P.y[0]', 'P.y[1]'], - >>> ) + >>> from control import LinearIOSystem, interconnect, rss, summing_junction, tf + + >>> P = LinearIOSystem( + ... rss(2, 2, 2, strictly_proper=True), + ... name='P') + >>> C = LinearIOSystem( + ... rss(2, 2, 2), + ... name='C') + >>> T = interconnect( + ... [P, C], + ... connections = [ + ... ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'], + ... ['C.u[0]', '-P.y[0]'], ['C.u[1]', '-P.y[1]']], + ... inplist = ['C.u[0]', 'C.u[1]'], + ... outlist = ['P.y[0]', 'P.y[1]'], + ... ) For a SISO system, this example can be simplified by using the :func:`~control.summing_block` function and the ability to automatically interconnect signals with the same names: - >>> P = control.tf(1, [1, 0], inputs='u', outputs='y') - >>> C = control.tf(10, [1, 1], inputs='e', outputs='u') - >>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e') - >>> T = control.interconnect([P, C, sumblk], inputs='r', outputs='y') + >>> P = tf(1, [1, 0], inputs='u', outputs='y') + >>> C = tf(10, [1, 1], inputs='e', outputs='u') + >>> sumblk = summing_junction(inputs=['r', '-y'], output='e') + >>> T = interconnect([P, C, sumblk], inputs='r', outputs='y') Notes ----- @@ -2986,10 +3009,14 @@ def summing_junction( Examples -------- - >>> P = control.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y') - >>> C = control.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u') - >>> sumblk = control.summing_junction(inputs=['r', '-y'], output='e') - >>> T = control.interconnect((P, C, sumblk), inputs='r', outputs='y') + >>> from control import tf2io, summing_junction, interconnect, tf + + >>> P = tf2io(tf(1, [1, 0]), inputs='u', outputs='y') + >>> C = tf2io(tf(10, [1, 1]), inputs='e', outputs='u') + >>> sumblk = summing_junction(inputs=['r', '-y'], output='e') + >>> T = interconnect((P, C, sumblk), inputs='r', outputs='y') + >>> T.ninputs, T.noutputs, T.nstates + (1, 1, 2) """ # Utility function to parse input and output signal lists diff --git a/control/lti.py b/control/lti.py index 1bc08229d..acbf3c8c9 100644 --- a/control/lti.py +++ b/control/lti.py @@ -324,6 +324,14 @@ def damp(sys, doprint=True): wn = abs(s) Z = -real(s)/wn. + Examples + -------- + >>> from control import damp, tf + >>> G = tf([1],[1, 4]) + >>> wn, damping, poles = damp(G) # doctest: +SKIP + _____Eigenvalue______ Damping___ Frequency_ + -4 1 4 + """ wn, damping, poles = sys.damp() if doprint: @@ -386,10 +394,10 @@ def evalfr(sys, x, squeeze=None): Examples -------- - >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> evalfr(sys, 1j) - array([[ 44.8-21.4j]]) - >>> # This is the transfer function matrix evaluated at s = i. + >>> from control import ss, evalfr + + >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> fresp = evalfr(G, 1j) # evaluate at s = 1j .. todo:: Add example with MIMO system @@ -449,12 +457,10 @@ def frequency_response(sys, omega, squeeze=None): Examples -------- - >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> mag, phase, omega = freqresp(sys, [0.1, 1., 10.]) - >>> mag - array([[[ 58.8576682 , 49.64876635, 13.40825927]]]) - >>> phase - array([[[-0.05408304, -0.44563154, -0.66837155]]]) + >>> from control import ss, freqresp + + >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> mag, phase, omega = freqresp(G, [0.1, 1., 10.]) .. todo:: Add example with MIMO system @@ -488,6 +494,11 @@ def dcgain(sys): the origin, (nan + nanj) if there is a pole/zero cancellation at the origin. + >>> from control import dcgain, tf + >>> G = tf([1], [1, 2]) + >>> dcgain(G) # doctest: +SKIP + 0.5 + """ return sys.dcgain() diff --git a/control/margins.py b/control/margins.py index 662634086..955dfd278 100644 --- a/control/margins.py +++ b/control/margins.py @@ -476,9 +476,11 @@ def phase_crossover_frequencies(sys): Examples -------- - >>> tf = TransferFunction([1], [1, 2, 3, 4]) - >>> phase_crossover_frequencies(tf) - (array([ 1.73205081, 0. ]), array([-0.5 , 0.25])) + >>> from control import phase_crossover_frequencies, tf + + >>> G = tf([1], [1, 2, 3, 4]) + >>> x_omega, x_gain = phase_crossover_frequencies(G) + """ # Convert to a transfer function tf = xferfcn._convert_to_transfer_function(sys) @@ -537,8 +539,10 @@ def margin(*args): Examples -------- - >>> sys = tf(1, [1, 2, 1, 0]) - >>> gm, pm, wcg, wcp = margin(sys) + >>> from control import margin, tf + + >>> G = tf(1, [1, 2, 1, 0]) + >>> gm, pm, wcg, wcp = margin(G) """ if len(args) == 1: diff --git a/control/matlab/timeresp.py b/control/matlab/timeresp.py index 58b5e589d..cf57952f1 100644 --- a/control/matlab/timeresp.py +++ b/control/matlab/timeresp.py @@ -46,7 +46,11 @@ def step(sys, T=None, X0=0., input=0, output=None, return_x=False): Examples -------- - >>> yout, T = step(sys, T, X0) + >>> from control.matlab import step, rss + + >>> G = rss(4) + >>> yout, T = step(G) + ''' from ..timeresp import step_response @@ -115,7 +119,11 @@ def stepinfo(sysdata, T=None, yfinal=None, SettlingTimeThreshold=0.02, Examples -------- - >>> S = stepinfo(sys, T) + >>> from control.matlab import stepinfo, rss + + >>> G = rss(4) + >>> S = stepinfo(G) + """ from ..timeresp import step_info @@ -166,7 +174,11 @@ def impulse(sys, T=None, X0=0., input=0, output=None, return_x=False): Examples -------- - >>> yout, T = impulse(sys, T) + >>> from control.matlab import rss, impulse + + >>> G = rss() + >>> yout, T = impulse(G) + ''' from ..timeresp import impulse_response @@ -214,7 +226,10 @@ def initial(sys, T=None, X0=0., input=None, output=None, return_x=False): Examples -------- - >>> yout, T = initial(sys, T, X0) + >>> from control.matlab import initial, rss + + >>> G = rss(4) + >>> yout, T = initial(G) ''' from ..timeresp import initial_response @@ -261,7 +276,13 @@ def lsim(sys, U=0., T=None, X0=0.): Examples -------- - >>> yout, T, xout = lsim(sys, U, T, X0) + >>> import numpy as np + >>> from control.matlab import rss, lsim + + >>> G = rss(4) + >>> T = np.linspace(0,10) + >>> yout, T, xout = lsim(G, T=T) + ''' from ..timeresp import forced_response diff --git a/control/matlab/wrappers.py b/control/matlab/wrappers.py index 22ec95f39..b17318edf 100644 --- a/control/matlab/wrappers.py +++ b/control/matlab/wrappers.py @@ -26,11 +26,13 @@ def bode(*args, **kwargs): a list of systems can be entered, or several systems can be specified (i.e. several parameters). The sys arguments may also be interspersed with format strings. A frequency argument (array_like) - may also be added, some examples: - * >>> bode(sys, w) # one system, freq vector - * >>> bode(sys1, sys2, ..., sysN) # several systems - * >>> bode(sys1, sys2, ..., sysN, w) - * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # + plot formats + may also be added, some examples:: + + >>> bode(sys, w) # one system, freq vector # doctest: +SKIP + >>> bode(sys1, sys2, ..., sysN) # several systems # doctest: +SKIP + >>> bode(sys1, sys2, ..., sysN, w) # doctest: +SKIP + >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # + plot formats # doctest: +SKIP + omega: freq_range Range of frequencies in rad/s dB : boolean @@ -44,6 +46,8 @@ def bode(*args, **kwargs): Examples -------- + >>> from control import ss, bode + >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> mag, phase, omega = bode(sys) @@ -51,10 +55,10 @@ def bode(*args, **kwargs): Document these use cases - * >>> bode(sys, w) - * >>> bode(sys1, sys2, ..., sysN) - * >>> bode(sys1, sys2, ..., sysN, w) - * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') + * >>> bode(sys, w) # doctest: +SKIP + * >>> bode(sys1, sys2, ..., sysN) # doctest: +SKIP + * >>> bode(sys1, sys2, ..., sysN, w) # doctest: +SKIP + * >>> bode(sys1, 'plotstyle1', ..., sysN, 'plotstyleN') # doctest: +SKIP """ from ..freqplot import bode_plot diff --git a/control/modelsimp.py b/control/modelsimp.py index b1c1ae31c..93411d264 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -84,7 +84,12 @@ def hsvd(sys): Examples -------- - >>> H = hsvd(sys) + >>> from control import tf, tf2ss, hsvd + + >>> G = tf2ss(tf([1],[1, 2])) + >>> H = hsvd(G) + >>> H[0] + 0.25 """ # TODO: implement for discrete time systems @@ -135,7 +140,13 @@ def modred(sys, ELIM, method='matchdc'): Examples -------- - >>> rsys = modred(sys, ELIM, method='truncate') + >>> from control import rss, modred + + >>> G = rss(4) + >>> Gr = modred(G, [0,2], method='matchdc') + >>> Gr.nstates + 2 + """ # Check for ss system object, need a utility for this? @@ -255,7 +266,12 @@ def balred(sys, orders, method='truncate', alpha=None): Examples -------- - >>> rsys = balred(sys, orders, method='truncate') + >>> from control import balred, rss + + >>> G = rss(4) + >>> Gr = balred(G, orders=2, method='matchdc') + >>> Gr.nstates + 2 """ if method != 'truncate' and method != 'matchdc': @@ -386,7 +402,7 @@ def era(YY, m, n, nin, nout, r): Examples -------- - >>> rsys = era(YY, m, n, nin, nout, r) + >>> rsys = era(YY, m, n, nin, nout, r) # doctest: +SKIP """ raise NotImplementedError('This function is not implemented yet.') @@ -446,9 +462,12 @@ def markov(Y, U, m=None, transpose=False): Examples -------- - >>> T = numpy.linspace(0, 10, 100) - >>> U = numpy.ones((1, 100)) - >>> T, Y, _ = forced_response(tf([1], [1, 0.5], True), T, U) + >>> import numpy as np + >>> from control import forced_response, markov, tf + + >>> T = np.linspace(0, 10, 100) + >>> U = np.ones((1, 100)) + >>> T, Y = forced_response(tf([1], [1, 0.5], True), T, U) >>> H = markov(Y, U, 3, transpose=False) """ diff --git a/control/robust.py b/control/robust.py index aa5c922dc..2c2c8465e 100644 --- a/control/robust.py +++ b/control/robust.py @@ -70,7 +70,25 @@ def h2syn(P, nmeas, ncon): Examples -------- - >>> K = h2syn(P,nmeas,ncon) + >>> from control import h2syn, interconnect, ss, tf, feedback + + >>> # Unstable first order SISI system + >>> G = tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> max(G.poles()) < 0 # Is G stable? + False + + >>> # Create partitioned system with trivial unity systems + >>> P11 = tf([0], [1], inputs=['w'], outputs=['z']) + >>> P12 = tf([1], [1], inputs=['u'], outputs=['z']) + >>> P21 = tf([1], [1], inputs=['w'], outputs=['y']) + >>> P22 = G + >>> P = interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) + + >>> # Synthesize H2 optimal stabilizing controller + >>> K = h2syn(P, nmeas=1, ncon=1) + >>> T = feedback(G, K, sign=1) + >>> max(T.poles()) < 0 # Is T stable? + True """ # Check for ss system object, need a utility for this? @@ -134,7 +152,25 @@ def hinfsyn(P, nmeas, ncon): Examples -------- - >>> K, CL, gam, rcond = hinfsyn(P,nmeas,ncon) + >>> from control import hinfsyn, interconnect, ss, tf, feedback + + >>> # Unstable first order SISI system + >>> G = tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> max(G.poles()) < 0 + False + + >>> # Create partitioned system with trivial unity systems + >>> P11 = tf([0], [1], inputs=['w'], outputs=['z']) + >>> P12 = tf([1], [1], inputs=['u'], outputs=['z']) + >>> P21 = tf([1], [1], inputs=['w'], outputs=['y']) + >>> P22 = G + >>> P = interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) + + >>> # Synthesize Hinf optimal stabilizing controller + >>> K, CL, gam, rcond = hinfsyn(P, nmeas=1, ncon=1) + >>> T = feedback(G, K, sign=1) + >>> max(T.poles()) < 0 + True """ @@ -221,28 +257,33 @@ def _size_as_needed(w, wname, n): def augw(g, w1=None, w2=None, w3=None): """Augment plant for mixed sensitivity problem. - Parameters - ---------- - g: LTI object, ny-by-nu - w1: weighting on S; None, scalar, or k1-by-ny LTI object - w2: weighting on KS; None, scalar, or k2-by-nu LTI object - w3: weighting on T; None, scalar, or k3-by-ny LTI object - p: augmented plant; StateSpace object - If a weighting is None, no augmentation is done for it. At least one weighting must not be None. If a weighting w is scalar, it will be replaced by I*w, where I is ny-by-ny for w1 and w3, and nu-by-nu for w2. + Parameters + ---------- + g: LTI object, ny-by-nu + Plant + w1: None, scalar, or k1-by-ny LTI object + Weighting on S + w2: None, scalar, or k2-by-nu LTI object + Weighting on KS + w3: None, scalar, or k3-by-ny LTI object + Weighting on T + Returns ------- - p: plant augmented with weightings, suitable for submission to hinfsyn or h2syn. + p: StateSpace + Plant augmented with weightings, suitable for submission to hinfsyn or + h2syn. Raises ------ ValueError - - if all weightings are None + If all weightings are None See Also -------- @@ -331,25 +372,35 @@ def mixsyn(g, w1=None, w2=None, w3=None): Parameters ---------- - g: LTI; the plant for which controller must be synthesized - w1: weighting on s = (1+g*k)**-1; None, or scalar or k1-by-ny LTI - w2: weighting on k*s; None, or scalar or k2-by-nu LTI - w3: weighting on t = g*k*(1+g*k)**-1; None, or scalar or k3-by-ny LTI - At least one of w1, w2, and w3 must not be None. + g: LTI + The plant for which controller must be synthesized + w1: None, or scalar or k1-by-ny LTI + Weighting on S = (1+G*K)**-1 + w2: None, or scalar or k2-by-nu LTI + Weighting on K*S + w3: None, or scalar or k3-by-ny LTI + Weighting on T = G*K*(1+G*K)**-1; Returns ------- - k: synthesized controller; StateSpace object - cl: closed system mapping evaluation inputs to evaluation outputs; if - p is the augmented plant, with - [z] = [p11 p12] [w], - [y] [p21 g] [u] - then cl is the system from w->z with u=-k*y. StateSpace object. - - info: tuple with entries, in order, - - gamma: scalar; H-infinity norm of cl - - rcond: array; estimates of reciprocal condition numbers - computed during synthesis. See hinfsyn for details + k: StateSpace + Synthesized controller; + cl: StateSpace + Closed system mapping evaluation inputs to evaluation outputs. + + Let p be the augmented plant, with:: + + [z] = [p11 p12] [w] + [y] [p21 g] [u] + + then cl is the system from w->z with `u = -k*y`. + + info: tuple + gamma: scalar + H-infinity norm of cl + rcond: array + Estimates of reciprocal condition numbers + computed during synthesis. See hinfsyn for details If a weighting w is scalar, it will be replaced by I*w, where I is ny-by-ny for w1 and w3, and nu-by-nu for w2. diff --git a/control/sisotool.py b/control/sisotool.py index 8a3b3d9f7..944b6f0d1 100644 --- a/control/sisotool.py +++ b/control/sisotool.py @@ -81,8 +81,10 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None, Examples -------- - >>> sys = tf([1000], [1,25,100,0]) - >>> sisotool(sys) + >>> from control import sisotool, tf + + >>> G = tf([1000], [1,25,100,0]) + >>> sisotool(G) # doctest: +SKIP """ from .rlocus import root_locus diff --git a/control/statefbk.py b/control/statefbk.py index c76a4e31a..57479c7a1 100644 --- a/control/statefbk.py +++ b/control/statefbk.py @@ -120,6 +120,8 @@ def place(A, B, p): Examples -------- + >>> from control import place + >>> A = [[-1, -1], [0, 1]] >>> B = [[0], [1]] >>> K = place(A, B, [-2, -5]) @@ -375,8 +377,8 @@ def lqr(*args, **kwargs): Examples -------- - >>> K, S, E = lqr(sys, Q, R, [N]) - >>> K, S, E = lqr(A, B, Q, R, [N]) + >>> K, S, E = lqr(sys, Q, R, [N]) # doctest: +SKIP + >>> K, S, E = lqr(A, B, Q, R, [N]) # doctest: +SKIP """ # @@ -520,9 +522,8 @@ def dlqr(*args, **kwargs): Examples -------- - >>> K, S, E = dlqr(dsys, Q, R, [N]) - >>> K, S, E = dlqr(A, B, Q, R, [N]) - + >>> K, S, E = dlqr(dsys, Q, R, [N]) # doctest: +SKIP + >>> K, S, E = dlqr(A, B, Q, R, [N]) # doctest: +SKIP """ # @@ -993,7 +994,13 @@ def ctrb(A, B): Examples -------- - >>> C = ctrb(A, B) + >>> import numpy as np + >>> from control import ctrb, tf, tf2ss + + >>> G = tf2ss(tf([1],[1, 2, 3])) + >>> C = ctrb(G.A, G.B) + >>> np.linalg.matrix_rank(C) + 2 """ @@ -1029,7 +1036,14 @@ def obsv(A, C): Examples -------- - >>> O = obsv(A, C) + >>> import numpy as np + >>> from control import obsv, tf, tf2ss + + >>> G = tf2ss(tf([1],[1, 2, 3])) + >>> C = obsv(G.A, G.C) + >>> np.linalg.matrix_rank(C) + 2 + """ # Convert input parameters to matrices (if they aren't already) @@ -1078,10 +1092,13 @@ def gram(sys, type): Examples -------- - >>> Wc = gram(sys, 'c') - >>> Wo = gram(sys, 'o') - >>> Rc = gram(sys, 'cf'), where Wc = Rc' * Rc - >>> Ro = gram(sys, 'of'), where Wo = Ro' * Ro + >>> from control import gram, rss + + >>> G = rss(4) + >>> Wc = gram(G, 'c') + >>> Wo = gram(G, 'o') + >>> Rc = gram(G, 'cf') # where Wc = Rc' * Rc + >>> Ro = gram(G, 'of') # where Wo = Ro' * Ro """ diff --git a/control/statesp.py b/control/statesp.py index 9fff28d27..a6f1756b2 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1217,8 +1217,8 @@ def returnScipySignalLTI(self, strict=True): For instance, - >>> out = ssobject.returnScipySignalLTI() - >>> out[3][5] + >>> out = ssobject.returnScipySignalLTI() # doctest: +SKIP + >>> out[3][5] # doctest: +SKIP is a :class:`scipy.signal.lti` object corresponding to the transfer function from the 6th input to the 4th output. @@ -1362,8 +1362,10 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> sys = StateSpace(0, 1, 1, 0) - >>> sysd = sys.sample(0.5, method='bilinear') + >>> from control import StateSpace + >>> + >>> G = StateSpace(0, 1, 1, 0) + >>> sysd = G.sample(0.5, method='bilinear') """ if not self.isctime(): @@ -1526,14 +1528,7 @@ def _convert_to_statespace(sys): If sys is already a state space, then it is returned. If sys is a transfer function object, then it is converted to a state space and - returned. If sys is a scalar, then the number of inputs and outputs can - be specified manually, as in: - - >>> sys = _convert_to_statespace(3.) # Assumes inputs = outputs = 1 - >>> sys = _convert_to_statespace(1., inputs=3, outputs=2) - - In the latter example, A = B = C = 0 and D = [[1., 1., 1.] - [1., 1., 1.]]. + returned. Note: no renaming of inputs and outputs is performed; this should be done by the calling function. @@ -1896,6 +1891,8 @@ def tf2ss(*args, **kwargs): Examples -------- + >>> from control import tf, tf2ss + >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] >>> sys1 = tf2ss(num, den) diff --git a/control/stochsys.py b/control/stochsys.py index 000c1b6ab..65b4ebd95 100644 --- a/control/stochsys.py +++ b/control/stochsys.py @@ -108,8 +108,8 @@ def lqe(*args, **kwargs): Examples -------- - >>> L, P, E = lqe(A, G, C, QN, RN) - >>> L, P, E = lqe(A, G, C, Q, RN, NN) + >>> L, P, E = lqe(A, G, C, QN, RN) # doctest: +SKIP + >>> L, P, E = lqe(A, G, C, Q, RN, NN) # doctest: +SKIP See Also -------- @@ -240,8 +240,8 @@ def dlqe(*args, **kwargs): Examples -------- - >>> L, P, E = dlqe(A, G, C, QN, RN) - >>> L, P, E = dlqe(A, G, C, QN, RN, NN) + >>> L, P, E = dlqe(A, G, C, QN, RN) # doctest: +SKIP + >>> L, P, E = dlqe(A, G, C, QN, RN, NN) # doctest: +SKIP See Also -------- diff --git a/control/timeresp.py b/control/timeresp.py index 24f553a32..5f001848c 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -916,7 +916,12 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, Examples -------- - >>> T, yout, xout = forced_response(sys, T, u, X0) + >>> import numpy as np + >>> from control import forced_response, rss + + >>> G = rss(4) + >>> T = np.linspace(0,10) + >>> T, yout = forced_response(G, T=T) See :ref:`time-series-convention` and :ref:`package-configuration-parameters`. @@ -1328,7 +1333,10 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, Examples -------- - >>> T, yout = step_response(sys, T, X0) + >>> from control import step_response, rss + + >>> G = rss(4) + >>> T, yout = step_response(G) """ # Create the time and input vectors @@ -1441,6 +1449,7 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None, Examples -------- >>> from control import step_info, TransferFunction + >>> sys = TransferFunction([-1, 1], [1, 1, 1]) >>> S = step_info(sys) >>> for k in S: @@ -1462,6 +1471,7 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None, >>> from numpy import sqrt >>> from control import step_info, StateSpace + >>> sys = StateSpace([[-1., -1.], ... [1., 0.]], ... [[-1./sqrt(2.), 1./sqrt(2.)], @@ -1686,7 +1696,10 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, Examples -------- - >>> T, yout = initial_response(sys, T, X0) + >>> from control import initial_response, rss + + >>> G = rss(4) + >>> T, yout = initial_response(G) """ squeeze, sys = _get_ss_simo(sys, input, output, squeeze=squeeze) @@ -1801,7 +1814,10 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, Examples -------- - >>> T, yout = impulse_response(sys, T, X0) + >>> from control import impulse_response, rss + + >>> G = rss(4) + >>> T, yout = impulse_response(G) """ # Convert to state space so that we can simulate diff --git a/control/xferfcn.py b/control/xferfcn.py index 0bc84e096..64daa9a08 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -110,7 +110,7 @@ class TransferFunction(LTI): The attribues 'num' and 'den' are 2-D lists of arrays containing MIMO numerator and denominator coefficients. For example, - >>> num[2][5] = numpy.array([1., 4., 8.]) + >>> num[2][5] = numpy.array([1., 4., 8.]) # doctest: +SKIP means that the numerator of the transfer function from the 6th input to the 3rd output is set to s^2 + 4s + 8. @@ -141,6 +141,8 @@ class TransferFunction(LTI): discrete time. These can be used to create variables that allow algebraic creation of transfer functions. For example, + >>> from control import TransferFunction + >>> s = TransferFunction.s >>> G = (s + 1)/(s**2 + 2*s + 1) @@ -874,8 +876,8 @@ def returnScipySignalLTI(self, strict=True): For instance, - >>> out = tfobject.returnScipySignalLTI() - >>> out[3][5] + >>> out = tfobject.returnScipySignalLTI() # doctest: +SKIP + >>> out[3][5] # doctest: +SKIP is a :class:`scipy.signal.lti` object corresponding to the transfer function from the 6th input to the 4th output. @@ -963,7 +965,7 @@ def _common_den(self, imag_tol=None, allow_nonproper=False): Examples -------- - >>> num, den, denorder = sys._common_den() + >>> num, den, denorder = sys._common_den() # doctest: +SKIP """ @@ -1145,7 +1147,9 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> sys = TransferFunction(1, [1,1]) + >>> from control import tf + + >>> sys = tf(1, [1,1]) >>> sysd = sys.sample(0.5, method='bilinear') """ @@ -1202,6 +1206,14 @@ def dcgain(self, warn_infinite=False): For real valued systems, the empty imaginary part of the complex zero-frequency response is discarded and a real array or scalar is returned. + + Examples + -------- + >>> from control import tf + >>> G = tf([1],[1, 4]) + >>> G.dcgain() + 0.25 + """ return self._dcgain(warn_infinite) @@ -1230,8 +1242,8 @@ def _isstatic(self): #: #: Example #: ------- - #: >>> s = TransferFunction.s - #: >>> G = (s + 1)/(s**2 + 2*s + 1) + #: >>> s = TransferFunction.s # doctest: +SKIP + #: >>> G = (s + 1)/(s**2 + 2*s + 1) # doctest: +SKIP #: #: :meta hide-value: s = None @@ -1243,8 +1255,8 @@ def _isstatic(self): #: #: Example #: ------- - #: >>> z = TransferFunction.z - #: >>> G = 2 * z / (4 * z**3 + 3*z - 1) + #: >>> z = TransferFunction.z # doctest: +SKIP + #: >>> G = 2 * z / (4 * z**3 + 3*z - 1) # doctest: +SKIP #: #: :meta hide-value: z = None @@ -1529,6 +1541,8 @@ def tf(*args, **kwargs): Examples -------- + >>> from control import tf, ss + >>> # Create a MIMO transfer function object >>> # The transfer function from the 2nd input to the 1st output is >>> # (3s + 4) / (6s^2 + 5s + 4). @@ -1682,6 +1696,8 @@ def ss2tf(*args, **kwargs): Examples -------- + >>> from control import ss, ss2tf + >>> A = [[1., -2], [3, -4]] >>> B = [[5.], [7]] >>> C = [[6., 8]] diff --git a/doc/classes.png b/doc/classes.png new file mode 100644 index 0000000000000000000000000000000000000000..25724b43f90de77b9659fbb9caa9c7aed35e9ecd GIT binary patch literal 9026 zcmX9^1y~f{*IpVVrI!*|1O%kJ8(36T(%mXz8-KsxqE z2`rtGl78#|d!Bh_?s?z$oO5U9%-)?lH}Qdq9`!A@TObgK8mh1T2m~SoKp=uELLxj; z(^u1tKTtg|daQ##{>5*9|Lr~!v@d}`+#sm7#^a#e-Mj(s?QbEN7zDGj!q;jf>SKaD zZ8fGoDkwkB%Jg=TAf3;c35P>4j;;Rb!$@fhP-r+;9AUcqM@IAD(KtrvZ{B_-b`|?6 zv~yy5c!}Cg@NKcc12NaY2-(P%W?oG3=5u)=zBUaaq4tK-8~ein3snc z_r57>U<4sw$SH z0Q$8=91|>lv5Gl!MqEXsT*W|2eOHV@>L;^|{m|07WR

)o>c5>)DcZ|!a1iq+UoY+{VUQg z=QmCFcWIVKnELmf8+t&txaIclUoEA0KQXq!C5ISfZOHoD+4J2nn7ZgzidgEoH3ai{ z!9f1!^OW2VF8fN)&S;ivs(F%7A6;raRaUGakO}X_FV$2DJOvM1^6cur_cidehb__0 ziRL75Q{JA{K59I2=4z%(ofy5kMm~i7UA;?xQ?>KTuapaU^Y_MXxkQ%;oMlv41|CH)Dm}kF zbOX%eBT?gB(INZCqdRFL1CsTdpMnE$o1d_EU_YVnjJfclZ!@{dIoGHsXd|yJw^Lp7 zHNJ_GRF?>Nk&3jhbSR|UpZ=xWe{QFGdkV5%c-sS>{a@XvGR0xt=uS$-0Q9pl+t31V z{lHAORt{~RS!N6#Y6mXxiDU6R;v&tI6>qC3P}FV~V%f2*4rsT0s^D*Kr#gE>cwc5b z*@h>}3b8C$-dm+fF81&k5wrJ#IVvb)1&aDaJPyXj=J4a~6a@-O8L0Fn?<~oCzMN8> zd_Fulgm=DIh-JX?3Vf8+kq+gx&M=}I$_HferxWdVs@3oVn(vDHdPNp05Mg(gKF9!u z5fdK2xcYwz6zb8IP4$4Xbx&X>7I-nkpJTd$usYU$NinA_rA5=a2V~ztdzolfob>+|~_7pu?OB#JkbfTq>I61pL7#p8(vU$z6tIknQYO0s7MQ+UFZAY zvYOk$@v$CDggveL!1d6sd^r&(*qyC{tQ@~yOlpa|ncVkb#QNwp3`5!OtU0}G@(kj( zUPMDZqR(hEQz%g5YwL6}E}uP{AlB1RE3a+A^G?2vC{On4l);~k9D?k7|GFgSt~;F- z-yg4pzind|aUzm2@NQ1*sop9>He;FAzxJUV6Tca%V-1A$(QO!lK2pu?%D&3@1qU=m zgwiWI@ZljX4$E~kNpuV7C4^^o26^~L)qV@vIn-{nFFn}{wUkm%c?kZZ7us*oM;82l z+dbjoN`oDA1dQF|FSK_XK1`=}g}1;Lu6ti}q@;Puo8~&cNUpNqFpEs^G>Vx!N#@G) z&7wZrbobPR8jO5e`-xsk!q-*Lqr$DrlB$1k_>nl4wi}!fdlg`=khomBXGoa(Pn3}X zgg-_4%VB+V8qTTjCdQO}-QxQvT7fcNQqipPvZvyUzv7V63H?Gpnt{9gs!K4H=AUPi zlmu+q)1)qoS`VI_tG&s>td>^q*~Dv2{II*Gtu&OpfaoOA3Oh3tyS|hP_;r!ZW3~-C zK2QE84&}mZ!=1zydT+zhEh1CymyOLe?GG|gnK;lA$u#m)>@DTa6^DP=c`aglM4%jb zhxRDJMS>~CzIt)}?Ugh&elLy?XG4!2Eq{Sb}#ZYcTWrCTQmW1&#n32v3 ziwM(>X-g<5T17t3juBwP&g}bg5M7zIt0JPV2=_F6avGD%Hqa7ZWLn0#%mJkrf=}rA zfU{rV0tJ7)%;IN%FCxVzL`w=Y3R_Sl@&He6(bl=sY?sIm{2-eHm@KKZbfm-0h`?UG$m+b}cU6 zB(yFIs&LkmnjIdrp`$S`LYHFlP?y2VjZxJVjRCiN4u5o0OCZg z=4wB-SYrvYCvFs`X!iFL^RvfC1iv_Y4H$`;%-qX-{*P4(X=Z!gUdtdCaaX#lb}MmtO6a`Y7H_e!E@2CS|JhgpSQYQIE=4oTYJ zb8)UvcwasU$td_qxbfO^Jw`Q6On8!^|wmW59>AvZ_aj z=bUI3p4=bs;+$9Yg!$G&^e+V-h&xN)Ksw^sc9`DV-s6MaF29Gl=!l3ZlA?fq$whD_ zQ^iNad;Fdjtu<`na>RVfoGgI zAR2

Y2m}(DWTn7i_3ap9Dp0_lo?b7y+#F|uADf& zO+tpLAW2*`=Cb*L4B~y>~lGNPhIk z-EP<6bfN*i#0PPSneF&<&ORxB&L2iUL;~)=d<^t73_K$`wh@0kr`42+;7}KUKiSSC zRj|n@aupOinyh%iR28q?RXRnu5$}0N+J+(DN$fCRE0y<^{CC2QSkK8hhOw=!6(Pk6 z=DctAnA3#P(p8sw8|6x7k3R{VTA(~+=r-TvcQT`F2V)|by_IA>Rc{`j^~ciaEb zd&uonlX*YV1o)C$^n3N?jy}3G_&o^DV~W<>i!kZ>OPc(E#|?Bh_dH5 z1tRl`!TO=(ezDsNL#`HZGr4gZbgeXzyQy5JyMhe9d!PRXO;6hX6Opp08n;EZ099jD zMJnAWYRp{?-(xE7U9im`>3VM{5L0}9ir)o`qsuZP!@@pG)1EqyGX#RM#yL@Su6T4m z6S2cTq^_#=n3(c|4BEFdy304W2p{>4Bv?HZ4k!Ph{xaQ#yP(Ri0%QeY$7Fm?FHLp@ zxu?nApW%H^_!*f1J$e9N@uz1x6nDQd!<)5s510`r54x;AHNk zhy5RUEkP8A6deUdhGMK3R9OURs5|LcJ{Jb$FTTj0X(vwm$_z4VD1|&d<|b47m8P;| zAM#yU^5M$PR6c@gLI*y~8SJ0xdmC{FnrPuj0X3cd{ zYeJDsehyY4-{&YTFN;Nr!9KH$VCSNJ!^T=?ke9#O5uN*ebg?v%_j?|J807uFrfply zPbttVV`!u4phx%&rSq;Kgl?-NlaUZq~K^W!F6#(J>>X(>$n}DH^1g ziP@Cte(u$%n&jz!H(6ru!ty0*E%tbK@W3z%HBPu>`E!hSxYv@D*#f=MGWqEq&6tss z0_0k`+_Ddl&fEqOv{InoN=qTWU<59VY8(#$8Gq1y)ctSo6M-(B%A)K%jSx$W^ttoh zZ;X8B&MeEUFD__5+(q}&z2kxlySuo$ecNngRyTxGZfiB5Twca=qVJ~nUm!i)X^Lx8 zt$$NNBx~MtTa0lSxOz2?iPjp&;0MZ~1Pq4cC29k8ENLw!Cw%1|x}#+5H2%kL`nB|( zrJQ3mMB#>h$U-{Vrk*PfbjoXyI!TUr`3>ZF4QS`kE`?~ISQ3z`Z|x3iDll{+k<0+h z*G1)n)FY0Gs-@BG0i&Eo4}H{c=H04Dr%+z_8yY?Sv8z*arM_9~6YgtyMM=@qS9}(X zD}=YOf)4rNOsl1SJ5>bW(w6Qm-$YKnq|Bm>b-$M0{t;SO#5-!nU_HKZ3_CUs5%k@9 z-RSmAqz%;HnALngS9F#!8uoGOb*>Ch4Vc9msKNP%^qMToP@IE=3YEx*FZ*ax=qce? z%d%$nfiJ#ny4y$2@iu(-56?ZW zuU4;Bc7Ilz;&9on3!sWe*J2S9A75vqRQ^Hyai%U#!Mb<+cwdbTd?q=x^cs2v7~em! zi5UCaz&zPq^IuMASTkpbNig7J@roXuJrMhg@P-|n%#(_c`%>#Y`l=~YqcnX z=c#9%Q~>fsWn8T+1?-QX@X%8hg)L6wNeLCdt4U9^ujsx`n(GXAZS8dOVp@|bA+71a zP)H30ROMi_xcZ~s8r#T`OM<;78+Z=wYtl7I%{5q zx&CGj9D7D14F4PiS$Y==_5i{d%PG!ZrHkdd481;#3rAb3fF&HwA z)6>0NdczX`M@L3`6byTpS+mBIvMD`lTf49}t2G|tm^gR)RCl&-#QCI9wBFSLr_(g! zot9%cW%H)sBV0leJt7^jb~g4n%_*CFqQPCIMthGuG-hXPq*#NwbX)nTrT@9LlI~|; zsuP{Wln2kI1moq8JO|8zNqrG+HP>xHRtmgnc$f!rV>Xasklm0W+AU!)WL}@1S$c|? zdIPA)wodF^1d+MM=6&;?c(6s11q6LH>i zXFHLQn5*7(Mp*am(0k*i9%l>0z7<7Ld+ta)j&|xpSUfE8tOuITB!p}Du%A50MKtDa z2Uwa;<71gD^60cPX3_4j!g2*z#`JVkDvqfH;DzZJpO$zynq?iYNnI?rjNgbk_ao@X zIX{CJOSk-yeCxu|;i1nT>RUxDi4vB_==;X8G4*CmNxaEliZvS3TxGRBGjNDAS_8UM zqkg0jms=(#8Q#D)kYaH*k9@THgo1nO+exx_dYkD#-|PR##Z~Ou#AE3Gy;yfOrs9xF z57#iUV@L5A5?I2z%`04lg-YO_?>wS7-l8c!z_UEXE?^kS*+AJ(W05IXy$q&aV*7{Y zBcuK@N~L==E8$I!Gfw~Jh912Gt^$Y*hQa~Al3m2k;s+i<((PGyA*ykt?XZvT>GKgf zB&-(>A$e3(StZ>936*mbM6Mo}s_%uwmpf9qjnIW<^XP>YYhcFfX18MA4O--o;3z#3 zo$L#&xpjKSe^b%I?cFn(KIy8SLIRb$=d;?1VR1%dizs_;owzxa%RkzjC*wGfD&pjy zfDCJv!}QH)e35|7vDs|oOyl&83zMX;ixheTYzKsoRcJ5F$j5JhxssNIhlK1)fD+tv ze_b2$x(JqNbfgv|ijP5yHOzSnNdJ}Mr5xK?9r)+uk`0~hEY(<3`5!q6gEXfxphmX^ z>(~C5r4(JZ`aS=USW6o0&`9AxHJWo&IgvCvoiNoGY3Ok2TR0d1_&LQV6F8CoM2v;;vUrM>X_-s& zn>g#btG-~)BAt_BQhxmp{e=d(jD)r$X%UpzV zRLaLITSrEVefV1R6evI~_9%}6Wdl}&BM1BkpM_0_dAV_zUHoW8SiePk2DZJn9(B-!u?SS3r9h_(suthjppGzUH%OLg$IU{WI~ zw>8U`!wKh~^YKh_02S#uEj3m(QDJ3c^?H>r873ki4iS#%m9M-D<^!tjG!eYrhKN@R zhE4(D2~^D<6>MP5vWx-QqUSG**Ft>VKWH&?Km>r`8x77+GD(Pb@wa!j(WmA&`|86hclTHP#IrO9RSE^vIG2rmAoZnJduCr+w01O`@xm? znFxf4JpSk2(~G9k=@;=IS!zG25#g=<0$y%0+5tI5*cD{9>;3lX(>PksF6Lk-&LMBW zcvN8Pm{5qVEK_7__CHycn(_wHk^U^6=|z=W5Ek6)t*R!SN)1xfTWPWpCI$B#|9ND+ z^$@9U18NQQjTcjo1ari6e9nt%_%-HA5TFxZ@VYH9-9DE}uOWR6qzLgJxOenT`4-xX z8v7(`c420Pw|3x-{SLLSlGr?0+Xgior5^^B7uD0Kk{q#ocUOGdeA@;k!?erQTPOe^dUE3E1Lgrctw+U`kC07+UuB1Q{&cbbhl7>dl0 zsxo~8=T&%A--&;(Qs8A6pw=)C?enoMK^vibMOJR9R53rD5>9lMT=_)@uXja68ri`_ zFhy}u6I-_q4H8C8Y$x(110tKjYz3RVG0I{HjPk*e)RcWeW_ z#3juUCQA%u(Ig|3c!3jl0yF-dGNIxCv6NI({pFGb{|}01Cf7Mc0+9JGo^N)0!@r$MR^d2 zVZH|P+SRlRMOBpN1PLt8?)4k5HzD1SdX$hp%*x)p4W>HZh%+KFQFZc!3r@@mW87L2 ze}GhU{~_>Rq;d>zW1g=wsBQBNZE%+2F9ZuX~nx*uxwpfTqMtWkO7p zy6J%^Q0N-K(f$344Y5AiG5#@qYK!cR4i`AbT66ZRD;C<@NsH zr$)#6dVg0~_OOM&z~OPj3;B?X-r^{>bCzscPmS>RC(%Zq)sZ!^w*Z@#N*2k2WYFg4 zaNkW;IP<`tyN)%jOelrn1I@K<-e+*0@&z_znNzjplp^UmShZc`cy^2@u!1ERhB;h0 z4C?$DQB>RYl^AE9^9mV9KSvIH!%isJ;ztG|7^D0-Ra#dmiJ$dIpq{L%%h^3KVbj}` zswu8b5cW%Zgk6v8R8V}aed;XI&nuCFQ>QEeR&SUHm}$$d*`NqeQc+xS3AM z9{j=SQF3Nje7LW>Y6Y3!DmZTDy(TI5)H5UkQtRTuu=mX)}JpVDNKg#&88auPpQ`r%nr?|1zmYp&Tp!UhR48$3-cx*K zMC6nC$(#zu%mt})qG7-0Sh7wvMQ>*(F&(ZDOajc!ujiM?VovoKyRWj zgF2s$zYaL9@lNowH2*ettJ>XDX!5wZ>GPt!^mr~lCE3a$`S)qGWdob-u8SGO!w&^J zp-~o9kU*RKir(T3vhjC>=XH{Z&#XiU&^6inTfr+kNm{a|3&hk;YdfzCGkCZCQ4WE$ z;S0kA2TfZ=z=aKQ3rSGqB5kt(3&qQWV6t_w&K0y7?-^spe)yc)@cw$sW#=`TftTG6 z1q!kY)P!r<@{uclRpLaKy}Q~8UFt{biSf(Hf)6)(aeXD4`J(9pT3j9P=4P}32F48b z_vF}TUj$6wlLr6L&nFN5R!1hFdFt-jY(9KH2g8yriksid_d*6o>uH|eZjx`%Td**b zNPF#BkX%P=%wVY!R;#&CAbu3C48QEll)Nw84M=2LmT@y&+O_9o+5GNY_il_(*sR+J&FltXOXLW6kGpyk^-6NLmP`W=taTt^P&{}{>$o^?2`&v^EqeS?_s!Z27&)h->IdXEqP({=l6|Chxcckp z)3)(bpS26I$p=j#$|9dI8`X2l*Z)e?m#hUNSmyH-+H+;>-Q|B+n&U=w?#sn>9;=V|l2Hjh?nCZhr( z_2X-*nFq-Tg!ss?gTZ}%49vH>uW|tj4rbGx^k#5P8%BEh{c58TKsM$0t8U1xw3!)@bKkxG#q* zN#XModO1&b>{GyY+vMHEs52vGnu<9%=`fvJIOXc}-{lg zd))^&MVL^{A-Tmx*&NVP+#MR1i4+RMYoAkcbe%G(lHgoQ$lFeAe!!!&EM*Q|>R-#I z30oSUuvD?^8r=>tv->hk*qe!+h%=Q;V85Z=2G8r?Ja62|%OJ6Hbw@^M#T%7gQ2u2D zo`{T>-I&X%FAOzZgZzi;UCUl!hb7)*ZPk95FQon5L`fpB3(LW=h>GKGnVVh5ZEtwp z!guYW^SDlJsqvZu6wTq*^~^DQw(5}aM8q-ro|v=i_j{nD>!a;!Pqp{^5m&Lb=X*K~ z4Yb)4{W)bf{JQbAzc5_H!%{wqLw5TP;$QDn8M<%|yrLX>QquakfMjmU@Zg&OWqCA1 zZrd{FQQKZc4I4?Th@0GZE+x`;ZJjLg5y8~@BHKSNM1dCU(G?B;OWLkWU`yySt3ajG z9HFsm)HH0RK5U^1u@rWvjk@XaFS^qLpq*Jhwyl=K&Gl# Date: Fri, 24 Mar 2023 19:32:54 -0700 Subject: [PATCH 3/9] remove imports and use ct.fcn instead --- control/__init__.py | 17 ++++++++++ control/bdalg.py | 62 +++++++++++++++---------------------- control/canonical.py | 57 ++++++++++++---------------------- control/config.py | 48 +++++++++++----------------- control/ctrlutil.py | 32 ++++++------------- control/delay.py | 6 ++-- control/descfcn.py | 26 +++++----------- control/dtime.py | 12 +++---- control/exception.py | 9 ++---- control/flatsys/__init__.py | 6 ++++ control/frdata.py | 9 ++---- control/freqplot.py | 34 +++++++------------- control/iosys.py | 49 ++++++++++++----------------- control/lti.py | 24 ++++++-------- control/margins.py | 12 +++---- control/matlab/timeresp.py | 1 - control/matlab/wrappers.py | 2 +- control/modelsimp.py | 25 +++++---------- control/optimal.py | 6 ++++ control/robust.py | 32 +++++++++---------- control/sisotool.py | 6 ++-- control/statefbk.py | 30 ++++++------------ control/statesp.py | 12 +++---- control/timeresp.py | 39 ++++++++--------------- control/xferfcn.py | 29 ++++++----------- doc/conf.py | 10 ++++++ doc/optimal.rst | 8 ++--- 27 files changed, 243 insertions(+), 360 deletions(-) diff --git a/control/__init__.py b/control/__init__.py index 649a90861..340370c8b 100644 --- a/control/__init__.py +++ b/control/__init__.py @@ -42,6 +42,23 @@ """ The Python Control Systems Library :mod:`control` provides common functions for analyzing and designing feedback control systems. + +Documentation is available in two forms: docstrings provided with the code, +and the python-control users guide, available from `the python-control +homepage `_. + +The docstring examples assume that the following import commands:: + + >>> import numpy as np + >>> import control as ct + +Available subpackages +--------------------- +flatsys + Differentially flat systems +optimal + Optimization-based control + """ # Import functions from within the control system library diff --git a/control/bdalg.py b/control/bdalg.py index f93e7d89c..911a20b53 100644 --- a/control/bdalg.py +++ b/control/bdalg.py @@ -99,17 +99,15 @@ def series(sys1, *sysn): Examples -------- - >>> from control import rss, series - - >>> G1 = rss(3) - >>> G2 = rss(4) - >>> G = series(G1, G2) # Same as sys3 = sys2 * sys1 + >>> G1 = ct.rss(3) + >>> G2 = ct.rss(4) + >>> G = ct.series(G1, G2) # Same as sys3 = sys2 * sys1 >>> G.ninputs, G.noutputs, G.nstates (1, 1, 7) - >>> G1 = rss(2, inputs=2, outputs=3) - >>> G2 = rss(3, inputs=3, outputs=1) - >>> G = series(G1, G2) # Same as sys3 = sys2 * sys1 + >>> G1 = ct.rss(2, inputs=2, outputs=3) + >>> G2 = ct.rss(3, inputs=3, outputs=1) + >>> G = ct.series(G1, G2) # Same as sys3 = sys2 * sys1 >>> G.ninputs, G.noutputs, G.nstates (2, 1, 5) @@ -156,17 +154,15 @@ def parallel(sys1, *sysn): Examples -------- - >>> from control import parallel, rss - - >>> G1 = rss(3) - >>> G2 = rss(4) - >>> G = parallel(G1, G2) # Same as sys3 = sys1 + sys2 + >>> G1 = ct.rss(3) + >>> G2 = ct.rss(4) + >>> G = ct.parallel(G1, G2) # Same as sys3 = sys1 + sys2 >>> G.ninputs, G.noutputs, G.nstates (1, 1, 7) - >>> G1 = rss(3, inputs=3, outputs=4) - >>> G2 = rss(4, inputs=3, outputs=4) - >>> G = parallel(G1, G2) # Add another system + >>> G1 = ct.rss(3, inputs=3, outputs=4) + >>> G2 = ct.rss(4, inputs=3, outputs=4) + >>> G = ct.parallel(G1, G2) # Add another system >>> G.ninputs, G.noutputs, G.nstates (3, 4, 7) @@ -194,13 +190,11 @@ def negate(sys): Examples -------- - >>> from control import negate, tf - - >>> G = tf([2],[1, 1]) + >>> G = ct.tf([2],[1, 1]) >>> G.dcgain() > 0 True - >>> Gn = negate(G) # Same as sys2 = -sys1. + >>> Gn = ct.negate(G) # Same as sys2 = -sys1. >>> Gn.dcgain() < 0 True @@ -252,11 +246,9 @@ def feedback(sys1, sys2=1, sign=-1): Examples -------- - >>> from control import feedback, rss - - >>> G = rss(3, inputs=2, outputs=5) - >>> C = rss(4, inputs=5, outputs=2) - >>> T = feedback(G, C, sign=1) + >>> G = ct.rss(3, inputs=2, outputs=5) + >>> C = ct.rss(4, inputs=5, outputs=2) + >>> T = ct.feedback(G, C, sign=1) >>> T.ninputs, T.noutputs, T.nstates (2, 5, 7) @@ -316,17 +308,15 @@ def append(*sys): Examples -------- - >>> from control import append, rss - >>> G1 = rss(3) - - >>> G2 = rss(4) - >>> G = append(G1, G2) + >>> G1 = ct.rss(3) + >>> G2 = ct.rss(4) + >>> G = ct.append(G1, G2) >>> G.ninputs, G.noutputs, G.nstates (2, 2, 7) - >>> G1 = rss(3, inputs=2, outputs=4) - >>> G2 = rss(4, inputs=1, outputs=4) - >>> G = append(G1, G2) + >>> G1 = ct.rss(3, inputs=2, outputs=4) + >>> G2 = ct.rss(4, inputs=1, outputs=4) + >>> G = ct.append(G1, G2) >>> G.ninputs, G.noutputs, G.nstates (3, 8, 7) @@ -371,11 +361,9 @@ def connect(sys, Q, inputv, outputv): Examples -------- - >>> from control import append, connect, rss - - >>> G = rss(7, inputs=2, outputs=2) + >>> G = ct.rss(7, inputs=2, outputs=2) >>> K = [[1, 2], [2, -1]] # negative feedback interconnection - >>> T = connect(G, K, [2], [1, 2]) + >>> T = ct.connect(G, K, [2], [1, 2]) >>> T.ninputs, T.noutputs, T.nstates (1, 2, 7) diff --git a/control/canonical.py b/control/canonical.py index f8aa0ff2e..436db667b 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -39,21 +39,18 @@ def canonical_form(xsys, form='reachable'): Examples -------- - >>> from control import canonical_form, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss(G) - - >>> Gc, T = canonical_form(Gs) # default reachable + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss(G) + >>> Gc, T = ct.canonical_form(Gs) # default reachable >>> Gc.B # doctest: +SKIP matrix([[1.], [0.]]) - >>> Gc, T = canonical_form(Gs, 'observable') + >>> Gc, T = ct.canonical_form(Gs, 'observable') >>> Gc.C # doctest: +SKIP matrix([[1., 0.]]) - >>> Gc, T = canonical_form(Gs, 'modal') + >>> Gc, T = ct.canonical_form(Gs, 'modal') >>> Gc.A # doctest: +SKIP matrix([[-2., 0.], [ 0., -1.]]) @@ -90,12 +87,9 @@ def reachable_form(xsys): Examples -------- - >>> from control import reachable_form, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss(G) - - >>> Gc, T = reachable_form(Gs) # default reachable + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss(G) + >>> Gc, T = ct.reachable_form(Gs) # default reachable >>> Gc.B # doctest: +SKIP matrix([[1.], [0.]]) @@ -157,12 +151,9 @@ def observable_form(xsys): Examples -------- - >>> from control import observable_form, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss(G) - - >>> Gc, T = observable_form(Gs) + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss(G) + >>> Gc, T = ct.observable_form(Gs) >>> Gc.C # doctest: +SKIP matrix([[1., 0.]]) @@ -227,17 +218,14 @@ def similarity_transform(xsys, T, timescale=1, inverse=False): Examples -------- - >>> import numpy as np - >>> from control import similarity_transform, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss(G) + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss(G) >>> Gs.A # doctest: +SKIP matrix([[-3., -2.], [ 1., 0.]]) >>> T = np.array([[0,1],[1,0]]) - >>> Gt = similarity_transform(Gs, T) + >>> Gt = ct.similarity_transform(Gs, T) >>> Gt.A # doctest: +SKIP matrix([[ 0., 1.], [-2., -3.]]) @@ -438,11 +426,9 @@ def bdschur(a, condmax=None, sort=None): Examples -------- - >>> from control import bdschur, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss(G) - >>> amodal, tmodal, blksizes = bdschur(Gs.A) + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss(G) + >>> amodal, tmodal, blksizes = ct.bdschur(Gs.A) >>> amodal #doctest: +SKIP array([[-2., 0.], [ 0., -1.]]) @@ -516,12 +502,9 @@ def modal_form(xsys, condmax=None, sort=False): Examples -------- - >>> from control import modal_form, tf, tf2ss - - >>> G = tf([1],[1, 3, 2]) - >>> Gs = tf2ss((G)) - - >>> Gc, T = modal_form(Gs) # default reachable + >>> G = ct.tf([1],[1, 3, 2]) + >>> Gs = ct.tf2ss((G)) + >>> Gc, T = ct.modal_form(Gs) # default reachable >>> Gc.A # doctest: +SKIP matrix([[-2., 0.], [ 0., -1.]]) diff --git a/control/config.py b/control/config.py index 4bb7648a9..78eaeeb6f 100644 --- a/control/config.py +++ b/control/config.py @@ -69,17 +69,15 @@ def set_defaults(module, **keywords): Examples -------- - >>> from control import defaults, reset_defaults, set_defaults - - >>> defaults['freqplot.number_of_samples'] + >>> ct.defaults['freqplot.number_of_samples'] 1000 - >>> set_defaults('freqplot', number_of_samples=100) - >>> defaults['freqplot.number_of_samples'] + >>> ct.set_defaults('freqplot', number_of_samples=100) + >>> ct.defaults['freqplot.number_of_samples'] 100 >>> # do some customized freqplotting - >>> reset_defaults() - >>> defaults['freqplot.number_of_samples'] + >>> ct.reset_defaults() + >>> ct.defaults['freqplot.number_of_samples'] 1000 """ @@ -97,17 +95,15 @@ def reset_defaults(): Examples -------- - >>> from control import defaults, reset_defaults, set_defaults - - >>> defaults['freqplot.number_of_samples'] + >>> ct.defaults['freqplot.number_of_samples'] 1000 - >>> set_defaults('freqplot', number_of_samples=100) - >>> defaults['freqplot.number_of_samples'] + >>> ct.set_defaults('freqplot', number_of_samples=100) + >>> ct.defaults['freqplot.number_of_samples'] 100 >>> # do some customized freqplotting - >>> reset_defaults() - >>> defaults['freqplot.number_of_samples'] + >>> ct.reset_defaults() + >>> ct.defaults['freqplot.number_of_samples'] 1000 """ @@ -213,11 +209,9 @@ def use_matlab_defaults(): Examples -------- - >>> from control import use_matlab_defaults, reset_defaults - - >>> use_matlab_defaults() + >>> ct.use_matlab_defaults() >>> # do some matlab style plotting - >>> reset_defaults() + >>> ct.reset_defaults() """ set_defaults('freqplot', dB=True, deg=True, Hz=False, grid=True) @@ -235,11 +229,9 @@ def use_fbs_defaults(): Examples -------- - >>> from control import use_fbs_defaults, reset_defaults - - >>> use_fbs_defaults() + >>> ct.use_fbs_defaults() >>> # do some FBS style plotting - >>> reset_defaults() + >>> ct.reset_defaults() """ set_defaults('freqplot', dB=False, deg=True, Hz=False, grid=False) @@ -271,11 +263,9 @@ class and functions. If flat is `False`, then matrices are Examples -------- - >>> from control import use_numpy_matrix, reset_defaults - - >>> use_numpy_matrix(True, False) + >>> ct.use_numpy_matrix(True, False) >>> # do some legacy calculations using np.matrix - >>> reset_defaults() + >>> ct.reset_defaults() """ if flag and warn: @@ -294,12 +284,10 @@ def use_legacy_defaults(version): Examples -------- - >>> from control import use_legacy_defaults, reset_defaults - - >>> use_legacy_defaults("0.9.0") + >>> ct.use_legacy_defaults("0.9.0") (0, 9, 0) >>> # do some legacy style plotting - >>> reset_defaults() + >>> ct.reset_defaults() """ import re diff --git a/control/ctrlutil.py b/control/ctrlutil.py index ddd75812b..f745e95ee 100644 --- a/control/ctrlutil.py +++ b/control/ctrlutil.py @@ -65,19 +65,15 @@ def unwrap(angle, period=2*math.pi): Examples -------- - >>> import numpy as np - >>> from control import unwrap - >>> from pprint import pprint - >>> # Already continuous >>> theta1 = np.array([1.0, 1.5, 2.0, 2.5, 3.0]) * np.pi - >>> theta2 = unwrap(theta1) + >>> theta2 = ct.unwrap(theta1) >>> theta2/np.pi # doctest: +SKIP array([1. , 1.5, 2. , 2.5, 3. ]) >>> # Wrapped, discontinuous >>> theta1 = np.array([1.0, 1.5, 0.0, 0.5, 1.0]) * np.pi - >>> theta2 = unwrap(theta1) + >>> theta2 = ct.unwrap(theta1) >>> theta2/np.pi # doctest: +SKIP array([1. , 1.5, 2. , 2.5, 3. ]) @@ -94,14 +90,12 @@ def issys(obj): Examples -------- - >>> from control import issys, tf, InputOutputSystem, LinearIOSystem - - >>> G = tf([1],[1, 1]) - >>> issys(G) + >>> G = ct.tf([1],[1, 1]) + >>> ct.issys(G) True - >>> K = InputOutputSystem() # Not necessarily LTI! - >>> issys(K) + >>> K = ct.InputOutputSystem() # Not necessarily LTI! + >>> ct.issys(K) False """ @@ -126,13 +120,10 @@ def db2mag(db): Examples -------- - >>> import numpy as np - >>> from control import db2mag - - >>> db2mag(-40.0) # doctest: +SKIP + >>> ct.db2mag(-40.0) # doctest: +SKIP 0.01 - >>> db2mag(np.array([0, -20])) # doctest: +SKIP + >>> ct.db2mag(np.array([0, -20])) # doctest: +SKIP array([1. , 0.1]) """ @@ -157,13 +148,10 @@ def mag2db(mag): Examples -------- - >>> import numpy as np - >>> from control import mag2db - - >>> mag2db(10.0) # doctest: +SKIP + >>> ct.mag2db(10.0) # doctest: +SKIP 20.0 - >>> mag2db(np.array([1, 0.01])) # doctest: +SKIP + >>> ct.mag2db(np.array([1, 0.01])) # doctest: +SKIP array([ 0., -40.]) """ diff --git a/control/delay.py b/control/delay.py index 42110c1ff..3779f1ddb 100644 --- a/control/delay.py +++ b/control/delay.py @@ -77,14 +77,12 @@ def pade(T, n=1, numdeg=None): Examples -------- - >>> from control import pade - >>> delay = 1 - >>> num, den = pade(delay, 5) + >>> num, den = ct.pade(delay, 5) >>> len(num), len(den) (6, 6) - >>> num, den = pade(delay, 5, -2) + >>> num, den = ct.pade(delay, 5, -2) >>> len(num), len(den) (4, 6) diff --git a/control/descfcn.py b/control/descfcn.py index dfd2c2d4b..2cc58c75b 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -122,12 +122,9 @@ def describing_function( Examples -------- - >>> import numpy as np - >>> from control import describing_function - >>> F = lambda x: np.exp(-x) # Basic diode description >>> A = np.logspace(-1, 1, 20) # Amplitudes from 0.1 to 10.0 - >>> df_values = describing_function(F, A) + >>> df_values = ct.describing_function(F, A) >>> len(df_values) 20 @@ -249,13 +246,10 @@ def describing_function_plot( Examples -------- - >>> import numpy as np - >>> from control import describing_function_plot, saturation_nonlinearity, tf - - >>> H_simple = tf([8], [1, 2, 2, 1]) - >>> F_saturation = saturation_nonlinearity(1) + >>> H_simple = ct.tf([8], [1, 2, 2, 1]) + >>> F_saturation = ct.saturation_nonlinearity(1) >>> amp = np.linspace(1, 4, 10) - >>> points = describing_function_plot(H_simple, F_saturation, amp) + >>> points = ct.describing_function_plot(H_simple, F_saturation, amp) >>> len(points) 1 @@ -371,9 +365,7 @@ class saturation_nonlinearity(DescribingFunctionNonlinearity): Examples -------- - >>> from control import saturation_nonlinearity - - >>> nl = saturation_nonlinearity(5) + >>> nl = ct.saturation_nonlinearity(5) >>> f = lambda x: round(nl(x)) >>> f(1) 1 @@ -437,9 +429,7 @@ class relay_hysteresis_nonlinearity(DescribingFunctionNonlinearity): Examples -------- - >>> from control import relay_hysteresis_nonlinearity - - >>> nl = relay_hysteresis_nonlinearity(1, 2) + >>> nl = ct.relay_hysteresis_nonlinearity(1, 2) >>> f = lambda x: round(nl(x)) >>> f(0) -1 @@ -509,9 +499,7 @@ class friction_backlash_nonlinearity(DescribingFunctionNonlinearity): Examples -------- - >>> from control import friction_backlash_nonlinearity - - >>> nl = friction_backlash_nonlinearity(2) # backlash of +/- 1 + >>> nl = ct.friction_backlash_nonlinearity(2) # backlash of +/- 1 >>> f = lambda x: round(nl(x)) >>> f(0) 0 diff --git a/control/dtime.py b/control/dtime.py index 164cc6874..6197ae8af 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -109,12 +109,10 @@ def sample_system(sysc, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> from control import sample_system, tf - - >>> Gc = tf([1], [1, 2, 1]) + >>> Gc = ct.tf([1], [1, 2, 1]) >>> Gc.isdtime() False - >>> Gd = sample_system(Gc, 1, method='bilinear') + >>> Gd = ct.sample_system(Gc, 1, method='bilinear') >>> Gd.isdtime() True @@ -157,12 +155,10 @@ def c2d(sysc, Ts, method='zoh', prewarp_frequency=None): Examples -------- - >>> from control import sample_system, tf - - >>> Gc = tf([1], [1, 2, 1]) + >>> Gc = ct.tf([1], [1, 2, 1]) >>> Gc.isdtime() False - >>> Gd = sample_system(Gc, 1, method='bilinear') + >>> Gd = ct.sample_system(Gc, 1, method='bilinear') >>> Gd.isdtime() True diff --git a/control/exception.py b/control/exception.py index 338de06f6..d308dc7f6 100644 --- a/control/exception.py +++ b/control/exception.py @@ -67,8 +67,7 @@ def slycot_check(): Examples -------- - >>> from control import slycot_check - >>> slycot_check() + >>> ct.slycot_check() True """ @@ -89,8 +88,7 @@ def pandas_check(): Examples -------- - >>> from control import pandas_check - >>> pandas_check() + >>> ct.pandas_check() True """ @@ -111,8 +109,7 @@ def cvxopt_check(): Examples -------- - >>> from control import cvxopt_check - >>> cvxopt_check() + >>> ct.cvxopt_check() True """ diff --git a/control/flatsys/__init__.py b/control/flatsys/__init__.py index 157800073..6345ee2b9 100644 --- a/control/flatsys/__init__.py +++ b/control/flatsys/__init__.py @@ -50,6 +50,12 @@ function can be used to solve an optimal control problem with trajectory and final costs or constraints. +The docstring examples assume that the following import commands:: + + >>> import numpy as np + >>> import control as ct + >>> import control.flatsys as fs + """ # Basis function families diff --git a/control/frdata.py b/control/frdata.py index 22f3b121e..0783aaa31 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -767,17 +767,14 @@ def frd(*args): Examples -------- - >>> from control import frd, tf - >>> # Create from measurements >>> response = [1.0, 1.0, 0.5] >>> freqs = [1, 10, 100] - >>> F = frd(response, freqs) + >>> F = ct.frd(response, freqs) - >>> G = tf([1], [1, 1]) + >>> G = ct.tf([1], [1, 1]) >>> freqs = [1, 10, 100] - >>> F = frd(G, freqs) - + >>> F = ct.frd(G, freqs) """ return FRD(*args) diff --git a/control/freqplot.py b/control/freqplot.py index 56b946aa3..08ba0ab16 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -174,10 +174,8 @@ def bode_plot(syslist, omega=None, Examples -------- - >>> from control import bode_plot, ss - - >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> Gmag, Gphase, Gomega = bode_plot(G) + >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> Gmag, Gphase, Gomega = ct.bode_plot(G) """ # Make a copy of the kwargs dictionary since we will modify it @@ -694,10 +692,8 @@ def nyquist_plot( Examples -------- - >>> from control import nyquist_plot, zpk - - >>> G = zpk([],[-1,-2,-3], gain=100) - >>> nyquist_plot(G) + >>> G = ct.zpk([],[-1,-2,-3], gain=100) + >>> ct.nyquist_plot(G) 2 """ @@ -1266,10 +1262,9 @@ def gangof4_plot(P, C, omega=None, **kwargs): Examples -------- - >>> from control import gangof4_plot, tf - >>> P = tf([1],[1, 1]) - >>> C = tf([2],[1]) - >>> gangof4_plot(P, C) + >>> P = ct.tf([1],[1, 1]) + >>> C = ct.tf([2],[1]) + >>> ct.gangof4_plot(P, C) """ if not P.issiso() or not C.issiso(): @@ -1414,15 +1409,12 @@ def singular_values_plot(syslist, omega=None, Examples -------- - >>> import numpy as np - >>> from control import tf, singular_values_plot - >>> omegas = np.logspace(-4, 1, 1000) >>> den = [75, 1] - >>> G = tf([[[87.8], [-86.4]], [[108.2], [-109.6]]], [[den, den], [den, den]]) - >>> sigmas, omegas = singular_values_plot(G, omega=omegas, plot=False) + >>> G = ct.tf([[[87.8], [-86.4]], [[108.2], [-109.6]]], [[den, den], [den, den]]) + >>> sigmas, omegas = ct.singular_values_plot(G, omega=omegas, plot=False) - >>> sigmas, omegas = singular_values_plot(G, 0.0, plot=False) + >>> sigmas, omegas = ct.singular_values_plot(G, 0.0, plot=False) """ @@ -1638,10 +1630,8 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None, Examples -------- - >>> from control import ss - - >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> omega = _default_frequency_range(G) + >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> omega = ct._default_frequency_range(G) >>> omega.min(), omega.max() (0.1, 100.0) diff --git a/control/iosys.py b/control/iosys.py index 410fa5c62..589d81add 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -2373,14 +2373,12 @@ def ss(*args, **kwargs): -------- Create a Linear I/O system object from matrices. - >>> from control import ss, tf - - >>> G = ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) + >>> G = ct.ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) Convert a TransferFunction to a StateSpace object. - >>> sys_tf = tf([2.], [1., 3]) - >>> sys2 = ss(sys_tf) + >>> sys_tf = ct.tf([2.], [1., 3]) + >>> sys2 = ct.ss(sys_tf) """ # See if this is a nonlinear I/O system @@ -2501,8 +2499,7 @@ def drss(*args, **kwargs): Examples -------- - >>> from control import drss - >>> G = drss(states=4, outputs=2, inputs=1) + >>> G = ct.drss(states=4, outputs=2, inputs=1) >>> G.ninputs, G.noutputs, G.nstates (1, 2, 4) >>> G.isdtime() @@ -2597,14 +2594,12 @@ def tf2io(*args, **kwargs): Examples -------- - >>> from control import tf2ss, tf - >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] - >>> sys1 = tf2ss(num, den) + >>> sys1 = ct.tf2ss(num, den) - >>> sys_tf = tf(num, den) - >>> G = tf2ss(sys_tf) + >>> sys_tf = ct.tf(num, den) + >>> G = ct.tf2ss(sys_tf) >>> G.ninputs, G.noutputs, G.nstates (2, 2, 8) @@ -2783,15 +2778,13 @@ def interconnect( Examples -------- - >>> from control import LinearIOSystem, interconnect, rss, summing_junction, tf - - >>> P = LinearIOSystem( - ... rss(2, 2, 2, strictly_proper=True), + >>> P = ct.LinearIOSystem( + ... ct.rss(2, 2, 2, strictly_proper=True), ... name='P') - >>> C = LinearIOSystem( - ... rss(2, 2, 2), + >>> C = ct.LinearIOSystem( + ... ct.rss(2, 2, 2), ... name='C') - >>> T = interconnect( + >>> T = ct.interconnect( ... [P, C], ... connections = [ ... ['P.u[0]', 'C.y[0]'], ['P.u[1]', 'C.y[1]'], @@ -2804,10 +2797,10 @@ def interconnect( :func:`~control.summing_block` function and the ability to automatically interconnect signals with the same names: - >>> P = tf(1, [1, 0], inputs='u', outputs='y') - >>> C = tf(10, [1, 1], inputs='e', outputs='u') - >>> sumblk = summing_junction(inputs=['r', '-y'], output='e') - >>> T = interconnect([P, C, sumblk], inputs='r', outputs='y') + >>> P = ct.tf(1, [1, 0], inputs='u', outputs='y') + >>> C = ct.tf(10, [1, 1], inputs='e', outputs='u') + >>> sumblk = ct.summing_junction(inputs=['r', '-y'], output='e') + >>> T = ct.interconnect([P, C, sumblk], inputs='r', outputs='y') Notes ----- @@ -3009,12 +3002,10 @@ def summing_junction( Examples -------- - >>> from control import tf2io, summing_junction, interconnect, tf - - >>> P = tf2io(tf(1, [1, 0]), inputs='u', outputs='y') - >>> C = tf2io(tf(10, [1, 1]), inputs='e', outputs='u') - >>> sumblk = summing_junction(inputs=['r', '-y'], output='e') - >>> T = interconnect((P, C, sumblk), inputs='r', outputs='y') + >>> P = ct.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y') + >>> C = ct.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u') + >>> sumblk = ct.summing_junction(inputs=['r', '-y'], output='e') + >>> T = ct.interconnect((P, C, sumblk), inputs='r', outputs='y') >>> T.ninputs, T.noutputs, T.nstates (1, 1, 2) diff --git a/control/lti.py b/control/lti.py index acbf3c8c9..c221832b5 100644 --- a/control/lti.py +++ b/control/lti.py @@ -326,9 +326,8 @@ def damp(sys, doprint=True): Examples -------- - >>> from control import damp, tf - >>> G = tf([1],[1, 4]) - >>> wn, damping, poles = damp(G) # doctest: +SKIP + >>> G = ct.tf([1],[1, 4]) + >>> wn, damping, poles = ct.damp(G) # doctest: +SKIP _____Eigenvalue______ Damping___ Frequency_ -4 1 4 @@ -394,10 +393,8 @@ def evalfr(sys, x, squeeze=None): Examples -------- - >>> from control import ss, evalfr - - >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> fresp = evalfr(G, 1j) # evaluate at s = 1j + >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> fresp = ct.evalfr(G, 1j) # evaluate at s = 1j .. todo:: Add example with MIMO system @@ -457,10 +454,8 @@ def frequency_response(sys, omega, squeeze=None): Examples -------- - >>> from control import ss, freqresp - - >>> G = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> mag, phase, omega = freqresp(G, [0.1, 1., 10.]) + >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> mag, phase, omega = ct.freqresp(G, [0.1, 1., 10.]) .. todo:: Add example with MIMO system @@ -494,9 +489,10 @@ def dcgain(sys): the origin, (nan + nanj) if there is a pole/zero cancellation at the origin. - >>> from control import dcgain, tf - >>> G = tf([1], [1, 2]) - >>> dcgain(G) # doctest: +SKIP + Examples + -------- + >>> G = ct.tf([1], [1, 2]) + >>> ct.dcgain(G) # doctest: +SKIP 0.5 """ diff --git a/control/margins.py b/control/margins.py index 955dfd278..28daaf358 100644 --- a/control/margins.py +++ b/control/margins.py @@ -476,10 +476,8 @@ def phase_crossover_frequencies(sys): Examples -------- - >>> from control import phase_crossover_frequencies, tf - - >>> G = tf([1], [1, 2, 3, 4]) - >>> x_omega, x_gain = phase_crossover_frequencies(G) + >>> G = ct.tf([1], [1, 2, 3, 4]) + >>> x_omega, x_gain = ct.phase_crossover_frequencies(G) """ # Convert to a transfer function @@ -539,10 +537,8 @@ def margin(*args): Examples -------- - >>> from control import margin, tf - - >>> G = tf(1, [1, 2, 1, 0]) - >>> gm, pm, wcg, wcp = margin(G) + >>> G = ct.tf(1, [1, 2, 1, 0]) + >>> gm, pm, wcg, wcp = ct.margin(G) """ if len(args) == 1: diff --git a/control/matlab/timeresp.py b/control/matlab/timeresp.py index cf57952f1..5420bfdf4 100644 --- a/control/matlab/timeresp.py +++ b/control/matlab/timeresp.py @@ -276,7 +276,6 @@ def lsim(sys, U=0., T=None, X0=0.): Examples -------- - >>> import numpy as np >>> from control.matlab import rss, lsim >>> G = rss(4) diff --git a/control/matlab/wrappers.py b/control/matlab/wrappers.py index b17318edf..1c4ee053a 100644 --- a/control/matlab/wrappers.py +++ b/control/matlab/wrappers.py @@ -46,7 +46,7 @@ def bode(*args, **kwargs): Examples -------- - >>> from control import ss, bode + >>> from control.matlab import ss, bode >>> sys = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") >>> mag, phase, omega = bode(sys) diff --git a/control/modelsimp.py b/control/modelsimp.py index 93411d264..05ac4827e 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -84,10 +84,8 @@ def hsvd(sys): Examples -------- - >>> from control import tf, tf2ss, hsvd - - >>> G = tf2ss(tf([1],[1, 2])) - >>> H = hsvd(G) + >>> G = ct.tf2ss(ct.tf([1],[1, 2])) + >>> H = ct.hsvd(G) >>> H[0] 0.25 @@ -140,10 +138,8 @@ def modred(sys, ELIM, method='matchdc'): Examples -------- - >>> from control import rss, modred - - >>> G = rss(4) - >>> Gr = modred(G, [0,2], method='matchdc') + >>> G = ct.rss(4) + >>> Gr = ct.modred(G, [0,2], method='matchdc') >>> Gr.nstates 2 @@ -266,10 +262,8 @@ def balred(sys, orders, method='truncate', alpha=None): Examples -------- - >>> from control import balred, rss - - >>> G = rss(4) - >>> Gr = balred(G, orders=2, method='matchdc') + >>> G = ct.rss(4) + >>> Gr = ct.balred(G, orders=2, method='matchdc') >>> Gr.nstates 2 @@ -462,13 +456,10 @@ def markov(Y, U, m=None, transpose=False): Examples -------- - >>> import numpy as np - >>> from control import forced_response, markov, tf - >>> T = np.linspace(0, 10, 100) >>> U = np.ones((1, 100)) - >>> T, Y = forced_response(tf([1], [1, 0.5], True), T, U) - >>> H = markov(Y, U, 3, transpose=False) + >>> T, Y = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U) + >>> H = ct.markov(Y, U, 3, transpose=False) """ # Convert input parameters to 2D arrays (if they aren't already) diff --git a/control/optimal.py b/control/optimal.py index 9a8218a91..77be02d2a 100644 --- a/control/optimal.py +++ b/control/optimal.py @@ -6,6 +6,12 @@ """The :mod:`~control.optimal` module provides support for optimization-based controllers for nonlinear systems with state and input constraints. +The docstring examples assume that the following import commands:: + + >>> import numpy as np + >>> import control as ct + >>> import control.optimal as obc + """ import numpy as np diff --git a/control/robust.py b/control/robust.py index 2c2c8465e..9874be9dc 100644 --- a/control/robust.py +++ b/control/robust.py @@ -70,23 +70,21 @@ def h2syn(P, nmeas, ncon): Examples -------- - >>> from control import h2syn, interconnect, ss, tf, feedback - >>> # Unstable first order SISI system - >>> G = tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> G = ct.tf([1],[1,-1], inputs=['u'], outputs=['y']) >>> max(G.poles()) < 0 # Is G stable? False >>> # Create partitioned system with trivial unity systems - >>> P11 = tf([0], [1], inputs=['w'], outputs=['z']) - >>> P12 = tf([1], [1], inputs=['u'], outputs=['z']) - >>> P21 = tf([1], [1], inputs=['w'], outputs=['y']) + >>> P11 = ct.tf([0], [1], inputs=['w'], outputs=['z']) + >>> P12 = ct.tf([1], [1], inputs=['u'], outputs=['z']) + >>> P21 = ct.tf([1], [1], inputs=['w'], outputs=['y']) >>> P22 = G - >>> P = interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) + >>> P = ct.interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) >>> # Synthesize H2 optimal stabilizing controller - >>> K = h2syn(P, nmeas=1, ncon=1) - >>> T = feedback(G, K, sign=1) + >>> K = ct.h2syn(P, nmeas=1, ncon=1) + >>> T = ct.feedback(G, K, sign=1) >>> max(T.poles()) < 0 # Is T stable? True @@ -152,23 +150,21 @@ def hinfsyn(P, nmeas, ncon): Examples -------- - >>> from control import hinfsyn, interconnect, ss, tf, feedback - >>> # Unstable first order SISI system - >>> G = tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> G = ct.tf([1],[1,-1], inputs=['u'], outputs=['y']) >>> max(G.poles()) < 0 False >>> # Create partitioned system with trivial unity systems - >>> P11 = tf([0], [1], inputs=['w'], outputs=['z']) - >>> P12 = tf([1], [1], inputs=['u'], outputs=['z']) - >>> P21 = tf([1], [1], inputs=['w'], outputs=['y']) + >>> P11 = ct.tf([0], [1], inputs=['w'], outputs=['z']) + >>> P12 = ct.tf([1], [1], inputs=['u'], outputs=['z']) + >>> P21 = ct.tf([1], [1], inputs=['w'], outputs=['y']) >>> P22 = G - >>> P = interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) + >>> P = ct.interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) >>> # Synthesize Hinf optimal stabilizing controller - >>> K, CL, gam, rcond = hinfsyn(P, nmeas=1, ncon=1) - >>> T = feedback(G, K, sign=1) + >>> K, CL, gam, rcond = ct.hinfsyn(P, nmeas=1, ncon=1) + >>> T = ct.feedback(G, K, sign=1) >>> max(T.poles()) < 0 True diff --git a/control/sisotool.py b/control/sisotool.py index 944b6f0d1..968a66956 100644 --- a/control/sisotool.py +++ b/control/sisotool.py @@ -81,10 +81,8 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None, Examples -------- - >>> from control import sisotool, tf - - >>> G = tf([1000], [1,25,100,0]) - >>> sisotool(G) # doctest: +SKIP + >>> G = ct.tf([1000], [1,25,100,0]) + >>> ct.sisotool(G) # doctest: +SKIP """ from .rlocus import root_locus diff --git a/control/statefbk.py b/control/statefbk.py index 57479c7a1..4a8e3b293 100644 --- a/control/statefbk.py +++ b/control/statefbk.py @@ -120,11 +120,9 @@ def place(A, B, p): Examples -------- - >>> from control import place - >>> A = [[-1, -1], [0, 1]] >>> B = [[0], [1]] - >>> K = place(A, B, [-2, -5]) + >>> K = ct.place(A, B, [-2, -5]) See Also -------- @@ -994,11 +992,8 @@ def ctrb(A, B): Examples -------- - >>> import numpy as np - >>> from control import ctrb, tf, tf2ss - - >>> G = tf2ss(tf([1],[1, 2, 3])) - >>> C = ctrb(G.A, G.B) + >>> G = ct.tf2ss(ct.tf([1],[1, 2, 3])) + >>> C = ct.ctrb(G.A, G.B) >>> np.linalg.matrix_rank(C) 2 @@ -1036,11 +1031,8 @@ def obsv(A, C): Examples -------- - >>> import numpy as np - >>> from control import obsv, tf, tf2ss - - >>> G = tf2ss(tf([1],[1, 2, 3])) - >>> C = obsv(G.A, G.C) + >>> G = ct.tf2ss(ct.tf([1],[1, 2, 3])) + >>> C = ct.obsv(G.A, G.C) >>> np.linalg.matrix_rank(C) 2 @@ -1092,13 +1084,11 @@ def gram(sys, type): Examples -------- - >>> from control import gram, rss - - >>> G = rss(4) - >>> Wc = gram(G, 'c') - >>> Wo = gram(G, 'o') - >>> Rc = gram(G, 'cf') # where Wc = Rc' * Rc - >>> Ro = gram(G, 'of') # where Wo = Ro' * Ro + >>> G = ct.rss(4) + >>> Wc = ct.gram(G, 'c') + >>> Wo = ct.gram(G, 'o') + >>> Rc = ct.gram(G, 'cf') # where Wc = Rc' * Rc + >>> Ro = ct.gram(G, 'of') # where Wo = Ro' * Ro """ diff --git a/control/statesp.py b/control/statesp.py index a6f1756b2..ca9c19acb 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1362,9 +1362,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> from control import StateSpace - >>> - >>> G = StateSpace(0, 1, 1, 0) + >>> G = ct.StateSpace(0, 1, 1, 0) >>> sysd = G.sample(0.5, method='bilinear') """ @@ -1891,14 +1889,12 @@ def tf2ss(*args, **kwargs): Examples -------- - >>> from control import tf, tf2ss - >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] - >>> sys1 = tf2ss(num, den) + >>> sys1 = ct.tf2ss(num, den) - >>> sys_tf = tf(num, den) - >>> sys2 = tf2ss(sys_tf) + >>> sys_tf = ct.tf(num, den) + >>> sys2 = ct.tf2ss(sys_tf) """ diff --git a/control/timeresp.py b/control/timeresp.py index 5f001848c..3264a87e6 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -916,12 +916,9 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, Examples -------- - >>> import numpy as np - >>> from control import forced_response, rss - - >>> G = rss(4) + >>> G = ct.rss(4) >>> T = np.linspace(0,10) - >>> T, yout = forced_response(G, T=T) + >>> T, yout = ct.forced_response(G, T=T) See :ref:`time-series-convention` and :ref:`package-configuration-parameters`. @@ -1333,10 +1330,8 @@ def step_response(sys, T=None, X0=0., input=None, output=None, T_num=None, Examples -------- - >>> from control import step_response, rss - - >>> G = rss(4) - >>> T, yout = step_response(G) + >>> G = ct.rss(4) + >>> T, yout = ct.step_response(G) """ # Create the time and input vectors @@ -1448,10 +1443,8 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None, Examples -------- - >>> from control import step_info, TransferFunction - - >>> sys = TransferFunction([-1, 1], [1, 1, 1]) - >>> S = step_info(sys) + >>> sys = ct.TransferFunction([-1, 1], [1, 1, 1]) + >>> S = ct.step_info(sys) >>> for k in S: ... print(f"{k}: {S[k]:3.4}") ... @@ -1469,16 +1462,14 @@ def step_info(sysdata, T=None, T_num=None, yfinal=None, characteristics for the second input and specify a 5% error until the signal is considered settled. - >>> from numpy import sqrt - >>> from control import step_info, StateSpace - - >>> sys = StateSpace([[-1., -1.], + >>> from math import sqrt + >>> sys = ct.StateSpace([[-1., -1.], ... [1., 0.]], ... [[-1./sqrt(2.), 1./sqrt(2.)], ... [0, 0]], ... [[sqrt(2.), -sqrt(2.)]], ... [[0, 0]]) - >>> S = step_info(sys, T=10., SettlingTimeThreshold=0.05) + >>> S = ct.step_info(sys, T=10., SettlingTimeThreshold=0.05) >>> for k, v in S[0][1].items(): ... print(f"{k}: {float(v):3.4}") RiseTime: 1.212 @@ -1696,10 +1687,8 @@ def initial_response(sys, T=None, X0=0., input=0, output=None, T_num=None, Examples -------- - >>> from control import initial_response, rss - - >>> G = rss(4) - >>> T, yout = initial_response(G) + >>> G = ct.rss(4) + >>> T, yout = ct.initial_response(G) """ squeeze, sys = _get_ss_simo(sys, input, output, squeeze=squeeze) @@ -1814,10 +1803,8 @@ def impulse_response(sys, T=None, X0=0., input=None, output=None, T_num=None, Examples -------- - >>> from control import impulse_response, rss - - >>> G = rss(4) - >>> T, yout = impulse_response(G) + >>> G = ct.rss(4) + >>> T, yout = ct.impulse_response(G) """ # Convert to state space so that we can simulate diff --git a/control/xferfcn.py b/control/xferfcn.py index 64daa9a08..b20b1229a 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -141,9 +141,7 @@ class TransferFunction(LTI): discrete time. These can be used to create variables that allow algebraic creation of transfer functions. For example, - >>> from control import TransferFunction - - >>> s = TransferFunction.s + >>> s = ct.TransferFunction.s >>> G = (s + 1)/(s**2 + 2*s + 1) """ @@ -1147,9 +1145,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> from control import tf - - >>> sys = tf(1, [1,1]) + >>> sys = ct.tf(1, [1,1]) >>> sysd = sys.sample(0.5, method='bilinear') """ @@ -1209,8 +1205,7 @@ def dcgain(self, warn_infinite=False): Examples -------- - >>> from control import tf - >>> G = tf([1],[1, 4]) + >>> G = ct.tf([1],[1, 4]) >>> G.dcgain() 0.25 @@ -1541,22 +1536,20 @@ def tf(*args, **kwargs): Examples -------- - >>> from control import tf, ss - >>> # Create a MIMO transfer function object >>> # The transfer function from the 2nd input to the 1st output is >>> # (3s + 4) / (6s^2 + 5s + 4). >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]] >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]] - >>> sys1 = tf(num, den) + >>> sys1 = ct.tf(num, den) >>> # Create a variable 's' to allow algebra operations for SISO systems - >>> s = tf('s') + >>> s = ct.tf('s') >>> G = (s + 1)/(s**2 + 2*s + 1) >>> # Convert a StateSpace to a TransferFunction object. - >>> sys_ss = ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") - >>> sys2 = tf(sys1) + >>> sys_ss = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> sys2 = ct.tf(sys1) """ @@ -1696,16 +1689,14 @@ def ss2tf(*args, **kwargs): Examples -------- - >>> from control import ss, ss2tf - >>> A = [[1., -2], [3, -4]] >>> B = [[5.], [7]] >>> C = [[6., 8]] >>> D = [[9.]] - >>> sys1 = ss2tf(A, B, C, D) + >>> sys1 = ct.ss2tf(A, B, C, D) - >>> sys_ss = ss(A, B, C, D) - >>> sys2 = ss2tf(sys_ss) + >>> sys_ss = ct.ss(A, B, C, D) + >>> sys2 = ct.ss2tf(sys_ss) """ diff --git a/doc/conf.py b/doc/conf.py index 9e78a47b7..7cfa4b4f6 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -270,3 +270,13 @@ def linkcode_resolve(domain, info): author, 'PythonControlLibrary', 'One line description of project.', 'Miscellaneous'), ] + +# -- Options for doctest ---------------------------------------------- + +# Import control as ct +doctest_global_setup = """ +import numpy as np +import control as ct +import control.optimal as obc +import control.flatsys as fs +""" diff --git a/doc/optimal.rst b/doc/optimal.rst index 807b9b9c6..e84cd2d98 100644 --- a/doc/optimal.rst +++ b/doc/optimal.rst @@ -225,18 +225,18 @@ penalizes the state and input using quadratic cost functions:: Q = np.diag([0, 0, 0.1]) # don't turn too sharply R = np.diag([1, 1]) # keep inputs small P = np.diag([1000, 1000, 1000]) # get close to final point - traj_cost = opt.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf) - term_cost = opt.quadratic_cost(vehicle, P, 0, x0=xf) + traj_cost = obc.quadratic_cost(vehicle, Q, R, x0=xf, u0=uf) + term_cost = obc.quadratic_cost(vehicle, P, 0, x0=xf) We also constraint the maximum turning rate to 0.1 radians (about 6 degees) and constrain the velocity to be in the range of 9 m/s to 11 m/s:: - constraints = [ opt.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ] + constraints = [ obc.input_range_constraint(vehicle, [8, -0.1], [12, 0.1]) ] Finally, we solve for the optimal inputs:: timepts = np.linspace(0, Tf, 10, endpoint=True) - result = opt.solve_ocp( + result = obc.solve_ocp( vehicle, timepts, x0, traj_cost, constraints, terminal_cost=term_cost, initial_guess=u0) From 98fdb35f06d0ab02d61747e8f807dd1fa6ba842c Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 24 Mar 2023 21:54:03 -0700 Subject: [PATCH 4/9] clean up/simplify docstring examples --- control/bdalg.py | 10 +++++----- control/canonical.py | 34 ++++++++++++++-------------------- control/config.py | 3 --- control/ctrlutil.py | 4 ++-- control/delay.py | 12 ++++++------ control/descfcn.py | 44 ++++++++++++++++++++------------------------ control/exception.py | 28 +++------------------------- control/frdata.py | 2 +- control/freqplot.py | 13 +++++++------ control/iosys.py | 16 ++++++---------- control/lti.py | 8 ++++---- control/modelsimp.py | 4 ++-- control/phaseplot.py | 3 --- control/robust.py | 7 ++++--- control/sisotool.py | 2 +- control/statefbk.py | 4 ++-- control/statesp.py | 2 +- control/timeresp.py | 2 +- control/xferfcn.py | 12 ++++++------ doc/control.rst | 3 --- 20 files changed, 85 insertions(+), 128 deletions(-) diff --git a/control/bdalg.py b/control/bdalg.py index 911a20b53..0b1d481c8 100644 --- a/control/bdalg.py +++ b/control/bdalg.py @@ -190,13 +190,13 @@ def negate(sys): Examples -------- - >>> G = ct.tf([2],[1, 1]) - >>> G.dcgain() > 0 - True + >>> G = ct.tf([2], [1, 1]) + >>> G.dcgain() + 2.0 >>> Gn = ct.negate(G) # Same as sys2 = -sys1. - >>> Gn.dcgain() < 0 - True + >>> Gn.dcgain() + -2.0 """ return -sys diff --git a/control/canonical.py b/control/canonical.py index 436db667b..d02cce98e 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -39,21 +39,20 @@ def canonical_form(xsys, form='reachable'): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss(G) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.canonical_form(Gs) # default reachable - >>> Gc.B # doctest: +SKIP - matrix([[1.], - [0.]]) + >>> Gc.B + array([[1.], + [0.]]) >>> Gc, T = ct.canonical_form(Gs, 'observable') - >>> Gc.C # doctest: +SKIP - matrix([[1., 0.]]) + >>> Gc.C + array([[1., 0.]]) >>> Gc, T = ct.canonical_form(Gs, 'modal') - >>> Gc.A # doctest: +SKIP - matrix([[-2., 0.], - [ 0., -1.]]) + >>> Gc.A + array([[-2., 0.], + [ 0., -1.]]) """ @@ -87,8 +86,7 @@ def reachable_form(xsys): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss(G) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.reachable_form(Gs) # default reachable >>> Gc.B # doctest: +SKIP matrix([[1.], @@ -151,8 +149,7 @@ def observable_form(xsys): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss(G) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.observable_form(Gs) >>> Gc.C # doctest: +SKIP matrix([[1., 0.]]) @@ -218,8 +215,7 @@ def similarity_transform(xsys, T, timescale=1, inverse=False): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss(G) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gs.A # doctest: +SKIP matrix([[-3., -2.], [ 1., 0.]]) @@ -426,8 +422,7 @@ def bdschur(a, condmax=None, sort=None): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss(G) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> amodal, tmodal, blksizes = ct.bdschur(Gs.A) >>> amodal #doctest: +SKIP array([[-2., 0.], @@ -502,8 +497,7 @@ def modal_form(xsys, condmax=None, sort=False): Examples -------- - >>> G = ct.tf([1],[1, 3, 2]) - >>> Gs = ct.tf2ss((G)) + >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.modal_form(Gs) # default reachable >>> Gc.A # doctest: +SKIP matrix([[-2., 0.], diff --git a/control/config.py b/control/config.py index 78eaeeb6f..abf2077af 100644 --- a/control/config.py +++ b/control/config.py @@ -74,11 +74,8 @@ def set_defaults(module, **keywords): >>> ct.set_defaults('freqplot', number_of_samples=100) >>> ct.defaults['freqplot.number_of_samples'] 100 - >>> # do some customized freqplotting >>> ct.reset_defaults() - >>> ct.defaults['freqplot.number_of_samples'] - 1000 """ if not isinstance(module, str): diff --git a/control/ctrlutil.py b/control/ctrlutil.py index f745e95ee..fa7b91ee5 100644 --- a/control/ctrlutil.py +++ b/control/ctrlutil.py @@ -90,11 +90,11 @@ def issys(obj): Examples -------- - >>> G = ct.tf([1],[1, 1]) + >>> G = ct.tf([1], [1, 1]) >>> ct.issys(G) True - >>> K = ct.InputOutputSystem() # Not necessarily LTI! + >>> K = np.array([[1, 1]]) >>> ct.issys(K) False diff --git a/control/delay.py b/control/delay.py index 3779f1ddb..d22e44107 100644 --- a/control/delay.py +++ b/control/delay.py @@ -78,13 +78,13 @@ def pade(T, n=1, numdeg=None): Examples -------- >>> delay = 1 - >>> num, den = ct.pade(delay, 5) - >>> len(num), len(den) - (6, 6) + >>> num, den = ct.pade(delay, 3) + >>> num, den + ([-1.0, 12.0, -60.0, 120.0], [1.0, 12.0, 60.0, 120.0]) - >>> num, den = ct.pade(delay, 5, -2) - >>> len(num), len(den) - (4, 6) + >>> num, den = ct.pade(delay, 3, -2) + >>> num, den + ([-6.0, 24.0], [1.0, 6.0, 18.0, 24.0]) """ if numdeg is None: diff --git a/control/descfcn.py b/control/descfcn.py index 2cc58c75b..715d61191 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -249,9 +249,8 @@ def describing_function_plot( >>> H_simple = ct.tf([8], [1, 2, 2, 1]) >>> F_saturation = ct.saturation_nonlinearity(1) >>> amp = np.linspace(1, 4, 10) - >>> points = ct.describing_function_plot(H_simple, F_saturation, amp) - >>> len(points) - 1 + >>> ct.describing_function_plot(H_simple, F_saturation, amp) + [(3.343844998258643, 1.4142293090899216)] """ # Decide whether to turn on warnings or not @@ -366,12 +365,11 @@ class saturation_nonlinearity(DescribingFunctionNonlinearity): Examples -------- >>> nl = ct.saturation_nonlinearity(5) - >>> f = lambda x: round(nl(x)) - >>> f(1) + >>> nl(1) 1 - >>> f(10) + >>> nl(10) 5 - >>> f(-10) + >>> nl(-10) -5 """ @@ -430,16 +428,15 @@ class relay_hysteresis_nonlinearity(DescribingFunctionNonlinearity): Examples -------- >>> nl = ct.relay_hysteresis_nonlinearity(1, 2) - >>> f = lambda x: round(nl(x)) - >>> f(0) + >>> nl(0) -1 - >>> f(1) # not enough for switching on + >>> nl(1) # not enough for switching on -1 - >>> f(5) + >>> nl(5) 1 - >>> f(-1) # not enough for switching off + >>> nl(-1) # not enough for switching off 1 - >>> f(-5) + >>> nl(-5) -1 """ @@ -500,19 +497,18 @@ class friction_backlash_nonlinearity(DescribingFunctionNonlinearity): Examples -------- >>> nl = ct.friction_backlash_nonlinearity(2) # backlash of +/- 1 - >>> f = lambda x: round(nl(x)) - >>> f(0) - 0 - >>> f(1) # not enough to overcome backlash + >>> nl(0) 0 - >>> f(2) - 1 - >>> f(1) - 1 - >>> f(0) # not enough to overcome backlash - 1 - >>> f(-1) + >>> nl(1) # not enough to overcome backlash 0 + >>> nl(2) + 1.0 + >>> nl(1) + 1.0 + >>> nl(0) # not enough to overcome backlash + 1.0 + >>> nl(-1) + 0.0 """ diff --git a/control/exception.py b/control/exception.py index d308dc7f6..e4758cc49 100644 --- a/control/exception.py +++ b/control/exception.py @@ -63,14 +63,7 @@ class ControlNotImplemented(NotImplementedError): # Utility function to see if slycot is installed slycot_installed = None def slycot_check(): - """Return True if slycot is installed, otherwise False. - - Examples - -------- - >>> ct.slycot_check() - True - - """ + """Return True if slycot is installed, otherwise False.""" global slycot_installed if slycot_installed is None: try: @@ -84,15 +77,7 @@ def slycot_check(): # Utility function to see if pandas is installed pandas_installed = None def pandas_check(): - """Return True if pandas is installed, otherwise False. - - Examples - -------- - >>> ct.pandas_check() - True - - """ - + """Return True if pandas is installed, otherwise False.""" global pandas_installed if pandas_installed is None: try: @@ -105,14 +90,7 @@ def pandas_check(): # Utility function to see if cvxopt is installed cvxopt_installed = None def cvxopt_check(): - """Return True if cvxopt is installed, otherwise False. - - Examples - -------- - >>> ct.cvxopt_check() - True - - """ + """Return True if cvxopt is installed, otherwise False.""" global cvxopt_installed if cvxopt_installed is None: try: diff --git a/control/frdata.py b/control/frdata.py index 0783aaa31..83873a120 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -672,7 +672,7 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1): a frequency response data at the specified omega. If sys is a scalar, then the number of inputs and outputs can be specified manually, as in: -+ + >>> import numpy as np >>> from control.frdata import _convert_to_FRD diff --git a/control/freqplot.py b/control/freqplot.py index 08ba0ab16..c20fb189e 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -174,7 +174,7 @@ def bode_plot(syslist, omega=None, Examples -------- - >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> G = ct.ss([[-1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) >>> Gmag, Gphase, Gomega = ct.bode_plot(G) """ @@ -692,7 +692,7 @@ def nyquist_plot( Examples -------- - >>> G = ct.zpk([],[-1,-2,-3], gain=100) + >>> G = ct.zpk([], [-1, -2, -3], gain=100) >>> ct.nyquist_plot(G) 2 @@ -1262,8 +1262,8 @@ def gangof4_plot(P, C, omega=None, **kwargs): Examples -------- - >>> P = ct.tf([1],[1, 1]) - >>> C = ct.tf([2],[1]) + >>> P = ct.tf([1], [1, 1]) + >>> C = ct.tf([2], [1]) >>> ct.gangof4_plot(P, C) """ @@ -1411,7 +1411,8 @@ def singular_values_plot(syslist, omega=None, -------- >>> omegas = np.logspace(-4, 1, 1000) >>> den = [75, 1] - >>> G = ct.tf([[[87.8], [-86.4]], [[108.2], [-109.6]]], [[den, den], [den, den]]) + >>> G = ct.tf([[[87.8], [-86.4]], [[108.2], [-109.6]]], + ... [[den, den], [den, den]]) >>> sigmas, omegas = ct.singular_values_plot(G, omega=omegas, plot=False) >>> sigmas, omegas = ct.singular_values_plot(G, 0.0, plot=False) @@ -1630,7 +1631,7 @@ def _default_frequency_range(syslist, Hz=None, number_of_samples=None, Examples -------- - >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> G = ct.ss([[-1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) >>> omega = ct._default_frequency_range(G) >>> omega.min(), omega.max() (0.1, 100.0) diff --git a/control/iosys.py b/control/iosys.py index 589d81add..97e1fb63d 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -2373,7 +2373,7 @@ def ss(*args, **kwargs): -------- Create a Linear I/O system object from matrices. - >>> G = ct.ss([[1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) + >>> G = ct.ss([[-1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) Convert a TransferFunction to a StateSpace object. @@ -2778,12 +2778,8 @@ def interconnect( Examples -------- - >>> P = ct.LinearIOSystem( - ... ct.rss(2, 2, 2, strictly_proper=True), - ... name='P') - >>> C = ct.LinearIOSystem( - ... ct.rss(2, 2, 2), - ... name='C') + >>> P = ct.rss(2, 2, 2, strictly_proper=True, name='P') + >>> C = ct.rss(2, 2, 2, name='C') >>> T = ct.interconnect( ... [P, C], ... connections = [ @@ -3002,10 +2998,10 @@ def summing_junction( Examples -------- - >>> P = ct.tf2io(ct.tf(1, [1, 0]), inputs='u', outputs='y') - >>> C = ct.tf2io(ct.tf(10, [1, 1]), inputs='e', outputs='u') + >>> P = ct.tf2io(1, [1, 0], inputs='u', outputs='y') + >>> C = ct.tf2io(10, [1, 1], inputs='e', outputs='u') >>> sumblk = ct.summing_junction(inputs=['r', '-y'], output='e') - >>> T = ct.interconnect((P, C, sumblk), inputs='r', outputs='y') + >>> T = ct.interconnect([P, C, sumblk], inputs='r', outputs='y') >>> T.ninputs, T.noutputs, T.nstates (1, 1, 2) diff --git a/control/lti.py b/control/lti.py index c221832b5..6dc3bc62c 100644 --- a/control/lti.py +++ b/control/lti.py @@ -326,8 +326,8 @@ def damp(sys, doprint=True): Examples -------- - >>> G = ct.tf([1],[1, 4]) - >>> wn, damping, poles = ct.damp(G) # doctest: +SKIP + >>> G = ct.tf([1], [1, 4]) + >>> wn, damping, poles = ct.damp(G) _____Eigenvalue______ Damping___ Frequency_ -4 1 4 @@ -393,7 +393,7 @@ def evalfr(sys, x, squeeze=None): Examples -------- - >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> G = ct.ss([[-1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) >>> fresp = ct.evalfr(G, 1j) # evaluate at s = 1j .. todo:: Add example with MIMO system @@ -454,7 +454,7 @@ def frequency_response(sys, omega, squeeze=None): Examples -------- - >>> G = ct.ss("1. -2; 3. -4", "5.; 7", "6. 8", "9.") + >>> G = ct.ss([[-1, -2], [3, -4]], [[5], [7]], [[6, 8]], [[9]]) >>> mag, phase, omega = ct.freqresp(G, [0.1, 1., 10.]) .. todo:: diff --git a/control/modelsimp.py b/control/modelsimp.py index 05ac4827e..f7b15093d 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -84,7 +84,7 @@ def hsvd(sys): Examples -------- - >>> G = ct.tf2ss(ct.tf([1],[1, 2])) + >>> G = ct.tf2ss([1], [1, 2]) >>> H = ct.hsvd(G) >>> H[0] 0.25 @@ -139,7 +139,7 @@ def modred(sys, ELIM, method='matchdc'): Examples -------- >>> G = ct.rss(4) - >>> Gr = ct.modred(G, [0,2], method='matchdc') + >>> Gr = ct.modred(G, [0, 2], method='matchdc') >>> Gr.nstates 2 diff --git a/control/phaseplot.py b/control/phaseplot.py index 6a4be5ca6..91d7b79b0 100644 --- a/control/phaseplot.py +++ b/control/phaseplot.py @@ -115,9 +115,6 @@ def phase_plot(odefun, X=None, Y=None, scale=1, X0=None, T=None, -------- box_grid : construct box-shaped grid of initial conditions - Examples - -------- - """ # diff --git a/control/robust.py b/control/robust.py index 9874be9dc..a0e53d199 100644 --- a/control/robust.py +++ b/control/robust.py @@ -71,7 +71,7 @@ def h2syn(P, nmeas, ncon): Examples -------- >>> # Unstable first order SISI system - >>> G = ct.tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> G = ct.tf([1], [1, -1], inputs=['u'], outputs=['y']) >>> max(G.poles()) < 0 # Is G stable? False @@ -80,7 +80,8 @@ def h2syn(P, nmeas, ncon): >>> P12 = ct.tf([1], [1], inputs=['u'], outputs=['z']) >>> P21 = ct.tf([1], [1], inputs=['w'], outputs=['y']) >>> P22 = G - >>> P = ct.interconnect([P11, P12, P21, P22], inplist=['w', 'u'], outlist=['z', 'y']) + >>> P = ct.interconnect([P11, P12, P21, P22], + ... inplist=['w', 'u'], outlist=['z', 'y']) >>> # Synthesize H2 optimal stabilizing controller >>> K = ct.h2syn(P, nmeas=1, ncon=1) @@ -151,7 +152,7 @@ def hinfsyn(P, nmeas, ncon): Examples -------- >>> # Unstable first order SISI system - >>> G = ct.tf([1],[1,-1], inputs=['u'], outputs=['y']) + >>> G = ct.tf([1], [1,-1], inputs=['u'], outputs=['y']) >>> max(G.poles()) < 0 False diff --git a/control/sisotool.py b/control/sisotool.py index 968a66956..c1b260d08 100644 --- a/control/sisotool.py +++ b/control/sisotool.py @@ -81,7 +81,7 @@ def sisotool(sys, initial_gain=None, xlim_rlocus=None, ylim_rlocus=None, Examples -------- - >>> G = ct.tf([1000], [1,25,100,0]) + >>> G = ct.tf([1000], [1, 25, 100, 0]) >>> ct.sisotool(G) # doctest: +SKIP """ diff --git a/control/statefbk.py b/control/statefbk.py index 4a8e3b293..20d99dff6 100644 --- a/control/statefbk.py +++ b/control/statefbk.py @@ -992,7 +992,7 @@ def ctrb(A, B): Examples -------- - >>> G = ct.tf2ss(ct.tf([1],[1, 2, 3])) + >>> G = ct.tf2ss([1], [1, 2, 3]) >>> C = ct.ctrb(G.A, G.B) >>> np.linalg.matrix_rank(C) 2 @@ -1031,7 +1031,7 @@ def obsv(A, C): Examples -------- - >>> G = ct.tf2ss(ct.tf([1],[1, 2, 3])) + >>> G = ct.tf2ss([1], [1, 2, 3]) >>> C = ct.obsv(G.A, G.C) >>> np.linalg.matrix_rank(C) 2 diff --git a/control/statesp.py b/control/statesp.py index ca9c19acb..41f92ae21 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -1362,7 +1362,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> G = ct.StateSpace(0, 1, 1, 0) + >>> G = ct.ss(0, 1, 1, 0) >>> sysd = G.sample(0.5, method='bilinear') """ diff --git a/control/timeresp.py b/control/timeresp.py index 3264a87e6..638a07329 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -917,7 +917,7 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, Examples -------- >>> G = ct.rss(4) - >>> T = np.linspace(0,10) + >>> T = np.linspace(0, 10) >>> T, yout = ct.forced_response(G, T=T) See :ref:`time-series-convention` and diff --git a/control/xferfcn.py b/control/xferfcn.py index b20b1229a..618b86416 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -1145,7 +1145,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None, Examples -------- - >>> sys = ct.tf(1, [1,1]) + >>> sys = ct.tf(1, [1, 1]) >>> sysd = sys.sample(0.5, method='bilinear') """ @@ -1205,7 +1205,7 @@ def dcgain(self, warn_infinite=False): Examples -------- - >>> G = ct.tf([1],[1, 4]) + >>> G = ct.tf([1], [1, 4]) >>> G.dcgain() 0.25 @@ -1689,10 +1689,10 @@ def ss2tf(*args, **kwargs): Examples -------- - >>> A = [[1., -2], [3, -4]] - >>> B = [[5.], [7]] - >>> C = [[6., 8]] - >>> D = [[9.]] + >>> A = [[-1, -2], [3, -4]] + >>> B = [[5], [6]] + >>> C = [[7, 8]] + >>> D = [[9]] >>> sys1 = ct.ss2tf(A, B, C, D) >>> sys_ss = ct.ss(A, B, C, D) diff --git a/doc/control.rst b/doc/control.rst index 313f2a3f1..8dc8a09a4 100644 --- a/doc/control.rst +++ b/doc/control.rst @@ -173,7 +173,6 @@ Utility functions and conversions augw bdschur canonical_form - cvxopt_check damp db2mag isctime @@ -184,13 +183,11 @@ Utility functions and conversions modal_form observable_form pade - pandas_check reachable_form reset_defaults sample_system set_defaults similarity_transform - slycot_check ss2tf ssdata tf2ss From 07cedd8ee2dc378ac273c88564f7b39485baa218 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 24 Mar 2023 22:10:18 -0700 Subject: [PATCH 5/9] revert to use classes.pdf in doc/ (instead of classes.png) --- doc/Makefile | 6 +++--- doc/classes.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index a38e94b17..6e1012343 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -15,9 +15,9 @@ help: .PHONY: help Makefile # Rules to create figures -FIGS = classes.png -classes.png: classes.fig - fig2dev -Lpng $< $@ +FIGS = classes.pdf +classes.pdf: classes.fig + fig2dev -Lpdf $< $@ # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). diff --git a/doc/classes.rst b/doc/classes.rst index 3db67915c..87ce457de 100644 --- a/doc/classes.rst +++ b/doc/classes.rst @@ -24,7 +24,7 @@ The following figure illustrates the relationship between the classes and some of the functions that can be used to convert objects from one class to another: -.. image:: classes.png +.. image:: classes.pdf :width: 800 | From 1d1c2890966ff80c606fd337e01e4bad9f75859b Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 24 Mar 2023 23:21:30 -0700 Subject: [PATCH 6/9] fix up rounding errors and matrix/array --- control/canonical.py | 63 +++++++++++++++++++++++++------------------- control/descfcn.py | 2 +- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/control/canonical.py b/control/canonical.py index d02cce98e..f5e1d25de 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -17,6 +17,7 @@ __all__ = ['canonical_form', 'reachable_form', 'observable_form', 'modal_form', 'similarity_transform', 'bdschur'] + def canonical_form(xsys, form='reachable'): """Convert a system into canonical form @@ -41,16 +42,16 @@ def canonical_form(xsys, form='reachable'): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.canonical_form(Gs) # default reachable - >>> Gc.B + >>> Gc.B.round() array([[1.], [0.]]) >>> Gc, T = ct.canonical_form(Gs, 'observable') - >>> Gc.C + >>> Gc.C.round() array([[1., 0.]]) >>> Gc, T = ct.canonical_form(Gs, 'modal') - >>> Gc.A + >>> Gc.A.round() # doctest: +SKIP array([[-2., 0.], [ 0., -1.]]) @@ -88,9 +89,9 @@ def reachable_form(xsys): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.reachable_form(Gs) # default reachable - >>> Gc.B # doctest: +SKIP - matrix([[1.], - [0.]]) + >>> Gc.B.round() + array([[1.], + [0.]]) """ # Check to make sure we have a SISO system @@ -151,8 +152,8 @@ def observable_form(xsys): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.observable_form(Gs) - >>> Gc.C # doctest: +SKIP - matrix([[1., 0.]]) + >>> Gc.C.round() + array([[1., 0.]]) """ # Check to make sure we have a SISO system @@ -216,15 +217,15 @@ def similarity_transform(xsys, T, timescale=1, inverse=False): Examples -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) - >>> Gs.A # doctest: +SKIP - matrix([[-3., -2.], - [ 1., 0.]]) + >>> Gs.A.round() + array([[-3., -2.], + [ 1., 0.]]) - >>> T = np.array([[0,1],[1,0]]) + >>> T = np.array([[0, 1], [1, 0]]) >>> Gt = ct.similarity_transform(Gs, T) - >>> Gt.A # doctest: +SKIP - matrix([[ 0., 1.], - [-2., -3.]]) + >>> Gt.A.round() + array([[ 0., 1.], + [-2., -3.]]) """ # Create a new system, starting with a copy of the old one @@ -293,7 +294,8 @@ def _bdschur_condmax_search(aschur, tschur, condmax): Iterates mb03rd with different pmax values until: - result is non-defective; - - or condition number of similarity transform is unchanging despite large pmax; + - or condition number of similarity transform is unchanging + despite large pmax; - or condition number of similarity transform is close to condmax. Parameters @@ -332,22 +334,25 @@ def _bdschur_condmax_search(aschur, tschur, condmax): # get lower bound; try condmax ** 0.5 first pmaxlower = condmax ** 0.5 - amodal, tmodal, blksizes, eigvals = mb03rd(aschur.shape[0], aschur, tschur, pmax=pmaxlower) + amodal, tmodal, blksizes, eigvals = mb03rd( + aschur.shape[0], aschur, tschur, pmax=pmaxlower) if np.linalg.cond(tmodal) <= condmax: reslower = amodal, tmodal, blksizes, eigvals else: pmaxlower = 1.0 - amodal, tmodal, blksizes, eigvals = mb03rd(aschur.shape[0], aschur, tschur, pmax=pmaxlower) + amodal, tmodal, blksizes, eigvals = mb03rd( + aschur.shape[0], aschur, tschur, pmax=pmaxlower) cond = np.linalg.cond(tmodal) if cond > condmax: - msg = 'minimum cond={} > condmax={}; try increasing condmax'.format(cond, condmax) + msg = f"minimum {cond=} > {condmax=}; try increasing condmax" raise RuntimeError(msg) pmax = pmaxlower # phase 1: search for upper bound on pmax for i in range(50): - amodal, tmodal, blksizes, eigvals = mb03rd(aschur.shape[0], aschur, tschur, pmax=pmax) + amodal, tmodal, blksizes, eigvals = mb03rd( + aschur.shape[0], aschur, tschur, pmax=pmax) cond = np.linalg.cond(tmodal) if cond < condmax: pmaxlower = pmax @@ -368,7 +373,8 @@ def _bdschur_condmax_search(aschur, tschur, condmax): # phase 2: bisection search for i in range(50): pmax = (pmaxlower * pmaxupper) ** 0.5 - amodal, tmodal, blksizes, eigvals = mb03rd(aschur.shape[0], aschur, tschur, pmax=pmax) + amodal, tmodal, blksizes, eigvals = mb03rd( + aschur.shape[0], aschur, tschur, pmax=pmax) cond = np.linalg.cond(tmodal) if cond < condmax: @@ -440,7 +446,8 @@ def bdschur(a, condmax=None, sort=None): return a.copy(), np.eye(a.shape[1], a.shape[0]), np.array([]) aschur, tschur = schur(a) - amodal, tmodal, blksizes, eigvals = _bdschur_condmax_search(aschur, tschur, condmax) + amodal, tmodal, blksizes, eigvals = _bdschur_condmax_search( + aschur, tschur, condmax) if sort in ('continuous', 'discrete'): @@ -484,9 +491,11 @@ def modal_form(xsys, condmax=None, sort=False): xsys : StateSpace object System to be transformed, with state `x` condmax : None or float, optional - An upper bound on individual transformations. If None, use `bdschur` default. + An upper bound on individual transformations. If None, use + `bdschur` default. sort : bool, optional - If False (default), Schur blocks will not be sorted. See `bdschur` for sort order. + If False (default), Schur blocks will not be sorted. See `bdschur` + for sort order. Returns ------- @@ -499,9 +508,9 @@ def modal_form(xsys, condmax=None, sort=False): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.modal_form(Gs) # default reachable - >>> Gc.A # doctest: +SKIP - matrix([[-2., 0.], - [ 0., -1.]]) + >>> Gc.A.round() # doctest: +SKIP + array([[-2., 0.], + [ 0., -1.]]) """ diff --git a/control/descfcn.py b/control/descfcn.py index 715d61191..d0f48618c 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -249,7 +249,7 @@ def describing_function_plot( >>> H_simple = ct.tf([8], [1, 2, 2, 1]) >>> F_saturation = ct.saturation_nonlinearity(1) >>> amp = np.linspace(1, 4, 10) - >>> ct.describing_function_plot(H_simple, F_saturation, amp) + >>> ct.describing_function_plot(H_simple, F_saturation, amp) # doctest: +SKIP [(3.343844998258643, 1.4142293090899216)] """ From c39bc875cb6c4bf0120013e9ce50426930d1fc23 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 25 Mar 2023 16:25:32 -0700 Subject: [PATCH 7/9] fix up errors from merge with zpk feature --- control/xferfcn.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/control/xferfcn.py b/control/xferfcn.py index 9f0a647e8..89e2546f8 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -475,7 +475,8 @@ def __str__(self, var=None): denstr = _tf_polynomial_to_string(self.den[no][ni], var=var) elif self.display_format == 'zpk': z, p, k = tf2zpk(self.num[no][ni], self.den[no][ni]) - numstr = _tf_factorized_polynomial_to_string(z, gain=k, var=var) + numstr = _tf_factorized_polynomial_to_string( + z, gain=k, var=var) denstr = _tf_factorized_polynomial_to_string(p, var=var) # Figure out the length of the separating line @@ -532,7 +533,8 @@ def _repr_latex_(self, var=None): denstr = _tf_polynomial_to_string(self.den[no][ni], var=var) elif self.display_format == 'zpk': z, p, k = tf2zpk(self.num[no][ni], self.den[no][ni]) - numstr = _tf_factorized_polynomial_to_string(z, gain=k, var=var) + numstr = _tf_factorized_polynomial_to_string( + z, gain=k, var=var) denstr = _tf_factorized_polynomial_to_string(p, var=var) numstr = _tf_string_to_latex(numstr, var=var) @@ -1707,12 +1709,13 @@ def zpk(zeros, poles, gain, *args, **kwargs): Examples -------- - >>> from control import tf - >>> G = zpk([1],[2, 3], gain=1, display_format='zpk') - >>> G - s - 1 - --------------- - (s - 2) (s - 3) + >>> G = ct.zpk([1], [2, 3], gain=1, display_format='zpk') + >>> print(G) # doctest: +SKIP + + s - 1 + --------------- + (s - 2) (s - 3) + """ num, den = zpk2tf(zeros, poles, gain) return TransferFunction(num, den, *args, **kwargs) From 983cbc072af7f864fd7917c5ed51bd6c02792e7b Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sun, 26 Mar 2023 17:46:52 -0700 Subject: [PATCH 8/9] revert use of round() in canonical examples (fix doctest failures next) --- control/canonical.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/control/canonical.py b/control/canonical.py index f5e1d25de..27c8af5a4 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -42,16 +42,16 @@ def canonical_form(xsys, form='reachable'): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.canonical_form(Gs) # default reachable - >>> Gc.B.round() + >>> Gc.B array([[1.], [0.]]) >>> Gc, T = ct.canonical_form(Gs, 'observable') - >>> Gc.C.round() + >>> Gc.C array([[1., 0.]]) >>> Gc, T = ct.canonical_form(Gs, 'modal') - >>> Gc.A.round() # doctest: +SKIP + >>> Gc.A array([[-2., 0.], [ 0., -1.]]) @@ -89,7 +89,7 @@ def reachable_form(xsys): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.reachable_form(Gs) # default reachable - >>> Gc.B.round() + >>> Gc.B array([[1.], [0.]]) @@ -152,7 +152,7 @@ def observable_form(xsys): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.observable_form(Gs) - >>> Gc.C.round() + >>> Gc.C array([[1., 0.]]) """ @@ -217,13 +217,13 @@ def similarity_transform(xsys, T, timescale=1, inverse=False): Examples -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) - >>> Gs.A.round() + >>> Gs.A array([[-3., -2.], [ 1., 0.]]) >>> T = np.array([[0, 1], [1, 0]]) >>> Gt = ct.similarity_transform(Gs, T) - >>> Gt.A.round() + >>> Gt.A array([[ 0., 1.], [-2., -3.]]) @@ -508,7 +508,7 @@ def modal_form(xsys, condmax=None, sort=False): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.modal_form(Gs) # default reachable - >>> Gc.A.round() # doctest: +SKIP + >>> Gc.A array([[-2., 0.], [ 0., -1.]]) From df38286f67b0eb7bae37209ac3cb900091f7b8e3 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sun, 26 Mar 2023 17:56:46 -0700 Subject: [PATCH 9/9] skip doctests with rounding errors in GitHub Actions --- control/canonical.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/control/canonical.py b/control/canonical.py index 27c8af5a4..c84ac1f8f 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -51,7 +51,7 @@ def canonical_form(xsys, form='reachable'): array([[1., 0.]]) >>> Gc, T = ct.canonical_form(Gs, 'modal') - >>> Gc.A + >>> Gc.A # doctest: +SKIP array([[-2., 0.], [ 0., -1.]]) @@ -508,7 +508,7 @@ def modal_form(xsys, condmax=None, sort=False): -------- >>> Gs = ct.tf2ss([1], [1, 3, 2]) >>> Gc, T = ct.modal_form(Gs) # default reachable - >>> Gc.A + >>> Gc.A # doctest: +SKIP array([[-2., 0.], [ 0., -1.]])