lq.go 7.2 KB

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