| // 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" |
| |
| "gonum.org/v1/gonum/graph" |
| "gonum.org/v1/gonum/graph/simple" |
| "gonum.org/v1/gonum/stat/sampleuv" |
| ) |
| |
| // 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.HasEdgeBetween |
| 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 := sampleuv.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 |
| } |