Skip to content

I/O system class restructuring #916

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
15 changes: 9 additions & 6 deletions control/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,35 @@

# Import functions from within the control system library
# Note: the functions we use are specified as __all__ variables in the modules

# Input/output system modules
from .iosys import *
from .nlsys import *
from .lti import *
from .statesp import *
from .xferfcn import *
from .frdata import *

from .bdalg import *
from .delay import *
from .descfcn import *
from .dtime import *
from .freqplot import *
from .lti import *
from .margins import *
from .mateqn import *
from .modelsimp import *
from .iosys import *
from .nichols import *
from .phaseplot import *
from .pzmap import *
from .rlocus import *
from .statefbk import *
from .statesp import *
from .stochsys import *
from .timeresp import *
from .xferfcn import *
from .ctrlutil import *
from .frdata import *
from .canonical import *
from .robust import *
from .config import *
from .sisotool import *
from .nlsys import *
from .passivity import *

# Exceptions
Expand Down
157 changes: 85 additions & 72 deletions control/bdalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,13 @@

"""

from functools import reduce
import numpy as np
from warnings import warn
from . import xferfcn as tf
from . import statesp as ss
from . import frdata as frd
from .iosys import InputOutputSystem

__all__ = ['series', 'parallel', 'negate', 'feedback', 'append', 'connect']

Expand All @@ -68,12 +71,13 @@ def series(sys1, *sysn):

Parameters
----------
sys1 : scalar, StateSpace, TransferFunction, or FRD
*sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
sys1, sys2, ..., sysn: scalar, array, or :class:`InputOutputSystem`
I/O systems to combine.

Returns
-------
out : scalar, StateSpace, or TransferFunction
out : scalar, array, or :class:`InputOutputSystem`
Series interconnection of the systems.

Raises
------
Expand All @@ -83,14 +87,15 @@ def series(sys1, *sysn):

See Also
--------
parallel
feedback
append, feedback, interconnect, negate, parallel

Notes
-----
This function is a wrapper for the __mul__ function in the StateSpace and
TransferFunction classes. The output type is usually the type of `sys2`.
If `sys2` is a scalar, then the output type is the type of `sys1`.
This function is a wrapper for the __mul__ function in the appropriate
:class:`NonlinearIOSystem`, :class:`StateSpace`,
:class:`TransferFunction`, or other I/O system class. The output type
is the type of `sys1` unless a more general type is required based on
type type of `sys2`.

If both systems have a defined timebase (dt = 0 for continuous time,
dt > 0 for discrete time), then the timebase for both systems must
Expand All @@ -112,8 +117,7 @@ def series(sys1, *sysn):
(2, 1, 5)

"""
from functools import reduce
return reduce(lambda x, y:y*x, sysn, sys1)
return reduce(lambda x, y: y * x, sysn, sys1)


def parallel(sys1, *sysn):
Expand All @@ -123,12 +127,13 @@ def parallel(sys1, *sysn):

Parameters
----------
sys1 : scalar, StateSpace, TransferFunction, or FRD
*sysn : other scalars, StateSpaces, TransferFunctions, or FRDs
sys1, sys2, ..., sysn: scalar, array, or :class:`InputOutputSystem`
I/O systems to combine.

Returns
-------
out : scalar, StateSpace, or TransferFunction
out : scalar, array, or :class:`InputOutputSystem`
Parallel interconnection of the systems.

Raises
------
Expand All @@ -137,8 +142,7 @@ def parallel(sys1, *sysn):

See Also
--------
series
feedback
append, feedback, interconnect, negate, series

Notes
-----
Expand Down Expand Up @@ -167,8 +171,7 @@ def parallel(sys1, *sysn):
(3, 4, 7)

