Skip to content

Commit 53dbfe4

Browse files
committed
add new tests with complex state matrices
1 parent e6f2f60 commit 53dbfe4

File tree

1 file changed

+215
-0
lines changed

1 file changed

+215
-0
lines changed

control/tests/statesp_test.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,50 @@ def setUp(self):
6363
D623 = np.zeros((3, 2))
6464
self.sys623 = StateSpace(A623, B623, C623, D623)
6565

66+
# Systems with complex matrices, sysC322, sysC222, sysC623
67+
# These systems have the same shape as the previous ones
68+
69+
A = np.array([[6.0 + 9.0j, 8.0 + 9.0j, -4.0 - 7.0j],
70+
[8.0 - 7.0j, 3.0, 1.0 - 1.0j],
71+
[-7.0 + 9.0j, -8.0 + 6.0j, 9.0 + 8.0j]])
72+
B = np.array([[6.0 + 3.0j, -9.0 - 2.0j],
73+
[9.0 + 5.0j, 7.0 + 3.0j],
74+
[3.0 + 5.0j, 8.0 - 6.0j]])
75+
C = np.array([[4.0 + 4.0j, -4.0 + 9.0j, -8.0 - 1.0j],
76+
[-9.0 - 3.0j, -9.0 - 9.0j, 6.0 - 2.0j]])
77+
D = np.array([[5.0 - 1.0j, -6.0 + 4.0j],
78+
[6.0 + 3.0j, 5.0j]])
79+
self.sysC322 = StateSpace(A, B, C, D)
80+
81+
A = np.array([[-4.0 - 7.0j, 3.0 + 9.0j],
82+
[3.0, -6.0 - 3.0j]])
83+
B = np.array([[2.0, 5.0 + 7.0j],
84+
[-5.0 + 4.0j, -5.0 + 9.0j]])
85+
C = np.array([[1.0 + 6.0j, -7.0 + 6.0j],
86+
[-7.0 - 5.0j, -5.0 - 5.0j]])
87+
D = np.array([[8.0 + 2.0j, -6.0 - 3.0j],
88+
[-3.0 - 1.0j, -5.0 + 6.0j]])
89+
self.sysC222 = StateSpace(A, B, C, D)
90+
91+
A = np.array(
92+
[[2.0 - 8.0j, -2.0 + 6.0j, 8.0 - 1.0j, -6.0 + 7.0j, -5.0 - 3.0j, -5.0 - 6.0j],
93+
[1.0 - 1.0j, 1.0 + 7.0j, -7.0 + 8.0j, 6.0 + 2.0j, 3.0, 8.0 - 5.0j],
94+
[8.0 - 7.0j, -8.0 - 8.0j, 1.0 - 6.0j, -4.0 + 1.0j, 4.0 - 2.0j, -7.0 - 2.0j],
95+
[-4.0 + 9.0j, -8.0 - 2.0j, -1.0 - 4.0j, 1.0 - 7.0j, 5.0 - 8.0j, 6.0 - 9.0j],
96+
[5.0 - 9.0j, 1.0 - 5.0j, -9.0 - 7.0j, -6.0 + 7.0j, -1.0 - 5.0j, 1.0 + 8.0j],
97+
[5.0 + 5.0j, 5.0 + 6.0j, -3.0 - 7.0j, 2.0 + 2.0j, -8.0 - 7.0j, 9.0 + 8.0j]])
98+
B = np.array([[6.0j, 5.0 + 3.0j, 8.0 + 4.0j],
99+
[-9.0j, -2.0 - 1.0j, 9.0 - 6.0j],
100+
[-3.0 - 9.0j, -5.0 + 1.0j, 1.0 - 2.0j],
101+
[8.0 - 6.0j, -2.0 - 4.0j, -8.0 + 2.0j],
102+
[-2.0 + 3.0j, -8.0 + 5.0j, -5.0 + 5.0j],
103+
[-7.0 + 4.0j, -7.0 - 6.0j, -3.0 - 8.0j]])
104+
C = np.array([[8.0 + 6.0j, -3.0j, -1.0 + 7.0j, 2.0j, 6.0 - 6.0j, 3.0 - 1.0j],
105+
[5.0 + 1.0j, -1.0 + 8.0j, -4.0 + 1.0j, 2.0j, 6.0 - 4.0j, -2.0 - 5.0j]])
106+
D = np.array([[7.0 - 4.0j, -5.0 - 1.0j, -5.0 + 8.0j],
107+
[-6.0 + 8.0j, -6.0 - 6.0j, -1.0 + 9.0j]])
108+
self.sysC623 = StateSpace(A, B, C, D)
109+
66110
def test_D_broadcast(self):
67111
"""Test broadcast of D=0 to the right shape"""
68112
# Giving D as a scalar 0 should broadcast to the right shape
@@ -519,6 +563,177 @@ def test_lft(self):
519563
np.testing.assert_allclose(np.array(pk.C).reshape(-1), Cmatlab)
520564
np.testing.assert_allclose(np.array(pk.D).reshape(-1), Dmatlab)
521565

