__all__ = ["solve_linear_equations",
           "inverse",
           "cholesky_decomposition",
           "qr_decomposition",
           "eigenvalues",
           "Heigenvalues",
           "eigenvectors",
           "Heigenvectors",
           "singular_value_decomposition",
           "generalized_inverse",
           "determinant",
           "linear_least_squares",
           "test"]

# This module is a lite version of LinAlg.py module which contains
# high-level Python interface to the LAPACK library.  The lite versioho
# only accesses the following LAPACK functions: dgesv, zgesv, dgeev,
# zgeev, dgesdd, zgesdd, dgelsd, zgelsd, dsyevd, zheevd, dgetrf, dpotrf.

import numarray.numeric as num
import numarray as na
import copy
import lapack_lite2
import math
import mlab

# import multiarray     # just used for fast 2D tranpose
multiarray = num 

class LinearAlgebraError(Exception):
    pass
LinAlgError = LinearAlgebraError

def __setup__():
    import sys
    sys.float_output_suppress_small = True

__test__ = {
"determiniant" : 
"""
>>> import numarray.linear_algebra as la, numarray.random_array as random_array
>>> A = random_array.random([100,5,5])
>>> _isClose(map(la.determinant, A), determinant(A))
1
>>> _isClose(la.determinant(A[0]), determinant(A[0]))
1
>>> A = random_array.random([100,5,5]) + 1j*random_array.random([100,5,5])
>>> _isClose(map(la.determinant, A), determinant(A))
1
>>> _isClose(la.determinant(A[0]), determinant(A[0]))
1

""",
"inverse" : 
"""
>>> import numarray.linear_algebra as la, numarray.random_array as random_array
>>> A = []
>>> while len(A) < 100:
...     candidate = random_array.random([5,5])
...     if abs(la.determinant(candidate)) > 1e-3:
...         A.append(candidate)
>>> A = na.asarray(A)
>>> _isClose(map(la.inverse, A), inverse(A))
1
>>> _isClose(la.inverse(A[0]), inverse(A[0]))
1
>>> A = []
>>> while len(A) < 100:
...     candidate = random_array.random([5,5]) + 1j*random_array.random([5,5])
...     if abs(la.determinant(candidate)) > 1e-3:
...         A.append(candidate)
>>> A = na.asarray(A)
>>> _isClose(map(la.inverse, A), inverse(A))
1
>>> _isClose(la.inverse(A[0]), inverse(A[0]))
1

""",
"solve_linear_equations" : 
"""
>>> import numarray.linear_algebra as la, numarray.random_array as random_array
>>> A = []
>>> while len(A) < 100:
...     candidate = random_array.random([5,5])
...     if abs(la.determinant(candidate)) > 1e-3:
...         A.append(candidate)
>>> A = na.asarray(A)
>>> B = random_array.random([100,5])
>>> _isClose(map(la.solve_linear_equations, A, B), solve_linear_equations(A, B))
1
>>> _isClose(la.solve_linear_equations(A[0], B[0]), la.solve_linear_equations(A[0], B[0]))
1
   
    
"""
}

# Helper routines
_lapack_type = { num.Float32: 0, num.Float64: 1,
                 num.Complex32: 2, num.Complex64: 3}
_lapack_letter = ['s', 'd', 'c', 'z']

_array_kind = {
    num.Bool:0,
    num.Int8:0,
    num.UInt8:0,
    num.Int16:0,
    num.UInt16:0,    
    num.Int32:0,
    num.UInt32:0,
    num.Int64:0,
    num.UInt64:0,
    num.Float32: 0,
    num.Float64: 0,
    num.Complex32: 1,
    num.Complex64: 1}

_array_precision = {
    num.Bool: 1,
    num.Int8: 1,
    num.UInt8: 1,
    num.Int16: 1,
    num.UInt16: 1,
    num.Int32: 1,
    num.UInt32: 1,
    num.Int64: 1,
    num.UInt64: 1,
    num.Float32: 0,
    num.Float64: 1,
    num.Complex32: 0,
    num.Complex64: 1
    }

_array_type = [[num.Float32, num.Float64],
               [num.Complex32, num.Complex64]]

def _commonType(*numarray):
    kind = 0
#    precision = 0
#   force higher precision in lite version
    precision = 1
    for a in numarray:
        t = a.type()
        kind = max(kind, _array_kind[t])
        precision = max(precision, _array_precision[t])
    return _array_type[kind][precision]

