def svd_gesvd(a, full_matrices=True, compute_uv=True, check_finite=True): """svd with LAPACK's '#gesvd' (with # = d/z for float/complex). Similar as :func:`numpy.linalg.svd`, but use LAPACK 'gesvd' driver. Works only with 2D arrays. Outer part is based on the code of `numpy.linalg.svd`. Parameters ---------- a, full_matrices, compute_uv : See :func:`numpy.linalg.svd` for details. check_finite : check whether input arrays contain 'NaN' or 'inf'. Returns ------- U, S, Vh : ndarray See :func:`numpy.linalg.svd` for details. """ a, wrap = _makearray(a) # uses order='C' _assertNoEmpty2d(a) _assertRank2(a) if check_finite: _assertFinite(a) M, N = a.shape # determine types t, result_t = _commonType(a) # t = type for calculation, (for my numpy version) actually always one of {double, cdouble} # result_t = one of {single, double, csingle, cdouble} is_complex = isComplexType(t) real_t = _realType(t) # real version of t with same precision # copy: the array is destroyed a = _fastCopyAndTranspose( t, a) # casts a to t, copy and transpose (=change to order='F') lapack_routine = _get_gesvd(t) # allocate output space & options if compute_uv: if full_matrices: nu = M lvt = N option = b'A' else: nu = min(N, M) lvt = min(N, M) option = b'S' u = np.zeros((M, nu), t, order='F') vt = np.zeros((lvt, N), t, order='F') else: option = b'N' nu = 1 u = np.empty((1, 1), t, order='F') vt = np.empty((1, 1), t, order='F') s = np.zeros((min(N, M), ), real_t, order='F') INFO = c_int(0) m = c_int(M) n = c_int(N) lu = c_int(u.shape[0]) lvt = c_int(vt.shape[0]) work = np.zeros((1, ), t) lwork = c_int(-1) # first call with lwork=-1 args = [option, option, m, n, a, m, s, u, lu, vt, lvt, work, lwork, INFO] if is_complex: # differnt call signature: additional array 'rwork' of fixed size rwork = np.zeros((5 * min(N, M), ), real_t) args.insert(-1, rwork) lapack_routine( *args) # first call: just calculate the required `work` size if INFO.value < 0: raise Exception('%d-th argument had an illegal value' % INFO.value) if is_complex: lwork = int(work[0].real) else: lwork = int(work[0]) work = np.zeros((lwork, ), t, order='F') args[11] = work args[12] = c_int(lwork) lapack_routine(*args) # second call: the actual calculation if INFO.value < 0: raise Exception('%d-th argument had an illegal value' % INFO.value) if INFO.value > 0: raise LinAlgError("SVD did not converge with 'gesvd'") s = s.astype(_realType(result_t)) if compute_uv: u = u.astype(result_t) # no repeated transpose: used fortran order vt = vt.astype(result_t) return wrap(u), s, wrap(vt) else: return s
def controllability_indices(A, B, tol=None): """Computes the controllability indices for a controllable pair (A, B) Controllability indices are defined as the maximum number of independent columns per input column of B in the following sense: consider the Kalman controllability matrix (widely known as Krylov sequence) :: C = [B, AB, ..., A^(n-1)B] We start testing (starting from the left-most) every column of this matrix whether it is a linear combination of the previous columns. Obviously, since C is (n x nm), there are many ways to pick a linearly independent subset. We select a version from [1]_. If a new column is dependent we delete that column and keep doing this until we are left with a full-rank square matrix (this is guaranteed if (A, B) is controllable). Then at some point, we are necessarily left with columns that are obtained from different input columns :: ̅C = [b₁,b₂,b₃...,Ab₁,Ab₃,...,A²b₁,A²b₃,...,A⁽ᵏ⁻¹⁾b₃,...] For example, it seems like Ab₂ is deleted due to dependence on the previous columns to its left. It can be shown that the higher powers will also be dependent and can be removed too. By reordering these columns, we combine the terms that involve each bⱼ :: ̅C = [b₁,Ab₁,A²b₁,b₂,b₃,Ab₃,A²b₃,...,A⁽ᵏ⁻¹⁾b₃,...] The controllability index associated with each input column is defined as the number of columns per bⱼ appearing in the resulting matrix. Here, 3 for first input 1 for second and so on. If B is not full rank then the index will be returned as 0 as that column bⱼ will be dropped too. Parameters ---------- A : ndarray 2D (n, n) real-valued array B : ndarray 2D (n, m) real-valued array tol : float Tolerance value for the Arnoldi iteration to decide linear dependence. By default it is `sqrt(eps)*n²` Returns ------- ind : ndarray 1D array that holds the computed controllability indices. The sum of the values add up to `n` if (A, B) is controllable. Notes ----- Though internally not used, this function can also be used as a controllability/observability test by summing up the resulting indices and comparing to `n`. References ---------- .. [1] : W.M. Wonham, "Linear Multivariable Control: A Geometric Approach", 3rd edition, 1985, Springer, ISBN:9780387960715 """ a, _ = _makearray(A) b, _ = _makearray(B) _assertRank2(a, b) _assertNoEmpty2d(a, b) _assertNdSquareness(a) n, m = b.shape if a.shape[0] != b.shape[0]: raise ValueError("A and B should have the same number of rows") # FIXME: Tolerance is a bit arbitrary for now!! tol = np.sqrt(np.spacing(1.)) * n**2 if tol is None else tol # Will be populated below remaining_cols = np.arange(m) indices = np.zeros(m, dtype=int) # Get the orthonormal columns of b first q, r, p = qr(b, mode='economic', pivoting=True) rank_b = sum(np.abs(np.diag(r)) > max(m, n) * np.spacing(norm(b, 2))) remaining_cols = remaining_cols[p][:rank_b].tolist() q = q[:, :rank_b] indices[remaining_cols] += 1 w = np.empty((n, 1), dtype=float) # Start the iteration - at most n-1 spins for k in range(1, n): # prepare new A @ Q test vectors q_bank = a @ q[:, -len(remaining_cols):] for ind, col in enumerate(remaining_cols.copy()): w[:] = q_bank[:, [ind]] for reorthogonalization in range(2): w -= ((q.T @ w).T * q).sum(axis=1, keepdims=True) normw = norm(w) if normw <= tol: remaining_cols.remove(col) continue else: q = np.append(q, w / normw, axis=1) indices[col] += 1 if len(remaining_cols) == 0: break return indices
def controllability_indices(A, B, tol=None): """Computes the controllability indices for a controllable pair (A, B) Controllability indices are defined as the maximum number of independent columns per input column of B in the following sense: consider the Kalman controllability matrix (widely known as Krylov sequence) :: C = [B, AB, ..., A^(n-1)B] We start testing (starting from the left-most) every column of this matrix whether it is a linear combination of the previous columns. Obviously, since C is (n x nm), there are many ways to pick a linearly independent subset. We select a version from [1]_. If a new column is dependent we delete that column and keep doing this until we are left with a full-rank square matrix (this is guaranteed if (A, B) is controllable). Then at some point, we are necessarily left with columns that are obtained from different input columns :: ̅C = [b₁,b₂,b₃...,Ab₁,Ab₃,...,A²b₁,A²b₃,...,A⁽ᵏ⁻¹⁾b₃,...] For example, it seems like Ab₂ is deleted due to dependence on the previous columns to its left. It can be shown that the higher powers will also be dependent and can be removed too. By reordering these columns, we combine the terms that involve each bⱼ :: ̅C = [b₁,Ab₁,A²b₁,b₂,b₃,Ab₃,A²b₃,...,A⁽ᵏ⁻¹⁾b₃,...] The controllability index associated with each input column is defined as the number of columns per bⱼ appearing in the resulting matrix. Here, 3 for first input 1 for second and so on. If B is not full rank then the index will be returned as 0 as that column bⱼ will be dropped too. Parameters ---------- A : ndarray 2D (n, n) real-valued array B : ndarray 2D (n, m) real-valued array tol : float Tolerance value for the Arnoldi iteration to decide linear dependence. By default it is `sqrt(eps)*n²` Returns ------- ind : ndarray 1D array that holds the computed controllability indices. The sum of the values add up to `n` if (A, B) is controllable. Notes ----- Though internally not used, this function can also be used as a controllability/observability test by summing up the resulting indices and comparing to `n`. References ---------- .. [1] : W.M. Wonham, "Linear Multivariable Control: A Geometric Approach", 3rd edition, 1985, Springer, ISBN:9780387960715 """ a, _ = _makearray(A) b, _ = _makearray(B) _assertRank2(a, b) _assertNoEmpty2d(a, b) _assertNdSquareness(a) n, m = b.shape if a.shape[0] != b.shape[0]: raise ValueError("A and B should have the same number of rows") # FIXME: Tolerance is a bit arbitrary for now!! tol = np.sqrt(np.spacing(1.))*n**2 if tol is None else tol # Will be populated below remaining_cols = np.arange(m) indices = np.zeros(m, dtype=int) # Get the orthonormal columns of b first q, r, p = qr(b, mode='economic', pivoting=True) rank_b = sum(np.abs(np.diag(r)) > max(m, n)*np.spacing(norm(b, 2))) remaining_cols = remaining_cols[p][:rank_b].tolist() q = q[:, :rank_b] indices[remaining_cols] += 1 w = np.empty((n, 1), dtype=float) # Start the iteration - at most n-1 spins for k in range(1, n): # prepare new A @ Q test vectors q_bank = a @ q[:, -len(remaining_cols):] for ind, col in enumerate(remaining_cols.copy()): w[:] = q_bank[:, [ind]] for reorthogonalization in range(2): w -= ((q.T @ w).T * q).sum(axis=1, keepdims=True) normw = norm(w) if normw <= tol: remaining_cols.remove(col) continue else: q = np.append(q, w/normw, axis=1) indices[col] += 1 if len(remaining_cols) == 0: break return indices
def lstsq(a, b, rcond=-1): """ Return the least-squares solution to an equation. Solves the equation `a x = b` by computing a vector `x` that minimizes the norm `|| b - a x ||`. Parameters ---------- a : array_like, shape (M, N) Input equation coefficients. b : array_like, shape (M,) or (M, K) Equation target values. If `b` is two-dimensional, the least squares solution is calculated for each of the `K` target sets. rcond : float, optional Cutoff for ``small`` singular values of `a`. Singular values smaller than `rcond` times the largest singular value are considered zero. Returns ------- x : ndarray, shape(N,) or (N, K) Least squares solution. The shape of `x` depends on the shape of `b`. residues : ndarray, shape(), (1,), or (K,) Sums of residues; squared Euclidian norm for each column in `b - a x`. If the rank of `a` is < N or > M, this is an empty array. If `b` is 1-dimensional, this is a (1,) shape array. Otherwise the shape is (K,). rank : integer Rank of matrix `a`. s : ndarray, shape(min(M,N),) Singular values of `a`. Raises ------ LinAlgError If computation does not converge. Notes ----- If `b` is a matrix, then all array results returned as matrices. Examples -------- Fit a line, ``y = mx + c``, through some noisy data-points: >>> x = np.array([0, 1, 2, 3]) >>> y = np.array([-1, 0.2, 0.9, 2.1]) By examining the coefficients, we see that the line should have a gradient of roughly 1 and cuts the y-axis at more-or-less -1. We can rewrite the line equation as ``y = Ap``, where ``A = [[x 1]]`` and ``p = [[m], [c]]``. Now use `lstsq` to solve for `p`: >>> A = np.vstack([x, np.ones(len(x))]).T >>> A array([[ 0., 1.], [ 1., 1.], [ 2., 1.], [ 3., 1.]]) >>> m, c = np.linalg.lstsq(A, y)[0] >>> print m, c 1.0 -0.95 Plot the data along with the fitted line: >>> import matplotlib.pyplot as plt >>> plt.plot(x, y, 'o', label='Original data', markersize=10) >>> plt.plot(x, m*x + c, 'r', label='Fitted line') >>> plt.legend() >>> plt.show() """ import math a, _ = _makearray(a) b, wrap = _makearray(b) is_1d = len(b.shape) == 1 if is_1d: b = b[:, newaxis] _assertRank2(a, b) m = a.shape[0] n = a.shape[1] n_rhs = b.shape[1] ldb = max(n, m) if m != b.shape[0]: raise LinAlgError, 'Incompatible dimensions' t, result_t = _commonType(a, b) real_t = _linalgRealType(t) bstar = zeros((ldb, n_rhs), t) bstar[:b.shape[0],:n_rhs] = b.copy() a, bstar = _fastCopyAndTranspose(t, a, bstar) s = zeros((min(m, n),), real_t) nlvl = max( 0, int( math.log( float(min(m, n))/2. ) ) + 1 ) iwork = zeros((3*min(m, n)*nlvl+11*min(m, n),), fortran_int) if isComplexType(t): lapack_routine = lapack_lite.zgelsd lwork = 1 rwork = zeros((lwork,), real_t) work = zeros((lwork,), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, -1, rwork, iwork, 0) lwork = int(abs(work[0])) rwork = zeros((lwork,), real_t) a_real = zeros((m, n), real_t) bstar_real = zeros((ldb, n_rhs,), real_t) results = lapack_lite.dgelsd(m, n, n_rhs, a_real, m, bstar_real, ldb, s, rcond, 0, rwork, -1, iwork, 0) lrwork = int(rwork[0]) work = zeros((lwork,), t) rwork = zeros((lrwork,), real_t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, lwork, rwork, iwork, 0) else: lapack_routine = lapack_lite.dgelsd lwork = 1 work = zeros((lwork,), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, -1, iwork, 0) lwork = int(work[0]) work = zeros((lwork,), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, lwork, iwork, 0) if results['info'] > 0: raise LinAlgError, 'SVD did not converge in Linear Least Squares' resids = array([], t) if is_1d: x = array(ravel(bstar)[:n], dtype=result_t, copy=True) if results['rank'] == n and m > n: resids = array([np.linalg.norm(ravel(bstar)[n:], 2)**2]) else: x = array(transpose(bstar)[:n,:], dtype=result_t, copy=True) if results['rank'] == n and m > n: resids = array([np.linalg.norm(v[n:], 2)**2 for v in bstar]) st = s[:min(n, m)].copy().astype(_realType(result_t)) return wrap(x), wrap(resids), results['rank'], st
def lstsq(a, b, rcond=-1): """ Return the least-squares solution to an equation. Solves the equation `a x = b` by computing a vector `x` that minimizes the norm `|| b - a x ||`. Parameters ---------- a : array_like, shape (M, N) Input equation coefficients. b : array_like, shape (M,) or (M, K) Equation target values. If `b` is two-dimensional, the least squares solution is calculated for each of the `K` target sets. rcond : float, optional Cutoff for ``small`` singular values of `a`. Singular values smaller than `rcond` times the largest singular value are considered zero. Returns ------- x : ndarray, shape(N,) or (N, K) Least squares solution. The shape of `x` depends on the shape of `b`. residues : ndarray, shape(), (1,), or (K,) Sums of residues; squared Euclidian norm for each column in `b - a x`. If the rank of `a` is < N or > M, this is an empty array. If `b` is 1-dimensional, this is a (1,) shape array. Otherwise the shape is (K,). rank : integer Rank of matrix `a`. s : ndarray, shape(min(M,N),) Singular values of `a`. Raises ------ LinAlgError If computation does not converge. Notes ----- If `b` is a matrix, then all array results returned as matrices. Examples -------- Fit a line, ``y = mx + c``, through some noisy data-points: >>> x = np.array([0, 1, 2, 3]) >>> y = np.array([-1, 0.2, 0.9, 2.1]) By examining the coefficients, we see that the line should have a gradient of roughly 1 and cuts the y-axis at more-or-less -1. We can rewrite the line equation as ``y = Ap``, where ``A = [[x 1]]`` and ``p = [[m], [c]]``. Now use `lstsq` to solve for `p`: >>> A = np.vstack([x, np.ones(len(x))]).T >>> A array([[ 0., 1.], [ 1., 1.], [ 2., 1.], [ 3., 1.]]) >>> m, c = np.linalg.lstsq(A, y)[0] >>> print m, c 1.0 -0.95 Plot the data along with the fitted line: >>> import matplotlib.pyplot as plt >>> plt.plot(x, y, 'o', label='Original data', markersize=10) >>> plt.plot(x, m*x + c, 'r', label='Fitted line') >>> plt.legend() >>> plt.show() """ import math a, _ = _makearray(a) b, wrap = _makearray(b) is_1d = len(b.shape) == 1 if is_1d: b = b[:, newaxis] _assertRank2(a, b) m = a.shape[0] n = a.shape[1] n_rhs = b.shape[1] ldb = max(n, m) if m != b.shape[0]: raise LinAlgError, 'Incompatible dimensions' t, result_t = _commonType(a, b) real_t = _linalgRealType(t) bstar = zeros((ldb, n_rhs), t) bstar[:b.shape[0], :n_rhs] = b.copy() a, bstar = _fastCopyAndTranspose(t, a, bstar) s = zeros((min(m, n), ), real_t) nlvl = max(0, int(math.log(float(min(m, n)) / 2.)) + 1) iwork = zeros((3 * min(m, n) * nlvl + 11 * min(m, n), ), fortran_int) if isComplexType(t): lapack_routine = lapack_lite.zgelsd lwork = 1 rwork = zeros((lwork, ), real_t) work = zeros((lwork, ), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, -1, rwork, iwork, 0) lwork = int(abs(work[0])) rwork = zeros((lwork, ), real_t) a_real = zeros((m, n), real_t) bstar_real = zeros(( ldb, n_rhs, ), real_t) results = lapack_lite.dgelsd(m, n, n_rhs, a_real, m, bstar_real, ldb, s, rcond, 0, rwork, -1, iwork, 0) lrwork = int(rwork[0]) work = zeros((lwork, ), t) rwork = zeros((lrwork, ), real_t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, lwork, rwork, iwork, 0) else: lapack_routine = lapack_lite.dgelsd lwork = 1 work = zeros((lwork, ), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, -1, iwork, 0) lwork = int(work[0]) work = zeros((lwork, ), t) results = lapack_routine(m, n, n_rhs, a, m, bstar, ldb, s, rcond, 0, work, lwork, iwork, 0) if results['info'] > 0: raise LinAlgError, 'SVD did not converge in Linear Least Squares' resids = array([], t) if is_1d: x = array(ravel(bstar)[:n], dtype=result_t, copy=True) if results['rank'] == n and m > n: resids = array([np.linalg.norm(ravel(bstar)[n:], 2)**2]) else: x = array(transpose(bstar)[:n, :], dtype=result_t, copy=True) if results['rank'] == n and m > n: resids = array([np.linalg.norm(v[n:], 2)**2 for v in bstar]) st = s[:min(n, m)].copy().astype(_realType(result_t)) return wrap(x), wrap(resids), results['rank'], st