|
From: <kk...@us...> - 2011-02-08 22:15:58
|
Revision: 75
http://python-control.svn.sourceforge.net/python-control/?rev=75&view=rev
Author: kkchen
Date: 2011-02-08 22:15:51 +0000 (Tue, 08 Feb 2011)
Log Message:
-----------
Numerous docstring edits to comply with Sphinx and Python styles.
Kevin K. Chen <kk...@pr...>
Modified Paths:
--------------
branches/control-0.4a/src/bdalg.py
branches/control-0.4a/src/lti.py
branches/control-0.4a/src/matlab.py
branches/control-0.4a/src/statesp.py
branches/control-0.4a/src/xferfcn.py
Modified: branches/control-0.4a/src/bdalg.py
===================================================================
--- branches/control-0.4a/src/bdalg.py 2011-02-08 22:15:45 UTC (rev 74)
+++ branches/control-0.4a/src/bdalg.py 2011-02-08 22:15:51 UTC (rev 75)
@@ -1,98 +1,200 @@
-# bdalg.py - functions for implmeenting block diagram algebra
-#
-# Author: Richard M. Murray
-# Date: 24 May 09
-#
-# This file contains some standard block diagram algebra. If all
-# arguments are SISO transfer functions, the results are a transfer
-# function. Otherwise, the computation is done in state space.
-#
-#! State space operations are not currently implemented.
-#
-# Copyright (c) 2010 by California Institute of Technology
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the California Institute of Technology nor
-# the names of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
-# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: bdalg.py 17 2010-05-29 23:50:52Z murrayrm $
+"""bdalg.py
+This file contains some standard block diagram algebra.
+
+Routines in this module:
+
+series
+parallel
+negate
+feedback
+
+Copyright (c) 2010 by California Institute of Technology
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the California Institute of Technology nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
+OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Author: Richard M. Murray
+Date: 24 May 09
+Revised: Kevin K. Chen, Dec 10
+
+$Id: bdalg.py 17 2010-05-29 23:50:52Z murrayrm $
+
+"""
+
import scipy as sp
import xferfcn as tf
import statesp as ss
def series(sys1, sys2):
- """Return the series connection sys1 * sys2 for --> sys2 --> sys1 -->."""
+ """Return the series connection sys2 * sys1 for --> sys1 --> sys2 -->.
+
+ Parameters
+ ---------
+ sys1: scalar, StateSpace, or TransferFunction
+ sys2: scalar, StateSpace, or TransferFunction
+
+ Returns
+ -------
+ out: scalar, StateSpace, or TransferFunction
+
+ Raises
+ ------
+ ValueError
+ if sys2.inputs does not equal sys1.outputs
+
+ See Also
+ --------
+ parallel
+ feedback
+
+ 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.
+
+ Examples
+ --------
+ >>> sys3 = series(sys1, sys2) # Same as sys3 = sys2 * sys1.
+
+ """
- return sys1 * sys2
+ return sys2 * sys1
def parallel(sys1, sys2):
- """Return the parallel connection sys1 + sys2."""
+ """
+ Return the parallel connection sys1 + sys2.
+
+ Parameters
+ ---------
+ sys1: scalar, StateSpace, or TransferFunction
+ sys2: scalar, StateSpace, or TransferFunction
+
+ Returns
+ -------
+ out: scalar, StateSpace, or TransferFunction
+
+ Raises
+ ------
+ ValueError
+ if sys1 and sys2 do not have the same numbers of inputs and outputs
+
+ See Also
+ --------
+ series
+ feedback
+ Notes
+ -----
+ This function is a wrapper for the __add__ function in the StateSpace and
+ TransferFunction classes. The output type is usually the type of sys1. If
+ sys1 is a scalar, then the output type is the type of sys2.
+
+ Examples
+ -------
+ >>> sys3 = parallel(sys1, sys2) # Same as sys3 = sys1 + sys2.
+
+ """
+
return sys1 + sys2
def negate(sys):
- """Return the negative of a system."""
+ """
+ Return the negative of a system.
+
+ Parameters
+ ----------
+ sys: StateSpace or TransferFunction
+
+ Returns
+ -------
+ out: StateSpace or TransferFunction
+
+ 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.
+
+ Examples
+ --------
+ >>> sys2 = negate(sys1) # Same as sys2 = -sys1.
+
+ """
return -sys;
def feedback(sys1, sys2, sign=-1):
- """Feedback interconnection between two I/O systems.
+ """
+ Feedback interconnection between two I/O systems.
- Usage
- =====
- sys = feedback(sys1, sys2)
- sys = feedback(sys1, sys2, sign)
-
- Compute the system corresponding to a feedback interconnection between
- sys1 and sys2. When sign is not specified, it assumes a value of -1
- (negative feedback). A sign of 1 indicates positive feedback.
-
Parameters
----------
- sys1, sys2: linsys
- Linear input/output systems
+ sys1: scalar, StateSpace, or TransferFunction
+ The primary plant.
+ sys2: scalar, StateSpace, or TransferFunction
+ The feedback plant (often a feedback controller).
sign: scalar
- Feedback sign.
+ 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.
- Return values
- -------------
- sys: linsys
+ Returns
+ -------
+ out: StateSpace or TransferFunction
+ Raises
+ ------
+ ValueError
+ if sys1 does not have as many inputs as sys2 has outputs, or if sys2
+ does not have as many inputs as sys1 has outputs
+ NotImplementedError
+ if an attempt is made to perform a feedback on a MIMO TransferFunction
+ object
+
+ See Also
+ --------
+ series
+ parallel
+
Notes
- -----
- 1. This function calls calls xferfcn.feedback if sys1 is a TransferFunction
- object and statesp.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 use
- xferfcn.feedback."""
+ ----
+ 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.
+ """
+
# Check for correct input types.
if not isinstance(sys1, (int, long, float, complex, tf.TransferFunction,
ss.StateSpace)):
Modified: branches/control-0.4a/src/lti.py
===================================================================
--- branches/control-0.4a/src/lti.py 2011-02-08 22:15:45 UTC (rev 74)
+++ branches/control-0.4a/src/lti.py 2011-02-08 22:15:51 UTC (rev 75)
@@ -1,9 +1,48 @@
+"""lti.py
+
+The lti module contains the Lti parent class to the child classes StateSpace and
+TransferFunction. It is designed for use in the python-control library.
+
+Routines in this module:
+
+Lti.__init__
+
+"""
+
class Lti:
- """The Lti is a parent class to the StateSpace and TransferFunction child
- classes. It only contains the number of inputs and outputs, but this can be
- expanded in the future."""
+
+ """Lti is a parent class to linear time invariant control (LTI) objects.
+ Lti is the parent to the StateSpace and TransferFunction child classes. It
+ contains the number of inputs and outputs, but this can be expanded in the
+ future.
+
+ The StateSpace and TransferFunction child classes contain several common
+ "virtual" functions. These are:
+
+ __init__
+ __str__
+ __neg__
+ __add__
+ __radd__
+ __sub__
+ __rsub__
+ __mul__
+ __rmul__
+ __div__
+ __rdiv__
+ evalfr
+ freqresp
+ pole
+ zero
+ feedback
+ returnScipySignalLti
+
+ """
+
def __init__(self, inputs=1, outputs=1):
+ """Assign the LTI object's numbers of inputs and ouputs."""
+
# Data members common to StateSpace and TransferFunction.
self.inputs = inputs
self.outputs = outputs
Modified: branches/control-0.4a/src/matlab.py
===================================================================
--- branches/control-0.4a/src/matlab.py 2011-02-08 22:15:45 UTC (rev 74)
+++ branches/control-0.4a/src/matlab.py 2011-02-08 22:15:51 UTC (rev 75)
@@ -1,53 +1,58 @@
-# matlab.py - MATLAB emulation functions
-#
-# Author: Richard M. Murray
-# Date: 29 May 09
-#
-# This file contains a number of functions that emulate some of the
-# functionality of MATLAB. The intent of these functions is to
-# provide a simple interface to the python control systems library
-# (python-control) for people who are familiar with the MATLAB Control
-# Systems Toolbox (tm). Most of the functions are just calls to
-# python-control functions defined elsewhere. Use 'from
-# control.matlab import *' in python to include all of the functions
-# defined here. Functions that are defined in other libraries that
-# have the same names as their MATLAB equivalents are automatically
-# imported here.
-#
-# Copyright (c) 2009 by California Institute of Technology
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the California Institute of Technology nor
-# the names of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
-# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: matlab.py 33 2010-11-26 21:59:57Z murrayrm $
+"""matlab.py
+MATLAB emulation functions.
+
+This file contains a number of functions that emulate some of the
+functionality of MATLAB. The intent of these functions is to
+provide a simple interface to the python control systems library
+(python-control) for people who are familiar with the MATLAB Control
+Systems Toolbox (tm). Most of the functions are just calls to
+python-control functions defined elsewhere. Use 'from
+control.matlab import *' in python to include all of the functions
+defined here. Functions that are defined in other libraries that
+have the same names as their MATLAB equivalents are automatically
+imported here.
+
+Copyright (c) 2009 by California Institute of Technology
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the California Institute of Technology nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
+OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Author: Richard M. Murray
+Date: 29 May 09
+Revised: Kevin K. Chen, Dec 10
+
+$Id: matlab.py 33 2010-11-26 21:59:57Z murrayrm $
+
+"""
+
# Libraries that we make use of
import scipy as sp # SciPy library (used all over)
import numpy as np # NumPy library
@@ -72,7 +77,7 @@
from statefbk import ctrb, obsv, gram, place, lqr
from delay import pade
-__doc__ = """
+__doc__ += """
The control.matlab module defines functions that are roughly the
equivalents of those in the MATLAB Control Toolbox. Items marked by a
'*' are currently implemented; those marked with a '-' are not planned
@@ -255,16 +260,44 @@
* linspace - generate a set of numbers that are linearly spaced
* logspace - generate a set of numbers that are logarithmically spaced
* unwrap - unwrap a phase angle to give a continuous curve
+
"""
def ss(*args):
- """Create a state space system.
+ """
+ Create a state space system.
- Usage
- =====
- ss(A, B, C, D)
- ss(sys)"""
-
+ Parameters
+ ----------
+ A: numpy matrix or matrix-like object
+ B: numpy matrix or matrix-like object
+ C: numpy matrix or matrix-like object
+ D: numpy matrix or matrix-like object
+ sys: StateSpace or TransferFunction object
+ ss accepts a set of A, B, C, D matrices or sys.
+
+ Returns
+ -------
+ out: StateSpace object
+
+ Raises
+ ------
+ ValueError
+ if matrix sizes are not self-consistent
+
+ See Also
+ --------
+ tf
+ ss2tf
+ tf2ss
+
+ Examples
+ --------
+ >>> sys = ss(A, B, C, D) # Create a StateSpace object from these matrices.
+ >>> sys = ss(sys1) # Convert a TransferFunction to a StateSpace object.
+
+ """
+
if len(args) == 4:
return StateSpace(args[0], args[1], args[2], args[3])
elif len(args) == 1:
@@ -275,21 +308,55 @@
return tf2ss(sys)
else:
raise TypeError("ss(sys): sys must be a StateSpace or \
-TransferFunction object.")
+TransferFunction object. It is %s." % type(sys))
else:
raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
def tf(*args):
- """Create a transfer function system.
+ """
+ Create a transfer function system.
- Usage
- =====
- tf(num, den)
- tf(sys)
-
- num and den can be scalars or vectors for SISO systems, or lists of lists of
- vectors for MIMO systems."""
+ Parameters
+ ----------
+ num: vector, or list of lists of vectors
+ den: vector, or list of lists of vectors
+ sys: StateSpace or TransferFunction object
+ tf accepts a num and den, or sys.
+ Returns
+ -------
+ out: TransferFunction object
+
+ Raises
+ ------
+ ValueError
+ if num and den have invalid or unequal dimensions
+ TypeError
+ if num or den are of incorrect type
+
+ See Also
+ --------
+ ss
+ ss2tf
+ tf2ss
+
+ Notes
+ --------
+ num[i][j] is the vector of polynomial coefficients of the transfer function
+ numerator from the (j+1)st output to the (i+1)st input. den[i][j] works the
+ same way.
+
+ Examples
+ --------
+ >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]]
+ >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]]
+ >>> sys = tf(num, den)
+ The transfer function from the 2nd input to the 1st output is
+ (3s + 4) / (6s^2 + 5s + 4).
+ >>> sys = tf(sys1) # Convert a StateSpace to a TransferFunction object.
+
+ """
+
if len(args) == 2:
return TransferFunction(args[0], args[1])
elif len(args) == 1:
@@ -300,38 +367,107 @@
return sys
else:
raise TypeError("tf(sys): sys must be a StateSpace or \
-TransferFunction object.")
+TransferFunction object. It is %s." % type(sys))
else:
raise ValueError("Needs 1 or 2 arguments; received %i." % len(args))
def ss2tf(*args):
- """Transform a state space system to a transfer function.
+ """
+ Transform a state space system to a transfer function.
- Usage
- =====
- ss2tf(A, B, C, D)
- ss2tf(sys) - sys should have attributes A, B, C, D"""
-
+ Parameters
+ ----------
+ A: numpy matrix or matrix-like object
+ B: numpy matrix or matrix-like object
+ C: numpy matrix or matrix-like object
+ D: numpy matrix or matrix-like object
+ sys: StateSpace object
+ ss accepts a set of A, B, C, D matrices or a StateSpace object.
+
+ Returns
+ -------
+ out: TransferFunction object
+
+ Raises
+ ------
+ ValueError
+ if matrix sizes are not self-consistent, or if an invalid number of
+ arguments is passed in
+ TypeError
+ if sys is not a StateSpace object
+
+ See Also
+ --------
+ tf
+ ss
+ tf2ss
+
+ Examples
+ --------
+ >>> sys = ss2tf(A, B, C, D)
+ >>> sys = ss2tf(sys1) # Convert a StateSpace to a TransferFunction object.
+
+ """
+
if len(args) == 4:
# Assume we were given the A, B, C, D matrix
return convertToTransferFunction(StateSpace(args[0], args[1], args[2],
args[3]))
elif len(args) == 1:
sys = args[0]
- if not isinstance(sys, StateSpace):
- raise TypeError("ss2tf(sys): sys must be a StateSpace object.")
- return convertToTransferFunction(sys)
+ if isinstance(sys, StateSpace):
+ return convertToTransferFunction(sys)
+ else:
+ raise TypeError("ss2tf(sys): sys must be a StateSpace object. It \
+is %s." % type(sys))
else:
raise ValueError("Needs 1 or 4 arguments; received %i." % len(args))
def tf2ss(*args):
- """Transform a transfer function to a state space system.
-
- Usage
- =====
- tf2ss(num, den)
- tf2ss(sys) - sys should be a system object (lti or TransferFunction)"""
+ """
+ Transform a transfer function to a state space system.
+ Parameters
+ ----------
+ num: vector, or list of lists of vectors
+ den: vector, or list of lists of vectors
+ sys: TransferFunction object
+ tf2ss accepts num and den, or sys.
+
+ Returns
+ -------
+ out: StateSpace object
+
+ Raises
+ ------
+ ValueError
+ if num and den have invalid or unequal dimensions, or if an invalid
+ number of arguments is passed in
+ TypeError
+ if num or den are of incorrect type, or if sys is not a TransferFunction
+ object
+
+ See Also
+ --------
+ ss
+ tf
+ ss2tf
+
+ Notes
+ --------
+ num[i][j] is the vector of polynomial coefficients of the transfer function
+ numerator from the (j+1)st output to the (i+1)st input. den[i][j] works the
+ same way.
+
+ Examples
+ --------
+ >>> num = [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]]]
+ >>> den = [[[9., 8., 7.], [6., 5., 4.]], [[3., 2., 1.], [-1., -2., -3.]]]
+ >>> sys = tf2ss(num, den)
+ >>> sys = tf2ss(sys1) # Convert a TransferFunction to a StateSpace object.
+
+ """
+
if len(args) == 2:
# Assume we were given the num, den
return convertToStateSpace(TransferFunction(args[0], args[1]))
@@ -459,10 +595,6 @@
yout response of the system
xout time evolution of the state vector
"""
- sys = args[0]
- ltiobjs = sys.returnScipySignalLti()
- ltiobj = ltiobjs[0][0]
-
return sp.signal.lsim2(*args, **keywords)
#! Redefine step to use lsim2
@@ -483,19 +615,8 @@
T time values of the output
yout response of the system
"""
- sys = args[0]
- ltiobjs = sys.returnScipySignalLti()
- ltiobj = ltiobjs[0][0]
- newargs = []
- newargs.append(ltiobj)
- for i in range(1, len(args)):
- newargs.append(args[i])
- newargs = tuple(newargs)
- print len(args)
- print len(newargs)
+ return sp.signal.step(*args, **keywords)
- return sp.signal.step(*newargs, **keywords)
-
# Redefine initial to use lsim2
#! Not yet implemented (uses step for now)
def initial(*args, **keywords):
@@ -514,10 +635,6 @@
T time values of the output
yout response of the system
"""
- sys = args[0]
- ltiobjs = sys.returnScipySignalLti()
- ltiobj = ltiobjs[0][0]
-
return sp.signal.initial(*args, **keywords)
# Redefine impulse to use initial()
@@ -538,9 +655,5 @@
T time values of the output
yout response of the system
"""
- sys = args[0]
- ltiobjs = sys.returnScipySignalLti()
- ltiobj = ltiobjs[0][0]
+ return sp.signal.impulse(*args, **keywords)
- return sp.signal.impulse(ltiobj, **keywords)
-
Modified: branches/control-0.4a/src/statesp.py
===================================================================
--- branches/control-0.4a/src/statesp.py 2011-02-08 22:15:45 UTC (rev 74)
+++ branches/control-0.4a/src/statesp.py 2011-02-08 22:15:51 UTC (rev 75)
@@ -1,67 +1,104 @@
-# stateSpace.py - state space class for control systems library
-#
-# Author: Richard M. Murray
-# Date: 24 May 09
-#
-# This file contains the StateSpace class, which is used to represent
-# linear systems in state space. This is the primary representation
-# for the control system library.
-#
-# Copyright (c) 2010 by California Institute of Technology
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the California Institute of Technology nor
-# the names of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
-# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: statesp.py 21 2010-06-06 17:29:42Z murrayrm $
+"""stateSpace.py
-import scipy as sp
-from scipy import concatenate, zeros
-from numpy.linalg import solve
+State space representation and functions.
+
+This file contains the StateSpace class, which is used to represent
+linear systems in state space. This is the primary representation
+for the python-control library.
+
+Routines in this module:
+
+StateSpace.__init__
+StateSpace.__str__
+StateSpace.__neg__
+StateSpace.__add__
+StateSpace.__radd__
+StateSpace.__sub__
+StateSpace.__rsub__
+StateSpace.__mul__
+StateSpace.__rmul__
+StateSpace.__div__
+StateSpace.__rdiv__
+StateSpace.evalfr
+StateSpace.freqresp
+StateSpace.pole
+StateSpace.zero
+StateSpace.feedback
+StateSpace.returnScipySignalLti
+convertToStateSpace
+rss_generate
+
+Copyright (c) 2010 by California Institute of Technology
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the California Institute of Technology nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
+OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Author: Richard M. Murray
+Date: 24 May 09
+Revised: Kevin K. Chen, Dec 10
+
+$Id: statepy 21 2010-06-06 17:29:42Z murrayrm $
+
+"""
+
+from numpy import angle, any, concatenate, cos, dot, empty, exp, eye, pi, \
+ poly, poly1d, matrix, roots, sin, zeros
+from numpy.random import rand, randn
+from numpy.linalg import inv, det, solve
+from numpy.linalg.linalg import LinAlgError
+from scipy.signal import lti
+from lti import Lti
import xferfcn
-from lti import Lti
class StateSpace(Lti):
- """The StateSpace class is used throughout the python-control library to
- represent systems in state space form. This class is derived from the Lti2
- base class."""
+ """The StateSpace class represents state space instances and functions.
+
+ The StateSpace class is used throughout the python-control library to
+ represent systems in state space form. This class is derived from the Lti
+ base class.
+
+ 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).
+
+ """
+
def __init__(self, A=0, B=0, C=0, D=1):
- """StateSpace constructor. The default constructor is the unit gain
- direct feedthrough system."""
+ """Construct a state space object. The default is unit static gain."""
# Here we're going to convert inputs to matrices, if the user gave a
# non-matrix type.
matrices = [A, B, C, D]
for i in range(len(matrices)):
# Convert to matrix first, if necessary.
- matrices[i] = sp.matrix(matrices[i])
+ matrices[i] = matrix(matrices[i])
[A, B, C, D] = matrices
self.A = A
@@ -85,7 +122,7 @@
raise ValueError("D must have the same row size as C.")
def __str__(self):
- """Style to use for printing."""
+ """String representation of the state space."""
str = "A = " + self.A.__str__() + "\n\n"
str += "B = " + self.B.__str__() + "\n\n"
@@ -101,17 +138,18 @@
# Addition of two transfer functions (parallel interconnection)
def __add__(self, other):
- """Add two state space systems."""
+ """Add two LTI systems (parallel connection)."""
# Check for a couple of special cases
if (isinstance(other, (int, long, float, complex))):
# Just adding a scalar; put it in the D matrix
A, B, C = self.A, self.B, self.C;
D = self.D + other;
+ else:
+ other = convertToStateSpace(other)
- else:
# Check to make sure the dimensions are OK
- if ((self.inputs != other.inputs) or \
+ if ((self.inputs != other.inputs) or
(self.outputs != other.outputs)):
raise ValueError, "Systems have different shapes."
@@ -125,52 +163,59 @@
B = concatenate((self.B, other.B), axis=0)
C = concatenate((self.C, other.C), axis=1)
D = self.D + other.D
+
return StateSpace(A, B, C, D)
# Reverse addition - just switch the arguments
def __radd__(self, other):
- """Add two state space systems."""
+ """Reverse add two LTI systems (parallel connection)."""
- return self.__add__(other)
+ return self + other
# Subtraction of two transfer functions (parallel interconnection)
def __sub__(self, other):
- """Subtract two state space systems."""
+ """Subtract two LTI systems."""
- return self.__add__(-other)
+ return self + (-other)
+ def __rsub__(self, other):
+ """Reverse subtract two LTI systems."""
+
+ return other + (-self)
+
# Multiplication of two transfer functions (series interconnection)
def __mul__(self, other):
- """Serial interconnection between two state space systems."""
+ """Multiply two LTI objects (serial connection)."""
# Check for a couple of special cases
if isinstance(other, (int, long, float, complex)):
# Just multiplying by a scalar; change the output
- A, B = self.A, self.B;
- C = self.C * other;
- D = self.D * other;
+ A, B = self.A, self.B
+ C = self.C * other
+ D = self.D * other
else:
- # Check to make sure the dimensions are OK
- if (self.outputs != other.inputs):
- raise ValueError, "Number of first's outputs must match number \
-of second's inputs."
+ other = convertToStateSpace(other)
- # Concatenate the various arrays
- A = concatenate(
- (concatenate((other.A, zeros((other.A.shape[0], self.A.shape[1]))),
- axis=1),
- concatenate((self.B * other.C, self.A), axis=1)),
- axis=0)
- B = concatenate((other.B, self.B * other.D), axis=0)
- C = concatenate((self.D * other.C, self.C),axis=1)
- D = self.D * other.D
+ # Check to make sure the dimensions are OK
+ if self.inputs != other.outputs:
+ raise ValueError("C = A * B: A has %i column(s) (input(s)), \
+but B has %i row(s)\n(output(s))." % (self.inputs, other.outputs))
+ # Concatenate the various arrays
+ A = concatenate(
+ (concatenate((other.A, zeros((other.A.shape[0], self.A.shape[1]))),
+ axis=1),
+ concatenate((self.B * other.C, self.A), axis=1)), axis=0)
+ B = concatenate((other.B, self.B * other.D), axis=0)
+ C = concatenate((self.D * other.C, self.C),axis=1)
+ D = self.D * other.D
+
return StateSpace(A, B, C, D)
# Reverse multiplication of two transfer functions (series interconnection)
# Just need to convert LH argument to a state space object
def __rmul__(self, other):
- """Serial interconnection between two state space systems"""
+ """Reverse multiply two LTI objects (serial connection)."""
# Check for a couple of special cases
if isinstance(other, (int, long, float, complex)):
@@ -183,69 +228,88 @@
else:
raise TypeError("can't interconnect systems")
- # Compute poles and zeros
- def pole(self):
- """Compute the poles of a state space system."""
+ # TODO: __div__ and __rdiv__ are not written yet.
+ def __div__(self, other):
+ """Divide two LTI systems."""
- return sp.roots(sp.poly(self.A))
+ raise NotImplementedError("StateSpace.__div__ is not implemented yet.")
- def zero(self):
- """Compute the zeros of a state space system."""
+ def __rdiv__(self, other):
+ """Reverse divide two LTI systems."""
- if self.inputs > 1 or self.outputs > 1:
- raise NotImplementedError("StateSpace.zeros is currently \
-implemented only for SISO systems.")
+ raise NotImplementedError("StateSpace.__rdiv__ is not implemented yet.")
- den = sp.poly1d(sp.poly(self.A))
- # Compute the numerator based on zeros
- #! TODO: This is currently limited to SISO systems
- num = sp.poly1d(\
- sp.poly(self.A - sp.dot(self.B, self.C)) + (self.D[0, 0] - 1) * den)
+ def evalfr(self, omega):
+ """Evaluate a SS system's transfer function at a single frequency.
- return (sp.roots(num))
+ self.evalfr(omega) returns the value of the transfer function matrix with
+ input value s = i * omega.
- def evalfr(self, freq):
- """Method for evaluating a system at one frequency."""
+ """
- fresp = self.C * solve(freq * 1.j * sp.eye(self.states) - self.A,
+ fresp = self.C * solve(omega * 1.j * eye(self.states) - self.A,
self.B) + self.D
+
return fresp
# Method for generating the frequency response of the system
def freqresp(self, omega=None):
- """Compute the response of a system to a list of frequencies."""
+ """Evaluate the system's transfer func. at a list of ang. frequencies.
+
+ mag, phase, omega = self.freqresp(omega)
+
+ reports the value of the magnitude, phase, and angular frequency of the
+ system's transfer function matrix evaluated at s = i * omega, where
+ omega is a list of angular frequencies.
+
+ """
# Preallocate outputs.
numfreq = len(omega)
- mag = sp.empty((self.outputs, self.inputs, numfreq))
- phase = sp.empty((self.outputs, self.inputs, numfreq))
- fresp = sp.empty((self.outputs, self.inputs, numfreq), dtype=complex)
+ mag = empty((self.outputs, self.inputs, numfreq))
+ phase = empty((self.outputs, self.inputs, numfreq))
+ fresp = empty((self.outputs, self.inputs, numfreq), dtype=complex)
for k in range(numfreq):
fresp[:, :, k] = self.evalfr(omega[k])
mag = abs(fresp)
- phase = sp.angle(fresp)
+ phase = angle(fresp)
return mag, phase, omega
+ # Compute poles and zeros
+ def pole(self):
+ """Compute the poles of a state space system."""
+
+ return roots(poly(self.A))
+
+ def zero(self):
+ """Compute the zeros of a state space system."""
+
+ if self.inputs > 1 or self.outputs > 1:
+ raise NotImplementedError("StateSpace.zeros is currently \
+implemented only for SISO systems.")
+
+ den = poly1d(poly(self.A))
+ # Compute the numerator based on zeros
+ #! TODO: This is currently limited to SISO systems
+ num = poly1d(\
+ poly(self.A - dot(self.B, self.C)) + (self.D[0, 0] - 1) * den)
+
+ return (roots(num))
+
# Feedback around a state space system
def feedback(self, other, sign=-1):
- """Feedback interconnection between two state space systems."""
-
- # Check for special cases
- if (isinstance(other, (int, long, float, complex))):
- # Scalar feedback, create state space system that is this case
- other = StateSpace([[0]], [[0]], [[0]], [[ other ]])
+ """Feedback interconnection between two LTI systems."""
+
+ other = convertToStateSpace(other)
# Check to make sure the dimensions are OK
if ((self.inputs != other.outputs) or (self.outputs != other.inputs)):
raise ValueError, "State space systems don't have compatible \
inputs/outputs for feedback."
- from numpy.linalg import inv, det
- from numpy import eye
-
A1 = self.A
B1 = self.B
C1 = self.C
@@ -276,17 +340,16 @@
return StateSpace(A, B, C, D)
def returnScipySignalLti(self):
- """Return a list of a list of scipy.signal.lti objects for a MIMO
- system. For instance,
+ """Return a list of a list of scipy.signal.lti objects.
+ For instance,
+
>>> out = ssobject.returnScipySignalLti()
>>> out[3][5]
is a signal.scipy.lti object corresponding to the transfer function from
the 6th input to the 4th output."""
- from scipy.signal import lti
-
# Preallocate the output.
out = [[[] for j in range(self.inputs)] for i in range(self.outputs)]
@@ -298,31 +361,39 @@
return out
def convertToStateSpace(sys, inputs=1, outputs=1):
- """Convert a system to state space form (if needed). If sys is a scalar,
- then the number of inputs and outputs can be specified manually."""
+ """Convert a system to state space form (if needed).
+
+ If sys is already a state space, then it is returned. If sys is a transfer
+ function object, then it is converted to a state space and returned. If sys
+ is a scalar, then the number of inputs and outputs can be specified
+ manually.
+ """
+
if isinstance(sys, StateSpace):
# Already a state space system; just return it
return sys
elif isinstance(sys, xferfcn.TransferFunction):
+ # TODO: Wrap SLICOT to do transfer function to state space conversion.
raise NotImplementedError("Transfer function to state space conversion \
is not implemented yet.")
elif (isinstance(sys, (int, long, float, complex))):
# Generate a simple state space system of the desired dimension
# The following Doesn't work due to inconsistencies in ltisys:
- # return StateSpace([[]], [[]], [[]], sp.eye(outputs, inputs))
+ # return StateSpace([[]], [[]], [[]], eye(outputs, inputs))
return StateSpace(0., zeros((1, inputs)), zeros((outputs, 1)),
- sys * sp.eye(outputs, inputs))
+ sys * eye(outputs, inputs))
else:
raise TypeError("Can't convert given type to StateSpace system.")
def rss_generate(states, inputs, outputs, type):
- """This does the actual random state space generation expected from rss and
- drss. type is 'c' for continuous systems and 'd' for discrete systems."""
-
- import numpy
- from numpy.random import rand, randn
+ """Generate a random state space.
+ This does the actual random state space generation expected from rss and
+ drss. type is 'c' for continuous systems and 'd' for discrete systems.
+
+ """
+
# Probability of repeating a previous root.
pRepeat = 0.05
# Probability of choosing a real root. Note that when choosing a complex
@@ -337,7 +408,7 @@
pDzero = 0.5
# Make some poles for A. Preallocate a complex array.
- poles = numpy.zeros(states) + numpy.zeros(states) * 0.j
+ poles = zeros(states) + zeros(states) * 0.j
i = 0
while i < states:
@@ -355,24 +426,24 @@
elif rand() < pReal or i == states - 1:
# No-oscillation pole.
if type == 'c':
- poles[i] = -sp.exp(randn()) + 0.j
+ poles[i] = -exp(randn()) + 0.j
elif type == 'd':
poles[i] = 2. * rand() - 1.
i += 1
else:
# Complex conjugate pair of oscillating poles.
if type == 'c':
- poles[i] = complex(-sp.exp(randn()), 3. * sp.exp(randn()))
+ poles[i] = complex(-exp(randn()), 3. * exp(randn()))
elif type == 'd':
mag = rand()
- phase = 2. * numpy.pi * rand()
- poles[i] = complex(mag * numpy.cos(phase),
- mag * numpy.sin(phase))
+ phase = 2. * pi * rand()
+ poles[i] = complex(mag * cos(phase),
+ mag * sin(phase))
poles[i+1] = complex(poles[i].real, -poles[i].imag)
i += 2
# Now put the poles in A as real blocks on the diagonal.
- A = numpy.zeros((states, states))
+ A = zeros((states, states))
i = 0
while i < states:
if poles[i].imag == 0:
@@ -387,9 +458,9 @@
while True:
T = randn(states, states)
try:
- A = numpy.dot(solve(T, A), T) # A = T \ A * T
+ A = dot(solve(T, A), T) # A = T \ A * T
break
- except numpy.linalg.linalg.LinAlgError:
+ except LinAlgError:
# In the unlikely event that T is rank-deficient, iterate again.
pass
@@ -401,14 +472,14 @@
# Make masks to zero out some of the elements.
while True:
Bmask = rand(states, inputs) < pBCmask
- if sp.any(Bmask): # Retry if we get all zeros.
+ if any(Bmask): # Retry if we get all zeros.
break
while True:
Cmask = rand(outputs, states) < pBCmask
- if sp.any(Cmask): # Retry if we get all zeros.
+ if any(Cmask): # Retry if we get all zeros.
break
if rand() < pDzero:
- Dmask = numpy.zeros((outputs, inputs))
+ Dmask = zeros((outputs, inputs))
else:
Dmask = rand(outputs, inputs) < pDmask
Modified: branches/control-0.4a/src/xferfcn.py
===================================================================
--- branches/control-0.4a/src/xferfcn.py 2011-02-08 22:15:45 UTC (rev 74)
+++ branches/control-0.4a/src/xferfcn.py 2011-02-08 22:15:51 UTC (rev 75)
@@ -1,89 +1,124 @@
-# xferfcn.py - transfer function class and functions
-#
-# Author: Richard M. Murray
-# Date: 24 May 09
-#
-# This file contains the TransferFunction class and also functions
-# that operate on transfer functions. This class extends the
-# signal.lti class by adding some additional useful functions like
-# block diagram algebra.
-#
-# NOTE: Transfer function in this module are restricted to be SISO
-# systems. To perform calcualtiosn on MIMO systems, you need to use
-# the state space module (statesp.py).
-#
-# Copyright (c) 2010 by California Institute of Technology
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# 1. Redistributions of source code must retain the above copyright
-# notice, this list of conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright
-# notice, this list of conditions and the following disclaimer in the
-# documentation and/or other materials provided with the distribution.
-#
-# 3. Neither the name of the California Institute of Technology nor
-# the names of its contributors may be used to endorse or promote
-# products derived from this software without specific prior
-# written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
-# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
-# OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
-# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
-# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
-# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
-# SUCH DAMAGE.
-#
-# $Id: xferfcn.py 21 2010-06-06 17:29:42Z murrayrm $
+"""xferfcn.py
+Transfer function representation and functions.
+
+This file contains the TransferFunction class and also functions
+that operate on transfer functions. This is the primary representation
+for the python-control library.
+
+Routines in this module:
+
+TransferFunction.__init__
+TransferFunction._truncatecoeff
+TransferFunction.__str__
+TransferFunction.__neg__
+TransferFunction.__add__
+TransferFunction.__radd__
+TransferFunction.__sub__
+TransferFunction.__rsub__
+TransferFunction.__mul__
+TransferFunction.__rmul__
+TransferFunction.__div__
+TransferFunction.__rdiv__
+TransferFunction.evalfr
+TransferFunction.freqresp
+TransferFunction.pole
+TransferFunction.zero
+TransferFunction.feedback
+TransferFunction.returnScipySignalLti
+_tfpolyToString
+_addSISO
+convertToTransferFunction
+
+Copyright (c) 2010 by California Institute of Technology
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the California Institute of Technology nor
+ the names of its contributors may be used to endorse or promote
+ products derived from this software without specific prior
+ written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CALTECH
+OR THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
+
+Author: Richard M. Murray
+Date: 24 May 09
+Revised: Kevin K. Chewn, Dec 10
+
+$Id: xferfcn.py 21 2010-06-06 17:29:42Z murrayrm $
+
+"""
+
# External function declarations
-import scipy as sp
+from numpy import angle, array, empty, ndarray, ones, polyadd, polymul, \
+ polyval, zeros
+from scipy.signal import lti
+from copy import deepcopy
from lti import Lti
+import statesp
class TransferFunction(Lti):
- """The TransferFunction class is derived from the Lti2 parent class. The
- main data members are 'num' and 'den', which are 2-D lists of arrays
+
+ """The TransferFunction class represents TF instances and functions.
+
+ The TransferFunction class is derived from the Lti parent class. It is used
+ throught the python-control library to represent systems in transfer
+ function form.
+
+ The main data members are 'num' and 'den', which 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."""
+ 3rd output is set to s^2 + 4s + 8.
+ """
+
def __init__(self, num=1, den=1):
- """This is the constructor. The default transfer function is 1 (unit
- gain direct feedthrough)."""
-
+ """Construct a transfer function. The default is unit static gain."""
+
# Make num and den into lists of lists of arrays, if necessary. Beware:
# this is a shallow copy! This should be okay, but be careful.
data = [num, den]
for i in range(len(data)):
if isinstance(data[i], (int, float, long, complex)):
# Convert scalar to list of list of array.
- data[i] = [[sp.array([data[i]])]]
- elif isinstance(data[i], (list, tuple, sp.ndarray)) and \
+ data[i] = [[array([data[i]])]]
+ elif isinstance(data[i], (list, tuple, ndarray)) and \
isinstance(data[i][0], (int, float, long, complex)):
# Convert array to list of list of array.
- data[i] = [[sp.array(data[i])]]
+ data[i] = [[array(data[i])]]
elif isinstance(data[i], list) and \
isinstance(data[i][0], list) and \
- isinstance(data[i][0][0], (list, tuple, sp.ndarray)) and \
+ isinstance(data[i][0][0], (list, tuple, ndarray)) and \
isinstance(data[i][0][0][0], (int, float, long, complex)):
# We might already have the right format. Convert the
# coefficient vectors to arrays, if necessary.
for j in range(len(data[i])):
for k in range(len(data[i][j])):
- data[i][j][k] = sp.array(data[i][j][k])
+ data[i][j][k] = array(data[i][j][k])
else:
# If the user passed in anything else, then it's unclear what
# the meaning is.
@@ -134,7 +169,7 @@
zeronum = False
break
if zeronum:
- den[i][j] = sp.ones(1)
+ den[i][j] = ones(1)
self.num = num
self.den = den
@@ -142,6 +177,35 @@
self._truncatecoeff()
+ def _truncatecoeff(self):
+ """Remove extraneous zero coefficients from num and den.
+
+ Check every element of the numerator and denominator matrices, and
+ truncate leading zeros. For instance, running self._truncatecoeff()
+ will reduce self.num = [[[0, 0, 1, 2]]] to [[[1, 2]]].
+
+ """
+
+ # Beware: this is a shallow copy. This should be okay.
+ data = [self.num, self.den]
+ for p in range(len(data)):
+ for i in range(self.outputs):
+ for j in range(self.inputs):
+ # Find the first nontrivial coefficient.
+ nonzero = None
+ for k in range(data[p][i][j].size):
+ if data[p][i][j][k]:
+ nonzero = k
+ break
+
+ if nonzero is None:
+ # The array is all zeros.
+ data[p][i][j] = zeros(1)
+ else:
+ # Truncate the trivial coefficients.
+ data[p][i][j] = data[p][i][j][nonzero:]
+ [self.num, self.den] = data
+
def __str__(self):
"""String representation of the transfer function."""
@@ -174,36 +238,10 @@
outstr += "\n" + numstr + "\n" + dashes + "\n" + denstr + "\n"
return outstr
- def _truncatecoeff(self):
- """Remove extraneous zero coefficients from polynomials in numerator and
- denominator matrices."""
-
- # Beware: this is a shallow copy. This should be okay.
- data = [self.num, self.den]
- for p in range(len(data)):
- for i in range(self.outputs):
- for j in range(self.inputs):
- # Find the first nontrivial coefficient.
- nonzero = None
- for k in range(data[p][i][j].size):
- if data[p][i][j][k]:
- nonzero = k
- break
-
- if nonzero is None:
- # The array is all zeros.
- data[p][i][j] = sp.zeros(1)
- else:
- # Truncate the trivial coefficients.
- data[p][i][j] = data[p][i][j][nonzero:]
- [self.num, self.den] = data
-
def __neg__(self):
"""Negate a transfer function."""
- import copy
-
- num = copy.deepcopy(self.num)
+ num = deepcopy(self.num)
for i in range(self.outputs):
for j in range(self.inputs):
num[i][j] *= -1
@@ -211,7 +249,7 @@
return TransferFunction(num, self.den)
def __add__(self, other):
- """Add two transfer functions (parallel connection)."""
+ """Add two LTI objects (parallel connection)."""
# Convert the second argument to a transfer function.
if not isinstance(other, TransferFunction):
@@ -237,26 +275,28 @@
return TransferFunction(num, den)
def __radd__(self, other):
- """Add two transfer functions (parallel connection)."""
+ """Reverse add two LTI objects (parallel connection)."""
return self + other;
def __sub__(self, other):
- """Subtract two transfer functions."""
+ """Subtract two LTI objects."""
return self + (-other)
def __rsub__(self, other):
- """Subtract two transfer functions."""
+ """Reverse subtract two LTI objects."""
return other + (-self)
def __mul__(self, other):
- """Multiply two transfer functions (serial connection)."""
+ """Multiply two LTI objects (serial connection)."""
# Convert the second argument to a transfer function.
- if not isinstance(other, TransferFunction):
+ if isinstance(other, (int, float, long, complex)):
other = convertToTransferFunction(other, self.inputs, self.inputs)
+ else:
+ other = convertToTransferFunction(other)
# Check that the input-output sizes are consistent.
if self.inputs != other.outputs:
@@ -278,21 +318,21 @@
for i in range(outputs): # Iterate through rows of product.
for j in range(inputs): # Iterate through columns of product.
for k in range(self.inputs): # Multiply & add.
- num_summand[k] = sp.polymul(self.num[i][k], other.num[k][j])
- den_summand[k] = sp.polymul(self.den[i][k], other.den[k][j])
+ num_summand[k] = polymul(self.num[i][k], other.num[k][j])
+ den_summand[k] = polymul(self.den[i][k], other.den[k][j])
num[i][j], den[i][j] = _addSISO(num[i][j], den[i][j],
num_summand[k], den_summand[k])
return TransferFunction(num, den)
def __rmul__(self, other):
- """Multiply two transfer functions (serial connection)."""
+ """Reverse multiply two LTI objects (serial connection)."""
return self * other
- # TODO: Division of MIMO transfer function objects is quite difficult.
+ # TODO: Division of MIMO transfer function objects is not written yet.
def __div__(self, other):
- """Divide two transfer functions."""
+ """Divide two LTI objects."""
if self.inputs > 1 or self.outputs > 1 or \
other.inputs > 1 or other.outputs > 1:
@@ -300,17 +340,16 @@
implemented only for SISO systems.")
# Convert the second argument to a transfer function.
- if not isinstance(other, TransferFunction):
- other = convertToTransferFunction(other, 1, 1)
+ other = convertToTransferFunction(other)
- num = sp.polymul(self.num[0][0], other.den[0][0])
- den = sp.polymul(self.den[0][0], other.num[0][0])
+ num = polymul(self.num[0][0], other.den[0][0])
+ den = polymul(self.den[0][0], other.num[0][0])
return TransferFunction(num, den)
- # TODO: Division of MIMO transfer function objects is quite difficult.
+ # TODO: Division of MIMO transfer function objects is not written yet.
def __rdiv__(self, other):
- """Reverse divide two transfer functions."""
+ """Reverse divide two LTI objects."""
if self.inputs > 1 or self.outputs > 1 or \
other.inputs > 1 or other.outputs > 1:
@@ -319,56 +358,70 @@
return other / self
- def evalfr(self, freq):
- """Evaluate a transfer function at a single frequency."""
+ def evalfr(self, omega):
+ """Evaluate a transfer function...
[truncated message content] |