solverforge-solver 0.15.0

Solver engine for SolverForge
Documentation
use std::fmt::{self, Debug};

use smallvec::{smallvec, SmallVec};
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;

use super::metadata::{
    encode_option_usize, encode_usize, hash_str, MoveTabuScope, MoveTabuSignature,
};
use super::{Move, MoveAffectedEntity};

pub const COMPOUND_SCALAR_VARIABLE: &str = "compound_scalar";

pub type ScalarEditLegality<S> = fn(&S, usize, usize, Option<usize>) -> bool;

#[derive(Clone)]
pub struct CompoundScalarEdit<S> {
    pub descriptor_index: usize,
    pub entity_index: usize,
    pub variable_index: usize,
    pub variable_name: &'static str,
    pub to_value: Option<usize>,
    pub getter: fn(&S, usize, usize) -> Option<usize>,
    pub setter: fn(&mut S, usize, usize, Option<usize>),
    pub value_is_legal: Option<ScalarEditLegality<S>>,
}

impl<S> Debug for CompoundScalarEdit<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CompoundScalarEdit")
            .field("descriptor_index", &self.descriptor_index)
            .field("entity_index", &self.entity_index)
            .field("variable_index", &self.variable_index)
            .field("variable_name", &self.variable_name)
            .field("to_value", &self.to_value)
            .finish()
    }
}

#[derive(Clone)]
pub struct CompoundScalarMove<S> {
    reason: &'static str,
    variable_label: &'static str,
    edits: Vec<CompoundScalarEdit<S>>,
    entity_indices: Vec<usize>,
    require_hard_improvement: bool,
    construction_value_order_key: Option<i64>,
}

impl<S> CompoundScalarMove<S> {
    pub fn new(reason: &'static str, edits: Vec<CompoundScalarEdit<S>>) -> Self {
        Self::with_label(reason, COMPOUND_SCALAR_VARIABLE, edits)
    }

    pub fn with_label(
        reason: &'static str,
        variable_label: &'static str,
        edits: Vec<CompoundScalarEdit<S>>,
    ) -> Self {
        let mut entity_indices = edits
            .iter()
            .map(|edit| edit.entity_index)
            .collect::<Vec<_>>();
        entity_indices.sort_unstable();
        entity_indices.dedup();
        Self {
            reason,
            variable_label,
            edits,
            entity_indices,
            require_hard_improvement: false,
            construction_value_order_key: None,
        }
    }

    pub fn with_require_hard_improvement(mut self, require_hard_improvement: bool) -> Self {
        self.require_hard_improvement = require_hard_improvement;
        self
    }

    pub(crate) fn with_construction_value_order_key(mut self, order_key: Option<i64>) -> Self {
        self.construction_value_order_key = order_key;
        self
    }

    pub(crate) fn construction_value_order_key(&self) -> Option<i64> {
        self.construction_value_order_key
    }

    pub fn edits(&self) -> &[CompoundScalarEdit<S>] {
        &self.edits
    }

    pub fn reason(&self) -> &'static str {
        self.reason
    }
}

impl<S> Debug for CompoundScalarMove<S> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CompoundScalarMove")
            .field("reason", &self.reason)
            .field("variable_label", &self.variable_label)
            .field("edits", &self.edits)
            .field("require_hard_improvement", &self.require_hard_improvement)
            .field(
                "construction_value_order_key",
                &self.construction_value_order_key,
            )
            .finish()
    }
}