def _castCopyAndTranspose(type, *numarray, **kargs):
    # This should work as a drop in replacement for LinearAlgegra2._castCopyAndTranspose
    indices = kargs.get("indices")
    cast_numarray = []
    for a in numarray:
        if indices is None:
            a = na.transpose(a)
        else:
            a = na.transpose(a, indices)
        if a.type() == type:
            cast_numarray.append(a.copy())
        else:
            cast_numarray.append(a.astype(type))
    if len(cast_numarray) == 1:
            return cast_numarray[0]
    else:
        return cast_numarray


"""
# _fastCopyAndTranpose is an optimized version of _castCopyAndTranspose.
# It assumes the input is 2D (as all the calls in here are).

_fastCT = multiarray._fastCopyAndTranspose

def _fastCopyAndTranspose(type, *numarray):
    cast_numarray = ()
    for a in numarray:
        if a.type() == type:
            cast_numarray = cast_numarray + (_fastCT(a),)
        else:
            cast_numarray = cast_numarray + (_fastCT(a.astype(type)),)
    if len(cast_numarray) == 1:
            return cast_numarray[0]
    else:
        return cast_numarray
"""

_fastCopyAndTranspose = _castCopyAndTranspose


def _assertRank(rank, *args):
    for a in args:
        if isinstance(rank, int):
            rank = (rank,)
        if len(a.shape) not in rank:
            raise LinAlgError, 'Array must be rank %s' % (' or '.join([str(x) for x in rank]))
   
def _assertRank2(*numarray):
    for a in numarray:
        if len(a.getshape()) != 2:
            raise LinAlgError, 'Array must be two-dimensional'

def _assertSubmatrixSquareness(*args):
    for a in args:
        if a.shape[-1] != a.shape[-2]:
            raise LinAlgError, 'Array (or it submatrices) must be square'


def _isClose(a, b, *args, **kargs):
    return bool(na.allclose(na.ravel(a), na.ravel(b), *args, **kargs))

def _assertSquareness(*numarray):
    for a in numarray:
        if max(a.getshape()) != min(a.getshape()):
            raise LinAlgError, 'Array must be square'
    

# Linear equations
        
