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: &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: &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: &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
168pub struct PerEntitySliceValueSelector<S, V> {
169    extractor: for<'a> fn(&'a S, usize) -> &'a [V],
170    _phantom: PhantomData<(fn() -> S, fn() -> V)>,
171}
172
173impl<S, V> Debug for PerEntitySliceValueSelector<S, V> {
174    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
175        f.debug_struct("PerEntitySliceValueSelector").finish()
176    }
177}
178
179impl<S, V> PerEntitySliceValueSelector<S, V> {
180    pub fn new(extractor: for<'a> fn(&'a S, usize) -> &'a [V]) -> Self {
181        Self {
182            extractor,
183            _phantom: PhantomData,
184        }
185    }
186}
187
188impl<S, V> ValueSelector<S, V> for PerEntitySliceValueSelector<S, V>
189where
190    S: PlanningSolution,
191    V: Copy + Send + Debug + 'static,
192{
193    fn iter_typed<'a, D: Director<S>>(
194        &'a self,
195        score_director: &D,
196        _descriptor_index: usize,
197        entity_index: usize,
198    ) -> impl Iterator<Item = V> + 'a {
199        (self.extractor)(score_director.working_solution(), entity_index)
200            .to_vec()
201            .into_iter()
202    }
203
204    fn size<D: Director<S>>(
205        &self,
206        score_director: &D,
207        _descriptor_index: usize,
208        entity_index: usize,
209    ) -> usize {
210        (self.extractor)(score_director.working_solution(), entity_index).len()
211    }
212}
213
214impl<S, V> ValueSelector<S, V> for FromSolutionValueSelector<S, V>
215where
216    S: PlanningSolution,
217    V: Clone + Send + Debug + 'static,
218{
219    fn iter_typed<'a, D: Director<S>>(
220        &'a self,
221        score_director: &D,
222        _descriptor_index: usize,
223        _entity_index: usize,
224    ) -> impl Iterator<Item = V> + 'a {
225        let values = (self.extractor)(score_director.working_solution());
226        values.into_iter()
227    }
228
229    fn size<D: Director<S>>(
230        &self,
231        score_director: &D,
232        _descriptor_index: usize,
233        _entity_index: usize,
234    ) -> usize {
235        (self.extractor)(score_director.working_solution()).len()
236    }
237}
238
239/// A value selector that generates a range of usize values 0..count.
240///
241/// Uses a function pointer to get the count from the solution.
242pub struct RangeValueSelector<S> {
243    count_fn: fn(&S) -> usize,
244    _phantom: PhantomData<fn() -> S>,
245}
246
247impl<S> Debug for RangeValueSelector<S> {
248    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249        f.debug_struct("RangeValueSelector").finish()
250    }
251}
252
253impl<S> RangeValueSelector<S> {
254    pub fn new(count_fn: fn(&S) -> usize) -> Self {
255        Self {
256            count_fn,
257            _phantom: PhantomData,
258        }
259    }
260}
261
262impl<S> ValueSelector<S, usize> for RangeValueSelector<S>
263where
264    S: PlanningSolution,
265{
266    fn iter_typed<'a, D: Director<S>>(
267        &'a self,
268        score_director: &D,
269        _descriptor_index: usize,
270        _entity_index: usize,
271    ) -> impl Iterator<Item = usize> + 'a {
272        let count = (self.count_fn)(score_director.working_solution());
273        0..count
274    }
275
276    fn size<D: Director<S>>(
277        &self,
278        score_director: &D,
279        _descriptor_index: usize,
280        _entity_index: usize,
281    ) -> usize {
282        (self.count_fn)(score_director.working_solution())
283    }
284}
285
286#[cfg(test)]
287#[path = "value_selector_tests.rs"]
288mod tests;