From 1fc4cc12826ddd05061397509618db064f9ff307 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Thu, 17 Dec 2020 15:10:35 -0800 Subject: [PATCH 1/3] added test needed to pass --- control/tests/bdalg_test.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/control/tests/bdalg_test.py b/control/tests/bdalg_test.py index fde503052..d33007310 100644 --- a/control/tests/bdalg_test.py +++ b/control/tests/bdalg_test.py @@ -9,7 +9,7 @@ import control as ctrl from control.xferfcn import TransferFunction from control.statesp import StateSpace -from control.bdalg import feedback +from control.bdalg import feedback, append, connect from control.lti import zero, pole class TestFeedback(unittest.TestCase): @@ -23,7 +23,9 @@ def setUp(self): # Two random SISO systems. self.sys1 = TransferFunction([1, 2], [1, 2, 3]) self.sys2 = StateSpace([[1., 4.], [3., 2.]], [[1.], [-4.]], - [[1., 0.]], [[0.]]) + [[1., 0.]], [[0.]]) # 2 states, SISO + self.sys3 = StateSpace([[-1.]], [[1.]], [[1.]], [[0.]]) # 1 state, SISO + # Two random scalars. self.x1 = 2.5 self.x2 = -3. @@ -270,6 +272,13 @@ def test_feedback_args(self): sys = ctrl.feedback(1, frd) self.assertTrue(isinstance(sys, ctrl.FRD)) + def testConnect(self): + sys = append(self.sys2, self.sys3) # two siso systems + + # feedback interconnection -3 is out of bounds + Q1 = [[1, 2], [2, -3]] + self.assertRaises(IndexError, connect(sys, Q1, [2], [1, 2])) + if __name__ == "__main__": unittest.main() From 85cc6ca9a7c1fed21202e6e15d462529012cda1b Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Fri, 18 Dec 2020 20:24:51 -0800 Subject: [PATCH 2/3] bdalg.connect: added and fixed index checks to fix #421; docstring updated to indicate that Q matrix can be >2 columns --- control/bdalg.py | 39 +++++++++++++++++++++++++++-------- control/tests/bdalg_test.py | 41 +++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 13 deletions(-) diff --git a/control/bdalg.py b/control/bdalg.py index 3f13fb1b3..7b92245b0 100644 --- a/control/bdalg.py +++ b/control/bdalg.py @@ -302,14 +302,16 @@ def connect(sys, Q, inputv, outputv): sys : StateSpace Transferfunction System to be connected Q : 2D array - Interconnection matrix. First column gives the input to be connected - second column gives the output to be fed into this input. Negative - values for the second column mean the feedback is negative, 0 means - no connection is made. Inputs and outputs are indexed starting at 1. + Interconnection matrix. First column gives the input to be connected. + The second column gives the index of an output that is to be fed into + that input. Each additional column gives the index of an additional + input that may be optionally added to that input. Negative + values mean the feedback is negative. A zero value is ignored. Inputs + and outputs are indexed starting at 1 to communicate sign information. inputv : 1D array - list of final external inputs + list of final external inputs, indexed starting at 1 outputv : 1D array - list of final external outputs + list of final external outputs, indexed starting at 1 Returns ------- @@ -325,15 +327,34 @@ def connect(sys, Q, inputv, outputv): >>> sysc = connect(sys, Q, [2], [1, 2]) """ + inputv, outputv, Q = np.asarray(inputv), np.asarray(outputv), np.asarray(Q) + # check indices + index_errors = (inputv - 1 > sys.inputs) | (inputv < 1) + if np.any(index_errors): + raise IndexError( + "inputv index %s out of bounds"%inputv[np.where(index_errors)]) + index_errors = (outputv - 1 > sys.outputs) | (outputv < 1) + if np.any(index_errors): + raise IndexError( + "outputv index %s out of bounds"%outputv[np.where(index_errors)]) + index_errors = (Q[:,0:1] - 1 > sys.inputs) | (Q[:,0:1] < 1) + if np.any(index_errors): + raise IndexError( + "Q input index %s out of bounds"%Q[np.where(index_errors)]) + index_errors = (np.abs(Q[:,1:]) - 1 > sys.outputs) + if np.any(index_errors): + raise IndexError( + "Q output index %s out of bounds"%Q[np.where(index_errors)]) + # first connect K = np.zeros((sys.inputs, sys.outputs)) for r in np.array(Q).astype(int): inp = r[0]-1 for outp in r[1:]: - if outp > 0 and outp <= sys.outputs: - K[inp,outp-1] = 1. - elif outp < 0 and -outp >= -sys.outputs: + if outp < 0: K[inp,-outp-1] = -1. + elif outp > 0: + K[inp,outp-1] = 1. sys = sys.feedback(np.array(K), sign=1) # now trim diff --git a/control/tests/bdalg_test.py b/control/tests/bdalg_test.py index d33007310..6a3e45eee 100644 --- a/control/tests/bdalg_test.py +++ b/control/tests/bdalg_test.py @@ -275,10 +275,43 @@ def test_feedback_args(self): def testConnect(self): sys = append(self.sys2, self.sys3) # two siso systems - # feedback interconnection -3 is out of bounds - Q1 = [[1, 2], [2, -3]] - self.assertRaises(IndexError, connect(sys, Q1, [2], [1, 2])) - + # feedback interconnection out of bounds: input too high + Q = [[1, 3], [2, -2]] + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 2]) + # feedback interconnection out of bounds: input too low + Q = [[0, 2], [2, -2]] + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 2]) + + # feedback interconnection out of bounds: output too high + Q = [[1, 2], [2, -3]] + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 2]) + Q = [[1, 2], [2, 4]] + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 2]) + + # input/output index testing + Q = [[1, 2], [2, -2]] # OK interconnection + + # input index is out of bounds: too high + with self.assertRaises(IndexError) as context: + connect(sys, Q, [3], [1, 2]) + # input index is out of bounds: too low + with self.assertRaises(IndexError) as context: + connect(sys, Q, [0], [1, 2]) + with self.assertRaises(IndexError) as context: + connect(sys, Q, [-2], [1, 2]) + # output index is out of bounds: too high + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 3]) + # output index is out of bounds: too low + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, 0]) + with self.assertRaises(IndexError) as context: + connect(sys, Q, [2], [1, -1]) + if __name__ == "__main__": unittest.main() From 9c5b5e4ae84f3be094241ea2a6464c29b58665d4 Mon Sep 17 00:00:00 2001 From: Sawyer Fuller Date: Sun, 20 Dec 2020 20:03:55 -0800 Subject: [PATCH 3/3] bdalg.connect: new tests that should pass, stylistic changes --- control/bdalg.py | 32 ++++++++-------- control/tests/bdalg_test.py | 74 +++++++++++++++++++++---------------- 2 files changed, 58 insertions(+), 48 deletions(-) diff --git a/control/bdalg.py b/control/bdalg.py index 7b92245b0..a9ba6cd16 100644 --- a/control/bdalg.py +++ b/control/bdalg.py @@ -303,10 +303,10 @@ def connect(sys, Q, inputv, outputv): System to be connected Q : 2D array Interconnection matrix. First column gives the input to be connected. - The second column gives the index of an output that is to be fed into - that input. Each additional column gives the index of an additional + The second column gives the index of an output that is to be fed into + that input. Each additional column gives the index of an additional input that may be optionally added to that input. Negative - values mean the feedback is negative. A zero value is ignored. Inputs + values mean the feedback is negative. A zero value is ignored. Inputs and outputs are indexed starting at 1 to communicate sign information. inputv : 1D array list of final external inputs, indexed starting at 1 @@ -330,22 +330,22 @@ def connect(sys, Q, inputv, outputv): inputv, outputv, Q = np.asarray(inputv), np.asarray(outputv), np.asarray(Q) # check indices index_errors = (inputv - 1 > sys.inputs) | (inputv < 1) - if np.any(index_errors): - raise IndexError( - "inputv index %s out of bounds"%inputv[np.where(index_errors)]) + if np.any(index_errors): + raise IndexError( + "inputv index %s out of bounds" % inputv[np.where(index_errors)]) index_errors = (outputv - 1 > sys.outputs) | (outputv < 1) - if np.any(index_errors): - raise IndexError( - "outputv index %s out of bounds"%outputv[np.where(index_errors)]) + if np.any(index_errors): + raise IndexError( + "outputv index %s out of bounds" % outputv[np.where(index_errors)]) index_errors = (Q[:,0:1] - 1 > sys.inputs) | (Q[:,0:1] < 1) - if np.any(index_errors): - raise IndexError( - "Q input index %s out of bounds"%Q[np.where(index_errors)]) + if np.any(index_errors): + raise IndexError( + "Q input index %s out of bounds" % Q[np.where(index_errors)]) index_errors = (np.abs(Q[:,1:]) - 1 > sys.outputs) - if np.any(index_errors): - raise IndexError( - "Q output index %s out of bounds"%Q[np.where(index_errors)]) - + if np.any(index_errors): + raise IndexError( + "Q output index %s out of bounds" % Q[np.where(index_errors)]) + # first connect K = np.zeros((sys.inputs, sys.outputs)) for r in np.array(Q).astype(int): diff --git a/control/tests/bdalg_test.py b/control/tests/bdalg_test.py index 6a3e45eee..a7ec6c14b 100644 --- a/control/tests/bdalg_test.py +++ b/control/tests/bdalg_test.py @@ -194,50 +194,50 @@ def testLists(self): sys1_2 = ctrl.series(sys1, sys2) np.testing.assert_array_almost_equal(sort(pole(sys1_2)), [-4., -2.]) np.testing.assert_array_almost_equal(sort(zero(sys1_2)), [-3., -1.]) - + sys1_3 = ctrl.series(sys1, sys2, sys3); np.testing.assert_array_almost_equal(sort(pole(sys1_3)), [-6., -4., -2.]) - np.testing.assert_array_almost_equal(sort(zero(sys1_3)), + np.testing.assert_array_almost_equal(sort(zero(sys1_3)), [-5., -3., -1.]) - + sys1_4 = ctrl.series(sys1, sys2, sys3, sys4); np.testing.assert_array_almost_equal(sort(pole(sys1_4)), [-8., -6., -4., -2.]) np.testing.assert_array_almost_equal(sort(zero(sys1_4)), [-7., -5., -3., -1.]) - + sys1_5 = ctrl.series(sys1, sys2, sys3, sys4, sys5); np.testing.assert_array_almost_equal(sort(pole(sys1_5)), [-8., -6., -4., -2., -0.]) - np.testing.assert_array_almost_equal(sort(zero(sys1_5)), + np.testing.assert_array_almost_equal(sort(zero(sys1_5)), [-9., -7., -5., -3., -1.]) - # Parallel + # Parallel sys1_2 = ctrl.parallel(sys1, sys2) np.testing.assert_array_almost_equal(sort(pole(sys1_2)), [-4., -2.]) np.testing.assert_array_almost_equal(sort(zero(sys1_2)), sort(zero(sys1 + sys2))) - + sys1_3 = ctrl.parallel(sys1, sys2, sys3); np.testing.assert_array_almost_equal(sort(pole(sys1_3)), [-6., -4., -2.]) - np.testing.assert_array_almost_equal(sort(zero(sys1_3)), + np.testing.assert_array_almost_equal(sort(zero(sys1_3)), sort(zero(sys1 + sys2 + sys3))) - + sys1_4 = ctrl.parallel(sys1, sys2, sys3, sys4); np.testing.assert_array_almost_equal(sort(pole(sys1_4)), [-8., -6., -4., -2.]) - np.testing.assert_array_almost_equal(sort(zero(sys1_4)), - sort(zero(sys1 + sys2 + + np.testing.assert_array_almost_equal(sort(zero(sys1_4)), + sort(zero(sys1 + sys2 + sys3 + sys4))) - + sys1_5 = ctrl.parallel(sys1, sys2, sys3, sys4, sys5); np.testing.assert_array_almost_equal(sort(pole(sys1_5)), [-8., -6., -4., -2., -0.]) - np.testing.assert_array_almost_equal(sort(zero(sys1_5)), - sort(zero(sys1 + sys2 + + np.testing.assert_array_almost_equal(sort(zero(sys1_5)), + sort(zero(sys1 + sys2 + sys3 + sys4 + sys5))) def testMimoSeries(self): """regression: bdalg.series reverses order of arguments""" @@ -274,44 +274,54 @@ def test_feedback_args(self): def testConnect(self): sys = append(self.sys2, self.sys3) # two siso systems - + + # should not raise error + connect(sys, [[1, 2], [2, -2]], [2], [1, 2]) + connect(sys, [[1, 2], [2, 0]], [2], [1, 2]) + connect(sys, [[1, 2, 0], [2, -2, 1]], [2], [1, 2]) + connect(sys, [[1, 2], [2, -2]], [2, 1], [1]) + sys3x3 = append(sys, self.sys3) # 3x3 mimo + connect(sys3x3, [[1, 2, 0], [2, -2, 1], [3, -3, 0]], [2], [1, 2]) + connect(sys3x3, [[1, 2, 0], [2, -2, 1], [3, -3, 0]], [1, 2, 3], [3]) + connect(sys3x3, [[1, 2, 0], [2, -2, 1], [3, -3, 0]], [2, 3], [2, 1]) + # feedback interconnection out of bounds: input too high - Q = [[1, 3], [2, -2]] - with self.assertRaises(IndexError) as context: + Q = [[1, 3], [2, -2]] + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 2]) # feedback interconnection out of bounds: input too low - Q = [[0, 2], [2, -2]] - with self.assertRaises(IndexError) as context: + Q = [[0, 2], [2, -2]] + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 2]) # feedback interconnection out of bounds: output too high - Q = [[1, 2], [2, -3]] - with self.assertRaises(IndexError) as context: + Q = [[1, 2], [2, -3]] + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 2]) - Q = [[1, 2], [2, 4]] - with self.assertRaises(IndexError) as context: + Q = [[1, 2], [2, 4]] + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 2]) - + # input/output index testing Q = [[1, 2], [2, -2]] # OK interconnection - + # input index is out of bounds: too high - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [3], [1, 2]) # input index is out of bounds: too low - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [0], [1, 2]) - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [-2], [1, 2]) # output index is out of bounds: too high - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 3]) # output index is out of bounds: too low - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, 0]) - with self.assertRaises(IndexError) as context: + with self.assertRaises(IndexError): connect(sys, Q, [2], [1, -1]) - + if __name__ == "__main__": unittest.main()