solverforge_solver/heuristic/selector/
pillar.rs1use 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 ) -> impl 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, fn() -> 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 ) -> impl Iterator<Item = Pillar> + 'a {
260 let pillars = self.build_pillars(score_director);
261 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)]
274#[path = "pillar_tests.rs"]
275mod tests;