diff --git a/graphs/gen/batagelj_brandes.go b/graphs/gen/batagelj_brandes.go new file mode 100644 index 00000000..cc23ceea --- /dev/null +++ b/graphs/gen/batagelj_brandes.go @@ -0,0 +1,356 @@ +// Copyright ©2015 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. + +// The functions in this file are random graph generators from the paper +// by Batagelj and Brandes http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf + +package gen + +import ( + "fmt" + "math" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +// Gnp constructs a graph in the destination, dst, of order n. Edges between nodes +// are formed with the probability, p. If src is not nil it is used as the random +// source, otherwise rand.Float64 is used. The graph is constructed in O(n+m) time +// where m is the number of edges added. +func Gnp(dst GraphBuilder, n int, p float64, src *rand.Rand) error { + if p == 0 { + return nil + } + if p < 0 || p > 1 { + return fmt.Errorf("gen: bad probability: p=%v", p) + } + var r func() float64 + if src == nil { + r = rand.Float64 + } else { + r = src.Float64 + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + lp := math.Log(1 - p) + + // Add forward edges for all graphs. + for v, w := 1, -1; v < n; { + w += 1 + int(math.Log(1-r())/lp) + for w >= v && v < n { + w -= v + v++ + } + if v < n { + dst.SetEdge(simple.Edge{F: simple.Node(w), T: simple.Node(v), W: 1}) + } + } + + // Add backward edges for directed graphs. + if _, ok := dst.(graph.Directed); !ok { + return nil + } + for v, w := 1, -1; v < n; { + w += 1 + int(math.Log(1-r())/lp) + for w >= v && v < n { + w -= v + v++ + } + if v < n { + dst.SetEdge(simple.Edge{F: simple.Node(v), T: simple.Node(w), W: 1}) + } + } + + return nil +} + +// edgeNodesFor returns the pair of nodes for the ith edge in a simple +// undirected graph. The pair is returned such that w.ID < v.ID. +func edgeNodesFor(i int) (v, w simple.Node) { + // This is an algebraic simplification of the expressions described + // on p3 of http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf + v = simple.Node(0.5 + math.Sqrt(float64(1+8*i))/2) + w = simple.Node(i) - v*(v-1)/2 + return v, w +} + +// Gnm constructs a graph in the destination, dst, of order n and size m. If src is not +// nil it is used as the random source, otherwise rand.Float64 is used. The graph is +// constructed in O(m) expected time for m ≤ (n choose 2)/2. +func Gnm(dst GraphBuilder, n, m int, src *rand.Rand) error { + if m == 0 { + return nil + } + + hasEdge := dst.HasEdge + d, isDirected := dst.(graph.Directed) + if isDirected { + m /= 2 + hasEdge = d.HasEdgeFromTo + } + + nChoose2 := (n - 1) * n / 2 + if m < 0 || m > nChoose2 { + return fmt.Errorf("gen: bad size: m=%d", m) + } + + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + // Add forward edges for all graphs. + for i := 0; i < m; i++ { + for { + v, w := edgeNodesFor(rnd(nChoose2)) + e := simple.Edge{F: w, T: v, W: 1} + if !hasEdge(e.F, e.T) { + dst.SetEdge(e) + break + } + } + } + + // Add backward edges for directed graphs. + if !isDirected { + return nil + } + for i := 0; i < m; i++ { + for { + v, w := edgeNodesFor(rnd(nChoose2)) + e := simple.Edge{F: v, T: w, W: 1} + if !hasEdge(e.F, e.T) { + dst.SetEdge(e) + break + } + } + } + + return nil +} + +// SmallWorldsBB constructs a small worlds graph of order n in the destination, dst. +// Node degree is specified by d and edge replacement by the probability, p. +// If src is not nil it is used as the random source, otherwise rand.Float64 is used. +// The graph is constructed in O(nd) time. +// +// The algorithm used is described in http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf +func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src *rand.Rand) error { + if d < 1 || d > (n-1)/2 { + return fmt.Errorf("gen: bad degree: d=%d", d) + } + if p == 0 { + return nil + } + if p < 0 || p >= 1 { + return fmt.Errorf("gen: bad replacement: p=%v", p) + } + var ( + rnd func() float64 + rndN func(int) int + ) + if src == nil { + rnd = rand.Float64 + rndN = rand.Intn + } else { + rnd = src.Float64 + rndN = src.Intn + } + + hasEdge := dst.HasEdge + dg, isDirected := dst.(graph.Directed) + if isDirected { + hasEdge = dg.HasEdgeFromTo + } + + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + nChoose2 := (n - 1) * n / 2 + + lp := math.Log(1 - p) + + // Add forward edges for all graphs. + k := int(math.Log(1-rnd()) / lp) + m := 0 + replace := make(map[int]int) + for v := 0; v < n; v++ { + for i := 1; i <= d; i++ { + if k > 0 { + j := v*(v-1)/2 + (v+i)%n + ej := simple.Edge{W: 1} + ej.T, ej.F = edgeNodesFor(j) + if !hasEdge(ej.From(), ej.To()) { + dst.SetEdge(ej) + } + k-- + m++ + em := simple.Edge{W: 1} + em.T, em.F = edgeNodesFor(m) + if !hasEdge(em.From(), em.To()) { + replace[j] = m + } else { + replace[j] = replace[m] + } + } else { + k = int(math.Log(1-rnd()) / lp) + } + } + } + for i := m + 1; i <= n*d && i < nChoose2; i++ { + r := rndN(nChoose2-i) + i + er := simple.Edge{W: 1} + er.T, er.F = edgeNodesFor(r) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } else { + er.T, er.F = edgeNodesFor(replace[r]) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } + } + ei := simple.Edge{W: 1} + ei.T, ei.F = edgeNodesFor(i) + if !hasEdge(ei.From(), ei.To()) { + replace[r] = i + } else { + replace[r] = replace[i] + } + } + + // Add backward edges for directed graphs. + if !isDirected { + return nil + } + k = int(math.Log(1-rnd()) / lp) + m = 0 + replace = make(map[int]int) + for v := 0; v < n; v++ { + for i := 1; i <= d; i++ { + if k > 0 { + j := v*(v-1)/2 + (v+i)%n + ej := simple.Edge{W: 1} + ej.F, ej.T = edgeNodesFor(j) + if !hasEdge(ej.From(), ej.To()) { + dst.SetEdge(ej) + } + k-- + m++ + if !hasEdge(edgeNodesFor(m)) { + replace[j] = m + } else { + replace[j] = replace[m] + } + } else { + k = int(math.Log(1-rnd()) / lp) + } + } + } + for i := m + 1; i <= n*d && i < nChoose2; i++ { + r := rndN(nChoose2-i) + i + er := simple.Edge{W: 1} + er.F, er.T = edgeNodesFor(r) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } else { + er.F, er.T = edgeNodesFor(replace[r]) + if !hasEdge(er.From(), er.To()) { + dst.SetEdge(er) + } + } + if !hasEdge(edgeNodesFor(i)) { + replace[r] = i + } else { + replace[r] = replace[i] + } + } + + return nil +} + +/* +// Multigraph generators. + +type EdgeAdder interface { + AddEdge(graph.Edge) +} + +func PreferentialAttachment(dst EdgeAdder, n, d int, src *rand.Rand) { + if d < 1 { + panic("gen: bad d") + } + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + m := make([]simple.Node, 2*n*d) + for v := 0; v < n; v++ { + for i := 0; i < d; i++ { + m[2*(v*d+i)] = simple.Node(v) + m[2*(v*d+i)+1] = simple.Node(m[rnd(2*v*d+i+1)]) + } + } + for i := 0; i < n*d; i++ { + dst.AddEdge(simple.Edge{F: m[2*i], T: m[2*i+1], W: 1}) + } +} + +func BipartitePreferentialAttachment(dst EdgeAdder, n, d int, src *rand.Rand) { + if d < 1 { + panic("gen: bad d") + } + var rnd func(int) int + if src == nil { + rnd = rand.Intn + } else { + rnd = src.Intn + } + + m1 := make([]simple.Node, 2*n*d) + m2 := make([]simple.Node, 2*n*d) + for v := 0; v < n; v++ { + for i := 0; i < d; i++ { + m1[2*(v*d+i)] = simple.Node(v) + m2[2*(v*d+i)] = simple.Node(n + v) + + if r := rnd(2*v*d + i + 1); r&0x1 == 0 { + m1[2*(v*d+i)+1] = m2[r] + } else { + m1[2*(v*d+i)+1] = m1[r] + } + + if r := rnd(2*v*d + i + 1); r&0x1 == 0 { + m2[2*(v*d+i)+1] = m1[r] + } else { + m2[2*(v*d+i)+1] = m2[r] + } + } + } + for i := 0; i < n*d; i++ { + dst.AddEdge(simple.Edge{F: m1[2*i], T: m1[2*i+1], W: 1}) + dst.AddEdge(simple.Edge{F: m2[2*i], T: m2[2*i+1], W: 1}) + } +} +*/ diff --git a/graphs/gen/batagelj_brandes_test.go b/graphs/gen/batagelj_brandes_test.go new file mode 100644 index 00000000..ab977576 --- /dev/null +++ b/graphs/gen/batagelj_brandes_test.go @@ -0,0 +1,175 @@ +// Copyright ©2015 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 gen + +import ( + "math" + "testing" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" +) + +type gnUndirected struct { + graph.UndirectedBuilder + addBackwards bool + addSelfLoop bool + addMultipleEdge bool +} + +func (g *gnUndirected) SetEdge(e graph.Edge) { + switch { + case e.From().ID() == e.To().ID(): + g.addSelfLoop = true + return + case e.From().ID() > e.To().ID(): + g.addBackwards = true + case g.UndirectedBuilder.HasEdge(e.From(), e.To()): + g.addMultipleEdge = true + } + + g.UndirectedBuilder.SetEdge(e) +} + +type gnDirected struct { + graph.DirectedBuilder + addSelfLoop bool + addMultipleEdge bool +} + +func (g *gnDirected) SetEdge(e graph.Edge) { + switch { + case e.From().ID() == e.To().ID(): + g.addSelfLoop = true + return + case g.DirectedBuilder.HasEdgeFromTo(e.From(), e.To()): + g.addMultipleEdge = true + } + + g.DirectedBuilder.SetEdge(e) +} + +func TestGnpUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := Gnp(g, n, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, p=%v", n, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, p=%v", n, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) + } + } + } +} + +func TestGnpDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := Gnp(g, n, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, p=%v", n, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, p=%v", n, p) + } + } + } +} + +func TestGnmUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + nChoose2 := (n - 1) * n / 2 + for m := 0; m <= nChoose2; m++ { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := Gnm(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} + +func TestGnmDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + nChoose2 := (n - 1) * n / 2 + for m := 0; m <= nChoose2*2; m++ { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := Gnm(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} + +func TestSmallWorldsBBUndirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for d := 1; d <= (n-1)/2; d++ { + for p := 0.; p < 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := SmallWorldsBB(g, n, d, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, d=%d, p=%v", n, d, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) + } + } + } + } +} + +func TestSmallWorldsBBDirected(t *testing.T) { + for n := 2; n <= 20; n++ { + for d := 1; d <= (n-1)/2; d++ { + for p := 0.; p < 1; p += 0.1 { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := SmallWorldsBB(g, n, d, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, d=%d, p=%v", n, d, p) + } + } + } + } +} diff --git a/graphs/gen/gen.go b/graphs/gen/gen.go new file mode 100644 index 00000000..4c9a5e74 --- /dev/null +++ b/graphs/gen/gen.go @@ -0,0 +1,22 @@ +// Copyright ©2015 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 gen provides random graph generation functions. +package gen + +import "github.com/gonum/graph" + +// GraphBuilder is a graph that can have nodes and edges added. +type GraphBuilder interface { + Has(graph.Node) bool + HasEdge(x, y graph.Node) bool + graph.Builder +} + +func abs(a int) int { + if a < 0 { + return -a + } + return a +} diff --git a/graphs/gen/holme_kim.go b/graphs/gen/holme_kim.go new file mode 100644 index 00000000..1623dc03 --- /dev/null +++ b/graphs/gen/holme_kim.go @@ -0,0 +1,160 @@ +// Copyright ©2015 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 gen + +import ( + "errors" + "fmt" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" + "github.com/gonum/stat/sample" +) + +// TunableClusteringScaleFree constructs a graph in the destination, dst, of order n. +// The graph is constructed successively starting from an m order graph with one node +// having degree m-1. At each iteration of graph addition, one node is added with m +// additional edges joining existing nodes with probability proportional to the nodes' +// degrees. The edges are formed as a triad with probability, p. +// If src is not nil it is used as the random source, otherwise rand.Float64 and +// rand.Intn are used. +// +// The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452. +func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64, src *rand.Rand) error { + if p < 0 || p > 1 { + return fmt.Errorf("gen: bad probability: p=%v", p) + } + if n <= m { + return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) + } + + var ( + rnd func() float64 + rndN func(int) int + ) + if src == nil { + rnd = rand.Float64 + rndN = rand.Intn + } else { + rnd = src.Float64 + rndN = src.Intn + } + + // Initial condition. + wt := make([]float64, n) + for u := 0; u < m; u++ { + if !dst.Has(simple.Node(u)) { + dst.AddNode(simple.Node(u)) + } + // We need to give equal probability for + // adding the first generation of edges. + wt[u] = 1 + } + ws := sample.NewWeighted(wt, src) + for i := range wt { + // These weights will organically grow + // after the first growth iteration. + wt[i] = 0 + } + + // Growth. + for v := m; v < n; v++ { + var u int + pa: + for i := 0; i < m; i++ { + // Triad formation. + if i != 0 && rnd() < p { + for _, w := range permute(dst.From(simple.Node(u)), rndN) { + wid := w.ID() + if wid == v || dst.HasEdge(w, simple.Node(v)) { + continue + } + dst.SetEdge(simple.Edge{F: w, T: simple.Node(v), W: 1}) + wt[wid]++ + wt[v]++ + continue pa + } + } + + // Preferential attachment. + for { + var ok bool + u, ok = ws.Take() + if !ok { + return errors.New("gen: depleted distribution") + } + if u == v || dst.HasEdge(simple.Node(u), simple.Node(v)) { + continue + } + dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) + wt[u]++ + wt[v]++ + break + } + } + + ws.ReweightAll(wt) + } + + return nil +} + +func permute(n []graph.Node, rnd func(int) int) []graph.Node { + for i := range n[:len(n)-1] { + j := rnd(len(n)-i) + i + n[i], n[j] = n[j], n[i] + } + return n +} + +// PreferentialAttachment constructs a graph in the destination, dst, of order n. +// The graph is constructed successively starting from an m order graph with one +// node having degree m-1. At each iteration of graph addition, one node is added +// with m additional edges joining existing nodes with probability proportional +// to the nodes' degrees. If src is not nil it is used as the random source, +// otherwise rand.Float64 is used. +// +// The algorithm is essentially as described in http://arxiv.org/abs/cond-mat/0110452 +// after 10.1126/science.286.5439.509. +func PreferentialAttachment(dst graph.UndirectedBuilder, n, m int, src *rand.Rand) error { + if n <= m { + return fmt.Errorf("gen: n <= m: n=%v m=%d", n, m) + } + + // Initial condition. + wt := make([]float64, n) + for u := 0; u < m; u++ { + if !dst.Has(simple.Node(u)) { + dst.AddNode(simple.Node(u)) + } + // We need to give equal probability for + // adding the first generation of edges. + wt[u] = 1 + } + ws := sample.NewWeighted(wt, src) + for i := range wt { + // These weights will organically grow + // after the first growth iteration. + wt[i] = 0 + } + + // Growth. + for v := m; v < n; v++ { + for i := 0; i < m; i++ { + // Preferential attachment. + u, ok := ws.Take() + if !ok { + return errors.New("gen: depleted distribution") + } + dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v), W: 1}) + wt[u]++ + wt[v]++ + } + ws.ReweightAll(wt) + } + + return nil +} diff --git a/graphs/gen/holme_kim_test.go b/graphs/gen/holme_kim_test.go new file mode 100644 index 00000000..567ddd52 --- /dev/null +++ b/graphs/gen/holme_kim_test.go @@ -0,0 +1,56 @@ +// Copyright ©2015 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 gen + +import ( + "math" + "testing" + + "github.com/gonum/graph/simple" +) + +func TestTunableClusteringScaleFree(t *testing.T) { + for n := 2; n <= 20; n++ { + for m := 0; m < n; m++ { + for p := 0.; p <= 1; p += 0.1 { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := TunableClusteringScaleFree(g, n, m, p, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d, p=%v", n, m, p) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d, p=%v", n, m, p) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d, p=%v", n, m, p) + } + } + } + } +} + +func TestPreferentialAttachment(t *testing.T) { + for n := 2; n <= 20; n++ { + for m := 0; m < n; m++ { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := PreferentialAttachment(g, n, m, nil) + if err != nil { + t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) + } + if g.addBackwards { + t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: n=%d, m=%d", n, m) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: n=%d, m=%d", n, m) + } + } + } +} diff --git a/graphs/gen/small_world.go b/graphs/gen/small_world.go new file mode 100644 index 00000000..126523d8 --- /dev/null +++ b/graphs/gen/small_world.go @@ -0,0 +1,204 @@ +// Copyright ©2015 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 gen + +import ( + "errors" + "fmt" + "math" + "math/rand" + + "github.com/gonum/graph" + "github.com/gonum/graph/simple" + "github.com/gonum/stat/sample" +) + +// NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity +// and random long-range connectivity in the destination, dst. The dims parameters specifies +// the length of each of the N dimensions, p defines the Manhattan distance between local +// nodes, and q defines the number of out-going long-range connections from each node. Long- +// range connections are made with a probability 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. +func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src *rand.Rand) (err error) { + if p < 1 { + return fmt.Errorf("gen: bad local distance: p=%v", p) + } + if q < 0 { + return fmt.Errorf("gen: bad distant link count: q=%v", q) + } + if r < 0 { + return fmt.Errorf("gen: bad decay constant: r=%v", r) + } + + n := 1 + for _, d := range dims { + n *= d + } + for i := 0; i < n; i++ { + if !dst.Has(simple.Node(i)) { + dst.AddNode(simple.Node(i)) + } + } + + hasEdge := dst.HasEdge + d, isDirected := dst.(graph.Directed) + if isDirected { + hasEdge = d.HasEdgeFromTo + } + + locality := make([]int, len(dims)) + for i := range locality { + locality[i] = p*2 + 1 + } + iterateOver(dims, func(u []int) { + uid := idFrom(u, dims) + iterateOver(locality, func(delta []int) { + d := manhattanDelta(u, delta, dims, -p) + if d == 0 || d > p { + return + } + vid := idFromDelta(u, delta, dims, -p) + e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} + if uid > vid { + e.F, e.T = e.T, e.F + } + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + if !isDirected { + return + } + e.F, e.T = e.T, e.F + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + }) + }) + + defer func() { + r := recover() + if r != nil { + if r != "depleted distribution" { + panic(r) + } + err = errors.New("depleted distribution") + } + }() + w := make([]float64, n) + ws := sample.NewWeighted(w, src) + iterateOver(dims, func(u []int) { + uid := idFrom(u, dims) + iterateOver(dims, func(v []int) { + d := manhattanBetween(u, v) + if d <= p { + return + } + w[idFrom(v, dims)] = math.Pow(float64(d), -r) + }) + ws.ReweightAll(w) + for i := 0; i < q; i++ { + vid, ok := ws.Take() + if !ok { + panic("depleted distribution") + } + e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid), W: 1} + if !isDirected && uid > vid { + e.F, e.T = e.T, e.F + } + if !hasEdge(e.From(), e.To()) { + dst.SetEdge(e) + } + } + for i := range w { + w[i] = 0 + } + }) + + return nil +} + +// iterateOver performs an iteration over all dimensions of dims, calling fn +// for each state. The elements of state must not be mutated by fn. +func iterateOver(dims []int, fn func(state []int)) { + iterator(0, dims, make([]int, len(dims)), fn) +} + +func iterator(d int, dims, state []int, fn func(state []int)) { + if d >= len(dims) { + fn(state) + return + } + for i := 0; i < dims[d]; i++ { + state[d] = i + iterator(d+1, dims, state, fn) + } +} + +// manhattanBetween returns the Manhattan distance between a and b. +func manhattanBetween(a, b []int) int { + if len(a) != len(b) { + panic("gen: unexpected dimension") + } + var d int + for i, v := range a { + d += abs(v - b[i]) + } + return d +} + +// manhattanDelta returns the Manhattan norm of delta+translate. If a +// translated by delta+translate is out of the range given by dims, +// zero is returned. +func manhattanDelta(a, delta, dims []int, translate int) int { + if len(a) != len(dims) { + panic("gen: unexpected dimension") + } + if len(delta) != len(dims) { + panic("gen: unexpected dimension") + } + var d int + for i, v := range delta { + v += translate + t := a[i] + v + if t < 0 || t >= dims[i] { + return 0 + } + d += abs(v) + } + return d +} + +// idFrom returns a node id for the slice n over the given dimensions. +func idFrom(n, dims []int) int { + s := 1 + var id int + for d, m := range dims { + p := n[d] + if p < 0 || p >= m { + panic("gen: element out of range") + } + id += p * s + s *= m + } + return id +} + +// idFromDelta returns a node id for the slice base plus the delta over the given +// dimensions and applying the translation. +func idFromDelta(base, delta, dims []int, translate int) int { + s := 1 + var id int + for d, m := range dims { + n := base[d] + delta[d] + translate + if n < 0 || n >= m { + panic("gen: element out of range") + } + id += n * s + s *= m + } + return id +} diff --git a/graphs/gen/small_world_test.go b/graphs/gen/small_world_test.go new file mode 100644 index 00000000..4dfcd4d7 --- /dev/null +++ b/graphs/gen/small_world_test.go @@ -0,0 +1,73 @@ +// Copyright ©2015 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 gen + +import ( + "math" + "testing" + + "github.com/gonum/graph/simple" +) + +var smallWorldDimensionParameters = [][]int{ + {50}, + {10, 10}, + {6, 5, 4}, +} + +func TestNavigableSmallWorldUndirected(t *testing.T) { + for p := 1; p < 5; p++ { + for q := 0; q < 10; q++ { + for r := 0.5; r < 10; r++ { + for _, dims := range smallWorldDimensionParameters { + g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph(0, math.Inf(1))} + err := NavigableSmallWorld(g, dims, p, q, r, nil) + n := 1 + for _, d := range dims { + n *= d + } + if err != nil { + t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err) + } + 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) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + } + } + } + } +} + +func TestNavigableSmallWorldDirected(t *testing.T) { + for p := 1; p < 5; p++ { + for q := 0; q < 10; q++ { + for r := 0.5; r < 10; r++ { + for _, dims := range smallWorldDimensionParameters { + g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph(0, math.Inf(1))} + err := NavigableSmallWorld(g, dims, p, q, r, nil) + n := 1 + for _, d := range dims { + n *= d + } + if err != nil { + t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v, r=%v: %v", dims, n, p, q, r, err) + } + if g.addSelfLoop { + t.Errorf("unexpected self edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + if g.addMultipleEdge { + t.Errorf("unexpected multiple edge: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } + } + } + } + } +}