solverforge_solver/heuristic/selector/
pillar.rs1use std::collections::HashMap;
9use std::fmt::Debug;
10use std::hash::Hash;
11use std::marker::PhantomData;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::Director;
15
16use super::entity::{EntityReference, EntitySelector};
17
18#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Pillar {
25 pub entities: Vec<EntityReference>,
27}
28
29impl Pillar {
30 pub fn new(entities: Vec<EntityReference>) -> Self {
31 Self { entities }
32 }
33
34 pub fn size(&self) -> usize {
35 self.entities.len()
36 }
37
38 pub fn is_empty(&self) -> bool {
39 self.entities.is_empty()
40 }
41
42 pub fn first(&self) -> Option<&EntityReference> {
43 self.entities.first()
44 }
45
46 pub fn iter(&self) -> impl Iterator<Item = &EntityReference> {
48 self.entities.iter()
49 }
50}
51
52pub trait PillarSelector<S: PlanningSolution>: Send + Debug {
60 fn iter<'a, D: Director<S>>(
66 &'a self,
67 score_director: &'a D,
68 ) -> impl Iterator<Item = Pillar> + 'a;
69
70 fn size<D: Director<S>>(&self, score_director: &D) -> usize;
71
72 fn is_never_ending(&self) -> bool {
74 false
75 }
76
77 fn descriptor_index(&self) -> usize;
78}
79
80#[derive(Debug, Clone)]
82pub struct SubPillarConfig {
83 pub enabled: bool,
85 pub minimum_size: usize,
87 pub maximum_size: usize,
89}
90
91impl Default for SubPillarConfig {
92 fn default() -> Self {
93 Self {
94 enabled: false,
95 minimum_size: 1,
96 maximum_size: usize::MAX,
97 }
98 }
99}
100
101impl SubPillarConfig {
102 pub fn none() -> Self {
103 Self::default()
104 }
105
106 pub fn all() -> Self {
107 Self {
108 enabled: true,
109 minimum_size: 1,
110 maximum_size: usize::MAX,
111 }
112 }
113
114 pub fn with_minimum_size(mut self, size: usize) -> Self {
115 self.minimum_size = size.max(1);
116 self
117 }
118
119 pub fn with_maximum_size(mut self, size: usize) -> Self {
120 self.maximum_size = size;
121 self
122 }
123}
124
125pub struct DefaultPillarSelector<S, V, ES, E>
138where
139 S: PlanningSolution,
140 V: Clone + Eq + Hash + Send + Sync + 'static,
141 ES: EntitySelector<S>,
142 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
143{
144 entity_selector: ES,
146 descriptor_index: usize,
148 variable_name: &'static str,
150 value_extractor: E,
152 sub_pillar_config: SubPillarConfig,
154 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
156}
157
158impl<S, V, ES, E> Debug for DefaultPillarSelector<S, V, ES, E>
159where
160 S: PlanningSolution,
161 V: Clone + Eq + Hash + Send + Sync + 'static,
162 ES: EntitySelector<S> + Debug,
163 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
164{
165 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166 f.debug_struct("DefaultPillarSelector")
167 .field("entity_selector", &self.entity_selector)
168 .field("descriptor_index", &self.descriptor_index)
169 .field("variable_name", &self.variable_name)
170 .field("sub_pillar_config", &self.sub_pillar_config)
171 .finish()
172 }
173}
174
175impl<S, V, ES, E> DefaultPillarSelector<S, V, ES, E>
176where
177 S: PlanningSolution,
178 V: Clone + Eq + Hash + Send + Sync + 'static,
179 ES: EntitySelector<S>,
180 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
181{
182 pub fn new(
191 entity_selector: ES,
192 descriptor_index: usize,
193 variable_name: &'static str,
194 value_extractor: E,
195 ) -> Self {
196 Self {
197 entity_selector,
198 descriptor_index,
199 variable_name,
200 value_extractor,
201 sub_pillar_config: SubPillarConfig::default(),
202 _phantom: PhantomData,
203 }
204 }
205
206 pub fn with_sub_pillar_config(mut self, config: SubPillarConfig) -> Self {
207 self.sub_pillar_config = config;
208 self
209 }
210
211 pub fn variable_name(&self) -> &str {
212 self.variable_name
213 }
214
215 fn build_pillars<D: Director<S>>(&self, score_director: &D) -> Vec<Pillar> {
217 let mut value_to_entities: HashMap<Option<V>, Vec<EntityReference>> = HashMap::new();
219
220 for entity_ref in self.entity_selector.iter(score_director) {
221 let value = (self.value_extractor)(
223 score_director,
224 entity_ref.descriptor_index,
225 entity_ref.entity_index,
226 );
227 value_to_entities.entry(value).or_default().push(entity_ref);
228 }
229
230 let min_size = self.sub_pillar_config.minimum_size;
232 value_to_entities
233 .into_values()
234 .filter(|entities| entities.len() >= min_size)
235 .map(Pillar::new)
236 .collect()
237 }
238}
239
240impl<S, V, ES, E> PillarSelector<S> for DefaultPillarSelector<S, V, ES, E>
241where
242 S: PlanningSolution,
243 V: Clone + Eq + Hash + Send + Sync + 'static,
244 ES: EntitySelector<S>,
245 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
246{
247 fn iter<'a, D: Director<S>>(
248 &'a self,
249 score_director: &'a D,
250 ) -> impl Iterator<Item = Pillar> + 'a {
251 let pillars = self.build_pillars(score_director);
252 pillars.into_iter()
253 }
254
255 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
256 self.build_pillars(score_director).len()
257 }
258
259 fn descriptor_index(&self) -> usize {
260 self.descriptor_index
261 }
262}
263
264#[cfg(test)]
265#[path = "pillar_tests.rs"]
266mod tests;