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  }