1use std::collections::HashMap;
8use std::fmt::Debug;
9use std::hash::Hash;
10use std::marker::PhantomData;
11
12use solverforge_core::domain::PlanningSolution;
13use solverforge_scoring::ScoreDirector;
14
15use super::entity::{EntityReference, EntitySelector};
16
17#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct Pillar {
23 pub entities: Vec<EntityReference>,
25}
26
27impl Pillar {
28 pub fn new(entities: Vec<EntityReference>) -> Self {
30 Self { entities }
31 }
32
33 pub fn size(&self) -> usize {
35 self.entities.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
40 self.entities.is_empty()
41 }
42
43 pub fn first(&self) -> Option<&EntityReference> {
45 self.entities.first()
46 }
47
48 pub fn iter(&self) -> impl Iterator<Item = &EntityReference> {
50 self.entities.iter()
51 }
52}
53
54pub trait PillarSelector<S: PlanningSolution>: Send + Debug {
62 fn iter<'a, D: ScoreDirector<S>>(
67 &'a self,
68 score_director: &'a D,
69 ) -> Box<dyn Iterator<Item = Pillar> + 'a>;
70
71 fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize;
73
74 fn is_never_ending(&self) -> bool {
76 false
77 }
78
79 fn descriptor_index(&self) -> usize;
81}
82
83#[derive(Debug, Clone)]
85pub struct SubPillarConfig {
86 pub enabled: bool,
88 pub minimum_size: usize,
90 pub maximum_size: usize,
92}
93
94impl Default for SubPillarConfig {
95 fn default() -> Self {
96 Self {
97 enabled: false,
98 minimum_size: 1,
99 maximum_size: usize::MAX,
100 }
101 }
102}
103
104impl SubPillarConfig {
105 pub fn none() -> Self {
107 Self::default()
108 }
109
110 pub fn all() -> Self {
112 Self {
113 enabled: true,
114 minimum_size: 1,
115 maximum_size: usize::MAX,
116 }
117 }
118
119 pub fn with_minimum_size(mut self, size: usize) -> Self {
121 self.minimum_size = size.max(1);
122 self
123 }
124
125 pub fn with_maximum_size(mut self, size: usize) -> Self {
127 self.maximum_size = size;
128 self
129 }
130}
131
132pub struct DefaultPillarSelector<S, V, ES, E>
145where
146 S: PlanningSolution,
147 V: Clone + Eq + Hash + Send + Sync + 'static,
148 ES: EntitySelector<S>,
149 E: Fn(&dyn ScoreDirector<S>, usize, usize) -> Option<V> + Send + Sync,
150{
151 entity_selector: ES,
153 descriptor_index: usize,
155 variable_name: &'static str,
157 value_extractor: E,
159 sub_pillar_config: SubPillarConfig,
161 _phantom: PhantomData<(fn() -> S, V)>,
163}
164
165impl<S, V, ES, E> Debug for DefaultPillarSelector<S, V, ES, E>
166where
167 S: PlanningSolution,
168 V: Clone + Eq + Hash + Send + Sync + 'static,
169 ES: EntitySelector<S> + Debug,
170 E: Fn(&dyn ScoreDirector<S>, usize, usize) -> Option<V> + Send + Sync,
171{
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 f.debug_struct("DefaultPillarSelector")
174 .field("entity_selector", &self.entity_selector)
175 .field("descriptor_index", &self.descriptor_index)
176 .field("variable_name", &self.variable_name)
177 .field("sub_pillar_config", &self.sub_pillar_config)
178 .finish()
179 }
180}
181
182impl<S, V, ES, E> DefaultPillarSelector<S, V, ES, E>
183where
184 S: PlanningSolution,
185 V: Clone + Eq + Hash + Send + Sync + 'static,
186 ES: EntitySelector<S>,
187 E: Fn(&dyn ScoreDirector<S>, usize, usize) -> Option<V> + Send + Sync,
188{
189 pub fn new(
198 entity_selector: ES,
199 descriptor_index: usize,
200 variable_name: &'static str,
201 value_extractor: E,
202 ) -> Self {
203 Self {
204 entity_selector,
205 descriptor_index,
206 variable_name,
207 value_extractor,
208 sub_pillar_config: SubPillarConfig::default(),
209 _phantom: PhantomData,
210 }
211 }
212
213 pub fn with_sub_pillar_config(mut self, config: SubPillarConfig) -> Self {
215 self.sub_pillar_config = config;
216 self
217 }
218
219 pub fn variable_name(&self) -> &str {
221 self.variable_name
222 }
223
224 fn build_pillars<D: ScoreDirector<S>>(&self, score_director: &D) -> Vec<Pillar> {
226 let mut value_to_entities: HashMap<Option<V>, Vec<EntityReference>> = HashMap::new();
228
229 for entity_ref in self.entity_selector.iter(score_director) {
230 let value = (self.value_extractor)(
232 score_director,
233 entity_ref.descriptor_index,
234 entity_ref.entity_index,
235 );
236 value_to_entities.entry(value).or_default().push(entity_ref);
237 }
238
239 let min_size = self.sub_pillar_config.minimum_size;
241 value_to_entities
242 .into_values()
243 .filter(|entities| entities.len() >= min_size)
244 .map(Pillar::new)
245 .collect()
246 }
247}
248
249impl<S, V, ES, E> PillarSelector<S> for DefaultPillarSelector<S, V, ES, E>
250where
251 S: PlanningSolution,
252 V: Clone + Eq + Hash + Send + Sync + 'static,
253 ES: EntitySelector<S>,
254 E: Fn(&dyn ScoreDirector<S>, usize, usize) -> Option<V> + Send + Sync,
255{
256 fn iter<'a, D: ScoreDirector<S>>(
257 &'a self,
258 score_director: &'a D,
259 ) -> Box<dyn Iterator<Item = Pillar> + 'a> {
260 let pillars = self.build_pillars(score_director);
261 Box::new(pillars.into_iter())
262 }
263
264 fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
265 self.build_pillars(score_director).len()
266 }
267
268 fn descriptor_index(&self) -> usize {
269 self.descriptor_index
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276 use crate::heuristic::selector::entity::FromSolutionEntitySelector;
277 use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
278 use solverforge_core::score::SimpleScore;
279 use solverforge_scoring::SimpleScoreDirector;
280 use std::any::TypeId;
281
282 #[derive(Clone, Debug)]
283 struct Employee {
284 id: usize,
285 shift: Option<i32>,
286 }
287
288 #[derive(Clone, Debug)]
289 struct ScheduleSolution {
290 employees: Vec<Employee>,
291 score: Option<SimpleScore>,
292 }
293
294 impl PlanningSolution for ScheduleSolution {
295 type Score = SimpleScore;
296
297 fn score(&self) -> Option<Self::Score> {
298 self.score
299 }
300
301 fn set_score(&mut self, score: Option<Self::Score>) {
302 self.score = score;
303 }
304 }
305
306 fn get_employees(s: &ScheduleSolution) -> &Vec<Employee> {
307 &s.employees
308 }
309
310 fn get_employees_mut(s: &mut ScheduleSolution) -> &mut Vec<Employee> {
311 &mut s.employees
312 }
313
314 fn create_test_director(
315 employees: Vec<Employee>,
316 ) -> SimpleScoreDirector<ScheduleSolution, impl Fn(&ScheduleSolution) -> SimpleScore> {
317 let solution = ScheduleSolution {
318 employees,
319 score: None,
320 };
321
322 let extractor = Box::new(TypedEntityExtractor::new(
323 "Employee",
324 "employees",
325 get_employees,
326 get_employees_mut,
327 ));
328 let entity_desc = EntityDescriptor::new("Employee", TypeId::of::<Employee>(), "employees")
329 .with_extractor(extractor);
330
331 let descriptor =
332 SolutionDescriptor::new("ScheduleSolution", TypeId::of::<ScheduleSolution>())
333 .with_entity(entity_desc);
334
335 SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
336 }
337
338 #[test]
339 fn test_pillar_new() {
340 let pillar = Pillar::new(vec![EntityReference::new(0, 0), EntityReference::new(0, 1)]);
341
342 assert_eq!(pillar.size(), 2);
343 assert!(!pillar.is_empty());
344 assert_eq!(pillar.first(), Some(&EntityReference::new(0, 0)));
345 }
346
347 #[test]
348 fn test_pillar_empty() {
349 let pillar = Pillar::new(vec![]);
350 assert!(pillar.is_empty());
351 assert_eq!(pillar.first(), None);
352 }
353
354 #[test]
355 fn test_default_pillar_selector_groups_by_value() {
356 let employees = vec![
358 Employee {
359 id: 0,
360 shift: Some(1),
361 },
362 Employee {
363 id: 1,
364 shift: Some(1),
365 },
366 Employee {
367 id: 2,
368 shift: Some(2),
369 },
370 Employee {
371 id: 3,
372 shift: Some(2),
373 },
374 Employee {
375 id: 4,
376 shift: Some(2),
377 },
378 Employee {
379 id: 5,
380 shift: Some(3),
381 },
382 ];
383 let director = create_test_director(employees);
384
385 let solution = director.working_solution();
387 for (i, emp) in solution.employees.iter().enumerate() {
388 assert_eq!(emp.id, i);
389 }
390
391 let entity_selector = FromSolutionEntitySelector::new(0);
392 let selector = DefaultPillarSelector::<ScheduleSolution, i32, _, _>::new(
393 entity_selector,
394 0,
395 "shift",
396 |sd: &dyn ScoreDirector<ScheduleSolution>, _desc_idx, entity_idx| {
397 let solution = sd.working_solution();
398 solution.employees.get(entity_idx).and_then(|e| e.shift)
399 },
400 );
401
402 let pillars: Vec<_> = selector.iter(&director).collect();
403
404 assert_eq!(pillars.len(), 3);
406
407 let mut sizes: Vec<_> = pillars.iter().map(|p| p.size()).collect();
409 sizes.sort();
410
411 assert_eq!(sizes, vec![1, 2, 3]);
413 }
414
415 #[test]
416 fn test_pillar_selector_with_minimum_size() {
417 let employees = vec![
419 Employee {
420 id: 0,
421 shift: Some(1),
422 },
423 Employee {
424 id: 1,
425 shift: Some(1),
426 },
427 Employee {
428 id: 2,
429 shift: Some(2),
430 },
431 Employee {
432 id: 3,
433 shift: Some(2),
434 },
435 Employee {
436 id: 4,
437 shift: Some(2),
438 },
439 Employee {
440 id: 5,
441 shift: Some(3),
442 },
443 ];
444 let director = create_test_director(employees);
445
446 let solution = director.working_solution();
448 for (i, emp) in solution.employees.iter().enumerate() {
449 assert_eq!(emp.id, i);
450 }
451
452 let entity_selector = FromSolutionEntitySelector::new(0);
453 let selector = DefaultPillarSelector::<ScheduleSolution, i32, _, _>::new(
454 entity_selector,
455 0,
456 "shift",
457 |sd: &dyn ScoreDirector<ScheduleSolution>, _desc_idx, entity_idx| {
458 let solution = sd.working_solution();
459 solution.employees.get(entity_idx).and_then(|e| e.shift)
460 },
461 )
462 .with_sub_pillar_config(SubPillarConfig::none().with_minimum_size(2));
463
464 let pillars: Vec<_> = selector.iter(&director).collect();
465
466 assert_eq!(pillars.len(), 2);
469 }
470
471 #[test]
472 fn test_pillar_selector_with_none_values() {
473 let employees = vec![
475 Employee {
476 id: 0,
477 shift: Some(1),
478 },
479 Employee { id: 1, shift: None },
480 Employee { id: 2, shift: None },
481 Employee {
482 id: 3,
483 shift: Some(1),
484 },
485 ];
486 let director = create_test_director(employees);
487
488 let solution = director.working_solution();
490 for (i, emp) in solution.employees.iter().enumerate() {
491 assert_eq!(emp.id, i);
492 }
493
494 let entity_selector = FromSolutionEntitySelector::new(0);
495 let selector = DefaultPillarSelector::<ScheduleSolution, i32, _, _>::new(
496 entity_selector,
497 0,
498 "shift",
499 |sd: &dyn ScoreDirector<ScheduleSolution>, _desc_idx, entity_idx| {
500 let solution = sd.working_solution();
501 solution.employees.get(entity_idx).and_then(|e| e.shift)
502 },
503 );
504
505 let pillars: Vec<_> = selector.iter(&director).collect();
506
507 assert_eq!(pillars.len(), 2);
509 }
510
511 #[test]
512 fn test_pillar_selector_empty_solution() {
513 let director = create_test_director(vec![]);
514
515 let entity_selector = FromSolutionEntitySelector::new(0);
516 let selector = DefaultPillarSelector::<ScheduleSolution, i32, _, _>::new(
517 entity_selector,
518 0,
519 "shift",
520 |sd: &dyn ScoreDirector<ScheduleSolution>, _desc_idx, entity_idx| {
521 let solution = sd.working_solution();
522 solution.employees.get(entity_idx).and_then(|e| e.shift)
523 },
524 );
525
526 let pillars: Vec<_> = selector.iter(&director).collect();
527 assert!(pillars.is_empty());
528 assert_eq!(selector.size(&director), 0);
529 }
530
531 #[test]
532 fn test_sub_pillar_config() {
533 let config = SubPillarConfig::none();
534 assert!(!config.enabled);
535 assert_eq!(config.minimum_size, 1);
536
537 let config = SubPillarConfig::all();
538 assert!(config.enabled);
539
540 let config = SubPillarConfig::none()
541 .with_minimum_size(2)
542 .with_maximum_size(5);
543 assert_eq!(config.minimum_size, 2);
544 assert_eq!(config.maximum_size, 5);
545 }
546}