Skip to content

Commit 88f7343

Browse files
committed
wip
1 parent 1e95603 commit 88f7343

File tree

8 files changed

+134
-80
lines changed

8 files changed

+134
-80
lines changed

algo/scc.go

Lines changed: 69 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package algo
22

33
import (
4+
"context"
45
"math"
56

67
"github.com/gammazero/deque"
@@ -10,7 +11,7 @@ import (
1011
"github.com/specterops/dawgs/util"
1112
)
1213

13-
func StronglyConnectedComponents(digraph container.DirectedGraph) ([]cardinality.Duplex[uint64], map[uint64]uint64) {
14+
func StronglyConnectedComponents(ctx context.Context, digraph container.DirectedGraph) ([]cardinality.Duplex[uint64], map[uint64]uint64) {
1415
defer util.SLogMeasure("StronglyConnectedComponents")()
1516

1617
type descentCursor struct {
@@ -34,86 +35,84 @@ func StronglyConnectedComponents(digraph container.DirectedGraph) ([]cardinality
3435
)
3536

3637
digraph.EachNode(func(node uint64) bool {
37-
if _, visited := visitedIndex[node]; visited {
38-
return true
39-
}
40-
41-
dfsDescentStack = append(dfsDescentStack, &descentCursor{
42-
id: node,
43-
branches: digraph.AdjacentNodes(node, graph.DirectionOutbound),
44-
branchIdx: 0,
45-
})
46-
47-
for len(dfsDescentStack) > 0 {
48-
nextCursor := dfsDescentStack[len(dfsDescentStack)-1]
38+
if _, visited := visitedIndex[node]; !visited {
39+
dfsDescentStack = append(dfsDescentStack, &descentCursor{
40+
id: node,
41+
branches: digraph.AdjacentNodes(node, graph.DirectionOutbound),
42+
branchIdx: 0,
43+
})
4944

50-
if nextCursor.branchIdx == 0 {
51-
// First visit of this node
52-
visitedIndex[nextCursor.id] = index
53-
lowLinks[nextCursor.id] = index
54-
index += 1
45+
for len(dfsDescentStack) > 0 {
46+
nextCursor := dfsDescentStack[len(dfsDescentStack)-1]
5547

56-
stack = append(stack, nextCursor.id)
57-
onStack.Add(nextCursor.id)
58-
} else if lastSearchedNodeID != nextCursor.id {
59-
// Revisiting this node from a descending DFS
60-
lowLinks[nextCursor.id] = min(lowLinks[nextCursor.id], lowLinks[lastSearchedNodeID])
61-
}
48+
if nextCursor.branchIdx == 0 {
49+
// First visit of this node
50+
visitedIndex[nextCursor.id] = index
51+
lowLinks[nextCursor.id] = index
52+
index += 1
6253

63-
// Set to the current cursor ID for ascent
64-
lastSearchedNodeID = nextCursor.id
65-
66-
if nextCursor.branchIdx < len(nextCursor.branches) {
67-
// Advance to the next branch
68-
nextBranchID := nextCursor.branches[nextCursor.branchIdx]
69-
nextCursor.branchIdx += 1
70-
71-
if _, visited := visitedIndex[nextBranchID]; !visited {
72-
// This node has not been visited yet, run a DFS for it
73-
lastSearchedNodeID = nextBranchID
74-
75-
dfsDescentStack = append(dfsDescentStack, &descentCursor{
76-
id: nextBranchID,
77-
branches: digraph.AdjacentNodes(nextBranchID, graph.DirectionOutbound),
78-
branchIdx: 0,
79-
})
80-
} else if onStack.Contains(nextBranchID) {
81-
// Branch is on the traversal stack; hence it is also in the current SCC
82-
lowLinks[nextCursor.id] = min(lowLinks[nextCursor.id], visitedIndex[nextBranchID])
54+
stack = append(stack, nextCursor.id)
55+
onStack.Add(nextCursor.id)
56+
} else if lastSearchedNodeID != nextCursor.id {
57+
// Revisiting this node from a descending DFS
58+
lowLinks[nextCursor.id] = min(lowLinks[nextCursor.id], lowLinks[lastSearchedNodeID])
8359
}
84-
} else {
85-
// Finished visiting branches; exiting node
86-
dfsDescentStack = dfsDescentStack[:len(dfsDescentStack)-1]
8760

88-
if lowLinks[nextCursor.id] == visitedIndex[nextCursor.id] {
89-
var (
90-
scc = cardinality.NewBitmap64()
91-
sccID = uint64(len(stronglyConnectedComponents))
92-
)
61+
// Set to the current cursor ID for ascent
62+
lastSearchedNodeID = nextCursor.id
63+
64+
if nextCursor.branchIdx < len(nextCursor.branches) {
65+
// Advance to the next branch
66+
nextBranchID := nextCursor.branches[nextCursor.branchIdx]
67+
nextCursor.branchIdx += 1
68+
69+
if _, visited := visitedIndex[nextBranchID]; !visited {
70+
// This node has not been visited yet, run a DFS for it
71+
lastSearchedNodeID = nextBranchID
72+
73+
dfsDescentStack = append(dfsDescentStack, &descentCursor{
74+
id: nextBranchID,
75+
branches: digraph.AdjacentNodes(nextBranchID, graph.DirectionOutbound),
76+
branchIdx: 0,
77+
})
78+
} else if onStack.Contains(nextBranchID) {
79+
// Branch is on the traversal stack; hence it is also in the current SCC
80+
lowLinks[nextCursor.id] = min(lowLinks[nextCursor.id], visitedIndex[nextBranchID])
81+
}
82+
} else {
83+
// Finished visiting branches; exiting node
84+
dfsDescentStack = dfsDescentStack[:len(dfsDescentStack)-1]
85+
86+
if lowLinks[nextCursor.id] == visitedIndex[nextCursor.id] {
87+
var (
88+
scc = cardinality.NewBitmap64()
89+
sccID = uint64(len(stronglyConnectedComponents))
90+
)
9391

94-
for {
95-
// Unwind the stack to the root of the component
96-
currentNode := stack[len(stack)-1]
97-
stack = stack[:len(stack)-1]
92+
for {
93+
// Unwind the stack to the root of the component
94+
currentNode := stack[len(stack)-1]
95+
stack = stack[:len(stack)-1]
9896

99-
onStack.Remove(currentNode)
97+
onStack.Remove(currentNode)
10098

101-
scc.Add(currentNode)
99+
scc.Add(currentNode)
102100

103-
// Reverse index origin node to SCC
104-
nodeToSCCIndex[currentNode] = sccID
101+
// Reverse index origin node to SCC
102+
nodeToSCCIndex[currentNode] = sccID
105103

106-
if currentNode == nextCursor.id {
107-
break
104+
if currentNode == nextCursor.id {
105+
break
106+
}
108107
}
109-
}
110108

111-
stronglyConnectedComponents = append(stronglyConnectedComponents, scc)
109+
stronglyConnectedComponents = append(stronglyConnectedComponents, scc)
110+
}
112111
}
113112
}
114113
}
115114

116-
return true
115+
return util.IsContextLive(ctx)
117116
})
118117

119118
return stronglyConnectedComponents, nodeToSCCIndex
@@ -260,9 +259,9 @@ func (s ComponentGraph) OriginReachable(startID, endID uint64) bool {
260259
return s.ComponentReachable(startComponent, endComponent)
261260
}
262261

263-
func NewComponentGraph(originGraph container.DirectedGraph) ComponentGraph {
262+
func NewComponentGraph(ctx context.Context, originGraph container.DirectedGraph) ComponentGraph {
264263
var (
265-
componentMembers, memberComponentLookup = StronglyConnectedComponents(originGraph)
264+
componentMembers, memberComponentLookup = StronglyConnectedComponents(ctx, originGraph)
266265
componentDigraph = container.NewAdjacencyMapGraph()
267266
nextEdgeID = uint64(1)
268267
)
@@ -283,7 +282,7 @@ func NewComponentGraph(originGraph container.DirectedGraph) ComponentGraph {
283282
nextEdgeID += 1
284283
}
285284

286-
return true
285+
return util.IsContextLive(ctx)
287286
})
288287

289288
originGraph.EachAdjacentNode(node, graph.DirectionOutbound, func(adjacent uint64) bool {
@@ -292,10 +291,10 @@ func NewComponentGraph(originGraph container.DirectedGraph) ComponentGraph {
292291
nextEdgeID += 1
293292
}
294293

295-
return true
294+
return util.IsContextLive(ctx)
296295
})
297296

298-
return true
297+
return util.IsContextLive(ctx)
299298
})
300299

301300
return ComponentGraph{

container/digraph.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package container
22

33
import (
4-
"fmt"
5-
64
"github.com/gammazero/deque"
75
"github.com/specterops/dawgs/cardinality"
86
"github.com/specterops/dawgs/graph"
@@ -28,8 +26,6 @@ func (s KindMap) FindFirst(id uint64) graph.Kind {
2826
}
2927
}
3028

31-
panic(fmt.Sprintf("Can't find kind for edge ID %d", id))
32-
3329
return nil
3430
}
3531

container/fetch.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,13 @@ type TSDB struct {
104104
EdgeKinds KindMap
105105
}
106106

107+
func NewTSDB() TSDB {
108+
return TSDB{
109+
Triplestore: NewTriplestore(),
110+
EdgeKinds: KindMap{},
111+
}
112+
}
113+
107114
func FetchTriplestore(ctx context.Context, graphDB database.Instance, filter cypher.SyntaxNode) (TSDB, error) {
108115
tsdb := TSDB{
109116
Triplestore: NewTriplestore(),

container/triplestore.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,51 @@ func (s *triplestore) EachAdjacentEdge(node uint64, direction graph.Direction, d
182182
})
183183
}
184184

185+
func TSDFS(ts Triplestore, nodeID uint64, direction graph.Direction, maxDepth int, descentFilter func(edge Edge) bool, handler func(segment *Segment) bool) int {
186+
var (
187+
traversals deque.Deque[*Segment]
188+
numImcompletePaths = 0
189+
)
190+
191+
traversals.PushBack(&Segment{
192+
Node: nodeID,
193+
})
194+
195+
for remainingTraversals := traversals.Len(); remainingTraversals > 0; remainingTraversals = traversals.Len() {
196+
var (
197+
nextSegment = traversals.PopBack()
198+
segmentDepth = nextSegment.Depth()
199+
depthExceded = maxDepth > 0 && maxDepth < segmentDepth
200+
)
201+
202+
if !depthExceded {
203+
ts.EachAdjacentEdge(nextSegment.Node, direction, func(nextEdge Edge) bool {
204+
if descentFilter(nextEdge) {
205+
traversals.PushBack(&Segment{
206+
Node: nextEdge.Pick(direction),
207+
Edge: nextEdge.ID,
208+
Previous: nextSegment,
209+
})
210+
}
211+
212+
return true
213+
})
214+
}
215+
216+
if segmentDepth > 1 && remainingTraversals-1 == traversals.Len() {
217+
if depthExceded {
218+
numImcompletePaths += 1
219+
}
220+
221+
if !handler(nextSegment) {
222+
break
223+
}
224+
}
225+
}
226+
227+
return numImcompletePaths
228+
}
229+
185230
func TSBFS(ts Triplestore, nodeID uint64, direction graph.Direction, maxDepth int, descentFilter func(edge Edge) bool, handler func(segment *Segment) bool) int {
186231
var (
187232
traversals deque.Deque[*Segment]

database/driver.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,6 @@ type Result interface {
2020
Scan(scanTargets ...any) error
2121
Error() error
2222
Close(ctx context.Context) error
23-
24-
// Values returns the next values array from the result.
25-
//
26-
// Deprecated: This function will be removed in future version.
2723
Values() []any
2824
}
2925

go.mod

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ require (
1515
github.com/jackc/pgx/v5 v5.7.6
1616
github.com/neo4j/neo4j-go-driver/v5 v5.28.4
1717
github.com/stretchr/testify v1.11.1
18-
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9
19-
github.com/specterops/dawgs v0.3.1
2018
)
2119

2220
require (
@@ -32,6 +30,7 @@ require (
3230
github.com/mschoch/smat v0.2.0 // indirect
3331
github.com/pmezard/go-difflib v1.0.0 // indirect
3432
golang.org/x/crypto v0.46.0 // indirect
33+
golang.org/x/exp v0.0.0-20251209150349-8475f28825e9 // indirect
3534
golang.org/x/sync v0.19.0 // indirect
3635
golang.org/x/text v0.32.0 // indirect
3736
gopkg.in/yaml.v3 v3.0.1 // indirect

util/context.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package util
2+
3+
import "context"
4+
5+
func IsContextLive(ctx context.Context) bool {
6+
return ctx.Err() == nil
7+
}

util/slog.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,8 @@ func SLogMeasure(msg string, args ...any) func(args ...any) {
2424
slog.Info(msg, exitArgs...)
2525
}
2626
}
27+
28+
func SLogError(msg string, err error, args ...any) {
29+
allArgs := append([]any{slog.String("err", err.Error())}, args...)
30+
slog.Error(msg, allArgs...)
31+
}

0 commit comments

Comments
 (0)