solverforge_solver/heuristic/selector/
nearby.rs1use std::cmp::Ordering;
15use std::fmt::Debug;
16
17use solverforge_core::domain::PlanningSolution;
18use solverforge_scoring::ScoreDirector;
19
20use super::entity::{EntityReference, EntitySelector};
21use super::mimic::MimicRecorder;
22
23pub trait NearbyDistanceMeter<Origin, Destination>: Send + Sync + Debug {
52 fn distance(&self, origin: &Origin, destination: &Destination) -> f64;
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum NearbyDistributionType {
65 #[default]
67 Linear,
68 Parabolic,
70 Block,
72}
73
74#[derive(Debug, Clone)]
76pub struct NearbySelectionConfig {
77 pub distribution_type: NearbyDistributionType,
79 pub max_nearby_size: Option<usize>,
82 pub min_distance: f64,
84}
85
86impl Default for NearbySelectionConfig {
87 fn default() -> Self {
88 Self {
89 distribution_type: NearbyDistributionType::Linear,
90 max_nearby_size: None,
91 min_distance: 0.0,
92 }
93 }
94}
95
96impl NearbySelectionConfig {
97 pub fn new() -> Self {
99 Self::default()
100 }
101
102 pub fn with_distribution_type(mut self, distribution_type: NearbyDistributionType) -> Self {
104 self.distribution_type = distribution_type;
105 self
106 }
107
108 pub fn with_max_nearby_size(mut self, max_size: usize) -> Self {
110 self.max_nearby_size = Some(max_size);
111 self
112 }
113
114 pub fn with_min_distance(mut self, min_distance: f64) -> Self {
116 self.min_distance = min_distance;
117 self
118 }
119}
120
121pub trait DynDistanceMeter: Send + Sync + Debug {
123 fn distance_between<S: PlanningSolution>(
125 &self,
126 score_director: &dyn ScoreDirector<S>,
127 origin: EntityReference,
128 destination: EntityReference,
129 ) -> f64;
130}
131
132pub struct NearbyEntitySelector<S, M, ES> {
142 child: ES,
144 origin_recorder: MimicRecorder,
146 distance_meter: M,
148 config: NearbySelectionConfig,
150 _phantom: std::marker::PhantomData<fn() -> S>,
152}
153
154impl<S, M, ES> NearbyEntitySelector<S, M, ES> {
155 pub fn new(
157 child: ES,
158 origin_recorder: MimicRecorder,
159 distance_meter: M,
160 config: NearbySelectionConfig,
161 ) -> Self {
162 Self {
163 child,
164 origin_recorder,
165 distance_meter,
166 config,
167 _phantom: std::marker::PhantomData,
168 }
169 }
170}
171
172impl<S: PlanningSolution, M: DynDistanceMeter, ES: Debug> Debug for NearbyEntitySelector<S, M, ES> {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 f.debug_struct("NearbyEntitySelector")
175 .field("child", &self.child)
176 .field("origin_recorder_id", &self.origin_recorder.id())
177 .field("distance_meter", &self.distance_meter)
178 .field("config", &self.config)
179 .finish()
180 }
181}
182
183impl<S, M, ES> EntitySelector<S> for NearbyEntitySelector<S, M, ES>
184where
185 S: PlanningSolution,
186 M: DynDistanceMeter + 'static,
187 ES: EntitySelector<S>,
188{
189 fn iter<'a, D: ScoreDirector<S>>(
190 &'a self,
191 score_director: &'a D,
192 ) -> Box<dyn Iterator<Item = EntityReference> + 'a> {
193 let origin = match self.origin_recorder.get_recorded_entity() {
195 Some(e) => e,
196 None => {
197 return Box::new(std::iter::empty());
199 }
200 };
201
202 let mut candidates: Vec<(EntityReference, f64)> = self
204 .child
205 .iter(score_director)
206 .filter(|&dest| dest != origin) .map(|dest| {
208 let dist = self
209 .distance_meter
210 .distance_between(score_director, origin, dest);
211 (dest, dist)
212 })
213 .filter(|(_, dist)| *dist >= self.config.min_distance)
214 .collect();
215
216 candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
218
219 if let Some(max_size) = self.config.max_nearby_size {
221 candidates.truncate(max_size);
222 }
223
224 Box::new(candidates.into_iter().map(|(entity, _)| entity))
225 }
226
227 fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
228 let child_size = self.child.size(score_director);
230 match self.config.max_nearby_size {
231 Some(max) => child_size.min(max),
232 None => child_size,
233 }
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::heuristic::selector::entity::FromSolutionEntitySelector;
241 use crate::heuristic::selector::mimic::{MimicRecorder, MimicRecordingEntitySelector};
242 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
243 use solverforge_core::score::SimpleScore;
244 use solverforge_scoring::SimpleScoreDirector;
245 use std::any::TypeId;
246
247 #[derive(Clone, Debug)]
248 struct Location {
249 id: i64,
250 x: f64,
251 y: f64,
252 }
253
254 #[derive(Clone, Debug)]
255 struct RoutingSolution {
256 locations: Vec<Location>,
257 score: Option<SimpleScore>,
258 }
259
260 impl PlanningSolution for RoutingSolution {
261 type Score = SimpleScore;
262
263 fn score(&self) -> Option<Self::Score> {
264 self.score
265 }
266
267 fn set_score(&mut self, score: Option<Self::Score>) {
268 self.score = score;
269 }
270 }
271
272 fn get_locations(s: &RoutingSolution) -> &Vec<Location> {
273 &s.locations
274 }
275
276 fn get_locations_mut(s: &mut RoutingSolution) -> &mut Vec<Location> {
277 &mut s.locations
278 }
279
280 #[derive(Debug)]
282 struct EuclideanDistanceMeter {
283 locations: Vec<(f64, f64)>,
285 }
286
287 impl EuclideanDistanceMeter {
288 fn new(locations: &[Location]) -> Self {
289 Self {
290 locations: locations.iter().map(|l| (l.x, l.y)).collect(),
291 }
292 }
293 }
294
295 impl DynDistanceMeter for EuclideanDistanceMeter {
296 fn distance_between<S: PlanningSolution>(
297 &self,
298 _score_director: &dyn ScoreDirector<S>,
299 origin: EntityReference,
300 destination: EntityReference,
301 ) -> f64 {
302 let (ox, oy) = self.locations[origin.entity_index];
303 let (dx, dy) = self.locations[destination.entity_index];
304 let delta_x = ox - dx;
305 let delta_y = oy - dy;
306 (delta_x * delta_x + delta_y * delta_y).sqrt()
307 }
308 }
309
310 fn create_test_director(
311 ) -> SimpleScoreDirector<RoutingSolution, impl Fn(&RoutingSolution) -> SimpleScore> {
312 let locations = vec![
314 Location {
315 id: 0,
316 x: 0.0,
317 y: 0.0,
318 },
319 Location {
320 id: 1,
321 x: 1.0,
322 y: 0.0,
323 },
324 Location {
325 id: 2,
326 x: 2.0,
327 y: 0.0,
328 },
329 Location {
330 id: 3,
331 x: 0.0,
332 y: 1.0,
333 },
334 Location {
335 id: 4,
336 x: 1.0,
337 y: 1.0,
338 },
339 Location {
340 id: 5,
341 x: 2.0,
342 y: 1.0,
343 },
344 ];
345
346 let solution = RoutingSolution {
347 locations,
348 score: None,
349 };
350
351 let extractor = Box::new(TypedEntityExtractor::new(
352 "Location",
353 "locations",
354 get_locations,
355 get_locations_mut,
356 ));
357 let entity_desc = EntityDescriptor::new("Location", TypeId::of::<Location>(), "locations")
358 .with_extractor(extractor);
359
360 let descriptor =
361 SolutionDescriptor::new("RoutingSolution", TypeId::of::<RoutingSolution>())
362 .with_entity(entity_desc);
363
364 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
365 }
366
367 #[test]
368 fn test_nearby_selector_sorts_by_distance() {
369 let director = create_test_director();
370
371 let solution = director.working_solution();
373 for (i, loc) in solution.locations.iter().enumerate() {
374 assert_eq!(loc.id, i as i64);
375 }
376
377 let recorder = MimicRecorder::new("origin");
379 let origin_child = FromSolutionEntitySelector::new(0);
380 let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
381
382 let dest_child = FromSolutionEntitySelector::new(0);
384 let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
385 let nearby_config = NearbySelectionConfig::default();
386 let nearby_selector =
387 NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
388
389 let mut origin_iter = origin_selector.iter(&director);
391 let _origin = origin_iter.next().unwrap();
392
393 let nearby: Vec<_> = nearby_selector.iter(&director).collect();
395
396 assert_eq!(nearby.len(), 5); assert!(
402 nearby[0].entity_index == 1 || nearby[0].entity_index == 3,
403 "Expected location 1 or 3, got {}",
404 nearby[0].entity_index
405 );
406 }
407
408 #[test]
409 fn test_nearby_selector_with_max_size() {
410 let director = create_test_director();
411
412 let recorder = MimicRecorder::new("origin");
413 let origin_child = FromSolutionEntitySelector::new(0);
414 let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
415
416 let dest_child = FromSolutionEntitySelector::new(0);
417 let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
418 let nearby_config = NearbySelectionConfig::default().with_max_nearby_size(2);
419 let nearby_selector =
420 NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
421
422 let mut origin_iter = origin_selector.iter(&director);
424 origin_iter.next();
425
426 let nearby: Vec<_> = nearby_selector.iter(&director).collect();
428 assert_eq!(nearby.len(), 2);
429 }
430
431 #[test]
432 fn test_nearby_selector_excludes_origin() {
433 let director = create_test_director();
434
435 let recorder = MimicRecorder::new("origin");
436 let origin_child = FromSolutionEntitySelector::new(0);
437 let origin_selector = MimicRecordingEntitySelector::new(origin_child, recorder.clone());
438
439 let dest_child = FromSolutionEntitySelector::new(0);
440 let distance_meter = EuclideanDistanceMeter::new(&director.working_solution().locations);
441 let nearby_config = NearbySelectionConfig::default();
442 let nearby_selector =
443 NearbyEntitySelector::new(dest_child, recorder.clone(), distance_meter, nearby_config);
444
445 let mut origin_iter = origin_selector.iter(&director);
447 let origin = origin_iter.next().unwrap();
448
449 let nearby: Vec<_> = nearby_selector.iter(&director).collect();
451 assert!(!nearby.contains(&origin));
452 }
453}