Skip to content

Commit 2b8986d

Browse files
authored
Merge pull request #23 from elecbug/v0.6
Update v0.7.0
2 parents 2db4d74 + 050d554 commit 2b8986d

14 files changed

Lines changed: 514 additions & 136 deletions

File tree

netkit.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,2 @@
1-
// Package netkit provides common types and utilities used across this
2-
// repository.
1+
// Package netkit provides common types and utilities used across this repository.
32
package netkit
4-
5-
// Any is an alias for interface{} for convenience.
6-
type Any = interface{}
Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,2 @@
11
// Package algorithm provides graph algorithms for network analysis.
22
package algorithm
3-
4-
import (
5-
"github.com/elecbug/netkit/network-graph/algorithm/config"
6-
"github.com/elecbug/netkit/network-graph/graph"
7-
)
8-
9-
// MetricType represents the type of metric to be calculated.
10-
type MetricType int
11-
12-
// Metric types for graph analysis.
13-
const (
14-
BETWEENNESS_CENTRALITY MetricType = iota
15-
CLOSENESS_CENTRALITY
16-
CLUSTERING_COEFFICIENT
17-
DEGREE_ASSORTATIVITY_COEFFICIENT
18-
DEGREE_CENTRALITY
19-
DIAMETER
20-
EDGE_BETWEENNESS_CENTRALITY
21-
EIGENVECTOR_CENTRALITY
22-
MODULARITY
23-
PAGE_RANK
24-
SHORTEST_PATHS
25-
)
26-
27-
// Metric calculates the specified metric for the given graph.
28-
func Metric(g *graph.Graph, cfg *config.Config, metricType MetricType) any {
29-
switch metricType {
30-
case BETWEENNESS_CENTRALITY:
31-
return BetweennessCentrality(g, cfg)
32-
case CLOSENESS_CENTRALITY:
33-
return ClosenessCentrality(g, cfg)
34-
case CLUSTERING_COEFFICIENT:
35-
return ClusteringCoefficient(g, cfg)
36-
case DEGREE_ASSORTATIVITY_COEFFICIENT:
37-
return DegreeAssortativityCoefficient(g, cfg)
38-
case DEGREE_CENTRALITY:
39-
return DegreeCentrality(g, cfg)
40-
case DIAMETER:
41-
return Diameter(g, cfg)
42-
case EDGE_BETWEENNESS_CENTRALITY:
43-
return EdgeBetweennessCentrality(g, cfg)
44-
case EIGENVECTOR_CENTRALITY:
45-
return EigenvectorCentrality(g, cfg)
46-
case MODULARITY:
47-
return Modularity(g, cfg)
48-
case PAGE_RANK:
49-
return PageRank(g, cfg)
50-
case SHORTEST_PATHS:
51-
return AllShortestPaths(g, cfg)
52-
default:
53-
return nil
54-
}
55-
}

