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));
});
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);
});
assert_eq!(affected, vec![1, 0]);
assert!(composite.is_doable(&director));
}