Skip to content

Commit 94a53b3

Browse files
authored
Merge pull request #29 from elecbug/v0.8
Update v0.8.1
2 parents 0c1d6b2 + f11647f commit 94a53b3

31 files changed

Lines changed: 282 additions & 304 deletions

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ jobs:
2525
run: go build -v ./...
2626

2727
- name: Test
28-
run: go test -v ./...
28+
run: go test ./...

graph/algorithm/betweenness_centrality.go

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,15 @@ import (
55
"sync"
66

77
"github.com/elecbug/netkit/graph"
8-
"github.com/elecbug/netkit/graph/node"
98
)
109

1110
// BetweennessCentrality computes betweenness centrality using cached all shortest paths.
1211
// - Uses AllShortestPaths(g, cfg) (assumed cached/fast) to enumerate all shortest paths.
1312
// - For each pair (s,t), each interior node on a shortest path gets 1/|SP(s,t)| credit.
1413
// - Undirected graphs enqueue only i<j pairs (no double counting).
1514
// - Normalization matches NetworkX: undirected => 2/((n-1)(n-2)), directed => 1/((n-1)(n-2)).
16-
func BetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
17-
res := make(map[node.ID]float64)
15+
func BetweennessCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
16+
res := make(map[graph.NodeID]float64)
1817
if g == nil {
1918
return res
2019
}
@@ -46,30 +45,30 @@ func BetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
4645
}
4746

4847
// Use cached all-pairs shortest paths.
49-
// Type: map[node.ID]map[node.ID][]path.Path
48+
// Type: map[graph.NodeID]map[graph.NodeID][]path.Path
5049
all := AllShortestPaths(g, cfg)
5150

5251
// Build an index for stable iteration and pair generation.
53-
idxOf := make(map[node.ID]int, n)
52+
idxOf := make(map[graph.NodeID]int, n)
5453
for i, u := range ids {
5554
idxOf[u] = i
5655
}
5756

58-
type pair struct{ s, t node.ID }
57+
type pair struct{ s, t graph.NodeID }
5958
isUndirected := g.IsBidirectional()
6059

6160
// Generate all (s,t) jobs.
6261
jobs := make(chan pair, n)
6362
var wg sync.WaitGroup
6463

6564
// Global accumulator with lock; each worker keeps a local map to minimize contention.
66-
global := make(map[node.ID]float64, n)
65+
global := make(map[graph.NodeID]float64, n)
6766
var mu sync.Mutex
6867