network-graph/graph/edge.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func (g *Graph) AddEdge(from, to node.ID) error {
2525

2626
g.edges[from][to] = true
2727

28-
if g.bidirectional {
28+
if g.isUndirected {
2929
if _, ok := g.edges[to]; !ok {
3030
g.edges[to] = make(map[node.ID]bool)
3131
}
@@ -52,7 +52,7 @@ func (g *Graph) RemoveEdge(from, to node.ID) error {
5252

5353
delete(g.edges[from], to)
5454

55-
if g.bidirectional {
55+
if g.isUndirected {
5656
if _, ok := g.edges[to]; !ok {
5757
g.edges[to] = make(map[node.ID]bool)
5858
}

network-graph/graph/graph.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ import (
1212

1313
// Graph maintains nodes and adjacency edges.
1414
type Graph struct {
15-
nodes map[node.ID]bool
16-
edges map[node.ID]map[node.ID]bool
17-
bidirectional bool
15+
nodes map[node.ID]bool
16+
edges map[node.ID]map[node.ID]bool
17+
isUndirected bool
1818
}
1919

2020
// New creates and returns an empty Graph.
21-
func New(bidirectional bool) *Graph {
21+
func New(isUndirected bool) *Graph {
2222
return &Graph{
23-
nodes: make(map[node.ID]bool),
24-
edges: make(map[node.ID]map[node.ID]bool),
25-
bidirectional: bidirectional,
23+
nodes: make(map[node.ID]bool),
24+
edges: make(map[node.ID]map[node.ID]bool),
25+
isUndirected: isUndirected,
2626
}
2727
}
2828

@@ -59,7 +59,7 @@ func Save(g *Graph) (string, error) {
5959
return "", fmt.Errorf("failed to marshal edges: %v", err)
6060
}
6161

62-
bidirectional, err := json.Marshal(g.bidirectional)
62+
bidirectional, err := json.Marshal(g.isUndirected)
6363

6464
if err != nil {
6565
return "", fmt.Errorf("failed to marshal bidirectional: %v", err)
@@ -92,15 +92,21 @@ func Load(data string) (*Graph, error) {
9292
}
9393

9494
return &Graph{
95-
nodes: nodes,
96-
edges: edges,
97-
bidirectional: bidirectional,
95+
nodes: nodes,
96+
edges: edges,
97+
isUndirected: bidirectional,
9898
}, nil
9999
}
100100

101+
// [deprecated] This function is deprecated. Use graph.IsUndirected() instead.
101102
// IsBidirectional returns true if the graph is bidirectional.
102103
func (g *Graph) IsBidirectional() bool {
103-
return g.bidirectional
104+
return g.isUndirected
105+
}
106+
107+
// IsUndirected returns true if the graph is undirected.
108+
func (g *Graph) IsUndirected() bool {
109+
return g.isUndirected
104110
}
105111

106112
// Hash returns the SHA-256 hash of the graph.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package standard_graph
2+
3+
import (
4+
"github.com/elecbug/netkit/network-graph/graph"
5+
"github.com/elecbug/netkit/network-graph/node"
6+
)
7+
8+
// BarabasiAlbertGraph generates a graph based on the Barabási–Albert preferential attachment model.
9+
func BarabasiAlbertGraph(n int, m int, isUndirected bool) *graph.Graph {
10+
if m < 1 || n <= m {
11+
return nil
12+
}
13+
14+
ra := genRand()
15+
g := graph.New(isUndirected)
16+
17+
// --- 1. initialize ---
18+
for i := 0; i < m; i++ {
19+
g.AddNode(node.ID(toString(i)))
20+
}
21+
for i := 0; i < m; i++ {
22+
for j := i + 1; j < m; j++ {
23+
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
24+
}
25+
}
26+
27+
// --- 2. preferential attachment ---
28+
for i := m; i < n; i++ {
29+
newNode := node.ID(toString(i))
30+
g.AddNode(newNode)
31+
32+
// calculate current node degrees
33+
degrees := make(map[node.ID]int)
34+
totalDegree := 0
35+
for _, id := range g.Nodes() {
36+
d := len(g.Neighbors(id)) // each node degree
37+
degrees[id] = d
38+
totalDegree += d
39+
}
40+
41+
// degree based sampling
42+
chosen := make(map[node.ID]bool)
43+
for len(chosen) < m {
44+
r := ra.Intn(totalDegree)
45+
accum := 0
46+
var target node.ID
47+
for id, d := range degrees {
48+
accum += d
49+
if r < accum {
50+
target = id
51+
break
52+
}
53+
}
54+
// self-loop and duplicate edges are not allowed
55+
if target != newNode && !chosen[target] {
56+
g.AddEdge(newNode, target)
57+
chosen[target] = true
58+
}
59+
}
60+
}
61+
62+
return g
63+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package standard_graph
2+
3+
import (
4+
"github.com/elecbug/netkit/network-graph/graph"
5+
"github.com/elecbug/netkit/network-graph/node"
6+
)
7+
8+
// ErdosRenyiGraph generates a random graph based on the Erdős-Rényi model.
9+
func ErdosRenyiGraph(n int, p float64, isUndirected bool) *graph.Graph {
10+
ra := genRand()
11+
12+
g := graph.New(isUndirected)
13+
14+
if isUndirected {
15+
for i := 0; i < n; i++ {
16+
for j := i + 1; j < n; j++ {
17+
if ra.Float64() < p {
18+
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
19+
}
20+
}
21+
}
22+
} else {
23+
for i := 0; i < n; i++ {
24+
for j := 0; j < n; j++ {
25+
if i != j && ra.Float64() < p {
26+
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
27+
}
28+
}
29+
}
30+
}
31+
32+
return g
33+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package standard_graph
2+
3+
import (
4+
"math"
5+
6+
"github.com/elecbug/netkit/network-graph/graph"
7+
"github.com/elecbug/netkit/network-graph/node"
8+
)
9+
10+
// RandomGeometricGraph generates a random geometric graph (RGG).
11+
// n = number of nodes
12+
// r = connection radius (0~1)
13+
// isUndirected = undirected or directed graph
14+
func RandomGeometricGraph(n int, r float64, isUndirected bool) *graph.Graph {
15+
if n < 1 || r <= 0 {
16+
return nil
17+
}
18+
19+
ra := genRand()
20+
g := graph.New(isUndirected)
21+
22+
// --- 1. Generate Nodes ---
23+
type point struct{ x, y float64 }
24+
positions := make(map[node.ID]point)
25+
26+
for i := 0; i < n; i++ {
27+
id := node.ID(toString(i))
28+
g.AddNode(id)
29+
positions[id] = point{
30+
x: ra.Float64(),
31+
y: ra.Float64(),
32+
}
33+
}
34+
35+
// --- 2. Generate Edges ---
36+
for i := 0; i < n; i++ {
37+
for j := i + 1; j < n; j++ {
38+
id1 := node.ID(toString(i))
39+
id2 := node.ID(toString(j))
40+
p1, p2 := positions[id1], positions[id2]
41+
42+
dx := p1.x - p2.x
43+
dy := p1.y - p2.y
44+
dist := math.Sqrt(dx*dx + dy*dy)
45+
46+
if dist <= r {
47+
g.AddEdge(id1, id2)
48+
}
49+
}
50+
}
51+
52+
return g
53+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package standard_graph
2+
3+
import (
4+
"github.com/elecbug/netkit/network-graph/graph"
5+
"github.com/elecbug/netkit/network-graph/node"
6+
)
7+
8+
// RandomRegularGraph generates a random k-regular graph with n nodes.
9+
// Each node has exactly degree k. Returns nil if impossible.
10+
func RandomRegularGraph(n, k int, isUndirected bool) *graph.Graph {
11+
if k < 0 || n < 1 || k >= n {
12+
return nil
13+
}
14+
if (n*k)%2 != 0 {
15+
// if undirected, n*k must be even
16+
return nil
17+
}
18+
19+
ra := genRand()
20+
g := graph.New(isUndirected)
21+
22+
// add nodes
23+
for i := 0; i < n; i++ {
24+
g.AddNode(node.ID(toString(i)))
25+
}
26+
27+
// duplicate each node k times as stubs
28+
stubs := make([]node.ID, 0, n*k)
29+
for i := 0; i < n; i++ {
30+
for j := 0; j < k; j++ {
31+
stubs = append(stubs, node.ID(toString(i)))
32+
}
33+
}
34+
35+
// shuffle
36+
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })
37+
38+
// attempt to create edges (self-loop / duplicate prevention)
39+
maxTries := n * k * 10
40+
edges := make(map[[2]string]bool)
41+
try := 0
42+
43+
for len(stubs) > 1 && try < maxTries {
44+
a := stubs[len(stubs)-1]
45+
b := stubs[len(stubs)-2]
46+
stubs = stubs[:len(stubs)-2]
47+
48+
// self-loop is not allowed
49+
if a == b {
50+
// put them back and shuffle
51+
stubs = append(stubs, a, b)
52+
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })
53+
try++
54+
continue
55+
}
56+
57+
// if undirected, edge key must be sorted
58+
key := [2]string{string(a), string(b)}
59+
if isUndirected && key[0] > key[1] {
60+
key[0], key[1] = key[1], key[0]
61+
}
62+
63+
if edges[key] {
64+
// edge already exists, put them back and shuffle
65+
stubs = append(stubs, a, b)
66+
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })
67+
try++
68+
continue
69+
}
70+
71+
// add edge
72+
g.AddEdge(a, b)
73+
edges[key] = true
74+
}
75+
76+
if len(stubs) > 0 {
77+
// if impossible to satisfy conditions
78+
return nil
79+
}
80+
81+
return g
82+
}

0 commit comments

Comments
 (0)