Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ go get github.com/elecbug/netkit@latest

### Graph

- [`network-graph`](./network-graph/): Unweighted network analysis library.
- [`graph`](./network-graph/graph/): Library for creating and building graphs.
- [`standard_graph`](./network-graph/graph/standard_graph/): Library for generating standard graphs like Erdos-Reyni graph.
- [`graph`](./network-graph/graph/): Library for creating and building unweighted graphs.
- [`standard_graph`](./network-graph/graph/standard_graph/): Library for generating standard graphs like Erdos-Reyni graph.
- [`algorithm`](./network-graph/algorithm/): Library containing various graph algorithms.

### P2P
Expand Down
2 changes: 1 addition & 1 deletion graph/algorithm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Default() *Config {
Betweenness: &BetweennessCentralityConfig{Normalized: true},
EdgeBetweenness: &EdgeBetweennessCentralityConfig{Normalized: true},
Eigenvector: &EigenvectorCentralityConfig{MaxIter: 100, Tol: 1e-6, Reverse: false, NStart: nil},
Degree: &DegreeCentralityConfig{Mode: "total"},
Degree: &DegreeCentralityConfig{Mode: DegreeCentralityTotal},
Assortativity: &AssortativityCoefficientConfig{Mode: AssortativityProjected, IgnoreSelfLoops: true},
Modularity: &ModularityConfig{Partition: nil},
}
Expand Down
2 changes: 1 addition & 1 deletion graph/algorithm/degree_centrality.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func DegreeCentrality(g *graph.Graph, cfg *Config) map[graph.NodeID]float64 {
denom := float64(n - 1)

// --- read config ---
mode := "total"
mode := DegreeCentralityTotal
if cfg != nil && cfg.Degree != nil && cfg.Degree.Mode != "" {
mode = cfg.Degree.Mode
}
Expand Down
10 changes: 9 additions & 1 deletion graph/algorithm/subconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,17 @@ type EigenvectorCentralityConfig struct {
NStart *map[graph.NodeID]float64 // initial vector; if nil, uniform distribution
}

type DegreeCentralityMode string

const (
DegreeCentralityTotal DegreeCentralityMode = "total"
DegreeCentralityIn DegreeCentralityMode = "in"
DegreeCentralityOut DegreeCentralityMode = "out"
)

// DegreeCentralityConfig holds the configuration settings for the degree centrality algorithm.
type DegreeCentralityConfig struct {
Mode string
Mode DegreeCentralityMode
}

// AssortativityMode defines how degree pairs (j,k) are taken on each edge/arc.
Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/barabasi_albert.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
)

