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  }