Skip to content

Commit 090ef72

Browse files
authored
Merge pull request #45 from elecbug/elecbug/master
Update v0.12.6
2 parents 9a45d32 + b2b7df9 commit 090ef72

6 files changed

Lines changed: 164 additions & 18 deletions

File tree

README.md

Lines changed: 116 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Netkit
22

3-
**Netkit** is a Go graph algorithm library focused on clarity, extensibility, and practical performance.
3+
**Netkit** is a Go toolkit for graph algorithms, network analysis, and P2P network simulation.
44

5-
It provides reusable graph data structures and common network analysis algorithms, with selected results validated against [NetworkX](https://networkx.org/).
5+
It provides reusable graph data structures, common network analysis algorithms, and a programmable P2P layer for testing message propagation over graph-based network topologies.
6+
7+
Selected graph algorithm results are validated against [NetworkX](https://networkx.org/).
68

79
- Go 1.25+
810
- Module: `github.com/elecbug/netkit`
@@ -12,7 +14,7 @@ It provides reusable graph data structures and common network analysis algorithm
1214

1315
## Features
1416

15-
Netkit provides graph utilities and network analysis algorithms for both directed and undirected graphs.
17+
Netkit provides graph utilities, network analysis algorithms, and P2P simulation tools for both directed and undirected graphs.
1618

1719
Current focus areas include:
1820

@@ -23,6 +25,8 @@ Current focus areas include:
2325
- Graph diameter
2426
- PageRank
2527
- Modularity analysis
28+
- P2P overlay construction
29+
- Message propagation simulation
2630
- NetworkX-compatible validation for selected algorithms
2731

2832
Implemented or planned algorithms include:
@@ -38,13 +42,22 @@ Implemented or planned algorithms include:
3842
- Diameter / weighted diameter
3943
- Modularity
4044

45+
The P2P simulation layer supports:
46+
47+
- Creating peer networks from graph topologies
48+
- Defining message propagation behavior
49+
- Configuring processing latency
50+
- Configuring network latency
51+
- Testing broadcast and relay strategies
52+
- Evaluating propagation behavior over generated or custom graphs
53+
4154
---
4255

4356
## Installation
4457

4558
```bash
4659
go get github.com/elecbug/netkit@latest
47-
````
60+
```
4861

4962
---
5063

@@ -53,6 +66,8 @@ go get github.com/elecbug/netkit@latest
5366
> [!NOTE]
5467
> Netkit is under active development. Public APIs may change before the first stable release.
5568
69+
The same graph can be used both for graph analysis and as a P2P overlay topology.
70+
5671
```go
5772
package main
5873

@@ -61,6 +76,7 @@ import (
6176

6277
"github.com/elecbug/netkit/v2/analyzer"
6378
"github.com/elecbug/netkit/v2/graph"
79+
"github.com/elecbug/netkit/v2/p2p"
6480
)
6581

6682
func main() {
@@ -73,21 +89,108 @@ func main() {
7389
g.AddEdge("0", "1")
7490
g.AddEdge("1", "2")
7591

76-
a := analyzer.NewAnalyzer(g, nil)
92+
a := analyzer.NewAnalyzer(g, 4, analyzer.DefaultConfig())
7793

7894
degree, err := a.DegreeCentrality()
7995
if err != nil {
8096
panic(err)
8197
}
8298

83-
fmt.Println(degree)
99+
fmt.Println("degree centrality:", degree)
100+
101+
cfg := &p2p.Config{
102+
ProcessingLatencyFunc: func(src p2p.PeerID) float64 {
103+
return 100
104+
},
105+
NetworkLatencyFunc: func(src, dst p2p.PeerID) float64 {
106+
return 1
107+
},
108+
}
109+
110+
network, err := p2p.New(g, cfg)
111+
if err != nil {
112+
panic(err)
113+
}
114+
115+
_ = network
116+
117+
// The graph is now also available as a P2P overlay.
118+
// Users can define propagation behavior and run message dissemination tests
119+
// over the same topology used for graph analysis.
120+
}
121+
```
122+
123+
A propagation function can decide which peers should receive a message next.
124+
125+
```go
126+
package main
127+
128+
import "github.com/elecbug/netkit/v2/p2p"
129+
130+
func ForwardToUnseenPeers(
131+
id p2p.PeerID,
132+
msg p2p.Message,
133+
known []p2p.PeerID,
134+
sent []p2p.PeerID,
135+
received []p2p.PeerID,
136+
params map[string]any,
137+
) *[]p2p.PeerID {
138+
result := make([]p2p.PeerID, 0)
139+
140+
for _, peerID := range known {
141+
alreadySent := false
142+
for _, s := range sent {
143+
if peerID == s {
144+
alreadySent = true
145+
break
146+
}
147+
}
148+
if alreadySent {
149+
continue
150+
}
151+
152+
alreadyReceived := false
153+
for _, r := range received {
154+
if peerID == r {
155+
alreadyReceived = true
156+
break
157+
}
158+
}
159+
if alreadyReceived {
160+
continue
161+
}
162+
163+
result = append(result, peerID)
164+
}
165+
166+
return &result
84167
}
85168
```
86169

87170
> API details may vary by version. See package documentation and examples for the latest usage.
88171
89172
---
90173

174+
## P2P Simulation
175+
176+
Netkit can form a P2P network directly from a graph.
177+
178+
This allows users to generate or load a topology, analyze its structural properties, and then run message propagation experiments on the same network.
179+
180+
Typical use cases include:
181+
182+
* Broadcast protocol testing
183+
* Gossip-style dissemination experiments
184+
* Overlay topology evaluation
185+
* Reachability analysis
186+
* Duplicate message analysis
187+
* Latency-sensitive propagation experiments
188+
* Comparing propagation behavior across graph models
189+
190+
This makes Netkit useful not only as a graph algorithm library, but also as a lightweight experimental framework for P2P network behavior.
191+
192+
---
193+
91194
## Validation
92195

93196
Netkit reimplements common graph and network algorithms in Go.
@@ -143,12 +246,15 @@ Netkit is designed with the following goals:
143246
Algorithms should be readable and easy to inspect.
144247

145248
2. **Extensibility**
146-
Graph structures and analysis components should be easy to extend.
249+
Graph structures, analysis components, and P2P protocol logic should be easy to extend.
147250

148251
3. **Practical performance**
149-
Implementations should be efficient enough for medium-to-large network analysis workloads.
252+
Implementations should be efficient enough for medium-to-large network analysis and simulation workloads.
253+
254+
4. **P2P experimentation**
255+
Users should be able to construct graph-based overlays and test message propagation behavior on them.
150256

151-
4. **Validation**
257+
5. **Validation**
152258
Results should be checked against established libraries such as NetworkX when possible.
153259

154260
---
@@ -163,4 +269,4 @@ MIT © 2025 elecbug. See [`LICENSE`](./LICENSE).
163269

164270
This project reimplements common graph and network algorithms in Go with selected results validated against NetworkX.
165271

166-
NetworkX is © the NetworkX Developers and distributed under the BSD 3-Clause License.
272+
NetworkX is © the NetworkX Developers and distributed under the BSD 3-Clause License.

v2/graph/analyzer/analyzer.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package analyzer
22

33
import (
4+
"runtime"
45
"sync"
56

67
"github.com/elecbug/netkit/v2/graph"
@@ -16,8 +17,8 @@ type Analyzer struct {
1617
cfg *Config // cfg holds configuration options for the analyzer, such as worker counts and normalization settings.
1718
}
1819

19-
// NewAnalyzer creates a new Analyzer instance based on the provided graph.
20-
func NewAnalyzer(g *graph.Graph, parallelCoreCount int, cfg *Config) *Analyzer {
20+
// New creates a new Analyzer instance based on the provided graph.
21+
func New(g *graph.Graph, parallelCoreCount int, cfg *Config) *Analyzer {
2122
return &Analyzer{
2223
baseGraph: g,
2324
graphHash: "",
@@ -27,6 +28,20 @@ func NewAnalyzer(g *graph.Graph, parallelCoreCount int, cfg *Config) *Analyzer {
2728
}
2829
}
2930

31+
// ClearCache clears the cached shortest paths and resets the graph hash. This should be called when the underlying graph
32+
// changes to ensure that subsequent shortest path computations are accurate and not based on stale data.
33+
func (a *Analyzer) ClearCache() string {
34+
a.mu.Lock()
35+
defer a.mu.Unlock()
36+
37+
a.allShortestPaths = make(map[graph.NodeID]map[graph.NodeID][]graph.Path)
38+
a.graphHash = ""
39+
40+
runtime.GC() // Trigger garbage collection to free memory used by the old cache
41+
42+
return a.graphHash
43+
}
44+
3045
// Graph returns the base graph associated with the analyzer.
3146
func (a *Analyzer) Graph() *graph.Graph {
3247
return a.baseGraph

v2/graph/analyzer/analyzer_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func testComputeShortestPath(t *testing.T) {
3636
g.AddEdge("A", "C", graph.NewWeight(2))
3737
g.AddEdge("C", "D", graph.NewWeight(1))
3838

39-
a := analyzer.NewAnalyzer(g, 1, analyzer.DefaultConfig())
39+
a := analyzer.New(g, 1, analyzer.DefaultConfig())
4040

4141
paths, err := a.ShortestPaths("A", "D")
4242
if err != nil {
@@ -106,7 +106,7 @@ func TestPerformance(t *testing.T) {
106106
t.Fatalf("failed to create graph: %v", err)
107107
}
108108

109-
a := analyzer.NewAnalyzer(g, 1, analyzer.DefaultConfig())
109+
a := analyzer.New(g, 1, analyzer.DefaultConfig())
110110

111111
startTime := time.Now()
112112
paths, err := a.ShortestPaths("0", "999")
@@ -121,7 +121,7 @@ func TestPerformance(t *testing.T) {
121121
duration := time.Since(startTime)
122122
fmt.Printf(" - Time taken to compute shortest paths: %v\n", duration)
123123

124-
a = analyzer.NewAnalyzer(g, 4, analyzer.DefaultConfig())
124+
a = analyzer.New(g, 4, analyzer.DefaultConfig())
125125

126126
startTime = time.Now()
127127
pathsCompared, err := a.ShortestPaths("0", "999")
@@ -135,7 +135,7 @@ func TestPerformance(t *testing.T) {
135135
t.Errorf("expected total distance %v, got %v", paths[0].TotalDistance(), pathsCompared[0].TotalDistance())
136136
}
137137

138-
a = analyzer.NewAnalyzer(g, 16, analyzer.DefaultConfig())
138+
a = analyzer.New(g, 16, analyzer.DefaultConfig())
139139

140140
startTime = time.Now()
141141
pathsCompared, err = a.ShortestPaths("0", "999")
@@ -149,7 +149,7 @@ func TestPerformance(t *testing.T) {
149149
t.Errorf("expected total distance %v, got %v", paths[0].TotalDistance(), pathsCompared[0].TotalDistance())
150150
}
151151

152-
a = analyzer.NewAnalyzer(g, 32, analyzer.DefaultConfig())
152+
a = analyzer.New(g, 32, analyzer.DefaultConfig())
153153

154154
startTime = time.Now()
155155
pathsCompared, err = a.ShortestPaths("0", "999")
@@ -204,7 +204,7 @@ func TestAnaylzer(t *testing.T) {
204204
}
205205

206206
cfg := analyzer.DefaultConfig()
207-
a := analyzer.NewAnalyzer(g, 16, cfg)
207+
a := analyzer.New(g, 16, cfg)
208208

209209
t.Run("ShortestPaths", func(t *testing.T) {
210210
results["shortest_paths"] = make(map[string]any)

v2/graph/graph.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ func New(directed bool, weighted bool) *Graph {
3434
}
3535
}
3636

37+
// Free clears all nodes and edges from the graph, effectively resetting it to an empty state.
38+
func (g *Graph) Free() {
39+
for id := range g.nodes {
40+
delete(g.nodes, id)
41+
}
42+
}
43+
3744
/* Node */
3845

3946
// AddNode adds a node to the graph.

v2/p2p/p2p.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@ func New(source *graph.Graph, cfg *Config) (*P2P, error) {
6868
return &P2P{peers: nodes, cfg: cfg}, nil
6969
}
7070

71+
// Free clears all peers from the P2P network, effectively resetting it to an empty state.
72+
func (p *P2P) Free() {
73+
for id := range p.peers {
74+
p.peers[id].eachStop()
75+
delete(p.peers, id)
76+
}
77+
}
78+
7179
/* Basic Actions */
7280

7381
// Run starts the message handling routines for all peers in the network.
@@ -250,6 +258,7 @@ func (p *P2P) MessageInfo(peerID PeerID, content string) (map[string]any, error)
250258
}
251259

252260
info["seen"] = peer.seenAt[content].String()
261+
info["first_from"] = peer.firstFrom[content]
253262

254263
return info, nil
255264
}

v2/p2p/peer.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,12 @@ func (p *peer) eachPublish(network *P2P, msg Message) {
146146
}(edgeCopy)
147147
}
148148
}
149+
150+
// eachStop marks the peer as inactive and closes its message queue.
151+
func (p *peer) eachStop() {
152+
p.mu.Lock()
153+
defer p.mu.Unlock()
154+
155+
p.alive = false
156+
close(p.msgQueue)
157+
}

0 commit comments

Comments
 (0)