mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 23:26:52 +08:00
91 lines
2.3 KiB
Go
91 lines
2.3 KiB
Go
// Copyright ©2018 The Gonum Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package mds
|
|
|
|
import (
|
|
"math"
|
|
|
|
"gonum.org/v1/gonum/blas/blas64"
|
|
"gonum.org/v1/gonum/mat"
|
|
)
|
|
|
|
// TorgersonScaling converts a dissimilarity matrix to a matrix containing
|
|
// Euclidean coordinates. TorgersonScaling places the coordinates in dst and
|
|
// returns it and the number of positive Eigenvalues if successful.
|
|
// Note that Eigen Decomposition is numerically unstable and so Eigenvalues
|
|
// near zero should be examined and the value returned for k is advisory only.
|
|
// If the scaling is not successful, dst will be empty upon return.
|
|
// When the scaling is successful, dst will be resized to k columns wide.
|
|
// Eigenvalues will be copied into eigdst and returned as eig if it is provided.
|
|
//
|
|
// TorgersonScaling will panic if dst is not empty.
|
|
func TorgersonScaling(dst *mat.Dense, eigdst []float64, dis mat.Symmetric) (k int, eig []float64) {
|
|
// https://doi.org/10.1007/0-387-28981-X_12
|
|
|
|
n := dis.SymmetricDim()
|
|
if dst.IsEmpty() {
|
|
dst.ReuseAs(n, n)
|
|
} else {
|
|
panic("mds: receiver matrix not empty")
|
|
}
|
|
|
|
b := mat.NewSymDense(n, nil)
|
|
for i := 0; i < n; i++ {
|
|
for j := i; j < n; j++ {
|
|
v := dis.At(i, j)
|
|
v *= v
|
|
b.SetSym(i, j, v)
|
|
}
|
|
}
|
|
c := mat.NewSymDense(n, nil)
|
|
s := -1 / float64(n)
|
|
for i := 0; i < n; i++ {
|
|
c.SetSym(i, i, 1+s)
|
|
for j := i + 1; j < n; j++ {
|
|
c.SetSym(i, j, s)
|
|
}
|
|
}
|
|
dst.Product(c, b, c)
|
|
for i := 0; i < n; i++ {
|
|
for j := i; j < n; j++ {
|
|
b.SetSym(i, j, -0.5*dst.At(i, j))
|
|
}
|
|
}
|
|
|
|
var ed mat.EigenSym
|
|
ok := ed.Factorize(b, true)
|
|
if !ok {
|
|
return 0, eigdst
|
|
}
|
|
ed.VectorsTo(dst)
|
|
vals := ed.Values(nil)
|
|
reverse(vals, dst.RawMatrix())
|
|
copy(eigdst, vals)
|
|
|
|
for i, v := range vals {
|
|
if v < 0 {
|
|
vals[i] = 0
|
|
continue
|
|
}
|
|
k = i + 1
|
|
vals[i] = math.Sqrt(v)
|
|
}
|
|
|
|
var tmp mat.Dense
|
|
tmp.Mul(dst, mat.NewDiagonalRect(n, k, vals[:k]))
|
|
*dst = *dst.Slice(0, n, 0, k).(*mat.Dense)
|
|
dst.Copy(&tmp)
|
|
|
|
return k, eigdst
|
|
}
|
|
|
|
func reverse(values []float64, vectors blas64.General) {
|
|
for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 {
|
|
values[i], values[j] = values[j], values[i]
|
|
blas64.Swap(blas64.Vector{N: vectors.Rows, Inc: vectors.Stride, Data: vectors.Data[i:]},
|
|
blas64.Vector{N: vectors.Rows, Inc: vectors.Stride, Data: vectors.Data[j:]})
|
|
}
|
|
}
|