From: <kk...@us...> - 2011-02-08 22:14:28
|
Revision: 57 http://python-control.svn.sourceforge.net/python-control/?rev=57&view=rev Author: kkchen Date: 2011-02-08 22:14:22 +0000 (Tue, 08 Feb 2011) Log Message: ----------- Added tests to TestXferFcn.py. Changes in xferfcn.py: - Shallow copy bug fix in xTransferFunction.__neg__ - Coefficient vectors are now converted to numpy arrays. - Simplification of xTrasferFunction.__rdiv__ Kevin K. Chen <kk...@pr...> Modified Paths: -------------- branches/control-0.4a/src/TestXferFcn.py branches/control-0.4a/src/xferfcn.py Modified: branches/control-0.4a/src/TestXferFcn.py =================================================================== --- branches/control-0.4a/src/TestXferFcn.py 2011-02-08 22:14:17 UTC (rev 56) +++ branches/control-0.4a/src/TestXferFcn.py 2011-02-08 22:14:22 UTC (rev 57) @@ -6,7 +6,8 @@ class TestXferFcn(unittest.TestCase): """These are tests for functionality and correct reporting of the transfer - function class.""" + function class. Throughout these tests, we will give different input + formats to the xTranferFunction constructor, to try to break it.""" def testTruncateCoeff(self): """Remove extraneous zeros in polynomial representations.""" @@ -16,23 +17,58 @@ np.testing.assert_array_equal(sys1.num, [[[1., 2.]]]) np.testing.assert_array_equal(sys1.den, [[[3., 2., 1.]]]) - def testAddSISO1(self): + def testNegScalar(self): + """Negate a direct feedthrough system.""" + + sys1 = xTransferFunction(2., np.array([-3])) + sys2 = - sys1 + + np.testing.assert_array_equal(sys2.num, [[[-2.]]]) + np.testing.assert_array_equal(sys2.den, [[[-3.]]]) + + def testNegSISO(self): + """Negate a SISO system.""" + + sys1 = xTransferFunction([1., 3., 5], [1., 6., 2., -1.]) + sys2 = - sys1 + + np.testing.assert_array_equal(sys2.num, [[[-1., -3., -5.]]]) + np.testing.assert_array_equal(sys2.den, [[[1., 6., 2., -1.]]]) + + def testNegMIMO(self): + """Negate a MIMO system.""" + + num1 = [[[1., 2.], [0., 3.], [2., -1.]], + [[1.], [4., 0.], [1., -4., 3.]]] + num3 = [[[-1., -2.], [0., -3.], [-2., 1.]], + [[-1.], [-4., 0.], [-1., 4., -3.]]] + den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]], + [[3., 0., .0], [2., -1., -1.], [1.]]] + + sys1 = xTransferFunction(num1, den1) + sys2 = - sys1 + sys3 = xTransferFunction(num3, den1) + + for i in range(sys3.outputs): + for j in range(sys3.inputs): + np.testing.assert_array_equal(sys2.num[i][j], sys3.num[i][j]) + np.testing.assert_array_equal(sys2.den[i][j], sys3.den[i][j]) + + def testAddScalar(self): """Add two direct feedthrough systems.""" - # Try different input formats, too. sys1 = xTransferFunction(1., [[[1.]]]) - sys2 = xTransferFunction([2.], [1.]) + sys2 = xTransferFunction(np.array([2.]), [1.]) sys3 = sys1 + sys2 np.testing.assert_array_equal(sys3.num, 3.) np.testing.assert_array_equal(sys3.den, 1.) - def testAddSISO2(self): + def testAddSISO(self): """Add two SISO systems.""" - # Try different input formats, too. sys1 = xTransferFunction([1., 3., 5], [1., 6., 2., -1]) - sys2 = xTransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]]) + sys2 = xTransferFunction([[np.array([-1., 3.])]], [[[1., 0., -1.]]]) sys3 = sys1 + sys2 # If sys3.num is [[[0., 20., 4., -8.]]], then this is wrong! @@ -63,16 +99,133 @@ for j in range(sys3.inputs): np.testing.assert_array_equal(sys3.num[i][j], num3[i][j]) np.testing.assert_array_equal(sys3.den[i][j], den3[i][j]) + + def testSubScalar(self): + """Add two direct feedthrough systems.""" + + sys1 = xTransferFunction(1., [[[1.]]]) + sys2 = xTransferFunction(np.array([2.]), [1.]) + sys3 = sys1 - sys2 + + np.testing.assert_array_equal(sys3.num, -1.) + np.testing.assert_array_equal(sys3.den, 1.) + + def testSubSISO(self): + """Add two SISO systems.""" + + sys1 = xTransferFunction([1., 3., 5], [1., 6., 2., -1]) + sys2 = xTransferFunction([[np.array([-1., 3.])]], [[[1., 0., -1.]]]) + sys3 = sys1 - sys2 + sys4 = sys2 - sys1 + + np.testing.assert_array_equal(sys3.num, [[[2., 6., -12., -10., -2.]]]) + np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]]) + np.testing.assert_array_equal(sys4.num, [[[-2., -6., 12., 10., 2.]]]) + np.testing.assert_array_equal(sys4.den, [[[1., 6., 1., -7., -2., 1.]]]) + + def testSubMIMO(self): + """Add two MIMO systems.""" + + num1 = [[[1., 2.], [0., 3.], [2., -1.]], + [[1.], [4., 0.], [1., -4., 3.]]] + den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]], + [[3., 0., .0], [2., -1., -1.], [1.]]] + num2 = [[[0., 0., -1], [2.], [-1., -1.]], + [[1., 2.], [-1., -2.], [4.]]] + den2 = [[[-1.], [1., 2., 3.], [-1., -1.]], + [[-4., -3., 2.], [0., 1.], [1., 0.]]] + num3 = [[[3., -3., -6], [5., 6., 9.], [-4., -2., 2]], + [[3., 2., -3., 2], [-2., -3., 7., 2.], [1., -4., 3., 4]]] + den3 = [[[3., -2., -4.], [1., 2., 3., 0., 0.], [-2., -1., 1.]], + [[-12., -9., 6., 0., 0.], [2., -1., -1.], [1., 0.]]] - def testMulSISO1(self): + sys1 = xTransferFunction(num1, den1) + sys2 = xTransferFunction(num2, den2) + sys3 = sys1 + sys2 + + for i in range(sys3.outputs): + for j in range(sys3.inputs): + np.testing.assert_array_equal(sys3.num[i][j], num3[i][j]) + np.testing.assert_array_equal(sys3.den[i][j], den3[i][j]) + + def testMulScalar(self): """Multiply two direct feedthrough systems.""" sys1 = xTransferFunction(2., [1.]) sys2 = xTransferFunction(1., 4.) sys3 = sys1 * sys2 + sys4 = sys1 * sys2 np.testing.assert_array_equal(sys3.num, [[[2.]]]) np.testing.assert_array_equal(sys3.den, [[[4.]]]) + np.testing.assert_array_equal(sys3.num, sys4.num) + np.testing.assert_array_equal(sys3.den, sys4.den) + + def testMulSISO(self): + """Multiply two SISO systems.""" + + sys1 = xTransferFunction([1., 3., 5], [1., 6., 2., -1]) + sys2 = xTransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]]) + sys3 = sys1 * sys2 + sys4 = sys2 * sys1 + + np.testing.assert_array_equal(sys3.num, [[[-1., 0., 4., 15.]]]) + np.testing.assert_array_equal(sys3.den, [[[1., 6., 1., -7., -2., 1.]]]) + np.testing.assert_array_equal(sys3.num, sys4.num) + np.testing.assert_array_equal(sys3.den, sys4.den) + + def testMulMIMO(self): + """Multiply two MIMO systems.""" + + num1 = [[[1., 2.], [0., 3.], [2., -1.]], + [[1.], [4., 0.], [1., -4., 3.]]] + den1 = [[[-3., 2., 4.], [1., 0., 0.], [2., -1.]], + [[3., 0., .0], [2., -1., -1.], [1.]]] + num2 = [[[0., 1., 2.]], + [[1., -5.]], + [[-2., 1., 4.]]] + den2 = [[[1., 0., 0., 0.]], + [[-2., 1., 3.]], + [[4., -1., -1., 0.]]] + num3 = [[[-24., 52., -14., 245., -490., -115., 467., -95., -56., 12., + 0., 0., 0.]], + [[24., -132., 138., 345., -768., -106., 510., 41., -79., -69., + -23., 17., 6., 0.]]] + den3 = [[[48., -92., -84., 183., 44., -97., -2., 12., 0., 0., 0., 0., + 0., 0.]], + [[-48., 60., 84., -81., -45., 21., 9., 0., 0., 0., 0., 0., 0.]]] + + sys1 = xTransferFunction(num1, den1) + sys2 = xTransferFunction(num2, den2) + sys3 = sys1 * sys2 + + for i in range(sys3.outputs): + for j in range(sys3.inputs): + np.testing.assert_array_equal(sys3.num[i][j], num3[i][j]) + np.testing.assert_array_equal(sys3.den[i][j], den3[i][j]) + def testDivScalar(self): + """Divide two direct feedthrough systems.""" + + sys1 = xTransferFunction(np.array([3.]), -4.) + sys2 = xTransferFunction(5., 2.) + sys3 = sys1 / sys2 + + np.testing.assert_array_equal(sys3.num, [[[6.]]]) + np.testing.assert_array_equal(sys3.den, [[[-20.]]]) + + def testDivSISO(self): + """Divide two SISO systems.""" + + sys1 = xTransferFunction([1., 3., 5], [1., 6., 2., -1]) + sys2 = xTransferFunction([[[-1., 3.]]], [[[1., 0., -1.]]]) + sys3 = sys1 / sys2 + sys4 = sys2 / sys1 + + np.testing.assert_array_equal(sys3.num, [[[1., 3., 4., -3., -5.]]]) + np.testing.assert_array_equal(sys3.den, [[[-1., -3., 16., 7., -3.]]]) + np.testing.assert_array_equal(sys4.num, sys3.den) + np.testing.assert_array_equal(sys4.den, sys3.num) + if __name__ == "__main__": unittest.main() \ No newline at end of file Modified: branches/control-0.4a/src/xferfcn.py =================================================================== --- branches/control-0.4a/src/xferfcn.py 2011-02-08 22:14:17 UTC (rev 56) +++ branches/control-0.4a/src/xferfcn.py 2011-02-08 22:14:22 UTC (rev 57) @@ -49,6 +49,7 @@ # External function declarations import scipy as sp import scipy.signal as signal +import copy import bdalg as bd import statesp from lti2 import Lti2 @@ -58,7 +59,7 @@ 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] = [1., 4., 8.] + >>> 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.""" @@ -67,27 +68,32 @@ """This is the constructor. The default transfer function is 1 (unit gain direct feedthrough).""" - # Make num and den into lists of lists of arrays, if necessary. + # 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] = [[[data[i]]]] + data[i] = [[sp.array([data[i]])]] elif isinstance(data[i], (list, tuple, sp.ndarray)) and \ isinstance(data[i][0], (int, float, long, complex)): # Convert array to list of list of array. - data[i] = [[data[i]]] - elif isinstance(data[i], (list, tuple, sp.ndarray)) and \ - isinstance(data[i][0], (list, tuple, sp.ndarray)) and \ + data[i] = [[sp.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][0], (int, float, long, complex)): - # We already have the right format. - pass + # 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]) else: # If the user passed in anything else, then it's unclear what # the meaning is. raise ValueError("The numerator and denominator inputs must be \ -scalars or arrays (for\nSISO), or lists of lists of arrays (for MIMO).") +scalars or vectors (for\nSISO), or lists of lists of vectors (for SISO or \ +MIMO).") [num, den] = data inputs = len(num[0]) @@ -166,8 +172,8 @@ """Remove extraneous zero coefficients from polynomials in numerator and denominator matrices.""" + # Beware: this is a shallow copy. data = [self.num, self.den] - for p in range(len(data)): for i in range(self.outputs): for j in range(self.inputs): @@ -177,15 +183,19 @@ k += 1 # Now truncate the trivial coefficients. - data[p][i][j] = data[p][i][j][k:] - + data[p][i][j] = data[p][i][j][k:] [self.num, self.den] = data def __neg__(self): """Negate a transfer function.""" - return xTransferFunction(-self.num, self.den) + num = copy.deepcopy(self.num) + for i in range(self.outputs): + for j in range(self.inputs): + num[i][j] *= -1 + return xTransferFunction(num, self.den) + def __add__(self, other): """Add two transfer functions (parallel connection).""" @@ -285,7 +295,7 @@ return xTransferFunction(num, den) # TODO: Division of MIMO transfer function objects is quite difficult. - def __rdiv__(self, sys): + def __rdiv__(self, other): """Reverse divide two transfer functions""" if self.inputs > 1 or self.outputs > 1 or \ @@ -293,15 +303,8 @@ raise NotImplementedError("xTransferFunction.__rdiv__ is currently \ implemented only for SISO systems.") - # Convert the second argument to a transfer function. - if not isinstance(other, xTransferFunction): - other = ss2tf(other) - - num = sp.polymul(self.den[0][0], other.num[0][0]) - den = sp.polymul(self.num[0][0], other.den[0][0]) + return other / self - return xTransferFunction(num, den) - def evalfr(self, freq): """Evaluate a transfer function at a single frequency""" @@ -329,6 +332,9 @@ pass +# This is the old TransferFunction class. It will be superceded by the +# xTransferFunction class (which will be renamed TransferFunction) when it is +# completed. class TransferFunction(signal.lti): """The TransferFunction class is used to represent linear input/output systems via its transfer function. This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |