use crate::heuristic::r#move::Move;
use crate::heuristic::selector::entity::FromSolutionEntitySelector;
use crate::heuristic::selector::list_change::ListChangeMoveSelector;
use crate::heuristic::selector::MoveSelector;
use solverforge_core::domain::{
EntityCollectionExtractor, EntityDescriptor, PlanningSolution, SolutionDescriptor,
};
use solverforge_core::score::SoftScore;
use solverforge_scoring::ScoreDirector;
use std::any::TypeId;
#[derive(Clone, Debug)]
struct Vehicle {
visits: Vec<i32>,
}
#[derive(Clone, Debug)]
struct Solution {
vehicles: Vec<Vehicle>,
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_vehicles(s: &Solution) -> &Vec<Vehicle> {
&s.vehicles
}
fn get_vehicles_mut(s: &mut Solution) -> &mut Vec<Vehicle> {
&mut s.vehicles
}
fn list_len(s: &Solution, entity_idx: usize) -> usize {
s.vehicles.get(entity_idx).map_or(0, |v| v.visits.len())
}
fn list_remove(s: &mut Solution, entity_idx: usize, pos: usize) -> Option<i32> {
s.vehicles.get_mut(entity_idx).map(|v| v.visits.remove(pos))
}
fn list_insert(s: &mut Solution, entity_idx: usize, pos: usize, val: i32) {
if let Some(v) = s.vehicles.get_mut(entity_idx) {
v.visits.insert(pos, val);
}
}
fn create_director(vehicles: Vec<Vehicle>) -> ScoreDirector<Solution, ()> {
let solution = Solution {
vehicles,
score: None,
};
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);
let descriptor =
SolutionDescriptor::new("Solution", TypeId::of::<Solution>()).with_entity(entity_desc);
ScoreDirector::simple(solution, descriptor, |s, _| s.vehicles.len())
}
#[test]
fn generates_intra_entity_moves() {
let vehicles = vec![Vehicle {
visits: vec![1, 2, 3],
}];
let director = create_director(vehicles);
let selector = ListChangeMoveSelector::<Solution, i32, _>::new(
FromSolutionEntitySelector::new(0),
list_len,
list_remove,
list_insert,
"visits",
0,
);
let moves: Vec<_> = selector.iter_moves(&director).collect();
assert_eq!(moves.len(), 4);
for m in &moves {
assert!(m.is_intra_list());
}
}
#[test]
fn generates_inter_entity_moves() {
let vehicles = vec![Vehicle { visits: vec![1, 2] }, Vehicle { visits: vec![10] }];
let director = create_director(vehicles);
let selector = ListChangeMoveSelector::<Solution, i32, _>::new(
FromSolutionEntitySelector::new(0),
list_len,
list_remove,
list_insert,
"visits",
0,
);
let moves: Vec<_> = selector.iter_moves(&director).collect();
let inter_count = moves.iter().filter(|m| !m.is_intra_list()).count();
assert_eq!(inter_count, 7);
}
#[test]
fn moves_are_doable() {
let vehicles = vec![
Vehicle {
visits: vec![1, 2, 3],
},
Vehicle { visits: vec![10] },
];
let director = create_director(vehicles);
let selector = ListChangeMoveSelector::<Solution, i32, _>::new(
FromSolutionEntitySelector::new(0),
list_len,
list_remove,
list_insert,
"visits",
0,
);
for m in selector.iter_moves(&director) {
assert!(m.is_doable(&director), "Move should be doable: {:?}", m);
}
}