Skip to main content

solverforge_solver/heuristic/selector/
value_selector.rs

1/* Value selectors for high-performance value iteration.
2
3Unlike the type-erased `ValueSelector` that yields `Arc<dyn Any>`,
4these selectors yield `V` directly with no heap allocation.
5*/
6
7use std::fmt::Debug;
8use std::marker::PhantomData;
9
10use solverforge_core::domain::PlanningSolution;
11use solverforge_scoring::Director;
12
13/// A value selector that yields values of type `V` directly.
14///
15/// Unlike `ValueSelector` which returns `Arc<dyn Any>`, this trait
16/// returns `V` inline, eliminating heap allocation per value.
17///
18/// # Type Parameters
19/// * `S` - The planning solution type
20/// * `V` - The value type
21pub trait ValueSelector<S: PlanningSolution, V>: Send + Debug {
22    // Returns an iterator over values for the given entity.
23    fn iter_typed<'a, D: Director<S>>(
24        &'a self,
25        score_director: &'a D,
26        descriptor_index: usize,
27        entity_index: usize,
28    ) -> impl Iterator<Item = V> + 'a;
29
30    fn size<D: Director<S>>(
31        &self,
32        score_director: &D,
33        descriptor_index: usize,
34        entity_index: usize,
35    ) -> usize;
36
37    // Returns true if this selector may return the same value multiple times.
38    fn is_never_ending(&self) -> bool {
39        false
40    }
41}
42
43/// A value selector with a static list of values.
44pub struct StaticValueSelector<S, V> {
45    values: Vec<V>,
46    _phantom: PhantomData<fn() -> S>,
47}
48
49impl<S, V: Clone> Clone for StaticValueSelector<S, V> {
50    fn clone(&self) -> Self {
51        Self {
52            values: self.values.clone(),
53            _phantom: PhantomData,
54        }
55    }
56}
57
58impl<S, V: Debug> Debug for StaticValueSelector<S, V> {
59    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60        f.debug_struct("StaticValueSelector")
61            .field("values", &self.values)
62            .finish()
63    }
64}
65
66impl<S, V: Clone> StaticValueSelector<S, V> {
67    pub fn new(values: Vec<V>) -> Self {
68        Self {
69            values,
70            _phantom: PhantomData,
71        }
72    }
73
74    pub fn values(&self) -> &[V] {
75        &self.values
76    }
77}
78
79impl<S, V> ValueSelector<S, V> for StaticValueSelector<S, V>
80where
81    S: PlanningSolution,
82    V: Clone + Send + Debug + 'static,
83{
84    fn iter_typed<'a, D: Director<S>>(
85        &'a self,
86        _score_director: &'a D,
87        _descriptor_index: usize,
88        _entity_index: usize,
89    ) -> impl Iterator<Item = V> + 'a {
90        self.values.iter().cloned()
91    }
92
93    fn size<D: Director<S>>(
94        &self,
95        _score_director: &D,
96        _descriptor_index: usize,
97        _entity_index: usize,
98    ) -> usize {
99        self.values.len()
100    }
101}
102
103/// A value selector that extracts values from the solution using a function pointer.
104pub struct FromSolutionValueSelector<S, V> {
105    extractor: fn(&S) -> Vec<V>,
106    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
107}
108
109impl<S, V> Debug for FromSolutionValueSelector<S, V> {
110    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111        f.debug_struct("FromSolutionValueSelector").finish()
112    }
113}
114
115impl<S, V> FromSolutionValueSelector<S, V> {
116    pub fn new(extractor: fn(&S) -> Vec<V>) -> Self {
117        Self {
118            extractor,
119            _phantom: PhantomData,
120        }
121    }
122}
123
124pub struct PerEntityValueSelector<S, V> {
125    extractor: fn(&S, usize) -> Vec<V>,
126    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
127}
128
129impl<S, V> Debug for PerEntityValueSelector<S, V> {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("PerEntityValueSelector").finish()
132    }
133}
134
135impl<S, V> PerEntityValueSelector<S, V> {
136    pub fn new(extractor: fn(&S, usize) -> Vec<V>) -> Self {
137        Self {
138            extractor,
139            _phantom: PhantomData,
140        }
141    }
142}
143
144impl<S, V> ValueSelector<S, V> for PerEntityValueSelector<S, V>
145where
146    S: PlanningSolution,
147    V: Clone + Send + Debug + 'static,
148{
149    fn iter_typed<'a, D: Director<S>>(
150        &'a self,
151        score_director: &'a D,
152        _descriptor_index: usize,
153        entity_index: usize,
154    ) -> impl Iterator<Item = V> + 'a {
155        (self.extractor)(score_director.working_solution(), entity_index).into_iter()
156    }
157
158    fn size<D: Director<S>>(
159        &self,
160        score_director: &D,
161        _descriptor_index: usize,
162        entity_index: usize,
163    ) -> usize {
164        (self.extractor)(score_director.working_solution(), entity_index).len()
165    }
166}
167
168impl<S, V> ValueSelector<S, V> for FromSolutionValueSelector<S, V>
169where
170    S: PlanningSolution,
171    V: Clone + Send + Debug + 'static,
172{
173    fn iter_typed<'a, D: Director<S>>(
174        &'a self,
175        score_director: &'a D,
176        _descriptor_index: usize,
177        _entity_index: usize,
178    ) -> impl Iterator<Item = V> + 'a {
179        let values = (self.extractor)(score_director.working_solution());
180        values.into_iter()
181    }
182
183    fn size<D: Director<S>>(
184        &self,
185        score_director: &D,
186        _descriptor_index: usize,
187        _entity_index: usize,
188    ) -> usize {
189        (self.extractor)(score_director.working_solution()).len()
190    }
191}
192
193/// A value selector that generates a range of usize values 0..count.
194///
195/// Uses a function pointer to get the count from the solution.
196pub struct RangeValueSelector<S> {
197    count_fn: fn(&S) -> usize,
198    _phantom: PhantomData<fn() -> S>,
199}
200
201impl<S> Debug for RangeValueSelector<S> {
202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203        f.debug_struct("RangeValueSelector").finish()
204    }
205}
206
207impl<S> RangeValueSelector<S> {
208    pub fn new(count_fn: fn(&S) -> usize) -> Self {
209        Self {
210            count_fn,
211            _phantom: PhantomData,
212        }
213    }
214}
215
216impl<S> ValueSelector<S, usize> for RangeValueSelector<S>
217where
218    S: PlanningSolution,
219{
220    fn iter_typed<'a, D: Director<S>>(
221        &'a self,
222        score_director: &'a D,
223        _descriptor_index: usize,
224        _entity_index: usize,
225    ) -> impl Iterator<Item = usize> + 'a {
226        let count = (self.count_fn)(score_director.working_solution());
227        0..count
228    }
229
230    fn size<D: Director<S>>(
231        &self,
232        score_director: &D,
233        _descriptor_index: usize,
234        _entity_index: usize,
235    ) -> usize {
236        (self.count_fn)(score_director.working_solution())
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use solverforge_core::domain::{
244        EntityCollectionExtractor, EntityDescriptor, SolutionDescriptor,
245    };
246    use solverforge_core::score::SoftScore;
247    use solverforge_scoring::ScoreDirector;
248    use std::any::TypeId;
249
250    #[derive(Clone, Debug)]
251    struct Task {
252        id: usize,
253        priority: Option<i32>,
254    }
255
256    #[derive(Clone, Debug)]
257    struct TaskSolution {
258        tasks: Vec<Task>,
259        score: Option<SoftScore>,
260    }
261
262    impl PlanningSolution for TaskSolution {
263        type Score = SoftScore;
264        fn score(&self) -> Option<Self::Score> {
265            self.score
266        }
267        fn set_score(&mut self, score: Option<Self::Score>) {
268            self.score = score;
269        }
270    }
271
272    fn create_director(tasks: Vec<Task>) -> ScoreDirector<TaskSolution, ()> {
273        let solution = TaskSolution { tasks, score: None };
274        let extractor = Box::new(EntityCollectionExtractor::new(
275            "Task",
276            "tasks",
277            |s: &TaskSolution| &s.tasks,
278            |s: &mut TaskSolution| &mut s.tasks,
279        ));
280        let entity_desc =
281            EntityDescriptor::new("Task", TypeId::of::<Task>(), "tasks").with_extractor(extractor);
282        let descriptor = SolutionDescriptor::new("TaskSolution", TypeId::of::<TaskSolution>())
283            .with_entity(entity_desc);
284        ScoreDirector::simple(solution, descriptor, |s, _| s.tasks.len())
285    }
286
287    #[test]
288    fn test_static_value_selector_selector() {
289        let director = create_director(vec![Task {
290            id: 0,
291            priority: None,
292        }]);
293        let selector = StaticValueSelector::<TaskSolution, i32>::new(vec![1, 2, 3, 4, 5]);
294
295        let values: Vec<_> = selector.iter_typed(&director, 0, 0).collect();
296        assert_eq!(values, vec![1, 2, 3, 4, 5]);
297        assert_eq!(selector.size(&director, 0, 0), 5);
298    }
299
300    #[test]
301    fn test_from_solution_value_selector_selector() {
302        let director = create_director(vec![
303            Task {
304                id: 0,
305                priority: Some(10),
306            },
307            Task {
308                id: 1,
309                priority: Some(20),
310            },
311        ]);
312
313        // Verify entity IDs
314        let solution = director.working_solution();
315        assert_eq!(solution.tasks[0].id, 0);
316        assert_eq!(solution.tasks[1].id, 1);
317
318        // Extract priorities directly from solution - zero erasure
319        fn extract_priorities(s: &TaskSolution) -> Vec<i32> {
320            s.tasks.iter().filter_map(|t| t.priority).collect()
321        }
322
323        let selector = FromSolutionValueSelector::new(extract_priorities);
324
325        let values: Vec<_> = selector.iter_typed(&director, 0, 0).collect();
326        assert_eq!(values, vec![10, 20]);
327    }
328}