solverforge-solver 0.9.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 = RecordingDirector::new(&mut director);
        m.do_move(&mut recording);

        assert_eq!(get_shift(recording.working_solution(), 0, 0), Some(2));
        assert_eq!(get_shift(recording.working_solution(), 1, 0), Some(2));
        assert_eq!(get_shift(recording.working_solution(), 2, 0), Some(1));
        assert_eq!(get_shift(recording.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 = RecordingDirector::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_move_id);
    assert_eq!(forward_signature.move_id, reverse_signature.move_id);
    assert_eq!(forward_signature.move_id, flipped_signature.move_id);
}