github.com/gopherd/gonum@v0.0.4/mat/lq.go (about) 1 // Copyright ©2013 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package mat 6 7 import ( 8 "math" 9 10 "github.com/gopherd/gonum/blas" 11 "github.com/gopherd/gonum/blas/blas64" 12 "github.com/gopherd/gonum/lapack" 13 "github.com/gopherd/gonum/lapack/lapack64" 14 ) 15 16 const badLQ = "mat: invalid LQ factorization" 17 18 // LQ is a type for creating and using the LQ factorization of a matrix. 19 type LQ struct { 20 lq *Dense 21 tau []float64 22 cond float64 23 } 24 25 func (lq *LQ) updateCond(norm lapack.MatrixNorm) { 26 // Since A = L*Q, and Q is orthogonal, we get for the condition number κ 27 // κ(A) := |A| |A^-1| = |L*Q| |(L*Q)^-1| = |L| |Qᵀ * L^-1| 28 // = |L| |L^-1| = κ(L), 29 // where we used that fact that Q^-1 = Qᵀ. However, this assumes that 30 // the matrix norm is invariant under orthogonal transformations which 31 // is not the case for CondNorm. Hopefully the error is negligible: κ 32 // is only a qualitative measure anyway. 33 m := lq.lq.mat.Rows 34 work := getFloat64s(3*m, false) 35 iwork := getInts(m, false) 36 l := lq.lq.asTriDense(m, blas.NonUnit, blas.Lower) 37 v := lapack64.Trcon(norm, l.mat, work, iwork) 38 lq.cond = 1 / v 39 putFloat64s(work) 40 putInts(iwork) 41 } 42 43 // Factorize computes the LQ factorization of an m×n matrix a where m <= n. The LQ 44 // factorization always exists even if A is singular. 45 // 46 // The LQ decomposition is a factorization of the matrix A such that A = L * Q. 47 // The matrix Q is an orthonormal n×n matrix, and L is an m×n lower triangular matrix. 48 // L and Q can be extracted using the LTo and QTo methods. 49 func (lq *LQ) Factorize(a Matrix) { 50 lq.factorize(a, CondNorm) 51 } 52 53 func (lq *LQ) factorize(a Matrix, norm lapack.MatrixNorm) { 54 m, n := a.Dims() 55 if m > n { 56 panic(ErrShape) 57 } 58 k := min(m, n) 59 if lq.lq == nil { 60 lq.lq = &Dense{} 61 } 62 lq.lq.CloneFrom(a) 63 work := []float64{0} 64 lq.tau = make([]float64, k) 65 lapack64.Gelqf(lq.lq.mat, lq.tau, work, -1) 66 work = getFloat64s(int(work[0]), false) 67 lapack64.Gelqf(lq.lq.mat, lq.tau, work, len(work)) 68 putFloat64s(work) 69 lq.updateCond(norm) 70 } 71 72 // isValid returns whether the receiver contains a factorization. 73 func (lq *LQ) isValid() bool { 74 return lq.lq != nil && !lq.lq.IsEmpty() 75 } 76 77 // Cond returns the condition number for the factorized matrix. 78 // Cond will panic if the receiver does not contain a factorization. 79 func (lq *LQ) Cond() float64 { 80 if !lq.isValid() { 81 panic(badLQ) 82 } 83 return lq.cond 84 } 85 86 // TODO(btracey): Add in the "Reduced" forms for extracting the m×m orthogonal 87 // and upper triangular matrices. 88 89 // LTo extracts the m×n lower trapezoidal matrix from a LQ decomposition. 90 // 91 // If dst is empty, LTo will resize dst to be r×c. When dst is 92 // non-empty, LTo will panic if dst is not r×c. LTo will also panic 93 // if the receiver does not contain a successful factorization. 94 func (lq *LQ) LTo(dst *Dense) { 95 if !lq.isValid() { 96 panic(badLQ) 97 } 98 99 r, c := lq.lq.Dims() 100 if dst.IsEmpty() { 101 dst.ReuseAs(r, c) 102 } else { 103 r2, c2 := dst.Dims() 104 if r != r2 || c != c2 { 105 panic(ErrShape) 106 } 107 } 108 109 // Disguise the LQ as a lower triangular. 110 t := &TriDense{ 111 mat: blas64.Triangular{ 112 N: r, 113 Stride: lq.lq.mat.Stride, 114 Data: lq.lq.mat.Data, 115 Uplo: blas.Lower, 116 Diag: blas.NonUnit, 117 }, 118 cap: lq.lq.capCols, 119 } 120 dst.Copy(t) 121 122 if r == c { 123 return 124 } 125 // Zero right of the triangular. 126 for i := 0; i < r; i++ { 127 zero(dst.mat.Data[i*dst.mat.Stride+r : i*dst.mat.Stride+c]) 128 } 129 } 130 131 // QTo extracts the n×n orthonormal matrix Q from an LQ decomposition. 132 // 133 // If dst is empty, QTo will resize dst to be c×c. When dst is 134 // non-empty, QTo will panic if dst is not c×c. QTo will also panic 135 // if the receiver does not contain a successful factorization. 136 func (lq *LQ) QTo(dst *Dense) { 137 if !lq.isValid() { 138 panic(badLQ) 139 } 140 141 _, c := lq.lq.Dims() 142 if dst.IsEmpty() { 143 dst.ReuseAs(c, c) 144 } else { 145 r2, c2 := dst.Dims() 146 if c != r2 || c != c2 { 147 panic(ErrShape) 148 } 149 dst.Zero() 150 } 151 q := dst.mat 152 153 // Set Q = I. 154 ldq := q.Stride 155 for i := 0; i < c; i++ { 156 q.Data[i*ldq+i] = 1 157 } 158 159 // Construct Q from the elementary reflectors. 160 work := []float64{0} 161 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, q, work, -1) 162 work = getFloat64s(int(work[0]), false) 163 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, q, work, len(work)) 164 putFloat64s(work) 165 } 166 167 // SolveTo finds a minimum-norm solution to a system of linear equations defined 168 // by the matrices A and b, where A is an m×n matrix represented in its LQ factorized 169 // form. If A is singular or near-singular a Condition error is returned. 170 // See the documentation for Condition for more information. 171 // 172 // The minimization problem solved depends on the input parameters. 173 // If trans == false, find the minimum norm solution of A * X = B. 174 // If trans == true, find X such that ||A*X - B||_2 is minimized. 175 // The solution matrix, X, is stored in place into dst. 176 // SolveTo will panic if the receiver does not contain a factorization. 177 func (lq *LQ) SolveTo(dst *Dense, trans bool, b Matrix) error { 178 if !lq.isValid() { 179 panic(badLQ) 180 } 181 182 r, c := lq.lq.Dims() 183 br, bc := b.Dims() 184 185 // The LQ solve algorithm stores the result in-place into the right hand side. 186 // The storage for the answer must be large enough to hold both b and x. 187 // However, this method's receiver must be the size of x. Copy b, and then 188 // copy the result into x at the end. 189 if trans { 190 if c != br { 191 panic(ErrShape) 192 } 193 dst.reuseAsNonZeroed(r, bc) 194 } else { 195 if r != br { 196 panic(ErrShape) 197 } 198 dst.reuseAsNonZeroed(c, bc) 199 } 200 // Do not need to worry about overlap between x and b because w has its own 201 // independent storage. 202 w := getDenseWorkspace(max(r, c), bc, false) 203 w.Copy(b) 204 t := lq.lq.asTriDense(lq.lq.mat.Rows, blas.NonUnit, blas.Lower).mat 205 if trans { 206 work := []float64{0} 207 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, -1) 208 work = getFloat64s(int(work[0]), false) 209 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, len(work)) 210 putFloat64s(work) 211 212 ok := lapack64.Trtrs(blas.Trans, t, w.mat) 213 if !ok { 214 return Condition(math.Inf(1)) 215 } 216 } else { 217 ok := lapack64.Trtrs(blas.NoTrans, t, w.mat) 218 if !ok { 219 return Condition(math.Inf(1)) 220 } 221 for i := r; i < c; i++ { 222 zero(w.mat.Data[i*w.mat.Stride : i*w.mat.Stride+bc]) 223 } 224 work := []float64{0} 225 lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, -1) 226 work = getFloat64s(int(work[0]), false) 227 lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, len(work)) 228 putFloat64s(work) 229 } 230 // x was set above to be the correct size for the result. 231 dst.Copy(w) 232 putDenseWorkspace(w) 233 if lq.cond > ConditionTolerance { 234 return Condition(lq.cond) 235 } 236 return nil 237 } 238 239 // SolveVecTo finds a minimum-norm solution to a system of linear equations. 240 // See LQ.SolveTo for the full documentation. 241 // SolveToVec will panic if the receiver does not contain a factorization. 242 func (lq *LQ) SolveVecTo(dst *VecDense, trans bool, b Vector) error { 243 if !lq.isValid() { 244 panic(badLQ) 245 } 246 247 r, c := lq.lq.Dims() 248 if _, bc := b.Dims(); bc != 1 { 249 panic(ErrShape) 250 } 251 252 // The Solve implementation is non-trivial, so rather than duplicate the code, 253 // instead recast the VecDenses as Dense and call the matrix code. 254 bm := Matrix(b) 255 if rv, ok := b.(RawVectorer); ok { 256 bmat := rv.RawVector() 257 if dst != b { 258 dst.checkOverlap(bmat) 259 } 260 b := VecDense{mat: bmat} 261 bm = b.asDense() 262 } 263 if trans { 264 dst.reuseAsNonZeroed(r) 265 } else { 266 dst.reuseAsNonZeroed(c) 267 } 268 return lq.SolveTo(dst.asDense(), trans, bm) 269 }