Documentation

Planning Solutions

The top-level container that holds entities, facts, and the current score.

A planning solution is the root struct that represents your entire problem and its current solution state. It owns your entity collections, problem facts, and score field.

The #[planning_solution] Macro

use solverforge::prelude::*;

#[planning_solution(
    constraints = "crate::constraints::define_constraints",
    scalar_groups = "crate::domain::scalar_groups"
)]
pub struct Schedule {
    #[problem_fact_collection]
    pub employees: Vec<Employee>,

    #[problem_fact_collection]
    pub availability: Vec<Availability>,

    #[planning_entity_collection]
    pub shifts: Vec<Shift>,

    #[planning_score]
    pub score: Option<HardSoftScore>,
}

The constraints parameter is optional, but it is what enables the generated Solvable and Analyzable implementations used by the stock runtime. scalar_groups is optional; use it when the model declares candidate-backed or assignment-backed ScalarGroup values for grouped construction and grouped scalar local search.

Field Attributes

#[planning_entity_collection]

Marks a Vec<T> field containing planning entities. The solver iterates these collections to find variables to change.

#[planning_entity_collection]
pub shifts: Vec<Shift>,

#[problem_fact_collection]

Marks a Vec<T> field containing immutable problem facts. Used by constraints but never modified by the solver.

#[problem_fact_collection]
pub employees: Vec<Employee>,

#[planning_score]

Marks the field that holds the current solution quality. Must be Option<ScoreType>.

#[planning_score]
pub score: Option<HardSoftScore>,

Supported score types: SoftScore, HardSoftScore, HardMediumSoftScore, HardSoftDecimalScore, BendableScore.

Generated Helpers

When constraints = "..." is present, #[planning_solution] also generates:

  • Collection source methods such as Schedule::shifts() and Schedule::employees(), so constraints can start from factory.for_each(Schedule::shifts()) without ad hoc slice closures
  • Solvable for retained-job solving through SolverManager::solve(), including the typed search declaration consumed by the runtime compiler
  • Analyzable for score breakdowns via analyze()

Before candidate work begins, generated solving resolves that declaration and the model’s descriptor/runtime slots into one immutable graph. Missing targets, capabilities, providers, stable list sources, or invalid selector composition surface as runtime-build errors instead of being discovered halfway through a phase.

Generated source methods are the default way to start constraints over planning entity and problem fact collections. See Constraint Factory Methods for the full method contract and the lower-level for_each(...) escape hatch.

For advanced list-shadow workflows, the struct can also carry #[shadow_variable_updates(...)]. When present, the macro generates PlanningSolution::update_entity_shadows(...) and update_all_shadows() overrides directly on the solution type, and the stock ScoreDirector calls those hooks automatically during solving and score analysis. The update configuration can maintain inverse_field, index_field, previous_field, and next_field views of the canonical owner list before cascading, aggregate, compute, and post-update listeners run.

Requirements

  • Must be a named Rust struct
  • Must have exactly one #[planning_score] field
  • Must have at least one #[planning_entity_collection] field for stock solving

Common value-range setup lives on the entity side, for example #[planning_variable(value_range_provider = "employees")].

See Also