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] |