github.com/gonum/matrix@v0.0.0-20181209220409-c518dec07be9/mat64/symmetric.go (about)

     1  // Copyright ©2015 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 mat64
     6  
     7  import (
     8  	"math"
     9  
    10  	"github.com/gonum/blas"
    11  	"github.com/gonum/blas/blas64"
    12  	"github.com/gonum/matrix"
    13  )
    14  
    15  var (
    16  	symDense *SymDense
    17  
    18  	_ Matrix           = symDense
    19  	_ Symmetric        = symDense
    20  	_ RawSymmetricer   = symDense
    21  	_ MutableSymmetric = symDense
    22  )
    23  
    24  const (
    25  	badSymTriangle = "mat64: blas64.Symmetric not upper"
    26  	badSymCap      = "mat64: bad capacity for SymDense"
    27  )
    28  
    29  // SymDense is a symmetric matrix that uses dense storage. SymDense
    30  // matrices are stored in the upper triangle.
    31  type SymDense struct {
    32  	mat blas64.Symmetric
    33  	cap int
    34  }
    35  
    36  // Symmetric represents a symmetric matrix (where the element at {i, j} equals
    37  // the element at {j, i}). Symmetric matrices are always square.
    38  type Symmetric interface {
    39  	Matrix
    40  	// Symmetric returns the number of rows/columns in the matrix.
    41  	Symmetric() int
    42  }
    43  
    44  // A RawSymmetricer can return a view of itself as a BLAS Symmetric matrix.
    45  type RawSymmetricer interface {
    46  	RawSymmetric() blas64.Symmetric
    47  }
    48  
    49  type MutableSymmetric interface {
    50  	Symmetric
    51  	SetSym(i, j int, v float64)
    52  }
    53  
    54  // NewSymDense creates a new Symmetric matrix with n rows and columns. If data == nil,
    55  // a new slice is allocated for the backing slice. If len(data) == n*n, data is
    56  // used as the backing slice, and changes to the elements of the returned SymDense
    57  // will be reflected in data. If neither of these is true, NewSymDense will panic.
    58  //
    59  // The data must be arranged in row-major order, i.e. the (i*c + j)-th
    60  // element in the data slice is the {i, j}-th element in the matrix.
    61  // Only the values in the upper triangular portion of the matrix are used.
    62  func NewSymDense(n int, data []float64) *SymDense {
    63  	if n < 0 {
    64  		panic("mat64: negative dimension")
    65  	}
    66  	if data != nil && n*n != len(data) {
    67  		panic(matrix.ErrShape)
    68  	}
    69  	if data == nil {
    70  		data = make([]float64, n*n)
    71  	}
    72  	return &SymDense{
    73  		mat: blas64.Symmetric{
    74  			N:      n,
    75  			Stride: n,
    76  			Data:   data,
    77  			Uplo:   blas.Upper,
    78  		},
    79  		cap: n,
    80  	}
    81  }
    82  
    83  func (s *SymDense) Dims() (r, c int) {
    84  	return s.mat.N, s.mat.N
    85  }
    86  
    87  // T implements the Matrix interface. Symmetric matrices, by definition, are
    88  // equal to their transpose, and this is a no-op.
    89  func (s *SymDense) T() Matrix {
    90  	return s
    91  }
    92  
    93  func (s *SymDense) Symmetric() int {
    94  	return s.mat.N
    95  }
    96  
    97  // RawSymmetric returns the matrix as a blas64.Symmetric. The returned
    98  // value must be stored in upper triangular format.
    99  func (s *SymDense) RawSymmetric() blas64.Symmetric {
   100  	return s.mat
   101  }
   102  
   103  // SetRawSymmetric sets the underlying blas64.Symmetric used by the receiver.
   104  // Changes to elements in the receiver following the call will be reflected
   105  // in b. SetRawSymmetric will panic if b is not an upper-encoded symmetric
   106  // matrix.
   107  func (s *SymDense) SetRawSymmetric(b blas64.Symmetric) {
   108  	if b.Uplo != blas.Upper {
   109  		panic(badSymTriangle)
   110  	}
   111  	s.mat = b
   112  }
   113  
   114  // Reset zeros the dimensions of the matrix so that it can be reused as the
   115  // receiver of a dimensionally restricted operation.
   116  //
   117  // See the Reseter interface for more information.
   118  func (s *SymDense) Reset() {
   119  	// N and Stride must be zeroed in unison.
   120  	s.mat.N, s.mat.Stride = 0, 0
   121  	s.mat.Data = s.mat.Data[:0]
   122  }
   123  
   124  func (s *SymDense) isZero() bool {
   125  	// It must be the case that m.Dims() returns
   126  	// zeros in this case. See comment in Reset().
   127  	return s.mat.N == 0
   128  }
   129  
   130  // reuseAs resizes an empty matrix to a n×n matrix,
   131  // or checks that a non-empty matrix is n×n.
   132  func (s *SymDense) reuseAs(n int) {
   133  	if s.mat.N > s.cap {
   134  		panic(badSymCap)
   135  	}
   136  	if s.isZero() {
   137  		s.mat = blas64.Symmetric{
   138  			N:      n,
   139  			Stride: n,
   140  			Data:   use(s.mat.Data, n*n),
   141  			Uplo:   blas.Upper,
   142  		}
   143  		s.cap = n
   144  		return
   145  	}
   146  	if s.mat.Uplo != blas.Upper {
   147  		panic(badSymTriangle)
   148  	}
   149  	if s.mat.N != n {
   150  		panic(matrix.ErrShape)
   151  	}
   152  }
   153  
   154  func (s *SymDense) isolatedWorkspace(a Symmetric) (w *SymDense, restore func()) {
   155  	n := a.Symmetric()
   156  	w = getWorkspaceSym(n, false)
   157  	return w, func() {
   158  		s.CopySym(w)
   159  		putWorkspaceSym(w)
   160  	}
   161  }
   162  
   163  func (s *SymDense) AddSym(a, b Symmetric) {
   164  	n := a.Symmetric()
   165  	if n != b.Symmetric() {
   166  		panic(matrix.ErrShape)
   167  	}
   168  	s.reuseAs(n)
   169  
   170  	if a, ok := a.(RawSymmetricer); ok {
   171  		if b, ok := b.(RawSymmetricer); ok {
   172  			amat, bmat := a.RawSymmetric(), b.RawSymmetric()
   173  			if s != a {
   174  				s.checkOverlap(amat)
   175  			}
   176  			if s != b {
   177  				s.checkOverlap(bmat)
   178  			}
   179  			for i := 0; i < n; i++ {
   180  				btmp := bmat.Data[i*bmat.Stride+i : i*bmat.Stride+n]
   181  				stmp := s.mat.Data[i*s.mat.Stride+i : i*s.mat.Stride+n]
   182  				for j, v := range amat.Data[i*amat.Stride+i : i*amat.Stride+n] {
   183  					stmp[j] = v + btmp[j]
   184  				}
   185  			}
   186  			return
   187  		}
   188  	}
   189  
   190  	for i := 0; i < n; i++ {
   191  		stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
   192  		for j := i; j < n; j++ {
   193  			stmp[j] = a.At(i, j) + b.At(i, j)
   194  		}
   195  	}
   196  }
   197  
   198  func (s *SymDense) CopySym(a Symmetric) int {
   199  	n := a.Symmetric()
   200  	n = min(n, s.mat.N)
   201  	if n == 0 {
   202  		return 0
   203  	}
   204  	switch a := a.(type) {
   205  	case RawSymmetricer:
   206  		amat := a.RawSymmetric()
   207  		if amat.Uplo != blas.Upper {
   208  			panic(badSymTriangle)
   209  		}
   210  		for i := 0; i < n; i++ {
   211  			copy(s.mat.Data[i*s.mat.Stride+i:i*s.mat.Stride+n], amat.Data[i*amat.Stride+i:i*amat.Stride+n])
   212  		}
   213  	default:
   214  		for i := 0; i < n; i++ {
   215  			stmp := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
   216  			for j := i; j < n; j++ {
   217  				stmp[j] = a.At(i, j)
   218  			}
   219  		}
   220  	}
   221  	return n
   222  }
   223  
   224  // SymRankOne performs a symetric rank-one update to the matrix a and stores
   225  // the result in the receiver
   226  //  s = a + alpha * x * x'
   227  func (s *SymDense) SymRankOne(a Symmetric, alpha float64, x *Vector) {
   228  	n := x.Len()
   229  	if a.Symmetric() != n {
   230  		panic(matrix.ErrShape)
   231  	}
   232  	s.reuseAs(n)
   233  	if s != a {
   234  		if rs, ok := a.(RawSymmetricer); ok {
   235  			s.checkOverlap(rs.RawSymmetric())
   236  		}
   237  		s.CopySym(a)
   238  	}
   239  	blas64.Syr(alpha, x.mat, s.mat)
   240  }
   241  
   242  // SymRankK performs a symmetric rank-k update to the matrix a and stores the
   243  // result into the receiver. If a is zero, see SymOuterK.
   244  //  s = a + alpha * x * x'
   245  func (s *SymDense) SymRankK(a Symmetric, alpha float64, x Matrix) {
   246  	n := a.Symmetric()
   247  	r, _ := x.Dims()
   248  	if r != n {
   249  		panic(matrix.ErrShape)
   250  	}
   251  	xMat, aTrans := untranspose(x)
   252  	var g blas64.General
   253  	if rm, ok := xMat.(RawMatrixer); ok {
   254  		g = rm.RawMatrix()
   255  	} else {
   256  		g = DenseCopyOf(x).mat
   257  		aTrans = false
   258  	}
   259  	if a != s {
   260  		if rs, ok := a.(RawSymmetricer); ok {
   261  			s.checkOverlap(rs.RawSymmetric())
   262  		}
   263  		s.reuseAs(n)
   264  		s.CopySym(a)
   265  	}
   266  	t := blas.NoTrans
   267  	if aTrans {
   268  		t = blas.Trans
   269  	}
   270  	blas64.Syrk(t, alpha, g, 1, s.mat)
   271  }
   272  
   273  // SymOuterK calculates the outer product of x with itself and stores
   274  // the result into the receiver. It is equivalent to the matrix
   275  // multiplication
   276  //  s = alpha * x * x'.
   277  // In order to update an existing matrix, see SymRankOne.
   278  func (s *SymDense) SymOuterK(alpha float64, x Matrix) {
   279  	n, _ := x.Dims()
   280  	switch {
   281  	case s.isZero():
   282  		s.mat = blas64.Symmetric{
   283  			N:      n,
   284  			Stride: n,
   285  			Data:   useZeroed(s.mat.Data, n*n),
   286  			Uplo:   blas.Upper,
   287  		}
   288  		s.cap = n
   289  		s.SymRankK(s, alpha, x)
   290  	case s.mat.Uplo != blas.Upper:
   291  		panic(badSymTriangle)
   292  	case s.mat.N == n:
   293  		if s == x {
   294  			w := getWorkspaceSym(n, true)
   295  			w.SymRankK(w, alpha, x)
   296  			s.CopySym(w)
   297  			putWorkspaceSym(w)
   298  		} else {
   299  			if rs, ok := x.(RawSymmetricer); ok {
   300  				s.checkOverlap(rs.RawSymmetric())
   301  			}
   302  			// Only zero the upper triangle.
   303  			for i := 0; i < n; i++ {
   304  				ri := i * s.mat.Stride
   305  				zero(s.mat.Data[ri+i : ri+n])
   306  			}
   307  			s.SymRankK(s, alpha, x)
   308  		}
   309  	default:
   310  		panic(matrix.ErrShape)
   311  	}
   312  }
   313  
   314  // RankTwo performs a symmmetric rank-two update to the matrix a and stores
   315  // the result in the receiver
   316  //  m = a + alpha * (x * y' + y * x')
   317  func (s *SymDense) RankTwo(a Symmetric, alpha float64, x, y *Vector) {
   318  	n := s.mat.N
   319  	if x.Len() != n {
   320  		panic(matrix.ErrShape)
   321  	}
   322  	if y.Len() != n {
   323  		panic(matrix.ErrShape)
   324  	}
   325  	var w SymDense
   326  	if s == a {
   327  		w = *s
   328  	}
   329  	w.reuseAs(n)
   330  	if s != a {
   331  		if rs, ok := a.(RawSymmetricer); ok {
   332  			s.checkOverlap(rs.RawSymmetric())
   333  		}
   334  		w.CopySym(a)
   335  	}
   336  	blas64.Syr2(alpha, x.mat, y.mat, w.mat)
   337  	*s = w
   338  	return
   339  }
   340  
   341  // ScaleSym multiplies the elements of a by f, placing the result in the receiver.
   342  func (s *SymDense) ScaleSym(f float64, a Symmetric) {
   343  	n := a.Symmetric()
   344  	s.reuseAs(n)
   345  	if a, ok := a.(RawSymmetricer); ok {
   346  		amat := a.RawSymmetric()
   347  		if s != a {
   348  			s.checkOverlap(amat)
   349  		}
   350  		for i := 0; i < n; i++ {
   351  			for j := i; j < n; j++ {
   352  				s.mat.Data[i*s.mat.Stride+j] = f * amat.Data[i*amat.Stride+j]
   353  			}
   354  		}
   355  		return
   356  	}
   357  	for i := 0; i < n; i++ {
   358  		for j := i; j < n; j++ {
   359  			s.mat.Data[i*s.mat.Stride+j] = f * a.At(i, j)
   360  		}
   361  	}
   362  }
   363  
   364  // SubsetSym extracts a subset of the rows and columns of the matrix a and stores
   365  // the result in-place into the receiver. The resulting matrix size is
   366  // len(set)×len(set). Specifically, at the conclusion of SubsetSym,
   367  // s.At(i, j) equals a.At(set[i], set[j]). Note that the supplied set does not
   368  // have to be a strict subset, dimension repeats are allowed.
   369  func (s *SymDense) SubsetSym(a Symmetric, set []int) {
   370  	n := len(set)
   371  	na := a.Symmetric()
   372  	s.reuseAs(n)
   373  	var restore func()
   374  	if a == s {
   375  		s, restore = s.isolatedWorkspace(a)
   376  		defer restore()
   377  	}
   378  
   379  	if a, ok := a.(RawSymmetricer); ok {
   380  		raw := a.RawSymmetric()
   381  		if s != a {
   382  			s.checkOverlap(raw)
   383  		}
   384  		for i := 0; i < n; i++ {
   385  			ssub := s.mat.Data[i*s.mat.Stride : i*s.mat.Stride+n]
   386  			r := set[i]
   387  			rsub := raw.Data[r*raw.Stride : r*raw.Stride+na]
   388  			for j := i; j < n; j++ {
   389  				c := set[j]
   390  				if r <= c {
   391  					ssub[j] = rsub[c]
   392  				} else {
   393  					ssub[j] = raw.Data[c*raw.Stride+r]
   394  				}
   395  			}
   396  		}
   397  		return
   398  	}
   399  	for i := 0; i < n; i++ {
   400  		for j := i; j < n; j++ {
   401  			s.mat.Data[i*s.mat.Stride+j] = a.At(set[i], set[j])
   402  		}
   403  	}
   404  }
   405  
   406  // ViewSquare returns a view of the submatrix starting at {i, i} and extending
   407  // for n rows and columns. ViewSquare panics if the view is outside the bounds
   408  // of the receiver.
   409  //
   410  // ViewSquare is deprecated and should not be used. It will be removed at a later date.
   411  func (s *SymDense) ViewSquare(i, n int) Matrix {
   412  	return s.SliceSquare(i, i+n)
   413  }
   414  
   415  // SliceSquare returns a new Matrix that shares backing data with the receiver.
   416  // The returned matrix starts at {i,i} of the recevier and extends k-i rows
   417  // and columns. The final row and column in the resulting matrix is k-1.
   418  // SliceSquare panics with ErrIndexOutOfRange if the slice is outside the bounds
   419  // of the receiver.
   420  func (s *SymDense) SliceSquare(i, k int) Matrix {
   421  	sz := s.Symmetric()
   422  	if i < 0 || sz < i || k < i || sz < k {
   423  		panic(matrix.ErrIndexOutOfRange)
   424  	}
   425  	v := *s
   426  	v.mat.Data = s.mat.Data[i*s.mat.Stride+i : (k-1)*s.mat.Stride+k]
   427  	v.mat.N = k - i
   428  	v.cap = s.cap - i
   429  	return &v
   430  }
   431  
   432  // GrowSquare returns the receiver expanded by n rows and n columns. If the
   433  // dimensions of the expanded matrix are outside the capacity of the receiver
   434  // a new allocation is made, otherwise not. Note that the receiver itself is
   435  // not modified during the call to GrowSquare.
   436  func (s *SymDense) GrowSquare(n int) Matrix {
   437  	if n < 0 {
   438  		panic(matrix.ErrIndexOutOfRange)
   439  	}
   440  	if n == 0 {
   441  		return s
   442  	}
   443  	var v SymDense
   444  	n += s.mat.N
   445  	if n > s.cap {
   446  		v.mat = blas64.Symmetric{
   447  			N:      n,
   448  			Stride: n,
   449  			Uplo:   blas.Upper,
   450  			Data:   make([]float64, n*n),
   451  		}
   452  		v.cap = n
   453  		// Copy elements, including those not currently visible. Use a temporary
   454  		// structure to avoid modifying the receiver.
   455  		var tmp SymDense
   456  		tmp.mat = blas64.Symmetric{
   457  			N:      s.cap,
   458  			Stride: s.mat.Stride,
   459  			Data:   s.mat.Data,
   460  			Uplo:   s.mat.Uplo,
   461  		}
   462  		tmp.cap = s.cap
   463  		v.CopySym(&tmp)
   464  		return &v
   465  	}
   466  	v.mat = blas64.Symmetric{
   467  		N:      n,
   468  		Stride: s.mat.Stride,
   469  		Uplo:   blas.Upper,
   470  		Data:   s.mat.Data[:(n-1)*s.mat.Stride+n],
   471  	}
   472  	v.cap = s.cap
   473  	return &v
   474  }
   475  
   476  // PowPSD computes a^pow where a is a positive symmetric definite matrix.
   477  //
   478  // PowPSD returns an error if the matrix is not  not positive symmetric definite
   479  // or the Eigendecomposition is not successful.
   480  func (s *SymDense) PowPSD(a Symmetric, pow float64) error {
   481  	dim := a.Symmetric()
   482  	s.reuseAs(dim)
   483  
   484  	var eigen EigenSym
   485  	ok := eigen.Factorize(a, true)
   486  	if !ok {
   487  		return matrix.ErrFailedEigen
   488  	}
   489  	values := eigen.Values(nil)
   490  	for i, v := range values {
   491  		if v <= 0 {
   492  			return matrix.ErrNotPSD
   493  		}
   494  		values[i] = math.Pow(v, pow)
   495  	}
   496  	var u Dense
   497  	u.EigenvectorsSym(&eigen)
   498  
   499  	s.SymOuterK(values[0], u.ColView(0))
   500  	for i := 1; i < dim; i++ {
   501  		s.SymRankOne(s, values[i], u.ColView(i))
   502  	}
   503  	return nil
   504  }