@@ -13,11 +13,24 @@ import (
1313 "github.com/matzehuels/stacktower/pkg/dag/perm"
1414)
1515
16- const maxCandidates = 10000
16+ const maxCandidatesBase = 10000
1717
1818type OptimalSearch struct {
1919 Progress func (explored , pruned , best int )
2020 Timeout time.Duration
21+ Debug func (info DebugInfo )
22+ }
23+
24+ type DebugInfo struct {
25+ Rows []RowDebugInfo
26+ MaxDepth int
27+ TotalRows int
28+ }
29+
30+ type RowDebugInfo struct {
31+ Row int
32+ NodeCount int
33+ Candidates int
2134}
2235
2336func (o OptimalSearch ) OrderRows (g * dag.DAG ) map [int ][]string {
@@ -42,12 +55,13 @@ func (o OptimalSearch) OrderRows(g *dag.DAG) map[int][]string {
4255 defer cancel ()
4356
4457 s := & solver {
45- g : g ,
46- fg : newFastGraph (g , rows ),
47- rows : rows ,
48- rowNodes : make (map [int ][]* dag.Node , len (rows )),
49- ctx : ctx ,
50- cancel : cancel ,
58+ g : g ,
59+ fg : newFastGraph (g , rows ),
60+ rows : rows ,
61+ rowNodes : make (map [int ][]* dag.Node , len (rows )),
62+ candLimit : calcCandidateLimit (len (rows )),
63+ ctx : ctx ,
64+ cancel : cancel ,
5165 }
5266 s .bestScore .Store (int64 (initialScore ))
5367 s .bestPath .Store (toIndexPath (g , rows , initial ))
@@ -66,6 +80,10 @@ func (o OptimalSearch) OrderRows(g *dag.DAG) map[int][]string {
6680 o .report (int (s .explored .Load ()), int (s .pruned .Load ()), int (s .bestScore .Load ()))
6781 }
6882
83+ if o .Debug != nil {
84+ o .Debug (s .collectDebugInfo (initial ))
85+ }
86+
6987 return toStringOrder (s .rowNodes , s .rows , s .bestPath .Load ().([][]int ))
7088}
7189
@@ -76,20 +94,32 @@ func (o OptimalSearch) report(explored, pruned, best int) {
7694}
7795
7896type solver struct {
79- g * dag.DAG
80- fg * fastGraph
81- rows []int
82- rowNodes map [int ][]* dag.Node
97+ g * dag.DAG
98+ fg * fastGraph
99+ rows []int
100+ rowNodes map [int ][]* dag.Node
101+ candLimit int
83102
84103 bestScore atomic.Int64
85104 bestPath atomic.Value
86105 explored atomic.Int64
87106 pruned atomic.Int64
107+ maxDepth atomic.Int64
88108
89109 ctx context.Context
90110 cancel context.CancelFunc
91111}
92112
113+ func calcCandidateLimit (numRows int ) int {
114+ if numRows <= 3 {
115+ return maxCandidatesBase
116+ }
117+ // Linear scaling: more rows = fewer candidates per row
118+ // 5 rows → 2000, 10 rows → 1000, 20 rows → 500
119+ limit := maxCandidatesBase / numRows
120+ return max (100 , min (1000 , limit ))
121+ }
122+
93123func (s * solver ) search () {
94124 workers := runtime .GOMAXPROCS (0 )
95125 parallelRow := s .findParallelRow ()
@@ -201,6 +231,14 @@ func (s *solver) dfs(depth, score int, path [][]int, ws *dag.CrossingWorkspace)
201231 return
202232 }
203233
234+ // Track max depth reached
235+ for {
236+ cur := s .maxDepth .Load ()
237+ if int64 (depth ) <= cur || s .maxDepth .CompareAndSwap (cur , int64 (depth )) {
238+ break
239+ }
240+ }
241+
204242 if score >= int (s .bestScore .Load ()) {
205243 s .pruned .Add (1 )
206244 return
@@ -261,7 +299,7 @@ func (s *solver) generateC1PCandidates(depth int, nodes []*dag.Node, prevOrder [
261299 return s .fallbackPermutations (n )
262300 }
263301
264- limit := maxCandidates
302+ limit := s . candLimit
265303 if n <= 8 {
266304 limit = tree .ValidCount ()
267305 }
@@ -306,7 +344,7 @@ func (s *solver) fallbackPermutations(n int) [][]int {
306344 if n <= 8 {
307345 return perm .Generate (n , - 1 )
308346 }
309- return perm .Generate (n , maxCandidates )
347+ return perm .Generate (n , s . candLimit )
310348}
311349
312350func (s * solver ) updateBest (path [][]int , score int ) {
@@ -331,6 +369,38 @@ func (s *solver) updateBest(path [][]int, score int) {
331369 }
332370}
333371
372+ func (s * solver ) collectDebugInfo (initialOrder map [int ][]string ) DebugInfo {
373+ info := DebugInfo {
374+ TotalRows : len (s .rows ),
375+ MaxDepth : int (s .maxDepth .Load ()),
376+ Rows : make ([]RowDebugInfo , len (s .rows )),
377+ }
378+
379+ path := toIndexPath (s .g , s .rows , initialOrder )
380+
381+ for i , r := range s .rows {
382+ nodes := s .rowNodes [r ]
383+ rowInfo := RowDebugInfo {
384+ Row : r ,
385+ NodeCount : len (nodes ),
386+ }
387+
388+ if len (nodes ) <= 1 {
389+ rowInfo .Candidates = 1
390+ } else if i == 0 {
391+ rowInfo .Candidates = min (perm .Factorial (len (nodes )), s .candLimit )
392+ } else {
393+ prevNodes := s .rowNodes [s .rows [i - 1 ]]
394+ candidates := s .generateC1PCandidates (i , nodes , path [i - 1 ], prevNodes )
395+ rowInfo .Candidates = len (candidates )
396+ }
397+
398+ info .Rows [i ] = rowInfo
399+ }
400+
401+ return info
402+ }
403+
334404func (s * solver ) monitor (fn func (int , int , int )) {
335405 ticker := time .NewTicker (250 * time .Millisecond )
336406 defer ticker .Stop ()
0 commit comments