mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00
205 lines
4.7 KiB
Go
205 lines
4.7 KiB
Go
// 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
|
|
}
|