solverforge-solver 0.21.0

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

use super::*;

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

#[derive(Clone, Debug)]
struct Solution {
    employees: Vec<Employee>,
    score: Option<SoftScore>,
}

impl PlanningSolution for Solution {
    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_shift(s: &Solution, idx: usize, _variable_index: usize) -> Option<i32> {
    s.employees.get(idx).and_then(|e| e.shift)
}

fn set_shift(s: &mut Solution, idx: usize, _variable_index: usize, v: Option<i32>) {
    if let Some(e) = s.employees.get_mut(idx) {
        e.shift = v;
    }
}

fn create_director(employees: Vec<Employee>) -> ScoreDirector<Solution, ()> {
    let solution = Solution {
        employees,
        score: None,
    };
    let extractor = Box::new(EntityCollectionExtractor::new(
        "Employee",
        "employees",
        |s: &Solution| &s.employees,
        |s: &mut Solution| &mut s.employees,
    ));
    let entity_desc = EntityDescriptor::new("Employee", TypeId::of::<Employee>(), "employees")
        .with_extractor(extractor);
    let descriptor =
        SolutionDescriptor::new("Solution", TypeId::of::<Solution>()).with_entity(entity_desc);
    ScoreDirector::simple(solution, descriptor, |s, _| s.employees.len())
}

#[test]
fn test_pillar_swap_all_entities() {
    let mut director = create_director(vec![
        Employee {
            id: 0,
            shift: Some(1),
        },
        Employee {
            id: 1,
            shift: Some(1),
        },
        Employee {
            id: 2,
            shift: Some(2),
        },
        Employee {
            id: 3,
            shift: Some(2),
        },
    ]);

    let m = PillarSwapMove::<Solution, i32>::new(
        vec![0, 1],
        vec![2, 3],
        get_shift,
        set_shift,
        0,
        "shift",
        0,
    );
    assert!(m.is_doable(&director));

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

        assert_eq!(get_shift(director.working_solution(), 0, 0), Some(2));
        assert_eq!(get_shift(director.working_solution(), 1, 0), Some(2));
        assert_eq!(get_shift(director.working_solution(), 2, 0), Some(1));
        assert_eq!(get_shift(director.working_solution(), 3, 0), Some(1));

        recording.undo_changes();
    }

    assert_eq!(get_shift(director.working_solution(), 0, 0), Some(1));
    assert_eq!(get_shift(director.working_solution(), 1, 0), Some(1));
    assert_eq!(get_shift(director.working_solution(), 2, 0), Some(2));
    assert_eq!(get_shift(director.working_solution(), 3, 0), Some(2));

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

#[test]
fn test_pillar_swap_same_value_not_doable() {
    let director = create_director(vec![
        Employee {
            id: 0,
            shift: Some(1),
        },
        Employee {
            id: 1,
            shift: Some(1),
        },
    ]);
    let m =
        PillarSwapMove::<Solution, i32>::new(vec![0], vec![1], get_shift, set_shift, 0, "shift", 0);
    assert!(!m.is_doable(&director));
}

#[test]
fn test_pillar_swap_empty_pillar_not_doable() {
    let director = create_director(vec![Employee {
        id: 0,
        shift: Some(1),
    }]);
    let m =
        PillarSwapMove::<Solution, i32>::new(vec![], vec![0], get_shift, set_shift, 0, "shift", 0);
    assert!(!m.is_doable(&director));
}

#[test]
fn pillar_swap_tabu_identity_is_direction_stable() {
    let mut director = create_director(vec![
        Employee {
            id: 0,
            shift: Some(1),
        },
        Employee {
            id: 1,
            shift: Some(1),
        },
        Employee {
            id: 2,
            shift: Some(2),
        },
        Employee {
            id: 3,
            shift: Some(2),
        },
    ]);
    let forward = PillarSwapMove::<Solution, i32>::new(
        vec![0, 1],
        vec![2, 3],
        get_shift,
        set_shift,
        0,
        "shift",
        0,
    );
    let forward_signature = forward.tabu_signature(&director);

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

    let reverse_same_coordinates = PillarSwapMove::<Solution, i32>::new(
        vec![0, 1],
        vec![2, 3],
        get_shift,
        set_shift,
        0,
        "shift",
        0,
    );
    let reverse_flipped_coordinates = PillarSwapMove::<Solution, i32>::new(
        vec![2, 3],
        vec![0, 1],
        get_shift,
        set_shift,
        0,
        "shift",
        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_id());
    assert_eq!(forward_signature.move_id, reverse_signature.move_id);
    assert_eq!(forward_signature.move_id, flipped_signature.move_id);
}

#[test]
fn pillar_swap_reports_both_pillar_halves_as_affected() {
    let director = create_director(vec![
        Employee {
            id: 0,
            shift: Some(1),
        },
        Employee {
            id: 1,
            shift: Some(1),
        },
        Employee {
            id: 2,
            shift: Some(2),
        },
        Employee {
            id: 3,
            shift: Some(2),
        },
        Employee {
            id: 4,
            shift: Some(2),
        },
    ]);
    let m = PillarSwapMove::<Solution, i32>::new(
        vec![0, 1],
        vec![2, 3, 4],
        get_shift,
        set_shift,
        0,
        "shift",
        0,
    );
    assert!(m.is_doable(&director));

    let mut affected = Vec::new();
    m.for_each_affected_entity(&mut |entity| {
        affected.push((entity.descriptor_index, entity.entity_index));
    });

    // Every entity of BOTH halves, exactly once, left then right.
    assert_eq!(affected, vec![(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)]);
}

#[test]
fn composite_reports_children_in_declaration_order_once() {
    use crate::heuristic::r#move::{ChangeMove, SequentialCompositeMove};

    let director = create_director(vec![
        Employee {
            id: 0,
            shift: Some(1),
        },
        Employee {
            id: 1,
            shift: Some(2),
        },
    ]);
    let first = ChangeMove::<Solution, i32>::new(1, Some(3), get_shift, set_shift, 0, "shift", 0);
    let second = ChangeMove::<Solution, i32>::new(0, Some(2), get_shift, set_shift, 0, "shift", 0);
    let composite = SequentialCompositeMove::new(
        first,
        second,
        0,
        smallvec::smallvec![0, 1],
        "shift",
        crate::heuristic::r#move::MoveTabuSignature::new(
            crate::heuristic::r#move::metadata::MoveTabuScope::new(0, "shift"),
            smallvec::smallvec![1],
            smallvec::smallvec![1],
        ),
    );

    let mut affected = Vec::new();
    composite.for_each_affected_entity(&mut |entity| {
        affected.push(entity.entity_index);
    });
    // Child order (second's entity 1 first, then first's entity 0), each once.
    assert_eq!(affected, vec![1, 0]);
    assert!(composite.is_doable(&director));
}