// Copyright 2016 Sonia Keys // License MIT: https://opensource.org/licenses/MIT package graph import ( "errors" "math" "math/rand" "github.com/soniakeys/bits" ) // ChungLu constructs a random simple undirected graph. // // The Chung Lu model is similar to a "configuration model" where each // node has a specified degree. In the Chung Lu model the degree specified // for each node is taken as an expected degree, not an exact degree. // // Argument w is "weight," the expected degree for each node. // The values of w must be given in decreasing order. // // The constructed graph will have node 0 with expected degree w[0] and so on // so degree will decrease with node number. To randomize degree across // node numbers, consider using the Permute method with a rand.Perm. // // Also returned is the actual size m of constructed graph g. // // If Rand r is nil, the rand package default shared source is used. func ChungLu(w []float64, rr *rand.Rand) (g Undirected, m int) { // Ref: "Efficient Generation of Networks with Given Expected Degrees" // Joel C. Miller and Aric Hagberg // accessed at http://aric.hagberg.org/papers/miller-2011-efficient.pdf rf := rand.Float64 if rr != nil { rf = rr.Float64 } a := make(AdjacencyList, len(w)) S := 0. for i := len(w) - 1; i >= 0; i-- { S += w[i] } for u := 0; u < len(w)-1; u++ { v := u + 1 p := w[u] * w[v] / S if p > 1 { p = 1 } for v < len(w) && p > 0 { if p != 1 { v += int(math.Log(rf()) / math.Log(1-p)) } if v < len(w) { q := w[u] * w[v] / S if q > 1 { q = 1 } if rf() < q/p { a[u] = append(a[u], NI(v)) a[v] = append(a[v], NI(u)) m++ } p = q v++ } } } return Undirected{a}, m } // Euclidean generates a random simple graph on the Euclidean plane. // // Nodes are associated with coordinates uniformly distributed on a unit // square. Arcs are added between random nodes with a bias toward connecting // nearer nodes. // // Unfortunately the function has a few "knobs". // The returned graph will have order nNodes and arc size nArcs. The affinity // argument controls the bias toward connecting nearer nodes. The function // selects random pairs of nodes as a candidate arc then rejects the candidate // if the nodes fail an affinity test. Also parallel arcs are rejected. // As more affine or denser graphs are requested, rejections increase, // increasing run time. The patience argument controls the number of arc // rejections allowed before the function gives up and returns an error. // Note that higher affinity will require more patience and that some // combinations of nNodes and nArcs cannot be achieved with any amount of // patience given that the returned graph must be simple. // // If Rand r is nil, the rand package default shared source is used. // // Returned is a directed simple graph and associated positions indexed by // node number. In the arc list for each node, to-nodes are in random // order. // // See also LabeledEuclidean. func Euclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g Directed, pos []struct{ X, Y float64 }, err error) { a := make(AdjacencyList, nNodes) // graph ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64 if rr != nil { ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64 } // generate random positions pos = make([]struct{ X, Y float64 }, nNodes) for i := range pos { pos[i].X = rf() pos[i].Y = rf() } // arcs var tooFar, dup int arc: for i := 0; i < nArcs; { if tooFar == nArcs*patience { err = errors.New("affinity not found") return } if dup == nArcs*patience { err = errors.New("overcrowding") return } n1 := NI(ri(nNodes)) var n2 NI for { n2 = NI(ri(nNodes)) if n2 != n1 { // no graph loops break } } c1 := &pos[n1] c2 := &pos[n2] dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y) if dist*affinity > re() { // favor near nodes tooFar++ continue } for _, nb := range a[n1] { if nb == n2 { // no parallel arcs dup++ continue arc } } a[n1] = append(a[n1], n2) i++ } g = Directed{a} return } // LabeledEuclidean generates a random simple graph on the Euclidean plane. // // Arc label values in the returned graph g are indexes into the return value // wt. Wt is the Euclidean distance between the from and to nodes of the arc. // // Otherwise the function arguments and return values are the same as for // function Euclidean. See Euclidean. func LabeledEuclidean(nNodes, nArcs int, affinity float64, patience int, rr *rand.Rand) (g LabeledDirected, pos []struct{ X, Y float64 }, wt []float64, err error) { a := make(LabeledAdjacencyList, nNodes) // graph wt = make([]float64, nArcs) // arc weights ri, rf, re := rand.Intn, rand.Float64, rand.ExpFloat64 if rr != nil { ri, rf, re = rr.Intn, rr.Float64, rr.ExpFloat64 } // generate random positions pos = make([]struct{ X, Y float64 }, nNodes) for i := range pos { pos[i].X = rf() pos[i].Y = rf() } // arcs var tooFar, dup int arc: for i := 0; i < nArcs; { if tooFar == nArcs*patience { err = errors.New("affinity not found") return } if dup == nArcs*patience { err = errors.New("overcrowding") return } n1 := NI(ri(nNodes)) var n2 NI for { n2 = NI(ri(nNodes)) if n2 != n1 { // no graph loops break } } c1 := &pos[n1] c2 := &pos[n2] dist := math.Hypot(c2.X-c1.X, c2.Y-c1.Y) if dist*affinity > re() { // favor near nodes tooFar++ continue } for _, nb := range a[n1] { if nb.To == n2 { // no parallel arcs dup++ continue arc } } wt[i] = dist a[n1] = append(a[n1], Half{n2, LI(i)}) i++ } g = LabeledDirected{a} return } // Geometric generates a random geometric graph (RGG) on the Euclidean plane. // // An RGG is an undirected simple graph. Nodes are associated with coordinates // uniformly distributed on a unit square. Edges are added between all nodes // falling within a specified distance or radius of each other. // // The resulting number of edges is somewhat random but asymptotically // approaches m = πr²n²/2. The method accumulates and returns the actual // number of edges constructed. In the arc list for each node, to-nodes are // ordered. Consider using ShuffleArcLists if random order is important. // // If Rand r is nil, the rand package default shared source is used. // // See also LabeledGeometric. func Geometric(nNodes int, radius float64, rr *rand.Rand) (g Undirected, pos []struct{ X, Y float64 }, m int) { // Expected degree is approximately nπr². a := make(AdjacencyList, nNodes) rf := rand.Float64 if rr != nil { rf = rr.Float64 } pos = make([]struct{ X, Y float64 }, nNodes) for i := range pos { pos[i].X = rf() pos[i].Y = rf() } for u, up := range pos { for v := u + 1; v < len(pos); v++ { vp := pos[v] dx := math.Abs(up.X - vp.X) if dx >= radius { continue } dy := math.Abs(up.Y - vp.Y) if dy >= radius { continue } if math.Hypot(dx, dy) < radius { a[u] = append(a[u], NI(v)) a[v] = append(a[v], NI(u)) m++ } } } g = Undirected{a} return } // LabeledGeometric generates a random geometric graph (RGG) on the Euclidean // plane. // // Edge label values in the returned graph g are indexes into the return value // wt. Wt is the Euclidean distance between nodes of the edge. The graph // size m is len(wt). // // See Geometric for additional description. func LabeledGeometric(nNodes int, radius float64, rr *rand.Rand) (g LabeledUndirected, pos []struct{ X, Y float64 }, wt []float64) { a := make(LabeledAdjacencyList, nNodes) rf := rand.Float64 if rr != nil { rf = rr.Float64 } pos = make([]struct{ X, Y float64 }, nNodes) for i := range pos { pos[i].X = rf() pos[i].Y = rf() } for u, up := range pos { for v := u + 1; v < len(pos); v++ { vp := pos[v] if w := math.Hypot(up.X-vp.X, up.Y-vp.Y); w < radius { a[u] = append(a[u], Half{NI(v), LI(len(wt))}) a[v] = append(a[v], Half{NI(u), LI(len(wt))}) wt = append(wt, w) } } } g = LabeledUndirected{a} return } // GnmUndirected constructs a random simple undirected graph. // // Construction is by the Erdős–Rényi model where the specified number of // distinct edges is selected from all possible edges with equal probability. // // Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2. // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // See also Gnm3Undirected, a method producing a statistically equivalent // result, but by an algorithm with somewhat different performance properties. // Performance of the two methods is expected to be similar in most cases but // it may be worth trying both with your data to see if one has a clear // advantage. func GnmUndirected(n, m int, rr *rand.Rand) Undirected { // based on Alg. 2 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf ri := rand.Intn if rr != nil { ri = rr.Intn } re := n * (n - 1) / 2 ml := m if m*2 > re { ml = re - m } e := map[int]struct{}{} for len(e) < ml { e[ri(re)] = struct{}{} } a := make(AdjacencyList, n) if m*2 > re { i := 0 for v := 1; v < n; v++ { for w := 0; w < v; w++ { if _, ok := e[i]; !ok { a[v] = append(a[v], NI(w)) a[w] = append(a[w], NI(v)) } i++ } } } else { for i := range e { v := 1 + int(math.Sqrt(.25+float64(2*i))-.5) w := i - (v * (v - 1) / 2) a[v] = append(a[v], NI(w)) a[w] = append(a[w], NI(v)) } } return Undirected{a} } // GnmDirected constructs a random simple directed graph. // // Construction is by the Erdős–Rényi model where the specified number of // distinct arcs is selected from all possible arcs with equal probability. // // Argument n is number of nodes, ma is number of arcs and must be <= n(n-1). // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // See also Gnm3Directed, a method producing a statistically equivalent // result, but by // an algorithm with somewhat different performance properties. Performance // of the two methods is expected to be similar in most cases but it may be // worth trying both with your data to see if one has a clear advantage. func GnmDirected(n, ma int, rr *rand.Rand) Directed { // based on Alg. 2 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf ri := rand.Intn if rr != nil { ri = rr.Intn } re := n * (n - 1) ml := ma if ma*2 > re { ml = re - ma } e := map[int]struct{}{} for len(e) < ml { e[ri(re)] = struct{}{} } a := make(AdjacencyList, n) if ma*2 > re { i := 0 for v := 0; v < n; v++ { for w := 0; w < n; w++ { if w == v { continue } if _, ok := e[i]; !ok { a[v] = append(a[v], NI(w)) } i++ } } } else { for i := range e { v := i / (n - 1) w := i % (n - 1) if w >= v { w++ } a[v] = append(a[v], NI(w)) } } return Directed{a} } // Gnm3Undirected constructs a random simple undirected graph. // // Construction is by the Erdős–Rényi model where the specified number of // distinct edges is selected from all possible edges with equal probability. // // Argument n is number of nodes, m is number of edges and must be <= n(n-1)/2. // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // See also GnmUndirected, a method producing a statistically equivalent // result, but by an algorithm with somewhat different performance properties. // Performance of the two methods is expected to be similar in most cases but // it may be worth trying both with your data to see if one has a clear // advantage. func Gnm3Undirected(n, m int, rr *rand.Rand) Undirected { // based on Alg. 3 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf // // I like this algorithm for its elegance. Pitty it tends to run a // a little slower than the retry algorithm of Gnm. ri := rand.Intn if rr != nil { ri = rr.Intn } a := make(AdjacencyList, n) re := n * (n - 1) / 2 rm := map[int]int{} for i := 0; i < m; i++ { er := i + ri(re-i) eNew := er if rp, ok := rm[er]; ok { eNew = rp } if rp, ok := rm[i]; !ok { rm[er] = i } else { rm[er] = rp } v := 1 + int(math.Sqrt(.25+float64(2*eNew))-.5) w := eNew - (v * (v - 1) / 2) a[v] = append(a[v], NI(w)) a[w] = append(a[w], NI(v)) } return Undirected{a} } // Gnm3Directed constructs a random simple directed graph. // // Construction is by the Erdős–Rényi model where the specified number of // distinct arcs is selected from all possible arcs with equal probability. // // Argument n is number of nodes, ma is number of arcs and must be <= n(n-1). // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // See also GnmDirected, a method producing a statistically equivalent result, // but by an algorithm with somewhat different performance properties. // Performance of the two methods is expected to be similar in most cases // but it may be worth trying both with your data to see if one has a clear // advantage. func Gnm3Directed(n, ma int, rr *rand.Rand) Directed { // based on Alg. 3 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf ri := rand.Intn if rr != nil { ri = rr.Intn } a := make(AdjacencyList, n) re := n * (n - 1) rm := map[int]int{} for i := 0; i < ma; i++ { er := i + ri(re-i) eNew := er if rp, ok := rm[er]; ok { eNew = rp } if rp, ok := rm[i]; !ok { rm[er] = i } else { rm[er] = rp } v := eNew / (n - 1) w := eNew % (n - 1) if w >= v { w++ } a[v] = append(a[v], NI(w)) } return Directed{a} } // GnpUndirected constructs a random simple undirected graph. // // Construction is by the Gilbert model, an Erdős–Rényi like model where // distinct edges are independently selected from all possible edges with // the specified probability. // // Argument n is number of nodes, p is probability for selecting an edge. // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // Also returned is the actual size m of constructed graph g. func GnpUndirected(n int, p float64, rr *rand.Rand) (g Undirected, m int) { a := make(AdjacencyList, n) if n < 2 { return Undirected{a}, 0 } rf := rand.Float64 if rr != nil { rf = rr.Float64 } // based on Alg. 1 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf var v, w NI = 1, -1 g: for c := 1 / math.Log(1-p); ; { w += 1 + NI(c*math.Log(1-rf())) for { if w < v { a[v] = append(a[v], w) a[w] = append(a[w], v) m++ continue g } w -= v v++ if v == NI(n) { break g } } } return Undirected{a}, m } // GnpDirected constructs a random simple directed graph. // // Construction is by the Gilbert model, an Erdős–Rényi like model where // distinct arcs are independently selected from all possible arcs with // the specified probability. // // Argument n is number of nodes, p is probability for selecting an arc. // // If Rand r is nil, the rand package default shared source is used. // // In the generated arc list for each node, to-nodes are ordered. // Consider using ShuffleArcLists if random order is important. // // Also returned is the actual arc size m of constructed graph g. func GnpDirected(n int, p float64, rr *rand.Rand) (g Directed, ma int) { a := make(AdjacencyList, n) if n < 2 { return Directed{a}, 0 } rf := rand.Float64 if rr != nil { rf = rr.Float64 } // based on Alg. 1 from "Efficient Generation of Large Random Networks", // Vladimir Batagelj and Ulrik Brandes. // accessed at http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf var v, w NI = 0, -1 g: for c := 1 / math.Log(1-p); ; { w += 1 + NI(c*math.Log(1-rf())) for ; ; w -= NI(n) { if w == v { w++ } if w < NI(n) { a[v] = append(a[v], w) ma++ continue g } v++ if v == NI(n) { break g } } } return Directed{a}, ma } // KroneckerDirected generates a Kronecker-like random directed graph. // // The returned graph g is simple and has no isolated nodes but is not // necessarily fully connected. The number of of nodes will be <= 2^scale, // and will be near 2^scale for typical values of arcFactor, >= 2. // ArcFactor * 2^scale arcs are generated, although loops and duplicate arcs // are rejected. In the arc list for each node, to-nodes are in random // order. // // If Rand r is nil, the rand package default shared source is used. // // Return value ma is the number of arcs retained in the result graph. func KroneckerDirected(scale uint, arcFactor float64, rr *rand.Rand) (g Directed, ma int) { a, m := kronecker(scale, arcFactor, true, rr) return Directed{a}, m } // KroneckerUndirected generates a Kronecker-like random undirected graph. // // The returned graph g is simple and has no isolated nodes but is not // necessarily fully connected. The number of of nodes will be <= 2^scale, // and will be near 2^scale for typical values of edgeFactor, >= 2. // EdgeFactor * 2^scale edges are generated, although loops and duplicate edges // are rejected. In the arc list for each node, to-nodes are in random // order. // // If Rand r is nil, the rand package default shared source is used. // // Return value m is the true number of edges--not arcs--retained in the result // graph. func KroneckerUndirected(scale uint, edgeFactor float64, rr *rand.Rand) (g Undirected, m int) { al, s := kronecker(scale, edgeFactor, false, rr) return Undirected{al}, s } // Styled after the Graph500 example code. Not well tested currently. // Graph500 example generates undirected only. No idea if the directed variant // here is meaningful or not. // // note mma returns arc size ma for dir=true, but returns size m for dir=false func kronecker(scale uint, edgeFactor float64, dir bool, rr *rand.Rand) (g AdjacencyList, mma int) { rf, ri, rp := rand.Float64, rand.Intn, rand.Perm if rr != nil { rf, ri, rp = rr.Float64, rr.Intn, rr.Perm } N := 1 << scale // node extent M := int(edgeFactor*float64(N) + .5) // number of arcs/edges to generate a, b, c := 0.57, 0.19, 0.19 // initiator probabilities ab := a + b cNorm := c / (1 - ab) aNorm := a / ab ij := make([][2]NI, M) bm := bits.New(N) var nNodes int for k := range ij { var i, j int for b := 1; b < N; b <<= 1 { if rf() > ab { i |= b if rf() > cNorm { j |= b } } else if rf() > aNorm { j |= b } } if bm.Bit(i) == 0 { bm.SetBit(i, 1) nNodes++ } if bm.Bit(j) == 0 { bm.SetBit(j, 1) nNodes++ } r := ri(k + 1) // shuffle edges as they are generated ij[k] = ij[r] ij[r] = [2]NI{NI(i), NI(j)} } p := rp(nNodes) // mapping to shuffle IDs of non-isolated nodes px := 0 rn := make([]NI, N) for i := range rn { if bm.Bit(i) == 1 { rn[i] = NI(p[px]) // fill lookup table px++ } } g = make(AdjacencyList, nNodes) ij: for _, e := range ij { if e[0] == e[1] { continue // skip loops } ri, rj := rn[e[0]], rn[e[1]] for _, nb := range g[ri] { if nb == rj { continue ij // skip parallel edges } } g[ri] = append(g[ri], rj) mma++ if !dir { g[rj] = append(g[rj], ri) } } return }