def solve_linear_equations(a, b):
    """solve_linear_equations(a, b) -> x such that dot(a,x) = b
    
    *a* may be either rank-2 or rank-3, in either case, it 
    must be square along the last two axes *b* may either 
    have a rank one lower than *a*, in which case it     
    represents a vector or an array of vectors, or it may be 
    the same rank as *a* in which case it represents a matrix 
    or an array of matrices.
    
    Since that may be a  bit confusing let's look at some examples.
    First the simplest case, a square matrix A, and a vector of 
    results B.
    
    >>> A = [[1,2,3], [3,5,5], [5,6,7]]
    >>> B = [1,1,1]
    >>> x = solve_linear_equations(A, B)
    >>> _isClose(x, num.array([-0.5,  0. ,  0.5]))
    1
    >>> _isClose(na.dot(A, x), # This should give us B
    ...        num.array([ 1.,  1.,  1.]))
    1
    
    The next simplest case is a square matrix A and a matrix B.
    
    >>> B = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
    >>> _isClose(solve_linear_equations(A, B),
    ...     num.array([[-0.625, -0.5  ,  0.625],
    ...                [-0.5  ,  1.   , -0.5  ],
    ...                [ 0.875, -0.5  ,  0.125]]))
    1
           
    If *a* is rank-3, then the first dimension of *a* **and** *b* is 
    interpreted as selecting different submatrices or subvectors to operate
    on. In this case, *b* will be rank-2 in the vector case and rank-3 in
    the matrix case. Here is what is looks like in the vector case.
    
    >>> A = [[[1, 3], [2j, 3j]],
    ...      [[2, 4], [4j, 4j]],
    ...      [[3, 5], [6j, 5j]]]
    >>> B = [[1, 0], [0, 1], [1, 1]]
    >>> _isClose(solve_linear_equations(A, B),
    ...      num.array([[-1.        +0.j        ,  0.66666667+0.j        ],
    ...                 [ 0.        -0.5j       ,  0.        +0.25j      ],
    ...                 [-0.33333333-0.33333333j,  0.4       +0.2j       ]]))
    1
           
    The first dimensions of *a* and *b* must either match or one of them must 
    be 1. In the latter case, the length-1 dimension is broadcast in the normal
    way.
    
    >>> B = [[1, 0], [0, 1]]
    >>> solve_linear_equations(A, B)
    Traceback (most recent call last):
       ...
    LinearAlgebraError: first dimensions of a and b must match or be 1
    >>> B = [[1, 0]]
    >>> _isClose(solve_linear_equations(A, B),
    ...          num.array([[-1.        +0.j,  0.66666667+0.j],
    ...                     [-0.5       +0.j,  0.5       +0.j],
    ...                     [-0.33333333+0.j,  0.4       +0.j]]))
    1
    """
    a = na.asarray(a)
    b = na.asarray(b)
    _assertRank((2,3), a)
    _assertSubmatrixSquareness(a)
    rank_a = len(a.shape)
    _assertRank((rank_a-1, rank_a), b)
    stretched = (rank_a == 2)
    if stretched:
        a = a[na.NewAxis,]
        b = b[na.NewAxis,]
    one_eq = (len(b.shape) == len(a.shape)-1)
    if one_eq: 
        b = b[:,:,na.NewAxis]
    broadcast_a = (a.shape[0] == 1)
    broadcast_b = (b.shape[0] == 1)
    if not (broadcast_a or broadcast_b or a.shape[0] == b.shape[0]):
        raise LinAlgError, "first dimensions of a and b must match or be 1"

    # 
    n_cases = max(a.shape[0], b.shape[0])
    n_eq = a.shape[1]
    n_rhs = b.shape[2]
    if n_eq != b.shape[1]:
        raise LinAlgError, 'Incompatible dimensions'
    t = _commonType(a, b)
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zgesv
    else:
        lapack_routine = lapack_lite2.dgesv
    a, b = _castCopyAndTranspose(t, a, b, indices=(0,2,1))
    result = na.zeros([n_cases, n_rhs, n_eq], b.type())
    result[:] = b 
    b = result
    pivots = na.zeros(n_eq, 'l')
    a_stride = n_eq * n_eq * a.itemsize()
    b_stride = n_eq * n_rhs * b.itemsize()
    a_view = a[0]
    b_view = b[0]
    a_i = a_view.copy()
    b_i = b_view.copy()
    for i in range(n_cases):
        if i:
            if not broadcast_a:
                a_i._copyFrom(a_view)
            b_i._copyFrom(b_view)
        outcome = lapack_routine(n_eq, n_rhs, a_i, n_eq, pivots, b_i, n_eq, 0)
        if outcome['info'] > 0:
            raise LinAlgError, 'Singular matrix' 
        b_view._copyFrom(b_i)
        a_view._byteoffset += a_stride
        b_view._byteoffset += b_stride
    b = na.transpose(b, (0,2,1))
    if one_eq:
        b = b[...,0]
    if stretched:
        b = b[0]
    return b


# Matrix inversion

def inverse(a):
    """inverse(a) -> inverse matrix of a
    
    *a* may be either rank-2 or rank-3. If it is rank-2, it must square.
    
    >>> A = [[1,2,3], [3,5,5], [5,6,7]]
    >>> Ainv = inverse(A)
    >>> _isClose(na.dot(A, Ainv), na.identity(3))
    1
    
    If *a* is rank-3, it is treated as an array of rank-2 matrices and
    must be square along the last 2 axes.
    
    >>> A = [[[1, 3], [2j, 3j]],
    ...      [[2, 4], [4j, 4j]],
    ...      [[3, 5], [6j, 5j]]]
    >>> Ainv = inverse(A)
    >>> _isClose(map(na.dot, A, Ainv), [na.identity(2)]*3)
    1
    
    If *a* is not square along its last two axes, a LinAlgError is raised.
    
    >>> inverse(na.asarray(A)[...,:1])
    Traceback (most recent call last):
       ...
    LinearAlgebraError: Array (or it submatrices) must be square
    
    """
    a = na.asarray(a)
    I = na.identity(a.shape[-2])
    if len(a.shape) == 3:
        I.shape = (1,) + I.shape
    return solve_linear_equations(a, I)


# Cholesky decomposition

def cholesky_decomposition(a):
    _assertRank2(a)
    _assertSquareness(a)
    t =_commonType(a)
    a = _castCopyAndTranspose(t, a)
    m = a.getshape()[0]
    n = a.getshape()[1]
    if _array_kind[t] == 1:
	lapack_routine = lapack_lite2.zpotrf
    else:
	lapack_routine = lapack_lite2.dpotrf
    results = lapack_routine('L', n, a, m, 0)
    if results['info'] > 0:
        raise LinAlgError, 'Matrix is not positive definite - Cholesky decomposition cannot be computed'
    return copy.copy(num.transpose(mlab.triu(a,k=0)))


