-
Notifications
You must be signed in to change notification settings - Fork 45
Rory/ab13md #171
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
Rory/ab13md #171
Changes from 4 commits
76f76a4
14ae201
dd8c738
05849a0
619cfc4
f0b134e
6c3b700
a1d4ee5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,8 @@ | |
| # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | ||
| # MA 02110-1301, USA. | ||
|
|
||
| import numpy as np | ||
|
|
||
| from . import _wrapper | ||
| from .exceptions import raise_if_slycot_error, SlycotParameterError | ||
|
|
||
|
|
@@ -1628,7 +1630,118 @@ def ab13fd(n, A, tol = 0.0): | |
| raise_if_slycot_error(out[-1], arg_list, ab13fd.__doc__) | ||
| return out[0], out[1] | ||
|
|
||
|
|
||
| def ab13md(n, Z, nblock, itype, x=None): | ||
| """mubound, d, g, xout = ab13md(n, Z, nblock, itype, [x]) | ||
|
|
||
| Find an upper bound for the structured singular value of complex | ||
| matrix Z and given block diagonal structure. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| n : integer | ||
| Order of Z; n=Z.shape[0]. | ||
|
|
||
| Z : (n,n) complex array | ||
| Matrix to find structured singular value upper bound of | ||
|
|
||
| nblock : (m,) integer array | ||
| The size of the block diagonals of the uncertainty structure; | ||
| i.e., nblock(i)=p means that the ith block is pxp. | ||
|
|
||
| itype : (m,) integer array | ||
| The type of each block diagonal uncertainty defined in nblock. | ||
| itype(i)==1 means that the ith block is real, while itype(i)==2 | ||
| means the the ith block is complex. Real blocks must be 1x1, | ||
| i.e., if itype(i)==1, ntype(i) must be 1. | ||
|
|
||
| x : (q,) real array or None | ||
| If not None, must be the output of a previous call to ab13md. | ||
| The previous call must have been with the same values of n, | ||
| nblock, and itype; and the previous call's Z should be "close" | ||
| to the current call's Z. | ||
|
|
||
| q is determined by the block structure; see SLICOT AB13MD for | ||
| details. | ||
|
|
||
| Returns | ||
| ------- | ||
| mubound : non-negative real scalar | ||
| Upper bound on structure singular value for given arguments | ||
|
|
||
| d, g : (n,) real arrays | ||
| Real arrays such that if D=np.diag(g), G=np.diag(G), and ZH = Z.T.conj(), then | ||
| ZH @ D**2 @ Z + 1j * (G@Z - ZH@G) - mu**2 * D**2 | ||
| will be negative semi-definite. | ||
|
|
||
| xout : (q,) real array | ||
| For use as ``x`` argument in subsequent call to ``ab13md``. | ||
|
|
||
| For scalar Z and real uncertainty (ntype=1, itype=1), returns 0 | ||
| instead of abs(Z). | ||
|
|
||
| Raises | ||
| ------ | ||
| SlycotArithmeticError | ||
| :info = 1: Block sizes must be positive | ||
| :info = 2: Block sizes must sum to n | ||
| :info = 3: Real blocks must be of size 1 | ||
| :info = 4: Block types must be 1 or 2 | ||
| :info = 5: Error in linear equation solution | ||
| :info = 6: Error in eigenvalue or singular value computation | ||
|
|
||
| Notes | ||
| ----- | ||
| This wraps SLICOT routine AB13MD, which implements the upper bound | ||
| of [1]. | ||
|
|
||
| References | ||
| ---------- | ||
| .. [1] Fan, M.K.H., Tits, A.L., and Doyle, J.C., "Robustness in | ||
| the presence of mixed parametric uncertainty and unmodeled | ||
| dynamics," IEEE Trans. Automatic Control, vol. AC-36, 1991, | ||
| pp. 25-38. | ||
|
|
||
| """ | ||
| hidden = ' (hidden by the wrapper)' | ||
| arg_list = ['fact' + hidden, 'n' + hidden, 'z', 'ldz' + hidden, | ||
| 'm' + hidden, 'nblock', 'itype', 'x', 'bound', 'd', 'g', | ||
|
||
| 'iwork' + hidden, 'dwork' + hidden, 'ldwork' + hidden, | ||
| 'zwork' + hidden, 'lzwork' + hidden, 'info' + hidden] | ||
|
|
||
| # prepare the "x" input and output | ||
|
|
||
| # x, in SLICOT, needs to be length m+mr-1. m is the length of | ||
| # nblock (and itype), and mr is the number of real blocks. | ||
|
|
||
| # In analysis.pyf x is specified as length 2*m-1, since I couldn't | ||
| # figure out how to express the m+mr-1 constraint there. | ||
|
|
||
| # The code here is to arrange for the user-visible part of x, | ||
| # which is length m+mr-1, to be packed into the 2*m-1-length array | ||
| # to pass the SLICOT routine. | ||
|
|
||
| m = len(nblock) | ||
| mr = np.count_nonzero(1==itype) | ||
|
|
||
| if x is None: | ||
| fact='N' | ||
| x = np.empty(2*m-1) | ||
| else: | ||
| fact='F' | ||
| if len(x) != m+mr-1: | ||
| raise ValueError(f'Require len(x)==m+mr-1, but len(x)={len(x)}, m={m}, mr={mr}') | ||
| x = np.concatenate([x,np.zeros(2*m-1-len(x))]) | ||
|
|
||
| x, bound, d, g, info = _wrapper.ab13md(fact, n, Z, nblock, itype, x) | ||
|
|
||
| raise_if_slycot_error(info, arg_list, ab13md.__doc__) | ||
|
|
||
| return bound, d, g, x[:m+mr-1] | ||
|
|
||
|
|
||
| def ag08bd(l,n,m,p,A,E,B,C,D,equil='N',tol=0.0,ldwork=None): | ||
|
|
||
roryyorke marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| """ Af,Ef,nrank,niz,infz,kronr,infe,kronl = ag08bd(l,n,m,p,A,E,B,C,D,[equil,tol,ldwork]) | ||
|
|
||
| To extract from the system pencil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| import numpy as np | ||
| from numpy.testing import assert_allclose, assert_array_less | ||
|
|
||
| import pytest | ||
|
|
||
| from slycot import ab13md | ||
|
|
||
| # References: | ||
| # [1] Skogestand & Postlethwaite, Multivariable Feedback Control, 1996 | ||
| # [2] slycot/src/SLICOT-Reference/examples | ||
|
|
||
| def slicot_example(): | ||
| # [2] AB13MD.dat & TAB13MD.f | ||
| n = 6 | ||
| nblock = np.array([1, 1, 2, 1, 1]) | ||
| itype = np.array([1, 1, 2, 2, 2]) | ||
| # this unpleasant looking array is the result of text editor | ||
| # search-and-replace on the Z array in AB13MD.dat | ||
| Z = np.array([ | ||
| complex(-1.0e0,6.0e0), complex(2.0e0,-3.0e0), complex(3.0e0,8.0e0), | ||
| complex(3.0e0,8.0e0), complex(-5.0e0,-9.0e0), complex(-6.0e0,2.0e0), | ||
| complex(4.0e0,2.0e0), complex(-2.0e0,5.0e0), complex(-6.0e0,-7.0e0), | ||
| complex(-4.0e0,11.0e0), complex(8.0e0,-7.0e0), complex(12.0e0,-1.0e0), | ||
| complex(5.0e0,-4.0e0), complex(-4.0e0,-8.0e0), complex(1.0e0,-3.0e0), | ||
| complex(-6.0e0,14.0e0), complex(2.0e0,-5.0e0), complex(4.0e0,16.0e0), | ||
| complex(-1.0e0,6.0e0), complex(2.0e0,-3.0e0), complex(3.0e0,8.0e0), | ||
| complex(3.0e0,8.0e0), complex(-5.0e0,-9.0e0), complex(-6.0e0,2.0e0), | ||
| complex(4.0e0,2.0e0), complex(-2.0e0,5.0e0), complex(-6.0e0,-7.0e0), | ||
| complex(-4.0e0,11.0e0), complex(8.0e0,-7.0e0), complex(12.0e0,-1.0e0), | ||
| complex(5.0e0,-4.0e0), complex(-4.0e0,-8.0e0), complex(1.0e0,-3.0e0), | ||
| complex(-6.0e0,14.0e0), complex(2.0e0,-5.0e0), complex(4.0e0,16.0e0), | ||
| ]) | ||
| Z = np.reshape(Z, (n, n)) | ||
| return n, Z, nblock, itype | ||
|
|
||
|
|
||
| def test_cached_inputoutput(): | ||
| # check x, cached working area, input and output, and error | ||
| n, Z, nblock, itype = slicot_example() | ||
|
|
||
| m = len(nblock) | ||
| mr = np.count_nonzero(1==itype) | ||
|
|
||
| mu0, d0, g0, x0 = ab13md(n, Z, nblock, itype) | ||
| assert m+mr-1 == len(x0) | ||
|
|
||
| mu1, d1, g1, x1 = ab13md(n, Z, nblock, itype, x0) | ||
|
|
||
| assert_allclose(mu1, mu0) | ||
|
|
||
| with pytest.raises(ValueError): | ||
| mu0, d, g, x = ab13md(n, Z, nblock, itype, np.ones(mr+mr)) | ||
|
|
||
|
|
||
| class TestReference: | ||
| # check a few reference cases | ||
| def test_complex_scalar(self): | ||
| # [1] (8.74) | ||
| n = 1 | ||
| nblock = np.array([1]) | ||
| itype = np.array([2]) # complex | ||
|
|
||
| z = np.array([[1+2j]]) | ||
| mu = ab13md(n,z,nblock,itype)[0] | ||
| assert_allclose(mu, abs(z)) | ||
|
|
||
|
|
||
| @pytest.mark.xfail(reason="https://github.com/SLICOT/SLICOT-Reference/issues/4") | ||
| def test_real_scalar_real_uncertainty(self): | ||
| # [1] (8.75) | ||
| n=1 | ||
| nblock=np.array([1]) | ||
| itype=np.array([1]) # real | ||
| z = np.array([[5.34]]) | ||
| mu = ab13md(n,z,nblock,itype)[0] | ||
| assert_allclose(mu, abs(z)) | ||
|
|
||
|
|
||
| def test_complex_scalar_real_uncertainty(self): | ||
| # [1] (8.75) | ||
| n=1 | ||
| nblock=np.array([1]) | ||
| itype=np.array([1]) # real | ||
|
|
||
| z = np.array([[6.78j]]) | ||
| mu = ab13md(n,z,nblock,itype)[0] | ||
| assert_allclose(mu, 0) | ||
|
|
||
|
|
||
| def test_sp85_part1(self): | ||
| # [1] Example 8.5 part 1, unstructured uncertainty | ||
| M = np.array([[2, 2], [-1, -1]]) | ||
| muref = 3.162 | ||
|
|
||
| n = M.shape[0] | ||
| nblock=np.array([2]) | ||
| itype=np.array([2]) | ||
|
|
||
| mu = ab13md(n, M, nblock, itype)[0] | ||
| assert_allclose(mu, muref, rtol=5e-4) | ||
|
|
||
|
|
||
| def test_sp85_part2(self): | ||
| # [1] Example 8.5 part 2, structured uncertainty | ||
| M = np.array([[2, 2], [-1, -1]]) | ||
| muref = 3.000 | ||
|
|
||
| n = M.shape[0] | ||
| nblock=np.array([1, 1]) | ||
| itype=np.array([2, 2]) | ||
|
|
||
| mu = ab13md(n, M, nblock, itype)[0] | ||
| assert_allclose(mu, muref, rtol=5e-4) | ||
|
|
||
| def test_slicot(self): | ||
| # besides muref, check that we've output d and g correctly | ||
|
|
||
| muref = 0.4174753408e+02 # [2] AB13MD.res | ||
|
|
||
| n, Z, nblock, itype = slicot_example() | ||
|
|
||
| mu, d, g, x = ab13md(n, Z, nblock, itype) | ||
|
|
||
| assert_allclose(mu, muref) | ||
|
|
||
| ZH = Z.T.conj() | ||
| D = np.diag(d) | ||
| G = np.diag(g) | ||
|
|
||
| # this matrix should be negative semi-definite | ||
| negsemidef = (ZH @ D**2 @ Z | ||
| + 1j * (G@Z - ZH@G) | ||
| - mu**2 * D**2) | ||
|
|
||
| # a check on the algebra, not ab13md! | ||
| assert_allclose(negsemidef, negsemidef.T.conj()) | ||
|
|
||
| evals = np.linalg.eigvalsh(negsemidef) | ||
| assert max(evals) < np.finfo(float).eps**0.5 |
Uh oh!
There was an error while loading. Please reload this page.