// Whole-graph analytics: PageRank, weakly-connected components, community// detection by label propagation (CDLP), the local clustering coefficient// (LCC), and single-source shortest paths (SSSP) -- the LDBC Graphalytics// kernel set. Outputs are node-indexed slices over the CSR id space (index// == node id; sparse-id safe). directed selects the forward direction:// true follows Outgoing (a directed graph), false Both (an undirected one// whose rels are stored once).package chickpeasimport ()func fwd( bool) Direction {if {returnOutgoing }returnBoth}func ind( bool) Direction {if {returnIncoming }returnBoth}// SSSP is single-source shortest paths over forward rels with additive// weights from the weightKey rel property ("" = unit weights); unreachable// nodes get +Inf. Unit weights make every distance the hop count, so that// case runs the BFS -- a unit-weight Dijkstra pays a heap push and pop// plus map probes per relationship to rediscover the ordering a BFS queue// gives for free (the same dispatch the COST-constant path search takes).// A weight key that resolves no column also means unit weights.func ( *Snapshot) ( NodeID, bool, string) []float64 { := make([]float64, .CSRIDSpace())varWeightFnif != "" {if , := .RelCol(); { := .F64() = func( NodeID, RelRef) float64 {if , := .Get(.Pos); {return }return1 } } }if == nil { := .BFSDistances(, fwd(), MatchAll(), -1)for := range {if , := [NodeID()]; { [] = float64() } else { [] = math.Inf(1) } }return } := .Dijkstra(, fwd(), MatchAll(), )for := range {if , := .Distance(NodeID()); { [] = } else { [] = math.Inf(1) } }return}// WCC labels each node with the smallest node id in its weakly-connected// component, flooding undirected (Both) rels in ascending id sweep order.func ( *Snapshot) () []uint32 {return .wccFlood(MatchAll(), Both)}// WCCVia is connected components over only the m-matched rels, flooding in// dir (pass Both for the usual weakly-connected sense over a// stored-directed rel set, e.g. a reply forest). Nodes with no matching// rel are their own singleton component.func ( *Snapshot) ( RelMatch, Direction) []uint32 {return .wccFlood(, )}func ( *Snapshot) ( RelMatch, Direction) []uint32 { := uint32(.CSRIDSpace())const = ^uint32(0) := make([]uint32, )for := range { [] = }var []uint32for := uint32(0); < ; ++ {if [] != {continue } [] = = append([:0], )forlen() > 0 { := [len()-1] = [:len()-1]for := range .NeighborsMatch(, , ) {if [] == { [] = = append(, ) } } } }return}// PageRank runs iterations of synchronous pull updates with the given// damping: PR0(v) = 1/|V|, then PRi(v) = (1-d)/|V| + d*(sum over in-// neighbors of PRi-1(u)/outdeg(u) + the uniformly redistributed rank of// sinks).func ( *Snapshot) ( bool, float64, int) []float64 { := int(.CSRIDSpace())if == 0 {returnnil }// N is the number of REAL nodes: on a sparse id space the arrays // stay id-space-sized (raw-id indexing in bounds), but init, the // teleport/dangling base, and the dangling sum are confined to // existing nodes -- broadcasting base over gap ids both contaminates // real ranks and breaks mass conservation (the padded-space form // conserves exactly, but over the wrong N). Gap entries stay 0 and // the emission layer filters them anyway. Dense graphs are // arithmetically identical (existence covers the whole space). := float64(.nNodes)if == 0 {returnmake([]float64, ) } := func( int) bool { returntrue }if .existence != nil && int(.existence.Len()) < { = func( int) bool { return .existence.Contains(uint32()) } } , := fwd(), ind() := MatchAll() := make([]uint32, )parallel.For(, func(, int) {for := ; < ; ++ { := uint32(0)forrange .NeighborsMatch(NodeID(), , ) { ++ } [] = } }) := make([]float64, ) := make([]float64, )for := range {if () { [] = 1 / } }forrange { := 0.0for := range {if [] == 0 && () { += [] } } := (1-)/ + */parallel.For(, func(, int) {for := ; < ; ++ {if !() { [] = 0continue } := 0.0for := range .NeighborsMatch(NodeID(), , ) {if := []; > 0 { += [] / float64() } } [] = + * } }) , = , }return}// CDLP is community detection by synchronous label propagation: L0(v) = v,// then each node adopts the most frequent label among its neighbors// (in+out tallied separately for directed graphs, so a mutual rel counts// twice), smallest label breaking ties; a node with no neighbors keeps its// label.func ( *Snapshot) ( bool, int) []uint32 { := make([]uint32, .CSRIDSpace())for := range { [] = uint32() }return .CDLPSeeded(, , )}// CDLPSeeded is CDLP with explicit initial labels (init[node] = L0(node));// seed with original vertex ids to match a vertex-id-keyed reference.// Missing slots (a short init) default to the node's own id.func ( *Snapshot) ( bool, int, []uint32) []uint32 { := int(.CSRIDSpace()) := make([]uint32, )for := range {if < len() { [] = [] } else { [] = uint32() } } := make([]uint32, ) := MatchAll()forrange {parallel.For(, func(, int) {// One label-tally scratch per chunk, reused across its nodes.var []uint32for := ; < ; ++ { = [:0]if {for := range .NeighborsMatch(NodeID(), Outgoing, ) { = append(, []) }for := range .NeighborsMatch(NodeID(), Incoming, ) { = append(, []) } } else {for := range .NeighborsMatch(NodeID(), Both, ) { = append(, []) } } [] = mostFrequentLabel(, []) } }) , = , }return}// mostFrequentLabel is the most frequent value of buf (smallest on a tie,// via sort + run scan), or fallback when buf is empty.func mostFrequentLabel( []uint32, uint32) uint32 {iflen() == 0 {return }sort.Slice(, func(, int) bool { return [] < [] }) , := , 0for := 0; < len(); { := + 1for < len() && [] == [] { ++ }if - > { = - = [] } = }return}// LCC is the local clustering coefficient: for each node v with undirected// neighbor set N(v) (each neighbor once, self excluded), 0 if |N(v)| <= 1,// else the number of forward rels between members of N(v) over// |N(v)|*(|N(v)|-1).func ( *Snapshot) ( bool) []float64 { := int(.CSRIDSpace()) := fwd() := MatchAll()// Sorted forward adjacency, built once, so the triangle count probes // the smaller side of each intersection. := make([]uint32, +1)for := range { := uint32(0)forrange .NeighborsMatch(NodeID(), , ) { ++ } [+1] = [] + } := make([]uint32, [])for := range { := []for := range .NeighborsMatch(NodeID(), , ) { [] = ++ } := [[]:]sort.Slice(, func(, int) bool { return [] < [] }) } := make([]float64, )parallel.For(, func(, int) { := make([]uint64, (+63)/64)var []uint32for := ; < ; ++ { = [:0]for := range .NeighborsMatch(NodeID(), Both, ) { := [>>6]>>(&63)&1 != 0if != NodeID() && ! { [>>6] |= 1 << ( & 63) = append(, ) } } := len()if >= 2 { := falsefor , := range {ifint([+1]-[]) > { = truebreak } }if {sort.Slice(, func(, int) bool { return [] < [] }) } := uint64(0)for , := range { := [[]:[+1]]iflen() <= {for , := range {if != && [>>6]>>(&63)&1 != 0 { ++ } } } else { := 0for , := range {if == {continue } , := gallop(, , ) = if { ++ } } } } [] = float64() / (float64() * float64(-1)) }for , := range { [>>6] &^= 1 << ( & 63) } } })return}// gallop is exponential search for target in uo[from:]: whether present// and the index where it is/would be (always >= from); the cursor only// advances.func gallop( []uint32, int, uint32) (bool, int) { := len()if >= {returnfalse, } := 1for + < && [+] < { *= 2 } := + /2 := min(++1, ) := sort.Search(-, func( int) bool { return [+] >= })if + < && [+] == {returntrue, + }returnfalse, + }
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.