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