Expand description
Unified monomorphized search kernel.
Tests: tests/solver.rs (general solve correctness),
tests/solution_set_invariance.rs (solution-set property test).
One tree-search skeleton — [search] — parameterized by a zero-sized
[SearchPolicy]. The policy decides the four things that actually differ
between the crate’s search modes:
| axis | [Feasibility] | [BranchBound] |
|---|---|---|
| leaf action | record, stop at max | score, keep best, never stop |
| node prune | none | optimistic-bound cutoff |
| value order | raw domain order | cost-sorted |
Every hook is #[inline] and every implementor is (near) zero-sized, so
monomorphization inlines the whole policy — no dispatch cost, the same
devirtualization the crate already applies to ConstraintEnum.
This kernel replaces the three near-verbatim recursive DFS functions that
preceded it (backtrack_recurse, backjump_recurse, bb_recurse) whose
propagate match, validity-check loop, and restore sweep were byte-identical.
Undo now runs off the shared [Trail] (touched-variable list) rather than an
O(num_vars) per-node sweep, and read-only state (weights, var_cids,
adjacency) is borrowed rather than deep-cloned per solve.
§Waiver: this file stays whole (CLOSED)
At 504 LOC the module sits four lines over the file budget, and the two
policies are visually fenced — a BranchBound-out split looks free. It
isn’t. BranchBound impls [SearchPolicy] and calls [search], so
extracting it to a sibling module forces trait SearchPolicy, fn search,
and likely Step — all currently private kernel internals — to widen to
pub(super). That re-widens three internals to buy a cosmetic file cut: an
encapsulation regression inside an encapsulation pass. The single reason to
change here is the one search skeleton; [Feasibility] and [BranchBound]
are its co-designed leaves, not separable concerns. Waiver recorded, closed.