github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/triangular.go (about) 1 package mat64 2 3 import ( 4 "math" 5 6 "github.com/gonum/blas" 7 "github.com/gonum/blas/blas64" 8 "github.com/gonum/lapack/lapack64" 9 "github.com/gonum/matrix" 10 ) 11 12 var ( 13 triDense *TriDense 14 _ Matrix = triDense 15 _ Triangular = triDense 16 _ RawTriangular = triDense 17 ) 18 19 const badTriCap = "mat64: bad capacity for TriDense" 20 21 // TriDense represents an upper or lower triangular matrix in dense storage 22 // format. 23 type TriDense struct { 24 mat blas64.Triangular 25 cap int 26 } 27 28 type Triangular interface { 29 Matrix 30 // Triangular returns the number of rows/columns in the matrix and its 31 // orientation. 32 Triangle() (n int, kind matrix.TriKind) 33 34 // TTri is the equivalent of the T() method in the Matrix interface but 35 // guarantees the transpose is of triangular type. 36 TTri() Triangular 37 } 38 39 type RawTriangular interface { 40 RawTriangular() blas64.Triangular 41 } 42 43 var ( 44 _ Matrix = TransposeTri{} 45 _ Triangular = TransposeTri{} 46 _ UntransposeTrier = TransposeTri{} 47 ) 48 49 // TransposeTri is a type for performing an implicit transpose of a Triangular 50 // matrix. It implements the Triangular interface, returning values from the 51 // transpose of the matrix within. 52 type TransposeTri struct { 53 Triangular Triangular 54 } 55 56 // At returns the value of the element at row i and column j of the transposed 57 // matrix, that is, row j and column i of the Triangular field. 58 func (t TransposeTri) At(i, j int) float64 { 59 return t.Triangular.At(j, i) 60 } 61 62 // Dims returns the dimensions of the transposed matrix. Triangular matrices are 63 // square and thus this is the same size as the original Triangular. 64 func (t TransposeTri) Dims() (r, c int) { 65 c, r = t.Triangular.Dims() 66 return r, c 67 } 68 69 // T performs an implicit transpose by returning the Triangular field. 70 func (t TransposeTri) T() Matrix { 71 return t.Triangular 72 } 73 74 // Triangle returns the number of rows/columns in the matrix and its orientation. 75 func (t TransposeTri) Triangle() (int, matrix.TriKind) { 76 n, upper := t.Triangular.Triangle() 77 return n, !upper 78 } 79 80 // TTri performs an implicit transpose by returning the Triangular field. 81 func (t TransposeTri) TTri() Triangular { 82 return t.Triangular 83 } 84 85 // Untranspose returns the Triangular field. 86 func (t TransposeTri) Untranspose() Matrix { 87 return t.Triangular 88 } 89 90 func (t TransposeTri) UntransposeTri() Triangular { 91 return t.Triangular 92 } 93 94 // NewTriDense creates a new Triangular matrix with n rows and columns. If data == nil, 95 // a new slice is allocated for the backing slice. If len(data) == n*n, data is 96 // used as the backing slice, and changes to the elements of the returned TriDense 97 // will be reflected in data. If neither of these is true, NewTriDense will panic. 98 // 99 // The data must be arranged in row-major order, i.e. the (i*c + j)-th 100 // element in the data slice is the {i, j}-th element in the matrix. 101 // Only the values in the triangular portion corresponding to kind are used. 102 func NewTriDense(n int, kind matrix.TriKind, data []float64) *TriDense { 103 if n < 0 { 104 panic("mat64: negative dimension") 105 } 106 if data != nil && len(data) != n*n { 107 panic(matrix.ErrShape) 108 } 109 if data == nil { 110 data = make([]float64, n*n) 111 } 112 uplo := blas.Lower 113 if kind == matrix.Upper { 114 uplo = blas.Upper 115 } 116 return &TriDense{ 117 mat: blas64.Triangular{ 118 N: n, 119 Stride: n, 120 Data: data, 121 Uplo: uplo, 122 Diag: blas.NonUnit, 123 }, 124 cap: n, 125 } 126 } 127 128 func (t *TriDense) Dims() (r, c int) { 129 return t.mat.N, t.mat.N 130 } 131 132 // Triangle returns the dimension of t and its orientation. The returned 133 // orientation is only valid when n is not zero. 134 func (t *TriDense) Triangle() (n int, kind matrix.TriKind) { 135 return t.mat.N, matrix.TriKind(!t.isZero()) && t.triKind() 136 } 137 138 func (t *TriDense) isUpper() bool { 139 return isUpperUplo(t.mat.Uplo) 140 } 141 142 func (t *TriDense) triKind() matrix.TriKind { 143 return matrix.TriKind(isUpperUplo(t.mat.Uplo)) 144 } 145 146 func isUpperUplo(u blas.Uplo) bool { 147 switch u { 148 case blas.Upper: 149 return true 150 case blas.Lower: 151 return false 152 default: 153 panic(badTriangle) 154 } 155 } 156 157 // asSymBlas returns the receiver restructured as a blas64.Symmetric with the 158 // same backing memory. Panics if the receiver is unit. 159 // This returns a blas64.Symmetric and not a *SymDense because SymDense can only 160 // be upper triangular. 161 func (t *TriDense) asSymBlas() blas64.Symmetric { 162 if t.mat.Diag == blas.Unit { 163 panic("mat64: cannot convert unit TriDense into blas64.Symmetric") 164 } 165 return blas64.Symmetric{ 166 N: t.mat.N, 167 Stride: t.mat.Stride, 168 Data: t.mat.Data, 169 Uplo: t.mat.Uplo, 170 } 171 } 172 173 // T performs an implicit transpose by returning the receiver inside a Transpose. 174 func (t *TriDense) T() Matrix { 175 return Transpose{t} 176 } 177 178 // TTri performs an implicit transpose by returning the receiver inside a TransposeTri. 179 func (t *TriDense) TTri() Triangular { 180 return TransposeTri{t} 181 } 182 183 func (t *TriDense) RawTriangular() blas64.Triangular { 184 return t.mat 185 } 186 187 // Reset zeros the dimensions of the matrix so that it can be reused as the 188 // receiver of a dimensionally restricted operation. 189 // 190 // See the Reseter interface for more information. 191 func (t *TriDense) Reset() { 192 // N and Stride must be zeroed in unison. 193 t.mat.N, t.mat.Stride = 0, 0 194 // Defensively zero Uplo to ensure 195 // it is set correctly later. 196 t.mat.Uplo = 0 197 t.mat.Data = t.mat.Data[:0] 198 } 199 200 func (t *TriDense) isZero() bool { 201 // It must be the case that t.Dims() returns 202 // zeros in this case. See comment in Reset(). 203 return t.mat.Stride == 0 204 } 205 206 // untranspose untransposes a matrix if applicable. If a is an Untransposer, then 207 // untranspose returns the underlying matrix and true. If it is not, then it returns 208 // the input matrix and false. 209 func untransposeTri(a Triangular) (Triangular, bool) { 210 if ut, ok := a.(UntransposeTrier); ok { 211 return ut.UntransposeTri(), true 212 } 213 return a, false 214 } 215 216 // reuseAs resizes a zero receiver to an n×n triangular matrix with the given 217 // orientation. If the receiver is non-zero, reuseAs checks that the receiver 218 // is the correct size and orientation. 219 func (t *TriDense) reuseAs(n int, kind matrix.TriKind) { 220 ul := blas.Lower 221 if kind == matrix.Upper { 222 ul = blas.Upper 223 } 224 if t.mat.N > t.cap { 225 panic(badTriCap) 226 } 227 if t.isZero() { 228 t.mat = blas64.Triangular{ 229 N: n, 230 Stride: n, 231 Diag: blas.NonUnit, 232 Data: use(t.mat.Data, n*n), 233 Uplo: ul, 234 } 235 t.cap = n 236 return 237 } 238 if t.mat.N != n { 239 panic(matrix.ErrShape) 240 } 241 if t.mat.Uplo != ul { 242 panic(matrix.ErrTriangle) 243 } 244 } 245 246 // isolatedWorkspace returns a new TriDense matrix w with the size of a and 247 // returns a callback to defer which performs cleanup at the return of the call. 248 // This should be used when a method receiver is the same pointer as an input argument. 249 func (t *TriDense) isolatedWorkspace(a Triangular) (w *TriDense, restore func()) { 250 n, kind := a.Triangle() 251 if n == 0 { 252 panic("zero size") 253 } 254 w = getWorkspaceTri(n, kind, false) 255 return w, func() { 256 t.Copy(w) 257 putWorkspaceTri(w) 258 } 259 } 260 261 // Copy makes a copy of elements of a into the receiver. It is similar to the 262 // built-in copy; it copies as much as the overlap between the two matrices and 263 // returns the number of rows and columns it copied. Only elements within the 264 // receiver's non-zero triangle are set. 265 // 266 // See the Copier interface for more information. 267 func (t *TriDense) Copy(a Matrix) (r, c int) { 268 r, c = a.Dims() 269 r = min(r, t.mat.N) 270 c = min(c, t.mat.N) 271 if r == 0 || c == 0 { 272 return 0, 0 273 } 274 275 switch a := a.(type) { 276 case RawMatrixer: 277 amat := a.RawMatrix() 278 if t.isUpper() { 279 for i := 0; i < r; i++ { 280 copy(t.mat.Data[i*t.mat.Stride+i:i*t.mat.Stride+c], amat.Data[i*amat.Stride+i:i*amat.Stride+c]) 281 } 282 } else { 283 for i := 0; i < r; i++ { 284 copy(t.mat.Data[i*t.mat.Stride:i*t.mat.Stride+i+1], amat.Data[i*amat.Stride:i*amat.Stride+i+1]) 285 } 286 } 287 case RawTriangular: 288 amat := a.RawTriangular() 289 aIsUpper := isUpperUplo(amat.Uplo) 290 tIsUpper := t.isUpper() 291 switch { 292 case tIsUpper && aIsUpper: 293 for i := 0; i < r; i++ { 294 copy(t.mat.Data[i*t.mat.Stride+i:i*t.mat.Stride+c], amat.Data[i*amat.Stride+i:i*amat.Stride+c]) 295 } 296 case !tIsUpper && !aIsUpper: 297 for i := 0; i < r; i++ { 298 copy(t.mat.Data[i*t.mat.Stride:i*t.mat.Stride+i+1], amat.Data[i*amat.Stride:i*amat.Stride+i+1]) 299 } 300 default: 301 for i := 0; i < r; i++ { 302 t.set(i, i, amat.Data[i*amat.Stride+i]) 303 } 304 } 305 default: 306 isUpper := t.isUpper() 307 for i := 0; i < r; i++ { 308 if isUpper { 309 for j := i; j < c; j++ { 310 t.set(i, j, a.At(i, j)) 311 } 312 } else { 313 for j := 0; j <= i; j++ { 314 t.set(i, j, a.At(i, j)) 315 } 316 } 317 } 318 } 319 320 return r, c 321 } 322 323 // InverseTri computes the inverse of the triangular matrix a, storing the result 324 // into the receiver. If a is ill-conditioned, a Condition error will be returned. 325 // Note that matrix inversion is numerically unstable, and should generally be 326 // avoided where possible, for example by using the Solve routines. 327 func (t *TriDense) InverseTri(a Triangular) error { 328 if rt, ok := a.(RawTriangular); ok { 329 t.checkOverlap(rt.RawTriangular()) 330 } 331 n, _ := a.Triangle() 332 t.reuseAs(a.Triangle()) 333 t.Copy(a) 334 work := make([]float64, 3*n) 335 iwork := make([]int, n) 336 cond := lapack64.Trcon(matrix.CondNorm, t.mat, work, iwork) 337 if math.IsInf(cond, 1) { 338 return matrix.Condition(cond) 339 } 340 ok := lapack64.Trtri(t.mat) 341 if !ok { 342 return matrix.Condition(math.Inf(1)) 343 } 344 if cond > matrix.ConditionTolerance { 345 return matrix.Condition(cond) 346 } 347 return nil 348 } 349 350 // MulTri takes the product of triangular matrices a and b and places the result 351 // in the receiver. The size of a and b must match, and they both must have the 352 // same TriKind, or Mul will panic. 353 func (t *TriDense) MulTri(a, b Triangular) { 354 n, kind := a.Triangle() 355 nb, kindb := b.Triangle() 356 if n != nb { 357 panic(matrix.ErrShape) 358 } 359 if kind != kindb { 360 panic(matrix.ErrTriangle) 361 } 362 363 aU, _ := untransposeTri(a) 364 bU, _ := untransposeTri(b) 365 t.reuseAs(n, kind) 366 var restore func() 367 if t == aU { 368 t, restore = t.isolatedWorkspace(aU) 369 defer restore() 370 } else if t == bU { 371 t, restore = t.isolatedWorkspace(bU) 372 defer restore() 373 } 374 375 // TODO(btracey): Improve the set of fast-paths. 376 if kind == matrix.Upper { 377 for i := 0; i < n; i++ { 378 for j := i; j < n; j++ { 379 var v float64 380 for k := i; k <= j; k++ { 381 v += a.At(i, k) * b.At(k, j) 382 } 383 t.SetTri(i, j, v) 384 } 385 } 386 return 387 } 388 for i := 0; i < n; i++ { 389 for j := 0; j <= i; j++ { 390 var v float64 391 for k := j; k <= i; k++ { 392 v += a.At(i, k) * b.At(k, j) 393 } 394 t.SetTri(i, j, v) 395 } 396 } 397 } 398 399 // copySymIntoTriangle copies a symmetric matrix into a TriDense 400 func copySymIntoTriangle(t *TriDense, s Symmetric) { 401 n, upper := t.Triangle() 402 ns := s.Symmetric() 403 if n != ns { 404 panic("mat64: triangle size mismatch") 405 } 406 ts := t.mat.Stride 407 if rs, ok := s.(RawSymmetricer); ok { 408 sd := rs.RawSymmetric() 409 ss := sd.Stride 410 if upper { 411 if sd.Uplo == blas.Upper { 412 for i := 0; i < n; i++ { 413 copy(t.mat.Data[i*ts+i:i*ts+n], sd.Data[i*ss+i:i*ss+n]) 414 } 415 return 416 } 417 for i := 0; i < n; i++ { 418 for j := i; j < n; j++ { 419 t.mat.Data[i*ts+j] = sd.Data[j*ss+i] 420 } 421 return 422 } 423 } 424 if sd.Uplo == blas.Upper { 425 for i := 0; i < n; i++ { 426 for j := 0; j <= i; j++ { 427 t.mat.Data[i*ts+j] = sd.Data[j*ss+i] 428 } 429 } 430 return 431 } 432 for i := 0; i < n; i++ { 433 copy(t.mat.Data[i*ts:i*ts+i+1], sd.Data[i*ss:i*ss+i+1]) 434 } 435 return 436 } 437 if upper { 438 for i := 0; i < n; i++ { 439 for j := i; j < n; j++ { 440 t.mat.Data[i*ts+j] = s.At(i, j) 441 } 442 } 443 return 444 } 445 for i := 0; i < n; i++ { 446 for j := 0; j <= i; j++ { 447 t.mat.Data[i*ts+j] = s.At(i, j) 448 } 449 } 450 }