// Copyright ©2015 The Gonum Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package community import ( "fmt" "golang.org/x/exp/rand" "gonum.org/v1/gonum/graph" "gonum.org/v1/gonum/graph/graphs/gen" "gonum.org/v1/gonum/graph/simple" ) // intset is an integer set. type intset map[int]struct{} func linksTo(i ...int) intset { if len(i) == 0 { return nil } s := make(intset) for _, v := range i { s[v] = struct{}{} } return s } type layer struct { g []intset edgeWeight float64 // Zero edge weight is interpreted as 1.0. weight float64 } var ( unconnected = []intset{ /* Nodes 0-4 are implicit .*/ 5: nil} smallDumbell = []intset{ 0: linksTo(1, 2), 1: linksTo(2), 2: linksTo(3), 3: linksTo(4, 5), 4: linksTo(5), 5: nil, } dumbellRepulsion = []intset{ 0: linksTo(4), 1: linksTo(5), 2: nil, 3: nil, 4: nil, 5: nil, } repulsion = []intset{ 0: linksTo(3, 4, 5), 1: linksTo(3, 4, 5), 2: linksTo(3, 4, 5), 3: linksTo(0, 1, 2), 4: linksTo(0, 1, 2), 5: linksTo(0, 1, 2), } simpleDirected = []intset{ 0: linksTo(1), 1: linksTo(0, 4), 2: linksTo(1), 3: linksTo(0, 4), 4: linksTo(2), } // http://www.slate.com/blogs/the_world_/2014/07/17/the_middle_east_friendship_chart.html middleEast = struct{ friends, complicated, enemies []intset }{ // green cells friends: []intset{ 0: nil, 1: linksTo(5, 7, 9, 12), 2: linksTo(11), 3: linksTo(4, 5, 10), 4: linksTo(3, 5, 10), 5: linksTo(1, 3, 4, 8, 10, 12), 6: nil, 7: linksTo(1, 12), 8: linksTo(5, 9, 11), 9: linksTo(1, 8, 12), 10: linksTo(3, 4, 5), 11: linksTo(2, 8), 12: linksTo(1, 5, 7, 9), }, // yellow cells complicated: []intset{ 0: linksTo(2, 4), 1: linksTo(4, 8), 2: linksTo(0, 3, 4, 5, 8, 9), 3: linksTo(2, 8, 11), 4: linksTo(0, 1, 2, 8), 5: linksTo(2), 6: nil, 7: linksTo(9, 11), 8: linksTo(1, 2, 3, 4, 10, 12), 9: linksTo(2, 7, 11), 10: linksTo(8), 11: linksTo(3, 7, 9, 12), 12: linksTo(8, 11), }, // red cells enemies: []intset{ 0: linksTo(1, 3, 5, 6, 7, 8, 9, 10, 11, 12), 1: linksTo(0, 2, 3, 6, 10, 11), 2: linksTo(1, 6, 7, 10, 12), 3: linksTo(0, 1, 6, 7, 9, 12), 4: linksTo(6, 7, 9, 11, 12), 5: linksTo(0, 6, 7, 9, 11), 6: linksTo(0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12), 7: linksTo(0, 2, 3, 4, 5, 6, 8, 10), 8: linksTo(0, 6, 7), 9: linksTo(0, 3, 4, 5, 6, 10), 10: linksTo(0, 1, 2, 6, 7, 9, 11, 12), 11: linksTo(0, 1, 4, 5, 6, 10), 12: linksTo(0, 2, 3, 4, 6, 10), }, } // W. W. Zachary, An information flow model for conflict and fission in small groups, // Journal of Anthropological Research 33, 452-473 (1977). // // The edge list here is constructed such that all link descriptions // head from a node with lower Page Rank to a node with higher Page // Rank. This has no impact on undirected tests, but allows a sensible // view for directed tests. zachary = []intset{ 0: nil, // rank=0.097 1: linksTo(0, 2), // rank=0.05288 2: linksTo(0, 32), // rank=0.05708 3: linksTo(0, 1, 2), // rank=0.03586 4: linksTo(0, 6, 10), // rank=0.02198 5: linksTo(0, 6), // rank=0.02911 6: linksTo(0, 5), // rank=0.02911 7: linksTo(0, 1, 2, 3), // rank=0.02449 8: linksTo(0, 2, 32, 33), // rank=0.02977 9: linksTo(2, 33), // rank=0.01431 10: linksTo(0, 5), // rank=0.02198 11: linksTo(0), // rank=0.009565 12: linksTo(0, 3), // rank=0.01464 13: linksTo(0, 1, 2, 3, 33), // rank=0.02954 14: linksTo(32, 33), // rank=0.01454 15: linksTo(32, 33), // rank=0.01454 16: linksTo(5, 6), // rank=0.01678 17: linksTo(0, 1), // rank=0.01456 18: linksTo(32, 33), // rank=0.01454 19: linksTo(0, 1, 33), // rank=0.0196 20: linksTo(32, 33), // rank=0.01454 21: linksTo(0, 1), // rank=0.01456 22: linksTo(32, 33), // rank=0.01454 23: linksTo(32, 33), // rank=0.03152 24: linksTo(27, 31), // rank=0.02108 25: linksTo(23, 24, 31), // rank=0.02101 26: linksTo(29, 33), // rank=0.01504 27: linksTo(2, 23, 33), // rank=0.02564 28: linksTo(2, 31, 33), // rank=0.01957 29: linksTo(23, 32, 33), // rank=0.02629 30: linksTo(1, 8, 32, 33), // rank=0.02459 31: linksTo(0, 32, 33), // rank=0.03716 32: linksTo(33), // rank=0.07169 33: nil, // rank=0.1009 } // doi:10.1088/1742-5468/2008/10/P10008 figure 1 // // The edge list here is constructed such that all link descriptions // head from a node with lower Page Rank to a node with higher Page // Rank. This has no impact on undirected tests, but allows a sensible // view for directed tests. blondel = []intset{ 0: linksTo(2), // rank=0.06858 1: linksTo(2, 4, 7), // rank=0.05264 2: nil, // rank=0.08249 3: linksTo(0, 7), // rank=0.03884 4: linksTo(0, 2, 10), // rank=0.06754 5: linksTo(0, 2, 7, 11), // rank=0.06738 6: linksTo(2, 7, 11), // rank=0.0528 7: nil, // rank=0.07008 8: linksTo(10), // rank=0.09226 9: linksTo(8), // rank=0.05821 10: nil, // rank=0.1035 11: linksTo(8, 10), // rank=0.08538 12: linksTo(9, 10), // rank=0.04052 13: linksTo(10, 11), // rank=0.03855 14: linksTo(8, 9, 10), // rank=0.05621 15: linksTo(8), // rank=0.02506 } ) type structure struct { resolution float64 memberships []intset want, tol float64 } type level struct { q float64 communities [][]graph.Node } type moveStructures struct { memberships []intset targetNodes []graph.Node resolution float64 tol float64 } func hasNegative(f []float64) bool { for _, v := range f { if v < 0 { return true } } return false } var ( dupGraph = simple.NewUndirectedGraph() dupGraphDirected = simple.NewDirectedGraph() ) func init() { err := gen.Duplication(dupGraph, 1000, 0.8, 0.1, 0.5, rand.New(rand.NewSource(1))) if err != nil { panic(err) } // Construct a directed graph from dupGraph // such that every edge dupGraph is replaced // with an edge that flows from the low node // ID to the high node ID. for _, e := range graph.EdgesOf(dupGraph.Edges()) { if e.To().ID() < e.From().ID() { se := e.(simple.Edge) se.F, se.T = se.T, se.F e = se } dupGraphDirected.SetEdge(e) } } // This init function checks the Middle East relationship data. func init() { world := make([]intset, len(middleEast.friends)) for i := range world { world[i] = make(intset) } for _, relationships := range [][]intset{middleEast.friends, middleEast.complicated, middleEast.enemies} { for i, rel := range relationships { for inter := range rel { if _, ok := world[i][inter]; ok { panic(fmt.Sprintf("unexpected relationship: %v--%v", i, inter)) } world[i][inter] = struct{}{} } } } for i := range world { if len(world[i]) != len(middleEast.friends)-1 { panic(fmt.Sprintf("missing relationship in %v: %v", i, world[i])) } } }