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