mirror of
https://github.com/gonum/gonum.git
synced 2025-10-30 18:16:32 +08:00
177 lines
3.2 KiB
Go
177 lines
3.2 KiB
Go
// Copyright ©2017 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 spectral
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"gonum.org/v1/gonum/floats/scalar"
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/graph/internal/ordered"
|
|
"gonum.org/v1/gonum/graph/iterator"
|
|
"gonum.org/v1/gonum/graph/simple"
|
|
"gonum.org/v1/gonum/mat"
|
|
)
|
|
|
|
var randomWalkLaplacianTests = []struct {
|
|
g []set
|
|
damp float64
|
|
|
|
want *mat.Dense
|
|
}{
|
|
{
|
|
g: []set{
|
|
A: linksTo(B, C),
|
|
B: linksTo(C),
|
|
C: nil,
|
|
},
|
|
|
|
want: mat.NewDense(3, 3, []float64{
|
|
1, 0, 0,
|
|
-0.5, 1, 0,
|
|
-0.5, -1, 0,
|
|
}),
|
|
},
|
|
{
|
|
g: []set{
|
|
A: linksTo(B, C),
|
|
B: linksTo(C),
|
|
C: nil,
|
|
},
|
|
damp: 0.85,
|
|
|
|
want: mat.NewDense(3, 3, []float64{
|
|
0.15, 0, 0,
|
|
-0.075, 0.15, 0,
|
|
-0.075, -0.15, 0,
|
|
}),
|
|
},
|
|
{
|
|
g: []set{
|
|
A: linksTo(B),
|
|
B: linksTo(C),
|
|
C: linksTo(A),
|
|
},
|
|
damp: 0.85,
|
|
|
|
want: mat.NewDense(3, 3, []float64{
|
|
0.15, 0, -0.15,
|
|
-0.15, 0.15, 0,
|
|
0, -0.15, 0.15,
|
|
}),
|
|
},
|
|
{
|
|
// Example graph from http://en.wikipedia.org/wiki/File:PageRanks-Example.svg 16:17, 8 July 2009
|
|
g: []set{
|
|
A: nil,
|
|
B: linksTo(C),
|
|
C: linksTo(B),
|
|
D: linksTo(A, B),
|
|
E: linksTo(D, B, F),
|
|
F: linksTo(B, E),
|
|
G: linksTo(B, E),
|
|
H: linksTo(B, E),
|
|
I: linksTo(B, E),
|
|
J: linksTo(E),
|
|
K: linksTo(E),
|
|
},
|
|
|
|
want: mat.NewDense(11, 11, []float64{
|
|
0, 0, 0, -0.5, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 1, -1, -0.5, -1. / 3., -0.5, -0.5, -0.5, -0.5, 0, 0,
|
|
0, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 1, -1. / 3., 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 1, -0.5, -0.5, -0.5, -0.5, -1, -1,
|
|
0, 0, 0, 0, -1. / 3., 1, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
|
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
|
}),
|
|
},
|
|
}
|
|
|
|
func TestRandomWalkLaplacian(t *testing.T) {
|
|
const tol = 1e-14
|
|
for i, test := range randomWalkLaplacianTests {
|
|
g := simple.NewDirectedGraph()
|
|
for u, e := range test.g {
|
|
// Add nodes that are not defined by an edge.
|
|
if g.Node(int64(u)) == nil {
|
|
g.AddNode(simple.Node(u))
|
|
}
|
|
for v := range e {
|
|
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
|
}
|
|
}
|
|
l := NewRandomWalkLaplacian(g, test.damp)
|
|
_, c := l.Dims()
|
|
for j := 0; j < c; j++ {
|
|
if got := mat.Sum(l.Matrix.(*mat.Dense).ColView(j)); !scalar.EqualWithinAbsOrRel(got, 0, tol, tol) {
|
|
t.Errorf("unexpected column sum for test %d, column %d: got:%v want:0", i, j, got)
|
|
}
|
|
}
|
|
l = NewRandomWalkLaplacian(sortedNodeGraph{g}, test.damp)
|
|
if !mat.EqualApprox(l, test.want, tol) {
|
|
t.Errorf("unexpected result for test %d:\ngot:\n% .2v\nwant:\n% .2v",
|
|
i, mat.Formatted(l), mat.Formatted(test.want))
|
|
}
|
|
}
|
|
}
|
|
|
|
type sortedNodeGraph struct {
|
|
graph.Graph
|
|
}
|
|
|
|
func (g sortedNodeGraph) Nodes() graph.Nodes {
|
|
n := graph.NodesOf(g.Graph.Nodes())
|
|
ordered.ByID(n)
|
|
return iterator.NewOrderedNodes(n)
|
|
}
|
|
|
|
const (
|
|
A = iota
|
|
B
|
|
C
|
|
D
|
|
E
|
|
F
|
|
G
|
|
H
|
|
I
|
|
J
|
|
K
|
|
L
|
|
M
|
|
N
|
|
O
|
|
P
|
|
Q
|
|
R
|
|
S
|
|
T
|
|
U
|
|
V
|
|
W
|
|
X
|
|
Y
|
|
Z
|
|
)
|
|
|
|
// set is an integer set.
|
|
type set map[int64]struct{}
|
|
|
|
func linksTo(i ...int64) set {
|
|
if len(i) == 0 {
|
|
return nil
|
|
}
|
|
s := make(set)
|
|
for _, v := range i {
|
|
s[v] = struct{}{}
|
|
}
|
|
return s
|
|
}
|