// Inverse computes the inverse of the matrix a, storing the result into the // receiver. If a is ill-conditioned, a Condition error will be returned. // Note that matrix inversion is numerically unstable, and should generally // be avoided where possible, for example by using the Solve routines. func (m *Dense) Inverse(a Matrix) error { // TODO(btracey): Special case for RawTriangular, etc. r, c := a.Dims() if r != c { panic(ErrSquare) } m.reuseAs(a.Dims()) aMat, aTrans := untranspose(a) if m != aMat { m.Copy(a) } else if aTrans { tmp := getWorkspace(r, c, false) tmp.Copy(a) m.Copy(tmp) putWorkspace(tmp) } ipiv := make([]int, r) lapack64.Getrf(m.mat, ipiv) work := make([]float64, 1, 4*r) // must be at least 4*r for cond. lapack64.Getri(m.mat, ipiv, work, -1) if int(work[0]) > 4*r { work = make([]float64, int(work[0])) } else { work = work[:4*r] } lapack64.Getri(m.mat, ipiv, work, len(work)) norm := lapack64.Lange(condNorm, m.mat, work) cond := lapack64.Gecon(condNorm, m.mat, norm, work, ipiv) // reuse ipiv if cond > condTol { return Condition(cond) } return nil }
// Inverse computes the inverse of the matrix a, storing the result into the // receiver. If a is ill-conditioned, a Condition error will be returned. // Note that matrix inversion is numerically unstable, and should generally // be avoided where possible, for example by using the Solve routines. func (m *Dense) Inverse(a Matrix) error { // TODO(btracey): Special case for RawTriangular, etc. r, c := a.Dims() if r != c { panic(matrix.ErrSquare) } m.reuseAs(a.Dims()) aU, aTrans := untranspose(a) switch rm := aU.(type) { case RawMatrixer: if m != aU || aTrans { if m == aU || m.checkOverlap(rm.RawMatrix()) { tmp := getWorkspace(r, c, false) tmp.Copy(a) m.Copy(tmp) putWorkspace(tmp) break } m.Copy(a) } default: if m != aU { m.Copy(a) } else if aTrans { // m and a share data so Copy cannot be used directly. tmp := getWorkspace(r, c, false) tmp.Copy(a) m.Copy(tmp) putWorkspace(tmp) } } ipiv := make([]int, r) lapack64.Getrf(m.mat, ipiv) work := make([]float64, 1, 4*r) // must be at least 4*r for cond. lapack64.Getri(m.mat, ipiv, work, -1) if int(work[0]) > 4*r { work = make([]float64, int(work[0])) } else { work = work[:4*r] } lapack64.Getri(m.mat, ipiv, work, len(work)) norm := lapack64.Lange(matrix.CondNorm, m.mat, work) cond := lapack64.Gecon(matrix.CondNorm, m.mat, norm, work, ipiv) // reuse ipiv if cond > matrix.ConditionTolerance { return matrix.Condition(cond) } return nil }
// updateCond updates the stored condition number of the matrix. Norm is the // norm of the original matrix. If norm is negative it will be estimated. func (lu *LU) updateCond(norm float64) { n := lu.lu.mat.Cols work := make([]float64, 4*n) iwork := make([]int, n) if norm < 0 { // This is an approximation. By the definition of a norm, ||AB|| <= ||A|| ||B||. // The condition number is ||A|| || A^-1||, so this will underestimate // the condition number somewhat. // The norm of the original factorized matrix cannot be stored because of // update possibilities, e.g. RankOne. u := lu.lu.asTriDense(n, blas.NonUnit, blas.Upper) l := lu.lu.asTriDense(n, blas.Unit, blas.Lower) unorm := lapack64.Lantr(matrix.CondNorm, u.mat, work) lnorm := lapack64.Lantr(matrix.CondNorm, l.mat, work) norm = unorm * lnorm } v := lapack64.Gecon(matrix.CondNorm, lu.lu.mat, norm, work, iwork) lu.cond = 1 / v }
// Cond returns the condition number of the given matrix under the given norm. // The condition number must be based on the 1-norm, 2-norm or ∞-norm. // Cond will panic with matrix.ErrShape if the matrix has zero size. // // BUG(btracey): The computation of the 1-norm and ∞-norm for non-square matrices // is innacurate, although is typically the right order of magnitude. See // https://github.com/xianyi/OpenBLAS/issues/636. While the value returned will // change with the resolution of this bug, the result from Cond will match the // condition number used internally. func Cond(a Matrix, norm float64) float64 { m, n := a.Dims() if m == 0 || n == 0 { panic(matrix.ErrShape) } var lnorm lapack.MatrixNorm switch norm { default: panic("mat64: bad norm value") case 1: lnorm = lapack.MaxColumnSum case 2: var svd SVD ok := svd.Factorize(a, matrix.SVDNone) if !ok { return math.Inf(1) } return svd.Cond() case math.Inf(1): lnorm = lapack.MaxRowSum } if m == n { // Use the LU decomposition to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 4*n) aNorm := lapack64.Lange(lnorm, tmp.mat, work) pivot := make([]int, m) lapack64.Getrf(tmp.mat, pivot) iwork := make([]int, n) v := lapack64.Gecon(lnorm, tmp.mat, aNorm, work, iwork) putWorkspace(tmp) return 1 / v } if m > n { // Use the QR factorization to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 3*n) tau := make([]float64, min(m, n)) lapack64.Geqrf(tmp.mat, tau, work, -1) if int(work[0]) > len(work) { work = make([]float64, int(work[0])) } lapack64.Geqrf(tmp.mat, tau, work, len(work)) iwork := make([]int, n) r := tmp.asTriDense(n, blas.NonUnit, blas.Upper) v := lapack64.Trcon(lnorm, r.mat, work, iwork) putWorkspace(tmp) return 1 / v } // Use the LQ factorization to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 3*m) tau := make([]float64, min(m, n)) lapack64.Gelqf(tmp.mat, tau, work, -1) if int(work[0]) > len(work) { work = make([]float64, int(work[0])) } lapack64.Gelqf(tmp.mat, tau, work, len(work)) iwork := make([]int, m) l := tmp.asTriDense(m, blas.NonUnit, blas.Lower) v := lapack64.Trcon(lnorm, l.mat, work, iwork) putWorkspace(tmp) return 1 / v }
// Cond returns the condition number of the given matrix under the given norm. // The condition number must be based on the 1-norm, 2-norm or ∞-norm. // Cond will panic with matrix.ErrShape if the matrix has zero size. // // BUG(btracey): The computation of the 1-norm and ∞-norm for non-square matrices // is innacurate, although is typically the right order of magnitude. See // https://github.com/xianyi/OpenBLAS/issues/636. While the value returned will // change with the resolution of this bug, the result from Cond will match the // condition number used internally. func Cond(a Matrix, norm float64) float64 { m, n := a.Dims() if m == 0 || n == 0 { panic(matrix.ErrShape) } var lnorm lapack.MatrixNorm switch norm { default: panic("mat64: bad norm value") case 1: lnorm = lapack.MaxColumnSum case 2: // Use SVD to compute the condition number. // TODO(btracey): Replace this with temporary workspace when SVD is fixed. tmp := NewDense(m, n, nil) tmp.Copy(a) svd := SVD(tmp, math.Pow(2, -52.0), math.Pow(2, -966.0), false, false) return svd.Cond() case math.Inf(1): lnorm = lapack.MaxRowSum } if m == n { // Use the LU decomposition to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 4*n) aNorm := lapack64.Lange(lnorm, tmp.mat, work) pivot := make([]int, m) lapack64.Getrf(tmp.mat, pivot) iwork := make([]int, n) v := lapack64.Gecon(lnorm, tmp.mat, aNorm, work, iwork) putWorkspace(tmp) return 1 / v } if m > n { // Use the QR factorization to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 3*n) tau := make([]float64, min(m, n)) lapack64.Geqrf(tmp.mat, tau, work, -1) if int(work[0]) > len(work) { work = make([]float64, int(work[0])) } lapack64.Geqrf(tmp.mat, tau, work, len(work)) iwork := make([]int, n) r := tmp.asTriDense(n, blas.NonUnit, blas.Upper) v := lapack64.Trcon(lnorm, r.mat, work, iwork) putWorkspace(tmp) return 1 / v } // Use the LQ factorization to compute the condition number. tmp := getWorkspace(m, n, false) tmp.Copy(a) work := make([]float64, 3*m) tau := make([]float64, min(m, n)) lapack64.Gelqf(tmp.mat, tau, work, -1) if int(work[0]) > len(work) { work = make([]float64, int(work[0])) } lapack64.Gelqf(tmp.mat, tau, work, len(work)) iwork := make([]int, m) l := tmp.asTriDense(m, blas.NonUnit, blas.Lower) v := lapack64.Trcon(lnorm, l.mat, work, iwork) putWorkspace(tmp) return 1 / v }