// Neighborhood aggregation and co-occurrence kernels: forest roots of a// functional rel chain (RootsVia/RootVia/NeighborVia), one-mode network// folding (FoldVia), seeded co-occurrence (CoOccurring), and neighbor /// common-neighbor histograms (NeighborCounts, CommonNeighbors,// CommonNeighborCounts). NeighborGroups lives in neighborgroups.go.package chickpeasimport ()// RootsVia is a built forest-root array: index it by node id for the// terminal of that node's functional-relationship chain. Shared and// immutable -- hot loops hold it and index lock-free.typeRootsVia []NodeID// NoNeighbor is the sentinel a NeighborVia array holds for a node with no// such neighbor.constNoNeighbor = NodeID(^uint32(0))type rootsKey struct { dir Direction t RelType}// RootsVia is the per-node forest-root array for the functional relType// chain in dir: roots[node] is the node reached by following the single// relType rel until one with no such rel (a terminal node maps to itself).// Built once per (direction, type) with path compression, then cached --// call once and index the slice, rather than RootVia per node. Intended// for a rel that is functional in dir; malformed data (multiple such rels,// or a cycle) follows the first neighbor in CSR order and is broken by a// depth cap, resolving deterministically.func ( *Snapshot) ( RelType, Direction) RootsVia { := rootsKey{dir: , t: } .rootsMu.Lock() , := .rootsViaIndex[] .rootsMu.Unlock()if {return }// Build outside the lock; a racing build is discarded (identical). := .buildRootsVia(, ) .rootsMu.Lock()defer .rootsMu.Unlock()if , := .rootsViaIndex[]; {return } .rootsViaIndex[] = return}// RootVia is the terminal of node's functional relType chain in dir (a// terminal node maps to itself) -- a convenience over RootsVia; in a hot// loop index the array instead.func ( *Snapshot) ( NodeID, RelType, Direction) NodeID { := .RootsVia(, )ifint() < len() {return [] }return}// terminalKey keys the terminal-exclusivity cache.type terminalKey struct { dir Direction t RelType l Label}// ChainCollapseVia reports whether an unbounded zero-minimum// reachable-set expansion over relType in dir, filtered to label, is// equivalent to one RootsVia lookup -- and returns the root array when it// is. Two structural facts, each verified (never assumed) and cached:// the type must be FUNCTIONAL in dir (every node has at most one such// rel, so the reachable set is exactly the ancestor chain), and label// must be TERMINAL-EXCLUSIVE for it (no labeled node has such a rel// outgoing in dir, so only a chain's terminal can carry the label --// making "reachable nodes with the label" either the root or nothing).func ( *Snapshot) ( string, Direction, string) (RootsVia, bool) {if == Both {returnnil, false } , := .RelType()if ! {returnnil, false } , := .Label()if ! {returnnil, false }if !.functionalCached(, ) {returnnil, false } := terminalKey{dir: , t: , l: } .rootsMu.Lock() , := .terminalOnly[] .rootsMu.Unlock()if ! { = .checkTerminalOnly(, , ) .rootsMu.Lock() .terminalOnly[] = .rootsMu.Unlock() }if ! {returnnil, false }return .RootsVia(, ), true}// FunctionalVia reports whether relType is functional in dir -- every// node has at most one such rel, so its reachability structure is a// forest of chains (RootsVia's precondition). Verified once and cached.func ( *Snapshot) ( string, Direction) bool {if == Both {returnfalse } , := .RelType()if ! {returnfalse }return .functionalCached(, )}// functionalCached is FunctionalVia's verify-once core.func ( *Snapshot) ( RelType, Direction) bool { .rootsMu.Lock() , := .functionalVia[rootsKey{dir: , t: }] .rootsMu.Unlock()if {return } = .checkFunctionalVia(, ) .rootsMu.Lock() .functionalVia[rootsKey{dir: , t: }] = .rootsMu.Unlock()return}// checkFunctionalVia verifies every node has at most one relType rel in// dir. One CSR pass.func ( *Snapshot) ( RelType, Direction) bool { , := .outOffsets, .outTypesif == Incoming { , = .inOffsets, .inTypes }for := 0; +1 < len(); ++ { := 0for := []; < [+1]; ++ {if [] == { ++if > 1 {returnfalse } } } }returntrue}// checkTerminalOnly verifies no l-labeled node has a relType rel// outgoing in dir. One pass over the label's members.func ( *Snapshot) ( RelType, Direction, Label) bool { , := .labelIndex[]if ! {returntrue } , := .outOffsets, .outTypesif == Incoming { , = .inOffsets, .inTypes }for := range .Iter() {ifint()+1 >= len() {continue }for := []; < [+1]; ++ {if [] == {returnfalse } } }returntrue}// NeighborVia is the single neighbor each node reaches via the functional// relType in dir (one hop -- e.g. a message's hasCreator); the depth-1// sibling of RootsVia. Node-indexed; a node with no such neighbor maps to// NoNeighbor. Built fresh, not cached.func ( *Snapshot) ( RelType, Direction) RootsVia { := int(.CSRIDSpace()) := MatchType() := make(RootsVia, )for := rangeNodeID() { [] = NoNeighborfor := range .NeighborsMatch(, , ) { [] = break } }return}// buildRootsVia resolves every node's chain terminal once via path// compression; a depth cap of the id-space size breaks any malformed// cycle, so the build always terminates deterministically.func ( *Snapshot) ( Direction, RelType) RootsVia { := int(.CSRIDSpace()) := MatchType()const = NodeID(^uint32(0)) := make(RootsVia, )for := range { [] = }var []NodeIDfor := rangeNodeID() {if [] != {continue } = [:0] := varNodeIDfor {if := []; != { = break } , := .FirstNeighborMatch(, , )// path length <= n bounds a malformed cycle (a valid chain has // at most n-1 rels, so this never trips on a forest).if ! || len() > { = break } = append(, ) = }for , := range { [] = } [] = }return}// FirstNeighborMatch is FirstNeighbor over a pre-resolved RelMatch.func ( *Snapshot) ( NodeID, Direction, RelMatch) (NodeID, bool) {for := range .NeighborsMatch(, , ) {return , true }return0, false}// NodePair is an unordered node pair (Lo <= Hi), the key of FoldVia.typeNodePairstruct { Lo, Hi NodeID}// FoldVia folds the m-matched rels (in dir) into a weighted node-pair map// by projecting both endpoints of each rel through projection -- the// one-mode / bipartite projection ("network folding") of a relation onto a// derived node set. For every matched rel a -> b, a' = projection[a] and// b' = projection[b] add one to the unordered pair count; self-pairs and// endpoints projecting to NoNeighbor are skipped. projection is a flat// node -> node array (a NeighborVia or RootsVia). Runs in parallel over// the id space, per-chunk maps merged small-into-large.func ( *Snapshot) ( RelMatch, Direction, []NodeID) map[NodePair]uint64 { := int(.CSRIDSpace())returnparallel.Fold(,func() map[NodePair]uint64 { returnmap[NodePair]uint64{} },func( map[NodePair]uint64, int) map[NodePair]uint64 {if >= len() {return } := []if == NoNeighbor {return }for := range .NeighborsMatch(NodeID(), , ) {ifint() >= len() {continue } := []if == NoNeighbor || == {continue } := NodePair{Lo: min(, ), Hi: max(, )} []++ }return },func(, map[NodePair]uint64) map[NodePair]uint64 {iflen() < len() { , = , }for , := range { [] += }return })}// CoWeight selects how CoOccurring accumulates each co-occurring node's// weight (declarative -- the kernel needs no per-element callback).typeCoWeightstruct { distinct bool distinctKey string}// CoCount weights by the number of shared centers (co-occurrence events).func () CoWeight {returnCoWeight{}}// CoDistinct weights by the number of distinct values of the property key// read off each shared center (e.g. distinct co-occurrence days); a center// lacking the key contributes nothing.func ( string) CoWeight {returnCoWeight{distinct: true, distinctKey: }}// CoOccurring is seeded co-occurrence -- one-mode projection by shared// neighbor: from seed over the m-matched rels, the nodes sharing a// neighbor with seed (seed -(m,dir)-> centers -(m,reversed)-> others),// seed itself excluded, each weighted per w. The seeded row of the// node-node co-occurrence matrix; the by-shared-neighbor complement of// FoldVia's by-rel-endpoint projection.func ( *Snapshot) ( NodeID, RelMatch, Direction, CoWeight) map[NodeID]uint64 { := .Reverse()if !.distinct { := map[NodeID]uint64{}for := range .NeighborsMatch(, , ) {for := range .NeighborsMatch(, , ) {if != { []++ } } }return } , := .PropertyKey(.distinctKey)if ! {returnmap[NodeID]uint64{} } , := .columns[]if ! {returnmap[NodeID]uint64{} } := map[NodeID]map[Value]struct{}{}for := range .NeighborsMatch(, , ) { , := .Get()if ! {continue }for := range .NeighborsMatch(, , ) {if == {continue } , := []if ! { = map[Value]struct{}{} [] = } [] = struct{}{} } } := make(map[NodeID]uint64, len())for , := range { [] = uint64(len()) }return}
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.