solverforge-solver 0.12.1

Solver engine for SolverForge
Documentation
use std::collections::{HashMap, HashSet};

use solverforge_core::domain::PlanningSolution;

use super::assignment_candidate::ScalarAssignmentMoveOptions;
use super::assignment_path::move_from_edits;
use super::assignment_state::ScalarAssignmentState;
use crate::builder::ScalarAssignmentBinding;
use crate::heuristic::r#move::CompoundScalarMove;

pub(crate) fn rematch_assignment_moves<S>(
    group: &ScalarAssignmentBinding<S>,
    solution: &S,
    options: ScalarAssignmentMoveOptions,
) -> Vec<CompoundScalarMove<S>>
where
    S: PlanningSolution,
{
    let mut state = ScalarAssignmentState::new(group, solution);
    let mut by_sequence: HashMap<Option<usize>, Vec<usize>> = HashMap::new();
    for entity_index in 0..group.entity_count(solution) {
        let Some(value) = state.current_value(entity_index) else {
            continue;
        };
        by_sequence
            .entry(group.sequence_key(solution, entity_index, value))
            .or_default()
            .push(entity_index);
    }

    let mut sequence_keys = by_sequence.keys().copied().collect::<Vec<_>>();
    sequence_keys.sort_unstable();

    let mut moves = Vec::new();
    let mut seen = HashSet::new();
    for sequence_key in sequence_keys {
        let Some(entities) = by_sequence.get_mut(&sequence_key) else {
            continue;
        };
        entities.sort_by_key(|entity_index| {
            (
                group.position_key(solution, *entity_index),
                group.entity_order_key(solution, *entity_index),
                *entity_index,
            )
        });
        for left_pos in 0..entities.len() {
            let right_limit = (left_pos + options.max_rematch_size).min(entities.len());
            for right_pos in (left_pos + 1)..right_limit {
                if moves.len() >= options.max_moves {
                    return moves;
                }
                let left = entities[left_pos];
                let right = entities[right_pos];
                let Some(left_value) = state.current_value(left) else {
                    continue;
                };
                let Some(right_value) = state.current_value(right) else {
                    continue;
                };
                if left_value == right_value
                    || !group.value_is_legal(solution, left, Some(right_value))
                    || !group.value_is_legal(solution, right, Some(left_value))
                {
                    continue;
                }
                let normalized = if left < right {
                    (left, right, left_value, right_value)
                } else {
                    (right, left, right_value, left_value)
                };
                if !seen.insert(normalized) {
                    continue;
                }
                let edits = [(left, Some(right_value)), (right, Some(left_value))];
                if !state.capacity_feasible_after_edits(group, solution, &edits) {
                    continue;
                }
                let scalar_edits = [
                    group.edit(left, Some(right_value)),
                    group.edit(right, Some(left_value)),
                ];
                if let Some(mov) =
                    move_from_edits(group, solution, &scalar_edits, "scalar_assignment_rematch")
                {
                    moves.push(mov);
                }
            }
        }
    }
    moves
}