graph/graphs/gen: make NavigableSmallWorld use dst's edge and node constructors

This changes the behaviour of the function to create the small world graph as
a disconnected subgraph in g rather than applying the algorithm to potentially
already existing nodes in g.
This commit is contained in:
Dan Kortschak
2018-10-21 18:13:39 +10:30
committed by Dan Kortschak
parent 75ba0793c2
commit f021a2e7da
2 changed files with 36 additions and 32 deletions

View File

@@ -12,16 +12,15 @@ import (
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
"gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph"
"gonum.org/v1/gonum/graph/simple"
"gonum.org/v1/gonum/stat/sampleuv" "gonum.org/v1/gonum/stat/sampleuv"
) )
// NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity // NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity
// and random long-range connectivity in the destination, dst. The dims parameters specifies // and random long-range connectivity as a subgraph in the destination, dst.
// the length of each of the N dimensions, p defines the Manhattan distance between local // The dims parameters specifies the length of each of the N dimensions, p defines the
// nodes, and q defines the number of out-going long-range connections from each node. Long- // Manhattan distance between local nodes, and q defines the number of out-going long-range
// range connections are made with a probability proportional to |d(u,v)|^-r where d is the // connections from each node. Long-range connections are made with a probability
// Manhattan distance between non-local nodes. // proportional to |d(u,v)|^-r where d is the Manhattan distance between non-local nodes.
// //
// The algorithm is essentially as described on p4 of http://www.cs.cornell.edu/home/kleinber/swn.pdf. // The algorithm is essentially as described on p4 of http://www.cs.cornell.edu/home/kleinber/swn.pdf.
func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src rand.Source) (err error) { func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src rand.Source) (err error) {
@@ -39,10 +38,11 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src
for _, d := range dims { for _, d := range dims {
n *= d n *= d
} }
for i := 0; i < n; i++ { nodes := make([]graph.Node, n)
if dst.Node(int64(i)) == nil { for i := range nodes {
dst.AddNode(simple.Node(i)) u := dst.NewNode()
} dst.AddNode(u)
nodes[i] = u
} }
hasEdge := dst.HasEdgeBetween hasEdge := dst.HasEdgeBetween
@@ -56,26 +56,25 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src
locality[i] = p*2 + 1 locality[i] = p*2 + 1
} }
iterateOver(dims, func(u []int) { iterateOver(dims, func(u []int) {
uid := idFrom(u, dims) un := nodes[idxFrom(u, dims)]
iterateOver(locality, func(delta []int) { iterateOver(locality, func(delta []int) {
d := manhattanDelta(u, delta, dims, -p) d := manhattanDelta(u, delta, dims, -p)
if d == 0 || d > p { if d == 0 || d > p {
return return
} }
vid := idFromDelta(u, delta, dims, -p) vn := nodes[idxFromDelta(u, delta, dims, -p)]
e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid)} if un.ID() > vn.ID() {
if uid > vid { un, vn = vn, un
e.F, e.T = e.T, e.F
} }
if !hasEdge(e.From().ID(), e.To().ID()) { if !hasEdge(un.ID(), vn.ID()) {
dst.SetEdge(e) dst.SetEdge(dst.NewEdge(un, vn))
} }
if !isDirected { if !isDirected {
return return
} }
e.F, e.T = e.T, e.F un, vn = vn, un
if !hasEdge(e.From().ID(), e.To().ID()) { if !hasEdge(un.ID(), vn.ID()) {
dst.SetEdge(e) dst.SetEdge(dst.NewEdge(un, vn))
} }
}) })
}) })
@@ -92,26 +91,26 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src
w := make([]float64, n) w := make([]float64, n)
ws := sampleuv.NewWeighted(w, src) ws := sampleuv.NewWeighted(w, src)
iterateOver(dims, func(u []int) { iterateOver(dims, func(u []int) {
uid := idFrom(u, dims) un := nodes[idxFrom(u, dims)]
iterateOver(dims, func(v []int) { iterateOver(dims, func(v []int) {
d := manhattanBetween(u, v) d := manhattanBetween(u, v)
if d <= p { if d <= p {
return return
} }
w[idFrom(v, dims)] = math.Pow(float64(d), -r) w[idxFrom(v, dims)] = math.Pow(float64(d), -r)
}) })
ws.ReweightAll(w) ws.ReweightAll(w)
for i := 0; i < q; i++ { for i := 0; i < q; i++ {
vid, ok := ws.Take() vidx, ok := ws.Take()
if !ok { if !ok {
panic("depleted distribution") panic("depleted distribution")
} }
e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid)} vn := nodes[vidx]
if !isDirected && uid > vid { if !isDirected && un.ID() > vn.ID() {
e.F, e.T = e.T, e.F un, vn = vn, un
} }
if !hasEdge(e.From().ID(), e.To().ID()) { if !hasEdge(un.ID(), vn.ID()) {
dst.SetEdge(e) dst.SetEdge(dst.NewEdge(un, vn))
} }
} }
for i := range w { for i := range w {
@@ -173,8 +172,8 @@ func manhattanDelta(a, delta, dims []int, translate int) int {
return d return d
} }
// idFrom returns a node id for the slice n over the given dimensions. // idxFrom returns a node index for the slice n over the given dimensions.
func idFrom(n, dims []int) int { func idxFrom(n, dims []int) int {
s := 1 s := 1
var id int var id int
for d, m := range dims { for d, m := range dims {
@@ -188,9 +187,9 @@ func idFrom(n, dims []int) int {
return id return id
} }
// idFromDelta returns a node id for the slice base plus the delta over the given // idxFromDelta returns a node index for the slice base plus the delta over the given
// dimensions and applying the translation. // dimensions and applying the translation.
func idFromDelta(base, delta, dims []int, translate int) int { func idxFromDelta(base, delta, dims []int, translate int) int {
s := 1 s := 1
var id int var id int
for d, m := range dims { for d, m := range dims {

View File

@@ -22,6 +22,8 @@ func TestNavigableSmallWorldUndirected(t *testing.T) {
for r := 0.5; r < 10; r++ { for r := 0.5; r < 10; r++ {
for _, dims := range smallWorldDimensionParameters { for _, dims := range smallWorldDimensionParameters {
g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()}
orig := g.NewNode()
g.AddNode(orig)
err := NavigableSmallWorld(g, dims, p, q, r, nil) err := NavigableSmallWorld(g, dims, p, q, r, nil)
n := 1 n := 1
for _, d := range dims { for _, d := range dims {
@@ -30,6 +32,9 @@ func TestNavigableSmallWorldUndirected(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err) t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err)
} }
if g.From(orig.ID()).Len() != 0 {
t.Errorf("edge added from already existing node: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r)
}
if g.addBackwards { if g.addBackwards {
t.Errorf("edge added with From.ID > To.ID: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) t.Errorf("edge added with From.ID > To.ID: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r)
} }