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>>(&'a self, score_director: &D) -> impl Iterator<Item = Pillar> + 'a;
66
67 fn size<D: Director<S>>(&self, score_director: &D) -> usize;
68
69 fn is_never_ending(&self) -> bool {
71 false
72 }
73
74 fn descriptor_index(&self) -> usize;
75}
76
77#[derive(Debug, Clone)]
79pub struct SubPillarConfig {
80 pub enabled: bool,
82 pub minimum_size: usize,
84 pub maximum_size: usize,
86}
87
88impl Default for SubPillarConfig {
89 fn default() -> Self {
90 Self {
91 enabled: false,
92 minimum_size: 1,
93 maximum_size: usize::MAX,
94 }
95 }
96}
97
98impl SubPillarConfig {
99 pub fn none() -> Self {
100 Self::default()
101 }
102
103 pub fn all() -> Self {
104 Self {
105 enabled: true,
106 minimum_size: 1,
107 maximum_size: usize::MAX,
108 }
109 }
110
111 pub fn with_minimum_size(mut self, size: usize) -> Self {
112 self.minimum_size = size.max(1);
113 self
114 }
115
116 pub fn with_maximum_size(mut self, size: usize) -> Self {
117 self.maximum_size = size;
118 self
119 }
120}
121
122pub struct DefaultPillarSelector<S, V, ES, E>
135where
136 S: PlanningSolution,
137 V: Clone + Eq + Hash + Send + Sync + 'static,
138 ES: EntitySelector<S>,
139 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
140{
141 entity_selector: ES,
143 descriptor_index: usize,
145 variable_name: &'static str,
147 value_extractor: E,
149 sub_pillar_config: SubPillarConfig,
151 _phantom: PhantomData<(fn() -> S, fn() -> V)>,
153}
154
155impl<S, V, ES, E> Debug for DefaultPillarSelector<S, V, ES, E>
156where
157 S: PlanningSolution,
158 V: Clone + Eq + Hash + Send + Sync + 'static,
159 ES: EntitySelector<S> + Debug,
160 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
161{
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.debug_struct("DefaultPillarSelector")
164 .field("entity_selector", &self.entity_selector)
165 .field("descriptor_index", &self.descriptor_index)
166 .field("variable_name", &self.variable_name)
167 .field("sub_pillar_config", &self.sub_pillar_config)
168 .finish()
169 }
170}
171
172impl<S, V, ES, E> DefaultPillarSelector<S, V, ES, E>
173where
174 S: PlanningSolution,
175 V: Clone + Eq + Hash + Send + Sync + 'static,
176 ES: EntitySelector<S>,
177 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
178{
179 pub fn new(
188 entity_selector: ES,
189 descriptor_index: usize,
190 variable_name: &'static str,
191 value_extractor: E,
192 ) -> Self {
193 Self {
194 entity_selector,
195 descriptor_index,
196 variable_name,
197 value_extractor,
198 sub_pillar_config: SubPillarConfig::default(),
199 _phantom: PhantomData,
200 }
201 }
202
203 pub fn with_sub_pillar_config(mut self, config: SubPillarConfig) -> Self {
204 self.sub_pillar_config = config;
205 self
206 }
207
208 pub fn variable_name(&self) -> &str {
209 self.variable_name
210 }
211
212 fn build_pillars<D: Director<S>>(&self, score_director: &D) -> Vec<Pillar> {
214 let mut value_to_entities: HashMap<Option<V>, Vec<EntityReference>> = HashMap::new();
216
217 for entity_ref in self.entity_selector.iter(score_director) {
218 let value = (self.value_extractor)(
220 score_director,
221 entity_ref.descriptor_index,
222 entity_ref.entity_index,
223 );
224 value_to_entities.entry(value).or_default().push(entity_ref);
225 }
226
227 let min_size = self.sub_pillar_config.minimum_size;
229 value_to_entities
230 .into_values()
231 .filter(|entities| entities.len() >= min_size)
232 .map(Pillar::new)
233 .collect()
234 }
235}
236
237impl<S, V, ES, E> PillarSelector<S> for DefaultPillarSelector<S, V, ES, E>
238where
239 S: PlanningSolution,
240 V: Clone + Eq + Hash + Send + Sync + 'static,
241 ES: EntitySelector<S>,
242 E: Fn(&dyn Director<S>, usize, usize) -> Option<V> + Send + Sync,
243{
244 fn iter<'a, D: Director<S>>(&'a self, score_director: &D) -> impl Iterator<Item = Pillar> + 'a {
245 let pillars = self.build_pillars(score_director);
246 pillars.into_iter()
247 }
248
249 fn size<D: Director<S>>(&self, score_director: &D) -> 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)]
259#[path = "pillar_tests.rs"]
260mod tests;