diff --git a/.github/workflows/python-package-conda.yml b/.github/workflows/python-package-conda.yml index 464624949..10cf2d1a9 100644 --- a/.github/workflows/python-package-conda.yml +++ b/.github/workflows/python-package-conda.yml @@ -10,7 +10,7 @@ jobs: strategy: max-parallel: 5 matrix: - python-version: [3.6, 3.9] + python-version: [3.7, 3.9] slycot: ["", "conda"] array-and-matrix: [0] include: diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8d8c76262..000000000 --- a/.travis.yml +++ /dev/null @@ -1,112 +0,0 @@ -sudo: false -language: python -dist: xenial - -services: - - xvfb - -cache: - apt: true - pip: true - directories: - - $HOME/.cache/pip - - $HOME/.local - -# Test against earliest supported (Python 3) release and latest stable release -python: - - "3.9" - - "3.6" - -env: - - SCIPY=scipy SLYCOT=conda # default, with slycot via conda - - SCIPY=scipy SLYCOT= # default, w/out slycot - -# Add optional builds that test against latest version of slycot, python -jobs: - include: - - name: "Python 3.9, slycot=source, array and matrix" - python: "3.9" - env: SCIPY=scipy SLYCOT=source PYTHON_CONTROL_ARRAY_AND_MATRIX=1 - # Because there were significant changes in SciPy between v0 and v1, we - # also test against the latest v0 (without Slycot) for old pythons. - # newer pythons should always use newer SciPy. - - name: "Python 2.7, Scipy 0.19.1" - python: "2.7" - env: SCIPY="scipy==0.19.1" SLYCOT= - - name: "Python 3.6, Scipy 0.19.1" - python: "3.6" - env: SCIPY="scipy==0.19.1" SLYCOT= - - allow_failures: - - env: SCIPY=scipy SLYCOT=source - - env: SCIPY=scipy SLYCOT=source PYTHON_CONTROL_ARRAY_AND_MATRIX=1 - - -# install required system libraries -before_install: - # Install gfortran for testing slycot; use apt-get instead of conda in - # order to include the proper CXXABI dependency (updated in GCC 4.9) - # Note: these commands should match the slycot .travis.yml configuration - - if [[ "$SLYCOT" = "source" ]]; then - sudo apt-get update -qq; - sudo apt-get install liblapack-dev libblas-dev; - sudo apt-get install gfortran; - fi - # use miniconda to install numpy/scipy, to avoid lengthy build from source - - if [[ "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then - wget https://repo.continuum.io/miniconda/Miniconda-latest-Linux-x86_64.sh -O miniconda.sh; - else - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh; - fi - - bash miniconda.sh -b -p $HOME/miniconda - - export PATH="$HOME/miniconda/bin:$PATH" - - hash -r - - conda config --set always_yes yes --set changeps1 no - - conda update -q conda - - conda config --add channels python-control - - conda info -a - - conda create -q -n test-environment python="$TRAVIS_PYTHON_VERSION" pip coverage - - source activate test-environment - # Install scikit-build for the build process if slycot is being used - - if [[ "$SLYCOT" = "source" ]]; then - conda install openblas; - conda install -c conda-forge cmake scikit-build; - fi - # Make sure to look in the right place for python libraries (for slycot) - - export LIBRARY_PATH="$HOME/miniconda/envs/test-environment/lib" - - conda install pytest - # coveralls not in conda repos => install via pip instead - - pip install coveralls - -# Install packages -install: - # Install packages needed by python-control - - conda install $SCIPY matplotlib - - # Figure out how to build slycot - # source: use "Unix Makefiles" as generator; Ninja cannot handle Fortran - # conda: use pre-compiled version of slycot on conda-forge - - if [[ "$SLYCOT" = "source" ]]; then - git clone https://github.com/python-control/Slycot.git slycot; - cd slycot; python setup.py install -G "Unix Makefiles"; cd ..; - elif [[ "$SLYCOT" = "conda" ]]; then - conda install -c conda-forge slycot; - fi - -# command to run tests -script: - - 'if [ $SLYCOT != "" ]; then python -c "import slycot"; fi' - - coverage run -m pytest control/tests - - # only run examples if Slycot is install - # set PYTHONPATH for examples - # pmw needed for examples/tfvis.py - # future is needed for Python 2, also for examples/tfvis.py - - if [[ "$SLYCOT" != "" ]]; then - export PYTHONPATH=$PWD; - conda install -c conda-forge pmw future; - (cd examples; bash run_examples.sh); - fi - -after_success: - - coveralls diff --git a/control/canonical.py b/control/canonical.py index 45846147f..7b2b58ef7 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -8,7 +8,7 @@ import numpy as np -from numpy import zeros, zeros_like, shape, poly, iscomplex, vstack, hstack, dot, \ +from numpy import zeros, zeros_like, shape, poly, iscomplex, vstack, hstack, \ transpose, empty, finfo, float64 from numpy.linalg import solve, matrix_rank, eig @@ -149,7 +149,7 @@ def observable_form(xsys): raise ValueError("Transformation matrix singular to working precision.") # Finally, compute the output matrix - zsys.B = Tzx.dot(xsys.B) + zsys.B = Tzx @ xsys.B return zsys, Tzx @@ -189,13 +189,13 @@ def rsolve(M, y): # Update the system matrices if not inverse: - zsys.A = rsolve(T, dot(T, zsys.A)) / timescale - zsys.B = dot(T, zsys.B) / timescale + zsys.A = rsolve(T, T @ zsys.A) / timescale + zsys.B = T @ zsys.B / timescale zsys.C = rsolve(T, zsys.C) else: - zsys.A = solve(T, zsys.A).dot(T) / timescale + zsys.A = solve(T, zsys.A) @ T / timescale zsys.B = solve(T, zsys.B) / timescale - zsys.C = zsys.C.dot(T) + zsys.C = zsys.C @ T return zsys @@ -405,8 +405,8 @@ def bdschur(a, condmax=None, sort=None): permidx = np.hstack([blkidxs[i] for i in sortidx]) rperm = np.eye(amodal.shape[0])[permidx] - tmodal = tmodal.dot(rperm) - amodal = rperm.dot(amodal).dot(rperm.T) + tmodal = tmodal @ rperm + amodal = rperm @ amodal @ rperm.T blksizes = blksizes[sortidx] elif sort is None: diff --git a/control/delay.py b/control/delay.py index d6350d45b..b5867ada8 100644 --- a/control/delay.py +++ b/control/delay.py @@ -42,7 +42,6 @@ # # $Id$ -from __future__ import division __all__ = ['pade'] diff --git a/control/flatsys/flatsys.py b/control/flatsys/flatsys.py index bbf1e7fc7..9ea40f2fb 100644 --- a/control/flatsys/flatsys.py +++ b/control/flatsys/flatsys.py @@ -462,8 +462,7 @@ def traj_const(null_coeffs): for type, fun, lb, ub in traj_constraints: if type == sp.optimize.LinearConstraint: # `fun` is A matrix associated with polytope... - values.append( - np.dot(fun, np.hstack([states, inputs]))) + values.append(fun @ np.hstack([states, inputs])) elif type == sp.optimize.NonlinearConstraint: values.append(fun(states, inputs)) else: diff --git a/control/flatsys/linflat.py b/control/flatsys/linflat.py index 1e96a23d2..931446ca8 100644 --- a/control/flatsys/linflat.py +++ b/control/flatsys/linflat.py @@ -110,7 +110,7 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, # Compute the flat output variable z = C x Cfz = np.zeros(np.shape(linsys.C)); Cfz[0, 0] = 1 - self.Cf = np.dot(Cfz, Tr) + self.Cf = Cfz @ Tr # Compute the flat flag from the state (and input) def forward(self, x, u): @@ -122,11 +122,11 @@ def forward(self, x, u): x = np.reshape(x, (-1, 1)) u = np.reshape(u, (1, -1)) zflag = [np.zeros(self.nstates + 1)] - zflag[0][0] = np.dot(self.Cf, x) + zflag[0][0] = self.Cf @ x H = self.Cf # initial state transformation for i in range(1, self.nstates + 1): - zflag[0][i] = np.dot(H, np.dot(self.A, x) + np.dot(self.B, u)) - H = np.dot(H, self.A) # derivative for next iteration + zflag[0][i] = H @ (self.A @ x + self.B @ u) + H = H @ self.A # derivative for next iteration return zflag # Compute state and input from flat flag @@ -137,6 +137,6 @@ def reverse(self, zflag): """ z = zflag[0][0:-1] - x = np.dot(self.Tinv, z) - u = zflag[0][-1] - np.dot(self.F, z) + x = self.Tinv @ z + u = zflag[0][-1] - self.F @ z return np.reshape(x, self.nstates), np.reshape(u, self.ninputs) diff --git a/control/frdata.py b/control/frdata.py index 5e2f3f2e1..1ac2f8b08 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -35,7 +35,6 @@ # Author: M.M. (Rene) van Paassen (using xferfcn.py as basis) # Date: 02 Oct 12 -from __future__ import division """ Frequency response data representation and functions. @@ -48,7 +47,7 @@ from warnings import warn import numpy as np from numpy import angle, array, empty, ones, \ - real, imag, absolute, eye, linalg, where, dot, sort + real, imag, absolute, eye, linalg, where, sort from scipy.interpolate import splprep, splev from .lti import LTI, _process_frequency_response from . import config @@ -302,7 +301,7 @@ def __mul__(self, other): fresp = empty((outputs, inputs, len(self.omega)), dtype=self.fresp.dtype) for i in range(len(self.omega)): - fresp[:, :, i] = dot(self.fresp[:, :, i], other.fresp[:, :, i]) + fresp[:, :, i] = self.fresp[:, :, i] @ other.fresp[:, :, i] return FRD(fresp, self.omega, smooth=(self.ifunc is not None) and (other.ifunc is not None)) @@ -330,7 +329,7 @@ def __rmul__(self, other): fresp = empty((outputs, inputs, len(self.omega)), dtype=self.fresp.dtype) for i in range(len(self.omega)): - fresp[:, :, i] = dot(other.fresp[:, :, i], self.fresp[:, :, i]) + fresp[:, :, i] = other.fresp[:, :, i] @ self.fresp[:, :, i] return FRD(fresp, self.omega, smooth=(self.ifunc is not None) and (other.ifunc is not None)) @@ -543,13 +542,9 @@ def feedback(self, other=1, sign=-1): # TODO: is there a reason to use linalg.solve instead of linalg.inv? # https://github.com/python-control/python-control/pull/314#discussion_r294075154 for k, w in enumerate(other.omega): - fresp[:, :, k] = np.dot( - self.fresp[:, :, k], - linalg.solve( - eye(self.ninputs) - + np.dot(other.fresp[:, :, k], self.fresp[:, :, k]), - eye(self.ninputs)) - ) + fresp[:, :, k] = self.fresp[:, :, k] @ linalg.solve( + eye(self.ninputs) + other.fresp[:, :, k] @ self.fresp[:, :, k], + eye(self.ninputs)) return FRD(fresp, other.omega, smooth=(self.ifunc is not None)) diff --git a/control/iosys.py b/control/iosys.py index 2c9e3aba5..1fed5c90f 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -843,14 +843,14 @@ def _update_params(self, params={}, warning=True): def _rhs(self, t, x, u): # Convert input to column vector and then change output to 1D array - xdot = np.dot(self.A, np.reshape(x, (-1, 1))) \ - + np.dot(self.B, np.reshape(u, (-1, 1))) + xdot = self.A @ np.reshape(x, (-1, 1)) \ + + self.B @ np.reshape(u, (-1, 1)) return np.array(xdot).reshape((-1,)) def _out(self, t, x, u): # Convert input to column vector and then change output to 1D array - y = np.dot(self.C, np.reshape(x, (-1, 1))) \ - + np.dot(self.D, np.reshape(u, (-1, 1))) + y = self.C @ np.reshape(x, (-1, 1)) \ + + self.D @ np.reshape(u, (-1, 1)) return np.array(y).reshape((-1,)) @@ -1197,7 +1197,7 @@ def _out(self, t, x, u): ulist, ylist = self._compute_static_io(t, x, u) # Make the full set of subsystem outputs to system output - return np.dot(self.output_map, ylist) + return self.output_map @ ylist def _compute_static_io(self, t, x, u): # Figure out the total number of inputs and outputs @@ -1239,7 +1239,7 @@ def _compute_static_io(self, t, x, u): output_index += sys.noutputs # Compute inputs based on connection map - new_ulist = np.dot(self.connect_map, ylist[:noutputs]) \ + new_ulist = self.connect_map @ ylist[:noutputs] \ + np.dot(self.input_map, u) # Check to see if any of the inputs changed diff --git a/control/margins.py b/control/margins.py index 48e0c6cc2..41739704e 100644 --- a/control/margins.py +++ b/control/margins.py @@ -9,9 +9,6 @@ margins.margin """ -# Python 3 compatibility (needs to go here) -from __future__ import print_function - """Copyright (c) 2011 by California Institute of Technology All rights reserved. diff --git a/control/mateqn.py b/control/mateqn.py index 6205b7219..a58194811 100644 --- a/control/mateqn.py +++ b/control/mateqn.py @@ -38,7 +38,7 @@ import warnings -from numpy import shape, size, asarray, copy, zeros, eye, dot, \ +from numpy import shape, size, asarray, copy, zeros, eye, \ finfo, inexact, atleast_2d from scipy.linalg import eigvals, solve_discrete_are, solve from .exception import ControlSlycot, ControlArgument @@ -496,9 +496,9 @@ def care(A, B, Q, R=None, S=None, E=None, stabilizing=True): # Calculate the gain matrix G if size(R_b) == 1: - G = dot(dot(1/(R_ba), asarray(B_ba).T), X) + G = 1/(R_ba) * asarray(B_ba).T @ X else: - G = dot(solve(R_ba, asarray(B_ba).T), X) + G = solve(R_ba, asarray(B_ba).T) @ X # Return the solution X, the closed-loop eigenvalues L and # the gain matrix G @@ -567,9 +567,9 @@ def care(A, B, Q, R=None, S=None, E=None, stabilizing=True): # Calculate the gain matrix G if size(R_b) == 1: - G = dot(1/(R_b), dot(asarray(B_b).T, dot(X, E_b)) + asarray(S_b).T) + G = 1/(R_b) * (asarray(B_b).T @ X @ E_b + asarray(S_b).T) else: - G = solve(R_b, dot(asarray(B_b).T, dot(X, E_b)) + asarray(S_b).T) + G = solve(R_b, asarray(B_b).T @ X @ E_b + asarray(S_b).T) # Return the solution X, the closed-loop eigenvalues L and # the gain matrix G @@ -629,8 +629,8 @@ def dare(A, B, Q, R, S=None, E=None, stabilizing=True): Rmat = _ssmatrix(R) Qmat = _ssmatrix(Q) X = solve_discrete_are(A, B, Qmat, Rmat) - G = solve(B.T.dot(X).dot(B) + Rmat, B.T.dot(X).dot(A)) - L = eigvals(A - B.dot(G)) + G = solve(B.T @ X @ B + Rmat, B.T @ X @ A) + L = eigvals(A - B @ G) return _ssmatrix(X), L, _ssmatrix(G) @@ -718,11 +718,11 @@ def dare_old(A, B, Q, R, S=None, E=None, stabilizing=True): # Calculate the gain matrix G if size(R_b) == 1: - G = dot(1/(dot(asarray(B_ba).T, dot(X, B_ba)) + R_ba), - dot(asarray(B_ba).T, dot(X, A_ba))) + G = (1/(asarray(B_ba).T @ X @ B_ba + R_ba) * + asarray(B_ba).T @ X @ A_ba) else: - G = solve(dot(asarray(B_ba).T, dot(X, B_ba)) + R_ba, - dot(asarray(B_ba).T, dot(X, A_ba))) + G = solve(asarray(B_ba).T @ X @ B_ba + R_ba, + asarray(B_ba).T @ X @ A_ba) # Return the solution X, the closed-loop eigenvalues L and # the gain matrix G @@ -791,11 +791,11 @@ def dare_old(A, B, Q, R, S=None, E=None, stabilizing=True): # Calculate the gain matrix G if size(R_b) == 1: - G = dot(1/(dot(asarray(B_b).T, dot(X, B_b)) + R_b), - dot(asarray(B_b).T, dot(X, A_b)) + asarray(S_b).T) + G = (1/(asarray(B_b).T @ X @ B_b + R_b) * + (asarray(B_b).T @ X @ A_b + asarray(S_b).T)) else: - G = solve(dot(asarray(B_b).T, dot(X, B_b)) + R_b, - dot(asarray(B_b).T, dot(X, A_b)) + asarray(S_b).T) + G = solve(asarray(B_b).T @ X @ B_b + R_b, + asarray(B_b).T @ X @ A_b + asarray(S_b).T) # Return the solution X, the closed-loop eigenvalues L and # the gain matrix G diff --git a/control/modelsimp.py b/control/modelsimp.py index ec015c16b..f43acc2fd 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -40,9 +40,6 @@ # # $Id$ -# Python 3 compatibility -from __future__ import print_function - # External packages and modules import numpy as np import warnings @@ -96,7 +93,7 @@ def hsvd(sys): Wc = gram(sys, 'c') Wo = gram(sys, 'o') - WoWc = np.dot(Wo, Wc) + WoWc = Wo @ Wc w, v = np.linalg.eig(WoWc) hsv = np.sqrt(w) @@ -195,10 +192,10 @@ def modred(sys, ELIM, method='matchdc'): A22I_A21 = A22I_A21_B2[:, :A21.shape[1]] A22I_B2 = A22I_A21_B2[:, A21.shape[1]:] - Ar = A11 - np.dot(A12, A22I_A21) - Br = B1 - np.dot(A12, A22I_B2) - Cr = C1 - np.dot(C2, A22I_A21) - Dr = sys.D - np.dot(C2, A22I_B2) + Ar = A11 - A12 @ A22I_A21 + Br = B1 - A12 @ A22I_B2 + Cr = C1 - C2 @ A22I_A21 + Dr = sys.D - C2 @ A22I_B2 elif method == 'truncate': # if truncate, simply discard state x2 Ar = A11 diff --git a/control/optimal.py b/control/optimal.py index 76e9a2d31..dd09532c5 100644 --- a/control/optimal.py +++ b/control/optimal.py @@ -387,33 +387,29 @@ def _constraint_function(self, coeffs): # Evaluate the constraint function along the trajectory value = [] for i, t in enumerate(self.timepts): - for type, fun, lb, ub in self.trajectory_constraints: + for ctype, fun, lb, ub in self.trajectory_constraints: if np.all(lb == ub): # Skip equality constraints continue - elif type == opt.LinearConstraint: + elif ctype == opt.LinearConstraint: # `fun` is the A matrix associated with the polytope... - value.append( - np.dot(fun, np.hstack([states[:, i], inputs[:, i]]))) - elif type == opt.NonlinearConstraint: + value.append(fun @ np.hstack([states[:, i], inputs[:, i]])) + elif ctype == opt.NonlinearConstraint: value.append(fun(states[:, i], inputs[:, i])) else: - raise TypeError("unknown constraint type %s" % - constraint[0]) + raise TypeError(f"unknown constraint type {ctype}") # Evaluate the terminal constraint functions - for type, fun, lb, ub in self.terminal_constraints: + for ctype, fun, lb, ub in self.terminal_constraints: if np.all(lb == ub): # Skip equality constraints continue - elif type == opt.LinearConstraint: - value.append( - np.dot(fun, np.hstack([states[:, i], inputs[:, i]]))) - elif type == opt.NonlinearConstraint: + elif ctype == opt.LinearConstraint: + value.append(fun @ np.hstack([states[:, i], inputs[:, i]])) + elif ctype == opt.NonlinearConstraint: value.append(fun(states[:, i], inputs[:, i])) else: - raise TypeError("unknown constraint type %s" % - constraint[0]) + raise TypeError(f"unknown constraint type {ctype}") # Update statistics self.constraint_evaluations += 1 @@ -475,33 +471,29 @@ def _eqconst_function(self, coeffs): # Evaluate the constraint function along the trajectory value = [] for i, t in enumerate(self.timepts): - for type, fun, lb, ub in self.trajectory_constraints: + for ctype, fun, lb, ub in self.trajectory_constraints: if np.any(lb != ub): # Skip inequality constraints continue - elif type == opt.LinearConstraint: + elif ctype == opt.LinearConstraint: # `fun` is the A matrix associated with the polytope... - value.append( - np.dot(fun, np.hstack([states[:, i], inputs[:, i]]))) - elif type == opt.NonlinearConstraint: + value.append(fun @ np.hstack([states[:, i], inputs[:, i]])) + elif ctype == opt.NonlinearConstraint: value.append(fun(states[:, i], inputs[:, i])) else: - raise TypeError("unknown constraint type %s" % - constraint[0]) + raise TypeError(f"unknown constraint type {ctype}") # Evaluate the terminal constraint functions - for type, fun, lb, ub in self.terminal_constraints: + for ctype, fun, lb, ub in self.terminal_constraints: if np.any(lb != ub): # Skip inequality constraints continue - elif type == opt.LinearConstraint: - value.append( - np.dot(fun, np.hstack([states[:, i], inputs[:, i]]))) - elif type == opt.NonlinearConstraint: + elif ctype == opt.LinearConstraint: + value.append(fun @ np.hstack([states[:, i], inputs[:, i]])) + elif ctype == opt.NonlinearConstraint: value.append(fun(states[:, i], inputs[:, i])) else: - raise TypeError("unknown constraint type %s" % - constraint[0]) + raise TypeError("unknown constraint type {ctype}") # Update statistics self.eqconst_evaluations += 1 diff --git a/control/phaseplot.py b/control/phaseplot.py index 83108ec01..6a4be5ca6 100644 --- a/control/phaseplot.py +++ b/control/phaseplot.py @@ -34,9 +34,6 @@ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. -# Python 3 compatibility -from __future__ import print_function - import numpy as np import matplotlib.pyplot as mpl diff --git a/control/statefbk.py b/control/statefbk.py index 58d19f0ea..e82923bb4 100644 --- a/control/statefbk.py +++ b/control/statefbk.py @@ -393,8 +393,7 @@ def lqe(*args, **keywords): N.shape[0] != ninputs or N.shape[1] != noutputs): raise ControlDimension("incorrect covariance matrix dimensions") - # P, E, LT = care(A.T, C.T, G @ Q @ G.T, R) - P, E, LT = care(A.T, C.T, np.dot(np.dot(G, Q), G.T), R) + P, E, LT = care(A.T, C.T, G @ Q @ G.T, R) return _ssmatrix(LT.T), _ssmatrix(P), E @@ -439,7 +438,7 @@ def acker(A, B, poles): n = np.size(p) pmat = p[n-1] * np.linalg.matrix_power(a, 0) for i in np.arange(1, n): - pmat = pmat + np.dot(p[n-i-1], np.linalg.matrix_power(a, i)) + pmat = pmat + p[n-i-1] * np.linalg.matrix_power(a, i) K = np.linalg.solve(ct, pmat) K = K[-1][:] # Extract the last row @@ -556,7 +555,7 @@ def lqr(*args, **keywords): # Now compute the return value # We assume that R is positive definite and, hence, invertible - K = np.linalg.solve(R, np.dot(B.T, X) + N.T) + K = np.linalg.solve(R, B.T @ X + N.T) S = X E = w[0:nstates] @@ -594,7 +593,7 @@ def ctrb(A, B): # Construct the controllability matrix ctrb = np.hstack( - [bmat] + [np.dot(np.linalg.matrix_power(amat, i), bmat) + [bmat] + [np.linalg.matrix_power(amat, i) @ bmat for i in range(1, n)]) return _ssmatrix(ctrb) @@ -628,7 +627,7 @@ def obsv(A, C): n = np.shape(amat)[0] # Construct the observability matrix - obsv = np.vstack([cmat] + [np.dot(cmat, np.linalg.matrix_power(amat, i)) + obsv = np.vstack([cmat] + [cmat @ np.linalg.matrix_power(amat, i) for i in range(1, n)]) return _ssmatrix(obsv) @@ -701,10 +700,10 @@ def gram(sys, type): raise ControlSlycot("can't find slycot module 'sb03md'") if type == 'c': tra = 'T' - C = -np.dot(sys.B, sys.B.transpose()) + C = -sys.B @ sys.B.T elif type == 'o': tra = 'N' - C = -np.dot(sys.C.transpose(), sys.C) + C = -sys.C.T @ sys.C n = sys.nstates U = np.zeros((n, n)) A = np.array(sys.A) # convert to NumPy array for slycot diff --git a/control/statesp.py b/control/statesp.py index 4dfdf5e95..0f1c560e2 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -8,10 +8,6 @@ """ -# Python 3 compatibility (needs to go here) -from __future__ import print_function -from __future__ import division # for _convert_to_statespace - """Copyright (c) 2010 by California Institute of Technology All rights reserved. @@ -53,8 +49,8 @@ import math import numpy as np -from numpy import any, array, asarray, concatenate, cos, delete, \ - dot, empty, exp, eye, isinf, ones, pad, sin, zeros, squeeze, pi +from numpy import any, asarray, concatenate, cos, delete, \ + empty, exp, eye, isinf, ones, pad, sin, zeros, squeeze from numpy.random import rand, randn from numpy.linalg import solve, eigvals, matrix_rank from numpy.linalg.linalg import LinAlgError @@ -716,11 +712,11 @@ def __mul__(self, other): (concatenate((other.A, zeros((other.A.shape[0], self.A.shape[1]))), axis=1), - concatenate((np.dot(self.B, other.C), self.A), axis=1)), + concatenate((self.B @ other.C, self.A), axis=1)), axis=0) - B = concatenate((other.B, np.dot(self.B, other.D)), axis=0) - C = concatenate((np.dot(self.D, other.C), self.C), axis=1) - D = np.dot(self.D, other.D) + B = concatenate((other.B, self.B @ other.D), axis=0) + C = concatenate((self.D @ other.C, self.C), axis=1) + D = self.D @ other.D return StateSpace(A, B, C, D, dt) @@ -745,8 +741,8 @@ def __rmul__(self, other): # try to treat this as a matrix try: X = _ssmatrix(other) - C = np.dot(X, self.C) - D = np.dot(X, self.D) + C = X @ self.C + D = X @ self.D return StateSpace(self.A, self.B, C, D, self.dt) except Exception as e: @@ -919,10 +915,8 @@ def horner(self, x, warn_infinite=True): # TODO: can this be vectorized? for idx, x_idx in enumerate(x_arr): try: - out[:, :, idx] = np.dot( - self.C, - solve(x_idx * eye(self.nstates) - self.A, self.B)) \ - + self.D + xr = solve(x_idx * eye(self.nstates) - self.A, self.B) + out[:, :, idx] = self.C @ xr + self.D except LinAlgError: # Issue a warning messsage, for consistency with xferfcn if warn_infinite: @@ -1023,7 +1017,7 @@ def feedback(self, other=1, sign=-1): C2 = other.C D2 = other.D - F = eye(self.ninputs) - sign * np.dot(D2, D1) + F = eye(self.ninputs) - sign * D2 @ D1 if matrix_rank(F) != self.ninputs: raise ValueError( "I - sign * D2 * D1 is singular to working precision.") @@ -1037,20 +1031,20 @@ def feedback(self, other=1, sign=-1): E_D2 = E_D2_C2[:, :other.ninputs] E_C2 = E_D2_C2[:, other.ninputs:] - T1 = eye(self.noutputs) + sign * np.dot(D1, E_D2) - T2 = eye(self.ninputs) + sign * np.dot(E_D2, D1) + T1 = eye(self.noutputs) + sign * D1 @ E_D2 + T2 = eye(self.ninputs) + sign * E_D2 @ D1 A = concatenate( (concatenate( - (A1 + sign * np.dot(np.dot(B1, E_D2), C1), - sign * np.dot(B1, E_C2)), axis=1), + (A1 + sign * B1 @ E_D2 @ C1, + sign * B1 @ E_C2), axis=1), concatenate( - (np.dot(B2, np.dot(T1, C1)), - A2 + sign * np.dot(np.dot(B2, D1), E_C2)), axis=1)), + (B2 @ T1 @ C1, + A2 + sign * B2 @ D1 @ E_C2), axis=1)), axis=0) - B = concatenate((np.dot(B1, T2), np.dot(np.dot(B2, D1), T2)), axis=0) - C = concatenate((np.dot(T1, C1), sign * np.dot(D1, E_C2)), axis=1) - D = np.dot(D1, T2) + B = concatenate((B1 @ T2, B2 @ D1 @ T2), axis=0) + C = concatenate((T1 @ C1, sign * D1 @ E_C2), axis=1) + D = D1 @ T2 return StateSpace(A, B, C, D, dt) @@ -1132,23 +1126,23 @@ def lft(self, other, nu=-1, ny=-1): H22 = TH[ny:, self.nstates + other.nstates + self.ninputs - nu:] Ares = np.block([ - [A + B2.dot(T21), B2.dot(T22)], - [Bbar1.dot(T11), Abar + Bbar1.dot(T12)] + [A + B2 @ T21, B2 @ T22], + [Bbar1 @ T11, Abar + Bbar1 @ T12] ]) Bres = np.block([ - [B1 + B2.dot(H21), B2.dot(H22)], - [Bbar1.dot(H11), Bbar2 + Bbar1.dot(H12)] + [B1 + B2 @ H21, B2 @ H22], + [Bbar1 @ H11, Bbar2 + Bbar1 @ H12] ]) Cres = np.block([ - [C1 + D12.dot(T21), D12.dot(T22)], - [Dbar21.dot(T11), Cbar2 + Dbar21.dot(T12)] + [C1 + D12 @ T21, D12 @ T22], + [Dbar21 @ T11, Cbar2 + Dbar21 @ T12] ]) Dres = np.block([ - [D11 + D12.dot(H21), D12.dot(H22)], - [Dbar21.dot(H11), Dbar22 + Dbar21.dot(H12)] + [D11 + D12 @ H21, D12 @ H22], + [Dbar21 @ H11, Dbar22 + Dbar21 @ H12] ]) return StateSpace(Ares, Bres, Cres, Dres, dt) @@ -1387,13 +1381,13 @@ def dynamics(self, t, x, u=None): if np.size(x) != self.nstates: raise ValueError("len(x) must be equal to number of states") if u is None: - return self.A.dot(x).reshape((-1,)) # return as row vector + return (self.A @ x).reshape((-1,)) # return as row vector else: # received t, x, and u, ignore t u = np.reshape(u, (-1, 1)) # force to column in case matrix if np.size(u) != self.ninputs: raise ValueError("len(u) must be equal to number of inputs") - return self.A.dot(x).reshape((-1,)) \ - + self.B.dot(u).reshape((-1,)) # return as row vector + return (self.A @ x).reshape((-1,)) \ + + (self.B @ u).reshape((-1,)) # return as row vector def output(self, t, x, u=None): """Compute the output of the system @@ -1430,13 +1424,13 @@ def output(self, t, x, u=None): raise ValueError("len(x) must be equal to number of states") if u is None: - return self.C.dot(x).reshape((-1,)) # return as row vector + return (self.C @ x).reshape((-1,)) # return as row vector else: # received t, x, and u, ignore t u = np.reshape(u, (-1, 1)) # force to a column in case matrix if np.size(u) != self.ninputs: raise ValueError("len(u) must be equal to number of inputs") - return self.C.dot(x).reshape((-1,)) \ - + self.D.dot(u).reshape((-1,)) # return as row vector + return (self.C @ x).reshape((-1,)) \ + + (self.D @ u).reshape((-1,)) # return as row vector def _isstatic(self): """True if and only if the system has no dynamics, that is, @@ -1629,7 +1623,7 @@ def _rss_generate(states, inputs, outputs, cdtype, strictly_proper=False): while True: T = randn(states, states) try: - A = dot(solve(T, A), T) # A = T \ A * T + A = solve(T, A) @ T # A = T \ A @ T break except LinAlgError: # In the unlikely event that T is rank-deficient, iterate again. diff --git a/control/tests/canonical_test.py b/control/tests/canonical_test.py index 0db6b924c..f822955fc 100644 --- a/control/tests/canonical_test.py +++ b/control/tests/canonical_test.py @@ -29,9 +29,9 @@ def test_reachable_form(self): [-0.74855725, -0.39136285, -0.18142339, -0.50356997], [-0.40688007, 0.81416369, 0.38002113, -0.16483334], [-0.44769516, 0.15654653, -0.50060858, 0.72419146]]) - A = np.linalg.solve(T_true, A_true).dot(T_true) + A = np.linalg.solve(T_true, A_true) @ T_true B = np.linalg.solve(T_true, B_true) - C = C_true.dot(T_true) + C = C_true @ T_true D = D_true # Create a state space system and convert it to the reachable canonical form @@ -77,9 +77,9 @@ def test_observable_form(self): [-0.74855725, -0.39136285, -0.18142339, -0.50356997], [-0.40688007, 0.81416369, 0.38002113, -0.16483334], [-0.44769516, 0.15654653, -0.50060858, 0.72419146]]) - A = np.linalg.solve(T_true, A_true).dot(T_true) + A = np.linalg.solve(T_true, A_true) @ T_true B = np.linalg.solve(T_true, B_true) - C = C_true.dot(T_true) + C = C_true @ T_true D = D_true # Create a state space system and convert it to the observable canonical form @@ -266,7 +266,7 @@ def test_bdschur_ref(eigvals, condmax, blksizes): bdiag_b = scipy.linalg.block_diag(*extract_bdiag(b, test_blksizes)) np.testing.assert_array_almost_equal(bdiag_b, b) - np.testing.assert_array_almost_equal(solve(t, a).dot(t), b) + np.testing.assert_array_almost_equal(solve(t, a) @ t, b) @slycotonly @@ -357,9 +357,9 @@ def test_modal_form(A_true, B_true, C_true, D_true): [-0.74855725, -0.39136285, -0.18142339, -0.50356997], [-0.40688007, 0.81416369, 0.38002113, -0.16483334], [-0.44769516, 0.15654653, -0.50060858, 0.72419146]]) - A = np.linalg.solve(T_true, A_true).dot(T_true) + A = np.linalg.solve(T_true, A_true) @ T_true B = np.linalg.solve(T_true, B_true) - C = C_true.dot(T_true) + C = C_true @ T_true D = D_true # Create a state space system and convert it to modal canonical form @@ -370,7 +370,7 @@ def test_modal_form(A_true, B_true, C_true, D_true): np.testing.assert_array_almost_equal(sys_check.A, a_bds) np.testing.assert_array_almost_equal(T_check, t_bds) np.testing.assert_array_almost_equal(sys_check.B, np.linalg.solve(t_bds, B)) - np.testing.assert_array_almost_equal(sys_check.C, C.dot(t_bds)) + np.testing.assert_array_almost_equal(sys_check.C, C @ t_bds) np.testing.assert_array_almost_equal(sys_check.D, D) # canonical_form(...,'modal') is the same as modal_form with default parameters @@ -384,9 +384,8 @@ def test_modal_form(A_true, B_true, C_true, D_true): # Make sure Hankel coefficients are OK for i in range(A.shape[0]): np.testing.assert_almost_equal( - np.dot(np.dot(C_true, np.linalg.matrix_power(A_true, i)), - B_true), - np.dot(np.dot(C, np.linalg.matrix_power(A, i)), B)) + C_true @ np.linalg.matrix_power(A_true, i) @ B_true, + C @ np.linalg.matrix_power(A, i) @ B) @slycotonly @@ -404,7 +403,7 @@ def test_modal_form_condmax(condmax, len_blksizes): np.testing.assert_array_almost_equal(zsys.A, amodal) np.testing.assert_array_almost_equal(t, tmodal) np.testing.assert_array_almost_equal(zsys.B, np.linalg.solve(tmodal, xsys.B)) - np.testing.assert_array_almost_equal(zsys.C, xsys.C.dot(tmodal)) + np.testing.assert_array_almost_equal(zsys.C, xsys.C @ tmodal) np.testing.assert_array_almost_equal(zsys.D, xsys.D) @@ -422,13 +421,13 @@ def test_modal_form_sort(sys_type): xsys = ss(a, [[1],[0],[0],[0],], [0,0,0,1], 0, dt) zsys, t = modal_form(xsys, sort=True) - my_amodal = np.linalg.solve(tmodal, a).dot(tmodal) + my_amodal = np.linalg.solve(tmodal, a) @ tmodal np.testing.assert_array_almost_equal(amodal, my_amodal) np.testing.assert_array_almost_equal(t, tmodal) np.testing.assert_array_almost_equal(zsys.A, amodal) np.testing.assert_array_almost_equal(zsys.B, np.linalg.solve(tmodal, xsys.B)) - np.testing.assert_array_almost_equal(zsys.C, xsys.C.dot(tmodal)) + np.testing.assert_array_almost_equal(zsys.C, xsys.C @ tmodal) np.testing.assert_array_almost_equal(zsys.D, xsys.D) diff --git a/control/tests/convert_test.py b/control/tests/convert_test.py index 7570b07b4..36eac223c 100644 --- a/control/tests/convert_test.py +++ b/control/tests/convert_test.py @@ -14,8 +14,6 @@ """ -from __future__ import print_function -from warnings import warn import numpy as np import pytest diff --git a/control/tests/delay_test.py b/control/tests/delay_test.py index 533eb4a72..25f37eeb5 100644 --- a/control/tests/delay_test.py +++ b/control/tests/delay_test.py @@ -4,8 +4,6 @@ Primitive; ideally test to numerical limits """ -from __future__ import division - import numpy as np import pytest @@ -94,4 +92,3 @@ def testT0(self): np.array(refnum), np.array(num)) np.testing.assert_array_almost_equal_nulp( np.array(refden), np.array(den)) - diff --git a/control/tests/discrete_test.py b/control/tests/discrete_test.py index 5a1a367ab..cb0ce3c76 100644 --- a/control/tests/discrete_test.py +++ b/control/tests/discrete_test.py @@ -416,7 +416,7 @@ def test_sample_ss(self, tsys): for sys in (sys1, sys2): for h in (0.1, 0.5, 1, 2): Ad = I + h * sys.A - Bd = h * sys.B + 0.5 * h**2 * np.dot(sys.A, sys.B) + Bd = h * sys.B + 0.5 * h**2 * sys.A @ sys.B sysd = sample_system(sys, h, method='zoh') np.testing.assert_array_almost_equal(sysd.A, Ad) np.testing.assert_array_almost_equal(sysd.B, Bd) diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index 4c8001797..5fd83e946 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -8,12 +8,10 @@ created for that purpose. """ -from __future__ import print_function import re import numpy as np import pytest -import scipy as sp import control as ct from control import iosys as ios @@ -58,7 +56,7 @@ def test_linear_iosys(self, tsys): for x, u in (([0, 0], 0), ([1, 0], 0), ([0, 1], 0), ([0, 0], 1)): np.testing.assert_array_almost_equal( np.reshape(iosys._rhs(0, x, u), (-1, 1)), - np.dot(linsys.A, np.reshape(x, (-1, 1))) + np.dot(linsys.B, u)) + linsys.A @ np.reshape(x, (-1, 1)) + linsys.B * u) # Make sure that simulations also line up T, U, X0 = tsys.T, tsys.U, tsys.X0 @@ -154,11 +152,13 @@ def test_nonlinear_iosys(self, tsys): # Create a nonlinear system with the same dynamics nlupd = lambda t, x, u, params: \ - np.reshape(np.dot(linsys.A, np.reshape(x, (-1, 1))) - + np.dot(linsys.B, u), (-1,)) + np.reshape(linsys.A @ np.reshape(x, (-1, 1)) + + linsys.B @ np.reshape(u, (-1, 1)), + (-1,)) nlout = lambda t, x, u, params: \ - np.reshape(np.dot(linsys.C, np.reshape(x, (-1, 1))) - + np.dot(linsys.D, u), (-1,)) + np.reshape(linsys.C @ np.reshape(x, (-1, 1)) + + linsys.D @ np.reshape(u, (-1, 1)), + (-1,)) nlsys = ios.NonlinearIOSystem(nlupd, nlout, inputs=1, outputs=1) # Make sure that simulations also line up @@ -907,12 +907,12 @@ def test_params(self, tsys): def test_named_signals(self, tsys): sys1 = ios.NonlinearIOSystem( updfcn = lambda t, x, u, params: np.array( - np.dot(tsys.mimo_linsys1.A, np.reshape(x, (-1, 1))) \ - + np.dot(tsys.mimo_linsys1.B, np.reshape(u, (-1, 1))) + tsys.mimo_linsys1.A @ np.reshape(x, (-1, 1)) \ + + tsys.mimo_linsys1.B @ np.reshape(u, (-1, 1)) ).reshape(-1,), outfcn = lambda t, x, u, params: np.array( - np.dot(tsys.mimo_linsys1.C, np.reshape(x, (-1, 1))) \ - + np.dot(tsys.mimo_linsys1.D, np.reshape(u, (-1, 1))) + tsys.mimo_linsys1.C @ np.reshape(x, (-1, 1)) \ + + tsys.mimo_linsys1.D @ np.reshape(u, (-1, 1)) ).reshape(-1,), inputs = ['u[0]', 'u[1]'], outputs = ['y[0]', 'y[1]'], @@ -1140,8 +1140,8 @@ def test_named_signals_linearize_inconsistent(self, tsys): def updfcn(t, x, u, params): """2 inputs, 2 states""" return np.array( - np.dot(tsys.mimo_linsys1.A, np.reshape(x, (-1, 1))) - + np.dot(tsys.mimo_linsys1.B, np.reshape(u, (-1, 1))) + tsys.mimo_linsys1.A @ np.reshape(x, (-1, 1)) + + tsys.mimo_linsys1.B @ np.reshape(u, (-1, 1)) ).reshape(-1,) def outfcn(t, x, u, params): @@ -1270,7 +1270,7 @@ def test_lineariosys_statespace(self, tsys): (2, 2, 'rss', ct.LinearIOSystem.__rsub__, 2, 2), (2, 2, 2, ct.LinearIOSystem.__rsub__, 2, 2), (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__rsub__, 2, 2), - + ]) def test_operand_conversion(self, Pout, Pin, C, op, PCout, PCin): P = ct.LinearIOSystem( @@ -1415,11 +1415,13 @@ def test_linear_interconnection(): outputs = ('y[0]', 'y[1]'), name = 'sys2') nl_sys2 = ios.NonlinearIOSystem( lambda t, x, u, params: np.array( - np.dot(ss_sys2.A, np.reshape(x, (-1, 1))) \ - + np.dot(ss_sys2.B, np.reshape(u, (-1, 1)))).reshape((-1,)), + ss_sys2.A @ np.reshape(x, (-1, 1)) \ + + ss_sys2.B @ np.reshape(u, (-1, 1)) + ).reshape((-1,)), lambda t, x, u, params: np.array( - np.dot(ss_sys2.C, np.reshape(x, (-1, 1))) \ - + np.dot(ss_sys2.D, np.reshape(u, (-1, 1)))).reshape((-1,)), + ss_sys2.C @ np.reshape(x, (-1, 1)) \ + + ss_sys2.D @ np.reshape(u, (-1, 1)) + ).reshape((-1,)), states = 2, inputs = ('u[0]', 'u[1]'), outputs = ('y[0]', 'y[1]'), diff --git a/control/tests/margin_test.py b/control/tests/margin_test.py index a1246103f..07e21114f 100644 --- a/control/tests/margin_test.py +++ b/control/tests/margin_test.py @@ -6,7 +6,6 @@ BG, 30 Jun 2020 -- convert to pytest, gh-425 BG, 16 Nov 2020 -- pick from gh-438 and add discrete test """ -from __future__ import print_function import numpy as np import pytest diff --git a/control/tests/mateqn_test.py b/control/tests/mateqn_test.py index facb1ce08..62fca6bd3 100644 --- a/control/tests/mateqn_test.py +++ b/control/tests/mateqn_test.py @@ -52,13 +52,13 @@ def test_lyap(self): Q = array([[1,0],[0,1]]) X = lyap(A,Q) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X) + X.dot(A.T) + Q, zeros((2,2))) + assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2))) A = array([[1, 2],[-3, -4]]) Q = array([[3, 1],[1, 1]]) X = lyap(A,Q) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X) + X.dot(A.T) + Q, zeros((2,2))) + assert_array_almost_equal(A @ X + X @ A.T + Q, zeros((2,2))) def test_lyap_sylvester(self): A = 5 @@ -66,14 +66,14 @@ def test_lyap_sylvester(self): C = array([2, 1]) X = lyap(A,B,C) # print("The solution obtained is ", X) - assert_array_almost_equal(A * X + X.dot(B) + C, zeros((1,2))) + assert_array_almost_equal(A * X + X @ B + C, zeros((1,2))) A = array([[2,1],[1,2]]) B = array([[1,2],[0.5,0.1]]) C = array([[1,0],[0,1]]) X = lyap(A,B,C) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X) + X.dot(B) + C, zeros((2,2))) + assert_array_almost_equal(A @ X + X @ B + C, zeros((2,2))) def test_lyap_g(self): A = array([[-1, 2],[-3, -4]]) @@ -81,7 +81,7 @@ def test_lyap_g(self): E = array([[1,2],[2,1]]) X = lyap(A,Q,None,E) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X).dot(E.T) + E.dot(X).dot(A.T) + Q, + assert_array_almost_equal(A @ X @ E.T + E @ X @ A.T + Q, zeros((2,2))) def test_dlyap(self): @@ -89,13 +89,13 @@ def test_dlyap(self): Q = array([[1,0],[0,1]]) X = dlyap(A,Q) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X).dot(A.T) - X + Q, zeros((2,2))) + assert_array_almost_equal(A @ X @ A.T - X + Q, zeros((2,2))) A = array([[-0.6, 0],[-0.1, -0.4]]) Q = array([[3, 1],[1, 1]]) X = dlyap(A,Q) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X).dot(A.T) - X + Q, zeros((2,2))) + assert_array_almost_equal(A @ X @ A.T - X + Q, zeros((2,2))) def test_dlyap_g(self): A = array([[-0.6, 0],[-0.1, -0.4]]) @@ -103,7 +103,7 @@ def test_dlyap_g(self): E = array([[1, 1],[2, 1]]) X = dlyap(A,Q,None,E) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X).dot(A.T) - E.dot(X).dot(E.T) + Q, + assert_array_almost_equal(A @ X @ A.T - E @ X @ E.T + Q, zeros((2,2))) def test_dlyap_sylvester(self): @@ -112,14 +112,14 @@ def test_dlyap_sylvester(self): C = array([2, 1]) X = dlyap(A,B,C) # print("The solution obtained is ", X) - assert_array_almost_equal(A * X.dot(B.T) - X + C, zeros((1,2))) + assert_array_almost_equal(A * X @ B.T - X + C, zeros((1,2))) A = array([[2,1],[1,2]]) B = array([[1,2],[0.5,0.1]]) C = array([[1,0],[0,1]]) X = dlyap(A,B,C) # print("The solution obtained is ", X) - assert_array_almost_equal(A.dot(X).dot(B.T) - X + C, zeros((2,2))) + assert_array_almost_equal(A @ X @ B.T - X + C, zeros((2,2))) def test_care(self): A = array([[-2, -1],[-1, -1]]) @@ -128,10 +128,10 @@ def test_care(self): X,L,G = care(A,B,Q) # print("The solution obtained is", X) - M = A.T.dot(X) + X.dot(A) - X.dot(B).dot(B.T).dot(X) + Q + M = A.T @ X + X @ A - X @ B @ B.T @ X + Q assert_array_almost_equal(M, zeros((2,2))) - assert_array_almost_equal(B.T.dot(X), G) + assert_array_almost_equal(B.T @ X, G) def test_care_g(self): A = array([[-2, -1],[-1, -1]]) @@ -143,11 +143,11 @@ def test_care_g(self): X,L,G = care(A,B,Q,R,S,E) # print("The solution obtained is", X) - Gref = solve(R, B.T.dot(X).dot(E) + S.T) + Gref = solve(R, B.T @ X @ E + S.T) assert_array_almost_equal(Gref, G) assert_array_almost_equal( - A.T.dot(X).dot(E) + E.T.dot(X).dot(A) - - (E.T.dot(X).dot(B) + S).dot(Gref) + Q, + A.T @ X @ E + E.T @ X @ A + - (E.T @ X @ B + S) @ Gref + Q, zeros((2,2))) def test_care_g2(self): @@ -160,10 +160,10 @@ def test_care_g2(self): X,L,G = care(A,B,Q,R,S,E) # print("The solution obtained is", X) - Gref = 1/R * (B.T.dot(X).dot(E) + S.T) + Gref = 1/R * (B.T @ X @ E + S.T) assert_array_almost_equal( - A.T.dot(X).dot(E) + E.T.dot(X).dot(A) - - (E.T.dot(X).dot(B) + S).dot(Gref) + Q , + A.T @ X @ E + E.T @ X @ A + - (E.T @ X @ B + S) @ Gref + Q , zeros((2,2))) assert_array_almost_equal(Gref , G) @@ -175,12 +175,12 @@ def test_dare(self): X,L,G = dare(A,B,Q,R) # print("The solution obtained is", X) - Gref = solve(B.T.dot(X).dot(B) + R, B.T.dot(X).dot(A)) + Gref = solve(B.T @ X @ B + R, B.T @ X @ A) assert_array_almost_equal(Gref, G) assert_array_almost_equal( - X, A.T.dot(X).dot(A) - A.T.dot(X).dot(B).dot(Gref) + Q) + X, A.T @ X @ A - A.T @ X @ B @ Gref + Q) # check for stable closed loop - lam = eigvals(A - B.dot(G)) + lam = eigvals(A - B @ G) assert_array_less(abs(lam), 1.0) A = array([[1, 0],[-1, 1]]) @@ -190,15 +190,15 @@ def test_dare(self): X,L,G = dare(A,B,Q,R) # print("The solution obtained is", X) - AtXA = A.T.dot(X).dot(A) - AtXB = A.T.dot(X).dot(B) - BtXA = B.T.dot(X).dot(A) - BtXB = B.T.dot(X).dot(B) + AtXA = A.T @ X @ A + AtXB = A.T @ X @ B + BtXA = B.T @ X @ A + BtXB = B.T @ X @ B assert_array_almost_equal( - X, AtXA - AtXB.dot(solve(BtXB + R, BtXA)) + Q) + X, AtXA - AtXB @ solve(BtXB + R, BtXA) + Q) assert_array_almost_equal(BtXA / (BtXB + R), G) # check for stable closed loop - lam = eigvals(A - B.dot(G)) + lam = eigvals(A - B @ G) assert_array_less(abs(lam), 1.0) def test_dare_g(self): @@ -211,13 +211,13 @@ def test_dare_g(self): X,L,G = dare(A,B,Q,R,S,E) # print("The solution obtained is", X) - Gref = solve(B.T.dot(X).dot(B) + R, B.T.dot(X).dot(A) + S.T) + Gref = solve(B.T @ X @ B + R, B.T @ X @ A + S.T) assert_array_almost_equal(Gref, G) assert_array_almost_equal( - E.T.dot(X).dot(E), - A.T.dot(X).dot(A) - (A.T.dot(X).dot(B) + S).dot(Gref) + Q) + E.T @ X @ E, + A.T @ X @ A - (A.T @ X @ B + S) @ Gref + Q) # check for stable closed loop - lam = eigvals(A - B.dot(G), E) + lam = eigvals(A - B @ G, E) assert_array_less(abs(lam), 1.0) def test_dare_g2(self): @@ -230,16 +230,16 @@ def test_dare_g2(self): X, L, G = dare(A, B, Q, R, S, E) # print("The solution obtained is", X) - AtXA = A.T.dot(X).dot(A) - AtXB = A.T.dot(X).dot(B) - BtXA = B.T.dot(X).dot(A) - BtXB = B.T.dot(X).dot(B) - EtXE = E.T.dot(X).dot(E) + AtXA = A.T @ X @ A + AtXB = A.T @ X @ B + BtXA = B.T @ X @ A + BtXB = B.T @ X @ B + EtXE = E.T @ X @ E assert_array_almost_equal( - EtXE, AtXA - (AtXB + S).dot(solve(BtXB + R, BtXA + S.T)) + Q) + EtXE, AtXA - (AtXB + S) @ solve(BtXB + R, BtXA + S.T) + Q) assert_array_almost_equal((BtXA + S.T) / (BtXB + R), G) # check for stable closed loop - lam = eigvals(A - B.dot(G), E) + lam = eigvals(A - B @ G, E) assert_array_less(abs(lam), 1.0) def test_raise(self): diff --git a/control/tests/modelsimp_test.py b/control/tests/modelsimp_test.py index fd474f9d0..70e94dd91 100644 --- a/control/tests/modelsimp_test.py +++ b/control/tests/modelsimp_test.py @@ -95,9 +95,9 @@ def testMarkovResults(self, k, m, n): Hd = c2d(Hc, Ts, 'zoh') # Compute the Markov parameters from state space - Mtrue = np.hstack([Hd.D] + [np.dot( - Hd.C, np.dot(np.linalg.matrix_power(Hd.A, i), - Hd.B)) for i in range(m-1)]) + Mtrue = np.hstack([Hd.D] + [ + Hd.C @ np.linalg.matrix_power(Hd.A, i) @ Hd.B + for i in range(m-1)]) # Generate input/output data T = np.array(range(n)) * Ts diff --git a/control/tests/statefbk_test.py b/control/tests/statefbk_test.py index 0f73d787c..03e1ff344 100644 --- a/control/tests/statefbk_test.py +++ b/control/tests/statefbk_test.py @@ -203,7 +203,7 @@ def testPlace(self, matarrayin): P = matarrayin([-0.5 + 1j, -0.5 - 1j, -5.0566, -8.6659]) K = place(A, B, P) assert ismatarrayout(K) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) self.checkPlaced(P, P_placed) # Test that the dimension checks work. @@ -228,7 +228,7 @@ def testPlace_varga_continuous(self, matarrayin): P = [-2., -2.] K = place_varga(A, B, P) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) self.checkPlaced(P, P_placed) # Test that the dimension checks work. @@ -241,7 +241,7 @@ def testPlace_varga_continuous(self, matarrayin): B = matarrayin([[0], [1]]) P = matarrayin([-20 + 10*1j, -20 - 10*1j]) K = place_varga(A, B, P) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) self.checkPlaced(P, P_placed) @@ -261,7 +261,7 @@ def testPlace_varga_continuous_partial_eigs(self, matarrayin): alpha = -1.5 K = place_varga(A, B, P, alpha=alpha) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) # No guarantee of the ordering, so sort them self.checkPlaced(P_expected, P_placed) @@ -275,7 +275,7 @@ def testPlace_varga_discrete(self, matarrayin): P = matarrayin([0.5, 0.5]) K = place_varga(A, B, P, dtime=True) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) # No guarantee of the ordering, so sort them self.checkPlaced(P, P_placed) @@ -293,12 +293,12 @@ def testPlace_varga_discrete_partial_eigs(self, matarrayin): P_expected = np.array([0.5, 0.6]) alpha = 0.51 K = place_varga(A, B, P, dtime=True, alpha=alpha) - P_placed = np.linalg.eigvals(A - B.dot(K)) + P_placed = np.linalg.eigvals(A - B @ K) self.checkPlaced(P_expected, P_placed) def check_LQR(self, K, S, poles, Q, R): - S_expected = asmatarrayout(np.sqrt(Q.dot(R))) + S_expected = asmatarrayout(np.sqrt(Q @ R)) K_expected = asmatarrayout(S_expected / R) poles_expected = -np.squeeze(np.asarray(K_expected)) np.testing.assert_array_almost_equal(S, S_expected) @@ -373,7 +373,7 @@ def test_lqr_call_format(self): K, S, E = lqr(sys.A, sys.B, sys.C, R, Q) def check_LQE(self, L, P, poles, G, QN, RN): - P_expected = asmatarrayout(np.sqrt(G.dot(QN.dot(G).dot(RN)))) + P_expected = asmatarrayout(np.sqrt(G @ QN @ G @ RN)) L_expected = asmatarrayout(P_expected / RN) poles_expected = -np.squeeze(np.asarray(L_expected)) np.testing.assert_array_almost_equal(P, P_expected) diff --git a/control/tests/statesp_test.py b/control/tests/statesp_test.py index dac8043df..78eacf857 100644 --- a/control/tests/statesp_test.py +++ b/control/tests/statesp_test.py @@ -592,12 +592,12 @@ def test_matrix_static_gain(self): g3 = StateSpace([], [], [], d2.T) h1 = g1 * g2 - np.testing.assert_allclose(np.dot(d1, d2), h1.D) + np.testing.assert_allclose(d1 @ d2, h1.D) h2 = g1 + g3 np.testing.assert_allclose(d1 + d2.T, h2.D) h3 = g1.feedback(g2) np.testing.assert_array_almost_equal( - solve(np.eye(2) + np.dot(d1, d2), d1), h3.D) + solve(np.eye(2) + d1 @ d2, d1), h3.D) h4 = g1.append(g2) np.testing.assert_allclose(block_diag(d1, d2), h4.D) @@ -767,18 +767,19 @@ def test_horner(self, sys322): [[1, 1], [[1], [1]], np.atleast_2d([1,1]).T]) @pytest.mark.parametrize('u', [0, 1, np.atleast_1d(2)]) def test_dynamics_and_output_siso(self, x, u, sys121): + uref = np.atleast_1d(u) assert_array_almost_equal( sys121.dynamics(0, x, u), - sys121.A.dot(x).reshape((-1,)) + sys121.B.dot(u).reshape((-1,))) + (sys121.A @ x).reshape((-1,)) + (sys121.B @ uref).reshape((-1,))) assert_array_almost_equal( sys121.output(0, x, u), - sys121.C.dot(x).reshape((-1,)) + sys121.D.dot(u).reshape((-1,))) + (sys121.C @ x).reshape((-1,)) + (sys121.D @ uref).reshape((-1,))) assert_array_almost_equal( sys121.dynamics(0, x), - sys121.A.dot(x).reshape((-1,))) + (sys121.A @ x).reshape((-1,))) assert_array_almost_equal( sys121.output(0, x), - sys121.C.dot(x).reshape((-1,))) + (sys121.C @ x).reshape((-1,))) # too few and too many states and inputs @pytest.mark.parametrize('x', [0, 1, [], [1, 2, 3], np.atleast_1d(2)]) @@ -801,16 +802,16 @@ def test_error_u_dynamics_output_siso(self, u, sys121): def test_dynamics_and_output_mimo(self, x, u, sys222): assert_array_almost_equal( sys222.dynamics(0, x, u), - sys222.A.dot(x).reshape((-1,)) + sys222.B.dot(u).reshape((-1,))) + (sys222.A @ x).reshape((-1,)) + (sys222.B @ u).reshape((-1,))) assert_array_almost_equal( sys222.output(0, x, u), - sys222.C.dot(x).reshape((-1,)) + sys222.D.dot(u).reshape((-1,))) + (sys222.C @ x).reshape((-1,)) + (sys222.D @ u).reshape((-1,))) assert_array_almost_equal( sys222.dynamics(0, x), - sys222.A.dot(x).reshape((-1,))) + (sys222.A @ x).reshape((-1,))) assert_array_almost_equal( sys222.output(0, x), - sys222.C.dot(x).reshape((-1,))) + (sys222.C @ x).reshape((-1,))) # too few and too many states and inputs @pytest.mark.parametrize('x', [0, 1, [1, 1, 1]]) @@ -1095,4 +1096,3 @@ def test_latex_repr_testsize(editsdefaults): gstatic = ss([], [], [], 1) assert gstatic._repr_latex_() is None - diff --git a/control/tests/timeresp_test.py b/control/tests/timeresp_test.py index bb53c310a..61c0cae38 100644 --- a/control/tests/timeresp_test.py +++ b/control/tests/timeresp_test.py @@ -743,7 +743,7 @@ def test_lsim_double_integrator(self, u, x0, xtrue): _t, yout, xout = forced_response(sys, t, u, x0, return_x=True) np.testing.assert_array_almost_equal(xout, xtrue, decimal=6) - ytrue = np.squeeze(np.asarray(C.dot(xtrue))) + ytrue = np.squeeze(np.asarray(C @ xtrue)) np.testing.assert_array_almost_equal(yout, ytrue, decimal=6) @@ -1010,9 +1010,7 @@ def test_squeeze(self, fcn, nstate, nout, ninp, squeeze, shape1, shape2): # Generate the time and input vectors tvec = np.linspace(0, 1, 8) - uvec = np.dot( - np.ones((sys.ninputs, 1)), - np.reshape(np.sin(tvec), (1, 8))) + uvec = np.ones((sys.ninputs, 1)) @ np.reshape(np.sin(tvec), (1, 8)) # # Pass squeeze argument and make sure the shape is correct @@ -1144,9 +1142,7 @@ def test_squeeze_0_8_4(self, nstate, nout, ninp, squeeze, shape): # Generate system, time, and input vectors sys = ct.rss(nstate, nout, ninp, strictly_proper=True) tvec = np.linspace(0, 1, 8) - uvec = np.dot( - np.ones((sys.ninputs, 1)), - np.reshape(np.sin(tvec), (1, 8))) + uvec =np.ones((sys.ninputs, 1)) @ np.reshape(np.sin(tvec), (1, 8)) _, yvec = ct.initial_response(sys, tvec, 1, squeeze=squeeze) assert yvec.shape == shape diff --git a/control/timeresp.py b/control/timeresp.py index 75e1dcf0b..3f3eacc27 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -997,7 +997,6 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, # Separate out the discrete and continuous time cases if isctime(sys, strict=True): # Solve the differential equation, copied from scipy.signal.ltisys. - dot = np.dot # Faster and shorter code # Faster algorithm if U is zero # (if not None, it was converted to array above) @@ -1005,8 +1004,8 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, # Solve using matrix exponential expAdt = sp.linalg.expm(A * dt) for i in range(1, n_steps): - xout[:, i] = dot(expAdt, xout[:, i-1]) - yout = dot(C, xout) + xout[:, i] = expAdt @ xout[:, i-1] + yout = C @ xout # General algorithm that interpolates U in between output points else: @@ -1034,9 +1033,9 @@ def forced_response(sys, T=None, U=0., X0=0., transpose=False, Bd0 = expM[:n_states, n_states:n_states + n_inputs] - Bd1 for i in range(1, n_steps): - xout[:, i] = (dot(Ad, xout[:, i-1]) + dot(Bd0, U[:, i-1]) + - dot(Bd1, U[:, i])) - yout = dot(C, xout) + dot(D, U) + xout[:, i] = (Ad @ xout[:, i-1] + + Bd0 @ U[:, i-1] + Bd1 @ U[:, i]) + yout = C @ xout + D @ U tout = T else: @@ -1973,7 +1972,7 @@ def _ideal_tfinal_and_dt(sys, is_step=True): # Incorporate balancing to outer factors l[perm, :] *= np.reciprocal(sca)[:, None] r[perm, :] *= sca[:, None] - w, v = sys_ss.C.dot(r), l.T.conj().dot(sys_ss.B) + w, v = sys_ss.C @ r, l.T.conj() @ sys_ss.B origin = False # Computing the "size" of the response of each simple mode diff --git a/control/xferfcn.py b/control/xferfcn.py index 356bf0e18..856b421ef 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -7,10 +7,6 @@ for the python-control library. """ -# Python 3 compatibility (needs to go here) -from __future__ import print_function -from __future__ import division - """Copyright (c) 2010 by California Institute of Technology All rights reserved. diff --git a/doc/pvtol-nested.rst b/doc/pvtol-nested.rst index f9a4538a8..08858be7b 100644 --- a/doc/pvtol-nested.rst +++ b/doc/pvtol-nested.rst @@ -17,8 +17,5 @@ Code Notes ..... -1. Importing `print_function` from `__future__` in line 11 is only -required if using Python 2.7. - -2. The environment variable `PYCONTROL_TEST_EXAMPLES` is used for +1. The environment variable `PYCONTROL_TEST_EXAMPLES` is used for testing to turn off plotting of the outputs. diff --git a/examples/check-controllability-and-observability.py b/examples/check-controllability-and-observability.py index 399693781..67ecdf26c 100644 --- a/examples/check-controllability-and-observability.py +++ b/examples/check-controllability-and-observability.py @@ -4,8 +4,6 @@ RMM, 6 Sep 2010 """ -from __future__ import print_function - import numpy as np # Load the scipy functions from control.matlab import * # Load the controls systems library diff --git a/examples/pvtol-nested.py b/examples/pvtol-nested.py index 7b48d2bb5..24cd7d1c5 100644 --- a/examples/pvtol-nested.py +++ b/examples/pvtol-nested.py @@ -8,8 +8,6 @@ # package. # -from __future__ import print_function - import os import matplotlib.pyplot as plt # MATLAB plotting functions from control.matlab import * # MATLAB-like functions @@ -30,7 +28,7 @@ # Inner loop control design # # This is the controller for the pitch dynamics. Goal is to have -# fast response for the pitch dynamics so that we can use this as a +# fast response for the pitch dynamics so that we can use this as a # control for the lateral dynamics # @@ -40,7 +38,7 @@ Li = Pi*Ci # Bode plot for the open loop process -plt.figure(1) +plt.figure(1) bode(Pi) # Bode plot for the loop transfer function, with margins @@ -137,7 +135,7 @@ # Add a box in the region we are going to expand plt.plot([-2, -2, 1, 1, -2], [-4, 4, 4, -4, -4], 'r-') -# Expanded region +# Expanded region plt.figure(8) plt.clf() nyquist(L) diff --git a/examples/slycot-import-test.py b/examples/slycot-import-test.py index c2c78fa89..2df9b5b23 100644 --- a/examples/slycot-import-test.py +++ b/examples/slycot-import-test.py @@ -39,6 +39,6 @@ dico = 'D' # Discrete system _, _, _, _, _, K, _ = sb01bd(n, m, npp, alpha, A, B, w, dico, tol=0.0, ldwork=None) print("[slycot] K = ", K) - print("[slycot] eigs = ", np.linalg.eig(A + np.dot(B, K))[0]) + print("[slycot] eigs = ", np.linalg.eig(A + B @ K)[0]) else: print("Slycot is not installed.") diff --git a/examples/tfvis.py b/examples/tfvis.py index f05a45780..30a084ffb 100644 --- a/examples/tfvis.py +++ b/examples/tfvis.py @@ -1,8 +1,5 @@ #!/usr/bin/python # needs pmw (in pypi, conda-forge) -# For Python 2, needs future (in conda pypi and "default") - -from __future__ import print_function """ Simple GUI application for visualizing how the poles/zeros of the transfer function effects the bode, nyquist and step response of a SISO system """ @@ -20,7 +17,7 @@ notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -3. Neither the name of the project author nor the names of its +3. Neither the name of the project author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. @@ -146,7 +143,7 @@ def set_poles(self, poles): self.denominator = make_poly(poles) self.denominator_widget.setentry( ' '.join([format(i,'.3g') for i in self.denominator])) - + def set_zeros(self, zeros): """ Set the zeros to the new positions""" self.numerator = make_poly(zeros) @@ -208,7 +205,7 @@ def __init__(self, parent): self.canvas_step.get_tk_widget().grid(row=1, column=0, padx=0, pady=0) - self.canvas_nyquist = FigureCanvasTkAgg(self.f_nyquist, + self.canvas_nyquist = FigureCanvasTkAgg(self.f_nyquist, master=self.figure) self.canvas_nyquist.draw() self.canvas_nyquist.get_tk_widget().grid(row=1, column=1, @@ -221,7 +218,7 @@ def __init__(self, parent): self.canvas_pzmap.mpl_connect('motion_notify_event', self.mouse_move) - self.apply() + self.apply() def button_press(self, event): """ Handle button presses, detect if we are going to move @@ -276,12 +273,12 @@ def button_release(self, event): self.zeros = tfcn.zero() self.poles = tfcn.pole() self.sys = tfcn - self.redraw() + self.redraw() def mouse_move(self, event): """ Handle mouse movement, redraw pzmap while drag/dropping """ if (self.move_zero != None and - event.xdata != None and + event.xdata != None and event.ydata != None): if (self.index1 == self.index2): @@ -320,7 +317,7 @@ def apply(self): self.zeros = tfcn.zero() self.poles = tfcn.pole() self.sys = tfcn - self.redraw() + self.redraw() def draw_pz(self, tfcn): """Draw pzmap""" @@ -338,7 +335,7 @@ def draw_pz(self, tfcn): def redraw(self): """ Redraw all diagrams """ self.draw_pz(self.sys) - + self.f_bode.clf() plt.figure(self.f_bode.number) control.matlab.bode(self.sys, logspace(-2, 2, 1000)) @@ -376,7 +373,7 @@ def handler(): # Launch a GUI for the Analysis module root = tkinter.Tk() root.protocol("WM_DELETE_WINDOW", handler) - Pmw.initialise(root) + Pmw.initialise(root) root.title('Analysis of Linear Systems') Analysis(root) root.mainloop() diff --git a/setup.py b/setup.py index b8f6f5034..f5e766ebb 100644 --- a/setup.py +++ b/setup.py @@ -16,10 +16,7 @@ Intended Audience :: Science/Research Intended Audience :: Developers License :: OSI Approved :: BSD License -Programming Language :: Python :: 2 -Programming Language :: Python :: 2.7 Programming Language :: Python :: 3 -Programming Language :: Python :: 3.6 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9