8000 Fix bdalg.connect by sawyerbfuller · Pull Request #474 · python-control/python-control · GitHub
[go: up one dir, main page]

Skip to content

Fix bdalg.connect #474

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 30 additions & 9 deletions control/bdalg.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------
Expand All @@ -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
Expand Down
84 changes: 68 additions & 16 deletions control/tests/bdalg_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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.
Expand Down Expand Up @@ -192,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"""
Expand Down Expand Up @@ -270,6 +272,56 @@ def test_feedback_args(self):
sys = ctrl.feedback(1, frd)
self.assertTrue(isinstance(sys, ctrl.FRD))

def testConnect(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only look for failures. What about tests that successfully connect systems?

Obviously I will pytest.parametrize this for #438

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests only look for failures. What about tests that successfully connect systems?

good idea; added.

Obviously I will pytest.parametrize this for #438

thx!

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] 9CDD , [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):
connect(sys, Q, [2], [1, 2])
# feedback interconnection out of bounds: input too low
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):
connect(sys, Q, [2], [1, 2])
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):
connect(sys, Q, [3], [1, 2])
# input index is out of bounds: too low
with self.assertRaises(IndexError):
connect(sys, Q, [0], [1, 2])
with self.assertRaises(IndexError):
connect(sys, Q, [-2], [1, 2])
# output index is out of bounds: too high
with self.assertRaises(IndexError):
connect(sys, Q, [2], [1, 3])
# output index is out of bounds: too low
with self.assertRaises(IndexError):
connect(sys, Q, [2], [1, 0])
with self.assertRaises(IndexError):
connect(sys, Q, [2], [1, -1])


if __name__ == "__main__":
unittest.main()
0