mirror of
https://github.com/gonum/gonum.git
synced 2025-11-03 11:21:14 +08:00
spectral: new package for spectral graph analysis
Initial commit to this is just a move of the Laplacian type from network.
This commit is contained in:
@@ -6,15 +6,12 @@ package network
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"gonum.org/v1/gonum/floats"
|
||||
"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"
|
||||
"gonum.org/v1/gonum/graph/spectral"
|
||||
)
|
||||
|
||||
var diffuseTests = []struct {
|
||||
@@ -160,7 +157,7 @@ func TestDiffuse(t *testing.T) {
|
||||
g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
|
||||
}
|
||||
}
|
||||
for j, lfn := range []func(g graph.Undirected) Laplacian{NewLaplacian, NewSymNormLaplacian} {
|
||||
for j, lfn := range []func(g graph.Undirected) spectral.Laplacian{spectral.NewLaplacian, spectral.NewSymNormLaplacian} {
|
||||
normalize := j == 1
|
||||
var wantTemp float64
|
||||
for _, v := range test.h {
|
||||
@@ -194,123 +191,6 @@ func TestDiffuse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
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)); !floats.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())
|
||||
sort.Sort(ordered.ByID(n))
|
||||
return iterator.NewOrderedNodes(n)
|
||||
}
|
||||
|
||||
var diffuseToEquilibriumTests = []struct {
|
||||
g []set
|
||||
builder builder
|
||||
@@ -478,7 +358,7 @@ func TestDiffuseToEquilibrium(t *testing.T) {
|
||||
for _, v := range test.h {
|
||||
wantTemp += v
|
||||
}
|
||||
got, ok := DiffuseToEquilibrium(nil, test.h, NewRandomWalkLaplacian(g, test.damp), test.tol*test.tol, test.iter)
|
||||
got, ok := DiffuseToEquilibrium(nil, test.h, spectral.NewRandomWalkLaplacian(g, test.damp), test.tol*test.tol, test.iter)
|
||||
if ok != test.wantOK {
|
||||
t.Errorf("unexpected success value for test %d: got:%t want:%t", i, ok, test.wantOK)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user