Example #1
0
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
Example #2
0
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
Example #3
0
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
Example #4
0
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
Example #5
0
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