solverforge-solver 0.15.0

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 super::assignment_value_index::assigned_value_sequence_index;
use crate::builder::ScalarAssignmentBinding;
use crate::heuristic::r#move::CompoundScalarMove;

pub(super) struct ValueWindowCycleCursor {
    triples: Vec<(usize, usize, usize)>,
    sequence_keys: Vec<usize>,
    by_value_sequence: HashMap<(usize, usize), Vec<usize>>,
    triple_pos: usize,
    start_pos: usize,
    len: usize,
    max_len: usize,
}

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

impl ValueWindowCycleCursor {
    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 triples = Vec::new();
        for first_pos in 0..index.values.len() {
            for second_pos in first_pos + 1..index.values.len() {
                for third_pos in second_pos + 1..index.values.len() {
                    let first = index.values[first_pos];
                    let second = index.values[second_pos];
                    let third = index.values[third_pos];
                    triples.push((first, second, third));
                    triples.push((first, third, second));
                }
            }
        }
        if !triples.is_empty() {
            let triple_count = triples.len();
            triples.rotate_left(options.entity_offset % triple_count);
        }

        let max_len = options
            .max_depth
            .max(2)
            .min(index.sequence_keys.len())
            .max(2);
        Self {
            triples,
            sequence_keys: index.sequence_keys,
            by_value_sequence: index.by_value_sequence,
            triple_pos: 0,
            start_pos: 0,
            len: 2,
            max_len,
        }
    }

    pub(super) fn next<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &mut ScalarAssignmentState,
    ) -> Option<CompoundScalarMove<S>>
    where
        S: PlanningSolution,
    {
        while self.triple_pos < self.triples.len() {
            while self.start_pos < self.sequence_keys.len() {
                while self.len <= self.max_len {
                    if self.start_pos + self.len > self.sequence_keys.len() {
                        break;
                    }
                    let (first, second, third) = self.triples[self.triple_pos];
                    let sequence_window =
                        &self.sequence_keys[self.start_pos..self.start_pos + self.len];
                    self.len += 1;
                    let edits = self.window_edits(
                        group,
                        solution,
                        state,
                        (first, second, third),
                        sequence_window,
                    );
                    if edits.len() < 3
                        || !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_window_cycle",
                    ) {
                        return Some(mov);
                    }
                }
                self.start_pos += 1;
                self.len = 2;
            }
            self.triple_pos += 1;
            self.start_pos = 0;
            self.len = 2;
        }
        None
    }

    fn window_edits<S>(
        &self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &ScalarAssignmentState,
        cycle: (usize, usize, usize),
        sequence_window: &[usize],
    ) -> Vec<(usize, Option<usize>)>
    where
        S: PlanningSolution,
    {
        let mut edits = Vec::new();
        let mut touched = HashSet::new();
        let (first, second, third) = cycle;
        for sequence_key in sequence_window {
            self.push_cycle_edits(
                &mut touched,
                &mut edits,
                group,
                solution,
                state,
                CycleStep {
                    from_value: first,
                    to_value: second,
                    sequence_key: *sequence_key,
                },
            );
            self.push_cycle_edits(
                &mut touched,
                &mut edits,
                group,
                solution,
                state,
                CycleStep {
                    from_value: second,
                    to_value: third,
                    sequence_key: *sequence_key,
                },
            );
            self.push_cycle_edits(
                &mut touched,
                &mut edits,
                group,
                solution,
                state,
                CycleStep {
                    from_value: third,
                    to_value: first,
                    sequence_key: *sequence_key,
                },
            );
        }
        edits
    }

    fn push_cycle_edits<S>(
        &self,
        touched: &mut HashSet<usize>,
        edits: &mut Vec<(usize, Option<usize>)>,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        state: &ScalarAssignmentState,
        step: CycleStep,
    ) where
        S: PlanningSolution,
    {
        let Some(entities) = self
            .by_value_sequence
            .get(&(step.from_value, step.sequence_key))
        else {
            return;
        };
        for entity_index in entities {
            if touched.insert(*entity_index)
                && 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)));
            }
        }
    }
}