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
6 changes: 1 addition & 5 deletions netkit.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
// Package netkit provides common types and utilities used across this
// repository.
// Package netkit provides common types and utilities used across this repository.
package netkit

// Any is an alias for interface{} for convenience.
type Any = interface{}
53 changes: 0 additions & 53 deletions network-graph/algorithm/algorithm.go
Original file line number Diff line number Diff line change
@@ -1,55 +1,2 @@
// Package algorithm provides graph algorithms for network analysis.
package algorithm

import (
"github.com/elecbug/netkit/network-graph/algorithm/config"
"github.com/elecbug/netkit/network-graph/graph"
)

// MetricType represents the type of metric to be calculated.
type MetricType int

// Metric types for graph analysis.
const (
BETWEENNESS_CENTRALITY MetricType = iota
CLOSENESS_CENTRALITY
CLUSTERING_COEFFICIENT
DEGREE_ASSORTATIVITY_COEFFICIENT
DEGREE_CENTRALITY
DIAMETER
EDGE_BETWEENNESS_CENTRALITY
EIGENVECTOR_CENTRALITY
MODULARITY
PAGE_RANK
SHORTEST_PATHS
)

// Metric calculates the specified metric for the given graph.
func Metric(g *graph.Graph, cfg *config.Config, metricType MetricType) any {
switch metricType {
case BETWEENNESS_CENTRALITY:
return BetweennessCentrality(g, cfg)
case CLOSENESS_CENTRALITY:
return ClosenessCentrality(g, cfg)
case CLUSTERING_COEFFICIENT:
return ClusteringCoefficient(g, cfg)
case DEGREE_ASSORTATIVITY_COEFFICIENT:
return DegreeAssortativityCoefficient(g, cfg)
case DEGREE_CENTRALITY:
return DegreeCentrality(g, cfg)
case DIAMETER:
return Diameter(g, cfg)
case EDGE_BETWEENNESS_CENTRALITY:
return EdgeBetweennessCentrality(g, cfg)
case EIGENVECTOR_CENTRALITY:
return EigenvectorCentrality(g, cfg)
case MODULARITY:
return Modularity(g, cfg)
case PAGE_RANK:
return PageRank(g, cfg)
case SHORTEST_PATHS:
return AllShortestPaths(g, cfg)
default:
return nil
}
}
4 changes: 2 additions & 2 deletions network-graph/graph/edge.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (g *Graph) AddEdge(from, to node.ID) error {

g.edges[from][to] = true

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

delete(g.edges[from], to)

if g.bidirectional {
if g.isUndirected {
if _, ok := g.edges[to]; !ok {
g.edges[to] = make(map[node.ID]bool)
}
Expand Down
30 changes: 18 additions & 12 deletions network-graph/graph/graph.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ import (

// Graph maintains nodes and adjacency edges.
type Graph struct {
nodes map[node.ID]bool
edges map[node.ID]map[node.ID]bool
bidirectional bool
nodes map[node.ID]bool
edges map[node.ID]map[node.ID]bool
isUndirected bool
}

// New creates and returns an empty Graph.
func New(bidirectional bool) *Graph {
func New(isUndirected bool) *Graph {
return &Graph{
nodes: make(map[node.ID]bool),
edges: make(map[node.ID]map[node.ID]bool),
bidirectional: bidirectional,
nodes: make(map[node.ID]bool),
edges: make(map[node.ID]map[node.ID]bool),
isUndirected: isUndirected,
}
}

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

bidirectional, err := json.Marshal(g.bidirectional)
bidirectional, err := json.Marshal(g.isUndirected)

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

return &Graph{
nodes: nodes,
edges: edges,
bidirectional: bidirectional,
nodes: nodes,
edges: edges,
isUndirected: bidirectional,
}, nil
}

// [deprecated] This function is deprecated. Use graph.IsUndirected() instead.
// IsBidirectional returns true if the graph is bidirectional.
func (g *Graph) IsBidirectional() bool {
return g.bidirectional
return g.isUndirected
}

// IsUndirected returns true if the graph is undirected.
func (g *Graph) IsUndirected() bool {
return g.isUndirected
}

// Hash returns the SHA-256 hash of the graph.
Expand Down
63 changes: 63 additions & 0 deletions network-graph/graph/standard_graph/barabasi_albert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package standard_graph

import (
"github.com/elecbug/netkit/network-graph/graph"
"github.com/elecbug/netkit/network-graph/node"
)

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

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

// --- 1. initialize ---
for i := 0; i < m; i++ {
g.AddNode(node.ID(toString(i)))
}
for i := 0; i < m; i++ {
for j := i + 1; j < m; j++ {
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
}
}

// --- 2. preferential attachment ---
for i := m; i < n; i++ {
newNode := node.ID(toString(i))
g.AddNode(newNode)

// calculate current node degrees
degrees := make(map[node.ID]int)
totalDegree := 0
for _, id := range g.Nodes() {
d := len(g.Neighbors(id)) // each node degree
degrees[id] = d
totalDegree += d
}

// degree based sampling
chosen := make(map[node.ID]bool)
for len(chosen) < m {
r := ra.Intn(totalDegree)
accum := 0
var target node.ID
for id, d := range degrees {
accum += d
if r < accum {
target = id
break
}
}
// self-loop and duplicate edges are not allowed
if target != newNode && !chosen[target] {
g.AddEdge(newNode, target)
chosen[target] = true
}
}
}

return g
}
33 changes: 33 additions & 0 deletions network-graph/graph/standard_graph/erdos_reyni.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package standard_graph

import (
"github.com/elecbug/netkit/network-graph/graph"
"github.com/elecbug/netkit/network-graph/node"
)

// 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()

g := graph.New(isUndirected)

if isUndirected {
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if ra.Float64() < p {
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
}
}
}
} else {
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
if i != j && ra.Float64() < p {
g.AddEdge(node.ID(toString(i)), node.ID(toString(j)))
}
}
}
}