6968
// Worker: consume pairs and accumulate contributions into a local map, then merge.
7069
workerFn := func() {
7170
defer wg.Done()
72-
local := make(map[node.ID]float64, n)
71+
local := make(map[graph.NodeID]float64, n)
7372

7473
for job := range jobs {
7574
row, ok := all[job.s]
@@ -84,7 +83,7 @@ func BetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
8483

8584
// For each shortest path s->...->t, every interior node gets 1/den.
8685
for _, pth := range pathsST {
87-
seq := pth.Nodes() // []node.ID; interior nodes are [1 : len-1)
86+
seq := pth.Nodes() // []graph.NodeID; interior nodes are [1 : len-1)
8887
if len(seq) <= 2 {
8988
continue // no interior node
9089
}

graph/algorithm/cache.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,30 @@ package algorithm
22

33
import (
44
"sync"
5+
"time"
56

6-
"github.com/elecbug/netkit/graph/path"
7+
"github.com/elecbug/netkit/graph"
78
)
89

9-
var cachedAllShortestPaths = make(map[string]path.GraphPaths)
10-
var cachedAllShortestPathLengths = make(map[string]path.PathLength)
10+
var cachedAllShortestPaths = make(map[string]graph.Paths)
11+
var cachedAllShortestPathLengths = make(map[string]graph.PathLength)
1112
var cacheMu sync.RWMutex
1213

1314
// CacheClear clears the cached shortest paths and their lengths.
1415
func CacheClear() {
1516
cacheMu.Lock()
1617
defer cacheMu.Unlock()
17-
cachedAllShortestPaths = make(map[string]path.GraphPaths)
18-
cachedAllShortestPathLengths = make(map[string]path.PathLength)
18+
cachedAllShortestPaths = make(map[string]graph.Paths)
19+
cachedAllShortestPathLengths = make(map[string]graph.PathLength)
20+
}
21+
22+
// AutoCacheClear starts a goroutine that clears the cache at regular intervals defined by tick.
23+
func AutoCacheClear(tick time.Duration) {
24+
go func() {
25+
for {
26+
time.Sleep(tick)
27+
28+
CacheClear()
29+
}
30+
}()
1931
}

graph/algorithm/closeness_centrality.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package algorithm
22

33
import (
44
"github.com/elecbug/netkit/graph"
5-
"github.com/elecbug/netkit/graph/node"
65
)
76

87
// ClosenessCentrality computes NetworkX-compatible closeness centrality.
@@ -17,8 +16,8 @@ import (
1716
// Requirements:
1817
// - AllShortestPaths(g, cfg) must respect directedness of g.
1918
// - cfg.Closeness.WfImproved follows NetworkX default (true) unless overridden.
20-
func ClosenessCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
21-
out := make(map[node.ID]float64)
19+
func ClosenessCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
20+
out := make(map[graph.NodeID]float64)
2221
if g == nil {
2322
return out
2423
}

graph/algorithm/clustering_coefficient.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@ import (
55
"sync"
66

77
"github.com/elecbug/netkit/graph"
8-
"github.com/elecbug/netkit/graph/node"
98
)
109

1110
// ClusteringCoefficientAll computes local clustering coefficients for all nodes.
1211
// - If g.IsBidirectional()==false (directed): Fagiolo (2007) directed clustering (matches NetworkX).
1312
// - If g.IsBidirectional()==true (undirected): standard undirected clustering.
14-
// Returns map[node.ID]float64 with a value for every node in g.
15-
func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
16-
res := make(map[node.ID]float64)
13+
// Returns map[graph.NodeID]float64 with a value for every node in g.
14+
func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
15+
res := make(map[graph.NodeID]float64)
1716
if g == nil {
1817
return res
1918
}
@@ -35,10 +34,10 @@ func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
3534
// Build helper structures
3635
// outNeighbors[v] = slice of out-neighbors of v (exclude self)
3736
// inNeighbors[v] = slice of in-neighbors of v (exclude self) - only needed for directed
38-
outNeighbors := make(map[node.ID][]node.ID, n)
37+
outNeighbors := make(map[graph.NodeID][]graph.NodeID, n)
3938
for _, v := range nodes {
4039
ns := g.Neighbors(v)
41-
buf := make([]node.ID, 0, len(ns))
40+
buf := make([]graph.NodeID, 0, len(ns))
4241
for _, w := range ns {
4342
if w != v {
4443
buf = append(buf, w)
@@ -48,9 +47,9 @@ func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
4847
}
4948

5049
isDirected := !g.IsBidirectional()
51-
var inNeighbors map[node.ID][]node.ID
50+
var inNeighbors map[graph.NodeID][]graph.NodeID
5251
if isDirected {
53-
inNeighbors = make(map[node.ID][]node.ID, n)
52+
inNeighbors = make(map[graph.NodeID][]graph.NodeID, n)
5453
for _, u := range nodes {
5554
for _, w := range outNeighbors[u] {
5655
// u -> w, so u is in-neighbor of w
@@ -59,13 +58,13 @@ func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
5958
}
6059
}
6160

62-
type job struct{ v node.ID }
61+
type job struct{ v graph.NodeID }
6362
jobs := make(chan job, workers*2)
6463
var wg sync.WaitGroup
6564
var mu sync.Mutex // protects res map
6665

6766
// Edge multiplicity for Fagiolo: b(u,v) = a_uv + a_vu ∈ {0,1,2}
68-
b := func(u, v node.ID) int {
67+
b := func(u, v graph.NodeID) int {
6968
sum := 0
7069

7170
if g.HasEdge(u, v) {
@@ -98,7 +97,7 @@ func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
9897
mu.Unlock()
9998
continue
10099
}
101-
outSet := make(map[node.ID]struct{}, kOut)
100+
outSet := make(map[graph.NodeID]struct{}, kOut)
102101
for _, w := range outNeighbors[v] {
103102
outSet[w] = struct{}{}
104103
}
@@ -119,15 +118,15 @@ func ClusteringCoefficient(g *graph.Graph, cfg *Config) map[node.ID]float64 {
119118

120119
// T_v = sum_{j != k} b(v,j) * b(j,k) * b(k,v)
121120
// with j,k in tot = in(v) ∪ out(v)
122-
totSet := make(map[node.ID]struct{}, kTot) // upper bound
121+
totSet := make(map[graph.NodeID]struct{}, kTot) // upper bound
123122
for _, u := range outNeighbors[v] {
124123
totSet[u] = struct{}{}
125124
}
126125
for _, u := range inNeighbors[v] {
127126
totSet[u] = struct{}{}
128127
}
129128
// Make a slice to iterate
130-
tot := make([]node.ID, 0, len(totSet))
129+
tot := make([]graph.NodeID, 0, len(totSet))
131130
for u := range totSet {
132131
if u != v { // guard (shouldn't be in set anyway)
133132
tot = append(tot, u)

graph/algorithm/degree_assortativity_coefficient.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"math"
77

88
"github.com/elecbug/netkit/graph"
9-
"github.com/elecbug/netkit/graph/node"
109
)
1110

1211
// DegreeAssortativityCoefficient computes Newman's degree assortativity coefficient (Pearson correlation)
@@ -54,16 +53,16 @@ func DegreeAssortativityCoefficient(g *graph.Graph, cfg *Config) float64 {
5453
}
5554

5655
// Build an index for upper-triangle filtering on undirected graphs.
57-
idxOf := make(map[node.ID]int, n)
56+
idxOf := make(map[graph.NodeID]int, n)
5857
for i, u := range ids {
5958
idxOf[u] = i
6059
}
6160

6261
// Degree caches
6362
// NOTE: Replace the neighbor getters with your graph API if needed.
64-
outDeg := make(map[node.ID]int, n)
65-
inDeg := make(map[node.ID]int, n)
66-
undeg := make(map[node.ID]int, n)
63+
outDeg := make(map[graph.NodeID]int, n)
64+
inDeg := make(map[graph.NodeID]int, n)
65+
undeg := make(map[graph.NodeID]int, n)
6766

6867
if isUndirected {
6968
for _, u := range ids {

graph/algorithm/degree_centrality.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"sync"
66

77
"github.com/elecbug/netkit/graph"
8-
"github.com/elecbug/netkit/graph/node"
98
)
109

1110
// DegreeCentralityConfig (suggested to be added inside your config package)
@@ -32,8 +31,8 @@ import (
3231
// - Undirected: deg(u)/(n-1).
3332
// - Directed (default "total"): (in(u)+out(u))/(n-1). Use "in"/"out" for the specific variants.
3433
// Self-loops are ignored for centrality.
35-
func DegreeCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
36-
res := make(map[node.ID]float64)
34+
func DegreeCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
35+
res := make(map[graph.NodeID]float64)
3736
if g == nil {
3837
return res
3938
}
@@ -55,7 +54,7 @@ func DegreeCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
5554
}
5655

5756
// --- indexing ---
58-
idxOf := make(map[node.ID]int, n)
57+
idxOf := make(map[graph.NodeID]int, n)
5958
for i, u := range ids {
6059
idxOf[u] = i
6160
}

graph/algorithm/edge_betweenness_centrality.go

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,9 @@ import (
55
"sync"
66

77
"github.com/elecbug/netkit/graph"
8-
"github.com/elecbug/netkit/graph/node"
98
)
109

11-
func makeEdgeKey(u, v node.ID, undirected bool) (node.ID, node.ID) {
10+
func makeEdgeKey(u, v graph.NodeID, undirected bool) (graph.NodeID, graph.NodeID) {
1211
if undirected && v < u {
1312
u, v = v, u
1413
}
@@ -34,11 +33,11 @@ func makeEdgeKey(u, v node.ID, undirected bool) (node.ID, node.ID) {
3433
// in Brandes accumulation (same practice as NetworkX).
3534
//
3635
// Returns:
37-
// - map[node.ID]map[node.ID]float64 where:
36+
// - map[graph.NodeID]map[graph.NodeID]float64 where:
3837
// - Undirected: key is canonical [min(u,v), max(u,v)]
3938
// - Directed: key is (u,v) ordered
40-
func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node.ID]float64 {
41-
out := make(map[node.ID]map[node.ID]float64)
39+
func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]map[graph.NodeID]float64 {
40+
out := make(map[graph.NodeID]map[graph.NodeID]float64)
4241
if g == nil {
4342
return out
4443
}
@@ -79,15 +78,15 @@ func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node
7978
u, v := makeEdgeKey(u, v, isUndirected)
8079

8180
if out[u] == nil {
82-
out[u] = make(map[node.ID]float64)
81+
out[u] = make(map[graph.NodeID]float64)
8382
}
8483

8584
out[u][v] = 0.0
8685
}
8786
}
8887

8988
// ----- worker pool over source nodes -----
90-
type job struct{ s node.ID }
89+
type job struct{ s graph.NodeID }
9190
jobs := make(chan job, n)
9291

9392
var mu sync.Mutex
@@ -97,16 +96,16 @@ func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node
9796
defer wg.Done()
9897

9998
// Local accumulator to reduce lock contention
100-
local := make(map[node.ID]map[node.ID]float64, 64)
99+
local := make(map[graph.NodeID]map[graph.NodeID]float64, 64)
101100

102101
for jb := range jobs {
103102
s := jb.s
104103

105104
// Brandes data structures
106-
stack := make([]node.ID, 0, n)
107-
preds := make(map[node.ID][]node.ID, n)
108-
sigma := make(map[node.ID]float64, n)
109-
dist := make(map[node.ID]int, n)
105+
stack := make([]graph.NodeID, 0, n)
106+
preds := make(map[graph.NodeID][]graph.NodeID, n)
107+
sigma := make(map[graph.NodeID]float64, n)
108+
dist := make(map[graph.NodeID]int, n)
110109

111110
for _, v := range ids {
112111
dist[v] = -1
@@ -115,7 +114,7 @@ func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node
115114
dist[s] = 0
116115

117116
// BFS (unweighted)
118-
q := []node.ID{s}
117+
q := []graph.NodeID{s}
119118
for len(q) > 0 {
120119
v := q[0]
121120
q = q[1:]
@@ -136,7 +135,7 @@ func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node
136135
}
137136

138137
// Dependency accumulation
139-
delta := make(map[node.ID]float64, n)
138+
delta := make(map[graph.NodeID]float64, n)
140139
for len(stack) > 0 {
141140
w := stack[len(stack)-1]
142141
stack = stack[:len(stack)-1]
@@ -150,7 +149,7 @@ func EdgeBetweennessCentrality(g *graph.Graph, cfg *Config) map[node.ID]map[node
150149
eu, ev := makeEdgeKey(v, w, isUndirected)
151150

152151
if local[eu] == nil {
153-
local[eu] = make(map[node.ID]float64)
152+
local[eu] = make(map[graph.NodeID]float64)
154153
}
155154

156155
local[eu][ev] += c

graph/algorithm/eigenvector_centrality.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"sync"
77

88
"github.com/elecbug/netkit/graph"
9-
"github.com/elecbug/netkit/graph/node"
109
)
1110

1211
// EigenvectorCentrality computes eigenvector centrality using parallel power iteration.
@@ -16,8 +15,8 @@ import (
1615
// Set Reverse=true to use successors/out-edges (right eigenvector).
1716
//
1817
// Unweighted edges are assumed. The result vector is L2-normalized (sum of squares == 1).
19-
func EigenvectorCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
20-
out := make(map[node.ID]float64)
18+
func EigenvectorCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
19+
out := make(map[graph.NodeID]float64)
2120
if g == nil {
2221
return out
2322
}
@@ -26,7 +25,7 @@ func EigenvectorCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
2625
maxIter := 100
2726
tol := 1e-6
2827
reverse := false
29-
var nstart *map[node.ID]float64
28+
var nstart *map[graph.NodeID]float64
3029
workers := runtime.NumCPU()
3130

3231
if cfg != nil {
@@ -54,7 +53,7 @@ func EigenvectorCentrality(g *graph.Graph, cfg *Config) map[node.ID]float64 {
5453
if n == 0 {
5554
return out
5655
}
57-
idxOf := make(map[node.ID]int, n)
56+
idxOf := make(map[graph.NodeID]int, n)
5857
for i, u := range ids {
5958
idxOf[u] = i
6059
}

0 commit comments

Comments
 (0)