"""Adaptive compute allocation under the 30-minute wall clock. Symbolic solvers cost ~0; the budget really governs LLM calls (CEGIS rounds and best-of-N). Strategy: reserve a safety margin, spread the rest over the LLM-needing puzzles, and degrade rounds/N as the clock runs down — never let the tail of the test set hit the fallback because the head overspent. """ from __future__ import annotations import time class Budget: def __init__(self, total_seconds: float = 1620.0, safety_margin: float = 120.0): self.start = time.monotonic() self.total = total_seconds self.safety = safety_margin def elapsed(self) -> float: return time.monotonic() - self.start def remaining(self) -> float: return self.total - self.safety - self.elapsed() def exhausted(self) -> bool: return self.remaining() <= 0 def cegis_rounds(self, puzzles_left: int, seconds_per_round: float = 12.0) -> int: """How many CEGIS refinement rounds this puzzle can afford, assuming every remaining puzzle needs at least one proposal.""" if puzzles_left <= 0: puzzles_left = 1 per_puzzle = self.remaining() / puzzles_left rounds = int(per_puzzle / seconds_per_round) - 1 # -1 for the initial proposal return max(0, min(rounds, 3)) def allow_llm(self, puzzles_left: int, seconds_per_call: float = 12.0) -> bool: """False once only fallback-speed work fits for the remaining set.""" return self.remaining() > puzzles_left * 0.2 + seconds_per_call