File: mds.go

package info (click to toggle)
golang-gonum-v1-gonum 0.15.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 18,792 kB
  • sloc: asm: 6,252; fortran: 5,271; sh: 377; ruby: 211; makefile: 98
file content (90 lines) | stat: -rw-r--r-- 2,376 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// 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:]})
	}
}