"""
from functools import reduce
return reduce(lambda x, y:x+y, sysn, sys1)
return reduce(lambda x, y: x + y, sysn, sys1)


def negate(sys):
Expand All @@ -177,17 +180,23 @@ def negate(sys):

Parameters
----------
sys : StateSpace, TransferFunction or FRD
sys: scalar, array, or :class:`InputOutputSystem`
I/O systems to negate.

Returns
-------
out : StateSpace or TransferFunction
out : scalar, array, or :class:`InputOutputSystem`
Negated system.

Notes
-----
This function is a wrapper for the __neg__ function in the StateSpace and
TransferFunction classes. The output type is the same as the input type.

See Also
--------
append, feedback, interconnect, parallel, series

Examples
--------
>>> G = ct.tf([2], [1, 1])
Expand All @@ -202,24 +211,23 @@ def negate(sys):
return -sys

#! TODO: expand to allow sys2 default to work in MIMO case?
#! TODO: allow renaming of signals (for all bdalg operations)
def feedback(sys1, sys2=1, sign=-1):
"""
Feedback interconnection between two I/O systems.
"""Feedback interconnection between two I/O systems.

Parameters
----------
sys1 : scalar, StateSpace, TransferFunction, FRD
The primary process.
sys2 : scalar, StateSpace, TransferFunction, FRD
The feedback process (often a feedback controller).
sys1, sys2: scalar, array, or :class:`InputOutputSystem`
I/O systems to combine.
sign: scalar
The sign of feedback. `sign` = -1 indicates negative feedback, and
`sign` = 1 indicates positive feedback. `sign` is an optional
argument; it assumes a value of -1 if not specified.

Returns
-------
out : StateSpace or TransferFunction
out : scalar, array, or :class:`InputOutputSystem`
Feedback interconnection of the systems.

Raises
------
Expand All @@ -232,17 +240,14 @@ def feedback(sys1, sys2=1, sign=-1):

See Also
--------
series
parallel
append, interconnect, negate, parallel, series

Notes
-----
This function is a wrapper for the feedback function in the StateSpace and
TransferFunction classes. It calls TransferFunction.feedback if `sys1` is a
TransferFunction object, and StateSpace.feedback if `sys1` is a StateSpace
object. If `sys1` is a scalar, then it is converted to `sys2`'s type, and
the corresponding feedback function is used. If `sys1` and `sys2` are both
scalars, then TransferFunction.feedback is used.
This function is a wrapper for the `feedback` function in the I/O
system classes. It calls sys1.feedback if `sys1` is an I/O system
object. If `sys1` is a scalar, then it is converted to `sys2`'s type,
and the corresponding feedback function is used.

Examples
--------
Expand All @@ -254,57 +259,55 @@ def feedback(sys1, sys2=1, sign=-1):

"""
# Allow anything with a feedback function to call that function
# TODO: rewrite to allow __rfeedback__
try:
return sys1.feedback(sys2, sign)
except AttributeError:
except (AttributeError, TypeError):
pass

# Check for correct input types.
if not isinstance(sys1, (int, float, complex, np.number,
tf.TransferFunction, ss.StateSpace, frd.FRD)):
raise TypeError("sys1 must be a TransferFunction, StateSpace " +
"or FRD object, or a scalar.")
if not isinstance(sys2, (int, float, complex, np.number,
tf.TransferFunction, ss.StateSpace, frd.FRD)):
raise TypeError("sys2 must be a TransferFunction, StateSpace " +
"or FRD object, or a scalar.")

# If sys1 is a scalar, convert it to the appropriate LTI type so that we can
# its feedback member function.
if isinstance(sys1, (int, float, complex, np.number)):
if isinstance(sys2, tf.TransferFunction):
# Check for correct input types
if not isinstance(sys1, (int, float, complex, np.number, np.ndarray,
InputOutputSystem)):
raise TypeError("sys1 must be an I/O system, scalar, or array")
elif not isinstance(sys2, (int, float, complex, np.number, np.ndarray,
InputOutputSystem)):
raise TypeError("sys2 must be an I/O system, scalar, or array")

# If sys1 is a scalar or ndarray, use the type of sys2 to figure
# out how to convert sys1, using transfer functions whenever possible.
if isinstance(sys1, (int, float, complex, np.number, np.ndarray)):
if isinstance(sys2, (int, float, complex, np.number, np.ndarray,
tf.TransferFunction)):
sys1 = tf._convert_to_transfer_function(sys1)
elif isinstance(sys2, ss.StateSpace):
sys1 = ss._convert_to_statespace(sys1)
elif isinstance(sys2, frd.FRD):
sys1 = frd._convert_to_FRD(sys1, sys2.omega)
else: # sys2 is a scalar.
sys1 = tf._convert_to_transfer_function(sys1)
sys2 = tf._convert_to_transfer_function(sys2)
else:
sys1 = ss._convert_to_statespace(sys1)