def qr_decomposition(a, mode='full'):

    """  calculates A=QR, Q orthonormal, R upper triangle matrix.

         mode: 'full'     ==> (Q,R) as return value
	       'r'        ==> (None, R) as return value
	       'economic' ==> (None, A') where the diagonal + upper triangle 
	                      part of A' is R. This is faster if one only requires R.     """
    

    _assertRank2(a)

    t=_commonType(a)

    m = a.getshape()[0]
    n = a.getshape()[1]
    mn = min(m,n)
    tau = num.zeros((mn,), t)

    #  a: convert num storing order to fortran storing order 
    a = _castCopyAndTranspose(t, a)

    if _array_kind[t] == 1:
	lapack_routine = lapack_lite2.zgeqrf
	routine_name='ZGEQRF'
    else:
	lapack_routine = lapack_lite2.dgeqrf
	routine_name='DGEQRF'

    # calculate optimal size of work data 'work'
    lwork = 1
    work = num.zeros((lwork,), t)
    results=lapack_routine(m, n, a, m, tau, work, -1, 0)
    if results['info'] > 0:
        raise LinAlgError, '%s returns %d' % (routine_name, results['info'])

    # do qr decomposition
    lwork = int(abs(work[0]))
    work = num.zeros((lwork,),t)
    results=lapack_routine(m, n, a, m, tau, work, lwork, 0)

    if results['info'] > 0:
        raise LinAlgError, '%s returns %d' % (routine_name, results['info'])


    #  atemp: convert fortrag storing order to num storing order 
    atemp = num.transpose(a)

    #  economic mode
    if mode[0]=='e': return None, atemp

    #  generate r
    r = num.zeros((mn,n), t)
    for i in range(mn):
	    r[i, i:] = atemp[i, i:]

    #  'r'-mode, that is, calculate only r
    if mode[0]=='r': return None, r


    #  from here on: build orthonormal matrix q from a

    if _array_kind[t] == 1:
	lapack_routine = lapack_lite2.zungqr
	routine_name = "ZUNGQR"
    else:
	lapack_routine = lapack_lite2.dorgqr
	routine_name = "DORGQR"


    # determine optimal lwork
    lwork = 1
    work=num.zeros((lwork,), t)
    results=lapack_routine(m,mn,mn, a, m, tau, work, -1, 0)
    if results['info'] > 0:
        raise LinAlgError, '%s returns %d' % (routine_name, results['info'])

    # compute q
    lwork = int(abs(work[0]))
    work=num.zeros((lwork,), t)
    results=lapack_routine(m,mn,mn, a, m, tau, work, lwork, 0)

    if results['info'] > 0:
        raise LinAlgError, '%s returns %d' % (routine_name, results['info'])

    q = num.transpose(a[:mn,:])
    return q,r


# Eigenvalues

def eigenvalues(a):
    _assertRank2(a)
    _assertSquareness(a)
    t =_commonType(a)
    real_t = _array_type[0][_array_precision[t]]
    a = _fastCopyAndTranspose(t, a)
    n = a.getshape()[0]
    dummy = num.zeros((1,), t)
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zgeev
        w = num.zeros((n,), t)
        rwork = num.zeros((n,),real_t)
        lwork = 1
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', 'N', n, a, n, w,
                                 dummy, 1, dummy, 1, work, -1, rwork, 0)
        lwork = int(abs(work[0]))
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', 'N', n, a, n, w,
                                 dummy, 1, dummy, 1, work, lwork, rwork, 0)
    else:
        lapack_routine = lapack_lite2.dgeev
        wr = num.zeros((n,), t)
        wi = num.zeros((n,), t)
        lwork = 1
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', 'N', n, a, n, wr, wi,
                                 dummy, 1, dummy, 1, work, -1, 0)
        lwork = int(work[0])
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', 'N', n, a, n, wr, wi,
                                 dummy, 1, dummy, 1, work, lwork, 0)
        if num.logical_and.reduce(num.equal(wi, 0.)):
            w = wr
        else:
            w = wr+1j*wi
    if results['info'] > 0:
        raise LinAlgError, 'Eigenvalues did not converge'
    return w


