hogsvd.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. // Copyright ©2017 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. "errors"
  7. "gonum.org/v1/gonum/blas/blas64"
  8. )
  9. // HOGSVD is a type for creating and using the Higher Order Generalized Singular Value
  10. // Decomposition (HOGSVD) of a set of matrices.
  11. //
  12. // The factorization is a linear transformation of the data sets from the given
  13. // variable×sample spaces to reduced and diagonalized "eigenvariable"×"eigensample"
  14. // spaces.
  15. type HOGSVD struct {
  16. n int
  17. v *Dense
  18. b []Dense
  19. err error
  20. }
  21. // succFact returns whether the receiver contains a successful factorization.
  22. func (gsvd *HOGSVD) succFact() bool {
  23. return gsvd.n != 0
  24. }
  25. // Factorize computes the higher order generalized singular value decomposition (HOGSVD)
  26. // of the n input r_i×c column tall matrices in m. HOGSV extends the GSVD case from 2 to n
  27. // input matrices.
  28. //
  29. // M_0 = U_0 * Σ_0 * Vᵀ
  30. // M_1 = U_1 * Σ_1 * Vᵀ
  31. // .
  32. // .
  33. // .
  34. // M_{n-1} = U_{n-1} * Σ_{n-1} * Vᵀ
  35. //
  36. // where U_i are r_i×c matrices of singular vectors, Σ are c×c matrices singular values, and V
  37. // is a c×c matrix of singular vectors.
  38. //
  39. // Factorize returns whether the decomposition succeeded. If the decomposition
  40. // failed, routines that require a successful factorization will panic.
  41. func (gsvd *HOGSVD) Factorize(m ...Matrix) (ok bool) {
  42. // Factorize performs the HOGSVD factorisation
  43. // essentially as described by Ponnapalli et al.
  44. // https://doi.org/10.1371/journal.pone.0028072
  45. if len(m) < 2 {
  46. panic("hogsvd: too few matrices")
  47. }
  48. gsvd.n = 0
  49. r, c := m[0].Dims()
  50. a := make([]Cholesky, len(m))
  51. var ts SymDense
  52. for i, d := range m {
  53. rd, cd := d.Dims()
  54. if rd < cd {
  55. gsvd.err = ErrShape
  56. return false
  57. }
  58. if rd > r {
  59. r = rd
  60. }
  61. if cd != c {
  62. panic(ErrShape)
  63. }
  64. ts.Reset()
  65. ts.SymOuterK(1, d.T())
  66. ok = a[i].Factorize(&ts)
  67. if !ok {
  68. gsvd.err = errors.New("hogsvd: cholesky decomposition failed")
  69. return false
  70. }
  71. }
  72. s := getWorkspace(c, c, true)
  73. defer putWorkspace(s)
  74. sij := getWorkspace(c, c, false)
  75. defer putWorkspace(sij)
  76. for i, ai := range a {
  77. for _, aj := range a[i+1:] {
  78. gsvd.err = ai.SolveCholTo(sij, &aj)
  79. if gsvd.err != nil {
  80. return false
  81. }
  82. s.Add(s, sij)
  83. gsvd.err = aj.SolveCholTo(sij, &ai)
  84. if gsvd.err != nil {
  85. return false
  86. }
  87. s.Add(s, sij)
  88. }
  89. }
  90. s.Scale(1/float64(len(m)*(len(m)-1)), s)
  91. var eig Eigen
  92. ok = eig.Factorize(s.T(), EigenRight)
  93. if !ok {
  94. gsvd.err = errors.New("hogsvd: eigen decomposition failed")
  95. return false
  96. }
  97. var vc CDense
  98. eig.VectorsTo(&vc)
  99. // vc is guaranteed to have real eigenvalues.
  100. rc, cc := vc.Dims()
  101. v := NewDense(rc, cc, nil)
  102. for i := 0; i < rc; i++ {
  103. for j := 0; j < cc; j++ {
  104. a := vc.At(i, j)
  105. v.set(i, j, real(a))
  106. }
  107. }
  108. // Rescale the columns of v by their Frobenius norms.
  109. // Work done in cv is reflected in v.
  110. var cv VecDense
  111. for j := 0; j < c; j++ {
  112. cv.ColViewOf(v, j)
  113. cv.ScaleVec(1/blas64.Nrm2(cv.mat), &cv)
  114. }
  115. b := make([]Dense, len(m))
  116. biT := getWorkspace(c, r, false)
  117. defer putWorkspace(biT)
  118. for i, d := range m {
  119. // All calls to reset will leave an emptied
  120. // matrix with capacity to store the result
  121. // without additional allocation.
  122. biT.Reset()
  123. gsvd.err = biT.Solve(v, d.T())
  124. if gsvd.err != nil {
  125. return false
  126. }
  127. b[i].CloneFrom(biT.T())
  128. }
  129. gsvd.n = len(m)
  130. gsvd.v = v
  131. gsvd.b = b
  132. return true
  133. }
  134. // Err returns the reason for a factorization failure.
  135. func (gsvd *HOGSVD) Err() error {
  136. return gsvd.err
  137. }
  138. // Len returns the number of matrices that have been factorized. If Len returns
  139. // zero, the factorization was not successful.
  140. func (gsvd *HOGSVD) Len() int {
  141. return gsvd.n
  142. }
  143. // UTo extracts the matrix U_n from the singular value decomposition, storing
  144. // the result in-place into dst. U_n is size r×c.
  145. //
  146. // If dst is empty, UTo will resize dst to be r×c. When dst is
  147. // non-empty, UTo will panic if dst is not r×c. UTo will also
  148. // panic if the receiver does not contain a successful factorization.
  149. func (gsvd *HOGSVD) UTo(dst *Dense, n int) {
  150. if !gsvd.succFact() {
  151. panic(badFact)
  152. }
  153. if n < 0 || gsvd.n <= n {
  154. panic("hogsvd: invalid index")
  155. }
  156. r, c := gsvd.b[n].Dims()
  157. if dst.IsEmpty() {
  158. dst.ReuseAs(r, c)
  159. } else {
  160. r2, c2 := dst.Dims()
  161. if r != r2 || c != c2 {
  162. panic(ErrShape)
  163. }
  164. }
  165. dst.Copy(&gsvd.b[n])
  166. var v VecDense
  167. for j, f := range gsvd.Values(nil, n) {
  168. v.ColViewOf(dst, j)
  169. v.ScaleVec(1/f, &v)
  170. }
  171. }
  172. // Values returns the nth set of singular values of the factorized system.
  173. // If the input slice is non-nil, the values will be stored in-place into the slice.
  174. // In this case, the slice must have length c, and Values will panic with
  175. // matrix.ErrSliceLengthMismatch otherwise. If the input slice is nil,
  176. // a new slice of the appropriate length will be allocated and returned.
  177. //
  178. // Values will panic if the receiver does not contain a successful factorization.
  179. func (gsvd *HOGSVD) Values(s []float64, n int) []float64 {
  180. if !gsvd.succFact() {
  181. panic(badFact)
  182. }
  183. if n < 0 || gsvd.n <= n {
  184. panic("hogsvd: invalid index")
  185. }
  186. _, c := gsvd.b[n].Dims()
  187. if s == nil {
  188. s = make([]float64, c)
  189. } else if len(s) != c {
  190. panic(ErrSliceLengthMismatch)
  191. }
  192. var v VecDense
  193. for j := 0; j < c; j++ {
  194. v.ColViewOf(&gsvd.b[n], j)
  195. s[j] = blas64.Nrm2(v.mat)
  196. }
  197. return s
  198. }
  199. // VTo extracts the matrix V from the singular value decomposition, storing
  200. // the result in-place into dst. V is size c×c.
  201. //
  202. // If dst is empty, VTo will resize dst to be c×c. When dst is
  203. // non-empty, VTo will panic if dst is not c×c. VTo will also
  204. // panic if the receiver does not contain a successful factorization.
  205. func (gsvd *HOGSVD) VTo(dst *Dense) {
  206. if !gsvd.succFact() {
  207. panic(badFact)
  208. }
  209. r, c := gsvd.v.Dims()
  210. if dst.IsEmpty() {
  211. dst.ReuseAs(r, c)
  212. } else {
  213. r2, c2 := dst.Dims()
  214. if r != r2 || c != c2 {
  215. panic(ErrShape)
  216. }
  217. }
  218. dst.Copy(gsvd.v)
  219. }