Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions control/flatsys/basis.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
Expand Down
73 changes: 40 additions & 33 deletions control/flatsys/linflat.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,46 +42,53 @@


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
Comment thread
bnavigator marked this conversation as resolved.
Resolved

"""

def __init__(self, linsys, inputs=None, outputs=None, states=None,
name=None):
"""Define a flat system from a SISO LTI system.

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)):
Expand Down
65 changes: 58 additions & 7 deletions control/frdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Comment thread
bnavigator marked this conversation as resolved.
Resolved
If ``True``, create an interpolation function that allows the
frequency response to be computed at any frequency within the range of
frequencies 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
-----
Comment thread
bnavigator marked this conversation as resolved.
Resolved
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
Expand All @@ -77,13 +100,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.
Expand Down Expand Up @@ -141,7 +186,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:
Expand Down Expand Up @@ -378,7 +424,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:
Expand All @@ -389,7 +435,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 "
Expand All @@ -398,7 +444,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):
Expand All @@ -417,6 +463,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
Expand Down Expand Up @@ -444,14 +493,15 @@ 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:
raise ValueError("input list must be 1D")

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__'):
Expand Down Expand Up @@ -510,6 +560,7 @@ def feedback(self, other=1, sign=-1):
# fixes this problem.
#


FRD = FrequencyResponseData


Expand All @@ -534,7 +585,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

Expand Down
Loading