def Heigenvalues(a, UPLO='L'):
    _assertRank2(a)
    _assertSquareness(a)
    t =_commonType(a)
    real_t = _array_type[0][_array_precision[t]]
    a = _castCopyAndTranspose(t, a)
    n = a.getshape()[0]
    liwork = 5*n+3 
    iwork = num.zeros((liwork,),'l')
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zheevd
        w = num.zeros((n,), real_t)
        lwork = 1
        work = num.zeros((lwork,), t)
        lrwork = 1
        rwork = num.zeros((lrwork,),real_t)
        results = lapack_routine('N', UPLO, n, a, n,w, work, -1, rwork, -1, iwork, liwork,  0)
        lwork = int(abs(work[0]))
        work = num.zeros((lwork,), t)
        lrwork = int(rwork[0])
        rwork = num.zeros((lrwork,),real_t)
        results = lapack_routine('N', UPLO, n, a, n,w, work, lwork, rwork, lrwork, iwork, liwork,  0)
    else:
        lapack_routine = lapack_lite2.dsyevd
        w = num.zeros((n,), t)
        lwork = 1
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', UPLO, n, a, n,w, work, -1, iwork, liwork, 0)
        lwork = int(work[0])
        work = num.zeros((lwork,), t)
        results = lapack_routine('N', UPLO, n, a, n,w, work, lwork, iwork, liwork, 0)
    if results['info'] > 0:
        raise LinAlgError, 'Eigenvalues did not converge'
    return w

# Eigenvectors

def eigenvectors(a):
    """eigenvectors(a) returns u,v  where u is the eigenvalues and
v is a matrix of eigenvectors with vector v[i] corresponds to 
eigenvalue u[i].  Satisfies the equation dot(a, v[i]) = u[i]*v[i]
"""
    _assertRank2(a)
    _assertSquareness(a)
    t =_commonType(a)
    real_t = _array_type[0][_array_precision[t]]
    a = _fastCopyAndTranspose(t, a)
    n = a.getshape()[0]
    dummy = num.zeros((1,), t)
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zgeev
        w = num.zeros((n,), t)
        v = num.zeros((n,n), t)
        lwork = 1
        work = num.zeros((lwork,),t)
        rwork = num.zeros((2*n,),real_t)
        results = lapack_routine('N', 'V', n, a, n, w,
                                  dummy, 1, v, n, work, -1, rwork, 0)
        lwork = int(abs(work[0]))
        work = num.zeros((lwork,),t)
        results = lapack_routine('N', 'V', n, a, n, w,
                                  dummy, 1, v, n, work, lwork, rwork, 0)
    else:
        lapack_routine = lapack_lite2.dgeev
        wr = num.zeros((n,), t)
        wi = num.zeros((n,), t)
        vr = num.zeros((n,n), t)
        lwork = 1
        work = num.zeros((lwork,),t)
        results = lapack_routine('N', 'V', n, a, n, wr, wi,
                                  dummy, 1, vr, n, work, -1, 0)
        lwork = int(work[0])
        work = num.zeros((lwork,),t)
        results = lapack_routine('N', 'V', n, a, n, wr, wi,
                                  dummy, 1, vr, n, work, lwork, 0)
        if num.logical_and.reduce(num.equal(wi, 0.)):
            w = wr
            v = vr
        else:
            w = wr+1j*wi
            v = num.array(vr,type=num.Complex)
            ind = num.nonzero(
                          num.equal(
                              num.equal(wi,0.0) # true for real e-vals
                                       ,0)          # true for complex e-vals
                                 )                  # indices of complex e-vals
            for i in range(len(ind)/2):
                v[ind[2*i]] = vr[ind[2*i]] + 1j*vr[ind[2*i+1]]
                v[ind[2*i+1]] = vr[ind[2*i]] - 1j*vr[ind[2*i+1]]
    if results['info'] > 0:
        raise LinAlgError, 'Eigenvalues did not converge'
    return w,v


