From fb6545939e78ae7d9ae9ad358619a93c5ad27870 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 11 Jun 2021 21:36:15 -0700 Subject: [PATCH 1/7] DOC: fix forced_response return arguments in documentation --- doc/conventions.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conventions.rst b/doc/conventions.rst index adcdbe96f..63f3fac2c 100644 --- a/doc/conventions.rst +++ b/doc/conventions.rst @@ -168,7 +168,7 @@ As all simulation functions return *arrays*, plotting is convenient:: The output of a MIMO system can be plotted like this:: - t, y, x = forced_response(sys, u, t) + t, y = forced_response(sys, u, t) plot(t, y[0], label='y_0') plot(t, y[1], label='y_1') From d89dfd863dafc901e60254da75ef353f789905d5 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Fri, 11 Jun 2021 21:53:40 -0700 Subject: [PATCH 2/7] DOC: update iosys docstrings - params not optional for updfcn, outfcn --- control/iosys.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/control/iosys.py b/control/iosys.py index 526da4cdb..e31bc95e5 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -790,17 +790,17 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None, updfcn : callable Function returning the state update function - `updfcn(t, x, u[, param]) -> array` + `updfcn(t, x, u, params) -> array` where `x` is a 1-D array with shape (nstates,), `u` is a 1-D array with shape (ninputs,), `t` is a float representing the currrent - time, and `param` is an optional dict containing the values of - parameters used by the function. + time, and `params` is a dict containing the values of parameters + used by the function. outfcn : callable Function returning the output at the given state - `outfcn(t, x, u[, param]) -> array` + `outfcn(t, x, u, params) -> array` where the arguments are the same as for `upfcn`. From 695cdfc45cee5ca8570ad9bf3410a836e353539a Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 12 Jun 2021 13:04:07 -0700 Subject: [PATCH 3/7] DOC: fix LTI system attribute docstrings, add __call__, PEP8 cleanup --- control/frdata.py | 42 +++++++++++--- control/iosys.py | 59 ++++++++++++++++--- control/lti.py | 34 ++++++++--- control/statesp.py | 112 +++++++++++++++++++++++++++--------- control/tests/iosys_test.py | 4 +- control/xferfcn.py | 104 ++++++++++++++++++++++++++++----- doc/classes.rst | 1 + doc/conf.py | 11 ++-- doc/control.rst | 5 +- doc/flatsys.rst | 1 + doc/matlab.rst | 1 + doc/optimal.rst | 1 + 12 files changed, 303 insertions(+), 72 deletions(-) diff --git a/control/frdata.py b/control/frdata.py index c620984f6..625e84b75 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -77,13 +77,35 @@ class FrequencyResponseData(LTI): above, i.e. the rows represent the outputs and the columns represent the inputs. + A frequency response data object is callable and returns the value of the + transfer function evaluated at a point in the complex plane (must be on + the imaginary access). See :meth:`~control.FrequencyResponseData.__call__` + for a more detailed description. + """ # Allow NDarray * StateSpace to give StateSpace._rmul_() priority # https://docs.scipy.org/doc/numpy/reference/arrays.classes.html __array_priority__ = 11 # override ndarray and matrix types - epsw = 1e-8 + # + # Class attributes + # + # These attributes are defined as class attributes so that they are + # documented properly. They are "overwritten" in __init__. + # + + #: Number of system inputs. + #: + #: :meta hide-value: + ninputs = 1 + + #: Number of system outputs. + #: + #: :meta hide-value: + noutputs = 1 + + _epsw = 1e-8 #: Bound for exact frequency match def __init__(self, *args, **kwargs): """Construct an FRD object. @@ -141,7 +163,8 @@ def __init__(self, *args, **kwargs): self.omega = args[0].omega self.fresp = args[0].fresp else: - raise ValueError("Needs 1 or 2 arguments; received %i." % len(args)) + raise ValueError( + "Needs 1 or 2 arguments; received %i." % len(args)) # create interpolation functions if smooth: @@ -378,7 +401,7 @@ def eval(self, omega, squeeze=None): then single-dimensional axes are removed. """ - omega_array = np.array(omega, ndmin=1) # array-like version of omega + omega_array = np.array(omega, ndmin=1) # array-like version of omega # Make sure that we are operating on a simple list if len(omega_array.shape) > 1: @@ -389,7 +412,7 @@ def eval(self, omega, squeeze=None): raise ValueError("FRD.eval can only accept real-valued omega") if self.ifunc is None: - elements = np.isin(self.omega, omega) # binary array + elements = np.isin(self.omega, omega) # binary array if sum(elements) < len(omega_array): raise ValueError( "not all frequencies omega are in frequency list of FRD " @@ -398,7 +421,7 @@ def eval(self, omega, squeeze=None): out = self.fresp[:, :, elements] else: out = empty((self.noutputs, self.ninputs, len(omega_array)), - dtype=complex) + dtype=complex) for i in range(self.noutputs): for j in range(self.ninputs): for k, w in enumerate(omega_array): @@ -417,6 +440,9 @@ def __call__(self, s, squeeze=None): To evaluate at a frequency omega in radians per second, enter ``s = omega * 1j`` or use ``sys.eval(omega)`` + For a frequency response data object, the argument must be an + imaginary number (since only the frequency response is defined). + Parameters ---------- s : complex scalar or 1D array_like @@ -444,6 +470,7 @@ def __call__(self, s, squeeze=None): If `s` is not purely imaginary, because :class:`FrequencyDomainData` systems are only defined at imaginary frequency values. + """ # Make sure that we are operating on a simple list if len(np.atleast_1d(s).shape) > 1: @@ -451,7 +478,7 @@ def __call__(self, s, squeeze=None): if any(abs(np.atleast_1d(s).real) > 0): raise ValueError("__call__: FRD systems can only accept " - "purely imaginary frequencies") + "purely imaginary frequencies") # need to preserve array or scalar status if hasattr(s, '__len__'): @@ -510,6 +537,7 @@ def feedback(self, other=1, sign=-1): # fixes this problem. # + FRD = FrequencyResponseData @@ -534,7 +562,7 @@ def _convert_to_FRD(sys, omega, inputs=1, outputs=1): if isinstance(sys, FRD): omega.sort() if len(omega) == len(sys.omega) and \ - (abs(omega - sys.omega) < FRD.epsw).all(): + (abs(omega - sys.omega) < FRD._epsw).all(): # frequencies match, and system was already frd; simply use return sys diff --git a/control/iosys.py b/control/iosys.py index e31bc95e5..7dfd8756c 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -95,11 +95,10 @@ class for a set of subclasses that are used to implement specific Dictionary of signal names for the inputs, outputs and states and the index of the corresponding array dt : None, True or float - System timebase. 0 (default) indicates continuous - time, True indicates discrete time with unspecified sampling - time, positive number is discrete time with specified - sampling time, None indicates unspecified timebase (either - continuous or discrete time). + System timebase. 0 (default) indicates continuous time, True indicates + discrete time with unspecified sampling time, positive number is + discrete time with specified sampling time, None indicates unspecified + timebase (either continuous or discrete time). params : dict, optional Parameter values for the systems. Passed to the evaluation functions for the system as default values, overriding internal defaults. @@ -120,12 +119,12 @@ class for a set of subclasses that are used to implement specific """ - idCounter = 0 + _idCounter = 0 def name_or_default(self, name=None): if name is None: - name = "sys[{}]".format(InputOutputSystem.idCounter) - InputOutputSystem.idCounter += 1 + name = "sys[{}]".format(InputOutputSystem._idCounter) + InputOutputSystem._idCounter += 1 return name def __init__(self, inputs=None, outputs=None, states=None, params={}, @@ -187,6 +186,28 @@ def __init__(self, inputs=None, outputs=None, states=None, params={}, self.set_outputs(outputs) self.set_states(states) + # + # Class attributes + # + # These attributes are defined as class attributes so that they are + # documented properly. They are "overwritten" in __init__. + # + + #: Number of system inputs. + #: + #: :meta hide-value: + ninputs = 0 + + #: Number of system outputs. + #: + #: :meta hide-value: + noutputs = 0 + + #: Number of system states. + #: + #: :meta hide-value: + nstates = 0 + def __repr__(self): return self.name if self.name is not None else str(type(self)) @@ -751,6 +772,17 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, if nstates is not None and linsys.nstates != nstates: raise ValueError("Wrong number/type of states given.") + # The following text needs to be replicated from StateSpace in order for + # this entry to show up properly in sphinx doccumentation (not sure why, + # but it was the only way to get it to work). + # + #: Deprecated attribute; use :attr:`nstates` instead. + #: + #: The ``state`` attribute was used to store the number of states for : a + #: state space system. It is no longer used. If you need to access the + #: number of states, use :attr:`nstates`. + states = property(StateSpace._get_states, StateSpace._set_states) + def _update_params(self, params={}, warning=True): # Parameters not supported; issue a warning if params and warning: @@ -1473,6 +1505,17 @@ def __init__(self, io_sys, ss_sys=None): else: raise TypeError("Second argument must be a state space system.") + # The following text needs to be replicated from StateSpace in order for + # this entry to show up properly in sphinx doccumentation (not sure why, + # but it was the only way to get it to work). + # + #: Deprecated attribute; use :attr:`nstates` instead. + #: + #: The ``state`` attribute was used to store the number of states for : a + #: state space system. It is no longer used. If you need to access the + #: number of states, use :attr:`nstates`. + states = property(StateSpace._get_states, StateSpace._set_states) + def input_output_response( sys, T, U=0., X0=0, params={}, diff --git a/control/lti.py b/control/lti.py index 52f6b2e72..b9adf644f 100644 --- a/control/lti.py +++ b/control/lti.py @@ -59,34 +59,52 @@ def __init__(self, inputs=1, outputs=1, dt=None): # future warning, so that users will see it. # - @property - def inputs(self): + def _get_inputs(self): warn("The LTI `inputs` attribute will be deprecated in a future " "release. Use `ninputs` instead.", DeprecationWarning, stacklevel=2) return self.ninputs - @inputs.setter - def inputs(self, value): + def _set_inputs(self, value): warn("The LTI `inputs` attribute will be deprecated in a future " "release. Use `ninputs` instead.", DeprecationWarning, stacklevel=2) self.ninputs = value - @property - def outputs(self): + #: Deprecated + inputs = property( + _get_inputs, _set_inputs, doc= + """ + Deprecated attribute; use :attr:`ninputs` instead. + + The ``input`` attribute was used to store the number of system inputs. + It is no longer used. If you need access to the number of inputs for + an LTI system, use :attr:`ninputs`. + """) + + def _get_outputs(self): warn("The LTI `outputs` attribute will be deprecated in a future " "release. Use `noutputs` instead.", DeprecationWarning, stacklevel=2) return self.noutputs - @outputs.setter - def outputs(self, value): + def _set_outputs(self, value): warn("The LTI `outputs` attribute will be deprecated in a future " "release. Use `noutputs` instead.", DeprecationWarning, stacklevel=2) self.noutputs = value + #: Deprecated + outputs = property( + _get_outputs, _set_outputs, doc= + """ + Deprecated attribute; use :attr:`noutputs` instead. + + The ``output`` attribute was used to store the number of system + outputs. It is no longer used. If you need access to the number of + outputs for an LTI system, use :attr:`noutputs`. + """) + def isdtime(self, strict=False): """ Check to see if a system is a discrete-time system diff --git a/control/statesp.py b/control/statesp.py index 92834b3e4..9e009fa85 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -166,7 +166,7 @@ class StateSpace(LTI): linear time-invariant (LTI) systems: dx/dt = A x + B u - y = C x + D u + y = C x + D u where u is the input, y is the output, and x is the state. @@ -195,6 +195,10 @@ class StateSpace(LTI): The default value of dt can be changed by changing the value of ``control.config.defaults['control.default_dt']``. + A state space system is callable and returns the value of the transfer + function evaluated at a point in the complex plane. See + :meth:`~control.StateSpace.__call__` for a more detailed description. + StateSpace instances have support for IPython LaTeX output, intended for pretty-printing in Jupyter notebooks. The LaTeX output can be configured using @@ -212,6 +216,7 @@ class StateSpace(LTI): `'partitioned'` or `'separate'`. If `'partitioned'`, the A, B, C, D matrices are shown as a single, partitioned matrix; if `'separate'`, the matrices are shown separately. + """ # Allow ndarray * StateSpace to give StateSpace._rmul_() priority @@ -296,7 +301,8 @@ def __init__(self, *args, **kwargs): elif len(args) == 5: dt = args[4] if 'dt' in kwargs: - warn('received multiple dt arguments, using positional arg dt=%s'%dt) + warn("received multiple dt arguments, " + "using positional arg dt = %s" % dt) elif len(args) == 1: try: dt = args[0].dt @@ -331,6 +337,48 @@ def __init__(self, *args, **kwargs): if remove_useless_states: self._remove_useless_states() + # + # Class attributes + # + # These attributes are defined as class attributes so that they are + # documented properly. They are "overwritten" in __init__. + # + + #: Number of system inputs. + #: + #: :meta hide-value: + ninputs = 0 + + #: Number of system outputs. + #: + #: :meta hide-value: + noutputs = 0 + + #: Number of system states. + #: + #: :meta hide-value: + nstates = 0 + + #: Dynamics matrix. + #: + #: :meta hide-value: + A = [] + + #: Input matrix. + #: + #: :meta hide-value: + B = [] + + #: Output matrix. + #: + #: :meta hide-value: + C = [] + + #: Direct term. + #: + #: :meta hide-value: + D = [] + # # Getter and setter functions for legacy state attributes # @@ -339,20 +387,25 @@ def __init__(self, *args, **kwargs): # future warning, so that users will see it. # - @property - def states(self): + def _get_states(self): warn("The StateSpace `states` attribute will be deprecated in a " "future release. Use `nstates` instead.", DeprecationWarning, stacklevel=2) return self.nstates - @states.setter - def states(self, value): + def _set_states(self, value): warn("The StateSpace `states` attribute will be deprecated in a " "future release. Use `nstates` instead.", DeprecationWarning, stacklevel=2) self.nstates = value + #: Deprecated attribute; use :attr:`nstates` instead. + #: + #: The ``state`` attribute was used to store the number of states for : a + #: state space system. It is no longer used. If you need to access the + #: number of states, use :attr:`nstates`. + states = property(_get_states, _set_states) + def _remove_useless_states(self): """Check for states that don't do anything, and remove them. @@ -626,8 +679,10 @@ def __mul__(self, other): # Check to make sure the dimensions are OK if self.ninputs != other.noutputs: - raise ValueError("C = A * B: A has %i column(s) (input(s)), \ - but B has %i row(s)\n(output(s))." % (self.ninputs, other.noutputs)) + raise ValueError( + "C = A * B: A has %i column(s) (input(s)), " + "but B has %i row(s)\n(output(s))." % + (self.ninputs, other.noutputs)) dt = common_timebase(self.dt, other.dt) # Concatenate the various arrays @@ -821,10 +876,10 @@ def horner(self, x, warn_infinite=True): out = empty((self.noutputs, self.ninputs, len(x_arr)), dtype=complex) - #TODO: can this be vectorized? + # TODO: can this be vectorized? for idx, x_idx in enumerate(x_arr): try: - out[:,:,idx] = np.dot( + out[:, :, idx] = np.dot( self.C, solve(x_idx * eye(self.nstates) - self.A, self.B)) \ + self.D @@ -837,9 +892,9 @@ def horner(self, x, warn_infinite=True): # Evaluating at a pole. Return value depends if there # is a zero at the same point or not. if x_idx in self.zero(): - out[:,:,idx] = complex(np.nan, np.nan) + out[:, :, idx] = complex(np.nan, np.nan) else: - out[:,:,idx] = complex(np.inf, np.nan) + out[:, :, idx] = complex(np.inf, np.nan) return out @@ -914,7 +969,7 @@ def feedback(self, other=1, sign=-1): other = _convert_to_statespace(other) # Check to make sure the dimensions are OK - if (self.ninputs != other.noutputs) or (self.noutputs != other.ninputs): + if self.ninputs != other.noutputs or self.noutputs != other.ninputs: raise ValueError("State space systems don't have compatible " "inputs/outputs for feedback.") dt = common_timebase(self.dt, other.dt) @@ -1288,17 +1343,17 @@ def dynamics(self, t, x, u=None): ------- dx/dt or x[t+dt] : ndarray """ - x = np.reshape(x, (-1, 1)) # force to a column in case matrix + x = np.reshape(x, (-1, 1)) # force to a column in case matrix 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 - else: # received t, x, and u, ignore t - u = np.reshape(u, (-1, 1)) # force to a column in case matrix + return self.A.dot(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 + + self.B.dot(u).reshape((-1,)) # return as row vector def output(self, t, x, u=None): """Compute the output of the system @@ -1312,8 +1367,8 @@ def output(self, t, x, u=None): The first argument `t` is ignored because :class:`StateSpace` systems are time-invariant. It is included so that the dynamics can be passed - to most numerical integrators, such as scipy's `integrate.solve_ivp` and - for consistency with :class:`IOSystem` systems. + to most numerical integrators, such as scipy's `integrate.solve_ivp` + and for consistency with :class:`IOSystem` systems. The inputs `x` and `u` must be of the correct length for the system. @@ -1330,18 +1385,18 @@ def output(self, t, x, u=None): ------- y : ndarray """ - x = np.reshape(x, (-1, 1)) # force to a column in case matrix + x = np.reshape(x, (-1, 1)) # force to a column in case matrix if np.size(x) != self.nstates: 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 - else: # received t, x, and u, ignore t - u = np.reshape(u, (-1, 1)) # force to a column in case matrix + return self.C.dot(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 + + self.D.dot(u).reshape((-1,)) # return as row vector def _isstatic(self): """True if and only if the system has no dynamics, that is, @@ -1349,7 +1404,6 @@ def _isstatic(self): return not np.any(self.A) and not np.any(self.B) - # TODO: add discrete time check def _convert_to_statespace(sys, **kw): """Convert a system to state space form (if needed). @@ -1446,7 +1500,7 @@ def _convert_to_statespace(sys, **kw): try: D = _ssmatrix(sys) return StateSpace([], [], [], D) - except: + except Exception: raise TypeError("Can't convert given type to StateSpace system.") @@ -1679,6 +1733,7 @@ def _mimo2simo(sys, input, warn_conversion=False): return sys + def ss(*args, **kwargs): """ss(A, B, C, D[, dt]) @@ -1767,7 +1822,8 @@ def ss(*args, **kwargs): raise TypeError("ss(sys): sys must be a StateSpace or " "TransferFunction object. It is %s." % type(sys)) else: - raise ValueError("Needs 1, 4, or 5 arguments; received %i." % len(args)) + raise ValueError( + "Needs 1, 4, or 5 arguments; received %i." % len(args)) def tf2ss(*args): diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index c1c4d8006..8acd83632 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -963,7 +963,7 @@ def test_sys_naming_convention(self, tsys): ct.config.use_legacy_defaults('0.8.4') # changed delims in 0.9.0 ct.config.use_numpy_matrix(False) # np.matrix deprecated - ct.InputOutputSystem.idCounter = 0 + ct.InputOutputSystem._idCounter = 0 sys = ct.LinearIOSystem(tsys.mimo_linsys1) assert sys.name == "sys[0]" @@ -1027,7 +1027,7 @@ def test_signals_naming_convention_0_8_4(self, tsys): ct.config.use_legacy_defaults('0.8.4') # changed delims in 0.9.0 ct.config.use_numpy_matrix(False) # np.matrix deprecated - ct.InputOutputSystem.idCounter = 0 + ct.InputOutputSystem._idCounter = 0 sys = ct.LinearIOSystem(tsys.mimo_linsys1) for statename in ["x[0]", "x[1]"]: assert statename in sys.state_index diff --git a/control/xferfcn.py b/control/xferfcn.py index 99603b253..117edd120 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -72,6 +72,7 @@ # Define module default parameter values _xferfcn_defaults = {} + class TransferFunction(LTI): """TransferFunction(num, den[, dt]) @@ -105,6 +106,10 @@ class TransferFunction(LTI): The default value of dt can be changed by changing the value of ``control.config.defaults['control.default_dt']``. + A transfer function is callable and returns the value of the transfer + function evaluated at a point in the complex plane. See + :meth:`~control.TransferFunction.__call__` for a more detailed description. + The TransferFunction class defines two constants ``s`` and ``z`` that represent the differentiation and delay operators in continuous and discrete time. These can be used to create variables that allow algebraic @@ -112,6 +117,7 @@ class TransferFunction(LTI): >>> s = TransferFunction.s >>> G = (s + 1)/(s**2 + 2*s + 1) + """ # Give TransferFunction._rmul_() priority for ndarray * TransferFunction @@ -234,6 +240,45 @@ def __init__(self, *args, **kwargs): dt = config.defaults['control.default_dt'] self.dt = dt + # + # Class attributes + # + # These attributes are defined as class attributes so that they are + # documented properly. They are "overwritten" in __init__. + # + + #: Number of system inputs. + #: + #: :meta hide-value: + ninputs = 1 + + #: Number of system outputs. + #: + #: :meta hide-value: + noutputs = 1 + + #: Transfer function numerator polynomial (array) + #: + #: The numerator of the transfer function is store as an 2D list of + #: arrays containing MIMO numerator coefficients, indexed by outputs and + #: inputs. For example, ``num[2][5]`` is the array of coefficients for + #: the numerator of the transfer function from the sixth input to the + #: third output. + #: + #: :meta hide-value: + num = [[0]] + + #: Transfer function denominator polynomial (array) + #: + #: The numerator of the transfer function is store as an 2D list of + #: arrays containing MIMO numerator coefficients, indexed by outputs and + #: inputs. For example, ``den[2][5]`` is the array of coefficients for + #: the denominator of the transfer function from the sixth input to the + #: third output. + #: + #: :meta hide-value: + den = [[0]] + def __call__(self, x, squeeze=None, warn_infinite=True): """Evaluate system's transfer function at complex frequencies. @@ -390,11 +435,13 @@ def __repr__(self): if self.issiso(): return "TransferFunction({num}, {den}{dt})".format( num=self.num[0][0].__repr__(), den=self.den[0][0].__repr__(), - dt=(isdtime(self, strict=True) and ', {}'.format(self.dt)) or '') + dt=', {}'.format(self.dt) if isdtime(self, strict=True) + else '') else: return "TransferFunction({num}, {den}{dt})".format( num=self.num.__repr__(), den=self.den.__repr__(), - dt=(isdtime(self, strict=True) and ', {}'.format(self.dt)) or '') + dt=', {}'.format(self.dt) if isdtime(self, strict=True) + else '') def _repr_latex_(self, var=None): """LaTeX representation of transfer function, for Jupyter notebook""" @@ -1047,7 +1094,7 @@ def sample(self, Ts, method='zoh', alpha=None, prewarp_frequency=None): if method == "matched": return _c2d_matched(self, Ts) sys = (self.num[0][0], self.den[0][0]) - if (method=='bilinear' or (method=='gbt' and alpha==0.5)) and \ + if (method == 'bilinear' or (method == 'gbt' and alpha == 0.5)) and \ prewarp_frequency is not None: Twarp = 2*np.tan(prewarp_frequency*Ts/2)/prewarp_frequency else: @@ -1084,15 +1131,45 @@ def dcgain(self, warn_infinite=False): return self._dcgain(warn_infinite) def _isstatic(self): - """returns True if and only if all of the numerator and denominator - polynomials of the (possibly MIMO) transfer function are zeroth order, - 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: - return False - return True + """returns True if and only if all of the numerator and denominator + polynomials of the (possibly MIMO) transfer function are zeroth order, + 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: + return False + return True + + # Attributes for differentiation and delay + # + # These attributes are created here with sphinx docstrings so that the + # autodoc generated documentation has a description. The actual values of + # the class attributes are set at the bottom of the file to avoid problems + # with recursive calls. + + #: Differentation operator (continuous time) + #: + #: The ``s`` constant can be used to create continuous time transfer + #: functions using algebraic expressions. + #: + #: Example + #: ------- + #: >>> s = TransferFunction.s + #: >>> G = (s + 1)/(s**2 + 2*s + 1) + s = None + + #: Delay operator (discrete time) + #: + #: The ``z`` constant can be used to create discrete time transfer + #: functions using algebraic expressions. + #: + #: Example + #: ------- + #: >>> z = TransferFunction.z + #: >>> G = 2 * z / (4 * z**3 + 3*z - 1) + z = None + # c2d function contributed by Benjamin White, Oct 2012 def _c2d_matched(sysC, Ts): @@ -1297,7 +1374,7 @@ def _convert_to_transfer_function(sys, **kw): num = [[[D[i, j]] for j in range(inputs)] for i in range(outputs)] den = [[[1] for j in range(inputs)] for i in range(outputs)] return TransferFunction(num, den) - except: + except Exception: raise TypeError("Can't convert given type to TransferFunction system.") @@ -1563,6 +1640,7 @@ def _clean_part(data): return data + # Define constants to represent differentiation, unit delay TransferFunction.s = TransferFunction([1, 0], [1], 0) TransferFunction.z = TransferFunction([1, 0], [1], True) diff --git a/doc/classes.rst b/doc/classes.rst index fdf39a457..2217c7bff 100644 --- a/doc/classes.rst +++ b/doc/classes.rst @@ -12,6 +12,7 @@ these directly. .. autosummary:: :toctree: generated/ + :recursive: TransferFunction StateSpace diff --git a/doc/conf.py b/doc/conf.py index ebff50858..6fb670869 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # If your documentation needs a minimal Sphinx version, state it here. # -# needs_sphinx = '1.0' +needs_sphinx = '3.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -64,11 +64,14 @@ # list of autodoc directive flags that should be automatically applied # to all autodoc directives. -autodoc_default_options = {'members': True, - 'inherited-members': True} +autodoc_default_options = { + 'members': True, + 'inherited-members': True, + 'special-members': '__call__', +} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +# templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: diff --git a/doc/control.rst b/doc/control.rst index e8a29deb9..a3e28881b 100644 --- a/doc/control.rst +++ b/doc/control.rst @@ -6,8 +6,9 @@ Function reference .. Include header information from the main control module .. automodule:: control - :no-members: - :no-inherited-members: + :no-members: + :no-inherited-members: + :no-special-members: System creation =============== diff --git a/doc/flatsys.rst b/doc/flatsys.rst index b6d2fe962..cd8a4b6ce 100644 --- a/doc/flatsys.rst +++ b/doc/flatsys.rst @@ -7,6 +7,7 @@ Differentially flat systems .. automodule:: control.flatsys :no-members: :no-inherited-members: + :no-special-members: Overview of differential flatness ================================= diff --git a/doc/matlab.rst b/doc/matlab.rst index ae5688dde..c14a67e1f 100644 --- a/doc/matlab.rst +++ b/doc/matlab.rst @@ -7,6 +7,7 @@ .. automodule:: control.matlab :no-members: :no-inherited-members: + :no-special-members: Creating linear models ====================== diff --git a/doc/optimal.rst b/doc/optimal.rst index 9538c28c2..97dbbed0b 100644 --- a/doc/optimal.rst +++ b/doc/optimal.rst @@ -7,6 +7,7 @@ Optimal control .. automodule:: control.optimal :no-members: :no-inherited-members: + :no-special-members: Problem setup ============= From bf2472f7f78ab748d41ed032ced9cc8f33bcfc11 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sat, 12 Jun 2021 15:34:45 -0700 Subject: [PATCH 4/7] DOC: move constructor docs from __init__ to class; remove attr docs --- control/flatsys/basis.py | 5 + control/flatsys/linflat.py | 73 +++++++------- control/frdata.py | 23 +++++ control/iosys.py | 117 +++++++++-------------- control/lti.py | 12 +-- control/statesp.py | 42 ++++++-- control/xferfcn.py | 35 ++++++- doc/_templates/custom-class-template.rst | 23 +++++ doc/classes.rst | 4 +- doc/conf.py | 6 +- doc/descfcn.rst | 1 + doc/flatsys.rst | 1 + doc/optimal.rst | 5 + 13 files changed, 222 insertions(+), 125 deletions(-) create mode 100644 doc/_templates/custom-class-template.rst diff --git a/control/flatsys/basis.py b/control/flatsys/basis.py index 7592b79a2..1ea957f52 100644 --- a/control/flatsys/basis.py +++ b/control/flatsys/basis.py @@ -47,6 +47,11 @@ class BasisFamily: :math:`z_i^{(q)}(t)` = basis.eval_deriv(self, i, j, t) + Parameters + ---------- + N : int + Order of the basis set. + """ def __init__(self, N): """Create a basis family of order N.""" diff --git a/control/flatsys/linflat.py b/control/flatsys/linflat.py index 6e74ed581..1deb71960 100644 --- a/control/flatsys/linflat.py +++ b/control/flatsys/linflat.py @@ -42,6 +42,46 @@ class LinearFlatSystem(FlatSystem, LinearIOSystem): + """Base class for a linear, differentially flat system. + + This class is used to create a differentially flat system representation + from a linear system. + + Parameters + ---------- + linsys : StateSpace + LTI StateSpace system to be converted + inputs : int, list of str or None, optional + Description of the system inputs. This can be given as an integer + count or as a list of strings that name the individual signals. + If an integer count is specified, the names of the signal will be + of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If + this parameter is not given or given as `None`, the relevant + quantity will be determined when possible based on other + information provided to functions using the system. + outputs : int, list of str or None, optional + Description of the system outputs. Same format as `inputs`. + states : int, list of str, or None, optional + Description of the system states. Same format as `inputs`. + dt : None, True or float, optional + System timebase. None (default) indicates continuous + time, True indicates discrete time with undefined sampling + time, positive number is discrete time with specified + sampling time. + params : dict, optional + Parameter values for the systems. Passed to the evaluation + functions for the system as default values, overriding internal + defaults. + name : string, optional + System name (used for specifying signals) + + Returns + ------- + iosys : LinearFlatSystem + Linear system represented as an flat input/output system + + """ + def __init__(self, linsys, inputs=None, outputs=None, states=None, name=None): """Define a flat system from a SISO LTI system. @@ -49,39 +89,6 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, Given a reachable, single-input/single-output, linear time-invariant system, create a differentially flat system representation. - Parameters - ---------- - linsys : StateSpace - LTI StateSpace system to be converted - inputs : int, list of str or None, optional - Description of the system inputs. This can be given as an integer - count or as a list of strings that name the individual signals. - If an integer count is specified, the names of the signal will be - of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If - this parameter is not given or given as `None`, the relevant - quantity will be determined when possible based on other - information provided to functions using the system. - outputs : int, list of str or None, optional - Description of the system outputs. Same format as `inputs`. - states : int, list of str, or None, optional - Description of the system states. Same format as `inputs`. - dt : None, True or float, optional - System timebase. None (default) indicates continuous - time, True indicates discrete time with undefined sampling - time, positive number is discrete time with specified - sampling time. - params : dict, optional - Parameter values for the systems. Passed to the evaluation - functions for the system as default values, overriding internal - defaults. - name : string, optional - System name (used for specifying signals) - - Returns - ------- - iosys : LinearFlatSystem - Linear system represented as an flat input/output system - """ # Make sure we can handle the system if (not control.isctime(linsys)): diff --git a/control/frdata.py b/control/frdata.py index 625e84b75..5e9591c55 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -64,6 +64,29 @@ class FrequencyResponseData(LTI): The FrequencyResponseData (FRD) class is used to represent systems in frequency response data form. + Parameters + ---------- + d : 1D or 3D complex array_like + The frequency response at each frequency point. If 1D, the system is + assumed to be SISO. If 3D, the system is MIMO, with the first + dimension corresponding to the output index of the FRD, the second + dimension corresponding to the input index, and the 3rd dimension + corresponding to the frequency points in omega + w : iterable of real frequencies + List of frequency points for which data are available. + smooth : bool, optional + If ``True``, create an interpoloation function that allows the + frequency response to be computed at any frequency within the range of + frquencies give in ``w``. If ``False`` (default), frequency response + can only be obtained at the frequencies specified in ``w``. + + Attributes + ---------- + ninputs, noutputs : int + Number of input and output variables. + + Notes + ----- The main data members are 'omega' and 'fresp', where `omega` is a 1D array with the frequency points of the response, and `fresp` is a 3D array, with the first dimension corresponding to the output index of the FRD, the diff --git a/control/iosys.py b/control/iosys.py index 7dfd8756c..b1cdfadf3 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -121,7 +121,7 @@ class for a set of subclasses that are used to implement specific _idCounter = 0 - def name_or_default(self, name=None): + def _name_or_default(self, name=None): if name is None: name = "sys[{}]".format(InputOutputSystem._idCounter) InputOutputSystem._idCounter += 1 @@ -138,39 +138,6 @@ def __init__(self, inputs=None, outputs=None, states=None, params={}, :class:`~control.LinearIOSystem`, :class:`~control.NonlinearIOSystem`, :class:`~control.InterconnectedSystem`. - Parameters - ---------- - inputs : int, list of str, or None - Description of the system inputs. This can be given as an integer - count or as a list of strings that name the individual signals. - If an integer count is specified, the names of the signal will be - of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If - this parameter is not given or given as `None`, the relevant - quantity will be determined when possible based on other - information provided to functions using the system. - outputs : int, list of str, or None - Description of the system outputs. Same format as `inputs`. - states : int, list of str, or None - Description of the system states. Same format as `inputs`. - dt : None, True or float, optional - System timebase. 0 (default) indicates continuous - time, True indicates discrete time with unspecified sampling - time, positive number is discrete time with specified - sampling time, None indicates unspecified timebase (either - continuous or discrete time). - params : dict, optional - Parameter values for the systems. Passed to the evaluation - functions for the system as default values, overriding internal - defaults. - name : string, optional - System name (used for specifying signals). If unspecified, a - generic name is generated with a unique integer id. - - Returns - ------- - InputOutputSystem - Input/output system object - """ # Store the input arguments @@ -179,7 +146,7 @@ def __init__(self, inputs=None, outputs=None, states=None, params={}, # timebase self.dt = kwargs.get('dt', config.defaults['control.default_dt']) # system name - self.name = self.name_or_default(name) + self.name = self._name_or_default(name) # Parse and store the number of inputs, outputs, and states self.set_inputs(inputs) @@ -686,7 +653,7 @@ def copy(self, newname=None): dup_prefix = config.defaults['iosys.duplicate_system_name_prefix'] dup_suffix = config.defaults['iosys.duplicate_system_name_suffix'] newsys = copy.copy(self) - newsys.name = self.name_or_default( + newsys.name = self._name_or_default( dup_prefix + self.name + dup_suffix if not newname else newname) return newsys @@ -697,6 +664,47 @@ class LinearIOSystem(InputOutputSystem, StateSpace): This class is used to implementat a system that is a linear state space system (defined by the StateSpace system object). + Parameters + ---------- + linsys : StateSpace + LTI StateSpace system to be converted + inputs : int, list of str or None, optional + Description of the system inputs. This can be given as an integer + count or as a list of strings that name the individual signals. If an + integer count is specified, the names of the signal will be of the + form `s[i]` (where `s` is one of `u`, `y`, or `x`). If this parameter + is not given or given as `None`, the relevant quantity will be + determined when possible based on other information provided to + functions using the system. + outputs : int, list of str or None, optional + Description of the system outputs. Same format as `inputs`. + states : int, list of str, or None, optional + Description of the system states. Same format as `inputs`. + dt : None, True or float, optional + System timebase. 0 (default) indicates continuous time, True indicates + discrete time with unspecified sampling time, positive number is + discrete time with specified sampling time, None indicates unspecified + timebase (either continuous or discrete time). + params : dict, optional + Parameter values for the systems. Passed to the evaluation functions + for the system as default values, overriding internal defaults. + name : string, optional + System name (used for specifying signals). If unspecified, a + generic name is generated with a unique integer id. + + Attributes + ---------- + ninputs, noutputs, nstates, dt, etc + See :class:`InputOutputSystem` for inherited attributes. + + A, B, C, D + See :class:`~control.StateSpace` for inherited attributes. + + Returns + ------- + iosys : LinearIOSystem + Linear system represented as an input/output system + """ def __init__(self, linsys, inputs=None, outputs=None, states=None, name=None, **kwargs): @@ -704,42 +712,7 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, Converts a :class:`~control.StateSpace` system into an :class:`~control.InputOutputSystem` with the same inputs, outputs, and - states. The new system can be a continuous or discrete time system - - Parameters - ---------- - linsys : StateSpace - LTI StateSpace system to be converted - inputs : int, list of str or None, optional - Description of the system inputs. This can be given as an integer - count or as a list of strings that name the individual signals. - If an integer count is specified, the names of the signal will be - of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If - this parameter is not given or given as `None`, the relevant - quantity will be determined when possible based on other - information provided to functions using the system. - outputs : int, list of str or None, optional - Description of the system outputs. Same format as `inputs`. - states : int, list of str, or None, optional - Description of the system states. Same format as `inputs`. - dt : None, True or float, optional - System timebase. 0 (default) indicates continuous - time, True indicates discrete time with unspecified sampling - time, positive number is discrete time with specified - sampling time, None indicates unspecified timebase (either - continuous or discrete time). - params : dict, optional - Parameter values for the systems. Passed to the evaluation - functions for the system as default values, overriding internal - defaults. - name : string, optional - System name (used for specifying signals). If unspecified, a - generic name is generated with a unique integer id. - - Returns - ------- - iosys : LinearIOSystem - Linear system represented as an input/output system + states. The new system can be a continuous or discrete time system. """ if not isinstance(linsys, StateSpace): diff --git a/control/lti.py b/control/lti.py index b9adf644f..ef5d5569a 100644 --- a/control/lti.py +++ b/control/lti.py @@ -24,13 +24,13 @@ class LTI: """LTI is a parent class to linear time-invariant (LTI) system objects. - LTI is the parent to the StateSpace and TransferFunction child - classes. It contains the number of inputs and outputs, and the - timebase (dt) for the system. + LTI is the parent to the StateSpace and TransferFunction child classes. It + contains the number of inputs and outputs, and the timebase (dt) for the + system. This function is not generally called directly by the user. - The timebase for the system, dt, is used to specify whether the - system is operating in continuous or discrete time. It can have - the following values: + The timebase for the system, dt, is used to specify whether the system + is operating in continuous or discrete time. It can have the following + values: * dt = None No timebase specified * dt = 0 Continuous time system diff --git a/control/statesp.py b/control/statesp.py index 9e009fa85..6b3a1dff3 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -160,7 +160,7 @@ def _f2s(f): class StateSpace(LTI): """StateSpace(A, B, C, D[, dt]) - A class for representing state-space models + A class for representing state-space models. The StateSpace class is used to represent state-space realizations of linear time-invariant (LTI) systems: @@ -170,13 +170,39 @@ class StateSpace(LTI): where u is the input, y is the output, and x is the state. - The main data members are the A, B, C, and D matrices. The class also - keeps track of the number of states (i.e., the size of A). The data - format used to store state space matrices is set using the value of - `config.defaults['use_numpy_matrix']`. If True (default), the state space - elements are stored as `numpy.matrix` objects; otherwise they are - `numpy.ndarray` objects. The :func:`~control.use_numpy_matrix` function - can be used to set the storage type. + Parameters + ---------- + A, B, C, D: array_like + System matrices of the appropriate dimensions. + dt : None, True or float, optional + System timebase. 0 (default) indicates continuous + time, True indicates discrete time with unspecified sampling + time, positive number is discrete time with specified + sampling time, None indicates unspecified timebase (either + continuous or discrete time). + + Attributes + ---------- + ninputs, noutputs, nstates : int + Number of input, output and state variables. + A, B, C, D : 2D arrays + System matrices defining the input/output dynamics. + dt : None, True or float + System timebase. 0 (default) indicates continuous time, True indicates + discrete time with unspecified sampling time, positive number is + discrete time with specified sampling time, None indicates unspecified + timebase (either continuous or discrete time). + + Notes + ----- + The main data members in the ``StateSpace`` class are the A, B, C, and D + matrices. The class also keeps track of the number of states (i.e., + the size of A). The data format used to store state space matrices is + set using the value of `config.defaults['use_numpy_matrix']`. If True + (default), the state space elements are stored as `numpy.matrix` objects; + otherwise they are `numpy.ndarray` objects. The + :func:`~control.use_numpy_matrix` function can be used to set the storage + type. A discrete time system is created by specifying a nonzero 'timebase', dt when the system is constructed: diff --git a/control/xferfcn.py b/control/xferfcn.py index 117edd120..4871ca5b8 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -76,11 +76,38 @@ class TransferFunction(LTI): """TransferFunction(num, den[, dt]) - A class for representing transfer functions + A class for representing transfer functions. The TransferFunction class is used to represent systems in transfer function form. + Parameters + ---------- + num : array_like, or list of list of array_like + Polynomial coefficients of the numerator + den : array_like, or list of list of array_like + Polynomial coefficients of the denominator + dt : None, True or float, optional + System timebase. 0 (default) indicates continuous + time, True indicates discrete time with unspecified sampling + time, positive number is discrete time with specified + sampling time, None indicates unspecified timebase (either + continuous or discrete time). + + Attributes + ---------- + ninputs, noutputs, nstates : int + Number of input, output and state variables. + num, den : 2D list of array + Polynomial coeffients of the numerator and denominator. + dt : None, True or float + System timebase. 0 (default) indicates continuous time, True indicates + discrete time with unspecified sampling time, positive number is + discrete time with specified sampling time, None indicates unspecified + timebase (either continuous or discrete time). + + Notes + ----- The main data members are 'num' and 'den', which are 2-D lists of arrays containing MIMO numerator and denominator coefficients. For example, @@ -259,7 +286,7 @@ def __init__(self, *args, **kwargs): #: Transfer function numerator polynomial (array) #: - #: The numerator of the transfer function is store as an 2D list of + #: The numerator of the transfer function is stored as an 2D list of #: arrays containing MIMO numerator coefficients, indexed by outputs and #: inputs. For example, ``num[2][5]`` is the array of coefficients for #: the numerator of the transfer function from the sixth input to the @@ -1157,6 +1184,8 @@ def _isstatic(self): #: ------- #: >>> s = TransferFunction.s #: >>> G = (s + 1)/(s**2 + 2*s + 1) + #: + #: :meta hide-value: s = None #: Delay operator (discrete time) @@ -1168,6 +1197,8 @@ def _isstatic(self): #: ------- #: >>> z = TransferFunction.z #: >>> G = 2 * z / (4 * z**3 + 3*z - 1) + #: + #: :meta hide-value: z = None diff --git a/doc/_templates/custom-class-template.rst b/doc/_templates/custom-class-template.rst new file mode 100644 index 000000000..53a76e905 --- /dev/null +++ b/doc/_templates/custom-class-template.rst @@ -0,0 +1,23 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :show-inheritance: + :inherited-members: + :special-members: + + {% block methods %} + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + :nosignatures: + {% for item in methods %} + {%- if not item.startswith('_') %} + ~{{ name }}.{{ item }} + {%- endif -%} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/doc/classes.rst b/doc/classes.rst index 2217c7bff..a1c0c3c39 100644 --- a/doc/classes.rst +++ b/doc/classes.rst @@ -12,7 +12,7 @@ these directly. .. autosummary:: :toctree: generated/ - :recursive: + :template: custom-class-template.rst TransferFunction StateSpace @@ -26,6 +26,7 @@ that allow for linear, nonlinear, and interconnected elements: .. autosummary:: :toctree: generated/ + :template: custom-class-template.rst InterconnectedSystem LinearICSystem @@ -35,6 +36,7 @@ that allow for linear, nonlinear, and interconnected elements: Additional classes ================== .. autosummary:: + :template: custom-class-template.rst flatsys.BasisFamily flatsys.FlatSystem diff --git a/doc/conf.py b/doc/conf.py index 6fb670869..19c2970e1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -48,7 +48,7 @@ # If your documentation needs a minimal Sphinx version, state it here. # -needs_sphinx = '3.0' +needs_sphinx = '3.1' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -67,11 +67,11 @@ autodoc_default_options = { 'members': True, 'inherited-members': True, - 'special-members': '__call__', + 'exclude-members': '__init__, __weakref__, __repr__, __str__' } # Add any paths that contain templates here, relative to this directory. -# templates_path = ['_templates'] +templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: diff --git a/doc/descfcn.rst b/doc/descfcn.rst index 05f6bd94a..cc3b8668d 100644 --- a/doc/descfcn.rst +++ b/doc/descfcn.rst @@ -79,6 +79,7 @@ Module classes and functions ============================ .. autosummary:: :toctree: generated/ + :template: custom-class-template.rst ~control.DescribingFunctionNonlinearity ~control.friction_backlash_nonlinearity diff --git a/doc/flatsys.rst b/doc/flatsys.rst index cd8a4b6ce..4db754717 100644 --- a/doc/flatsys.rst +++ b/doc/flatsys.rst @@ -260,6 +260,7 @@ Flat systems classes -------------------- .. autosummary:: :toctree: generated/ + :template: custom-class-template.rst BasisFamily BezierFamily diff --git a/doc/optimal.rst b/doc/optimal.rst index 97dbbed0b..133163cdd 100644 --- a/doc/optimal.rst +++ b/doc/optimal.rst @@ -277,8 +277,13 @@ Module classes and functions ============================ .. autosummary:: :toctree: generated/ + :template: custom-class-template.rst ~control.optimal.OptimalControlProblem + +.. autosummary:: + :toctree: generated/ + ~control.optimal.solve_ocp ~control.optimal.create_mpc_iosystem ~control.optimal.input_poly_constraint From 70a6cf91559ac6d5947743a980bc308e6a336239 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Sun, 13 Jun 2021 11:51:25 -0700 Subject: [PATCH 5/7] TRV: fix typos pointed out by @namannimmo10 --- control/frdata.py | 4 ++-- control/xferfcn.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/control/frdata.py b/control/frdata.py index 5e9591c55..5e00798af 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -75,9 +75,9 @@ class FrequencyResponseData(LTI): w : iterable of real frequencies List of frequency points for which data are available. smooth : bool, optional - If ``True``, create an interpoloation function that allows the + If ``True``, create an interpolation function that allows the frequency response to be computed at any frequency within the range of - frquencies give in ``w``. If ``False`` (default), frequency response + frequencies give in ``w``. If ``False`` (default), frequency response can only be obtained at the frequencies specified in ``w``. Attributes diff --git a/control/xferfcn.py b/control/xferfcn.py index 4871ca5b8..399def909 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -99,7 +99,7 @@ class TransferFunction(LTI): ninputs, noutputs, nstates : int Number of input, output and state variables. num, den : 2D list of array - Polynomial coeffients of the numerator and denominator. + Polynomial coefficients of the numerator and denominator. dt : None, True or float System timebase. 0 (default) indicates continuous time, True indicates discrete time with unspecified sampling time, positive number is @@ -143,7 +143,7 @@ class TransferFunction(LTI): creation of transfer functions. For example, >>> s = TransferFunction.s - >>> G = (s + 1)/(s**2 + 2*s + 1) + >>> G = (s + 1)/(s**2 + 2*s + 1) """ From bda7d82f2016fc89a0ddc4f0f6ccffa2a09e2d37 Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Mon, 14 Jun 2021 21:19:24 -0700 Subject: [PATCH 6/7] DOC: fix inconsistencies in class constructor + class list documentation --- control/descfcn.py | 16 ++--- control/flatsys/bezier.py | 2 +- control/flatsys/flatsys.py | 114 ++++++++++++++++---------------- control/flatsys/linflat.py | 5 -- control/flatsys/systraj.py | 41 ++++++------ control/frdata.py | 17 +++-- control/iosys.py | 129 ++++++++++++++++--------------------- control/optimal.py | 124 +++++++++++++++++------------------ control/xferfcn.py | 8 +-- doc/classes.rst | 7 +- doc/flatsys.rst | 18 ++---- doc/iosys.rst | 7 +- doc/optimal.rst | 1 + 13 files changed, 232 insertions(+), 257 deletions(-) diff --git a/control/descfcn.py b/control/descfcn.py index 14a345495..2ebb18569 100644 --- a/control/descfcn.py +++ b/control/descfcn.py @@ -26,7 +26,7 @@ # Class for nonlinearities with a built-in describing function class DescribingFunctionNonlinearity(): - """Base class for nonlinear systems with a describing function + """Base class for nonlinear systems with a describing function. This class is intended to be used as a base class for nonlinear functions that have an analytically defined describing function. Subclasses should @@ -36,16 +36,16 @@ class DescribingFunctionNonlinearity(): """ def __init__(self): - """Initailize a describing function nonlinearity (optional)""" + """Initailize a describing function nonlinearity (optional).""" pass def __call__(self, A): - """Evaluate the nonlinearity at a (scalar) input value""" + """Evaluate the nonlinearity at a (scalar) input value.""" raise NotImplementedError( "__call__() not implemented for this function (internal error)") def describing_function(self, A): - """Return the describing function for a nonlinearity + """Return the describing function for a nonlinearity. This method is used to allow analytical representations of the describing function for a nonlinearity. It turns the (complex) value @@ -56,7 +56,7 @@ def describing_function(self, A): "describing function not implemented for this function") def _isstatic(self): - """Return True if the function has no internal state (memoryless) + """Return True if the function has no internal state (memoryless). This internal function is used to optimize numerical computation of the describing function. It can be set to `True` if the instance @@ -329,7 +329,7 @@ def _find_intersection(L1a, L1b, L2a, L2b): # Saturation nonlinearity class saturation_nonlinearity(DescribingFunctionNonlinearity): - """Create a saturation nonlinearity for use in describing function analysis + """Create saturation nonlinearity for use in describing function analysis. This class creates a nonlinear function representing a saturation with given upper and lower bounds, including the describing function for the @@ -381,7 +381,7 @@ def describing_function(self, A): # Relay with hysteresis (FBS2e, Example 10.12) class relay_hysteresis_nonlinearity(DescribingFunctionNonlinearity): - """Relay w/ hysteresis nonlinearity for use in describing function analysis + """Relay w/ hysteresis nonlinearity for describing function analysis. This class creates a nonlinear function representing a a relay with symmetric upper and lower bounds of magnitude `b` and a hysteretic region @@ -437,7 +437,7 @@ def describing_function(self, A): # Friction-dominated backlash nonlinearity (#48 in Gelb and Vander Velde, 1968) class friction_backlash_nonlinearity(DescribingFunctionNonlinearity): - """Backlash nonlinearity for use in describing function analysis + """Backlash nonlinearity for describing function analysis. This class creates a nonlinear function representing a friction-dominated backlash nonlinearity ,including the describing function for the diff --git a/control/flatsys/bezier.py b/control/flatsys/bezier.py index 5d0d551de..45a28995f 100644 --- a/control/flatsys/bezier.py +++ b/control/flatsys/bezier.py @@ -43,7 +43,7 @@ from .basis import BasisFamily class BezierFamily(BasisFamily): - r"""Polynomial basis functions. + r"""Bezier curve basis functions. This class represents the family of polynomials of the form diff --git a/control/flatsys/flatsys.py b/control/flatsys/flatsys.py index 1905c4cb8..bbf1e7fc7 100644 --- a/control/flatsys/flatsys.py +++ b/control/flatsys/flatsys.py @@ -54,8 +54,59 @@ class FlatSystem(NonlinearIOSystem): """Base class for representing a differentially flat system. The FlatSystem class is used as a base class to describe differentially - flat systems for trajectory generation. The class must implement two - functions: + flat systems for trajectory generation. The output of the system does not + need to be the differentially flat output. + + Parameters + ---------- + forward : callable + A function to compute the flat flag given the states and input. + reverse : callable + A function to compute the states and input given the flat flag. + updfcn : callable, optional + Function returning the state update function + + `updfcn(t, x, u[, param]) -> array` + + where `x` is a 1-D array with shape (nstates,), `u` is a 1-D array + with shape (ninputs,), `t` is a float representing the currrent + time, and `param` is an optional dict containing the values of + parameters used by the function. If not specified, the state + space update will be computed using the flat system coordinates. + outfcn : callable + Function returning the output at the given state + + `outfcn(t, x, u[, param]) -> array` + + where the arguments are the same as for `upfcn`. If not + specified, the output will be the flat outputs. + inputs : int, list of str, or None + Description of the system inputs. This can be given as an integer + count or as a list of strings that name the individual signals. + If an integer count is specified, the names of the signal will be + of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If + this parameter is not given or given as `None`, the relevant + quantity will be determined when possible based on other + information provided to functions using the system. + outputs : int, list of str, or None + Description of the system outputs. Same format as `inputs`. + states : int, list of str, or None + Description of the system states. Same format as `inputs`. + dt : None, True or float, optional + System timebase. None (default) indicates continuous + time, True indicates discrete time with undefined sampling + time, positive number is discrete time with specified + sampling time. + params : dict, optional + Parameter values for the systems. Passed to the evaluation + functions for the system as default values, overriding internal + defaults. + name : string, optional + System name (used for specifying signals) + + Notes + ----- + The class must implement two functions: zflag = flatsys.foward(x, u) This function computes the flag (derivatives) of the flat output. @@ -83,65 +134,13 @@ def __init__(self, updfcn=None, outfcn=None, # I/O system inputs=None, outputs=None, states=None, params={}, dt=None, name=None): - """Create a differentially flat input/output system. + """Create a differentially flat I/O system. The FlatIOSystem constructor is used to create an input/output system - object that also represents a differentially flat system. The output - of the system does not need to be the differentially flat output. - - Parameters - ---------- - forward : callable - A function to compute the flat flag given the states and input. - reverse : callable - A function to compute the states and input given the flat flag. - updfcn : callable, optional - Function returning the state update function - - `updfcn(t, x, u[, param]) -> array` - - where `x` is a 1-D array with shape (nstates,), `u` is a 1-D array - with shape (ninputs,), `t` is a float representing the currrent - time, and `param` is an optional dict containing the values of - parameters used by the function. If not specified, the state - space update will be computed using the flat system coordinates. - outfcn : callable - Function returning the output at the given state - - `outfcn(t, x, u[, param]) -> array` - - where the arguments are the same as for `upfcn`. If not - specified, the output will be the flat outputs. - inputs : int, list of str, or None - Description of the system inputs. This can be given as an integer - count or as a list of strings that name the individual signals. - If an integer count is specified, the names of the signal will be - of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If - this parameter is not given or given as `None`, the relevant - quantity will be determined when possible based on other - information provided to functions using the system. - outputs : int, list of str, or None - Description of the system outputs. Same format as `inputs`. - states : int, list of str, or None - Description of the system states. Same format as `inputs`. - dt : None, True or float, optional - System timebase. None (default) indicates continuous - time, True indicates discrete time with undefined sampling - time, positive number is discrete time with specified - sampling time. - params : dict, optional - Parameter values for the systems. Passed to the evaluation - functions for the system as default values, overriding internal - defaults. - name : string, optional - System name (used for specifying signals) - - Returns - ------- - InputOutputSystem - Input/output system object + object that also represents a differentially flat system. """ + # TODO: specify default update and output functions if updfcn is None: updfcn = self._flat_updfcn if outfcn is None: outfcn = self._flat_outfcn @@ -158,6 +157,7 @@ def __init__(self, # Save the length of the flat flag def forward(self, x, u, params={}): + """Compute the flat flag given the states and input. Given the states and inputs for a system, compute the flat diff --git a/control/flatsys/linflat.py b/control/flatsys/linflat.py index 1deb71960..1e96a23d2 100644 --- a/control/flatsys/linflat.py +++ b/control/flatsys/linflat.py @@ -75,11 +75,6 @@ class LinearFlatSystem(FlatSystem, LinearIOSystem): name : string, optional System name (used for specifying signals) - Returns - ------- - iosys : LinearFlatSystem - Linear system represented as an flat input/output system - """ def __init__(self, linsys, inputs=None, outputs=None, states=None, diff --git a/control/flatsys/systraj.py b/control/flatsys/systraj.py index 4505d3563..c6ffb0867 100644 --- a/control/flatsys/systraj.py +++ b/control/flatsys/systraj.py @@ -41,30 +41,29 @@ class SystemTrajectory: """Class representing a system trajectory. - The `SystemTrajectory` class is used to represent the trajectory of - a (differentially flat) system. Used by the - :func:`~control.trajsys.point_to_point` function to return a - trajectory. + The `SystemTrajectory` class is used to represent the + trajectory of a (differentially flat) system. Used by the + :func:`~control.trajsys.point_to_point` function to return a trajectory. - """ - def __init__(self, sys, basis, coeffs=[], flaglen=[]): - """Initilize a system trajectory object. + Parameters + ---------- + sys : FlatSystem + Flat system object associated with this trajectory. + basis : BasisFamily + Family of basis vectors to use to represent the trajectory. + coeffs : list of 1D arrays, optional + For each flat output, define the coefficients of the basis + functions used to represent the trajectory. Defaults to an empty + list. + flaglen : list of ints, optional + For each flat output, the number of derivatives of the flat + output used to define the trajectory. Defaults to an empty + list. - Parameters - ---------- - sys : FlatSystem - Flat system object associated with this trajectory. - basis : BasisFamily - Family of basis vectors to use to represent the trajectory. - coeffs : list of 1D arrays, optional - For each flat output, define the coefficients of the basis - functions used to represent the trajectory. Defaults to an empty - list. - flaglen : list of ints, optional - For each flat output, the number of derivatives of the flat output - used to define the trajectory. Defaults to an empty list. + """ - """ + def __init__(self, sys, basis, coeffs=[], flaglen=[]): + """Initilize a system trajectory object.""" self.nstates = sys.nstates self.ninputs = sys.ninputs self.system = sys diff --git a/control/frdata.py b/control/frdata.py index 5e00798af..9eee5aa86 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -57,9 +57,9 @@ class FrequencyResponseData(LTI): - """FrequencyResponseData(d, w) + """FrequencyResponseData(d, w[, smooth]) - A class for models defined by frequency response data (FRD) + A class for models defined by frequency response data (FRD). The FrequencyResponseData (FRD) class is used to represent systems in frequency response data form. @@ -84,13 +84,18 @@ class FrequencyResponseData(LTI): ---------- ninputs, noutputs : int Number of input and output variables. + omega : 1D array + Frequency points of the response. + fresp : 3D array + Frequency response, indexed by output index, input index, and + frequency point. Notes ----- - The main data members are 'omega' and 'fresp', where `omega` is a 1D array - with the frequency points of the response, and `fresp` is a 3D array, with - the first dimension corresponding to the output index of the FRD, the - second dimension corresponding to the input index, and the 3rd dimension + The main data members are 'omega' and 'fresp', where 'omega' is a the 1D + arran yf frequency points and and 'fresp' is a 3D array, with the first + dimension corresponding to the output index of the FRD, the 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]) diff --git a/control/iosys.py b/control/iosys.py index b1cdfadf3..c8469bce0 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -700,11 +700,6 @@ class LinearIOSystem(InputOutputSystem, StateSpace): A, B, C, D See :class:`~control.StateSpace` for inherited attributes. - Returns - ------- - iosys : LinearIOSystem - Linear system represented as an input/output system - """ def __init__(self, linsys, inputs=None, outputs=None, states=None, name=None, **kwargs): @@ -777,78 +772,68 @@ def _out(self, t, x, u): class NonlinearIOSystem(InputOutputSystem): """Nonlinear I/O system. - This class is used to implement a system that is a nonlinear state - space system (defined by and update function and an output function). - - """ - def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None, - states=None, params={}, name=None, **kwargs): - """Create a nonlinear I/O system given update and output functions. - - Creates an :class:`~control.InputOutputSystem` for a nonlinear system - by specifying a state update function and an output function. The new - system can be a continuous or discrete time system (Note: - discrete-time systems not yet supported by most function.) - - Parameters - ---------- - updfcn : callable - Function returning the state update function + Creates an :class:`~control.InputOutputSystem` for a nonlinear system + by specifying a state update function and an output function. The new + system can be a continuous or discrete time system (Note: + discrete-time systems not yet supported by most function.) - `updfcn(t, x, u, params) -> array` + Parameters + ---------- + updfcn : callable + Function returning the state update function - where `x` is a 1-D array with shape (nstates,), `u` is a 1-D array - with shape (ninputs,), `t` is a float representing the currrent - time, and `params` is a dict containing the values of parameters - used by the function. + `updfcn(t, x, u, params) -> array` - outfcn : callable - Function returning the output at the given state + where `x` is a 1-D array with shape (nstates,), `u` is a 1-D array + with shape (ninputs,), `t` is a float representing the currrent + time, and `params` is a dict containing the values of parameters + used by the function. - `outfcn(t, x, u, params) -> array` + outfcn : callable + Function returning the output at the given state - where the arguments are the same as for `upfcn`. + `outfcn(t, x, u, params) -> array` - inputs : int, list of str or None, optional - Description of the system inputs. This can be given as an integer - count or as a list of strings that name the individual signals. - If an integer count is specified, the names of the signal will be - of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If - this parameter is not given or given as `None`, the relevant - quantity will be determined when possible based on other - information provided to functions using the system. + where the arguments are the same as for `upfcn`. - outputs : int, list of str or None, optional - Description of the system outputs. Same format as `inputs`. + inputs : int, list of str or None, optional + Description of the system inputs. This can be given as an integer + count or as a list of strings that name the individual signals. + If an integer count is specified, the names of the signal will be + of the form `s[i]` (where `s` is one of `u`, `y`, or `x`). If + this parameter is not given or given as `None`, the relevant + quantity will be determined when possible based on other + information provided to functions using the system. - states : int, list of str, or None, optional - Description of the system states. Same format as `inputs`. + outputs : int, list of str or None, optional + Description of the system outputs. Same format as `inputs`. - params : dict, optional - Parameter values for the systems. Passed to the evaluation - functions for the system as default values, overriding internal - defaults. + states : int, list of str, or None, optional + Description of the system states. Same format as `inputs`. - dt : timebase, optional - The timebase for the system, used to specify whether the system is - operating in continuous or discrete time. It can have the - following values: + params : dict, optional + Parameter values for the systems. Passed to the evaluation + functions for the system as default values, overriding internal + defaults. - * dt = 0: continuous time system (default) - * dt > 0: discrete time system with sampling period 'dt' - * dt = True: discrete time with unspecified sampling period - * dt = None: no timebase specified + dt : timebase, optional + The timebase for the system, used to specify whether the system is + operating in continuous or discrete time. It can have the + following values: - name : string, optional - System name (used for specifying signals). If unspecified, a - generic name is generated with a unique integer id. + * dt = 0: continuous time system (default) + * dt > 0: discrete time system with sampling period 'dt' + * dt = True: discrete time with unspecified sampling period + * dt = None: no timebase specified - Returns - ------- - iosys : NonlinearIOSystem - Nonlinear system represented as an input/output system. + name : string, optional + System name (used for specifying signals). If unspecified, a + generic name is generated with a unique integer id. - """ + """ + def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None, + states=None, params={}, name=None, **kwargs): + """Create a nonlinear I/O system given update and output functions.""" # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) outputs = _parse_signal_parameter(outputs, 'output', kwargs) @@ -949,21 +934,14 @@ class InterconnectedSystem(InputOutputSystem): whose inputs and outputs are connected via a connection map. The overall system inputs and outputs are subsets of the subsystem inputs and outputs. + See :func:`~control.interconnect` for a list of parameters. + """ def __init__(self, syslist, connections=[], inplist=[], outlist=[], inputs=None, outputs=None, states=None, params={}, dt=None, name=None, **kwargs): - """Create an I/O system from a list of systems + connection info. - - The InterconnectedSystem class is used to represent an input/output - system that consists of an interconnection between a set of subystems. - The outputs of each subsystem can be summed together to provide - inputs to other subsystems. The overall system inputs and outputs can - be any subset of subsystem inputs and outputs. - - See :func:`~control.interconnect` for a list of parameters. + """Create an I/O system from a list of systems + connection info.""" - """ # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True) @@ -1430,6 +1408,9 @@ class LinearICSystem(InterconnectedSystem, LinearIOSystem): :class:`StateSpace` class structure, allowing it to be passed to functions that expect a :class:`StateSpace` system. + This class is usually generated using :func:`~control.interconnect` and + not called directly + """ def __init__(self, io_sys, ss_sys=None): @@ -2190,7 +2171,7 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[], Notes ----- If a system is duplicated in the list of systems to be connected, - a warning is generated a copy of the system is created with the + a warning is generated and a copy of the system is created with the name of the new system determined by adding the prefix and suffix strings in config.defaults['iosys.linearized_system_name_prefix'] and config.defaults['iosys.linearized_system_name_suffix'], with the diff --git a/control/optimal.py b/control/optimal.py index 63509ef4f..bbc8d0c9a 100644 --- a/control/optimal.py +++ b/control/optimal.py @@ -22,7 +22,7 @@ class OptimalControlProblem(): - """Description of a finite horizon, optimal control problem + """Description of a finite horizon, optimal control problem. The `OptimalControlProblem` class holds all of the information required to specify and optimal control problem: the system dynamics, cost function, @@ -31,12 +31,64 @@ class OptimalControlProblem(): `optimize.minimize` module, with the hope that this makes it easier to remember how to describe a problem. + Parameters + ---------- + sys : InputOutputSystem + I/O system for which the optimal input will be computed. + timepts : 1D array_like + List of times at which the optimal input should be computed. + integral_cost : callable + Function that returns the integral cost given the current state + and input. Called as integral_cost(x, u). + trajectory_constraints : list of tuples, optional + List of constraints that should hold at each point in the time + vector. Each element of the list should consist of a tuple with + first element given by :meth:`~scipy.optimize.LinearConstraint` or + :meth:`~scipy.optimize.NonlinearConstraint` and the remaining + elements of the tuple are the arguments that would be passed to + those functions. The constraints will be applied at each time + point along the trajectory. + terminal_cost : callable, optional + Function that returns the terminal cost given the current state + and input. Called as terminal_cost(x, u). + initial_guess : 1D or 2D array_like + Initial inputs to use as a guess for the optimal input. The + inputs should either be a 2D vector of shape (ninputs, horizon) + or a 1D input of shape (ninputs,) that will be broadcast by + extension of the time axis. + log : bool, optional + If `True`, turn on logging messages (using Python logging module). + kwargs : dict, optional + Additional parameters (passed to :func:`scipy.optimal.minimize`). + + Returns + ------- + ocp : OptimalControlProblem + Optimal control problem object, to be used in computing optimal + controllers. + + Additional parameters + --------------------- + solve_ivp_method : str, optional + Set the method used by :func:`scipy.integrate.solve_ivp`. + solve_ivp_kwargs : str, optional + Pass additional keywords to :func:`scipy.integrate.solve_ivp`. + minimize_method : str, optional + Set the method used by :func:`scipy.optimize.minimize`. + minimize_options : str, optional + Set the options keyword used by :func:`scipy.optimize.minimize`. + minimize_kwargs : str, optional + Pass additional keywords to :func:`scipy.optimize.minimize`. + Notes ----- - This class sets up an optimization over the inputs at each point in - time, using the integral and terminal costs as well as the - trajectory and terminal constraints. The `compute_trajectory` - method sets up an optimization problem that can be solved using + To describe an optimal control problem we need an input/output system, a + time horizon, a cost function, and (optionally) a set of constraints on + the state and/or input, either along the trajectory and at the terminal + time. This class sets up an optimization over the inputs at each point in + time, using the integral and terminal costs as well as the trajectory and + terminal constraints. The `compute_trajectory` method sets up an + optimization problem that can be solved using :func:`scipy.optimize.minimize`. The `_cost_function` method takes the information computes the cost of the @@ -62,63 +114,7 @@ def __init__( self, sys, timepts, integral_cost, trajectory_constraints=[], terminal_cost=None, terminal_constraints=[], initial_guess=None, basis=None, log=False, **kwargs): - """Set up an optimal control problem - - To describe an optimal control problem we need an input/output system, - a time horizon, a cost function, and (optionally) a set of constraints - on the state and/or input, either along the trajectory and at the - terminal time. - - Parameters - ---------- - sys : InputOutputSystem - I/O system for which the optimal input will be computed. - timepts : 1D array_like - List of times at which the optimal input should be computed. - integral_cost : callable - Function that returns the integral cost given the current state - and input. Called as integral_cost(x, u). - trajectory_constraints : list of tuples, optional - List of constraints that should hold at each point in the time - vector. Each element of the list should consist of a tuple with - first element given by :meth:`~scipy.optimize.LinearConstraint` or - :meth:`~scipy.optimize.NonlinearConstraint` and the remaining - elements of the tuple are the arguments that would be passed to - those functions. The constraints will be applied at each time - point along the trajectory. - terminal_cost : callable, optional - Function that returns the terminal cost given the current state - and input. Called as terminal_cost(x, u). - initial_guess : 1D or 2D array_like - Initial inputs to use as a guess for the optimal input. The - inputs should either be a 2D vector of shape (ninputs, horizon) - or a 1D input of shape (ninputs,) that will be broadcast by - extension of the time axis. - log : bool, optional - If `True`, turn on logging messages (using Python logging module). - kwargs : dict, optional - Additional parameters (passed to :func:`scipy.optimal.minimize`). - - Returns - ------- - ocp : OptimalControlProblem - Optimal control problem object, to be used in computing optimal - controllers. - - Additional parameters - --------------------- - solve_ivp_method : str, optional - Set the method used by :func:`scipy.integrate.solve_ivp`. - solve_ivp_kwargs : str, optional - Pass additional keywords to :func:`scipy.integrate.solve_ivp`. - minimize_method : str, optional - Set the method used by :func:`scipy.optimize.minimize`. - minimize_options : str, optional - Set the options keyword used by :func:`scipy.optimize.minimize`. - minimize_kwargs : str, optional - Pass additional keywords to :func:`scipy.optimize.minimize`. - - """ + """Set up an optimal control problem.""" # Save the basic information for use later self.system = sys self.timepts = timepts @@ -772,9 +768,9 @@ def compute_mpc(self, x, squeeze=None): # Optimal control result class OptimalControlResult(sp.optimize.OptimizeResult): - """Represents the optimal control result + """Result from solving an optimal control problem. - This class is a subclass of :class:`sp.optimize.OptimizeResult` with + This class is a subclass of :class:`scipy.optimize.OptimizeResult` with additional attributes associated with solving optimal control problems. Attributes diff --git a/control/xferfcn.py b/control/xferfcn.py index 399def909..cb3bb4d41 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -108,13 +108,13 @@ class TransferFunction(LTI): Notes ----- - The main data members are 'num' and 'den', which are 2-D lists of arrays - containing MIMO numerator and denominator coefficients. For example, + 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.]) - means that the numerator of the transfer function from the 6th input to the - 3rd output is set to s^2 + 4s + 8. + means that the numerator of the transfer function from the 6th input to + the 3rd output is set to s^2 + 4s + 8. A discrete time transfer function is created by specifying a nonzero 'timebase' dt when the system is constructed: diff --git a/doc/classes.rst b/doc/classes.rst index a1c0c3c39..b80b7dd54 100644 --- a/doc/classes.rst +++ b/doc/classes.rst @@ -17,7 +17,6 @@ these directly. TransferFunction StateSpace FrequencyResponseData - InputOutputSystem Input/output system subclasses ============================== @@ -25,9 +24,10 @@ Input/output systems are accessed primarily via a set of subclasses that allow for linear, nonlinear, and interconnected elements: .. autosummary:: - :toctree: generated/ :template: custom-class-template.rst + :nosignatures: + InputOutputSystem InterconnectedSystem LinearICSystem LinearIOSystem @@ -37,10 +37,13 @@ Additional classes ================== .. autosummary:: :template: custom-class-template.rst + :nosignatures: + DescribingFunctionNonlinearity flatsys.BasisFamily flatsys.FlatSystem flatsys.LinearFlatSystem flatsys.PolyFamily flatsys.SystemTrajectory optimal.OptimalControlProblem + optimal.OptimalControlResult diff --git a/doc/flatsys.rst b/doc/flatsys.rst index 4db754717..7599dd2af 100644 --- a/doc/flatsys.rst +++ b/doc/flatsys.rst @@ -256,22 +256,18 @@ the endpoints. Module classes and functions ============================ -Flat systems classes --------------------- .. autosummary:: :toctree: generated/ :template: custom-class-template.rst - BasisFamily - BezierFamily - FlatSystem - LinearFlatSystem - PolyFamily - SystemTrajectory + ~control.flatsys.BasisFamily + ~control.flatsys.BezierFamily + ~control.flatsys.FlatSystem + ~control.flatsys.LinearFlatSystem + ~control.flatsys.PolyFamily + ~control.flatsys.SystemTrajectory -Flat systems functions ----------------------- .. autosummary:: :toctree: generated/ - point_to_point + ~control.flatsys.point_to_point diff --git a/doc/iosys.rst b/doc/iosys.rst index 1b160bad1..41e37cfec 100644 --- a/doc/iosys.rst +++ b/doc/iosys.rst @@ -263,9 +263,9 @@ unconnected (so be careful!). Module classes and functions ============================ -Input/output system classes ---------------------------- .. autosummary:: + :toctree: generated/ + :template: custom-class-template.rst ~control.InputOutputSystem ~control.InterconnectedSystem @@ -273,9 +273,8 @@ Input/output system classes ~control.LinearIOSystem ~control.NonlinearIOSystem -Input/output system functions ------------------------------ .. autosummary:: + :toctree: generated/ ~control.find_eqpt ~control.linearize diff --git a/doc/optimal.rst b/doc/optimal.rst index 133163cdd..e173e430b 100644 --- a/doc/optimal.rst +++ b/doc/optimal.rst @@ -280,6 +280,7 @@ Module classes and functions :template: custom-class-template.rst ~control.optimal.OptimalControlProblem + ~control.optimal.OptimalControlResult .. autosummary:: :toctree: generated/ From 866a07c6de72de77680e34358e7959dc67b41ffa Mon Sep 17 00:00:00 2001 From: Richard Murray Date: Tue, 15 Jun 2021 08:07:45 -0700 Subject: [PATCH 7/7] TRV: fix typos in FRD docstring --- control/frdata.py | 15 +++++++-------- control/iosys.py | 8 ++++---- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/control/frdata.py b/control/frdata.py index 9eee5aa86..5e2f3f2e1 100644 --- a/control/frdata.py +++ b/control/frdata.py @@ -92,18 +92,17 @@ class FrequencyResponseData(LTI): Notes ----- - The main data members are 'omega' and 'fresp', where 'omega' is a the 1D - arran yf frequency points and and 'fresp' is a 3D array, with the first - dimension corresponding to the output index of the FRD, the second - dimension corresponding to the input index, and the 3rd dimension + The main data members are 'omega' and 'fresp', where 'omega' is a 1D array + of frequency points and and 'fresp' is a 3D array of frequency responses, + with the first dimension corresponding to the output index of the FRD, the + 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]) - 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 represent the outputs and the columns - represent the inputs. + 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 + represent the outputs and the columns represent the inputs. A frequency response data object is callable and returns the value of the transfer function evaluated at a point in the complex plane (must be on diff --git a/control/iosys.py b/control/iosys.py index c8469bce0..08249a651 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -772,10 +772,10 @@ def _out(self, t, x, u): class NonlinearIOSystem(InputOutputSystem): """Nonlinear I/O system. - Creates an :class:`~control.InputOutputSystem` for a nonlinear system - by specifying a state update function and an output function. The new - system can be a continuous or discrete time system (Note: - discrete-time systems not yet supported by most function.) + Creates an :class:`~control.InputOutputSystem` for a nonlinear system by + specifying a state update function and an output function. The new system + can be a continuous or discrete time system (Note: discrete-time systems + are not yet supported by most functions.) Parameters ----------