diff --git a/.gitignore b/.gitignore index c63a6cf06..0262ab46f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ __conda_*.txt record.txt build.log *.egg-info/ +.eggs/ .coverage doc/_build doc/generated @@ -18,3 +19,7 @@ examples/.ipynb_checkpoints/ .project Untitled*.ipynb *.idea/ + +# Files created by or for emacs (RMM, 29 Dec 2017) +*~ +TAGS diff --git a/control/tests/input_element_int_test.py b/control/tests/input_element_int_test.py new file mode 100644 index 000000000..c6a6f64a3 --- /dev/null +++ b/control/tests/input_element_int_test.py @@ -0,0 +1,54 @@ +# input_element_int_test.py +# +# Author: Kangwon Lee (kangwonlee) +# Date: 22 Oct 2017 +# +# Unit tests contributed as part of PR #158, "SISO tf() may not work +# with numpy arrays with numpy.int elements" +# +# Modified: +# * 29 Dec 2017, RMM - updated file name and added header + +import unittest +import numpy as np +import control as ctl + +class TestTfInputIntElement(unittest.TestCase): + # currently these do not pass + def test_tf_den_with_numpy_int_element(self): + num = 1 + den = np.convolve([1, 2, 1], [1, 1, 1]) + + sys = ctl.tf(num, den) + + self.assertAlmostEqual(1.0, ctl.dcgain(sys)) + + def test_tf_num_with_numpy_int_element(self): + num = np.convolve([1], [1, 1]) + den = np.convolve([1, 2, 1], [1, 1, 1]) + + sys = ctl.tf(num, den) + + self.assertAlmostEqual(1.0, ctl.dcgain(sys)) + + # currently these pass + def test_tf_input_with_int_element_works(self): + num = 1 + den = np.convolve([1.0, 2, 1], [1, 1, 1]) + + sys = ctl.tf(num, den) + + self.assertAlmostEqual(1.0, ctl.dcgain(sys)) + + def test_ss_input_with_int_element(self): + ident = np.matrix(np.identity(2), dtype=int) + a = np.matrix([[0, 1], + [-1, -2]], dtype=int) * ident + b = np.matrix([[0], + [1]], dtype=int) + c = np.matrix([[0, 1]], dtype=int) + d = 0 + + sys = ctl.ss(a, b, c, d) + sys2 = ctl.ss2tf(sys) + self.assertAlmostEqual(ctl.dcgain(sys), ctl.dcgain(sys2)) diff --git a/control/tests/xferfcn_input_test.py b/control/tests/xferfcn_input_test.py new file mode 100644 index 000000000..acddc64ae --- /dev/null +++ b/control/tests/xferfcn_input_test.py @@ -0,0 +1,261 @@ +#!/usr/bin/env python +# +# xferfcn_input_test.py - test inputs to TransferFunction class +# jed-frey, 18 Feb 2017 (based on xferfcn_test.py) + +import unittest +import numpy as np + +from numpy import int, int8, int16, int32, int64 +from numpy import float, float16, float32, float64, float128 +from numpy import all, ndarray, array + +from control.xferfcn import _cleanPart + +class TestXferFcnInput(unittest.TestCase): + """These are tests for functionality of cleaning and validating + XferFucnInput.""" + + # Tests for raising exceptions. + def testBadInputType(self): + """Give the part cleaner invalid input type.""" + + self.assertRaises(TypeError, _cleanPart, [[0., 1.], [2., 3.]]) + + def testBadInputType2(self): + """Give the part cleaner another invalid input type.""" + self.assertRaises(TypeError, _cleanPart, [1,"a"]) + + def testScalar(self): + """Test single scalar value.""" + num = 1 + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testListScalar(self): + """Test single scalar value in list.""" + num = [1] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testTupleScalar(self): + """Test single scalar value in tuple.""" + num = (1) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testList(self): + """Test multiple values in a list.""" + num = [1, 2] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 2.0], dtype=float)) + + def testTuple(self): + """Test multiple values in tuple.""" + num = (1, 2) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 2.0], dtype=float)) + + def testAllScalarTypes(self): + """Test single scalar value for all valid data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = dtype(1) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testNpArray(self): + """Test multiple values in numpy array.""" + num = np.array([1, 2]) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 2.0], dtype=float)) + + def testAllNumpyArrayTypes(self): + """Test scalar value in numpy array of ndim=0 for all data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = np.array(1, dtype=dtype) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testAllNumpyArrayTypes2(self): + """Test numpy array for all types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = np.array([1, 2], dtype=dtype) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 2.0], dtype=float)) + + def testListAllTypes(self): + """Test list of a single value for all data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = [dtype(1)] + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testListAllTypes2(self): + """List of list of numbers of all data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = [dtype(1), dtype(2)] + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 2.0], dtype=float)) + + def testTupleAllTypes(self): + """Test tuple of a single value for all data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = (dtype(1),) + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testTupleAllTypes2(self): + """Test tuple of a single value for all data types.""" + for dtype in [int, int8, int16, int32, int64, float, float16, float32, float64, float128]: + num = (dtype(1), dtype(2)) + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1, 2], dtype=float)) + + def testListListListInt(self): + """ Test an int in a list of a list of a list.""" + num = [[[1]]] + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testListListListFloat(self): + """ Test a float in a list of a list of a list.""" + num = [[[1.0]]] + num_ = _cleanPart(num) + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0], dtype=float)) + + def testListListListInts(self): + """Test 2 lists of ints in a list in a list.""" + num = [[[1,1],[2,2]]] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testListListListFloats(self): + """Test 2 lists of ints in a list in a list.""" + num = [[[1.0,1.0],[2.0,2.0]]] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testListListArray(self): + """List of list of numpy arrays for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = [[array([1,1], dtype=dtype),array([2,2], dtype=dtype)]] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testTupleListArray(self): + """Tuple of list of numpy arrays for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = ([array([1,1], dtype=dtype),array([2,2], dtype=dtype)],) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testListTupleArray(self): + """List of tuple of numpy array for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = [(array([1,1], dtype=dtype),array([2,2], dtype=dtype))] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testTupleTuplesArrays(self): + """Tuple of tuples of numpy arrays for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = ((array([1,1], dtype=dtype),array([2,2], dtype=dtype)), + (array([3,4], dtype=dtype),array([4,4], dtype=dtype))) + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testListTuplesArrays(self): + """List of tuples of numpy arrays for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = [(array([1,1], dtype=dtype),array([2,2], dtype=dtype)), + (array([3,4], dtype=dtype),array([4,4], dtype=dtype))] + num_ = _cleanPart(num) + + assert isinstance(num_, list) + assert np.all([isinstance(part, list) for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + + def testListListArrays(self): + """List of list of numpy arrays for all valid types.""" + for dtype in int, int8, int16, int32, int64, float, float16, float32, float64, float128: + num = [[array([1,1], dtype=dtype),array([2,2], dtype=dtype)], + [array([3,3], dtype=dtype),array([4,4], dtype=dtype)]] + num_ = _cleanPart(num) + + assert len(num_) == 2 + assert np.all([isinstance(part, list) for part in num_]) + assert np.all([len(part) == 2 for part in num_]) + np.testing.assert_array_equal(num_[0][0], array([1.0, 1.0], dtype=float)) + np.testing.assert_array_equal(num_[0][1], array([2.0, 2.0], dtype=float)) + np.testing.assert_array_equal(num_[1][0], array([3.0, 3.0], dtype=float)) + np.testing.assert_array_equal(num_[1][1], array([4.0, 4.0], dtype=float)) + +def suite(): + return unittest.TestLoader().loadTestsFromTestCase(TestXferFcnInput) + +if __name__ == "__main__": + unittest.main() diff --git a/control/xferfcn.py b/control/xferfcn.py index 0d7d2f417..982b8c5ba 100644 --- a/control/xferfcn.py +++ b/control/xferfcn.py @@ -92,12 +92,12 @@ def __init__(self, *args): The default constructor is TransferFunction(num, den), where num and den are lists of lists of arrays containing polynomial coefficients. - To crete a discrete time transfer funtion, use TransferFunction(num, + To create a discrete time transfer funtion, use TransferFunction(num, den, dt). To call the copy constructor, call TransferFunction(sys), where sys is a TransferFunction object (continuous or discrete). """ - + args = deepcopy(args) if len(args) == 2: # The user provided a numerator and a denominator. (num, den) = args @@ -121,55 +121,13 @@ def __init__(self, *args): raise ValueError("Needs 1, 2 or 3 arguments; received %i." % len(args)) - # 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)): - # Check for a scalar (including 0d ndarray) - if (isinstance(data[i], (int, float, complex, np.number)) or - (isinstance(data[i], ndarray) and data[i].ndim == 0)): - # Convert scalar to list of list of array. - if (isinstance(data[i], int)): - # Convert integers to floats at this point - data[i] = [[array([data[i]], dtype=float)]] - else: - data[i] = [[array([data[i]])]] - elif (isinstance(data[i], (list, tuple, ndarray)) and - isinstance(data[i][0], (int, float, complex, np.number))): - # Convert array to list of list of array. - if (isinstance(data[i][0], int)): - # Convert integers to floats at this point - #! Not sure this covers all cases correctly - data[i] = [[array(data[i], dtype=float)]] - else: - data[i] = [[array(data[i])]] - elif (isinstance(data[i], list) and - isinstance(data[i][0], list) and - isinstance(data[i][0][0], (list, tuple, ndarray)) and - isinstance(data[i][0][0][0], (int, float, complex, - np.number))): - # 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])): - if (isinstance(data[i][j][k], int)): - data[i][j][k] = array(data[i][j][k], dtype=float) - else: - 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. - raise TypeError("The numerator and denominator inputs must be \ -scalars or vectors (for\nSISO), or lists of lists of vectors (for SISO or \ -MIMO).") - [num, den] = data + num = _cleanPart(num) + den = _cleanPart(den) inputs = len(num[0]) outputs = len(num) - # Make sure the numerator and denominator matrices have consistent - # sizes. + # Make sure numerator and denominator matrices have consistent sizes if inputs != len(den[0]): raise ValueError("The numerator has %i input(s), but the \ denominator has %i\ninput(s)." % (inputs, len(den[0]))) @@ -177,8 +135,9 @@ def __init__(self, *args): raise ValueError("The numerator has %i output(s), but the \ denominator has %i\noutput(s)." % (outputs, len(den))) + # Additional checks/updates on structure of the transfer function for i in range(outputs): - # Make sure that each row has the same number of columns. + # Make sure that each row has the same number of columns if len(num[i]) != inputs: raise ValueError("Row 0 of the numerator matrix has %i \ elements, but row %i\nhas %i." % (inputs, i, len(num[i]))) @@ -186,6 +145,7 @@ def __init__(self, *args): raise ValueError("Row 0 of the denominator matrix has %i \ elements, but row %i\nhas %i." % (inputs, i, len(den[i]))) + # Check for zeros in numerator or denominator # TODO: Right now these checks are only done during construction. # It might be worthwhile to think of a way to perform checks if the # user modifies the transfer function after construction. @@ -1358,3 +1318,52 @@ def tfdata(sys): tf = _convertToTransferFunction(sys) return (tf.num, tf.den) + +def _cleanPart(data): + ''' + Return a valid, cleaned up numerator or denominator + for the TransferFunction class. + + Parameters + ---------- + data: numerator or denominator of a transfer function. + + Returns + ------- + data: list of lists of ndarrays, with int converted to float + ''' + valid_types = (int, float, complex, np.number) + valid_collection = (list, tuple, ndarray) + + if (isinstance(data, valid_types) or + (isinstance(data, ndarray) and data.ndim == 0)): + # Data is a scalar (including 0d ndarray) + data = [[array([data])]] + elif (isinstance(data, valid_collection) and + all([isinstance(d, valid_types) for d in data])): + data = [[array(data)]] + elif (isinstance(data, (list, tuple)) and + isinstance(data[0], (list, tuple)) and + (isinstance(data[0][0], valid_collection) and + all([isinstance(d, valid_types) for d in data[0][0]]))): + data = list(data) + for j in range(len(data)): + data[j] = list(data[j]) + for k in range(len(data[j])): + data[j][k] = array(data[j][k]) + else: + # If the user passed in anything else, then it's unclear what + # the meaning is. + raise TypeError("The numerator and denominator inputs must be \ +scalars or vectors (for\nSISO), or lists of lists of vectors (for SISO or \ +MIMO).") + + # Check for coefficients that are ints and convert to floats + for i in range(len(data)): + for j in range(len(data[i])): + for k in range(len(data[i][j])): + if (isinstance(data[i][j][k], (int, np.int))): + data[i][j][k] = float(data[i][j][k]) + + return data +
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: