import unittest import numpy as np import control import control.robust from control.exception import slycot_check class TestHinf(unittest.TestCase): @unittest.skipIf(not slycot_check(), "slycot not installed") def testHinfsyn(self): """Test hinfsyn""" p = control.ss(-1, [1, 1], [[1], [1]], [[0, 1], [1, 0]]) k, cl, gam, rcond = control.robust.hinfsyn(p, 1, 1) # from Octave, which also uses SB10AD: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # [k,cl] = hinfsyn(g,1,1); np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]]) np.testing.assert_array_almost_equal(cl.A, [[-1, -1], [1, -3]]) np.testing.assert_array_almost_equal(cl.B, [[1], [1]]) np.testing.assert_array_almost_equal(cl.C, [[1, -1]]) np.testing.assert_array_almost_equal(cl.D, [[0]]) # TODO: add more interesting examples class TestH2(unittest.TestCase): @unittest.skipIf(not slycot_check(), "slycot not installed") def testH2syn(self): """Test h2syn""" p = control.ss(-1, [1, 1], [[1], [1]], [[0, 1], [1, 0]]) k = control.robust.h2syn(p, 1, 1) # from Octave, which also uses SB10HD for H-2 synthesis: # a= -1; b1= 1; b2= 1; c1= 1; c2= 1; d11= 0; d12= 1; d21= 1; d22= 0; # g = ss(a,[b1,b2],[c1;c2],[d11,d12;d21,d22]); # k = h2syn(g,1,1); # the solution is the same as for the hinfsyn test np.testing.assert_array_almost_equal(k.A, [[-3]]) np.testing.assert_array_almost_equal(k.B, [[1]]) np.testing.assert_array_almost_equal(k.C, [[-1]]) np.testing.assert_array_almost_equal(k.D, [[0]]) class TestAugw(unittest.TestCase): """Test control.robust.augw""" # tolerance for system equality TOL = 1e-8 def siso_almost_equal(self, g, h): """siso_almost_equal(g,h) -> None Raises AssertionError if g and h, two SISO LTI objects, are not almost equal""" from control import tf, minreal gmh = tf(minreal(g - h, verbose=False)) if not (gmh.num[0][0] < self.TOL).all(): maxnum = max(abs(gmh.num[0][0])) raise AssertionError( 'systems not approx equal; max num. coeff is {}\nsys 1:\n{}\nsys 2:\n{}'.format( maxnum, g, h)) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW1(self): """SISO plant with S weighting""" from control import augw, ss g = ss([-1.], [1.], [1.], [1.]) w1 = ss([-2], [2.], [1.], [2.]) p = augw(g, w1) self.assertEqual(2, p.outputs) self.assertEqual(2, p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1, p[0, 0]) # w->v should be 1 self.siso_almost_equal(ss([], [], [], [1]), p[1, 0]) # u->z1 should be -w1*g self.siso_almost_equal(-w1 * g, p[0, 1]) # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW2(self): """SISO plant with KS weighting""" from control import augw, ss g = ss([-1.], [1.], [1.], [1.]) w2 = ss([-2], [1.], [1.], [2.]) p = augw(g, w2=w2) self.assertEqual(2, p.outputs) self.assertEqual(2, p.inputs) # w->z2 should be 0 self.siso_almost_equal(ss([], [], [], 0), p[0, 0]) # w->v should be 1 self.siso_almost_equal(ss([], [], [], [1]), p[1, 0]) # u->z2 should be w2 self.siso_almost_equal(w2, p[0, 1]) # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW3(self): """SISO plant with T weighting""" from control import augw, ss g = ss([-1.], [1.], [1.], [1.]) w3 = ss([-2], [1.], [1.], [2.]) p = augw(g, w3=w3) self.assertEqual(2, p.outputs) self.assertEqual(2, p.inputs) # w->z3 should be 0 self.siso_almost_equal(ss([], [], [], 0), p[0, 0]) # w->v should be 1 self.siso_almost_equal(ss([], [], [], [1]), p[1, 0]) # u->z3 should be w3*g self.siso_almost_equal(w3 * g, p[0, 1]) # u->v should be -g self.siso_almost_equal(-g, p[1, 1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testSisoW123(self): """SISO plant with all weights""" from control import augw, ss g = ss([-1.], [1.], [1.], [1.]) w1 = ss([-2.], [2.], [1.], [2.]) w2 = ss([-3.], [3.], [1.], [3.]) w3 = ss([-4.], [4.], [1.], [4.]) p = augw(g, w1, w2, w3) self.assertEqual(4, p.outputs) self.assertEqual(2, p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1, p[0, 0]) # w->z2 should be 0 self.siso_almost_equal(0, p[1, 0]) # w->z3 should be 0 self.siso_almost_equal(0, p[2, 0]) # w->v should be 1 self.siso_almost_equal(ss([], [], [], [1]), p[3, 0]) # u->z1 should be -w1*g self.siso_almost_equal(-w1 * g, p[0, 1]) # u->z2 should be w2 self.siso_almost_equal(w2, p[1, 1]) # u->z3 should be w3*g self.siso_almost_equal(w3 * g, p[2, 1]) # u->v should be -g self.siso_almost_equal(-g, p[3, 1]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW1(self): """MIMO plant with S weighting""" from control import augw, ss g = ss([[-1., -2], [-3, -4]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]]) w1 = ss([-2], [2.], [1.], [2.]) p = augw(g, w1) self.assertEqual(4, p.outputs) self.assertEqual(4, p.inputs) # w->z1 should be diag(w1,w1) self.siso_almost_equal(w1, p[0, 0]) self.siso_almost_equal(0, p[0, 1]) self.siso_almost_equal(0, p[1, 0]) self.siso_almost_equal(w1, p[1, 1]) # w->v should be I self.siso_almost_equal(1, p[2, 0]) self.siso_almost_equal(0, p[2, 1]) self.siso_almost_equal(0, p[3, 0]) self.siso_almost_equal(1, p[3, 1]) # u->z1 should be -w1*g self.siso_almost_equal(-w1 * g[0, 0], p[0, 2]) self.siso_almost_equal(-w1 * g[0, 1], p[0, 3]) self.siso_almost_equal(-w1 * g[1, 0], p[1, 2]) self.siso_almost_equal(-w1 * g[1, 1], p[1, 3]) # # u->v should be -g self.siso_almost_equal(-g[0, 0], p[2, 2]) self.siso_almost_equal(-g[0, 1], p[2, 3]) self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW2(self): """MIMO plant with KS weighting""" from control import augw, ss g = ss([[-1., -2], [-3, -4]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]]) w2 = ss([-2], [2.], [1.], [2.]) p = augw(g, w2=w2) self.assertEqual(4, p.outputs) self.assertEqual(4, p.inputs) # w->z2 should be 0 self.siso_almost_equal(0, p[0, 0]) self.siso_almost_equal(0, p[0, 1]) self.siso_almost_equal(0, p[1, 0]) self.siso_almost_equal(0, p[1, 1]) # w->v should be I self.siso_almost_equal(1, p[2, 0]) self.siso_almost_equal(0, p[2, 1]) self.siso_almost_equal(0, p[3, 0]) self.siso_almost_equal(1, p[3, 1]) # u->z2 should be w2 self.siso_almost_equal(w2, p[0, 2]) self.siso_almost_equal(0, p[0, 3]) self.siso_almost_equal(0, p[1, 2]) self.siso_almost_equal(w2, p[1, 3]) # # u->v should be -g self.siso_almost_equal(-g[0, 0], p[2, 2]) self.siso_almost_equal(-g[0, 1], p[2, 3]) self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW3(self): """MIMO plant with T weighting""" from control import augw, ss g = ss([[-1., -2], [-3, -4]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]]) w3 = ss([-2], [2.], [1.], [2.]) p = augw(g, w3=w3) self.assertEqual(4, p.outputs) self.assertEqual(4, p.inputs) # w->z3 should be 0 self.siso_almost_equal(0, p[0, 0]) self.siso_almost_equal(0, p[0, 1]) self.siso_almost_equal(0, p[1, 0]) self.siso_almost_equal(0, p[1, 1]) # w->v should be I self.siso_almost_equal(1, p[2, 0]) self.siso_almost_equal(0, p[2, 1]) self.siso_almost_equal(0, p[3, 0]) self.siso_almost_equal(1, p[3, 1]) # u->z3 should be w3*g self.siso_almost_equal(w3 * g[0, 0], p[0, 2]) self.siso_almost_equal(w3 * g[0, 1], p[0, 3]) self.siso_almost_equal(w3 * g[1, 0], p[1, 2]) self.siso_almost_equal(w3 * g[1, 1], p[1, 3]) # # u->v should be -g self.siso_almost_equal(-g[0, 0], p[2, 2]) self.siso_almost_equal(-g[0, 1], p[2, 3]) self.siso_almost_equal(-g[1, 0], p[3, 2]) self.siso_almost_equal(-g[1, 1], p[3, 3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testMimoW123(self): """MIMO plant with all weights""" from control import augw, ss, append, minreal g = ss([[-1., -2], [-3, -4]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]], [[1., 0.], [0., 1.]]) # this should be expaned to w1*I w1 = ss([-2.], [2.], [1.], [2.]) # diagonal weighting w2 = append(ss([-3.], [3.], [1.], [3.]), ss([-4.], [4.], [1.], [4.])) # full weighting w3 = ss([[-4., -5], [-6, -7]], [[2., 3.], [5., 7.]], [[11., 13.], [17., 19.]], [[23., 29.], [31., 37.]]) p = augw(g, w1, w2, w3) self.assertEqual(8, p.outputs) self.assertEqual(4, p.inputs) # w->z1 should be w1 self.siso_almost_equal(w1, p[0, 0]) self.siso_almost_equal(0, p[0, 1]) self.siso_almost_equal(0, p[1, 0]) self.siso_almost_equal(w1, p[1, 1]) # w->z2 should be 0 self.siso_almost_equal(0, p[2, 0]) self.siso_almost_equal(0, p[2, 1]) self.siso_almost_equal(0, p[3, 0]) self.siso_almost_equal(0, p[3, 1]) # w->z3 should be 0 self.siso_almost_equal(0, p[4, 0]) self.siso_almost_equal(0, p[4, 1]) self.siso_almost_equal(0, p[5, 0]) self.siso_almost_equal(0, p[5, 1]) # w->v should be I self.siso_almost_equal(1, p[6, 0]) self.siso_almost_equal(0, p[6, 1]) self.siso_almost_equal(0, p[7, 0]) self.siso_almost_equal(1, p[7, 1]) # u->z1 should be -w1*g self.siso_almost_equal(-w1 * g[0, 0], p[0, 2]) self.siso_almost_equal(-w1 * g[0, 1], p[0, 3]) self.siso_almost_equal(-w1 * g[1, 0], p[1, 2]) self.siso_almost_equal(-w1 * g[1, 1], p[1, 3]) # u->z2 should be w2 self.siso_almost_equal(w2[0, 0], p[2, 2]) self.siso_almost_equal(w2[0, 1], p[2, 3]) self.siso_almost_equal(w2[1, 0], p[3, 2]) self.siso_almost_equal(w2[1, 1], p[3, 3]) # u->z3 should be w3*g w3g = w3 * g; self.siso_almost_equal(w3g[0, 0], minreal(p[4, 2])) self.siso_almost_equal(w3g[0, 1], minreal(p[4, 3])) self.siso_almost_equal(w3g[1, 0], minreal(p[5, 2])) self.siso_almost_equal(w3g[1, 1], minreal(p[5, 3])) # u->v should be -g self.siso_almost_equal(-g[0, 0], p[6, 2]) self.siso_almost_equal(-g[0, 1], p[6, 3]) self.siso_almost_equal(-g[1, 0], p[7, 2]) self.siso_almost_equal(-g[1, 1], p[7, 3]) @unittest.skipIf(not slycot_check(), "slycot not installed") def testErrors(self): """Error cases handled""" from control import augw, ss # no weights g1by1 = ss(-1, 1, 1, 0) g2by2 = ss(-np.eye(2), np.eye(2), np.eye(2), np.zeros((2, 2))) self.assertRaises(ValueError, augw, g1by1) # mismatched size of weight and plant self.assertRaises(ValueError, augw, g1by1, w1=g2by2) self.assertRaises(ValueError, augw, g1by1, w2=g2by2) self.assertRaises(ValueError, augw, g1by1, w3=g2by2) class TestMixsyn(unittest.TestCase): """Test control.robust.mixsyn""" # it's a relatively simple wrapper; compare results with augw, hinfsyn @unittest.skipIf(not slycot_check(), "slycot not installed") def testSiso(self): """mixsyn with SISO system""" from control import tf, augw, hinfsyn, mixsyn from control import ss # Skogestad+Postlethwaite, Multivariable Feedback Control, 1st Ed., Example 2.11 s = tf([1, 0], 1) # plant g = 200 / (10 * s + 1) / (0.05 * s + 1) ** 2 # sensitivity weighting M = 1.5 wb = 10 A = 1e-4 w1 = (s / M + wb) / (s + wb * A) # KS weighting w2 = tf(1, 1) p = augw(g, w1, w2) kref, clref, gam, rcond = hinfsyn(p, 1, 1) ktest, cltest, info = mixsyn(g, w1, w2) # check similar to S+P's example np.testing.assert_allclose(gam, 1.37, atol=1e-2) # mixsyn is a convenience wrapper around augw and hinfsyn, so # results will be exactly the same. Given than, use the lazy # but fragile testing option. np.testing.assert_allclose(ktest.A, kref.A) np.testing.assert_allclose(ktest.B, kref.B) np.testing.assert_allclose(ktest.C, kref.C) np.testing.assert_allclose(ktest.D, kref.D) np.testing.assert_allclose(cltest.A, clref.A) np.testing.assert_allclose(cltest.B, clref.B) np.testing.assert_allclose(cltest.C, clref.C) np.testing.assert_allclose(cltest.D, clref.D) np.testing.assert_allclose(gam, info[0]) np.testing.assert_allclose(rcond, info[1]) if __name__ == "__main__": unittest.main()