// BarabasiAlbertGraph generates a graph based on the Barabási–Albert preferential attachment model.
func BarabasiAlbertGraph(n int, m int, isUndirected bool) *graph.Graph {
func (sg *StandardGraph) BarabasiAlbertGraph(n int, m int, isUndirected bool) *graph.Graph {
if m < 1 || n <= m {
return nil
}

ra := genRand()
ra := sg.genRand()
g := graph.New(isUndirected)

// --- 1. initialize ---
Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/erdos_reyni.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import (
)

// ErdosRenyiGraph generates a random graph based on the Erdős-Rényi model.
func ErdosRenyiGraph(n int, p float64, isUndirected bool) *graph.Graph {
ra := genRand()
func (sg *StandardGraph) ErdosRenyiGraph(n int, p float64, isUndirected bool) *graph.Graph {
ra := sg.genRand()

g := graph.New(isUndirected)

Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/random_geometric.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (
// n = number of nodes
// r = connection radius (0~1)
// isUndirected = undirected or directed graph
func RandomGeometricGraph(n int, r float64, isUndirected bool) *graph.Graph {
func (sg *StandardGraph) RandomGeometricGraph(n int, r float64, isUndirected bool) *graph.Graph {
if n < 1 || r <= 0 {
return nil
}

ra := genRand()
ra := sg.genRand()
g := graph.New(isUndirected)

// --- 1. Generate Nodes ---
Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/random_regular.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (

// RandomRegularGraph generates a random k-regular graph with n nodes.
// Each node has exactly degree k. Returns nil if impossible.
func RandomRegularGraph(n, k int, isUndirected bool) *graph.Graph {
func (sg *StandardGraph) RandomRegularGraph(n, k int, isUndirected bool) *graph.Graph {
if k < 0 || n < 1 || k >= n {
return nil
}
Expand All @@ -15,7 +15,7 @@ func RandomRegularGraph(n, k int, isUndirected bool) *graph.Graph {
return nil
}

ra := genRand()
ra := sg.genRand()
g := graph.New(isUndirected)

// add nodes
Expand Down
24 changes: 16 additions & 8 deletions graph/standard_graph/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,30 @@ import (

const randCode = 42

var seed = int64(randCode)
type StandardGraph struct {
seed int64
}

func NewStandardGraph() *StandardGraph {
return &StandardGraph{
seed: randCode,
}
}

// SetSeed sets the seed for random operations in the graph.
func SetSeed(value int64) {
seed = value
func (g *StandardGraph) SetSeed(value int64) {
g.seed = value
}

// SetSeedRandom sets the seed to a random value for non-deterministic behavior.
func SetSeedRandom() {
seed = randCode
func (g *StandardGraph) SetSeedRandom() {
g.seed = randCode
}

func genRand() *rand.Rand {
localSeed := seed
func (g *StandardGraph) genRand() *rand.Rand {
localSeed := g.seed

if seed == randCode {
if g.seed == randCode {
localSeed = rand.Int63()
}

Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/watts_strogatz.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
// n = number of nodes
// k = each node is connected to k nearest neighbors in ring (must be even)
// beta = rewiring probability (0 = regular lattice, 1 = random graph)
func WattsStrogatzGraph(n, k int, beta float64, isUndirected bool) *graph.Graph {
func (sg *StandardGraph) WattsStrogatzGraph(n, k int, beta float64, isUndirected bool) *graph.Graph {
if n < 1 || k < 2 || k >= n || k%2 != 0 {
return nil
}

ra := genRand()
ra := sg.genRand()
g := graph.New(isUndirected)

// --- 1. Generate Nodes ---
Expand Down
4 changes: 2 additions & 2 deletions graph/standard_graph/waxman.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import (
// n = number of nodes
// alpha, beta = Waxman parameters (0<alpha<=1, 0<beta<=1)
// isUndirected = undirected or directed graph
func WaxmanGraph(n int, alpha, beta float64, isUndirected bool) *graph.Graph {
func (sg *StandardGraph) WaxmanGraph(n int, alpha, beta float64, isUndirected bool) *graph.Graph {
if n < 1 || alpha <= 0 || alpha > 1 || beta <= 0 || beta > 1 {
return nil
}

ra := genRand()
ra := sg.genRand()
g := graph.New(isUndirected)

// --- 1. Generate Node Positions ---
Expand Down
44 changes: 44 additions & 0 deletions p2p/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,47 @@ func LogNormalRand(mu, sigma float64, src rand.Source) float64 {

return math.Exp(mu + sigma*z)
}

// PoissonRand generates a Poisson-distributed random integer
// with given lambda parameter.
func PoissonRand(lambda float64, src rand.Source) int {
L := math.Exp(-lambda)
k := 0
p := 1.0

r := rand.New(src)

for p > L {
k++
p *= r.Float64()
}

return k - 1
}

// ExponentialRand generates an exponentially distributed random number
// with given rate parameter.
func ExponentialRand(rate float64, src rand.Source) float64 {
r := rand.New(src)
u := r.Float64()
return -math.Log(1-u) / rate
}

// UniformRand generates a uniformly distributed random number
// between min and max.
func UniformRand(min, max float64, src rand.Source) float64 {
r := rand.New(src)
return min + r.Float64()*(max-min)
}

// NormalRand generates a normally distributed random number
// with given mean and standard deviation.
func NormalRand(mean, stddev float64, src rand.Source) float64 {
r := rand.New(src)

u1 := r.Float64()
u2 := r.Float64()
z := math.Sqrt(-2.0*math.Log(u1)) * math.Cos(2*math.Pi*u2)

return mean + stddev*z
}
6 changes: 3 additions & 3 deletions p2p/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ func (n *Network) RunNetworkSimulation(ctx context.Context) {
wg.Wait()
}

// NodeIDs returns a slice of all node IDs in the network.
func (n *Network) NodeIDs() []PeerID {
// PeerIDs returns a slice of all node IDs in the network.
func (n *Network) PeerIDs() []PeerID {
ids := make([]PeerID, 0, len(n.nodes))

for id := range n.nodes {
Expand All @@ -91,7 +91,7 @@ func (n *Network) Publish(nodeID PeerID, msg string, protocol BroadcastProtocol)
return fmt.Errorf("node %d is not alive", nodeID)
}

node.msgQueue <- Message{From: nodeID, Content: msg, Protocol: protocol}
node.msgQueue <- Message{From: nodeID, Content: msg, Protocol: protocol, HopCount: 0}
return nil
}

Expand Down
21 changes: 16 additions & 5 deletions p2p/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package p2p

import (
"context"
"math/rand"
"sync"
"time"
)
Expand Down Expand Up @@ -46,10 +47,9 @@ func newNode(id PeerID, nodeLatency float64) *p2pNode {

// eachRun starts the message handling routine for the node.
func (n *p2pNode) eachRun(network *Network, wg *sync.WaitGroup, ctx context.Context) {
defer wg.Done()

go func(ctx context.Context) {
go func(ctx context.Context, wg *sync.WaitGroup) {
n.alive = true
wg.Done()

for msg := range n.msgQueue {
select {
Expand Down Expand Up @@ -81,7 +81,7 @@ func (n *p2pNode) eachRun(network *Network, wg *sync.WaitGroup, ctx context.Cont
}
}
}
}(ctx)
}(ctx, wg)
}

// copyIDSet creates a shallow copy of a set of IDs.
Expand All @@ -97,6 +97,7 @@ func copyIDSet(src map[PeerID]struct{}) map[PeerID]struct{} {
func (n *p2pNode) publish(network *Network, msg Message, exclude map[PeerID]struct{}) {
content := msg.Content
protocol := msg.Protocol
hopCount := msg.HopCount

n.mu.Lock()
defer n.mu.Unlock()
Expand All @@ -123,6 +124,10 @@ func (n *p2pNode) publish(network *Network, msg Message, exclude map[PeerID]stru
}

if protocol == Gossiping && len(willSendEdges) > 0 {
rand.Shuffle(len(willSendEdges), func(i, j int) {
willSendEdges[i], willSendEdges[j] = willSendEdges[j], willSendEdges[i]
})

k := int(float64(len(willSendEdges)) * network.cfg.GossipFactor)
willSendEdges = willSendEdges[:k]
}
Expand All @@ -132,7 +137,13 @@ func (n *p2pNode) publish(network *Network, msg Message, exclude map[PeerID]stru

go func(e p2pEdge) {
time.Sleep(time.Duration(e.Latency) * time.Millisecond)
network.nodes[e.TargetID].msgQueue <- Message{From: n.id, Content: content, Protocol: protocol}

network.nodes[e.TargetID].msgQueue <- Message{
From: n.id,
Content: content,
Protocol: protocol,
HopCount: hopCount + 1,
}
}(edgeCopy)
}
}
8 changes: 5 additions & 3 deletions p2p/type.go → p2p/p2p.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
// Package p2p provides types and interfaces for a peer-to-peer networking simulation.
package p2p

// PeerID represents a unique identifier for a node in the P2P network.
type PeerID uint64

// Message represents a message sent between nodes in the P2P network.
type Message struct {
From PeerID
Content string
Protocol BroadcastProtocol
HopCount int
}

// Config holds configuration parameters for the P2P network.
type Config struct {
GossipFactor float64 // fraction of neighbors to gossip to
}

// PeerID represents a unique identifier for a node in the P2P network.
type PeerID uint64
Loading