Documentation

Constraints

Define business rules using constraint streams, joiners, collectors, and score types.

Constraints are the business rules that define what makes a good solution. SolverForge uses a constraint streams API - a declarative, composable way to express rules that reads like a pipeline of filters and transformations.

How Constraints Work

  1. Select entities or facts from your solution using generated source methods
  2. Filter, project, join, or group to narrow down the matches
  3. Penalize or reward to affect the score
  4. Name the constraint with .named()
let factory = ConstraintFactory::<Schedule, HardSoftScore>::new();

factory.for_each(Schedule::shifts())                              // Select all shifts
    .filter(|s| s.employee_idx.is_none())     // Keep unassigned ones
    .penalize(HardSoftScore::ONE_HARD)        // Penalize each
    .named("Unassigned shift")                // Finalize

Constraints are returned as a tuple implementing ConstraintSet<S, Sc>, which the solver evaluates incrementally as it explores moves. Generated solution sources such as Schedule::shifts() preserve source metadata for localized incremental updates. Projected streams can emit retained scoring rows from one source or one joined pair, grouped streams can use collectors such as consecutive_runs(...), collect_vec(...), and indexed_presence(...), and projected self-joins can be symmetric with equal(...) or directed with equal_bi(left_key, right_key). Direct cross joins can group joined pairs without projecting them first. Direct cross-join groups can also call complement(...) against generated target sources, and filtered keyed joins retain the filter contract on both sides of the join.

Annotate reusable constraint functions with #[solverforge_constraints] when the same grouped stream feeds multiple named terminal constraints. SolverForge then shares the retained grouped node work while keeping each terminal constraint’s name, ordering, metadata, and explanation independent. The lower-level constraint metadata borrows full ConstraintRef identity from the owning constraint. Package-qualified constraints use ConstraintRef::full_name() as the configured key; package-less constraints use the short name.

Sections