solverforge-solver 0.15.0

Solver engine for SolverForge
Documentation
use std::collections::HashMap;

use solverforge_core::domain::PlanningSolution;

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

pub(super) struct ValueRunGapSwapCursor {
    pairs: Vec<(usize, usize)>,
    sequence_keys: Vec<usize>,
    by_value_sequence: HashMap<(usize, usize), Vec<usize>>,
    pair_pos: usize,
    fill_pos: usize,
    release_pos: usize,
    fill_sequences: Vec<usize>,
    release_sequences: Vec<usize>,
}

#[derive(Clone, Copy)]
struct RunGapWindow {
    target_value: usize,
    source_value: usize,
    fill_sequence: usize,
    release_sequence: usize,
}

#[derive(Clone, Copy)]
struct ValueStep {
    from_value: usize,
    to_value: usize,
    sequence: usize,
}

impl ValueRunGapSwapCursor {
    pub(super) fn new<S>(
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &ScalarAssignmentState,
        options: ScalarAssignmentMoveOptions,
    ) -> Self
    where
        S: PlanningSolution,
    {
        let index = assigned_value_sequence_index(group, solution, state, options);
        let mut pairs = Vec::new();
        for target in &index.values {
            for source in &index.values {
                if target != source {
                    pairs.push((*target, *source));
                }
            }
        }
        if !pairs.is_empty() {
            let pair_count = pairs.len();
            pairs.rotate_left(options.entity_offset % pair_count);
        }

        Self {
            pairs,
            sequence_keys: index.sequence_keys,
            by_value_sequence: index.by_value_sequence,
            pair_pos: 0,
            fill_pos: 0,
            release_pos: 0,
            fill_sequences: Vec::new(),
            release_sequences: Vec::new(),
        }
    }

    pub(super) fn next<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &mut ScalarAssignmentState,
    ) -> Option<CompoundScalarMove<S>>
    where
        S: PlanningSolution,
    {
        while self.pair_pos < self.pairs.len() {
            self.ensure_pair_loaded();
            while self.fill_pos < self.fill_sequences.len() {
                while self.release_pos < self.release_sequences.len() {
                    let (target_value, source_value) = self.pairs[self.pair_pos];
                    let fill_sequence = self.fill_sequences[self.fill_pos];
                    let release_sequence = self.release_sequences[self.release_pos];
                    self.release_pos += 1;
                    let edits = self.swap_edits(
                        group,
                        solution,
                        state,
                        RunGapWindow {
                            target_value,
                            source_value,
                            fill_sequence,
                            release_sequence,
                        },
                    );
                    if edits.len() < 2
                        || !state.assignment_feasible_after_edits(group, solution, &edits)
                    {
                        continue;
                    }
                    let scalar_edits = edits
                        .iter()
                        .map(|(entity_index, value)| group.edit(*entity_index, *value))
                        .collect::<Vec<_>>();
                    if let Some(mov) = move_from_edits(
                        group,
                        solution,
                        &scalar_edits,
                        "scalar_assignment_value_run_gap_swap",
                    ) {
                        return Some(mov);
                    }
                }
                self.fill_pos += 1;
                self.release_pos = 0;
            }
            self.advance_pair();
        }
        None
    }

    fn ensure_pair_loaded(&mut self) {
        if !self.fill_sequences.is_empty() || !self.release_sequences.is_empty() {
            return;
        }
        let (target_value, source_value) = self.pairs[self.pair_pos];
        self.fill_sequences = self
            .sequence_keys
            .iter()
            .copied()
            .filter(|sequence| {
                self.has_value(source_value, *sequence)
                    && self.is_single_gap(target_value, *sequence)
            })
            .collect();
        self.release_sequences = self
            .sequence_keys
            .iter()
            .copied()
            .filter(|sequence| {
                self.has_value(target_value, *sequence) && !self.has_value(source_value, *sequence)
            })
            .collect();
        let by_value_sequence = &self.by_value_sequence;
        self.release_sequences.sort_by_key(|sequence| {
            (
                !is_single_island(by_value_sequence, target_value, *sequence),
                *sequence,
            )
        });
    }

    fn advance_pair(&mut self) {
        self.pair_pos += 1;
        self.fill_pos = 0;
        self.release_pos = 0;
        self.fill_sequences.clear();
        self.release_sequences.clear();
    }

    fn swap_edits<S>(
        &self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &ScalarAssignmentState,
        window: RunGapWindow,
    ) -> Vec<(usize, Option<usize>)>
    where
        S: PlanningSolution,
    {
        let mut edits = Vec::new();
        self.push_reassign_edits(
            group,
            solution,
            state,
            ValueStep {
                from_value: window.source_value,
                to_value: window.target_value,
                sequence: window.fill_sequence,
            },
            &mut edits,
        );
        self.push_reassign_edits(
            group,
            solution,
            state,
            ValueStep {
                from_value: window.target_value,
                to_value: window.source_value,
                sequence: window.release_sequence,
            },
            &mut edits,
        );
        edits
    }

    fn push_reassign_edits<S>(
        &self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &ScalarAssignmentState,
        step: ValueStep,
        edits: &mut Vec<(usize, Option<usize>)>,
    ) where
        S: PlanningSolution,
    {
        let Some(entities) = self
            .by_value_sequence
            .get(&(step.from_value, step.sequence))
        else {
            return;
        };
        for entity_index in entities {
            if state.current_value(*entity_index) != Some(step.to_value)
                && group.value_is_legal(solution, *entity_index, Some(step.to_value))
            {
                edits.push((*entity_index, Some(step.to_value)));
            }
        }
    }

    fn is_single_gap(&self, value: usize, sequence: usize) -> bool {
        !self.has_value(value, sequence)
            && sequence
                .checked_sub(1)
                .is_some_and(|previous| self.has_value(value, previous))
            && sequence
                .checked_add(1)
                .is_some_and(|next| self.has_value(value, next))
    }

    fn has_value(&self, value: usize, sequence: usize) -> bool {
        self.by_value_sequence
            .get(&(value, sequence))
            .is_some_and(|entities| !entities.is_empty())
    }
}

fn is_single_island(
    by_value_sequence: &HashMap<(usize, usize), Vec<usize>>,
    value: usize,
    sequence: usize,
) -> bool {
    has_value(by_value_sequence, value, sequence)
        && sequence
            .checked_sub(1)
            .is_none_or(|previous| !has_value(by_value_sequence, value, previous))
        && sequence
            .checked_add(1)
            .is_none_or(|next| !has_value(by_value_sequence, value, next))
}

fn has_value(
    by_value_sequence: &HashMap<(usize, usize), Vec<usize>>,
    value: usize,
    sequence: usize,
) -> bool {
    by_value_sequence
        .get(&(value, sequence))
        .is_some_and(|entities| !entities.is_empty())
}