diff --git a/graph/graphs/gen/batagelj_brandes.go b/graph/graphs/gen/batagelj_brandes.go index 582ef20ab3..210371dc09 100644 --- a/graph/graphs/gen/batagelj_brandes.go +++ b/graph/graphs/gen/batagelj_brandes.go @@ -14,14 +14,13 @@ import ( "golang.org/x/exp/rand" "gonum.org/v1/gonum/graph" - "gonum.org/v1/gonum/graph/simple" ) -// Gnp constructs a Gilbert’s model graph in the destination, dst, of order n. Edges +// Gnp constructs a Gilbert’s model subgraph in the destination, dst, of order n. Edges // between nodes are formed with the probability, p. If src is not nil it is used // as the random source, otherwise rand.Float64 is used. The graph is constructed // in O(n+m) time where m is the number of edges added. -func Gnp(dst GraphBuilder, n int, p float64, src rand.Source) error { +func Gnp(dst graph.Builder, n int, p float64, src rand.Source) error { if p == 0 { return nil } @@ -35,10 +34,11 @@ func Gnp(dst GraphBuilder, n int, p float64, src rand.Source) error { r = rand.New(src).Float64 } - for i := 0; i < n; i++ { - if dst.Node(int64(i)) == nil { - dst.AddNode(simple.Node(i)) - } + nodes := make([]graph.Node, n) + for i := range nodes { + u := dst.NewNode() + dst.AddNode(u) + nodes[i] = u } lp := math.Log(1 - p) @@ -51,7 +51,7 @@ func Gnp(dst GraphBuilder, n int, p float64, src rand.Source) error { v++ } if v < n { - dst.SetEdge(simple.Edge{F: simple.Node(w), T: simple.Node(v)}) + dst.SetEdge(dst.NewEdge(nodes[w], nodes[v])) } } @@ -66,24 +66,14 @@ func Gnp(dst GraphBuilder, n int, p float64, src rand.Source) error { v++ } if v < n { - dst.SetEdge(simple.Edge{F: simple.Node(v), T: simple.Node(w)}) + dst.SetEdge(dst.NewEdge(nodes[v], nodes[w])) } } return nil } -// edgeNodesFor returns the pair of nodes for the ith edge in a simple -// undirected graph. The pair is returned such that w.ID < v.ID. -func edgeNodesFor(i int) (v, w simple.Node) { - // This is an algebraic simplification of the expressions described - // on p3 of http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf - v = simple.Node(0.5 + math.Sqrt(float64(1+8*i))/2) - w = simple.Node(i) - v*(v-1)/2 - return v, w -} - -// Gnm constructs a Erdős-Rényi model graph in the destination, dst, of +// Gnm constructs a Erdős-Rényi model subgraph in the destination, dst, of // order n and size m. If src is not nil it is used as the random source, // otherwise rand.Intn is used. The graph is constructed in O(m) expected // time for m ≤ (n choose 2)/2. @@ -111,19 +101,19 @@ func Gnm(dst GraphBuilder, n, m int, src rand.Source) error { rnd = rand.New(src).Intn } - for i := 0; i < n; i++ { - if dst.Node(int64(i)) == nil { - dst.AddNode(simple.Node(i)) - } + nodes := make([]graph.Node, n) + for i := range nodes { + u := dst.NewNode() + dst.AddNode(u) + nodes[i] = u } // Add forward edges for all graphs. for i := 0; i < m; i++ { for { - v, w := edgeNodesFor(rnd(nChoose2)) - e := simple.Edge{F: w, T: v} - if !hasEdge(e.F.ID(), e.T.ID()) { - dst.SetEdge(e) + v, w := edgeNodesFor(rnd(nChoose2), nodes) + if !hasEdge(w.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(w, v)) break } } @@ -135,10 +125,9 @@ func Gnm(dst GraphBuilder, n, m int, src rand.Source) error { } for i := 0; i < m; i++ { for { - v, w := edgeNodesFor(rnd(nChoose2)) - e := simple.Edge{F: v, T: w} - if !hasEdge(e.F.ID(), e.T.ID()) { - dst.SetEdge(e) + v, w := edgeNodesFor(rnd(nChoose2), nodes) + if !hasEdge(v.ID(), w.ID()) { + dst.SetEdge(dst.NewEdge(v, w)) break } } @@ -147,7 +136,7 @@ func Gnm(dst GraphBuilder, n, m int, src rand.Source) error { return nil } -// SmallWorldsBB constructs a small worlds graph of order n in the destination, dst. +// SmallWorldsBB constructs a small worlds subgraph of order n in the destination, dst. // Node degree is specified by d and edge replacement by the probability, p. // If src is not nil it is used as the random source, otherwise rand.Float64 is used. // The graph is constructed in O(nd) time. @@ -158,6 +147,9 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error return fmt.Errorf("gen: bad degree: d=%d", d) } if p == 0 { + for i := 0; i < n; i++ { + dst.AddNode(dst.NewNode()) + } return nil } if p < 0 || p >= 1 { @@ -182,10 +174,11 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error hasEdge = dg.HasEdgeFromTo } - for i := 0; i < n; i++ { - if dst.Node(int64(i)) == nil { - dst.AddNode(simple.Node(i)) - } + nodes := make([]graph.Node, n) + for i := range nodes { + u := dst.NewNode() + dst.AddNode(u) + nodes[i] = u } nChoose2 := (n - 1) * n / 2 @@ -200,16 +193,24 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error for i := 1; i <= d; i++ { if k > 0 { j := v*(v-1)/2 + (v+i)%n - var ej simple.Edge - ej.T, ej.F = edgeNodesFor(j) - if !hasEdge(ej.From().ID(), ej.To().ID()) { - dst.SetEdge(ej) + if v, u := edgeNodesFor(j, nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) } k-- m++ - var em simple.Edge - em.T, em.F = edgeNodesFor(m) - if !hasEdge(em.From().ID(), em.To().ID()) { + + // For small graphs, m may be an + // edge that has an end that is + // not in the subgraph. + if m >= nChoose2 { + // Since m is monotonically + // increasing, no m edges from + // here on are valid, so don't + // add them to replace. + continue + } + + if v, u := edgeNodesFor(m, nodes); !hasEdge(u.ID(), v.ID()) { replace[j] = m } else { replace[j] = replace[m] @@ -221,19 +222,12 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error } for i := m + 1; i <= n*d && i < nChoose2; i++ { r := rndN(nChoose2-i) + i - var er simple.Edge - er.T, er.F = edgeNodesFor(r) - if !hasEdge(er.From().ID(), er.To().ID()) { - dst.SetEdge(er) - } else { - er.T, er.F = edgeNodesFor(replace[r]) - if !hasEdge(er.From().ID(), er.To().ID()) { - dst.SetEdge(er) - } + if v, u := edgeNodesFor(r, nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) + } else if v, u = edgeNodesFor(replace[r], nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) } - var ei simple.Edge - ei.T, ei.F = edgeNodesFor(i) - if !hasEdge(ei.From().ID(), ei.To().ID()) { + if v, u := edgeNodesFor(i, nodes); !hasEdge(u.ID(), v.ID()) { replace[r] = i } else { replace[r] = replace[i] @@ -251,14 +245,24 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error for i := 1; i <= d; i++ { if k > 0 { j := v*(v-1)/2 + (v+i)%n - var ej simple.Edge - ej.F, ej.T = edgeNodesFor(j) - if !hasEdge(ej.From().ID(), ej.To().ID()) { - dst.SetEdge(ej) + if u, v := edgeNodesFor(j, nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) } k-- m++ - if u, v := edgeNodesFor(m); !hasEdge(u.ID(), v.ID()) { + + // For small graphs, m may be an + // edge that has an end that is + // not in the subgraph. + if m >= nChoose2 { + // Since m is monotonically + // increasing, no m edges from + // here on are valid, so don't + // add them to replace. + continue + } + + if u, v := edgeNodesFor(m, nodes); !hasEdge(u.ID(), v.ID()) { replace[j] = m } else { replace[j] = replace[m] @@ -270,17 +274,12 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error } for i := m + 1; i <= n*d && i < nChoose2; i++ { r := rndN(nChoose2-i) + i - var er simple.Edge - er.F, er.T = edgeNodesFor(r) - if !hasEdge(er.From().ID(), er.To().ID()) { - dst.SetEdge(er) - } else { - er.F, er.T = edgeNodesFor(replace[r]) - if !hasEdge(er.From().ID(), er.To().ID()) { - dst.SetEdge(er) - } + if u, v := edgeNodesFor(r, nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) + } else if u, v = edgeNodesFor(replace[r], nodes); !hasEdge(u.ID(), v.ID()) { + dst.SetEdge(dst.NewEdge(u, v)) } - if u, v := edgeNodesFor(i); !hasEdge(u.ID(), v.ID()) { + if u, v := edgeNodesFor(i, nodes); !hasEdge(u.ID(), v.ID()) { replace[r] = i } else { replace[r] = replace[i] @@ -290,6 +289,17 @@ func SmallWorldsBB(dst GraphBuilder, n, d int, p float64, src rand.Source) error return nil } +// edgeNodesFor returns the pair of nodes for the ith edge in a simple +// undirected graph. The pair is returned such that the index of w in +// nodes is less than the index of v in nodes. +func edgeNodesFor(i int, nodes []graph.Node) (v, w graph.Node) { + // This is an algebraic simplification of the expressions described + // on p3 of http://algo.uni-konstanz.de/publications/bb-eglrn-05.pdf + vi := int(0.5 + math.Sqrt(float64(1+8*i))/2) + wi := i - vi*(vi-1)/2 + return nodes[vi], nodes[wi] +} + // Multigraph generators. // PowerLaw constructs a power-law degree graph by preferential attachment in dst diff --git a/graph/graphs/gen/batagelj_brandes_test.go b/graph/graphs/gen/batagelj_brandes_test.go index a0d2cec485..be45889bf3 100644 --- a/graph/graphs/gen/batagelj_brandes_test.go +++ b/graph/graphs/gen/batagelj_brandes_test.go @@ -56,10 +56,15 @@ func TestGnpUndirected(t *testing.T) { for n := 2; n <= 20; n++ { for p := 0.; p <= 1; p += 0.1 { g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := Gnp(g, n, p, nil) if err != nil { t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, p=%v", n, p) + } if g.addBackwards { t.Errorf("edge added with From.ID > To.ID: n=%d, p=%v", n, p) } @@ -77,10 +82,15 @@ func TestGnpDirected(t *testing.T) { for n := 2; n <= 20; n++ { for p := 0.; p <= 1; p += 0.1 { g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := Gnp(g, n, p, nil) if err != nil { t.Fatalf("unexpected error: n=%d, p=%v: %v", n, p, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, p=%v", n, p) + } if g.addSelfLoop { t.Errorf("unexpected self edge: n=%d, p=%v", n, p) } @@ -96,10 +106,15 @@ func TestGnmUndirected(t *testing.T) { nChoose2 := (n - 1) * n / 2 for m := 0; m <= nChoose2; m++ { g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := Gnm(g, n, m, nil) if err != nil { t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, m=%d", n, m) + } if g.addBackwards { t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d", n, m) } @@ -118,10 +133,15 @@ func TestGnmDirected(t *testing.T) { nChoose2 := (n - 1) * n / 2 for m := 0; m <= nChoose2*2; m++ { g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := Gnm(g, n, m, nil) if err != nil { t.Fatalf("unexpected error: n=%d, m=%d: %v", n, m, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, m=%d", n, m) + } if g.addSelfLoop { t.Errorf("unexpected self edge: n=%d, m=%d", n, m) } @@ -137,10 +157,15 @@ func TestSmallWorldsBBUndirected(t *testing.T) { for d := 1; d <= (n-1)/2; d++ { for p := 0.; p < 1; p += 0.1 { g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := SmallWorldsBB(g, n, d, p, nil) if err != nil { t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, d=%d, p=%v", n, d, p) + } if g.addBackwards { t.Errorf("edge added with From.ID > To.ID: n=%d, d=%d, p=%v", n, d, p) } @@ -160,10 +185,15 @@ func TestSmallWorldsBBDirected(t *testing.T) { for d := 1; d <= (n-1)/2; d++ { for p := 0.; p < 1; p += 0.1 { g := &gnDirected{DirectedBuilder: simple.NewDirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := SmallWorldsBB(g, n, d, p, nil) if err != nil { t.Fatalf("unexpected error: n=%d, d=%d, p=%v: %v", n, d, p, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, d=%d, p=%v", n, d, p) + } if g.addSelfLoop { t.Errorf("unexpected self edge: n=%d, d=%d, p=%v", n, d, p) } diff --git a/graph/graphs/gen/gen.go b/graph/graphs/gen/gen.go index ad30876d90..e632b8f280 100644 --- a/graph/graphs/gen/gen.go +++ b/graph/graphs/gen/gen.go @@ -8,7 +8,6 @@ import "gonum.org/v1/gonum/graph" // GraphBuilder is a graph that can have nodes and edges added. type GraphBuilder interface { - Node(id int64) graph.Node HasEdgeBetween(xid, yid int64) bool graph.Builder } diff --git a/graph/graphs/gen/holme_kim.go b/graph/graphs/gen/holme_kim.go index f8d2185525..8f49995d7d 100644 --- a/graph/graphs/gen/holme_kim.go +++ b/graph/graphs/gen/holme_kim.go @@ -15,7 +15,7 @@ import ( "gonum.org/v1/gonum/stat/sampleuv" ) -// TunableClusteringScaleFree constructs a graph in the destination, dst, of order n. +// TunableClusteringScaleFree constructs a subgraph in the destination, dst, of order n. // The graph is constructed successively starting from an m order graph with one node // having degree m-1. At each iteration of graph addition, one node is added with m // additional edges joining existing nodes with probability proportional to the nodes' @@ -47,10 +47,11 @@ func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64 // Initial condition. wt := make([]float64, n) + id := make([]int64, n) for u := 0; u < m; u++ { - if dst.Node(int64(0)) == nil { - dst.AddNode(simple.Node(u)) - } + un := dst.NewNode() + dst.AddNode(un) + id[u] = un.ID() // We need to give equal probability for // adding the first generation of edges. wt[u] = 1 @@ -64,17 +65,24 @@ func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64 // Growth. for v := m; v < n; v++ { + vn := dst.NewNode() + dst.AddNode(vn) + id[v] = vn.ID() var u int pa: for i := 0; i < m; i++ { // Triad formation. if i != 0 && rnd() < p { - for _, w := range permute(graph.NodesOf(dst.From(int64(u))), rndN) { + // TODO(kortschak): Decide whether the node + // order in this input to permute should be + // sorted first to allow repeatable runs. + for _, w := range permute(graph.NodesOf(dst.From(id[u])), rndN) { wid := w.ID() - if wid == int64(v) || dst.HasEdgeBetween(wid, int64(v)) { + if wid == id[v] || dst.HasEdgeBetween(wid, id[v]) { continue } - dst.SetEdge(simple.Edge{F: w, T: simple.Node(v)}) + + dst.SetEdge(dst.NewEdge(w, vn)) wt[wid]++ wt[v]++ continue pa @@ -88,10 +96,10 @@ func TunableClusteringScaleFree(dst graph.UndirectedBuilder, n, m int, p float64 if !ok { return errors.New("gen: depleted distribution") } - if u == v || dst.HasEdgeBetween(int64(u), int64(v)) { + if u == v || dst.HasEdgeBetween(id[u], id[v]) { continue } - dst.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)}) + dst.SetEdge(dst.NewEdge(dst.Node(id[u]), vn)) wt[u]++ wt[v]++ break diff --git a/graph/graphs/gen/holme_kim_test.go b/graph/graphs/gen/holme_kim_test.go index c25e1985c6..443599d555 100644 --- a/graph/graphs/gen/holme_kim_test.go +++ b/graph/graphs/gen/holme_kim_test.go @@ -15,10 +15,15 @@ func TestTunableClusteringScaleFree(t *testing.T) { for m := 0; m < n; m++ { for p := 0.; p <= 1; p += 0.1 { g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := TunableClusteringScaleFree(g, n, m, p, nil) if err != nil { t.Fatalf("unexpected error: n=%d, m=%d, p=%v: %v", n, m, p, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: n=%d, m=%d, p=%v", n, m, p) + } if g.addBackwards { t.Errorf("edge added with From.ID > To.ID: n=%d, m=%d, p=%v", n, m, p) } diff --git a/graph/graphs/gen/small_world.go b/graph/graphs/gen/small_world.go index 735d8a52c8..1846ef10e5 100644 --- a/graph/graphs/gen/small_world.go +++ b/graph/graphs/gen/small_world.go @@ -12,16 +12,15 @@ import ( "golang.org/x/exp/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. +// 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) { @@ -39,10 +38,11 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src for _, d := range dims { n *= d } - for i := 0; i < n; i++ { - if dst.Node(int64(i)) == nil { - dst.AddNode(simple.Node(i)) - } + nodes := make([]graph.Node, n) + for i := range nodes { + u := dst.NewNode() + dst.AddNode(u) + nodes[i] = u } hasEdge := dst.HasEdgeBetween @@ -56,26 +56,25 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src locality[i] = p*2 + 1 } iterateOver(dims, func(u []int) { - uid := idFrom(u, dims) + un := nodes[idxFrom(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)} - if uid > vid { - e.F, e.T = e.T, e.F + vn := nodes[idxFromDelta(u, delta, dims, -p)] + if un.ID() > vn.ID() { + un, vn = vn, un } - if !hasEdge(e.From().ID(), e.To().ID()) { - dst.SetEdge(e) + if !hasEdge(un.ID(), vn.ID()) { + dst.SetEdge(dst.NewEdge(un, vn)) } if !isDirected { return } - e.F, e.T = e.T, e.F - if !hasEdge(e.From().ID(), e.To().ID()) { - dst.SetEdge(e) + un, vn = vn, un + if !hasEdge(un.ID(), vn.ID()) { + dst.SetEdge(dst.NewEdge(un, vn)) } }) }) @@ -92,26 +91,26 @@ func NavigableSmallWorld(dst GraphBuilder, dims []int, p, q int, r float64, src w := make([]float64, n) ws := sampleuv.NewWeighted(w, src) iterateOver(dims, func(u []int) { - uid := idFrom(u, dims) + un := nodes[idxFrom(u, dims)] iterateOver(dims, func(v []int) { d := manhattanBetween(u, v) if d <= p { return } - w[idFrom(v, dims)] = math.Pow(float64(d), -r) + w[idxFrom(v, dims)] = math.Pow(float64(d), -r) }) ws.ReweightAll(w) for i := 0; i < q; i++ { - vid, ok := ws.Take() + vidx, ok := ws.Take() if !ok { panic("depleted distribution") } - e := simple.Edge{F: simple.Node(uid), T: simple.Node(vid)} - if !isDirected && uid > vid { - e.F, e.T = e.T, e.F + vn := nodes[vidx] + if !isDirected && un.ID() > vn.ID() { + un, vn = vn, un } - if !hasEdge(e.From().ID(), e.To().ID()) { - dst.SetEdge(e) + if !hasEdge(un.ID(), vn.ID()) { + dst.SetEdge(dst.NewEdge(un, vn)) } } for i := range w { @@ -173,8 +172,8 @@ func manhattanDelta(a, delta, dims []int, translate int) int { return d } -// idFrom returns a node id for the slice n over the given dimensions. -func idFrom(n, dims []int) int { +// 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 { @@ -188,9 +187,9 @@ func idFrom(n, dims []int) int { return id } -// idFromDelta returns a node id for the slice base plus the delta over the given +// idxFromDelta returns a node index for the slice base plus the delta over the given // dimensions and applying the translation. -func idFromDelta(base, delta, dims []int, translate int) int { +func idxFromDelta(base, delta, dims []int, translate int) int { s := 1 var id int for d, m := range dims { diff --git a/graph/graphs/gen/small_world_test.go b/graph/graphs/gen/small_world_test.go index dcf60b4e0d..44823ab184 100644 --- a/graph/graphs/gen/small_world_test.go +++ b/graph/graphs/gen/small_world_test.go @@ -22,6 +22,8 @@ func TestNavigableSmallWorldUndirected(t *testing.T) { for r := 0.5; r < 10; r++ { for _, dims := range smallWorldDimensionParameters { g := &gnUndirected{UndirectedBuilder: simple.NewUndirectedGraph()} + orig := g.NewNode() + g.AddNode(orig) err := NavigableSmallWorld(g, dims, p, q, r, nil) n := 1 for _, d := range dims { @@ -30,6 +32,9 @@ func TestNavigableSmallWorldUndirected(t *testing.T) { if err != nil { t.Fatalf("unexpected error: dims=%v n=%d, p=%d, q=%d, r=%v: %v", dims, n, p, q, r, err) } + if g.From(orig.ID()).Len() != 0 { + t.Errorf("edge added from already existing node: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) + } if g.addBackwards { t.Errorf("edge added with From.ID > To.ID: dims=%v n=%d, p=%d, q=%d, r=%v", dims, n, p, q, r) }