github.com/gopherd/gonum@v0.0.4/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 "math/rand" 11 12 "github.com/gopherd/gonum/graph" 13 "github.com/gopherd/gonum/graph/graphs/gen" 14 "github.com/gopherd/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 }