solverforge-solver 0.20.4

Solver engine for SolverForge
Documentation
use super::{pair_was_checked, ScalarAssignmentBinding, ScalarAssignmentState};
use crate::planning::ScalarEdit;
use solverforge_core::fast_hash::{FastMap, FastSet};

#[derive(Default)]
pub(super) struct OverlayScratch {
    targets: FastSet<usize>,
    changed: FastMap<usize, Option<usize>>,
    capacity_deltas: FastMap<usize, isize>,
    arrivals: FastMap<(usize, usize), Vec<usize>>,
    rows: Vec<Vec<usize>>,
    edited: Vec<(usize, Option<usize>, Option<usize>)>,
    checked: Vec<(usize, usize)>,
}

impl ScalarAssignmentState {
    pub(in super::super) fn assignment_feasible_after_edits<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        edits: &[(usize, Option<usize>)],
    ) -> bool {
        self.final_edits_feasible(group, solution, edits.iter().copied())
    }

    pub(in super::super) fn scalar_edits_feasible<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        edits: &[ScalarEdit<S>],
    ) -> bool {
        self.final_edits_feasible(
            group,
            solution,
            edits
                .iter()
                .map(|edit| (edit.entity_index(), edit.to_value())),
        )
    }

    fn final_edits_feasible<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        edits: impl IntoIterator<Item = (usize, Option<usize>)>,
    ) -> bool {
        let mut overlay = std::mem::take(&mut self.overlay);
        let feasible = overlay.prepare(self, group, solution, edits, true)
            && overlay.capacity_feasible(self)
            && overlay.rules_feasible(self, group, solution);
        self.overlay = overlay;
        feasible
    }
}

impl ScalarAssignmentState {
    pub(in super::super) fn reinsert_value_allowed<S>(
        &mut self,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        edits: &[(usize, Option<usize>)],
        entity: usize,
        value: usize,
    ) -> bool {
        let mut overlay = std::mem::take(&mut self.overlay);
        let allowed = overlay.prepare(self, group, solution, edits.iter().copied(), false)
            && overlay.reinsert_allowed(self, group, solution, entity, value);
        self.overlay = overlay;
        allowed
    }
}

impl OverlayScratch {
    fn prepare<S>(
        &mut self,
        state: &ScalarAssignmentState,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        edits: impl IntoIterator<Item = (usize, Option<usize>)>,
        final_assignment: bool,
    ) -> bool {
        self.targets.clear();
        self.changed.clear();
        self.capacity_deltas.clear();
        self.edited.clear();
        self.checked.clear();
        for (_, mut row) in self.arrivals.drain() {
            row.clear();
            self.rows.push(row);
        }
        for (entity, next) in edits {
            if entity >= state.values.len()
                || !self.targets.insert(entity)
                || (final_assignment && next.is_none() && state.is_required(entity))
                || !group.value_is_legal(solution, entity, next)
            {
                return false;
            }
            let previous = state.current_value(entity);
            self.edited.push((entity, previous, next));
            if let Some(key) = previous.and_then(|v| group.capacity_key(solution, entity, v)) {
                *self.capacity_deltas.entry(key).or_default() -= 1;
            }
            if let Some(key) = next.and_then(|v| group.capacity_key(solution, entity, v)) {
                *self.capacity_deltas.entry(key).or_default() += 1;
            }
            if previous == next {
                continue;
            }
            self.changed.insert(entity, next);
            if let Some(value) = next {
                if let Some(sequence) = group.sequence_key(solution, entity, value) {
                    self.arrivals
                        .entry((value, sequence))
                        .or_insert_with(|| self.rows.pop().unwrap_or_default())
                        .push(entity);
                }
            }
        }
        true
    }

    fn reinsert_allowed<S>(
        &self,
        state: &ScalarAssignmentState,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
        entity: usize,
        value: usize,
    ) -> bool {
        if let Some(key) = group.capacity_key(solution, entity, value) {
            let count = state.occupancy.get(&key).map_or(0, Vec::len);
            let delta = self.capacity_deltas.get(&key).copied().unwrap_or(0);
            if !count.checked_add_signed(delta).is_some_and(|n| n <= 1) {
                return false;
            }
        }
        if !group.has_assignment_rule() {
            return true;
        }
        let Some(sequence) = group.sequence_key(solution, entity, value) else {
            return true;
        };
        if let Some(previous) = sequence.checked_sub(1) {
            for left in self.members(state, value, previous) {
                if left != entity
                    && !group.assignment_edge_allowed(solution, left, value, entity, value)
                {
                    return false;
                }
            }
        }
        if let Some(next) = sequence.checked_add(1) {
            for right in self.members(state, value, next) {
                if right != entity
                    && !group.assignment_edge_allowed(solution, entity, value, right, value)
                {
                    return false;
                }
            }
        }
        true
    }

    fn capacity_feasible(&self, state: &ScalarAssignmentState) -> bool {
        self.capacity_deltas.iter().all(|(key, delta)| {
            let count = state.occupancy.get(key).map_or(0, Vec::len);
            count.checked_add_signed(*delta).is_some_and(|n| n <= 1)
        })
    }

    fn members<'a>(
        &'a self,
        state: &'a ScalarAssignmentState,
        value: usize,
        sequence: usize,
    ) -> impl Iterator<Item = usize> + 'a {
        state
            .assigned_by_sequence
            .get(&(value, sequence))
            .into_iter()
            .flatten()
            .copied()
            .filter(|entity| !self.changed.contains_key(entity))
            .chain(
                self.arrivals
                    .get(&(value, sequence))
                    .into_iter()
                    .flatten()
                    .copied(),
            )
    }

    fn rules_feasible<S>(
        &mut self,
        state: &ScalarAssignmentState,
        group: &ScalarAssignmentBinding<S>,
        solution: &S,
    ) -> bool {
        if !group.has_assignment_rule() {
            return true;
        }
        let mut checked = std::mem::take(&mut self.checked);
        let mut feasible = true;
        'edits: for (entity, previous, next) in &self.edited {
            for value in [*previous, *next].into_iter().flatten() {
                let Some(sequence) = group.sequence_key(solution, *entity, value) else {
                    continue;
                };
                let edges = [
                    sequence.checked_sub(1).map(|left| (left, sequence)),
                    sequence.checked_add(1).map(|right| (sequence, right)),
                ];
                for (left_sequence, right_sequence) in edges.into_iter().flatten() {
                    for left in self.members(state, value, left_sequence) {
                        for right in self.members(state, value, right_sequence) {
                            if left == right || pair_was_checked(&checked, left, right) {
                                continue;
                            }
                            checked.push((left, right));
                            if !group.assignment_edge_allowed(solution, left, value, right, value) {
                                feasible = false;
                                break 'edits;
                            }
                        }
                    }
                }
            }
        }
        self.checked = checked;
        feasible
    }
}