impl<S> Move<S> for CompoundScalarMove<S>
where
    S: PlanningSolution,
{
    type Undo = Vec<Option<usize>>;

    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
        if self.edits.is_empty() {
            return false;
        }

        let solution = score_director.working_solution();
        let mut changes_value = false;
        for edit in &self.edits {
            if let Some(value_is_legal) = edit.value_is_legal {
                if !value_is_legal(
                    solution,
                    edit.entity_index,
                    edit.variable_index,
                    edit.to_value,
                ) {
                    return false;
                }
            }
            let current = (edit.getter)(solution, edit.entity_index, edit.variable_index);
            changes_value |= current != edit.to_value;
        }

        changes_value
    }

    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
        let mut undo = Vec::with_capacity(self.edits.len());
        let affected = unique_affected_entities(&self.edits);
        for edit in &self.edits {
            let old_value = (edit.getter)(
                score_director.working_solution(),
                edit.entity_index,
                edit.variable_index,
            );
            undo.push(old_value);
        }
        for (descriptor_index, entity_index) in &affected {
            score_director.before_variable_changed(*descriptor_index, *entity_index);
        }
        for edit in &self.edits {
            (edit.setter)(
                score_director.working_solution_mut(),
                edit.entity_index,
                edit.variable_index,
                edit.to_value,
            );
        }
        for (descriptor_index, entity_index) in affected.iter().rev() {
            score_director.after_variable_changed(*descriptor_index, *entity_index);
        }
        undo
    }

    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
        let affected = unique_affected_entities(&self.edits);
        for (descriptor_index, entity_index) in &affected {
            score_director.before_variable_changed(*descriptor_index, *entity_index);
        }
        for (edit, old_value) in self.edits.iter().zip(undo) {
            (edit.setter)(
                score_director.working_solution_mut(),
                edit.entity_index,
                edit.variable_index,
                old_value,
            );
        }
        for (descriptor_index, entity_index) in affected.iter().rev() {
            score_director.after_variable_changed(*descriptor_index, *entity_index);
        }
    }

    fn descriptor_index(&self) -> usize {
        self.edits
            .first()
            .map(|edit| edit.descriptor_index)
            .unwrap_or(usize::MAX)
    }

    fn entity_indices(&self) -> &[usize] {
        &self.entity_indices
    }

    fn variable_name(&self) -> &str {
        self.variable_label
    }

    fn telemetry_label(&self) -> &'static str {
        self.reason
    }

    fn requires_hard_improvement(&self) -> bool {
        self.require_hard_improvement
    }

    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
        let scope = MoveTabuScope::new(self.descriptor_index(), self.variable_label);
        let mut move_id = smallvec![hash_str(self.reason)];
        let mut undo_move_id = smallvec![hash_str(self.reason)];
        let mut entity_tokens: SmallVec<[_; 8]> = SmallVec::new();
        let mut destination_tokens: SmallVec<[_; 8]> = SmallVec::new();

        for edit in &self.edits {
            let current = (edit.getter)(
                score_director.working_solution(),
                edit.entity_index,
                edit.variable_index,
            );
            let descriptor = encode_usize(edit.descriptor_index);
            let entity = encode_usize(edit.entity_index);
            let variable = hash_str(edit.variable_name);
            let from = encode_option_usize(current);
            let to = encode_option_usize(edit.to_value);
            let edit_scope = MoveTabuScope::new(edit.descriptor_index, edit.variable_name);

            move_id.extend([descriptor, entity, variable, from, to]);
            undo_move_id.extend([descriptor, entity, variable, to, from]);
            entity_tokens.push(edit_scope.entity_token(entity));
            destination_tokens.push(edit_scope.value_token(to));
        }

        MoveTabuSignature::new(scope, move_id, undo_move_id)
            .with_entity_tokens(entity_tokens)
            .with_destination_value_tokens(destination_tokens)
    }

    fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
        for edit in &self.edits {
            visitor(MoveAffectedEntity {
                descriptor_index: edit.descriptor_index,
                entity_index: edit.entity_index,
                variable_name: edit.variable_name,
            });
        }
    }
}

fn unique_affected_entities<S>(edits: &[CompoundScalarEdit<S>]) -> Vec<(usize, usize)> {
    let mut affected = Vec::new();
    for edit in edits {
        let entity = (edit.descriptor_index, edit.entity_index);
        if !affected.contains(&entity) {
            affected.push(entity);
        }
    }
    affected
}