solverforge-solver 0.16.0

Solver engine for SolverForge
Documentation
use std::any::TypeId;
use std::collections::HashSet;

use crate::heuristic::r#move::Move;
use crate::heuristic::selector::entity::FromSolutionEntitySelector;
use crate::heuristic::selector::list_permute::ListPermuteMoveSelector;
use crate::heuristic::selector::MoveSelector;
use solverforge_core::domain::{
    EntityCollectionExtractor, EntityDescriptor, PlanningSolution, SolutionDescriptor,
};
use solverforge_core::score::SoftScore;
use solverforge_scoring::ScoreDirector;

#[derive(Clone, Debug)]
struct Vehicle {
    visits: Vec<usize>,
}

#[derive(Clone, Debug)]
struct Plan {
    vehicles: Vec<Vehicle>,
    score: Option<SoftScore>,
}

impl PlanningSolution for Plan {
    type Score = SoftScore;

    fn score(&self) -> Option<Self::Score> {
        self.score
    }

    fn set_score(&mut self, score: Option<Self::Score>) {
        self.score = score;
    }
}

fn get_vehicles(plan: &Plan) -> &Vec<Vehicle> {
    &plan.vehicles
}

fn get_vehicles_mut(plan: &mut Plan) -> &mut Vec<Vehicle> {
    &mut plan.vehicles
}

fn descriptor() -> SolutionDescriptor {
    let extractor = Box::new(EntityCollectionExtractor::new(
        "Vehicle",
        "vehicles",
        get_vehicles,
        get_vehicles_mut,
    ));
    let entity_desc = EntityDescriptor::new("Vehicle", TypeId::of::<Vehicle>(), "vehicles")
        .with_extractor(extractor);
    SolutionDescriptor::new("Plan", TypeId::of::<Plan>()).with_entity(entity_desc)
}

fn create_director(vehicles: Vec<Vehicle>) -> ScoreDirector<Plan, ()> {
    ScoreDirector::simple(
        Plan {
            vehicles,
            score: None,
        },
        descriptor(),
        |plan, _| plan.vehicles.len(),
    )
}

fn list_len(plan: &Plan, entity_idx: usize) -> usize {
    plan.vehicles
        .get(entity_idx)
        .map_or(0, |vehicle| vehicle.visits.len())
}

fn list_get(plan: &Plan, entity_idx: usize, pos: usize) -> Option<usize> {
    plan.vehicles
        .get(entity_idx)
        .and_then(|vehicle| vehicle.visits.get(pos))
        .copied()
}

fn sublist_remove(plan: &mut Plan, entity_idx: usize, start: usize, end: usize) -> Vec<usize> {
    plan.vehicles
        .get_mut(entity_idx)
        .map(|vehicle| vehicle.visits.drain(start..end).collect())
        .unwrap_or_default()
}

fn sublist_insert(plan: &mut Plan, entity_idx: usize, pos: usize, items: Vec<usize>) {
    if let Some(vehicle) = plan.vehicles.get_mut(entity_idx) {
        for (offset, item) in items.into_iter().enumerate() {
            vehicle.visits.insert(pos + offset, item);
        }
    }
}

fn fixed_owner(_: &Plan, element: &usize) -> Option<usize> {
    Some(*element / 100)
}

fn selector(
    min_window_size: usize,
    max_window_size: usize,
) -> ListPermuteMoveSelector<Plan, usize, FromSolutionEntitySelector> {
    ListPermuteMoveSelector::new(
        FromSolutionEntitySelector::new(0),
        min_window_size,
        max_window_size,
        list_len,
        list_get,
        sublist_remove,
        sublist_insert,
        "visits",
        0,
    )
}

#[test]
fn list_permute_enumerates_windows_without_batching_moves() {
    let director = create_director(vec![Vehicle {
        visits: vec![1, 2, 3],
    }]);
    let selector = selector(2, 3);

    assert_eq!(selector.size(&director), 7);

    let moves: Vec<_> = selector.iter_moves(&director).collect();
    assert_eq!(moves.len(), 7);
    assert!(moves.iter().all(|mov| mov.is_doable(&director)));
    assert_eq!(moves[0].start(), 0);
    assert_eq!(moves[0].end(), 2);
    assert_eq!(moves[0].permutation(), &[1, 0]);
    assert!(moves.iter().all(|mov| mov.entity_index() == 0));
}

#[test]
fn list_permute_size_matches_streamed_unique_candidates() {
    let director = create_director(vec![
        Vehicle {
            visits: vec![1, 2, 3, 4],
        },
        Vehicle {
            visits: vec![100, 101, 102],
        },
    ]);
    let selector = selector(2, 3);

    let moves: Vec<_> = selector.iter_moves(&director).collect();
    let signatures: HashSet<_> = moves
        .iter()
        .map(|mov| {
            (
                mov.entity_index(),
                mov.start(),
                mov.end(),
                mov.permutation().to_vec(),
            )
        })
        .collect();

    assert_eq!(moves.len(), selector.size(&director));
    assert_eq!(signatures.len(), moves.len());
}

#[test]
fn list_permute_respects_fixed_owner_restrictions() {
    let director = create_director(vec![
        Vehicle {
            visits: vec![0, 100, 1],
        },
        Vehicle {
            visits: vec![101, 102],
        },
    ]);
    let selector = selector(2, 2).with_element_owner_fn(Some(fixed_owner));

    let moves: Vec<_> = selector.iter_moves(&director).collect();
    assert_eq!(selector.size(&director), 1);
    assert_eq!(moves.len(), 1);
    assert_eq!(moves[0].entity_index(), 1);
    assert_eq!(moves[0].start(), 0);
    assert_eq!(moves[0].permutation(), &[1, 0]);
}