def Heigenvectors(a, UPLO='L'):
    _assertRank2(a)
    _assertSquareness(a)
    t =_commonType(a)
    real_t = _array_type[0][_array_precision[t]]
    a = _castCopyAndTranspose(t, a)
    n = a.getshape()[0]
    liwork = 5*n+3
    iwork = num.zeros((liwork,),'l')
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zheevd
        w = num.zeros((n,), real_t)
        lwork = 1
        work = num.zeros((lwork,), t)
        lrwork = 1
        rwork = num.zeros((lrwork,),real_t)
        results = lapack_routine('V', UPLO, n, a, n,w, work, -1, rwork, -1, iwork, liwork,  0)
        lwork = int(abs(work[0]))
        work = num.zeros((lwork,), t)
        lrwork = int(rwork[0])
        rwork = num.zeros((lrwork,),real_t)
        results = lapack_routine('V', UPLO, n, a, n,w, work, lwork, rwork, lrwork, iwork, liwork,  0)
    else:
        lapack_routine = lapack_lite2.dsyevd
        w = num.zeros((n,), t)
        lwork = 1
        work = num.zeros((lwork,),t)
        results = lapack_routine('V', UPLO, n, a, n,w, work, -1, iwork, liwork, 0)
        lwork = int(work[0])
        work = num.zeros((lwork,),t)
        results = lapack_routine('V', UPLO, n, a, n,w, work, lwork, iwork, liwork, 0)
    if results['info'] > 0:
        raise LinAlgError, 'Eigenvalues did not converge'
    return (w,a)

   
# Singular value decomposition

def singular_value_decomposition(a, full_matrices = 0):
    _assertRank2(a)
    n = a.getshape()[1]
    m = a.getshape()[0]
    t =_commonType(a)
    real_t = _array_type[0][_array_precision[t]]
    a = _fastCopyAndTranspose(t, a)
    if full_matrices:
	nu = m
	nvt = n
	option = 'A'
    else:
	nu = min(n,m)
	nvt = min(n,m)
	option = 'S'
    s = num.zeros((min(n,m),), real_t)
    u = num.zeros((nu, m), t)
    vt = num.zeros((n, nvt), t)
    iwork = num.zeros((8*min(m,n),), 'l')
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zgesdd
        rwork = num.zeros((5*min(m,n)*min(m,n) + 5*min(m,n),), real_t)
        lwork = 1
        work = num.zeros((lwork,), t)
        results = lapack_routine(option, m, n, a, m, s, u, m, vt, nvt,
                                 work, -1, rwork, iwork, 0)
        lwork = int(abs(work[0]))
        work = num.zeros((lwork,), t)
        results = lapack_routine(option, m, n, a, m, s, u, m, vt, nvt,
                                 work, lwork, rwork, iwork, 0)
    else:
        lapack_routine = lapack_lite2.dgesdd
        lwork = 1
        work = num.zeros((lwork,), t)
        results = lapack_routine(option, m, n, a, m, s, u, m, vt, nvt,
                                 work, -1, iwork, 0)
        lwork = int(work[0])
        work = num.zeros((lwork,), t)
        results = lapack_routine(option, m, n, a, m, s, u, m, vt, nvt,
                                 work, lwork, iwork, 0)
    if results['info'] > 0:
        raise LinAlgError, 'SVD did not converge'
    return multiarray.transpose(u), s, multiarray.transpose(vt) # why copy here?


# Generalized inverse

def generalized_inverse(a, rcond = 1.e-10):
    u, s, vt = singular_value_decomposition(a, 0)
    m = u.getshape()[0]
    n = vt.getshape()[1]
    cutoff = rcond*num.maximum.reduce(s)
    for i in range(min(n,m)):
        if s[i] > cutoff:
            s[i] = 1./s[i]
	else:
	    s[i] = 0.;
    return num.dot(num.transpose(vt),
		       s[:, num.NewAxis]*num.transpose(u))

# Determinant

