gonum.org/v1/gonum@v0.14.0/graph/community/louvain_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  	"fmt"
     9  
    10  	"golang.org/x/exp/rand"
    11  
    12  	"gonum.org/v1/gonum/graph"
    13  	"gonum.org/v1/gonum/graph/graphs/gen"
    14  	"gonum.org/v1/gonum/graph/simple"
    15  )
    16  
    17  // intset is an integer set.
    18  type intset map[int]struct{}
    19  
    20  func linksTo(i ...int) intset {
    21  	if len(i) == 0 {
    22  		return nil
    23  	}
    24  	s := make(intset)
    25  	for _, v := range i {
    26  		s[v] = struct{}{}
    27  	}
    28  	return s
    29  }
    30  
    31  type layer struct {
    32  	g          []intset
    33  	edgeWeight float64 // Zero edge weight is interpreted as 1.0.
    34  	weight     float64
    35  }
    36  
    37  var (
    38  	unconnected = []intset{ /* Nodes 0-4 are implicit .*/ 5: nil}
    39  
    40  	smallDumbell = []intset{
    41  		0: linksTo(1, 2),
    42  		1: linksTo(2),
    43  		2: linksTo(3),
    44  		3: linksTo(4, 5),
    45  		4: linksTo(5),
    46  		5: nil,
    47  	}
    48  	dumbellRepulsion = []intset{
    49  		0: linksTo(4),
    50  		1: linksTo(5),
    51  		2: nil,
    52  		3: nil,
    53  		4: nil,
    54  		5: nil,
    55  	}
    56  
    57  	repulsion = []intset{
    58  		0: linksTo(3, 4, 5),
    59  		1: linksTo(3, 4, 5),
    60  		2: linksTo(3, 4, 5),
    61  		3: linksTo(0, 1, 2),
    62  		4: linksTo(0, 1, 2),
    63  		5: linksTo(0, 1, 2),
    64  	}
    65  
    66  	simpleDirected = []intset{
    67  		0: linksTo(1),
    68  		1: linksTo(0, 4),
    69  		2: linksTo(1),
    70  		3: linksTo(0, 4),
    71  		4: linksTo(2),
    72  	}
    73  
    74  	// http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html
    75  	middleEast = struct{ friends, complicated, enemies []intset }{
    76  		// green cells
    77  		friends: []intset{
    78  			0:  nil,
    79  			1:  linksTo(5, 7, 9, 12),
    80  			2:  linksTo(11),
    81  			3:  linksTo(4, 5, 10),
    82  			4:  linksTo(3, 5, 10),
    83  			5:  linksTo(1, 3, 4, 8, 10, 12),
    84  			6:  nil,
    85  			7:  linksTo(1, 12),
    86  			8:  linksTo(5, 9, 11),
    87  			9:  linksTo(1, 8, 12),
    88  			10: linksTo(3, 4, 5),
    89  			11: linksTo(2, 8),
    90  			12: linksTo(1, 5, 7, 9),
    91  		},
    92  
    93  		// yellow cells
    94  		complicated: []intset{
    95  			0:  linksTo(2, 4),
    96  			1:  linksTo(4, 8),
    97  			2:  linksTo(0, 3, 4, 5, 8, 9),
    98  			3:  linksTo(2, 8, 11),
    99  			4:  linksTo(0, 1, 2, 8),
   100  			5:  linksTo(2),
   101  			6:  nil,
   102  			7:  linksTo(9, 11),
   103  			8:  linksTo(1, 2, 3, 4, 10, 12),
   104  			9:  linksTo(2, 7, 11),
   105  			10: linksTo(8),
   106  			11: linksTo(3, 7, 9, 12),
   107  			12: linksTo(8, 11),
   108  		},
   109  
   110  		// red cells
   111  		enemies: []intset{
   112  			0:  linksTo(1, 3, 5, 6, 7, 8, 9, 10, 11, 12),
   113  			1:  linksTo(0, 2, 3, 6, 10, 11),
   114  			2:  linksTo(1, 6, 7, 10, 12),
   115  			3:  linksTo(0, 1, 6, 7, 9, 12),
   116  			4:  linksTo(6, 7, 9, 11, 12),
   117  			5:  linksTo(0, 6, 7, 9, 11),
   118  			6:  linksTo(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12),
   119  			7:  linksTo(0, 2, 3, 4, 5, 6, 8, 10),
   120  			8:  linksTo(0, 6, 7),
   121  			9:  linksTo(0, 3, 4, 5, 6, 10),
   122  			10: linksTo(0, 1, 2, 6, 7, 9, 11, 12),
   123  			11: linksTo(0, 1, 4, 5, 6, 10),
   124  			12: linksTo(0, 2, 3, 4, 6, 10),
   125  		},
   126  	}
   127  
   128  	// W. W. Zachary, An information flow model for conflict and fission in small groups,
   129  	// Journal of Anthropological Research 33, 452-473 (1977).
   130  	//
   131  	// The edge list here is constructed such that all link descriptions
   132  	// head from a node with lower Page Rank to a node with higher Page
   133  	// Rank. This has no impact on undirected tests, but allows a sensible
   134  	// view for directed tests.
   135  	zachary = []intset{
   136  		0:  nil,                     // rank=0.097
   137  		1:  linksTo(0, 2),           // rank=0.05288
   138  		2:  linksTo(0, 32),          // rank=0.05708
   139  		3:  linksTo(0, 1, 2),        // rank=0.03586
   140  		4:  linksTo(0, 6, 10),       // rank=0.02198
   141  		5:  linksTo(0, 6),           // rank=0.02911
   142  		6:  linksTo(0, 5),           // rank=0.02911
   143  		7:  linksTo(0, 1, 2, 3),     // rank=0.02449
   144  		8:  linksTo(0, 2, 32, 33),   // rank=0.02977
   145  		9:  linksTo(2, 33),          // rank=0.01431
   146  		10: linksTo(0, 5),           // rank=0.02198
   147  		11: linksTo(0),              // rank=0.009565
   148  		12: linksTo(0, 3),           // rank=0.01464
   149  		13: linksTo(0, 1, 2, 3, 33), // rank=0.02954
   150  		14: linksTo(32, 33),         // rank=0.01454
   151  		15: linksTo(32, 33),         // rank=0.01454
   152  		16: linksTo(5, 6),           // rank=0.01678
   153  		17: linksTo(0, 1),           // rank=0.01456
   154  		18: linksTo(32, 33),         // rank=0.01454
   155  		19: linksTo(0, 1, 33),       // rank=0.0196
   156  		20: linksTo(32, 33),         // rank=0.01454
   157  		21: linksTo(0, 1),           // rank=0.01456
   158  		22: linksTo(32, 33),         // rank=0.01454
   159  		23: linksTo(32, 33),         // rank=0.03152
   160  		24: linksTo(27, 31),         // rank=0.02108
   161  		25: linksTo(23, 24, 31),     // rank=0.02101
   162  		26: linksTo(29, 33),         // rank=0.01504
   163  		27: linksTo(2, 23, 33),      // rank=0.02564
   164  		28: linksTo(2, 31, 33),      // rank=0.01957
   165  		29: linksTo(23, 32, 33),     // rank=0.02629
   166  		30: linksTo(1, 8, 32, 33),   // rank=0.02459
   167  		31: linksTo(0, 32, 33),      // rank=0.03716
   168  		32: linksTo(33),             // rank=0.07169
   169  		33: nil,                     // rank=0.1009
   170  	}
   171  
   172  	// doi:10.1088/1742-5468/2008/10/P10008 figure 1
   173  	//
   174  	// The edge list here is constructed such that all link descriptions
   175  	// head from a node with lower Page Rank to a node with higher Page
   176  	// Rank. This has no impact on undirected tests, but allows a sensible
   177  	// view for directed tests.
   178  	blondel = []intset{
   179  		0:  linksTo(2),           // rank=0.06858
   180  		1:  linksTo(2, 4, 7),     // rank=0.05264
   181  		2:  nil,                  // rank=0.08249
   182  		3:  linksTo(0, 7),        // rank=0.03884
   183  		4:  linksTo(0, 2, 10),    // rank=0.06754
   184  		5:  linksTo(0, 2, 7, 11), // rank=0.06738
   185  		6:  linksTo(2, 7, 11),    // rank=0.0528
   186  		7:  nil,                  // rank=0.07008
   187  		8:  linksTo(10),          // rank=0.09226
   188  		9:  linksTo(8),           // rank=0.05821
   189  		10: nil,                  // rank=0.1035
   190  		11: linksTo(8, 10),       // rank=0.08538
   191  		12: linksTo(9, 10),       // rank=0.04052
   192  		13: linksTo(10, 11),      // rank=0.03855
   193  		14: linksTo(8, 9, 10),    // rank=0.05621
   194  		15: linksTo(8),           // rank=0.02506
   195  	}
   196  )
   197  
   198  type structure struct {
   199  	resolution  float64
   200  	memberships []intset
   201  	want, tol   float64
   202  }
   203  
   204  type level struct {
   205  	q           float64
   206  	communities [][]graph.Node
   207  }
   208  
   209  type moveStructures struct {
   210  	memberships []intset
   211  	targetNodes []graph.Node
   212  
   213  	resolution float64
   214  	tol        float64
   215  }
   216  
   217  func reverse(f []float64) {
   218  	for i, j := 0, len(f)-1; i < j; i, j = i+1, j-1 {
   219  		f[i], f[j] = f[j], f[i]
   220  	}
   221  }
   222  
   223  func hasNegative(f []float64) bool {
   224  	for _, v := range f {
   225  		if v < 0 {
   226  			return true
   227  		}
   228  	}
   229  	return false
   230  }
   231  
   232  var (
   233  	dupGraph         = simple.NewUndirectedGraph()
   234  	dupGraphDirected = simple.NewDirectedGraph()
   235  )
   236  
   237  func init() {
   238  	err := gen.Duplication(dupGraph, 1000, 0.8, 0.1, 0.5, rand.New(rand.NewSource(1)))
   239  	if err != nil {
   240  		panic(err)
   241  	}
   242  
   243  	// Construct a directed graph from dupGraph
   244  	// such that every edge dupGraph is replaced
   245  	// with an edge that flows from the low node
   246  	// ID to the high node ID.
   247  	for _, e := range graph.EdgesOf(dupGraph.Edges()) {
   248  		if e.To().ID() < e.From().ID() {
   249  			se := e.(simple.Edge)
   250  			se.F, se.T = se.T, se.F
   251  			e = se
   252  		}
   253  		dupGraphDirected.SetEdge(e)
   254  	}
   255  }
   256  
   257  // This init function checks the Middle East relationship data.
   258  func init() {
   259  	world := make([]intset, len(middleEast.friends))
   260  	for i := range world {
   261  		world[i] = make(intset)
   262  	}
   263  	for _, relationships := range [][]intset{middleEast.friends, middleEast.complicated, middleEast.enemies} {
   264  		for i, rel := range relationships {
   265  			for inter := range rel {
   266  				if _, ok := world[i][inter]; ok {
   267  					panic(fmt.Sprintf("unexpected relationship: %v--%v", i, inter))
   268  				}
   269  				world[i][inter] = struct{}{}
   270  			}
   271  		}
   272  	}
   273  	for i := range world {
   274  		if len(world[i]) != len(middleEast.friends)-1 {
   275  			panic(fmt.Sprintf("missing relationship in %v: %v", i, world[i]))
   276  		}
   277  	}
   278  }