solverforge-solver 0.12.0

Solver engine for SolverForge
Documentation
// Tests for SwapMove operations.

use super::*;

#[derive(Clone, Debug)]
struct Task {
    id: usize,
    priority: Option<i32>,
}

#[derive(Clone, Debug)]
struct TaskSolution {
    tasks: Vec<Task>,
    score: Option<SoftScore>,
}

impl PlanningSolution for TaskSolution {
    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_tasks(s: &TaskSolution) -> &Vec<Task> {
    &s.tasks
}

fn get_tasks_mut(s: &mut TaskSolution) -> &mut Vec<Task> {
    &mut s.tasks
}

fn get_priority(s: &TaskSolution, idx: usize, _variable_index: usize) -> Option<i32> {
    s.tasks.get(idx).and_then(|t| t.priority)
}

fn set_priority(s: &mut TaskSolution, idx: usize, _variable_index: usize, v: Option<i32>) {
    if let Some(task) = s.tasks.get_mut(idx) {
        task.priority = v;
    }
}

fn create_director(tasks: Vec<Task>) -> ScoreDirector<TaskSolution, ()> {
    let solution = TaskSolution { tasks, score: None };

    let extractor = Box::new(EntityCollectionExtractor::new(
        "Task",
        "tasks",
        get_tasks,
        get_tasks_mut,
    ));
    let entity_desc =
        EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks").with_extractor(extractor);

    let descriptor = SolutionDescriptor::new("TaskSolution", TypeId::of::<TaskSolution>())
        .with_entity(entity_desc);

    ScoreDirector::simple(solution, descriptor, |s, _| s.tasks.len())
}

#[test]
fn test_swap_move_do_and_undo() {
    let tasks = vec![
        Task {
            id: 0,
            priority: Some(1),
        },
        Task {
            id: 1,
            priority: Some(5),
        },
    ];
    let mut director = create_director(tasks);

    let m = SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, 0, "priority", 0);
    assert!(m.is_doable(&director));

    {
        let mut recording = RecordingDirector::new(&mut director);
        m.do_move(&mut recording);

        assert_eq!(get_priority(recording.working_solution(), 0, 0), Some(5));
        assert_eq!(get_priority(recording.working_solution(), 1, 0), Some(1));

        recording.undo_changes();
    }

    assert_eq!(get_priority(director.working_solution(), 0, 0), Some(1));
    assert_eq!(get_priority(director.working_solution(), 1, 0), Some(5));

    let solution = director.working_solution();
    assert_eq!(solution.tasks[0].id, 0);
    assert_eq!(solution.tasks[1].id, 1);
}

#[test]
fn test_swap_same_value_not_doable() {
    let tasks = vec![
        Task {
            id: 0,
            priority: Some(5),
        },
        Task {
            id: 1,
            priority: Some(5),
        },
    ];
    let director = create_director(tasks);

    let m = SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, 0, "priority", 0);
    assert!(
        !m.is_doable(&director),
        "swapping same values should not be doable"
    );
}

#[test]
fn test_swap_self_not_doable() {
    let tasks = vec![Task {
        id: 0,
        priority: Some(1),
    }];
    let director = create_director(tasks);

    let m = SwapMove::<TaskSolution, i32>::new(0, 0, get_priority, set_priority, 0, "priority", 0);
    assert!(!m.is_doable(&director), "self-swap should not be doable");
}

#[test]
fn test_swap_entity_indices() {
    let m = SwapMove::<TaskSolution, i32>::new(2, 5, get_priority, set_priority, 0, "priority", 0);
    assert_eq!(m.entity_indices(), &[2, 5]);
}

#[test]
fn swap_tabu_identity_is_direction_stable() {
    use crate::phase::localsearch::{Acceptor, TabuSearchAcceptor, TabuSearchPolicy};

    let tasks = vec![
        Task {
            id: 0,
            priority: Some(1),
        },
        Task {
            id: 1,
            priority: Some(5),
        },
    ];
    let mut director = create_director(tasks);
    let forward =
        SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, 0, "priority", 0);
    let forward_signature = forward.tabu_signature(&director);

    {
        let mut recording = RecordingDirector::new(&mut director);
        forward.do_move(&mut recording);
    }

    let reverse_same_coordinates =
        SwapMove::<TaskSolution, i32>::new(0, 1, get_priority, set_priority, 0, "priority", 0);
    let reverse_flipped_coordinates =
        SwapMove::<TaskSolution, i32>::new(1, 0, get_priority, set_priority, 0, "priority", 0);
    let reverse_signature = reverse_same_coordinates.tabu_signature(&director);
    let flipped_signature = reverse_flipped_coordinates.tabu_signature(&director);

    assert_eq!(forward_signature.move_id, forward_signature.undo_move_id);
    assert_eq!(forward_signature.move_id, reverse_signature.move_id);
    assert_eq!(forward_signature.move_id, flipped_signature.move_id);

    let mut acceptor = TabuSearchAcceptor::<TaskSolution>::new(TabuSearchPolicy::move_only(10));
    acceptor.phase_started(&SoftScore::of(-10));
    acceptor.step_ended(&SoftScore::of(-9), Some(&forward_signature));

    assert!(!acceptor.is_accepted(
        &SoftScore::of(-9),
        &SoftScore::of(-9),
        Some(&reverse_signature)
    ));
}