mirror of
https://github.com/gonum/gonum.git
synced 2025-10-05 15:16:59 +08:00
204 lines
4.6 KiB
Go
204 lines
4.6 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/v2"
|
|
|
|
"gonum.org/v1/gonum/graph"
|
|
"gonum.org/v1/gonum/stat/sampleuv"
|
|
)
|
|
|
|
// NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity
|
|
// and random long-range connectivity as a subgraph 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.Source) (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
|
|
}
|
|
nodes := make([]graph.Node, n)
|
|
for i := range nodes {
|
|
u := dst.NewNode()
|
|
dst.AddNode(u)
|
|
nodes[i] = u
|
|
}
|
|
|
|
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) {
|
|
un := nodes[idxFrom(u, dims)]
|
|
iterateOver(locality, func(delta []int) {
|
|
d := manhattanDelta(u, delta, dims, -p)
|
|
if d == 0 || d > p {
|
|
return
|
|
}
|
|
vn := nodes[idxFromDelta(u, delta, dims, -p)]
|
|
if un.ID() > vn.ID() {
|
|
un, vn = vn, un
|
|
}
|
|
if !hasEdge(un.ID(), vn.ID()) {
|
|
dst.SetEdge(dst.NewEdge(un, vn))
|
|
}
|
|
if !isDirected {
|
|
return
|
|
}
|
|
un, vn = vn, un
|
|
if !hasEdge(un.ID(), vn.ID()) {
|
|
dst.SetEdge(dst.NewEdge(un, vn))
|
|
}
|
|
})
|
|
})
|
|
|
|
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) {
|
|
un := nodes[idxFrom(u, dims)]
|
|
iterateOver(dims, func(v []int) {
|
|
d := manhattanBetween(u, v)
|
|
if d <= p {
|
|
return
|
|
}
|
|
w[idxFrom(v, dims)] = math.Pow(float64(d), -r)
|
|
})
|
|
ws.ReweightAll(w)
|
|
for i := 0; i < q; i++ {
|
|
vidx, ok := ws.Take()
|
|
if !ok {
|
|
panic("depleted distribution")
|
|
}
|
|
vn := nodes[vidx]
|
|
if !isDirected && un.ID() > vn.ID() {
|
|
un, vn = vn, un
|
|
}
|
|
if !hasEdge(un.ID(), vn.ID()) {
|
|
dst.SetEdge(dst.NewEdge(un, vn))
|
|
}
|
|
}
|
|
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
|
|
}
|
|
|
|
// idxFrom returns a node index for the slice n over the given dimensions.
|
|
func idxFrom(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
|
|
}
|
|
|
|
// idxFromDelta returns a node index for the slice base plus the delta over the given
|
|
// dimensions and applying the translation.
|
|
func idxFromDelta(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
|
|
}
|