LSR/env/lib/python3.6/site-packages/control/tests/robust_test.py
2020-06-04 17:24:47 +02:00

369 lines
14 KiB
Python

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()