gonum.org/v1/gonum@v0.14.0/graph/graphs/gen/small_world.go (about) 1 // Copyright ©2015 The Gonum Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package gen 6 7 import ( 8 "errors" 9 "fmt" 10 "math" 11 12 "golang.org/x/exp/rand" 13 14 "gonum.org/v1/gonum/graph" 15 "gonum.org/v1/gonum/stat/sampleuv" 16 ) 17 18 // NavigableSmallWorld constructs an N-dimensional grid with guaranteed local connectivity 19 // and random long-range connectivity as a subgraph in the destination, dst. 20 // The dims parameters specifies the length of each of the N dimensions, p defines the 21 // Manhattan distance between local nodes, and q defines the number of out-going long-range 22 // connections from each node. Long-range connections are made with a probability 23 // proportional to |d(u,v)|^-r where d is the Manhattan distance between non-local nodes. 24 // 25 // The algorithm is essentially as described on p4 of http://www.cs.cornell.edu/home/kleinber/swn.pdf. 26 func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src rand.Source) (err error) { 27 if p < 1 { 28 return fmt.Errorf("gen: bad local distance: p=%v", p) 29 } 30 if q < 0 { 31 return fmt.Errorf("gen: bad distant link count: q=%v", q) 32 } 33 if r < 0 { 34 return fmt.Errorf("gen: bad decay constant: r=%v", r) 35 } 36 37 n := 1 38 for _, d := range dims { 39 n *= d 40 } 41 nodes := make([]graph.Node, n) 42 for i := range nodes { 43 u := dst.NewNode() 44 dst.AddNode(u) 45 nodes[i] = u 46 } 47 48 hasEdge := dst.HasEdgeBetween 49 d, isDirected := dst.(graph.Directed) 50 if isDirected { 51 hasEdge = d.HasEdgeFromTo 52 } 53 54 locality := make([]int, len(dims)) 55 for i := range locality { 56 locality[i] = p*2 + 1 57 } 58 iterateOver(dims, func(u []int) { 59 un := nodes[idxFrom(u, dims)] 60 iterateOver(locality, func(delta []int) { 61 d := manhattanDelta(u, delta, dims, -p) 62 if d == 0 || d > p { 63 return 64 } 65 vn := nodes[idxFromDelta(u, delta, dims, -p)] 66 if un.ID() > vn.ID() { 67 un, vn = vn, un 68 } 69 if !hasEdge(un.ID(), vn.ID()) { 70 dst.SetEdge(dst.NewEdge(un, vn)) 71 } 72 if !isDirected { 73 return 74 } 75 un, vn = vn, un 76 if !hasEdge(un.ID(), vn.ID()) { 77 dst.SetEdge(dst.NewEdge(un, vn)) 78 } 79 }) 80 }) 81 82 defer func() { 83 r := recover() 84 if r != nil { 85 if r != "depleted distribution" { 86 panic(r) 87 } 88 err = errors.New("depleted distribution") 89 } 90 }() 91 w := make([]float64, n) 92 ws := sampleuv.NewWeighted(w, src) 93 iterateOver(dims, func(u []int) { 94 un := nodes[idxFrom(u, dims)] 95 iterateOver(dims, func(v []int) { 96 d := manhattanBetween(u, v) 97 if d <= p { 98 return 99 } 100 w[idxFrom(v, dims)] = math.Pow(float64(d), -r) 101 }) 102 ws.ReweightAll(w) 103 for i := 0; i < q; i++ { 104 vidx, ok := ws.Take() 105 if !ok { 106 panic("depleted distribution") 107 } 108 vn := nodes[vidx] 109 if !isDirected && un.ID() > vn.ID() { 110 un, vn = vn, un 111 } 112 if !hasEdge(un.ID(), vn.ID()) { 113 dst.SetEdge(dst.NewEdge(un, vn)) 114 } 115 } 116 for i := range w { 117 w[i] = 0 118 } 119 }) 120 121 return nil 122 } 123 124 // iterateOver performs an iteration over all dimensions of dims, calling fn 125 // for each state. The elements of state must not be mutated by fn. 126 func iterateOver(dims []int, fn func(state []int)) { 127 iterator(0, dims, make([]int, len(dims)), fn) 128 } 129 130 func iterator(d int, dims, state []int, fn func(state []int)) { 131 if d >= len(dims) { 132 fn(state) 133 return 134 } 135 for i := 0; i < dims[d]; i++ { 136 state[d] = i 137 iterator(d+1, dims, state, fn) 138 } 139 } 140 141 // manhattanBetween returns the Manhattan distance between a and b. 142 func manhattanBetween(a, b []int) int { 143 if len(a) != len(b) { 144 panic("gen: unexpected dimension") 145 } 146 var d int 147 for i, v := range a { 148 d += abs(v - b[i]) 149 } 150 return d 151 } 152 153 // manhattanDelta returns the Manhattan norm of delta+translate. If a 154 // translated by delta+translate is out of the range given by dims, 155 // zero is returned. 156 func manhattanDelta(a, delta, dims []int, translate int) int { 157 if len(a) != len(dims) { 158 panic("gen: unexpected dimension") 159 } 160 if len(delta) != len(dims) { 161 panic("gen: unexpected dimension") 162 } 163 var d int 164 for i, v := range delta { 165 v += translate 166 t := a[i] + v 167 if t < 0 || t >= dims[i] { 168 return 0 169 } 170 d += abs(v) 171 } 172 return d 173 } 174 175 // idxFrom returns a node index for the slice n over the given dimensions. 176 func idxFrom(n, dims []int) int { 177 s := 1 178 var id int 179 for d, m := range dims { 180 p := n[d] 181 if p < 0 || p >= m { 182 panic("gen: element out of range") 183 } 184 id += p * s 185 s *= m 186 } 187 return id 188 } 189 190 // idxFromDelta returns a node index for the slice base plus the delta over the given 191 // dimensions and applying the translation. 192 func idxFromDelta(base, delta, dims []int, translate int) int { 193 s := 1 194 var id int 195 for d, m := range dims { 196 n := base[d] + delta[d] + translate 197 if n < 0 || n >= m { 198 panic("gen: element out of range") 199 } 200 id += n * s 201 s *= m 202 } 203 return id 204 }