Skip to content

Commit 077a8bc

Browse files
authored
Merge pull request python-control#200 from ipa-lth/master
Fix canonical form 'observable'
2 parents 81ebbcf + 5009e92 commit 077a8bc

File tree

2 files changed

+48
-1
lines changed

2 files changed

+48
-1
lines changed

control/canonical.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,10 @@ def observable_form(xsys):
133133
Wrz = obsv(zsys.A, zsys.C)
134134

135135
# Transformation from one form to another
136-
Tzx = inv(Wrz) * Wrx
136+
Tzx = solve(Wrz, Wrx) # matrix left division, Tzx = inv(Wrz) * Wrx
137+
138+
if matrix_rank(Tzx) != xsys.states:
139+
raise ValueError("Transformation matrix singular to working precision.")
137140

138141
# Finally, compute the output matrix
139142
zsys.B = Tzx * xsys.B

control/tests/canonical_test.py

+44
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,47 @@ def test_unreachable_system(self):
5252

5353
# Check if an exception is raised
5454
np.testing.assert_raises(ValueError, canonical_form, sys, "reachable")
55+
56+
def test_observable_form(self):
57+
"""Test the observable canonical form"""
58+
59+
# Create a system in the observable canonical form
60+
coeffs = [1.0, 2.0, 3.0, 4.0, 1.0]
61+
A_true = np.polynomial.polynomial.polycompanion(coeffs)
62+
A_true = np.fliplr(np.flipud(A_true))
63+
B_true = np.matrix("1.0 1.0 1.0 1.0").T
64+
C_true = np.matrix("1.0 0.0 0.0 0.0")
65+
D_true = 42.0
66+
67+
# Perform a coordinate transform with a random invertible matrix
68+
T_true = np.matrix([[-0.27144004, -0.39933167, 0.75634684, 0.44135471],
69+
[-0.74855725, -0.39136285, -0.18142339, -0.50356997],
70+
[-0.40688007, 0.81416369, 0.38002113, -0.16483334],
71+
[-0.44769516, 0.15654653, -0.50060858, 0.72419146]])
72+
A = np.linalg.solve(T_true, A_true)*T_true
73+
B = np.linalg.solve(T_true, B_true)
74+
C = C_true*T_true
75+
D = D_true
76+
77+
# Create a state space system and convert it to the observable canonical form
78+
sys_check, T_check = canonical_form(ss(A, B, C, D), "observable")
79+
80+
# Check against the true values
81+
np.testing.assert_array_almost_equal(sys_check.A, A_true)
82+
np.testing.assert_array_almost_equal(sys_check.B, B_true)
83+
np.testing.assert_array_almost_equal(sys_check.C, C_true)
84+
np.testing.assert_array_almost_equal(sys_check.D, D_true)
85+
np.testing.assert_array_almost_equal(T_check, T_true)
86+
87+
def test_unobservable_system(self):
88+
"""Test observable canonical form with an unobservable system"""
89+
90+
# Create an unobservable system
91+
A = np.matrix("1.0 2.0 2.0; 4.0 5.0 5.0; 7.0 8.0 8.0")
92+
B = np.matrix("1.0 1.0 1.0").T
93+
C = np.matrix("1.0 1.0 1.0")
94+
D = 42.0
95+
sys = ss(A, B, C, D)
96+
97+
# Check if an exception is raised
98+
np.testing.assert_raises(ValueError, canonical_form, sys, "observable")

0 commit comments

Comments
 (0)