github.com/gopherd/gonum@v0.0.4/graph/community/louvain_directed_test.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 community
     6  
     7  import (
     8  	"math"
     9  	"reflect"
    10  	"sort"
    11  	"testing"
    12  
    13  	"math/rand"
    14  
    15  	"github.com/gopherd/gonum/floats/scalar"
    16  	"github.com/gopherd/gonum/graph"
    17  	"github.com/gopherd/gonum/graph/internal/ordered"
    18  	"github.com/gopherd/gonum/graph/simple"
    19  )
    20  
    21  type communityDirectedQTest struct {
    22  	name       string
    23  	g          []intset
    24  	structures []structure
    25  
    26  	wantLevels []level
    27  }
    28  
    29  var communityDirectedQTests = []communityDirectedQTest{
    30  	{
    31  		name: "simple_directed",
    32  		g:    simpleDirected,
    33  		// community structure and modularity calculated by C++ implementation: louvain igraph.
    34  		// Note that louvain igraph returns Q as an unscaled value.
    35  		structures: []structure{
    36  			{
    37  				resolution: 1,
    38  				memberships: []intset{
    39  					0: linksTo(0, 1),
    40  					1: linksTo(2, 3, 4),
    41  				},
    42  				want: 0.5714285714285716 / 7,
    43  				tol:  1e-10,
    44  			},
    45  		},
    46  		wantLevels: []level{
    47  			{
    48  				communities: [][]graph.Node{
    49  					{simple.Node(0), simple.Node(1)},
    50  					{simple.Node(2), simple.Node(3), simple.Node(4)},
    51  				},
    52  				q: 0.5714285714285716 / 7,
    53  			},
    54  			{
    55  				communities: [][]graph.Node{
    56  					{simple.Node(0)},
    57  					{simple.Node(1)},
    58  					{simple.Node(2)},
    59  					{simple.Node(3)},
    60  					{simple.Node(4)},
    61  				},
    62  				q: -1.2857142857142856 / 7,
    63  			},
    64  		},
    65  	},
    66  	{
    67  		name: "zachary",
    68  		g:    zachary,
    69  		// community structure and modularity calculated by C++ implementation: louvain igraph.
    70  		// Note that louvain igraph returns Q as an unscaled value.
    71  		structures: []structure{
    72  			{
    73  				resolution: 1,
    74  				memberships: []intset{
    75  					0: linksTo(0, 1, 2, 3, 7, 11, 12, 13, 17, 19, 21),
    76  					1: linksTo(4, 5, 6, 10, 16),
    77  					2: linksTo(8, 9, 14, 15, 18, 20, 22, 26, 29, 30, 32, 33),
    78  					3: linksTo(23, 24, 25, 27, 28, 31),
    79  				},
    80  				want: 34.3417721519 / 79 /* 5->6 and 6->5 because of co-equal rank */, tol: 1e-4,
    81  			},
    82  		},
    83  		wantLevels: []level{
    84  			{
    85  				q: 0.43470597660631316,
    86  				communities: [][]graph.Node{
    87  					{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(7), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(17), simple.Node(19), simple.Node(21)},
    88  					{simple.Node(4), simple.Node(5), simple.Node(6), simple.Node(10), simple.Node(16)},
    89  					{simple.Node(8), simple.Node(9), simple.Node(14), simple.Node(15), simple.Node(18), simple.Node(20), simple.Node(22), simple.Node(26), simple.Node(29), simple.Node(30), simple.Node(32), simple.Node(33)},
    90  					{simple.Node(23), simple.Node(24), simple.Node(25), simple.Node(27), simple.Node(28), simple.Node(31)},
    91  				},
    92  			},
    93  			{
    94  				q: 0.3911232174331037,
    95  				communities: [][]graph.Node{
    96  					{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(7), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(17), simple.Node(19), simple.Node(21)},
    97  					{simple.Node(4), simple.Node(10)},
    98  					{simple.Node(5), simple.Node(6), simple.Node(16)},
    99  					{simple.Node(8), simple.Node(30)},
   100  					{simple.Node(9), simple.Node(14), simple.Node(15), simple.Node(18), simple.Node(20), simple.Node(22), simple.Node(32), simple.Node(33)},
   101  					{simple.Node(23), simple.Node(24), simple.Node(25), simple.Node(27), simple.Node(28), simple.Node(31)},
   102  					{simple.Node(26), simple.Node(29)},
   103  				},
   104  			},
   105  			{
   106  				q: -0.014580996635154624,
   107  				communities: [][]graph.Node{
   108  					{simple.Node(0)},
   109  					{simple.Node(1)},
   110  					{simple.Node(2)},
   111  					{simple.Node(3)},
   112  					{simple.Node(4)},
   113  					{simple.Node(5)},
   114  					{simple.Node(6)},
   115  					{simple.Node(7)},
   116  					{simple.Node(8)},
   117  					{simple.Node(9)},
   118  					{simple.Node(10)},
   119  					{simple.Node(11)},
   120  					{simple.Node(12)},
   121  					{simple.Node(13)},
   122  					{simple.Node(14)},
   123  					{simple.Node(15)},
   124  					{simple.Node(16)},
   125  					{simple.Node(17)},
   126  					{simple.Node(18)},
   127  					{simple.Node(19)},
   128  					{simple.Node(20)},
   129  					{simple.Node(21)},
   130  					{simple.Node(22)},
   131  					{simple.Node(23)},
   132  					{simple.Node(24)},
   133  					{simple.Node(25)},
   134  					{simple.Node(26)},
   135  					{simple.Node(27)},
   136  					{simple.Node(28)},
   137  					{simple.Node(29)},
   138  					{simple.Node(30)},
   139  					{simple.Node(31)},
   140  					{simple.Node(32)},
   141  					{simple.Node(33)},
   142  				},
   143  			},
   144  		},
   145  	},
   146  	{
   147  		name: "blondel",
   148  		g:    blondel,
   149  		// community structure and modularity calculated by C++ implementation: louvain igraph.
   150  		// Note that louvain igraph returns Q as an unscaled value.
   151  		structures: []structure{
   152  			{
   153  				resolution: 1,
   154  				memberships: []intset{
   155  					0: linksTo(0, 1, 2, 3, 4, 5, 6, 7),
   156  					1: linksTo(8, 9, 10, 11, 12, 13, 14, 15),
   157  				},
   158  				want: 11.1428571429 / 28, tol: 1e-4,
   159  			},
   160  		},
   161  		wantLevels: []level{
   162  			{
   163  				q: 0.3979591836734694,
   164  				communities: [][]graph.Node{
   165  					{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(3), simple.Node(4), simple.Node(5), simple.Node(6), simple.Node(7)},
   166  					{simple.Node(8), simple.Node(9), simple.Node(10), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(14), simple.Node(15)},
   167  				},
   168  			},
   169  			{
   170  				q: 0.36862244897959184,
   171  				communities: [][]graph.Node{
   172  					{simple.Node(0), simple.Node(1), simple.Node(2), simple.Node(4), simple.Node(5)},
   173  					{simple.Node(3), simple.Node(6), simple.Node(7)},
   174  					{simple.Node(8), simple.Node(9), simple.Node(10), simple.Node(11), simple.Node(12), simple.Node(13), simple.Node(14), simple.Node(15)},
   175  				},
   176  			},
   177  			{
   178  				q: -0.022959183673469385,
   179  				communities: [][]graph.Node{
   180  					{simple.Node(0)},
   181  					{simple.Node(1)},
   182  					{simple.Node(2)},
   183  					{simple.Node(3)},
   184  					{simple.Node(4)},
   185  					{simple.Node(5)},
   186  					{simple.Node(6)},
   187  					{simple.Node(7)},
   188  					{simple.Node(8)},
   189  					{simple.Node(9)},
   190  					{simple.Node(10)},
   191  					{simple.Node(11)},
   192  					{simple.Node(12)},
   193  					{simple.Node(13)},
   194  					{simple.Node(14)},
   195  					{simple.Node(15)},
   196  				},
   197  			},
   198  		},
   199  	},
   200  }
   201  
   202  func TestCommunityQDirected(t *testing.T) {
   203  	for _, test := range communityDirectedQTests {
   204  		g := simple.NewDirectedGraph()
   205  		for u, e := range test.g {
   206  			// Add nodes that are not defined by an edge.
   207  			if g.Node(int64(u)) == nil {
   208  				g.AddNode(simple.Node(u))
   209  			}
   210  			for v := range e {
   211  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   212  			}
   213  		}
   214  
   215  		testCommunityQDirected(t, test, g)
   216  	}
   217  }
   218  
   219  func TestCommunityQWeightedDirected(t *testing.T) {
   220  	for _, test := range communityDirectedQTests {
   221  		g := simple.NewWeightedDirectedGraph(0, 0)
   222  		for u, e := range test.g {
   223  			// Add nodes that are not defined by an edge.
   224  			if g.Node(int64(u)) == nil {
   225  				g.AddNode(simple.Node(u))
   226  			}
   227  			for v := range e {
   228  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   229  			}
   230  		}
   231  
   232  		testCommunityQDirected(t, test, g)
   233  	}
   234  }
   235  
   236  func testCommunityQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
   237  	for _, structure := range test.structures {
   238  		communities := make([][]graph.Node, len(structure.memberships))
   239  		for i, c := range structure.memberships {
   240  			for n := range c {
   241  				communities[i] = append(communities[i], simple.Node(n))
   242  			}
   243  		}
   244  		got := Q(g, communities, structure.resolution)
   245  		if !scalar.EqualWithinAbsOrRel(got, structure.want, structure.tol, structure.tol) && !math.IsNaN(structure.want) {
   246  			for _, c := range communities {
   247  				ordered.ByID(c)
   248  			}
   249  			t.Errorf("unexpected Q value for %q %v: got: %v want: %v",
   250  				test.name, communities, got, structure.want)
   251  		}
   252  	}
   253  }
   254  
   255  func TestCommunityDeltaQDirected(t *testing.T) {
   256  	for _, test := range communityDirectedQTests {
   257  		g := simple.NewDirectedGraph()
   258  		for u, e := range test.g {
   259  			// Add nodes that are not defined by an edge.
   260  			if g.Node(int64(u)) == nil {
   261  				g.AddNode(simple.Node(u))
   262  			}
   263  			for v := range e {
   264  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   265  			}
   266  		}
   267  
   268  		testCommunityDeltaQDirected(t, test, g)
   269  	}
   270  }
   271  
   272  func TestCommunityDeltaQWeightedDirected(t *testing.T) {
   273  	for _, test := range communityDirectedQTests {
   274  		g := simple.NewWeightedDirectedGraph(0, 0)
   275  		for u, e := range test.g {
   276  			// Add nodes that are not defined by an edge.
   277  			if g.Node(int64(u)) == nil {
   278  				g.AddNode(simple.Node(u))
   279  			}
   280  			for v := range e {
   281  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   282  			}
   283  		}
   284  
   285  		testCommunityDeltaQDirected(t, test, g)
   286  	}
   287  }
   288  
   289  func testCommunityDeltaQDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
   290  	rnd := rand.New(rand.NewSource(1)).Intn
   291  	for _, structure := range test.structures {
   292  		communityOf := make(map[int64]int)
   293  		communities := make([][]graph.Node, len(structure.memberships))
   294  		for i, c := range structure.memberships {
   295  			for n := range c {
   296  				n := int64(n)
   297  				communityOf[n] = i
   298  				communities[i] = append(communities[i], simple.Node(n))
   299  			}
   300  			ordered.ByID(communities[i])
   301  		}
   302  
   303  		before := Q(g, communities, structure.resolution)
   304  
   305  		l := newDirectedLocalMover(reduceDirected(g, nil), communities, structure.resolution)
   306  		if l == nil {
   307  			if !math.IsNaN(before) {
   308  				t.Errorf("unexpected nil localMover with non-NaN Q graph: Q=%.4v", before)
   309  			}
   310  			return
   311  		}
   312  
   313  		// This is done to avoid run-to-run
   314  		// variation due to map iteration order.
   315  		ordered.ByID(l.nodes)
   316  
   317  		l.shuffle(rnd)
   318  
   319  		for _, target := range l.nodes {
   320  			got, gotDst, gotSrc := l.deltaQ(target)
   321  
   322  			want, wantDst := math.Inf(-1), -1
   323  			migrated := make([][]graph.Node, len(structure.memberships))
   324  			for i, c := range structure.memberships {
   325  				for n := range c {
   326  					n := int64(n)
   327  					if n == target.ID() {
   328  						continue
   329  					}
   330  					migrated[i] = append(migrated[i], simple.Node(n))
   331  				}
   332  				ordered.ByID(migrated[i])
   333  			}
   334  
   335  			for i, c := range structure.memberships {
   336  				if i == communityOf[target.ID()] {
   337  					continue
   338  				}
   339  				connected := false
   340  				for n := range c {
   341  					if g.HasEdgeBetween(int64(n), target.ID()) {
   342  						connected = true
   343  						break
   344  					}
   345  				}
   346  				if !connected {
   347  					continue
   348  				}
   349  				migrated[i] = append(migrated[i], target)
   350  				after := Q(g, migrated, structure.resolution)
   351  				migrated[i] = migrated[i][:len(migrated[i])-1]
   352  				if after-before > want {
   353  					want = after - before
   354  					wantDst = i
   355  				}
   356  			}
   357  
   358  			if !scalar.EqualWithinAbsOrRel(got, want, structure.tol, structure.tol) || gotDst != wantDst {
   359  				t.Errorf("unexpected result moving n=%d in c=%d of %s/%.4v: got: %.4v,%d want: %.4v,%d"+
   360  					"\n\t%v\n\t%v",
   361  					target.ID(), communityOf[target.ID()], test.name, structure.resolution, got, gotDst, want, wantDst,
   362  					communities, migrated)
   363  			}
   364  			if gotSrc.community != communityOf[target.ID()] {
   365  				t.Errorf("unexpected source community index: got: %d want: %d", gotSrc, communityOf[target.ID()])
   366  			} else if communities[gotSrc.community][gotSrc.node].ID() != target.ID() {
   367  				wantNodeIdx := -1
   368  				for i, n := range communities[gotSrc.community] {
   369  					if n.ID() == target.ID() {
   370  						wantNodeIdx = i
   371  						break
   372  					}
   373  				}
   374  				t.Errorf("unexpected source node index: got: %d want: %d", gotSrc.node, wantNodeIdx)
   375  			}
   376  		}
   377  	}
   378  }
   379  
   380  func TestReduceQConsistencyDirected(t *testing.T) {
   381  	for _, test := range communityDirectedQTests {
   382  		g := simple.NewDirectedGraph()
   383  		for u, e := range test.g {
   384  			// Add nodes that are not defined by an edge.
   385  			if g.Node(int64(u)) == nil {
   386  				g.AddNode(simple.Node(u))
   387  			}
   388  			for v := range e {
   389  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   390  			}
   391  		}
   392  
   393  		testReduceQConsistencyDirected(t, test, g)
   394  	}
   395  }
   396  
   397  func TestReduceQConsistencyWeightedDirected(t *testing.T) {
   398  	for _, test := range communityDirectedQTests {
   399  		g := simple.NewWeightedDirectedGraph(0, 0)
   400  		for u, e := range test.g {
   401  			// Add nodes that are not defined by an edge.
   402  			if g.Node(int64(u)) == nil {
   403  				g.AddNode(simple.Node(u))
   404  			}
   405  			for v := range e {
   406  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   407  			}
   408  		}
   409  
   410  		testReduceQConsistencyDirected(t, test, g)
   411  	}
   412  }
   413  
   414  func testReduceQConsistencyDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
   415  	for _, structure := range test.structures {
   416  		if math.IsNaN(structure.want) {
   417  			return
   418  		}
   419  
   420  		communities := make([][]graph.Node, len(structure.memberships))
   421  		for i, c := range structure.memberships {
   422  			for n := range c {
   423  				communities[i] = append(communities[i], simple.Node(n))
   424  			}
   425  			ordered.ByID(communities[i])
   426  		}
   427  
   428  		gQ := Q(g, communities, structure.resolution)
   429  		gQnull := Q(g, nil, 1)
   430  
   431  		cg0 := reduceDirected(g, nil)
   432  		cg0Qnull := Q(cg0, cg0.Structure(), 1)
   433  		if !scalar.EqualWithinAbsOrRel(gQnull, cg0Qnull, structure.tol, structure.tol) {
   434  			t.Errorf("disagreement between null Q from method: %v and function: %v", cg0Qnull, gQnull)
   435  		}
   436  		cg0Q := Q(cg0, communities, structure.resolution)
   437  		if !scalar.EqualWithinAbsOrRel(gQ, cg0Q, structure.tol, structure.tol) {
   438  			t.Errorf("unexpected Q result after initial reduction: got: %v want :%v", cg0Q, gQ)
   439  		}
   440  
   441  		cg1 := reduceDirected(cg0, communities)
   442  		cg1Q := Q(cg1, cg1.Structure(), structure.resolution)
   443  		if !scalar.EqualWithinAbsOrRel(gQ, cg1Q, structure.tol, structure.tol) {
   444  			t.Errorf("unexpected Q result after second reduction: got: %v want :%v", cg1Q, gQ)
   445  		}
   446  	}
   447  }
   448  
   449  type localDirectedMoveTest struct {
   450  	name       string
   451  	g          []intset
   452  	structures []moveStructures
   453  }
   454  
   455  var localDirectedMoveTests = []localDirectedMoveTest{
   456  	{
   457  		name: "blondel",
   458  		g:    blondel,
   459  		structures: []moveStructures{
   460  			{
   461  				memberships: []intset{
   462  					0: linksTo(0, 1, 2, 4, 5),
   463  					1: linksTo(3, 6, 7),
   464  					2: linksTo(8, 9, 10, 12, 14, 15),
   465  					3: linksTo(11, 13),
   466  				},
   467  				targetNodes: []graph.Node{simple.Node(0)},
   468  				resolution:  1,
   469  				tol:         1e-14,
   470  			},
   471  			{
   472  				memberships: []intset{
   473  					0: linksTo(0, 1, 2, 4, 5),
   474  					1: linksTo(3, 6, 7),
   475  					2: linksTo(8, 9, 10, 12, 14, 15),
   476  					3: linksTo(11, 13),
   477  				},
   478  				targetNodes: []graph.Node{simple.Node(3)},
   479  				resolution:  1,
   480  				tol:         1e-14,
   481  			},
   482  			{
   483  				memberships: []intset{
   484  					0: linksTo(0, 1, 2, 4, 5),
   485  					1: linksTo(3, 6, 7),
   486  					2: linksTo(8, 9, 10, 12, 14, 15),
   487  					3: linksTo(11, 13),
   488  				},
   489  				// Case to demonstrate when A_aa != k_a^𝛼.
   490  				targetNodes: []graph.Node{simple.Node(3), simple.Node(2)},
   491  				resolution:  1,
   492  				tol:         1e-14,
   493  			},
   494  		},
   495  	},
   496  }
   497  
   498  func TestMoveLocalDirected(t *testing.T) {
   499  	for _, test := range localDirectedMoveTests {
   500  		g := simple.NewDirectedGraph()
   501  		for u, e := range test.g {
   502  			// Add nodes that are not defined by an edge.
   503  			if g.Node(int64(u)) == nil {
   504  				g.AddNode(simple.Node(u))
   505  			}
   506  			for v := range e {
   507  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   508  			}
   509  		}
   510  
   511  		testMoveLocalDirected(t, test, g)
   512  	}
   513  }
   514  
   515  func TestMoveLocalWeightedDirected(t *testing.T) {
   516  	for _, test := range localDirectedMoveTests {
   517  		g := simple.NewWeightedDirectedGraph(0, 0)
   518  		for u, e := range test.g {
   519  			// Add nodes that are not defined by an edge.
   520  			if g.Node(int64(u)) == nil {
   521  				g.AddNode(simple.Node(u))
   522  			}
   523  			for v := range e {
   524  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   525  			}
   526  		}
   527  
   528  		testMoveLocalDirected(t, test, g)
   529  	}
   530  }
   531  
   532  func testMoveLocalDirected(t *testing.T, test localDirectedMoveTest, g graph.Directed) {
   533  	for _, structure := range test.structures {
   534  		communities := make([][]graph.Node, len(structure.memberships))
   535  		for i, c := range structure.memberships {
   536  			for n := range c {
   537  				communities[i] = append(communities[i], simple.Node(n))
   538  			}
   539  			ordered.ByID(communities[i])
   540  		}
   541  
   542  		r := reduceDirected(reduceDirected(g, nil), communities)
   543  
   544  		l := newDirectedLocalMover(r, r.communities, structure.resolution)
   545  		for _, n := range structure.targetNodes {
   546  			dQ, dst, src := l.deltaQ(n)
   547  			if dQ > 0 {
   548  				before := Q(r, l.communities, structure.resolution)
   549  				l.move(dst, src)
   550  				after := Q(r, l.communities, structure.resolution)
   551  				want := after - before
   552  				if !scalar.EqualWithinAbsOrRel(dQ, want, structure.tol, structure.tol) {
   553  					t.Errorf("unexpected deltaQ for %q: got: %v want: %v", test.name, dQ, want)
   554  				}
   555  			}
   556  		}
   557  	}
   558  }
   559  
   560  func TestModularizeDirected(t *testing.T) {
   561  	for _, test := range communityDirectedQTests {
   562  		g := simple.NewDirectedGraph()
   563  		for u, e := range test.g {
   564  			// Add nodes that are not defined by an edge.
   565  			if g.Node(int64(u)) == nil {
   566  				g.AddNode(simple.Node(u))
   567  			}
   568  			for v := range e {
   569  				g.SetEdge(simple.Edge{F: simple.Node(u), T: simple.Node(v)})
   570  			}
   571  		}
   572  
   573  		testModularizeDirected(t, test, g)
   574  	}
   575  }
   576  
   577  func TestModularizeWeightedDirected(t *testing.T) {
   578  	for _, test := range communityDirectedQTests {
   579  		g := simple.NewWeightedDirectedGraph(0, 0)
   580  		for u, e := range test.g {
   581  			// Add nodes that are not defined by an edge.
   582  			if g.Node(int64(u)) == nil {
   583  				g.AddNode(simple.Node(u))
   584  			}
   585  			for v := range e {
   586  				g.SetWeightedEdge(simple.WeightedEdge{F: simple.Node(u), T: simple.Node(v), W: 1})
   587  			}
   588  		}
   589  
   590  		testModularizeDirected(t, test, g)
   591  	}
   592  }
   593  
   594  func testModularizeDirected(t *testing.T, test communityDirectedQTest, g graph.Directed) {
   595  	const louvainIterations = 20
   596  
   597  	if test.structures[0].resolution != 1 {
   598  		panic("bad test: expect resolution=1")
   599  	}
   600  	want := make([][]graph.Node, len(test.structures[0].memberships))
   601  	for i, c := range test.structures[0].memberships {
   602  		for n := range c {
   603  			want[i] = append(want[i], simple.Node(n))
   604  		}
   605  		ordered.ByID(want[i])
   606  	}
   607  	ordered.BySliceIDs(want)
   608  
   609  	var (
   610  		got   *ReducedDirected
   611  		bestQ = math.Inf(-1)
   612  	)
   613  	// Modularize is randomised so we do this to
   614  	// ensure the level tests are consistent.
   615  	src := rand.New(rand.NewSource(1))
   616  	for i := 0; i < louvainIterations; i++ {
   617  		r := Modularize(g, 1, src).(*ReducedDirected)
   618  		if q := Q(r, nil, 1); q > bestQ || math.IsNaN(q) {
   619  			bestQ = q
   620  			got = r
   621  
   622  			if math.IsNaN(q) {
   623  				// Don't try again for non-connected case.
   624  				break
   625  			}
   626  		}
   627  
   628  		var qs []float64
   629  		for p := r; p != nil; p = p.Expanded().(*ReducedDirected) {
   630  			qs = append(qs, Q(p, nil, 1))
   631  		}
   632  
   633  		// Recovery of Q values is reversed.
   634  		if reverse(qs); !sort.Float64sAreSorted(qs) {
   635  			t.Errorf("Q values not monotonically increasing: %.5v", qs)
   636  		}
   637  	}
   638  
   639  	gotCommunities := got.Communities()
   640  	for _, c := range gotCommunities {
   641  		ordered.ByID(c)
   642  	}
   643  	ordered.BySliceIDs(gotCommunities)
   644  	if !reflect.DeepEqual(gotCommunities, want) {
   645  		t.Errorf("unexpected community membership for %s Q=%.4v:\n\tgot: %v\n\twant:%v",
   646  			test.name, bestQ, gotCommunities, want)
   647  		return
   648  	}
   649  
   650  	var levels []level
   651  	for p := got; p != nil; p = p.Expanded().(*ReducedDirected) {
   652  		var communities [][]graph.Node
   653  		if p.parent != nil {
   654  			communities = p.parent.Communities()
   655  			for _, c := range communities {
   656  				ordered.ByID(c)
   657  			}
   658  			ordered.BySliceIDs(communities)
   659  		} else {
   660  			communities = reduceDirected(g, nil).Communities()
   661  		}
   662  		q := Q(p, nil, 1)
   663  		if math.IsNaN(q) {
   664  			// Use an equalable flag value in place of NaN.
   665  			q = math.Inf(-1)
   666  		}
   667  		levels = append(levels, level{q: q, communities: communities})
   668  	}
   669  	if !reflect.DeepEqual(levels, test.wantLevels) {
   670  		t.Errorf("unexpected level structure:\n\tgot: %v\n\twant:%v", levels, test.wantLevels)
   671  	}
   672  }
   673  
   674  func TestNonContiguousDirected(t *testing.T) {
   675  	g := simple.NewDirectedGraph()
   676  	for _, e := range []simple.Edge{
   677  		{F: simple.Node(0), T: simple.Node(1)},
   678  		{F: simple.Node(4), T: simple.Node(5)},
   679  	} {
   680  		g.SetEdge(e)
   681  	}
   682  
   683  	func() {
   684  		defer func() {
   685  			r := recover()
   686  			if r != nil {
   687  				t.Error("unexpected panic with non-contiguous ID range")
   688  			}
   689  		}()
   690  		Modularize(g, 1, nil)
   691  	}()
   692  }
   693  
   694  func TestNonContiguousWeightedDirected(t *testing.T) {
   695  	g := simple.NewWeightedDirectedGraph(0, 0)
   696  	for _, e := range []simple.WeightedEdge{
   697  		{F: simple.Node(0), T: simple.Node(1), W: 1},
   698  		{F: simple.Node(4), T: simple.Node(5), W: 1},
   699  	} {
   700  		g.SetWeightedEdge(e)
   701  	}
   702  
   703  	func() {
   704  		defer func() {
   705  			r := recover()
   706  			if r != nil {
   707  				t.Error("unexpected panic with non-contiguous ID range")
   708  			}
   709  		}()
   710  		Modularize(g, 1, nil)
   711  	}()
   712  }
   713  
   714  func BenchmarkLouvainDirected(b *testing.B) {
   715  	src := rand.New(rand.NewSource(1))
   716  	for i := 0; i < b.N; i++ {
   717  		Modularize(dupGraphDirected, 1, src)
   718  	}
   719  }