def solve_lyap_dense(A, E, B, trans=False, options=None): """Compute the solution of a Lyapunov equation. See :func:`pymor.algorithms.lyapunov.solve_lyap_dense` for a general description. This function uses `slycot.sb03md` (if `E is None`) and `slycot.sg03ad` (if `E is not None`), which are based on the Bartels-Stewart algorithm. Parameters ---------- A The operator A as a 2D |NumPy array|. E The operator E as a 2D |NumPy array| or `None`. B The operator B as a 2D |NumPy array|. trans Whether the first operator in the Lyapunov equation is transposed. options The solver options to use (see :func:`lyap_dense_solver_options`). Returns ------- X Lyapunov equation solution as a |NumPy array|. """ _solve_lyap_dense_check_args(A, E, B, trans) options = _parse_options(options, lyap_dense_solver_options(), 'slycot_bartels-stewart', None, False) if options['type'] == 'slycot_bartels-stewart': n = A.shape[0] C = -B.dot(B.T) if not trans else -B.T.dot(B) trana = 'T' if not trans else 'N' dico = 'C' job = 'B' if E is None: U = np.zeros((n, n)) X, scale, sep, ferr, _ = slycot.sb03md(n, C, A, U, dico, job=job, trana=trana) _solve_check(A.dtype, 'slycot.sb03md', sep, ferr) else: fact = 'N' uplo = 'L' Q = np.zeros((n, n)) Z = np.zeros((n, n)) _, _, _, _, X, scale, sep, ferr, _, _, _ = slycot.sg03ad(dico, job, fact, trana, uplo, n, A, E, Q, Z, C) _solve_check(A.dtype, 'slycot.sg03ad', sep, ferr) X /= scale else: raise ValueError(f"Unexpected Lyapunov equation solver ({options['type']}).") return X
def lyap(A,Q,C=None,E=None): """ X = lyap(A,Q) solves the continuous-time Lyapunov equation A X + X A^T + Q = 0 where A and Q are square matrices of the same dimension. Further, Q must be symmetric. X = lyap(A,Q,C) solves the Sylvester equation A X + X Q + C = 0 where A and Q are square matrices. X = lyap(A,Q,None,E) solves the generalized continuous-time Lyapunov equation A X E^T + E X A^T + Q = 0 where Q is a symmetric matrix and A, Q and E are square matrices of the same dimension. """ # Make sure we have access to the right slycot routines try: from slycot import sb03md except ImportError: raise ControlSlycot("can't find slycot module 'sb03md'") try: from slycot import sb04md except ImportError: raise ControlSlycot("can't find slycot module 'sb04md'") # Reshape 1-d arrays if len(shape(A)) == 1: A = A.reshape(1,A.size) if len(shape(Q)) == 1: Q = Q.reshape(1,Q.size) if C != None and len(shape(C)) == 1: C = C.reshape(1,C.size) if E != None and len(shape(E)) == 1: E = E.reshape(1,E.size) # Determine main dimensions if size(A) == 1: n = 1 else: n = size(A,0) if size(Q) == 1: m = 1 else: m = size(Q,0) # Solve standard Lyapunov equation if C==None and E==None: # Check input data for consistency if shape(A) != shape(Q): raise ControlArgument("A and Q must be matrices of identical \ sizes.") if size(A) > 1 and shape(A)[0] != shape(A)[1]: raise ControlArgument("A must be a quadratic matrix.") if size(Q) > 1 and shape(Q)[0] != shape(Q)[1]: raise ControlArgument("Q must be a quadratic matrix.") if not (asarray(Q) == asarray(Q).T).all(): raise ControlArgument("Q must be a symmetric matrix.") # Solve the Lyapunov equation by calling Slycot function sb03md try: X,scale,sep,ferr,w = sb03md(n,-Q,A,eye(n,n),'C',trana='T') except ValueError(ve): if ve.info < 0: e = ValueError(ve.message) e.info = ve.info elif ve.info == n+1: e = ValueError("The matrix A and -A have common or very \ close eigenvalues.") e.info = ve.info else: e = ValueError("The QR algorithm failed to compute all \ the eigenvalues (see LAPACK Library routine DGEES).") e.info = ve.info raise e # Solve the Sylvester equation elif C != None and E==None: # Check input data for consistency if size(A) > 1 and shape(A)[0] != shape(A)[1]: raise ControlArgument("A must be a quadratic matrix.") if size(Q) > 1 and shape(Q)[0] != shape(Q)[1]: raise ControlArgument("Q must be a quadratic matrix.") if (size(C) > 1 and shape(C)[0] != n) or \ (size(C) > 1 and shape(C)[1] != m) or \ (size(C) == 1 and size(A) != 1) or (size(C) == 1 and size(Q) != 1): raise ControlArgument("C matrix has incompatible dimensions.") # Solve the Sylvester equation by calling the Slycot function sb04md try: X = sb04md(n,m,A,Q,-C) except ValueError(ve): if ve.info < 0: e = ValueError(ve.message) e.info = ve.info elif ve.info > m: e = ValueError("A singular matrix was encountered whilst \ solving for the %i-th column of matrix X." % ve.info-m) e.info = ve.info else: e = ValueError("The QR algorithm failed to compute all the \ eigenvalues (see LAPACK Library routine DGEES).") e.info = ve.info raise e # Solve the generalized Lyapunov equation elif C == None and E != None: # Check input data for consistency if (size(Q) > 1 and shape(Q)[0] != shape(Q)[1]) or \ (size(Q) > 1 and shape(Q)[0] != n) or \ (size(Q) == 1 and n > 1): raise ControlArgument("Q must be a square matrix with the same \ dimension as A.") if (size(E) > 1 and shape(E)[0] != shape(E)[1]) or \ (size(E) > 1 and shape(E)[0] != n) or \ (size(E) == 1 and n > 1): raise ControlArgument("E must be a square matrix with the same \ dimension as A.") if not (asarray(Q) == asarray(Q).T).all(): raise ControlArgument("Q must be a symmetric matrix.") # Make sure we have access to the write slicot routine try: from slycot import sg03ad except ImportError: raise ControlSlycot("can't find slycot module 'sg03ad'") # Solve the generalized Lyapunov equation by calling Slycot # function sg03ad try: A,E,Q,Z,X,scale,sep,ferr,alphar,alphai,beta = \ sg03ad('C','B','N','T','L',n,A,E,eye(n,n),eye(n,n),-Q) except ValueError(ve): if ve.info < 0 or ve.info > 4: e = ValueError(ve.message) e.info = ve.info elif ve.info == 1: e = ValueError("The matrix contained in the upper \ Hessenberg part of the array A is not in \ upper quasitriangular form") e.info = ve.info elif ve.info == 2: e = ValueError("The pencil A - lambda * E cannot be \ reduced to generalized Schur form: LAPACK \ routine DGEGS has failed to converge") e.info = ve.info elif ve.info == 4: e = ValueError("The pencil A - lambda * E has a \ degenerate pair of eigenvalues. That is, \ lambda_i = lambda_j for some i and j, where \ lambda_i and lambda_j are eigenvalues of \ A - lambda * E. Hence, the equation is \ singular; perturbed values were \ used to solve the equation (but the matrices \ A and E are unchanged)") e.info = ve.info raise e # Invalid set of input parameters else: raise ControlArgument("Invalid set of input parameters") return X
def dlyap(A, Q, C=None, E=None): """ dlyap(A,Q) solves the discrete-time Lyapunov equation :math:`A X A^T - X + Q = 0` where A and Q are square matrices of the same dimension. Further Q must be symmetric. dlyap(A,Q,C) solves the Sylvester equation :math:`A X Q^T - X + C = 0` where A and Q are square matrices. dlyap(A,Q,None,E) solves the generalized discrete-time Lyapunov equation :math:`A X A^T - E X E^T + Q = 0` where Q is a symmetric matrix and A, Q and E are square matrices of the same dimension. """ # Make sure we have access to the right slycot routines if sb03md is None: raise ControlSlycot("can't find slycot module 'sb03md'") if sb04qd is None: raise ControlSlycot("can't find slycot module 'sb04qd'") if sg03ad is None: raise ControlSlycot("can't find slycot module 'sg03ad'") # Reshape 1-d arrays if len(shape(A)) == 1: A = A.reshape(1, A.size) if len(shape(Q)) == 1: Q = Q.reshape(1, Q.size) if C is not None and len(shape(C)) == 1: C = C.reshape(1, C.size) if E is not None and len(shape(E)) == 1: E = E.reshape(1, E.size) # Determine main dimensions if size(A) == 1: n = 1 else: n = size(A, 0) if size(Q) == 1: m = 1 else: m = size(Q, 0) # Solve standard Lyapunov equation if C is None and E is None: # Check input data for consistency if shape(A) != shape(Q): raise ControlArgument("A and Q must be matrices of identical \ sizes.") if size(A) > 1 and shape(A)[0] != shape(A)[1]: raise ControlArgument("A must be a quadratic matrix.") if size(Q) > 1 and shape(Q)[0] != shape(Q)[1]: raise ControlArgument("Q must be a quadratic matrix.") if not _is_symmetric(Q): raise ControlArgument("Q must be a symmetric matrix.") # Solve the Lyapunov equation by calling the Slycot function sb03md try: X, scale, sep, ferr, w = \ sb03md(n, -Q, A, eye(n, n), 'D', trana='T') except ValueError as ve: if ve.info < 0: e = ValueError(ve.message) e.info = ve.info else: e = ValueError("The QR algorithm failed to compute all the \ eigenvalues (see LAPACK Library routine DGEES).") e.info = ve.info raise e # Solve the Sylvester equation elif C is not None and E is None: # Check input data for consistency if size(A) > 1 and shape(A)[0] != shape(A)[1]: raise ControlArgument("A must be a quadratic matrix") if size(Q) > 1 and shape(Q)[0] != shape(Q)[1]: raise ControlArgument("Q must be a quadratic matrix") if (size(C) > 1 and shape(C)[0] != n) or \ (size(C) > 1 and shape(C)[1] != m) or \ (size(C) == 1 and size(A) != 1) or (size(C) == 1 and size(Q) != 1): raise ControlArgument("C matrix has incompatible dimensions") # Solve the Sylvester equation by calling Slycot function sb04qd try: X = sb04qd(n, m, -A, asarray(Q).T, C) except ValueError as ve: if ve.info < 0: e = ValueError(ve.message) e.info = ve.info elif ve.info > m: e = ValueError("A singular matrix was encountered whilst \ solving for the %i-th column of matrix X." % ve.info - m) e.info = ve.info else: e = ValueError("The QR algorithm failed to compute all the \ eigenvalues (see LAPACK Library routine DGEES)") e.info = ve.info raise e # Solve the generalized Lyapunov equation elif C is None and E is not None: # Check input data for consistency if (size(Q) > 1 and shape(Q)[0] != shape(Q)[1]) or \ (size(Q) > 1 and shape(Q)[0] != n) or \ (size(Q) == 1 and n > 1): raise ControlArgument("Q must be a square matrix with the same \ dimension as A.") if (size(E) > 1 and shape(E)[0] != shape(E)[1]) or \ (size(E) > 1 and shape(E)[0] != n) or \ (size(E) == 1 and n > 1): raise ControlArgument("E must be a square matrix with the same \ dimension as A.") if not _is_symmetric(Q): raise ControlArgument("Q must be a symmetric matrix.") # Solve the generalized Lyapunov equation by calling Slycot # function sg03ad try: A, E, Q, Z, X, scale, sep, ferr, alphar, alphai, beta = \ sg03ad('D', 'B', 'N', 'T', 'L', n, A, E, eye(n, n), eye(n, n), -Q) except ValueError as ve: if ve.info < 0 or ve.info > 4: e = ValueError(ve.message) e.info = ve.info elif ve.info == 1: e = ValueError("The matrix contained in the upper \ Hessenberg part of the array A is not in \ upper quasitriangular form") e.info = ve.info elif ve.info == 2: e = ValueError("The pencil A - lambda * E cannot be \ reduced to generalized Schur form: LAPACK \ routine DGEGS has failed to converge") e.info = ve.info elif ve.info == 3: e = ValueError("The pencil A - lambda * E has a \ pair of reciprocal eigenvalues. That is, \ lambda_i = 1/lambda_j for some i and j, \ where lambda_i and lambda_j are eigenvalues \ of A - lambda * E. Hence, the equation is \ singular; perturbed values were \ used to solve the equation (but the \ matrices A and E are unchanged)") e.info = ve.info raise e # Invalid set of input parameters else: raise ControlArgument("Invalid set of input parameters") return _ssmatrix(X)
def solve_lyap(A, E, B, trans=False, options=None): """Find a factor of the solution of a Lyapunov equation. Returns factor :math:`Z` such that :math:`Z Z^T` is approximately the solution :math:`X` of a Lyapunov equation (if E is `None`). .. math:: A X + X A^T + B B^T = 0 or generalized Lyapunov equation .. math:: A X E^T + E X A^T + B B^T = 0. If trans is `True`, then it solves (if E is `None`) .. math:: A^T X + X A + B^T B = 0 or .. math:: A^T X E + E^T X A + B^T B = 0. This uses the `slycot` package, in particular its interfaces to SLICOT functions `SB03MD` (for the standard Lyapunov equations) and `SG03AD` (for the generalized Lyapunov equations). These methods are only applicable to medium-sized dense problems and need access to the matrix data of all operators. Parameters ---------- A The |Operator| A. E The |Operator| E or `None`. B The |Operator| B. trans If the dual equation needs to be solved. options The |solver_options| to use (see :func:`lyap_solver_options`). Returns ------- Z Low-rank factor of the Lyapunov equation solution, |VectorArray| from `A.source`. """ _solve_lyap_check_args(A, E, B, trans) options = _parse_options(options, lyap_solver_options(), 'slycot', None, False) assert options['type'] == 'slycot' import slycot A_mat = to_matrix(A, format='dense') if E is not None: E_mat = to_matrix(E, format='dense') B_mat = to_matrix(B, format='dense') n = A_mat.shape[0] if not trans: C = -B_mat.dot(B_mat.T) trana = 'T' else: C = -B_mat.T.dot(B_mat) trana = 'N' dico = 'C' if E is None: U = np.zeros((n, n)) X, scale, _, _, _ = slycot.sb03md(n, C, A_mat, U, dico, trana=trana) else: job = 'B' fact = 'N' Q = np.zeros((n, n)) Z = np.zeros((n, n)) uplo = 'L' X = C _, _, _, _, X, scale, _, _, _, _, _ = slycot.sg03ad(dico, job, fact, trana, uplo, n, A_mat, E_mat, Q, Z, X) from pymor.bindings.scipy import chol Z = chol(X, copy=False) Z = A.source.from_numpy(np.array(Z).T) return Z