566+
def test_pole_complex(self):
567+
"""Evaluate the poles of a complex MIMO system."""
568+
569+
p = np.sort(self.sysC322.pole())
570+
true_p = np.sort([21.7521962567499187 +7.8381752267389437j
571+
-6.4620734598457208 +2.8701578198630169j,
572+
2.7098772030958163 +6.2916669533980274j])
573+
574+
np.testing.assert_array_almost_equal(p, true_p)
575+
576+
577+
@unittest.skipIf(not slycot_check(), "slycot not installed")
578+
def test_zero_siso_complex(self):
579+
"""Evaluate the zeros of a complex SISO system."""
580+
# extract only first input / first output system of sysC222. This system is denoted sysC111
581+
# or tfC111
582+
tfC111 = ss2tf(self.sysC222)
583+
sysC111 = tf2ss(tfC111[0, 0])
584+
585+
# compute zeros as root of the characteristic polynomial at the numerator of tfC111
586+
# this method is simple and assumed as valid in this test
587+
true_z = np.sort(tfC111[0, 0].zero())
588+
# Compute the zeros through ab08nd, which is tested here
589+
z = np.sort(sysC111.zero())
590+
591+
np.testing.assert_almost_equal(true_z, z)
592+
593+
@unittest.skipIf(not slycot_check(), "slycot not installed")
594+
def test_zero_mimo_sysC322_square_complex(self):
595+
"""Evaluate the zeros of a square complex MIMO system."""
596+
597+
z = np.sort(self.sysC322.zero())
598+
true_z = np.sort([36.4937595620286217 +8.0738640861708575j,
599+
-4.7860079612333388 +29.3266582804945379j,
600+
-7.4509692664104161 +5.5262915134608006j])
601+
np.testing.assert_array_almost_equal(z, true_z)
602+
603+
@unittest.skipIf(not slycot_check(), "slycot not installed")
604+
def test_zero_mimo_sysC222_square_complex(self):
605+
"""Evaluate the zeros of a square complex MIMO system."""
606+
607+
z = np.sort(self.sysC222.zero())
608+
true_z = np.sort([-10.5685005560737366,
609+
3.3685005560737391])
610+
np.testing.assert_array_almost_equal(z, true_z)
611+
612+
@unittest.skipIf(not slycot_check(), "slycot not installed")
613+
def test_zero_mimo_sysC623_non_square_complex(self):
614+
"""Evaluate the zeros of a non square complex MIMO system."""
615+
616+
z = np.sort(self.sysC623.zero())
617+
true_z = np.sort([]) # System has no transmission zeros, not sure what slycot returns
618+
np.testing.assert_array_almost_equal(z, true_z)
619+
620+
def test_add_ss_complex(self):
621+
"""Add two complex MIMO systems."""
622+
623+
A = np.array([[6.0 +9.0j, 8.0 +9.0j, -4.0 -7.0j, 0.0, 0.0],
624+
[8.0 -7.0j, 3.0, 1.0 -1.0j, 0.0, 0.0],
625+
[-7.0 +9.0j, -8.0 +6.0j, 9.0 +8.0j, 0.0, 0.0],
626+
[0.0, 0.0, 0.0, -4.0 -7.0j, 3.0 +9.0j],
627+
[0.0, 0.0, 0.0, 3.0, -6.0 -3.0j]])
628+
B = np.array([[6.0 +3.0j, -9.0 -2.0j],
629+
[9.0 +5.0j, 7.0 +3.0j],
630+
[3.0 +5.0j, 8.0 -6.0j],
631+
[2.0, 5.0 +7.0j],
632+
[-5.0 +4.0j, -5.0 +9.0j]])
633+
C = np.array([[4.0 +4.0j, -4.0 +9.0j, -8.0 -1.0j, 1.0 +6.0j, -7.0 +6.0j],
634+
[-9.0 -3.0j, -9.0 -9.0j, 6.0 -2.0j, -7.0 -5.0j, -5.0 -5.0j]])
635+
D = np.array([[13.0 +1.0j, -12.0 +1.0j],
636+
[3.0 +2.0j, -5.0 +11.0j]])
637+
638+
sys = self.sysC322 + self.sysC222
639+
640+
np.testing.assert_array_almost_equal(sys.A, A)
641+
np.testing.assert_array_almost_equal(sys.B, B)
642+
np.testing.assert_array_almost_equal(sys.C, C)
643+
np.testing.assert_array_almost_equal(sys.D, D)
644+
645+
def test_subtract_ss_complex(self):
646+
"""Subtract two complex MIMO systems."""
647+
648+
A = np.array([[6.0 +9.0j, 8.0 +9.0j, -4.0 -7.0j, 0.0, 0.0],
649+
[8.0 -7.0j, 3.0, 1.0 -1.0j, 0.0, 0.0],
650+
[-7.0 +9.0j, -8.0 +6.0j, 9.0 +8.0j, 0.0, 0.0],
651+
[0.0, 0.0, 0.0, -4.0 -7.0j, 3.0 +9.0j],
652+
[0.0, 0.0, 0.0, 3.0, -6.0 -3.0j]])
653+
B = np.array([[6.0 +3.0j, -9.0 -2.0j],
654+
[9.0 +5.0j, 7.0 +3.0j],
655+
[3.0 +5.0j, 8.0 -6.0j],
656+
[2.0, 5.0 +7.0j],
657+
[-5.0 +4.0j, -5.0 +9.0j]])
658+
C = np.array([[4.0 +4.0j, -4.0 +9.0j, -8.0 -1.0j, -1.0 -6.0j, 7.0 -6.0j],
659+
[-9.0 -3.0j, -9.0 -9.0j, 6.0 -2.0j, 7.0 +5.0j, 5.0 +5.0j]])
660+
D = np.array([[-3.0 -3.0j, 7.0j],
661+
[9.0 +4.0j, 5.0 -1.0j]])
662+
663+
sys = self.sysC322 - self.sysC222
664+
665+
np.testing.assert_array_almost_equal(sys.A, A)
666+
np.testing.assert_array_almost_equal(sys.B, B)
667+
np.testing.assert_array_almost_equal(sys.C, C)
668+
np.testing.assert_array_almost_equal(sys.D, D)
669+
670+
def test_multiply_ss_complex(self):
671+
"""Multiply two complex MIMO systems."""
672+
673+
A = np.array([[6.0 +9.0j, 8.0 +9.0j, -4.0 -7.0j, 41.0 +98.0j, -25.0 +70.0j],
674+
[8.0 -7.0j, 3.0, 1.0 -1.0j, -55.0 +3.0j, -113.0 -31.0j],
675+
[-7.0 +9.0j, -8.0 +6.0j, 9.0 +8.0j, -113.0 +25.0j, -121.0 -27.0j],
676+
[0.0, 0.0, 0.0, -4.0 -7.0j, 3.0 +9.0j],
677+
[0.0, 0.0, 0.0, 3.0, -6.0 -3.0j]])
678+
B = np.array([[67.0 +51.0j, 30.0 -80.0j],
679+
[44.0 +42.0j, -92.0 -30.0j],
680+
[-16.0 +56.0j, -7.0 +39.0j],
681+
[2.0, 5.0 +7.0j],
682+
[-5.0 +4.0j, -5.0 +9.0j]])
683+
C = np.array([[4.0 +4.0j, -4.0 +9.0j, -8.0 -1.0j, 73.0 +31.0j, 21.0 +47.0j],
684+
[-9.0 -3.0j, -9.0 -9.0j, 6.0 -2.0j, 13.0 +4.0j, -35.0 -10.0j]])
685+
D = np.array([[64.0 -4.0j, -27.0 -65.0j],
686+
[47.0 +21.0j, -57.0 -61.0j]])
687+
688+
sys = self.sysC322 * self.sysC222
689+
690+
np.testing.assert_array_almost_equal(sys.A, A)
691+
np.testing.assert_array_almost_equal(sys.B, B)
692+
np.testing.assert_array_almost_equal(sys.C, C)
693+
np.testing.assert_array_almost_equal(sys.D, D)
694+
695+
def test_evalfr(self):
696+
"""Evaluate the frequency response at one frequency."""
697+
698+
resp = [[4.6799374736968655 -34.9854626345217383j,
699+
-10.8392352552155344 -10.3778031623880267j],
700+
[28.8313352973005479 +17.1145433776227947j,
701+
5.6628990560933081 +8.8759694583057787j]]
702+
703+
# Correct versions of the call
704+
np.testing.assert_almost_equal(evalfr(sysC322, 1j), resp)
705+
np.testing.assert_almost_equal(sysC322._evalfr(1.), resp)
706+
707+
# Deprecated version of the call (should generate warning)
708+
import warnings
709+
with warnings.catch_warnings(record=True) as w:
710+
# Set up warnings filter to only show warnings in control module
711+
warnings.filterwarnings("ignore")
712+
warnings.filterwarnings("always", module="control")
713+
714+
# Make sure that we get a pending deprecation warning
715+
sys.evalfr(1.)
716+
assert len(w) == 1
717+
assert issubclass(w[-1].category, PendingDeprecationWarning)
718+
719+
@unittest.skipIf(not slycot_check(), "slycot not installed")
720+
def test_freq_resp(self):
721+
"""Evaluate the frequency response at multiple frequencies."""
722+
723+
true_mag = [[15.2721178549527039, 3.9176691825112484, 20.5865790875032246],
724+
[24.5384389050919864, 2.8374330975514015, 18.2268344283306227]]
725+
726+
true_phase = [[1.0345533469994428, 2.2133291186570987, 2.6715324185062164],
727+
[1.8217663044282106, -2.8266936088743044, 2.2694910839768196]]
728+
true_omega = [0.1, 10, 0.01j, -1j];
729+
730+
mag, phase, omega = sysC623.freqresp(true_omega)
731+
732+
np.testing.assert_almost_equal(mag, true_mag)
733+
np.testing.assert_almost_equal(phase, true_phase)
734+
np.testing.assert_equal(omega, true_omega)
735+
736+
522737
class TestRss(unittest.TestCase):
523738
"""These are tests for the proper functionality of statesp.rss."""
524739

0 commit comments

Comments
 (0)