diff --git a/.github/workflows/ruff-check.yml b/.github/workflows/ruff-check.yml new file mode 100644 index 000000000..e056204bf --- /dev/null +++ b/.github/workflows/ruff-check.yml @@ -0,0 +1,29 @@ +# run ruff check on library source +# TODO: extend to tests, examples, benchmarks + +name: ruff-check + +on: [push, pull_request] + +jobs: + ruff-check-linux: + # ruff *shouldn't* be sensitive to platform + runs-on: ubuntu-latest + + steps: + - name: Checkout python-control + uses: actions/checkout@v3 + + - name: Setup environment + uses: actions/setup-python@v4 + with: + python-version: 3.13 # todo: latest? + + - name: Install ruff + run: | + python -m pip install --upgrade pip + python -m pip install ruff + + - name: Run ruff check + run: | + ruff check diff --git a/control/__init__.py b/control/__init__.py index 1aaaa42e8..3f78452fa 100644 --- a/control/__init__.py +++ b/control/__init__.py @@ -70,6 +70,11 @@ # Import functions from within the control system library # Note: the functions we use are specified as __all__ variables in the modules +# don't warn about `import *` +# ruff: noqa: F403 +# don't warn about unknown names; they come via `import *` +# ruff: noqa: F405 + # Input/output system modules from .iosys import * from .nlsys import * @@ -106,8 +111,8 @@ from .sysnorm import * # Allow access to phase_plane functions as ct.phaseplot.fcn or ct.pp.fcn -from . import phaseplot -from . import phaseplot as pp +from . import phaseplot as phaseplot +pp = phaseplot # Exceptions from .exception import * diff --git a/control/canonical.py b/control/canonical.py index 7be7f88ad..67d3127a9 100644 --- a/control/canonical.py +++ b/control/canonical.py @@ -8,9 +8,8 @@ import numpy as np -from numpy import zeros, zeros_like, shape, poly, iscomplex, vstack, hstack, \ - transpose, empty, finfo, float64 -from numpy.linalg import solve, matrix_rank, eig +from numpy import zeros_like, poly, transpose +from numpy.linalg import solve, matrix_rank from scipy.linalg import schur diff --git a/control/dtime.py b/control/dtime.py index 39b207e02..6d1545fc0 100644 --- a/control/dtime.py +++ b/control/dtime.py @@ -48,7 +48,6 @@ """ from .iosys import isctime -from .statesp import StateSpace __all__ = ['sample_system', 'c2d'] diff --git a/control/exception.py b/control/exception.py index add5d01ae..feaf1f0ae 100644 --- a/control/exception.py +++ b/control/exception.py @@ -71,7 +71,7 @@ def slycot_check(): global slycot_installed if slycot_installed is None: try: - import slycot + import slycot # noqa: F401 slycot_installed = True except: slycot_installed = False @@ -85,7 +85,7 @@ def pandas_check(): global pandas_installed if pandas_installed is None: try: - import pandas + import pandas # noqa: F401 pandas_installed = True except: pandas_installed = False @@ -98,7 +98,7 @@ def cvxopt_check(): global cvxopt_installed if cvxopt_installed is None: try: - import cvxopt + import cvxopt # noqa: F401 cvxopt_installed = True except: cvxopt_installed = False diff --git a/control/flatsys/__init__.py b/control/flatsys/__init__.py index c6934d825..16374b589 100644 --- a/control/flatsys/__init__.py +++ b/control/flatsys/__init__.py @@ -61,15 +61,17 @@ """ # Basis function families -from .basis import BasisFamily -from .poly import PolyFamily -from .bezier import BezierFamily -from .bspline import BSplineFamily +from .basis import BasisFamily as BasisFamily +from .poly import PolyFamily as PolyFamily +from .bezier import BezierFamily as BezierFamily +from .bspline import BSplineFamily as BSplineFamily # Classes -from .systraj import SystemTrajectory -from .flatsys import FlatSystem, flatsys -from .linflat import LinearFlatSystem +from .systraj import SystemTrajectory as SystemTrajectory +from .flatsys import FlatSystem as FlatSystem +from .flatsys import flatsys as flatsys +from .linflat import LinearFlatSystem as LinearFlatSystem # Package functions -from .flatsys import point_to_point, solve_flat_ocp +from .flatsys import point_to_point as point_to_point +from .flatsys import solve_flat_ocp as solve_flat_ocp diff --git a/control/flatsys/bspline.py b/control/flatsys/bspline.py index c771beb59..d42cb4074 100644 --- a/control/flatsys/bspline.py +++ b/control/flatsys/bspline.py @@ -8,7 +8,7 @@ import numpy as np from .basis import BasisFamily -from scipy.interpolate import BSpline, splev +from scipy.interpolate import BSpline class BSplineFamily(BasisFamily): """B-spline basis functions. diff --git a/control/flatsys/flatsys.py b/control/flatsys/flatsys.py index 5818d118b..d00c2b311 100644 --- a/control/flatsys/flatsys.py +++ b/control/flatsys/flatsys.py @@ -8,6 +8,7 @@ import warnings from .poly import PolyFamily from .systraj import SystemTrajectory +from ..exception import ControlArgument from ..nlsys import NonlinearIOSystem from ..timeresp import _check_convert_array @@ -245,7 +246,6 @@ def flatsys(*args, updfcn=None, outfcn=None, **kwargs): """ from .linflat import LinearFlatSystem from ..statesp import StateSpace - from ..iosys import _process_iosys_keywords if len(args) == 1 and isinstance(args[0], StateSpace): # We were passed a linear system, so call linflat diff --git a/control/frdata.py b/control/frdata.py index 1200bfffa..195d73bfb 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -15,8 +15,8 @@ from warnings import warn import numpy as np -from numpy import absolute, angle, array, empty, eye, imag, linalg, ones, \ - real, sort, where +from numpy import absolute, array, empty, eye, imag, linalg, ones, \ + real, sort from scipy.interpolate import splev, splprep from . import config diff --git a/control/freqplot.py b/control/freqplot.py index 456431f38..db06da0c1 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -11,7 +11,6 @@ import itertools import math import warnings -from os.path import commonprefix import matplotlib as mpl import matplotlib.pyplot as plt @@ -760,13 +759,11 @@ def _make_line_label(response, output_index, input_index): if plot_magnitude: ax_mag.axhline(y=0 if dB else 1, color='k', linestyle=':', zorder=-20) - mag_ylim = ax_mag.get_ylim() if plot_phase: ax_phase.axhline(y=phase_limit if deg else math.radians(phase_limit), color='k', linestyle=':', zorder=-20) - phase_ylim = ax_phase.get_ylim() # Annotate the phase margin (if it exists) if plot_phase and pm != float('inf') and Wcp != float('nan'): @@ -2222,7 +2219,7 @@ def gangof4_plot( See :class:`ControlPlot` for more detailed information. """ - if len(args) == 1 and isinstance(arg, FrequencyResponseData): + if len(args) == 1 and isinstance(args[0], FrequencyResponseData): if any([kw is not None for kw in [omega, omega_limits, omega_num, Hz]]): raise ValueError( diff --git a/control/iosys.py b/control/iosys.py index 373bc2111..293657319 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -8,7 +8,6 @@ import re from copy import deepcopy -from warnings import warn import numpy as np diff --git a/control/lti.py b/control/lti.py index cb785ca5f..b5f634169 100644 --- a/control/lti.py +++ b/control/lti.py @@ -7,7 +7,8 @@ import numpy as np import math -from numpy import real, angle, abs +# todo: override built-in abs +from numpy import real, abs from warnings import warn from . import config from .iosys import InputOutputSystem diff --git a/control/margins.py b/control/margins.py index 019c866be..80edc12c9 100644 --- a/control/margins.py +++ b/control/margins.py @@ -52,7 +52,6 @@ import numpy as np import scipy as sp from . import xferfcn -from .lti import evalfr from .iosys import issiso from . import frdata from . import freqplot diff --git a/control/mateqn.py b/control/mateqn.py index 52b69e2b0..9100f567c 100644 --- a/control/mateqn.py +++ b/control/mateqn.py @@ -37,7 +37,7 @@ import warnings import numpy as np -from numpy import copy, eye, dot, finfo, inexact, atleast_2d +from numpy import eye, finfo, inexact import scipy as sp from scipy.linalg import eigvals, solve diff --git a/control/matlab/__init__.py b/control/matlab/__init__.py index 98e6babc7..dca522ec5 100644 --- a/control/matlab/__init__.py +++ b/control/matlab/__init__.py @@ -50,6 +50,9 @@ """ +# Silence unused imports (F401), * imports (F403), unknown symbols (F405) +# ruff: noqa: F401, F403, F405 + # Import MATLAB-like functions that are defined in other packages from scipy.signal import zpk2ss, ss2zpk, tf2zpk, zpk2tf from numpy import linspace, logspace diff --git a/control/modelsimp.py b/control/modelsimp.py index fe519b82d..968003051 100644 --- a/control/modelsimp.py +++ b/control/modelsimp.py @@ -340,7 +340,7 @@ def balanced_reduction(sys, orders, method='truncate', alpha=None): # check if orders is a list or a scalar try: - order = iter(orders) + iter(orders) except TypeError: # if orders is a scalar orders = [orders] diff --git a/control/nichols.py b/control/nichols.py index ac42c9c37..933bf7507 100644 --- a/control/nichols.py +++ b/control/nichols.py @@ -21,7 +21,6 @@ from .ctrlplot import ControlPlot, _get_line_labels, _process_ax_keyword, \ _process_legend_keywords, _process_line_labels, _update_plot_title from .ctrlutil import unwrap -from .freqplot import _default_frequency_range, _freqplot_defaults from .lti import frequency_response from .statesp import StateSpace from .xferfcn import TransferFunction @@ -137,7 +136,7 @@ def nichols_plot( # Decide on the system name and label sysname = response.sysname if response.sysname is not None \ - else f"Unknown-{idx_sys}" + else f"Unknown-sys_{idx}" label_ = sysname if label is None else label[idx] # Generate the plot diff --git a/control/nlsys.py b/control/nlsys.py index 7683d3382..39d61c4ee 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -18,7 +18,6 @@ """ -import copy from warnings import warn import numpy as np @@ -26,7 +25,7 @@ from . import config from .iosys import InputOutputSystem, _parse_spec, _process_iosys_keywords, \ - _process_signal_list, common_timebase, iosys_repr, isctime, isdtime + common_timebase, iosys_repr, isctime, isdtime from .timeresp import _check_convert_array, _process_time_response, \ TimeResponseData, TimeResponseList @@ -211,7 +210,7 @@ def __mul__(self, other): "can't multiply systems with incompatible inputs and outputs") # Make sure timebase are compatible - dt = common_timebase(other.dt, self.dt) + common_timebase(other.dt, self.dt) # Create a new system to handle the composition inplist = [(0, i) for i in range(other.ninputs)] @@ -243,7 +242,7 @@ def __rmul__(self, other): "inputs and outputs") # Make sure timebase are compatible - dt = common_timebase(self.dt, other.dt) + common_timebase(self.dt, other.dt) # Create a new system to handle the composition inplist = [(0, i) for i in range(self.ninputs)] @@ -811,7 +810,7 @@ def cxn_string(signal, gain, first): return (" - " if not first else "-") + \ f"{abs(gain)} * {signal}" - out += f"\nConnections:\n" + out += "\nConnections:\n" for i in range(len(input_list)): first = True cxn = f"{input_list[i]} <- " @@ -831,7 +830,7 @@ def cxn_string(signal, gain, first): cxn, width=78, initial_indent=" * ", subsequent_indent=" ")) + "\n" - out += f"\nOutputs:\n" + out += "\nOutputs:\n" for i in range(len(self.output_labels)): first = True cxn = f"{self.output_labels[i]} <- " @@ -2474,8 +2473,7 @@ def interconnect( `outputs`, for more natural naming of SISO systems. """ - from .statesp import LinearICSystem, StateSpace, _convert_to_statespace - from .xferfcn import TransferFunction + from .statesp import LinearICSystem, StateSpace dt = kwargs.pop('dt', None) # bypass normal 'dt' processing name, inputs, outputs, states, _ = _process_iosys_keywords(kwargs) @@ -2537,7 +2535,7 @@ def interconnect( # This includes signal lists such as ('sysname', ['sig1', 'sig2', ...]) # as well as slice-based specifications such as 'sysname.signal[i:j]'. # - dprint(f"Pre-processing connections:") + dprint("Pre-processing connections:") new_connections = [] for connection in connections: dprint(f" parsing {connection=}") @@ -2576,7 +2574,7 @@ def interconnect( # dprint(f"Pre-processing input connections: {inplist}") if not isinstance(inplist, list): - dprint(f" converting inplist to list") + dprint(" converting inplist to list") inplist = [inplist] new_inplist, new_inputs = [], [] if inplist_none else inputs @@ -2639,7 +2637,7 @@ def interconnect( else: if isinstance(connection, list): # Passed a list => create input map - dprint(f" detected input list") + dprint(" detected input list") signal_list = [] for spec in connection: isys, indices, gain = _parse_spec(syslist, spec, 'input') @@ -2665,7 +2663,7 @@ def interconnect( # dprint(f"Pre-processing output connections: {outlist}") if not isinstance(outlist, list): - dprint(f" converting outlist to list") + dprint(" converting outlist to list") outlist = [outlist] new_outlist, new_outputs = [], [] if outlist_none else outputs for iout, connection in enumerate(outlist): @@ -2742,7 +2740,7 @@ def _find_output_or_input_signal(spec): if isinstance(connection, list): # Passed a list => create input map - dprint(f" detected output list") + dprint(" detected output list") signal_list = [] for spec in connection: signal_list += _find_output_or_input_signal(spec) diff --git a/control/optimal.py b/control/optimal.py index 77cfd370e..10384fce0 100644 --- a/control/optimal.py +++ b/control/optimal.py @@ -23,8 +23,7 @@ import time from . import config -from .exception import ControlNotImplemented -from .iosys import _process_indices, _process_labels, \ +from .iosys import _process_labels, \ _process_control_disturbance_indices @@ -163,7 +162,7 @@ def __init__( if trajectory_method is None: trajectory_method = 'collocation' if sys.isctime() else 'shooting' elif trajectory_method not in _optimal_trajectory_methods: - raise NotImplementedError(f"Unkown method {method}") + raise NotImplementedError(f"Unknown method {trajectory_method}") self.shooting = trajectory_method in {'shooting'} self.collocation = trajectory_method in {'collocation'} @@ -1106,7 +1105,7 @@ def solve_ocp( # Process (legacy) method keyword if kwargs.get('method'): method = kwargs.pop('method') - if method not in optimal_methods: + if method not in _optimal_trajectory_methods: if kwargs.get('minimize_method'): raise ValueError("'minimize_method' specified more than once") warnings.warn( diff --git a/control/phaseplot.py b/control/phaseplot.py index e6123bc0e..f5621253e 100644 --- a/control/phaseplot.py +++ b/control/phaseplot.py @@ -38,7 +38,7 @@ from . import config from .ctrlplot import ControlPlot, _add_arrows_to_line2D, _get_color, \ _process_ax_keyword, _update_plot_title -from .exception import ControlNotImplemented +from .exception import ControlArgument from .nlsys import NonlinearIOSystem, find_operating_point, \ input_output_response @@ -162,7 +162,6 @@ def phase_plane_plot( # Create copy of kwargs for later checking to find unused arguments initial_kwargs = dict(kwargs) - passed_kwargs = False # Utility function to create keyword arguments def _create_kwargs(global_kwargs, local_kwargs, **other_kwargs): @@ -631,7 +630,7 @@ def separatrices( case (stable_color, unstable_color) | [stable_color, unstable_color]: pass case single_color: - stable_color = unstable_color = color + stable_color = unstable_color = single_color # Make sure all keyword arguments were processed if _check_kwargs and kwargs: @@ -1091,9 +1090,9 @@ def phase_plot(odefun, X=None, Y=None, scale=1, X0=None, T=None, # Get parameters to pass to function if parms: warnings.warn( - f"keyword 'parms' is deprecated; use 'params'", FutureWarning) + "keyword 'parms' is deprecated; use 'params'", FutureWarning) if params: - raise ControlArgument(f"duplicate keywords 'parms' and 'params'") + raise ControlArgument("duplicate keywords 'parms' and 'params'") else: params = parms @@ -1144,10 +1143,11 @@ def phase_plot(odefun, X=None, Y=None, scale=1, X0=None, T=None, if scale is None: plt.quiver(x1, x2, dx[:,:,1], dx[:,:,2], angles='xy') elif (scale != 0): + plt.quiver(x1, x2, dx[:,:,0]*np.abs(scale), + dx[:,:,1]*np.abs(scale), angles='xy') #! TODO: optimize parameters for arrows #! TODO: figure out arguments to make arrows show up correctly - xy = plt.quiver(x1, x2, dx[:,:,0]*np.abs(scale), - dx[:,:,1]*np.abs(scale), angles='xy') + # xy = plt.quiver(...) # set(xy, 'LineWidth', PP_arrow_linewidth, 'Color', 'b') #! TODO: Tweak the shape of the plot @@ -1257,15 +1257,17 @@ def phase_plot(odefun, X=None, Y=None, scale=1, X0=None, T=None, #! TODO: figure out arguments to make arrows show up correctly plt.quiver(x1, x2, dx[:,:,0], dx[:,:,1], angles='xy') elif scale != 0 and Narrows > 0: + plt.quiver(x1, x2, dx[:,:,0]*abs(scale), dx[:,:,1]*abs(scale), + angles='xy') #! TODO: figure out arguments to make arrows show up correctly - xy = plt.quiver(x1, x2, dx[:,:,0]*abs(scale), dx[:,:,1]*abs(scale), - angles='xy') + # xy = plt.quiver(...) # set(xy, 'LineWidth', PP_arrow_linewidth) # set(xy, 'AutoScale', 'off') # set(xy, 'AutoScaleFactor', 0) if scale < 0: - bp = plt.plot(x1, x2, 'b.'); # add dots at base + plt.plot(x1, x2, 'b.'); # add dots at base + # bp = plt.plot(...) # set(bp, 'MarkerSize', PP_arrow_markersize) diff --git a/control/pzmap.py b/control/pzmap.py index f1d0ecae9..b62e0dcf0 100644 --- a/control/pzmap.py +++ b/control/pzmap.py @@ -11,21 +11,18 @@ import itertools import warnings -from math import pi import matplotlib.pyplot as plt import numpy as np -from numpy import cos, exp, imag, linspace, real, sin, sqrt +from numpy import imag, real from . import config from .config import _process_legacy_keyword from .ctrlplot import ControlPlot, _get_color, _get_color_offset, \ _get_line_labels, _process_ax_keyword, _process_legend_keywords, \ _process_line_labels, _update_plot_title -from .freqplot import _freqplot_defaults from .grid import nogrid, sgrid, zgrid from .iosys import isctime, isdtime -from .lti import LTI from .statesp import StateSpace from .xferfcn import TransferFunction diff --git a/control/rlocus.py b/control/rlocus.py index e5f61f914..065fbc10d 100644 --- a/control/rlocus.py +++ b/control/rlocus.py @@ -16,17 +16,14 @@ # import warnings -from functools import partial -import matplotlib.pyplot as plt import numpy as np import scipy.signal # signal processing toolbox -from numpy import array, imag, poly1d, real, vstack, zeros_like +from numpy import poly1d, vstack, zeros_like from . import config from .ctrlplot import ControlPlot from .exception import ControlMIMONotImplemented -from .iosys import isdtime from .lti import LTI from .xferfcn import _convert_to_transfer_function @@ -455,20 +452,18 @@ def _RLSortRoots(roots): one branch to another.""" sorted = zeros_like(roots) - for n, row in enumerate(roots): - if n == 0: - sorted[n, :] = row - else: - # sort the current row by finding the element with the - # smallest absolute distance to each root in the - # previous row - available = list(range(len(prevrow))) - for elem in row: - evect = elem - prevrow[available] - ind1 = abs(evect).argmin() - ind = available.pop(ind1) - sorted[n, ind] = elem - prevrow = sorted[n, :] + sorted[0] = roots[0] + for n, row in enumerate(roots[1:], start=1): + # sort the current row by finding the element with the + # smallest absolute distance to each root in the + # previous row + prevrow = sorted[n-1] + available = list(range(len(prevrow))) + for elem in row: + evect = elem - prevrow[available] + ind1 = abs(evect).argmin() + ind = available.pop(ind1) + sorted[n, ind] = elem return sorted diff --git a/control/robust.py b/control/robust.py index f9283af48..3e44c1bb3 100644 --- a/control/robust.py +++ b/control/robust.py @@ -42,9 +42,8 @@ # External packages and modules import numpy as np import warnings -from .exception import * +from .exception import ControlSlycot from .statesp import StateSpace -from .statefbk import * def h2syn(P, nmeas, ncon): @@ -98,12 +97,6 @@ def h2syn(P, nmeas, ncon): # Check for ss system object, need a utility for this? # TODO: Check for continous or discrete, only continuous supported right now - # if isCont(): - # dico = 'C' - # elif isDisc(): - # dico = 'D' - # else: - dico = 'C' try: from slycot import sb10hd @@ -186,12 +179,6 @@ def hinfsyn(P, nmeas, ncon): # Check for ss system object, need a utility for this? # TODO: Check for continous or discrete, only continuous supported right now - # if isCont(): - # dico = 'C' - # elif isDisc(): - # dico = 'D' - # else: - dico = 'C' try: from slycot import sb10ad diff --git a/control/statefbk.py b/control/statefbk.py index 87b12da82..5d635196a 100644 --- a/control/statefbk.py +++ b/control/statefbk.py @@ -46,8 +46,7 @@ from . import statesp from .config import _process_legacy_keyword -from .exception import ControlArgument, ControlDimension, \ - ControlSlycot +from .exception import ControlArgument, ControlSlycot from .iosys import _process_indices, _process_labels, isctime, isdtime from .lti import LTI from .mateqn import care, dare diff --git a/control/statesp.py b/control/statesp.py index c0e8e7862..a5b5f9fd2 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -16,14 +16,15 @@ import math import sys from collections.abc import Iterable -from copy import deepcopy from warnings import warn import numpy as np import scipy as sp import scipy.linalg -from numpy import any, array, asarray, concatenate, cos, delete, empty, \ - exp, eye, isinf, ones, pad, sin, squeeze, zeros +# array needed in eval() call +from numpy import array # noqa: F401 +from numpy import any, asarray, concatenate, cos, delete, empty, \ + exp, eye, isinf, pad, sin, squeeze, zeros from numpy.linalg import LinAlgError, eigvals, matrix_rank, solve from numpy.random import rand, randn from scipy.signal import StateSpace as signalStateSpace @@ -34,9 +35,9 @@ from .exception import ControlDimension, ControlMIMONotImplemented, \ ControlSlycot, slycot_check from .frdata import FrequencyResponseData -from .iosys import InputOutputSystem, NamedSignal, _process_dt_keyword, \ +from .iosys import InputOutputSystem, NamedSignal, \ _process_iosys_keywords, _process_signal_list, _process_subsys_index, \ - common_timebase, iosys_repr, isdtime, issiso + common_timebase, issiso from .lti import LTI, _process_frequency_response from .mateqn import _check_shape from .nlsys import InterconnectedSystem, NonlinearIOSystem diff --git a/control/stochsys.py b/control/stochsys.py index 5aaa29415..1dca7c448 100644 --- a/control/stochsys.py +++ b/control/stochsys.py @@ -16,16 +16,18 @@ __maintainer__ = "Richard Murray" __email__ = "murray@cds.caltech.edu" +import warnings + import numpy as np import scipy as sp from math import sqrt -from .statesp import StateSpace from .lti import LTI -from .iosys import InputOutputSystem, isctime, isdtime, _process_indices, \ - _process_labels, _process_control_disturbance_indices +from .iosys import (isctime, isdtime, _process_labels, + _process_control_disturbance_indices) from .nlsys import NonlinearIOSystem from .mateqn import care, dare, _check_shape +from .statesp import StateSpace from .exception import ControlArgument, ControlNotImplemented from .config import _process_legacy_keyword @@ -164,12 +166,14 @@ def lqe(*args, **kwargs): # Get the cross-covariance matrix, if given if (len(args) > index + 2): - NN = np.array(args[index+2], ndmin=2, dtype=float) + # NN = np.array(args[index+2], ndmin=2, dtype=float) raise ControlNotImplemented("cross-covariance not implemented") else: + pass # For future use (not currently used below) - NN = np.zeros((QN.shape[0], RN.shape[1])) + # NN = np.zeros((QN.shape[0], RN.shape[1])) + # Check dimensions of G (needed before calling care()) _check_shape(QN, G.shape[1], G.shape[1], name="QN") @@ -288,7 +292,7 @@ def dlqe(*args, **kwargs): # NN = np.zeros(QN.size(0),RN.size(1)) # NG = G @ NN if len(args) > index + 2: - NN = np.array(args[index+2], ndmin=2, dtype=float) + # NN = np.array(args[index+2], ndmin=2, dtype=float) raise ControlNotImplemented("cross-covariance not yet implememented") # Check dimensions of G (needed before calling care()) @@ -459,7 +463,7 @@ def create_estimator_iosystem( # Set the input and direct matrices B = sys.B[:, ctrl_idx] if not np.allclose(sys.D, 0): - raise NotImplemented("nonzero 'D' matrix not yet implemented") + raise NotImplementedError("nonzero 'D' matrix not yet implemented") # Set the output matrices if C is not None: diff --git a/control/sysnorm.py b/control/sysnorm.py index 6737dc5c0..680bb4b15 100644 --- a/control/sysnorm.py +++ b/control/sysnorm.py @@ -12,7 +12,6 @@ """ import numpy as np -import scipy as sp import numpy.linalg as la import warnings diff --git a/control/tests/docstrings_test.py b/control/tests/docstrings_test.py index 4dbf52ee8..44963cd7c 100644 --- a/control/tests/docstrings_test.py +++ b/control/tests/docstrings_test.py @@ -30,9 +30,9 @@ function_docstring_hash = { control.append: '1bddbac0fe932755c85e9fb0bfb97d88', control.describing_function_plot: '95f894706b1d3eeb3b854934596af09f', - control.dlqe: 'f2e52e35692cf5ffe911684d41d284c9', + control.dlqe: 'e1e9479310e4e5a6f50f5459fb3d2dfb', control.dlqr: '56d7f3a452bc8d7a7256a52d9d1dcb37', - control.lqe: 'f0ba6cde8191cbc10f052096ffc3fcbb', + control.lqe: '0447235d11b685b9dfaf485dd01fdb9a', control.lqr: 'a3e0a85f781fc9c0f69a4b7da4f0bd22', control.margin: 'f02b3034f5f1d44ce26f916cc3e51600', control.parallel: 'bfc470aef75dbb923f9c6fb8bf3c9b43', @@ -40,7 +40,7 @@ control.ss2tf: 'e779b8d70205bc1218cc2a4556a66e4b', control.tf2ss: '086a3692659b7321c2af126f79f4bc11', control.markov: 'a4199c54cb50f07c0163d3790739eafe', - control.gangof4: '0e52eb6cf7ce024f9a41f3ae3ebf04f7', + control.gangof4: 'f9673ae4c6d26c202060ed4b9ef54800', } # List of keywords that we can skip testing (special cases) diff --git a/control/tests/flatsys_test.py b/control/tests/flatsys_test.py index 5b66edaf5..df30d7090 100644 --- a/control/tests/flatsys_test.py +++ b/control/tests/flatsys_test.py @@ -618,6 +618,11 @@ def test_point_to_point_errors(self): flat_sys, timepts, x0, u0, xf, uf, constraints=[(None, 0, 0, 0)], basis=fs.PolyFamily(8)) + # too few timepoints + with pytest.raises(ct.ControlArgument, match="at least three time points"): + fs.point_to_point( + flat_sys, timepts[:2], x0, u0, xf, uf, basis=fs.PolyFamily(10), cost=cost_fcn) + # Unsolvable optimization constraint = [opt.input_range_constraint(flat_sys, -0.01, 0.01)] with pytest.warns(UserWarning, match="unable to solve"): diff --git a/control/timeplot.py b/control/timeplot.py index 1c7efe894..6fde8c67a 100644 --- a/control/timeplot.py +++ b/control/timeplot.py @@ -11,7 +11,6 @@ import itertools from warnings import warn -import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np @@ -178,8 +177,6 @@ def time_response_plot( """ from .ctrlplot import _process_ax_keyword, _process_line_labels - from .iosys import InputOutputSystem - from .timeresp import TimeResponseData # # Process keywords and set defaults diff --git a/control/timeresp.py b/control/timeresp.py index 67641d239..e62812634 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -1838,8 +1838,6 @@ def initial_response( >>> T, yout = ct.initial_response(G) """ - from .lti import LTI - # Create the time and input vectors if T is None or np.asarray(T).size == 1: T = _default_time_vector(sysdata, N=T_num, tfinal=T, is_step=False) diff --git a/control/xferfcn.py b/control/xferfcn.py index 4a8fd4a1c..1f8686c29 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -22,9 +22,11 @@ import numpy as np import scipy as sp -from numpy import angle, array, delete, empty, exp, finfo, float64, ndarray, \ - nonzero, ones, pi, poly, polyadd, polymul, polyval, real, roots, sqrt, \ - squeeze, where, zeros +# float64 needed in eval() call +from numpy import float64 # noqa: F401 +from numpy import array, delete, empty, exp, finfo, ndarray, \ + nonzero, ones, poly, polyadd, polymul, polyval, real, roots, sqrt, \ + where, zeros from scipy.signal import TransferFunction as signalTransferFunction from scipy.signal import cont2discrete, tf2zpk, zpk2tf @@ -33,7 +35,7 @@ from .exception import ControlMIMONotImplemented from .frdata import FrequencyResponseData from .iosys import InputOutputSystem, NamedSignal, _process_iosys_keywords, \ - _process_subsys_index, common_timebase, isdtime + _process_subsys_index, common_timebase from .lti import LTI, _process_frequency_response __all__ = ['TransferFunction', 'tf', 'zpk', 'ss2tf', 'tfdata'] @@ -219,8 +221,8 @@ def __init__(self, *args, **kwargs): # Determine if the transfer function is static (needed for dt) static = True for arr in [num, den]: - for poly in np.nditer(arr, flags=['refs_ok']): - if poly.item().size > 1: + for poly_ in np.nditer(arr, flags=['refs_ok']): + if poly_.item().size > 1: static = False break if not static: @@ -1281,8 +1283,8 @@ def _isstatic(self): that is, if the system has no dynamics. """ for list_of_polys in self.num, self.den: for row in list_of_polys: - for poly in row: - if len(poly) > 1: + for poly_ in row: + if len(poly_) > 1: return False return True @@ -1501,7 +1503,6 @@ def _convert_to_transfer_function( """ from .statesp import StateSpace - kwargs = {} if isinstance(sys, TransferFunction): return sys diff --git a/pyproject.toml b/pyproject.toml index 649dcad5d..46d41fbf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,14 @@ filterwarnings = [ "error:.*matrix subclass:PendingDeprecationWarning", ] -[tool.ruff.lint] -select = ['D', 'E', 'W', 'DOC'] +[tool.ruff] + +# TODO: expand to cover all code +include = ['control/**.py'] +exclude = ['control/tests/*.py'] -[tool.ruff.lint.pydocstyle] -convention = 'numpy' +[tool.ruff.lint] +select = [ + 'F', # pyflakes + # todo: add more as needed +] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy