From 8c6f20a7446e5e458c7941c9836b7714e51a9e52 Mon Sep 17 00:00:00 2001 From: halehdizaji Date: Mon, 15 Sep 2025 11:34:30 +0200 Subject: [PATCH 1/4] Update graph.go Added the function: HasEdge() --- model/graph.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/model/graph.go b/model/graph.go index 4a1cd50..f1bb30f 100644 --- a/model/graph.go +++ b/model/graph.go @@ -371,6 +371,47 @@ func (g *UndirectedGraph) RemoveEdge(edge Edge) { } } +/* +HasEode checks if the UndirectedGraph contains a specific edge. + +Parameters: +- edge: . + +Returns: +- bool: True if the edge exists in the graph, otherwise false. + +Description: +The function returns true if the specified edge is present in the UndirectedGraph, indicating its existence in the graph. Otherwise, it returns false. + +Example: + + undirectedGraph := UndirectedGraph{ + Nodes: map[Node]bool{ + 1: true, + 2: true, + 3: true, + 4: true, + }, + Edges: map[Node][]Node{ + 1: {2, 3, 4}, + 2: {1, 3}, + 3: {1, 2, 4}, + 4: {1, 3}, + }, + } + + result1 := undirectedGraph.HasEdge(2,3) // true + result2 := undirectedGraph.HasNode(4,2) // false +*/ +func (g *UndirectedGraph) HasEdge(u, v Node) bool { + for _, nb := range g.Edges[u] { + if nb == v { + return true + } + } + return false +} + /* RemoveNode removes a node from the UndirectedGraph and all associated edges. From 4fc58d202948e23d6593ed2a03c23641a741d3f1 Mon Sep 17 00:00:00 2001 From: halehdizaji Date: Mon, 15 Sep 2025 11:36:05 +0200 Subject: [PATCH 2/4] Update sampling.go Changed the PreservationTopKEdgeSampling to sample a ratio of each node's neighborhood, selecting neighbors with higher degrees. --- model/sampling.go | 71 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/model/sampling.go b/model/sampling.go index 348661e..77cd97f 100644 --- a/model/sampling.go +++ b/model/sampling.go @@ -2,6 +2,7 @@ package model import ( "fmt" + "math" "math/rand" "sort" @@ -556,35 +557,65 @@ func (strategy *PreservationRandomWalkWithJumpSampling) Sample(graph UndirectedG return ng, nil } -func (strategy *PreservationTopKEdgeSampling) Sample(g UndirectedGraph, sampledGraphSizeRatio float32) (UndirectedGraph, error) { - // TODO: where do we get the K parameter? - // shall we change the interface to passing sampling parameters object? - // and some Factory, to create the appropriate map of parameters - topK := 5 +func (strategy *PreservationTopKEdgeSampling) Sample( + g UndirectedGraph, + sampleNeighborRatio float32, // e.g., 0.3 keeps top 30% per node +) (UndirectedGraph, error) { + ng := UndirectedGraph{ Nodes: make(map[Node]bool), Edges: make(map[Node][]Node), } - for nodeId := range g.Nodes { - neighbours := g.Edges[nodeId] - weightedNeighbours := make([]WeightedElement, 0) - for k := 0; k < len(neighbours); k++ { - weightedNeighbours = append(weightedNeighbours, WeightedElement{ - Payload: neighbours[k], - Weight: float32(g.NodeDegree(neighbours[k])), - }) + for u := range g.Nodes { + nbrs := g.Edges[u] + if len(nbrs) == 0 { + continue + } + + // Build (neighbor, weight) where weight = neighbor's degree + type we struct { + v Node + weight float32 } - sort.SliceStable(weightedNeighbours, func(i, j int) bool { - return weightedNeighbours[i].Weight < weightedNeighbours[j].Weight - }) - for idx := 0; idx < topK; idx++ { - ng.AddEdge(Edge{ - Node1: nodeId, - Node2: weightedNeighbours[idx].Payload.(Node), + ws := make([]we, 0, len(nbrs)) + for _, v := range nbrs { + if v == u { // skip self-loops if any + continue + } + ws = append(ws, we{ + v: v, + weight: float32(g.NodeDegree(v)), }) } + if len(ws) == 0 { + continue + } + + // Sort by weight DESC (keep highest-degree neighbors) + sort.SliceStable(ws, func(i, j int) bool { return ws[i].weight > ws[j].weight }) + + // Top-K per node, where K = ceil(ratio * deg(u)) + k := int(math.Ceil(float64(sampleNeighborRatio) * float64(len(ws)))) + if k < 0 { + k = 0 + } + if k > len(ws) { + k = len(ws) + } + if k == 0 { + continue + } + + // Add the top-K edges (once for undirected graphs) + for i := 0; i < k; i++ { + v := ws[i].v + if !ng.HasEdge(u, v) { + ng.AddEdge(Edge{Node1: u, Node2: v}) + } + } } + return ng, nil } From 26141164ced690682fd4b2556302593f81508f49 Mon Sep 17 00:00:00 2001 From: halehdizaji Date: Mon, 15 Sep 2025 12:00:57 +0200 Subject: [PATCH 3/4] Update graph.go Changed the AddEdge() function to avoid multi edges in undirected graphs. When adding edges, it checks for the existence of the edge. --- model/graph.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/model/graph.go b/model/graph.go index f1bb30f..a5d73b6 100644 --- a/model/graph.go +++ b/model/graph.go @@ -126,6 +126,7 @@ Example: fmt.Println(undirectedGraph.Edges) // Output: map[1:[2] 2:[1]] */ + func (g *UndirectedGraph) AddEdge(edge Edge) { // Ensure the existence of the Edges map if g.Edges == nil { @@ -136,9 +137,11 @@ func (g *UndirectedGraph) AddEdge(edge Edge) { g.AddNode(edge.Node1) g.AddNode(edge.Node2) - // Add the edge to the Edges map - g.Edges[edge.Node1] = append(g.Edges[edge.Node1], edge.Node2) - g.Edges[edge.Node2] = append(g.Edges[edge.Node2], edge.Node1) + // Only add if edge doesn’t already exist (undirected) + if !g.HasEdge(edge.Node1, edge.Node2) { + g.Edges[edge.Node1] = append(g.Edges[edge.Node1], edge.Node2) + g.Edges[edge.Node2] = append(g.Edges[edge.Node2], edge.Node1) + } } func (g *UndirectedGraph) DFS(startNode Node) *UndirectedGraph { From bbf54f63a0e5e5c992eb41133f389087a7a4c438 Mon Sep 17 00:00:00 2001 From: halehdizaji Date: Mon, 15 Sep 2025 18:30:40 +0200 Subject: [PATCH 4/4] Update sampling.go Separated TopKEdge sampling and TopRatioEdge sampling algorithms. --- model/sampling.go | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/model/sampling.go b/model/sampling.go index 77cd97f..53b86ca 100644 --- a/model/sampling.go +++ b/model/sampling.go @@ -239,6 +239,7 @@ type PreservationRandomWalkSampling struct{ ISamplingStrategy } type PreservationRandomWalkWithJumpSampling struct{ ISamplingStrategy } type PreservationRandomWalkWithRestartSampling struct{ ISamplingStrategy } type PreservationTopKEdgeSampling struct{ ISamplingStrategy } +type PreservationTopRatioEdgeSampling struct{ ISamplingStrategy } func (strategy *PreservationRandomNodeSampling) Sample(g UndirectedGraph, sampledGraphSizeRatio float32) (UndirectedGraph, error) { ng := UndirectedGraph{ @@ -557,7 +558,7 @@ func (strategy *PreservationRandomWalkWithJumpSampling) Sample(graph UndirectedG return ng, nil } -func (strategy *PreservationTopKEdgeSampling) Sample( +func (strategy *PreservationTopRatioEdgeSampling) Sample( g UndirectedGraph, sampleNeighborRatio float32, // e.g., 0.3 keeps top 30% per node ) (UndirectedGraph, error) { @@ -619,6 +620,48 @@ func (strategy *PreservationTopKEdgeSampling) Sample( return ng, nil } +func (strategy *PreservationTopKEdgeSampling) Sample(g UndirectedGraph, sampledGraphSizeRatio float32) (UndirectedGraph, error) { + // TODO: where do we get the K parameter? + // shall we change the interface to passing sampling parameters object? + // and some Factory, to create the appropriate map of parameters + topK := 5 + ng := UndirectedGraph{ + Nodes: make(map[Node]bool), + Edges: make(map[Node][]Node), + } + + for nodeId := range g.Nodes { + neighbours := g.Edges[nodeId] + if len(neighbours) == 0 { + continue + } + weightedNeighbours := make([]WeightedElement, 0) + for k := 0; k < len(neighbours); k++ { + weightedNeighbours = append(weightedNeighbours, WeightedElement{ + Payload: neighbours[k], + Weight: float32(g.NodeDegree(neighbours[k])), + }) + } + sort.SliceStable(weightedNeighbours, func(i, j int) bool { + return weightedNeighbours[i].Weight > weightedNeighbours[j].Weight + }) + + topK_adjusted := min(topK, len(neighbours)) + + for idx := 0; idx < topK_adjusted; idx++ { + nodeId_1 := nodeId + nodeId_2 := weightedNeighbours[idx].Payload.(Node) + if !ng.HasEdge(nodeId_1, nodeId_2) { + ng.AddEdge(Edge{ + Node1: nodeId_1, + Node2: nodeId_2, + }) + } + } + } + return ng, nil +} + /* CONTRACTION GRAPH SAMPLING METHODS */