Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Solver

Solver finds solutions to a puzzle. Three live impls today, one contract.

/// One valid solution: the candidate set per coord narrowed to a
/// singleton, expressed as a flat coord → mark map. Genre-specific
/// readers (e.g., "extract bulb positions") project from this.
pub type Solution = HashMap<Coord, Mark>;

/// Up to `max_solutions` solutions, in unspecified order. Length
/// ≤ `max_solutions`; equal to the true solution count if fewer.
/// The generator's uniqueness gate calls `solve(p, 2)` and checks
/// `len == 1`.
pub type SolutionSet = Vec<Solution>;

pub trait Solver {
    type Config: Default + Serialize + DeserializeOwned;
    const NAME: &'static str;
    fn new(config: Self::Config) -> Self;
    fn solve(&self, puzzle: &Puzzle, max_solutions: usize) -> SolutionSet;
}

/// Cross-puzzle solver options. Per-impl Configs typically embed
/// this with `#[serde(flatten)]`.
pub struct SolverParams {
    pub time_budget: Duration,
}

One CSP engine for every genre

The generic FastSolver, driven through Propagator over FastGame, is the single solve path for every genre. The rule vocabulary — DegreeIn, CellMin, LoopFreezesCounts, plus the per-cell counter machinery the engine maintains automatically — is expressive enough that no per-genre solver override is needed. Per-genre fast solvers (e.g. a future AkariFastSolver that registers fast genre-specific propagators while keeping the same outer DFS shape) are post-MVP; the generic impl earns its keep on every benchmark we run today.

OR-Tools as the sanity-check oracle

A separate algorithm/ortools/ impl wraps Google OR-Tools’ CP-SAT behind the same Solver trait. It’s not on the hot path — FastSolver outperforms it for everything we ship. What it provides is a totally different code path with the same contract: every generated puzzle and every imported dataset can be cross-checked against an industry-grade CSP solver to confirm the answer set matches. When FastSolver and OR-Tools disagree, the bug is in our engine, full stop. Fallout from going hard on the CSP-engine path — once the rule vocabulary maps cleanly onto CP-SAT primitives, getting a second opinion is mostly plumbing.

Cross-checks

FastSolver, SimpleSolver, and the OR-Tools wrapper all implement the same Solver trait. The benchmark harness runs them in the same Criterion invocation on a fixed puzzle set; the differential tests run them over a fuzzed corpus and assert solve(p, k).len() agrees and the returned sets are equal as sets when fewer than max_solutions exist. SimpleSolver is the brute-force baseline, OR-Tools is the independent oracle, FastSolver is the production path. Three impls, one contract, three angles on every disagreement.