solverforge-solver 0.18.0

Solver engine for SolverForge
Documentation
use smallvec::{smallvec, SmallVec};
use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;

use crate::builder::RuntimeScalarSlot;
use crate::heuristic::r#move::metadata::{
    append_canonical_usize_slice_pair, encode_option_debug, encode_option_usize, encode_usize,
    hash_str, ordered_coordinate_pair, scoped_move_identity, MoveTabuScope, TABU_OP_PILLAR_SWAP,
    TABU_OP_SWAP,
};
use crate::heuristic::r#move::MoveTabuSignature;

use super::super::spec::RuntimeScalarRecipe;
use super::recipe_slot;

pub(super) fn tabu_signature<S, D>(
    recipe: &RuntimeScalarRecipe<S>,
    director: &D,
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let slot = recipe_slot(recipe);
    let scope = MoveTabuScope::new(slot.descriptor_index(), slot.variable_name());
    match recipe {
        RuntimeScalarRecipe::Change {
            entity_index,
            to_value,
            ..
        } => change(scope, slot, director, *entity_index, *to_value),
        RuntimeScalarRecipe::Swap {
            left_entity_index,
            right_entity_index,
            ..
        } => swap(
            scope,
            slot,
            director,
            *left_entity_index,
            *right_entity_index,
        ),
        RuntimeScalarRecipe::PillarChange {
            entity_indices,
            to_value,
            ..
        } => pillar_change(scope, slot, director, entity_indices, *to_value),
        RuntimeScalarRecipe::PillarSwap {
            left_indices,
            right_indices,
            ..
        } => pillar_swap(scope, slot, director, left_indices, right_indices),
        RuntimeScalarRecipe::RuinRecreate {
            entity_indices,
            recreate_heuristic_type,
            ..
        } => ruin(
            scope,
            slot,
            director,
            entity_indices,
            *recreate_heuristic_type,
        ),
    }
}

fn change<S, D>(
    scope: MoveTabuScope,
    slot: &RuntimeScalarSlot<S>,
    director: &D,
    entity_index: usize,
    to_value: Option<usize>,
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let current = slot.current_value(director.working_solution(), entity_index);
    let from = encode_option_debug(current.as_ref());
    let to = encode_option_debug(to_value.as_ref());
    let entity = encode_usize(entity_index);
    let variable = hash_str(slot.variable_name());
    MoveTabuSignature::new(
        scope,
        smallvec![
            encode_usize(slot.descriptor_index()),
            variable,
            entity,
            from,
            to
        ],
        smallvec![
            encode_usize(slot.descriptor_index()),
            variable,
            entity,
            to,
            from
        ],
    )
    .with_entity_tokens([scope.entity_token(entity)])
    .with_destination_value_tokens([scope.value_token(to)])
}

fn swap<S, D>(
    scope: MoveTabuScope,
    slot: &RuntimeScalarSlot<S>,
    director: &D,
    left: usize,
    right: usize,
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let left_current = slot.current_value(director.working_solution(), left);
    let right_current = slot.current_value(director.working_solution(), right);
    let left_value = encode_option_debug(left_current.as_ref());
    let right_value = encode_option_debug(right_current.as_ref());
    let entities = ordered_coordinate_pair((encode_usize(left), 0), (encode_usize(right), 0));
    let move_id = scoped_move_identity(
        scope,
        TABU_OP_SWAP,
        entities.into_iter().map(|(entity, _)| entity),
    );
    MoveTabuSignature::new(scope, move_id.clone(), move_id)
        .with_entity_tokens([
            scope.entity_token(encode_usize(left)),
            scope.entity_token(encode_usize(right)),
        ])
        .with_destination_value_tokens([
            scope.value_token(right_value),
            scope.value_token(left_value),
        ])
}

fn pillar_change<S, D>(
    scope: MoveTabuScope,
    slot: &RuntimeScalarSlot<S>,
    director: &D,
    entities: &[usize],
    to_value: Option<usize>,
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let from = entities
        .first()
        .and_then(|&entity| slot.current_value(director.working_solution(), entity));
    let from = encode_option_debug(from.as_ref());
    let to = encode_option_debug(to_value.as_ref());
    let variable = hash_str(slot.variable_name());
    let entity_ids = entities
        .iter()
        .copied()
        .map(encode_usize)
        .collect::<SmallVec<[_; 8]>>();
    let mut move_id = smallvec![
        encode_usize(slot.descriptor_index()),
        variable,
        encode_usize(entities.len()),
        from,
        to
    ];
    move_id.extend(entity_ids.iter().copied());
    let mut undo_move_id = smallvec![
        encode_usize(slot.descriptor_index()),
        variable,
        encode_usize(entities.len()),
        to,
        from
    ];
    undo_move_id.extend(entity_ids.iter().copied());
    MoveTabuSignature::new(scope, move_id, undo_move_id)
        .with_entity_tokens(
            entity_ids
                .iter()
                .copied()
                .map(|entity| scope.entity_token(entity)),
        )
        .with_destination_value_tokens([scope.value_token(to)])
}

fn pillar_swap<S, D>(
    scope: MoveTabuScope,
    slot: &RuntimeScalarSlot<S>,
    director: &D,
    left: &[usize],
    right: &[usize],
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let left_value = left
        .first()
        .and_then(|&entity| slot.current_value(director.working_solution(), entity));
    let right_value = right
        .first()
        .and_then(|&entity| slot.current_value(director.working_solution(), entity));
    let mut move_id = scoped_move_identity(scope, TABU_OP_PILLAR_SWAP, std::iter::empty());
    append_canonical_usize_slice_pair(&mut move_id, left, right);
    let mut entities = left
        .iter()
        .chain(right)
        .copied()
        .map(encode_usize)
        .collect::<Vec<_>>();
    entities.sort_unstable();
    entities.dedup();
    MoveTabuSignature::new(scope, move_id.clone(), move_id)
        .with_entity_tokens(
            entities
                .iter()
                .copied()
                .map(|entity| scope.entity_token(entity)),
        )
        .with_destination_value_tokens([
            scope.value_token(encode_option_debug(right_value.as_ref())),
            scope.value_token(encode_option_debug(left_value.as_ref())),
        ])
}

fn ruin<S, D>(
    scope: MoveTabuScope,
    slot: &RuntimeScalarSlot<S>,
    director: &D,
    entities: &[usize],
    recreate_heuristic_type: solverforge_config::RecreateHeuristicType,
) -> MoveTabuSignature
where
    S: PlanningSolution,
    D: Director<S>,
{
    let heuristic = match recreate_heuristic_type {
        solverforge_config::RecreateHeuristicType::FirstFit => hash_str("first_fit"),
        solverforge_config::RecreateHeuristicType::CheapestInsertion => {
            hash_str("cheapest_insertion")
        }
    };
    let variable = hash_str(slot.variable_name());
    let mut move_id = smallvec![
        hash_str("ruin_recreate"),
        encode_usize(slot.descriptor_index()),
        variable,
        heuristic,
        encode_usize(entities.len()),
    ];
    for &entity in entities {
        move_id.push(encode_usize(entity));
        move_id.push(encode_option_usize(
            slot.current_value(director.working_solution(), entity),
        ));
    }
    MoveTabuSignature::new(scope, move_id.clone(), move_id).with_entity_tokens(
        entities
            .iter()
            .copied()
            .map(encode_usize)
            .map(|entity| scope.entity_token(entity)),
    )
}