// 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 }
// Factorize computes the LU factorization of the square matrix a and stores the // result. The LU decomposition will complete regardless of the singularity of a. // // The LU factorization is computed with pivoting, and so really the decomposition // is a PLU decomposition where P is a permutation matrix. The individual matrix // factors can be extracted from the factorization using the Permutation method // on Dense, and the LFrom and UFrom methods on TriDense. func (lu *LU) Factorize(a Matrix) { r, c := a.Dims() if r != c { panic(matrix.ErrSquare) } if lu.lu == nil { lu.lu = &Dense{} } lu.lu.Clone(a) if cap(lu.pivot) < r { lu.pivot = make([]int, r) } lu.pivot = lu.pivot[:r] work := make([]float64, r) anorm := lapack64.Lange(matrix.CondNorm, lu.lu.mat, work) lapack64.Getrf(lu.lu.mat, lu.pivot) lu.updateCond(anorm) }
// Norm returns the specified (induced) norm of the matrix a. See // https://en.wikipedia.org/wiki/Matrix_norm for the definition of an induced norm. // // Valid norms are: // 1 - The maximum absolute column sum // 2 - Frobenius norm, the square root of the sum of the squares of the elements. // Inf - The maximum absolute row sum. // Norm will panic with ErrNormOrder if an illegal norm order is specified. func Norm(a Matrix, norm float64) float64 { r, c := a.Dims() aMat, aTrans := untranspose(a) var work []float64 switch rma := aMat.(type) { case RawMatrixer: rm := rma.RawMatrix() n := normLapack(norm, aTrans) if n == lapack.MaxColumnSum { work = make([]float64, rm.Cols) } return lapack64.Lange(n, rm, work) case RawTriangular: rm := rma.RawTriangular() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lantr(n, rm, work) case RawSymmetricer: rm := rma.RawSymmetric() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lansy(n, rm, work) } switch norm { default: panic("unreachable") case 1: var max float64 for j := 0; j < c; j++ { var sum float64 for i := 0; i < r; i++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max case 2: var sum float64 for i := 0; i < r; i++ { for j := 0; j < c; j++ { v := a.At(i, j) sum += v * v } } return math.Sqrt(sum) case math.Inf(1): var max float64 for i := 0; i < r; i++ { var sum float64 for j := 0; j < c; j++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max } }
// Norm returns the specified (induced) norm of the matrix a. See // https://en.wikipedia.org/wiki/Matrix_norm for the definition of an induced norm. // // Valid norms are: // 1 - The maximum absolute column sum // 2 - Frobenius norm, the square root of the sum of the squares of the elements. // Inf - The maximum absolute row sum. // Norm will panic with ErrNormOrder if an illegal norm order is specified and // with matrix.ErrShape if the matrix has zero size. func Norm(a Matrix, norm float64) float64 { r, c := a.Dims() if r == 0 || c == 0 { panic(matrix.ErrShape) } aU, aTrans := untranspose(a) var work []float64 switch rma := aU.(type) { case RawMatrixer: rm := rma.RawMatrix() n := normLapack(norm, aTrans) if n == lapack.MaxColumnSum { work = make([]float64, rm.Cols) } return lapack64.Lange(n, rm, work) case RawTriangular: rm := rma.RawTriangular() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lantr(n, rm, work) case RawSymmetricer: rm := rma.RawSymmetric() n := normLapack(norm, aTrans) if n == lapack.MaxRowSum || n == lapack.MaxColumnSum { work = make([]float64, rm.N) } return lapack64.Lansy(n, rm, work) case *Vector: rv := rma.RawVector() switch norm { default: panic("unreachable") case 1: if aTrans { imax := blas64.Iamax(rma.n, rv) return math.Abs(rma.At(imax, 0)) } return blas64.Asum(rma.n, rv) case 2: return blas64.Nrm2(rma.n, rv) case math.Inf(1): if aTrans { return blas64.Asum(rma.n, rv) } imax := blas64.Iamax(rma.n, rv) return math.Abs(rma.At(imax, 0)) } } switch norm { default: panic("unreachable") case 1: var max float64 for j := 0; j < c; j++ { var sum float64 for i := 0; i < r; i++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max case 2: var sum float64 for i := 0; i < r; i++ { for j := 0; j < c; j++ { v := a.At(i, j) sum += v * v } } return math.Sqrt(sum) case math.Inf(1): var max float64 for i := 0; i < r; i++ { var sum float64 for j := 0; j < c; j++ { sum += math.Abs(a.At(i, j)) } if sum > max { max = sum } } return max } }
// 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 }