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