Skip to content
Open
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
11 changes: 7 additions & 4 deletions algo/closeness.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import (

"github.com/specterops/dawgs/container"
"github.com/specterops/dawgs/graph"
"github.com/specterops/dawgs/util"
)

func ClosenessForDirectedUnweightedGraph(digraph container.DirectedGraph, direction graph.Direction, sampleFunc SampleFunc, nSamples int) map[uint64]Weight {
scores := make(map[uint64]Weight, nSamples)

for _, nodeID := range sampleFunc(digraph, nSamples) {
if shortestPathTerminals := digraph.BFSTree(nodeID, direction); len(shortestPathTerminals) > 0 {
if shortestPathTerminals := container.BFSTree(digraph, nodeID, direction); len(shortestPathTerminals) > 0 {
var distanceSum Weight = 0

for _, shortestPathTerminal := range shortestPathTerminals {
Expand All @@ -29,22 +30,24 @@ func ClosenessForDirectedUnweightedGraph(digraph container.DirectedGraph, direct
return scores
}

func ClosenessForDirectedUnweightedGraphParallel(digraph container.DirectedGraph, direction graph.Direction, sampleFunc SampleFunc, nSamples int) map[uint64]Weight {
func ClosenessForDirectedUnweightedGraphParallel(digraph container.DirectedGraph, direction graph.Direction, sampleFunc SampleFunc, nSamples int) WeightMap {
var (
scores = make(map[uint64]Weight, nSamples)
scores = make(WeightMap, nSamples)
scoresLock = &sync.Mutex{}
workerWG = &sync.WaitGroup{}
nodeC = make(chan uint64)
)

defer util.SLogMeasure("ClosenessForDirectedUnweightedGraphParallel")()

for workerID := 0; workerID < runtime.NumCPU(); workerID++ {
workerWG.Add(1)

go func() {
defer workerWG.Done()

for nodeID := range nodeC {
if shortestPathTerminals := digraph.BFSTree(nodeID, direction); len(shortestPathTerminals) > 0 {
if shortestPathTerminals := container.BFSTree(digraph, nodeID, direction); len(shortestPathTerminals) > 0 {
var distanceSum Weight = 0

for _, shortestPathTerminal := range shortestPathTerminals {
Expand Down
22 changes: 16 additions & 6 deletions algo/katz.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package algo

import (
"log/slog"
"maps"
"math"

"github.com/specterops/dawgs/container"
"github.com/specterops/dawgs/graph"
"github.com/specterops/dawgs/util"
)

/*
Expand All @@ -31,13 +33,15 @@ High katz centrality values indicate that a node has significant influence withi
edges, as well as direct ones. The centrality score also accounts for the declining importance of more distant
relationships.
*/
func CalculateKatzCentrality(digraph container.DirectedGraph, alpha, beta, epsilon Weight, iterations int) (map[uint64]float64, bool) {
func CalculateKatzCentrality(digraph container.DirectedGraph, alpha, beta, epsilon Weight, iterations int, direction graph.Direction) (map[uint64]Weight, bool) {
var (
numNodes = digraph.Nodes().Cardinality()
centrality = make(map[uint64]float64, numNodes)
prevCentrality = make(map[uint64]float64, numNodes)
centrality = make(map[uint64]Weight, numNodes)
prevCentrality = make(map[uint64]Weight, numNodes)
)

defer util.SLogMeasure("CalculateKatzCentrality", slog.String("direction", direction.String()))()

// Initialize centrality scores to baseline
digraph.Nodes().Each(func(value uint64) bool {
centrality[value] = beta
Expand All @@ -52,15 +56,21 @@ func CalculateKatzCentrality(digraph container.DirectedGraph, alpha, beta, epsil
digraph.Nodes().Each(func(sourceNode uint64) bool {
sum := 0.0

digraph.EachAdjacent(sourceNode, graph.DirectionBoth, func(adjacentNode uint64) bool {
digraph.EachAdjacentNode(sourceNode, direction, func(adjacentNode uint64) bool {
sum += prevCentrality[adjacentNode]
return true
})

centrality[sourceNode] = beta + alpha*sum

if math.Abs(centrality[sourceNode]-prevCentrality[sourceNode]) > epsilon {
changed = true
// Only calculate epsilon tolerance if there is no tolerance violation yet detected
if !changed {
diff := math.Abs(centrality[sourceNode] - prevCentrality[sourceNode])
changed = diff > epsilon

if changed {
slog.Info("Tolerance Check Failure", slog.Float64("diff", diff), slog.Float64("epsilon", epsilon), slog.Uint64("src_node", sourceNode))
}
}

return true
Expand Down
6 changes: 6 additions & 0 deletions algo/sample.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ func sampleHighestDegrees(digraph container.DirectedGraph, nSamples int, directi
return nodeSamples
}

func SampleExact(samples []uint64) SampleFunc {
return func(digraph container.DirectedGraph, nSamples int) []uint64 {
return samples
}
}
Comment on lines +49 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add defensive copy to prevent slice aliasing.

The function returns the same slice reference on every invocation of the returned SampleFunc. If any caller modifies the returned slice, those mutations will be visible to all subsequent invocations, which can lead to subtle data integrity bugs.

Apply this diff to create a defensive copy:

 func SampleExact(samples []uint64) SampleFunc {
 	return func(digraph container.DirectedGraph, nSamples int) []uint64 {
-		return samples
+		result := make([]uint64, len(samples))
+		copy(result, samples)
+		return result
 	}
 }

Alternatively, if you want to avoid allocating on every call, you could copy once when creating the closure:

 func SampleExact(samples []uint64) SampleFunc {
+	samplesCopy := make([]uint64, len(samples))
+	copy(samplesCopy, samples)
 	return func(digraph container.DirectedGraph, nSamples int) []uint64 {
-		return samples
+		return samplesCopy
 	}
 }

The second approach is more efficient if the returned function will be called multiple times, but still prevents external mutations from affecting the captured slice.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func SampleExact(samples []uint64) SampleFunc {
return func(digraph container.DirectedGraph, nSamples int) []uint64 {
return samples
}
}
func SampleExact(samples []uint64) SampleFunc {
return func(digraph container.DirectedGraph, nSamples int) []uint64 {
result := make([]uint64, len(samples))
copy(result, samples)
return result
}
}
🤖 Prompt for AI Agents
In algo/sample.go around lines 49-53, the returned SampleFunc currently returns
the original samples slice causing aliasing and shared mutable state; to fix it
either make a defensive copy when building the closure (allocate a new []uint64,
copy(samples, newSlice), capture newSlice and return it) or copy on each
invocation (allocate a result slice in the returned func, copy from samples, and
return the new slice); choose the first option for efficiency if the closure is
called multiple times.


func SampleHighestDegrees(direction graph.Direction) SampleFunc {
return func(digraph container.DirectedGraph, nSamples int) []uint64 {
return sampleHighestDegrees(digraph, nSamples, direction)
Expand Down
Loading