Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
allow bdalg fucntions (series, parallel, feedback) on general I/O sys…
…tems
  • Loading branch information
murrayrm committed Jun 19, 2023
commit e9e3a8e3ae0b7d6afc2ea1966b639e6243e71da3
148 changes: 80 additions & 68 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 @@ -204,23 +213,21 @@ def negate(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 @@ -233,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 @@ -258,55 +262,52 @@ def feedback(sys1, sys2=1, sign=-1):
# 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 @@ -331,6 +332,10 @@ def append(*sys):
def connect(sys, Q, inputv, outputv):
"""Index-based interconnection of an LTI system.

.. deprecated:: 0.10.0
Comment thread
murrayrm marked this conversation as resolved.
Outdated
`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 @@ -342,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 @@ -358,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 @@ -377,6 +386,9 @@ def connect(sys, Q, inputv, outputv):
interconnecting multiple systems.

"""
# TODO: maintain `connect` for use in MATLAB submodule (?)
Comment thread
murrayrm marked this conversation as resolved.
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: 5 additions & 4 deletions control/nlsys.py
Original file line number Diff line number Diff line change
Expand Up @@ -630,11 +630,12 @@ def __init__(self, syslist, connections=None, inplist=None, outlist=None,
dt = common_timebase(dt, sys.dt)

# Make sure number of inputs, outputs, states is given
if sys.ninputs is None or sys.noutputs is None or \
sys.nstates is None:
if sys.ninputs is None or sys.noutputs is None:
raise TypeError("system '%s' must define number of inputs, "
"outputs, states in order to be connected" %
sys.name)
elif sys.nstates is None:
raise TypeError("can't interconnect systems with no state")

# Keep track of the offsets into the states, inputs, outputs
self.input_offset.append(ninputs)
Expand Down Expand Up @@ -1203,8 +1204,8 @@ def nlsys(
--------
ss, tf

Example
-------
Examples
--------
>>> def kincar_update(t, x, u, params):
... l = params.get('l', 1) # wheelbase
... return np.array([
Expand Down
Loading