def determinant(a):
    """determinant(a) -> ||a||
 
    *a* may be either rank-2 or rank-3. If it is rank-2, it must square. 
    
    >>> A = [[1,2,3], [3,4,5], [5,6,7]]
    >>> _isClose(determinant(A), 0)
    1
    
    If *a* is rank-3, it is treated as an array of rank-2 matrices and
    must be square along the last 2 axes.
 
    >>> A = [[[1, 3], [2j, 3j]], [[2, 4], [4j, 4j]], [[3, 5], [6j, 5j]]]
    >>> _isClose(determinant(A), [-3j, -8j, -15j])
    1

    If *a* is not square along its last two axes, a LinAlgError is raised.
    
    >>> determinant(na.asarray(A)[...,:1])
    Traceback (most recent call last):
       ...
    LinearAlgebraError: Array (or it submatrices) must be square
    
    """
    a = na.asarray(a)
    _assertRank((2,3), a)
    _assertSubmatrixSquareness(a)
    stretched = (len(a.shape) == 2)
    if stretched:
        a = a[na.NewAxis,]
    t = _commonType(a)
    a = _castCopyAndTranspose(t, a, indices=(0,2,1))
    n_cases, n = a.shape[:2]
    if _array_kind[t] == 1:
        lapack_routine = lapack_lite2.zgetrf
    else:
        lapack_routine = lapack_lite2.dgetrf
    no_pivoting = na.arrayrange(1, n+1)
    pivots = na.zeros((n,), 'l')
    all_pivots = na.zeros((n_cases, n,), 'l')
    sum , not_equal = na.sum, na.not_equal
    stride = n * n * a.itemsize()
    pivots_stride = n * pivots.itemsize()
    view = a[0].view()
    view_pivots = all_pivots[0]
    a_i = view.copy()
    for i in range(n_cases):
        if i:
            a_i._copyFrom(view)
        outcome = lapack_routine(n, n, a_i, n, pivots, 0)
        view_pivots._copyFrom(pivots)
        view._copyFrom(a_i)
        view._byteoffset += stride
        view_pivots._byteoffset += pivots_stride
    signs = na.where(sum(not_equal(all_pivots, no_pivoting), 1) % 2, -1, 1).astype(t)
    for i in range(n):
        signs *= a[:,i,i]
    if stretched:
        signs = signs[0]
    return signs


# Linear Least Squares 
        
def linear_least_squares(a, b, rcond=1.e-10):
    """solveLinearLeastSquares(a,b) returns x,resids,rank,s 
where x minimizes 2-norm(|b - Ax|) 
      resids is the sum square residuals
      rank is the rank of A
      s is an rank of the singual values of A in desending order

If b is a matrix then x is also a matrix with corresponding columns.
If the rank of A is less than the number of columns of A or greater than
the numer of rows, then residuals will be returned as an empty array
otherwise resids = sum((b-dot(A,x)**2).
Singular values less than s[0]*rcond are treated as zero.
"""
    one_eq = len(b.getshape()) == 1
    if one_eq:
        b = b[:, num.NewAxis]
    _assertRank2(a, b)
    m  = a.getshape()[0]
    n  = a.getshape()[1]
    n_rhs = b.getshape()[1]
    ldb = max(n,m)
    if m != b.getshape()[0]:
        raise LinAlgError, 'Incompatible dimensions'
    t =_commonType(a, b)
    real_t = _array_type[0][_array_precision[t]]
    bstar = num.zeros((ldb,n_rhs),t)
    bstar[:b.getshape()[0],:n_rhs] = copy.copy(b)
    a,bstar = _castCopyAndTranspose(t, a, bstar)
    s = num.zeros((min(m,n),),real_t)
    nlvl = max( 0, int( math.log( float(min( m,n ))/2. ) ) + 1 )
    iwork = num.zeros((3*min(m,n)*nlvl+11*min(m,n),), 'l')
    if _array_kind[t] == 1: # Complex routines take different arguments
        lapack_routine = lapack_lite2.zgelsd
        lwork = 1
        rwork = num.zeros((lwork,), real_t)
        work = num.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 = num.zeros((lwork,),real_t)
        a_real = num.zeros((m,n),real_t)
        bstar_real = num.zeros((ldb,n_rhs,),real_t)
        results = lapack_lite2.dgelsd( m, n, n_rhs, a_real, m, bstar_real,ldb , s, rcond,
                        0,rwork,-1,iwork,0 )
        lrwork = int(rwork[0])
        work = num.zeros((lwork,), t)
        rwork = num.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_lite2.dgelsd
        lwork = 1
        work = num.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 = num.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 = num.array([],type=t)
    if one_eq:
        x = copy.copy(num.ravel(bstar)[:n])
        if (results['rank']==n) and (m>n):
            resids = num.array([num.sum((num.ravel(bstar)[n:])**2)])
    else:
        x = copy.copy(num.transpose(bstar)[:n,:])
        if (results['rank']==n) and (m>n):
            resids = copy.copy(num.sum((num.transpose(bstar)[n:,:])**2))
    return x,resids,results['rank'],copy.copy(s[:min(n,m)]) 


def test():
	import doctest, dtest, LinearAlgebra2
	return doctest.testmod(dtest), doctest.testmod(LinearAlgebra2)

if __name__ == '__main__':
    test()
