From cf77e2350ac014fd13a3c1c1dd4884a859f59c60 Mon Sep 17 00:00:00 2001 From: Daniel Bankmann Date: Thu, 21 Mar 2019 15:36:18 +0100 Subject: [PATCH 1/6] added option for computing anti-stabilizing solutions of Riccati equations --- control/mateqn.py | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/control/mateqn.py b/control/mateqn.py index e5f7ba4f6..7e842234a 100644 --- a/control/mateqn.py +++ b/control/mateqn.py @@ -411,7 +411,7 @@ def dlyap(A,Q,C=None,E=None): #### Riccati equation solvers care and dare -def care(A,B,Q,R=None,S=None,E=None): +def care(A, B, Q, R=None, S=None, E=None, stabilizing=True): """ (X,L,G) = care(A,B,Q,R=None) solves the continuous-time algebraic Riccati equation @@ -527,7 +527,11 @@ def care(A,B,Q,R=None,S=None,E=None): raise e try: - X,rcond,w,S_o,U,A_inv = sb02md(n,A,G,Q,'C') + if stabilizing: + sort = 'S' + else: + sort = 'U' + X, rcond, w, S_o, U, A_inv = sb02md(n, A, G, Q, 'C', sort=sort) except ValueError as ve: if ve.info < 0 or ve.info > 5: e = ValueError(ve.message) @@ -613,8 +617,12 @@ def care(A,B,Q,R=None,S=None,E=None): # Solve the generalized algebraic Riccati equation by calling the # Slycot function sg02ad try: - rcondu,X,alfar,alfai,beta,S_o,T,U,iwarn = \ - sg02ad('C','B','N','U','N','N','S','R',n,m,0,A,E,B,Q,R,S) + if stabilizing: + sort = 'S' + else: + sort = 'U' + rcondu, X, alfar, alfai, beta, S_o, T, U, iwarn = \ + sg02ad('C', 'B', 'N', 'U', 'N', 'N', sort, 'R', n, m, 0, A, E, B, Q, R, S) except ValueError as ve: if ve.info < 0 or ve.info > 7: e = ValueError(ve.message) @@ -671,7 +679,7 @@ def care(A,B,Q,R=None,S=None,E=None): else: raise ControlArgument("Invalid set of input parameters.") -def dare(A,B,Q,R,S=None,E=None): +def dare(A, B, Q, R, S=None, E=None, stabilizing=True): """ (X,L,G) = dare(A,B,Q,R) solves the discrete-time algebraic Riccati equation @@ -692,8 +700,8 @@ def dare(A,B,Q,R,S=None,E=None): matrix :math:`G = (B^T X B + R)^{-1} (B^T X A + S^T)` and the closed loop eigenvalues L, i.e., the eigenvalues of A - B G , E. """ - if S is not None or E is not None: - return dare_old(A, B, Q, R, S, E) + if S is not None or E is not None or not stabilizing: + return dare_old(A, B, Q, R, S, E, stabilizing) else: Rmat = asmatrix(R) Qmat = asmatrix(Q) @@ -702,7 +710,7 @@ def dare(A,B,Q,R,S=None,E=None): L = eigvals(A - B.dot(G)) return X, L, G -def dare_old(A,B,Q,R,S=None,E=None): +def dare_old(A, B, Q, R, S=None, E=None, stabilizing=True): # Make sure we can import required slycot routine try: from slycot import sb02md @@ -795,7 +803,12 @@ def dare_old(A,B,Q,R,S=None,E=None): raise e try: - X,rcond,w,S,U,A_inv = sb02md(n,A,G,Q,'D') + if stabilizing: + sort = 'S' + else: + sort = 'U' + + X, rcond, w, S, U, A_inv = sb02md(n, A, G, Q, 'D', sort=sort) except ValueError as ve: if ve.info < 0 or ve.info > 5: e = ValueError(ve.message) @@ -884,8 +897,12 @@ def dare_old(A,B,Q,R,S=None,E=None): # Solve the generalized algebraic Riccati equation by calling the # Slycot function sg02ad try: - rcondu,X,alfar,alfai,beta,S_o,T,U,iwarn = \ - sg02ad('D','B','N','U','N','N','S','R',n,m,0,A,E,B,Q,R,S) + if stabilizing: + sort = 'S' + else: + sort = 'U' + rcondu, X, alfar, alfai, beta, S_o, T, U, iwarn = \ + sg02ad('D', 'B', 'N', 'U', 'N', 'N', sort, 'R', n, m, 0, A, E, B, Q, R, S) except ValueError as ve: if ve.info < 0 or ve.info > 7: e = ValueError(ve.message) From 3ece9aa539e75f2e3a9524f8b6c10564d96702fa Mon Sep 17 00:00:00 2001 From: Daniel Bankmann Date: Thu, 21 Mar 2019 18:51:21 +0100 Subject: [PATCH 2/6] added unit test for riccati equation --- examples/test-riccati.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 examples/test-riccati.py diff --git a/examples/test-riccati.py b/examples/test-riccati.py new file mode 100644 index 000000000..ab6b49994 --- /dev/null +++ b/examples/test-riccati.py @@ -0,0 +1,30 @@ +import numpy as np +import control + + +#unit test for stabilizing and anti-stabilizing feedbacks +#continuous-time + +A = np.diag([1,-1]) +B = np.identity(2) +Q = np.identity(2) +R = np.identity(2) +S = 0 * B +E = np.identity(2) +X, L , G = control.care(A, B, Q, R, S, E, stabilizing=True) +assert np.all(np.real(L) < 0) +X, L , G = control.care(A, B, Q, R, S, E, stabilizing=False) +assert np.all(np.real(L) > 0) + + +#discrete-time +A = np.diag([0.5,2]) +B = np.identity(2) +Q = np.identity(2) +R = np.identity(2) +S = 0 * B +E = np.identity(2) +X, L , G = control.dare(A, B, Q, R, S, E, stabilizing=True) +assert np.all(np.abs(L) < 1) +X, L , G = control.dare(A, B, Q, R, S, E, stabilizing=False) +assert np.all(np.abs(L) > 1) From ed4590c32e1cc564ec7bf36f4ebe67b08fcb14a3 Mon Sep 17 00:00:00 2001 From: Daniel Bankmann Date: Sun, 24 Mar 2019 14:51:28 +0100 Subject: [PATCH 3/6] moved tests to appropriate position --- control/tests/statefbk_test.py | 32 ++++++++++++++++++++++++++++++++ examples/test-riccati.py | 30 ------------------------------ 2 files changed, 32 insertions(+), 30 deletions(-) delete mode 100644 examples/test-riccati.py diff --git a/control/tests/statefbk_test.py b/control/tests/statefbk_test.py index 35df769a2..66dce2b12 100644 --- a/control/tests/statefbk_test.py +++ b/control/tests/statefbk_test.py @@ -9,6 +9,7 @@ from control.statefbk import ctrb, obsv, place, place_varga, lqr, gram, acker from control.matlab import * from control.exception import slycot_check, ControlDimension +from control.mateqn import care, dare class TestStatefbk(unittest.TestCase): """Test state feedback functions""" @@ -298,6 +299,37 @@ def test_LQR_3args(self): K, S, poles = lqr(sys, Q, R) self.check_LQR(K, S, poles, Q, R) + @unittest.skipIf(not slycot_check(), "slycot not installed") + def test_care(self): + #unit test for stabilizing and anti-stabilizing feedbacks + #continuous-time + + A = np.diag([1,-1]) + B = np.identity(2) + Q = np.identity(2) + R = np.identity(2) + S = 0 * B + E = np.identity(2) + X, L , G = care(A, B, Q, R, S, E, stabilizing=True) + assert np.all(np.real(L) < 0) + X, L , G = care(A, B, Q, R, S, E, stabilizing=False) + assert np.all(np.real(L) > 0) + + @unittest.skipIf(not slycot_check(), "slycot not installed") + def test_dare(self): + #discrete-time + A = np.diag([0.5,2]) + B = np.identity(2) + Q = np.identity(2) + R = np.identity(2) + S = 0 * B + E = np.identity(2) + X, L , G = dare(A, B, Q, R, S, E, stabilizing=True) + assert np.all(np.abs(L) < 1) + X, L , G = dare(A, B, Q, R, S, E, stabilizing=False) + assert np.all(np.abs(L) > 1) + + def test_suite(): return unittest.TestLoader().loadTestsFromTestCase(TestStatefbk) diff --git a/examples/test-riccati.py b/examples/test-riccati.py deleted file mode 100644 index ab6b49994..000000000 --- a/examples/test-riccati.py +++ /dev/null @@ -1,30 +0,0 @@ -import numpy as np -import control - - -#unit test for stabilizing and anti-stabilizing feedbacks -#continuous-time - -A = np.diag([1,-1]) -B = np.identity(2) -Q = np.identity(2) -R = np.identity(2) -S = 0 * B -E = np.identity(2) -X, L , G = control.care(A, B, Q, R, S, E, stabilizing=True) -assert np.all(np.real(L) < 0) -X, L , G = control.care(A, B, Q, R, S, E, stabilizing=False) -assert np.all(np.real(L) > 0) - - -#discrete-time -A = np.diag([0.5,2]) -B = np.identity(2) -Q = np.identity(2) -R = np.identity(2) -S = 0 * B -E = np.identity(2) -X, L , G = control.dare(A, B, Q, R, S, E, stabilizing=True) -assert np.all(np.abs(L) < 1) -X, L , G = control.dare(A, B, Q, R, S, E, stabilizing=False) -assert np.all(np.abs(L) > 1) From a321925c0b1ab3b51ff650dde96fe4d1d79d9345 Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Tue, 19 Mar 2019 15:05:41 +0100 Subject: [PATCH 4/6] install scikit-build for testing with slycot --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a28f57419..396f266c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,6 +53,8 @@ before_install: # Install openblas if slycot is being used - if [[ "$SLYCOT" != "" ]]; then conda install openblas; + # also install scikit-build for the build process + conda install -c conda-forge scikit-build; fi # Make sure to look in the right place for python libraries (for slycot) - export LIBRARY_PATH="$HOME/miniconda/envs/test-environment/lib" From 0fad329cf5f3095c3966c31e7fbd71d362a8a3ac Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Mon, 25 Mar 2019 17:13:19 +0100 Subject: [PATCH 5/6] move the comment line in .travis.yml --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 396f266c1..3dc192f3f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,10 +51,10 @@ before_install: - conda create -q -n test-environment python="$TRAVIS_PYTHON_VERSION" pip coverage - source activate test-environment # Install openblas if slycot is being used + # also install scikit-build for the build process - if [[ "$SLYCOT" != "" ]]; then - conda install openblas; - # also install scikit-build for the build process - conda install -c conda-forge scikit-build; + conda install openblas; + conda install -c conda-forge scikit-build; fi # Make sure to look in the right place for python libraries (for slycot) - export LIBRARY_PATH="$HOME/miniconda/envs/test-environment/lib" From be598c1f797815fa3847d3114c5e4ec2b792d6bf Mon Sep 17 00:00:00 2001 From: Rene van Paassen Date: Mon, 25 Mar 2019 17:38:16 +0100 Subject: [PATCH 6/6] use Unix Makefiles for install with scikit-build --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3dc192f3f..a719fa131 100644 --- a/.travis.yml +++ b/.travis.yml @@ -67,10 +67,11 @@ install: - conda install $SCIPY matplotlib # Build slycot from source # For python 3, need to provide pointer to python library + # Use "Unix Makefiles" as generator, because Ninja cannot handle Fortran #! git clone https://github.com/repagh/Slycot.git slycot; - if [[ "$SLYCOT" != "" ]]; then git clone https://github.com/python-control/Slycot.git slycot; - cd slycot; python setup.py install; cd ..; + cd slycot; python setup.py install -G "Unix Makefiles"; cd ..; fi # command to run tests