unifierv0.3.2

Solvers

The five solver strategies, what each can prove, and how to limit or cancel a run.

Every solver takes a ValidatedGraph and SolverOptions and returns a SolveOutcome. Because they share this signature, you can swap strategies without touching the model.

let outcome = BranchAndBoundSolver::new().solve(&graph, &SolverOptions::default());

Strategies

Solver Kind Can prove Use it for
BacktrackingSolver::new() complete tree search with propagation and dom/wdeg ordering feasibility, infeasibility pure CSP: any valid assignment, or proof that none exists
BranchAndBoundSolver::new() complete tree search with propagation, MRV ordering and optimistic-bound pruning optimality, infeasibility COP when the optimum should be proven
LocalSearchSolver::new(tabu_tenure) tabu search over value changes and swaps a good solution quickly on large models
LnsSolver::new(destroy_fraction) large neighbourhood search: relax part of the assignment, repair it by backtracking improving an existing solution on large models
ParallelSolver::new() portfolio of the four above, one thread each what its workers prove not knowing in advance which strategy fits

LocalSearchSolver and LnsSolver also implement Default. LnsSolver clamps the destroy fraction to the range 0.1 to 0.9.

LnsSolver::solve_from(&graph, &baseline, &options) starts from an existing assignment instead of a fresh backtracking solution, for example an earlier plan after a constraint has changed. The baseline is the centre of the neighbourhoods even when it is no longer feasible; only feasible solutions are returned. If the baseline leaves a variable unassigned or holds a value outside its domain, solve_from behaves like solve.

ParallelSolver runs Backtracking, Local Search, LNS and Branch & Bound at the same time. They share a best-so-far solution (the shared incumbent), so a fast incomplete worker hands Branch & Bound a strong bound to prune against. The result is the best solution any worker found, not the first one reported, and every worker thread is joined before solve returns.

Propagation

The complete solvers propagate after every decision: each constraint removes values from the domains of its variables that can no longer be part of a solution, until nothing changes or a domain runs empty. The engine is AC-3 with event-based re-queueing: only constraints on a variable whose domain actually shrank run again. Domains are reversible (checkpoint and undo) instead of being copied at every search node.

Outcomes

SolveOutcome reports what the run established, separately from whether it found a solution:

SolveStatus Meaning solution
Optimal a feasible solution, proven to have the best score; only Branch & Bound proves this, alone or inside the portfolio present
Feasible a feasible solution, not proven optimal present
Infeasible the search space was exhausted without a feasible assignment absent
Aborted(reason) the run stopped before it found a solution or proved anything absent

Aborted carries an AbortReason: Cancelled, Timeout, NodeLimit, or LocalOptimum when local search has no improving, non-tabu move left. None of these prove infeasibility.

The outcome also includes statistics (nodes expanded, elapsed time) and, for Branch & Bound, bound: an upper bound on the achievable soft score. When a search is stopped early, the gap between the solution’s score and bound shows how far from proven the result is.

When several solutions share the best score, which one is returned can differ between runs.

Limits and cancellation

use std::time::Duration;
use unifier::CancellationToken;

let token = CancellationToken::new();
let options = SolverOptions {
    time_limit: Some(Duration::from_secs(2)),
    max_nodes: Some(1_000_000),
    cancellation_token: Some(token.clone()),
    ..SolverOptions::default()
};
// Another thread can call token.cancel() to stop the search.
Field Default Effect
time_limit 10 seconds stop after this duration
max_nodes none stop after expanding this many search nodes
cancellation_token none stop when the token is cancelled
shared_incumbent none set by ParallelSolver for its workers; leave it empty

A run that stops after finding a solution returns Feasible with the best solution so far, which is what makes the solvers usable as anytime solvers. ParallelSolver only observes the token you pass in and never cancels it itself, so the token can be reused.

Edit this page on GitHub · Docs for v0.3.2