return sys1.feedback(sys2, sign)

def append(*sys):
"""append(sys1, sys2, [..., sysn])

Group models by appending their inputs and outputs.
Group LTI state space models by appending their inputs and outputs.

Forms an augmented system model, and appends the inputs and
outputs together. The system type will be the type of the first
system given; if you mix state-space systems and gain matrices,
make sure the gain matrices are not first.
outputs together.

Parameters
----------
sys1, sys2, ..., sysn: StateSpace or TransferFunction
LTI systems to combine

sys1, sys2, ..., sysn: scalar, array, or :class:`StateSpace`
I/O systems to combine.

Returns
-------
sys: LTI system
Combined LTI system, with input/output vectors consisting of all
input/output vectors appended
out: :class:`StateSpace`
Combined system, with input/output vectors consisting of all
input/output vectors appended.

See Also
--------
interconnect, feedback, negate, parallel, series

Examples
--------
Expand All @@ -329,6 +332,10 @@ def append(*sys):
def connect(sys, Q, inputv, outputv):
"""Index-based interconnection of an LTI system.

.. deprecated:: 0.10.0
`connect` will be removed in a future version of python-control in
favor of `interconnect`, which works with named signals.

The system `sys` is a system typically constructed with `append`, with
multiple inputs and outputs. The inputs and outputs are connected
according to the interconnection matrix `Q`, and then the final inputs and
Expand All @@ -340,8 +347,8 @@ def connect(sys, Q, inputv, outputv):

Parameters
----------
sys : StateSpace or TransferFunction
System to be connected
sys : :class:`InputOutputSystem`
System to be connected.
Q : 2D array
Interconnection matrix. First column gives the input to be connected.
The second column gives the index of an output that is to be fed into
Expand All @@ -356,8 +363,12 @@ def connect(sys, Q, inputv, outputv):

Returns
-------
sys: LTI system
Connected and trimmed LTI system
out : :class:`InputOutputSystem`
Connected and trimmed I/O system.

See Also
--------
append, feedback, interconnect, negate, parallel, series

Examples
--------
Expand All @@ -369,12 +380,14 @@ def connect(sys, Q, inputv, outputv):

Notes
-----
The :func:`~control.interconnect` function in the
:ref:`input/output systems <iosys-module>` module allows the use
of named signals and provides an alternative method for
interconnecting multiple systems.
The :func:`~control.interconnect` function in the :ref:`input/output
systems <iosys-module>` module allows the use of named signals and
provides an alternative method for interconnecting multiple systems.

"""
# TODO: maintain `connect` for use in MATLAB submodule (?)
warn("`connect` is deprecated; use `interconnect`", DeprecationWarning)

inputv, outputv, Q = \
np.atleast_1d(inputv), np.atleast_1d(outputv), np.atleast_1d(Q)
# check indices
Expand Down
9 changes: 3 additions & 6 deletions control/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,18 +123,15 @@ def reset_defaults():
from .sisotool import _sisotool_defaults
defaults.update(_sisotool_defaults)

from .iosys import _namedio_defaults
defaults.update(_namedio_defaults)
from .iosys import _iosys_defaults
defaults.update(_iosys_defaults)

from .xferfcn import _xferfcn_defaults
defaults.update(_xferfcn_defaults)

from .statesp import _statesp_defaults
defaults.update(_statesp_defaults)

from .nlsys import _iosys_defaults
defaults.update(_iosys_defaults)

from .optimal import _optimal_defaults
defaults.update(_optimal_defaults)

Expand Down Expand Up @@ -300,7 +297,7 @@ def use_legacy_defaults(version):
set_defaults('control', default_dt=None)

# changed iosys naming conventions
set_defaults('namedio', state_name_delim='.',
set_defaults('iosys', state_name_delim='.',
duplicate_system_name_prefix='copy of ',
duplicate_system_name_suffix='',
linearized_system_name_prefix='',
Expand Down
Loading