return g
}
53 changes: 53 additions & 0 deletions network-graph/graph/standard_graph/random_geometric.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package standard_graph

import (
"math"

"github.com/elecbug/netkit/network-graph/graph"
"github.com/elecbug/netkit/network-graph/node"
)

// RandomGeometricGraph generates a random geometric graph (RGG).
// n = number of nodes
// r = connection radius (0~1)
// isUndirected = undirected or directed graph
func RandomGeometricGraph(n int, r float64, isUndirected bool) *graph.Graph {
if n < 1 || r <= 0 {
return nil
}

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

// --- 1. Generate Nodes ---
type point struct{ x, y float64 }
positions := make(map[node.ID]point)

for i := 0; i < n; i++ {
id := node.ID(toString(i))
g.AddNode(id)
positions[id] = point{
x: ra.Float64(),
y: ra.Float64(),
}
}

// --- 2. Generate Edges ---
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
id1 := node.ID(toString(i))
id2 := node.ID(toString(j))
p1, p2 := positions[id1], positions[id2]

dx := p1.x - p2.x
dy := p1.y - p2.y
dist := math.Sqrt(dx*dx + dy*dy)

if dist <= r {
g.AddEdge(id1, id2)
}
}
}

return g
}
82 changes: 82 additions & 0 deletions network-graph/graph/standard_graph/random_regular.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package standard_graph

import (
"github.com/elecbug/netkit/network-graph/graph"
"github.com/elecbug/netkit/network-graph/node"
)

// 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 {
if k < 0 || n < 1 || k >= n {
return nil
}
if (n*k)%2 != 0 {
// if undirected, n*k must be even
return nil
}

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

// add nodes
for i := 0; i < n; i++ {
g.AddNode(node.ID(toString(i)))
}

// duplicate each node k times as stubs
stubs := make([]node.ID, 0, n*k)
for i := 0; i < n; i++ {
for j := 0; j < k; j++ {
stubs = append(stubs, node.ID(toString(i)))
}
}

// shuffle
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })

// attempt to create edges (self-loop / duplicate prevention)
maxTries := n * k * 10
edges := make(map[[2]string]bool)
try := 0

for len(stubs) > 1 && try < maxTries {
a := stubs[len(stubs)-1]
b := stubs[len(stubs)-2]
stubs = stubs[:len(stubs)-2]

// self-loop is not allowed
if a == b {
// put them back and shuffle
stubs = append(stubs, a, b)
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })
try++
continue
}

// if undirected, edge key must be sorted
key := [2]string{string(a), string(b)}
if isUndirected && key[0] > key[1] {
key[0], key[1] = key[1], key[0]
}

if edges[key] {
// edge already exists, put them back and shuffle
stubs = append(stubs, a, b)
ra.Shuffle(len(stubs), func(i, j int) { stubs[i], stubs[j] = stubs[j], stubs[i] })
try++
continue
}

// add edge
g.AddEdge(a, b)
edges[key] = true
}

if len(stubs) > 0 {
// if impossible to satisfy conditions
return nil
}

return g
}
Loading