List Variables

Ordered sequence variables for routing, sequencing, and scheduling problems.

List variables model problems where the solver must determine the order of elements in a sequence — not just which value is assigned, but what comes before and after. This is essential for vehicle routing (stop ordering), job shop scheduling (operation sequencing), and similar problems.

When to Use List Variables

Use list variables when:

  • The order of assignments matters (routes, sequences)
  • Each element belongs to exactly one list
  • The solver needs to optimize both assignment and ordering

Use basic planning variables when:

  • Only the assignment matters, not the order
  • Multiple entities can share the same value

The ListVariableSolution Trait

List variable solutions implement ListVariableSolution to define the relationship between lists and their elements.

use solverforge::prelude::*;

#[problem_fact]
#[derive(Clone, Debug)]
pub struct Stop {
    #[planning_id]
    pub id: i64,
    pub location: Location,
    pub demand: i32,
}

#[planning_entity]
#[derive(Clone, Debug)]
pub struct Vehicle {
    #[planning_id]
    pub id: i64,
    pub capacity: i32,
    pub depot: Location,
    #[planning_list_variable]
    pub stops: Vec<Stop>,
}

#[planning_solution(constraints = "crate::constraints::define_constraints")]
pub struct VehicleRoutePlan {
    #[problem_fact_collection]
    pub stops: Vec<Stop>,
    #[planning_entity_collection]
    pub vehicles: Vec<Vehicle>,
    #[planning_score]
    pub score: Option<HardSoftScore>,
}

Shadow Variables for Lists

List variables support shadow variables that automatically track predecessor and successor relationships:

#[problem_fact]
#[derive(Clone, Debug)]
pub struct Stop {
    #[planning_id]
    pub id: i64,

    #[previous_element_shadow_variable(source_variable = "stops")]
    pub previous_stop: Option<Stop>,

    #[next_element_shadow_variable(source_variable = "stops")]
    pub next_stop: Option<Stop>,

    #[inverse_relation_shadow_variable(source_variable = "stops")]
    pub vehicle: Option<Vehicle>,
}

These shadow variables are maintained automatically as the solver moves elements between lists and reorders them.

List Moves

The solver uses specialized moves for list variables:

MoveDescription
ListChangeMoveMove an element from one list to another (or within the same list)
ListSwapMoveSwap two elements between or within lists
ListReverseMoveReverse a subsequence within a list
SubListChangeMoveMove a contiguous subsequence to another position
SubListSwapMoveSwap two contiguous subsequences
KOptMoveK-opt style moves for routing problems
RuinMoveRemove elements and reinsert them (ruin-and-recreate)

Example: Vehicle Routing

fn define_constraints() -> impl ConstraintSet<VehicleRoutePlan, HardSoftScore> {
    let factory = ConstraintFactory::<VehicleRoutePlan, HardSoftScore>::new();

    (
        // Hard: don't exceed vehicle capacity
        factory.for_each(|s: &VehicleRoutePlan| s.vehicles.as_slice())
            .filter(|v| v.total_demand() > v.capacity)
            .penalize_hard_with(|v: &Vehicle| {
                HardSoftScore::of_hard((v.total_demand() - v.capacity) as i64)
            })
            .named("Capacity"),

        // Soft: minimize total driving distance
        factory.for_each(|s: &VehicleRoutePlan| s.vehicles.as_slice())
            .penalize_with(|v: &Vehicle| HardSoftScore::of_soft(v.total_distance()))
            .named("Distance"),
    )
}

See Also