use crate::heuristic::selector::entity::FromSolutionEntitySelector;
use crate::heuristic::selector::mimic::{MimicRecorder, MimicRecordingEntitySelector};
use crate::heuristic::selector::nearby::{
DynDistanceMeter, NearbyEntitySelector, NearbySelectionConfig,
};
use crate::heuristic::selector::{EntityReference, EntitySelector};
use solverforge_core::domain::{
EntityCollectionExtractor, EntityDescriptor, PlanningSolution, SolutionDescriptor,
};
use solverforge_core::score::SoftScore;
use solverforge_scoring::{Director, ScoreDirector};
use std::any::TypeId;
#[derive(Clone, Debug)]
struct Location {
id: i64,
x: f64,
y: f64,
}
#[derive(Clone, Debug)]
struct RoutingSolution {
locations: Vec<Location>,
score: Option<SoftScore>,
}
impl PlanningSolution for RoutingSolution {
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_locations(s: &RoutingSolution) -> &Vec<Location> {
&s.locations
}
fn get_locations_mut(s: &mut RoutingSolution) -> &mut Vec<Location> {
&mut s.locations
}
#[derive(Debug)]
struct EuclideanDistanceMeter {
locations: Vec<(f64, f64)>,
}
impl EuclideanDistanceMeter {
fn new(locations: &[Location]) -> Self {
Self {
locations: locations.iter().map(|l| (l.x, l.y)).collect(),
}
}
}
impl DynDistanceMeter for EuclideanDistanceMeter {
fn distance_between<S: PlanningSolution>(
&self,
_score_director: &dyn Director<S>,
origin: EntityReference,
destination: EntityReference,
) -> f64 {
let (ox, oy) = self.locations[origin.entity_index];
let (dx, dy) = self.locations[destination.entity_index];
let delta_x = ox - dx;
let delta_y = oy - dy;
(delta_x * delta_x + delta_y * delta_y).sqrt()
}
}
fn create_test_director() -> ScoreDirector<RoutingSolution, ()> {
let locations = vec![
Location {
id: 0,
x: 0.0,
y: 0.0,
},
Location {
id: 1,
x: 1.0,
y: 0.0,
},
Location {
id: 2,
x: 2.0,
y: 0.0,
},
Location {
id: 3,
x: 0.0,
y: 1.0,
},
Location {
id: 4,
x: 1.0,
y: 1.0,
},
Location {
id: 5,
x: 2.0,
y: 1.0,
},
];
let solution = RoutingSolution {
locations,
score: None,
};
let extractor = Box::new(EntityCollectionExtractor::new(
"Location",
"locations",
get_locations,
get_locations_mut,
));
let entity_desc = EntityDescriptor::new("Location", TypeId::of::<Location>(), "locations")
.with_extractor(extractor);
let descriptor = SolutionDescriptor::new("RoutingSolution", TypeId::of::<RoutingSolution>())
.with_entity(entity_desc);
ScoreDirector::simple(solution, descriptor, |s, _| s.locations.len())
}
#[test]
fn test_nearby_selector_sorts_by_distance() {
let director = create_test_director();
let solution = director.working_solution();
for (i, loc) in solution.locations.iter().enumerate() {
assert_eq!(loc.id, i as i64);
}
let recorder = MimicRecorder::new("origin");
let origin_child = FromSolutionEntitySelector::new(0);
let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
let dest_child = FromSolutionEntitySelector::new(0);
let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
let nearby_config = NearbySelectionConfig::default();
let nearby_selector =
NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
let mut origin_iter = origin_selector.iter(&director);
let _origin = origin_iter.next().unwrap();
let nearby: Vec<_> = nearby_selector.iter(&director).collect();
assert_eq!(nearby.len(), 5);
assert!(
nearby[0].entity_index == 1 || nearby[0].entity_index == 3,
"Expected location 1 or 3, got {}",
nearby[0].entity_index
);
}
#[test]
fn test_nearby_selector_with_max_size() {
let director = create_test_director();
let recorder = MimicRecorder::new("origin");
let origin_child = FromSolutionEntitySelector::new(0);
let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
let dest_child = FromSolutionEntitySelector::new(0);
let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
let nearby_config = NearbySelectionConfig::default().with_max_nearby_size(2);
let nearby_selector =
NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
let mut origin_iter = origin_selector.iter(&director);
origin_iter.next();
let nearby: Vec<_> = nearby_selector.iter(&director).collect();
assert_eq!(nearby.len(), 2);
}
#[test]
fn test_nearby_selector_excludes_origin() {
let director = create_test_director();
let recorder = MimicRecorder::new("origin");
let origin_child = FromSolutionEntitySelector::new(0);
let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
let dest_child = FromSolutionEntitySelector::new(0);
let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
let nearby_config = NearbySelectionConfig::default();
let nearby_selector =
NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
let mut origin_iter = origin_selector.iter(&director);
let origin = origin_iter.next().unwrap();
let nearby: Vec<_> = nearby_selector.iter(&director).collect();
assert!(!nearby.contains(&origin));
}