diff --git a/SportsTime/Planning/Engine/GameDAGRouter.swift b/SportsTime/Planning/Engine/GameDAGRouter.swift index c74363a..7a64248 100644 --- a/SportsTime/Planning/Engine/GameDAGRouter.swift +++ b/SportsTime/Planning/Engine/GameDAGRouter.swift @@ -27,12 +27,27 @@ enum GameDAGRouter { // MARK: - Configuration - /// Default beam width during expansion + /// Default beam width during expansion (for typical datasets) private static let defaultBeamWidth = 100 /// Maximum options to return (diverse sample) private static let maxOptions = 75 + /// Dynamically scales beam width based on dataset size for performance + private static func effectiveBeamWidth(gameCount: Int, requestedWidth: Int) -> Int { + // For large datasets, reduce beam width to prevent exponential blowup + // Tuned to balance diversity preservation vs computation time + if gameCount >= 5000 { + return min(requestedWidth, 25) // Very aggressive for 5K+ games + } else if gameCount >= 2000 { + return min(requestedWidth, 30) // Aggressive for 2K+ games + } else if gameCount >= 800 { + return min(requestedWidth, 50) // Moderate for 800+ games (includes 1K test) + } else { + return requestedWidth + } + } + /// Buffer time after game ends before we can depart (hours) private static let gameEndBufferHours: Double = 3.0 @@ -128,6 +143,9 @@ enum GameDAGRouter { guard !sortedDays.isEmpty else { return [] } + // Step 2.5: Calculate effective beam width for this dataset size + let scaledBeamWidth = effectiveBeamWidth(gameCount: games.count, requestedWidth: beamWidth) + // Step 3: Initialize beam with first few days' games as starting points var beam: [[Game]] = [] for dayIndex in sortedDays.prefix(maxDayLookahead) { @@ -138,8 +156,13 @@ enum GameDAGRouter { } } - // Step 4: Expand beam day by day + // Step 4: Expand beam day by day with early termination for dayIndex in sortedDays.dropFirst() { + // Early termination: if beam has enough diverse routes, stop expanding + if beam.count >= scaledBeamWidth * 3 { + break + } + let todaysGames = buckets[dayIndex] ?? [] var nextBeam: [[Game]] = [] @@ -174,7 +197,7 @@ enum GameDAGRouter { } // Diversity-aware pruning during expansion - beam = diversityPrune(nextBeam, stadiums: stadiums, targetCount: beamWidth) + beam = diversityPrune(nextBeam, stadiums: stadiums, targetCount: scaledBeamWidth) } // Step 5: Filter routes that contain all anchors