gonum.org/v1/gonum@v0.14.0/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 "gonum.org/v1/gonum/blas" 11 "gonum.org/v1/gonum/blas/blas64" 12 "gonum.org/v1/gonum/lapack" 13 "gonum.org/v1/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 // 174 // If trans == false, find the minimum norm solution of A * X = B. 175 // If trans == true, find X such that ||A*X - B||_2 is minimized. 176 // 177 // The solution matrix, X, is stored in place into dst. 178 // SolveTo will panic if the receiver does not contain a factorization. 179 func (lq *LQ) SolveTo(dst *Dense, trans bool, b Matrix) error { 180 if !lq.isValid() { 181 panic(badLQ) 182 } 183 184 r, c := lq.lq.Dims() 185 br, bc := b.Dims() 186 187 // The LQ solve algorithm stores the result in-place into the right hand side. 188 // The storage for the answer must be large enough to hold both b and x. 189 // However, this method's receiver must be the size of x. Copy b, and then 190 // copy the result into x at the end. 191 if trans { 192 if c != br { 193 panic(ErrShape) 194 } 195 dst.reuseAsNonZeroed(r, bc) 196 } else { 197 if r != br { 198 panic(ErrShape) 199 } 200 dst.reuseAsNonZeroed(c, bc) 201 } 202 // Do not need to worry about overlap between x and b because w has its own 203 // independent storage. 204 w := getDenseWorkspace(max(r, c), bc, false) 205 w.Copy(b) 206 t := lq.lq.asTriDense(lq.lq.mat.Rows, blas.NonUnit, blas.Lower).mat 207 if trans { 208 work := []float64{0} 209 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, -1) 210 work = getFloat64s(int(work[0]), false) 211 lapack64.Ormlq(blas.Left, blas.NoTrans, lq.lq.mat, lq.tau, w.mat, work, len(work)) 212 putFloat64s(work) 213 214 ok := lapack64.Trtrs(blas.Trans, t, w.mat) 215 if !ok { 216 return Condition(math.Inf(1)) 217 } 218 } else { 219 ok := lapack64.Trtrs(blas.NoTrans, t, w.mat) 220 if !ok { 221 return Condition(math.Inf(1)) 222 } 223 for i := r; i < c; i++ { 224 zero(w.mat.Data[i*w.mat.Stride : i*w.mat.Stride+bc]) 225 } 226 work := []float64{0} 227 lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, -1) 228 work = getFloat64s(int(work[0]), false) 229 lapack64.Ormlq(blas.Left, blas.Trans, lq.lq.mat, lq.tau, w.mat, work, len(work)) 230 putFloat64s(work) 231 } 232 // x was set above to be the correct size for the result. 233 dst.Copy(w) 234 putDenseWorkspace(w) 235 if lq.cond > ConditionTolerance { 236 return Condition(lq.cond) 237 } 238 return nil 239 } 240 241 // SolveVecTo finds a minimum-norm solution to a system of linear equations. 242 // See LQ.SolveTo for the full documentation. 243 // SolveToVec will panic if the receiver does not contain a factorization. 244 func (lq *LQ) SolveVecTo(dst *VecDense, trans bool, b Vector) error { 245 if !lq.isValid() { 246 panic(badLQ) 247 } 248 249 r, c := lq.lq.Dims() 250 if _, bc := b.Dims(); bc != 1 { 251 panic(ErrShape) 252 } 253 254 // The Solve implementation is non-trivial, so rather than duplicate the code, 255 // instead recast the VecDenses as Dense and call the matrix code. 256 bm := Matrix(b) 257 if rv, ok := b.(RawVectorer); ok { 258 bmat := rv.RawVector() 259 if dst != b { 260 dst.checkOverlap(bmat) 261 } 262 b := VecDense{mat: bmat} 263 bm = b.asDense() 264 } 265 if trans { 266 dst.reuseAsNonZeroed(r) 267 } else { 268 dst.reuseAsNonZeroed(c) 269 } 270 return lq.SolveTo(dst.asDense(), trans, bm) 271 }