Skip to main content

solverforge_solver/phase/construction/
forager.rs

1/* Foragers for construction heuristic move selection
2
3Foragers determine which move to select from the candidates
4generated for each entity placement.
5
6# Zero-Erasure Design
7
8Foragers return indices into the placement's move Vec, not cloned moves.
9The caller takes ownership via the index.
10*/
11
12use std::fmt::Debug;
13use std::marker::PhantomData;
14
15use solverforge_config::ConstructionObligation;
16use solverforge_core::domain::PlanningSolution;
17use solverforge_scoring::Director;
18
19use crate::heuristic::r#move::Move;
20use crate::scope::{ProgressCallback, StepScope};
21
22use super::Placement;
23
24/// Selection result for a single construction placement.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum ConstructionChoice {
27    KeepCurrent,
28    Select(usize),
29}
30
31/// Trait for selecting a move during construction.
32///
33/// Foragers evaluate candidate moves and pick one based on their strategy.
34// Returns either a selected move index or an explicit keep-current choice.
35///
36/// # Type Parameters
37/// * `S` - The planning solution type
38/// * `M` - The move type
39pub trait ConstructionForager<S, M>: Send + Debug
40where
41    S: PlanningSolution,
42    M: Move<S>,
43{
44    /* Picks a construction choice from the placement's candidates.
45     */
46    fn pick_move_index<D: Director<S>>(
47        &self,
48        placement: &Placement<S, M>,
49        score_director: &mut D,
50    ) -> ConstructionChoice;
51
52    fn select_move_index<D, BestCb>(
53        &self,
54        placement: &Placement<S, M>,
55        _construction_obligation: ConstructionObligation,
56        step_scope: &mut StepScope<'_, '_, '_, S, D, BestCb>,
57    ) -> Option<ConstructionChoice>
58    where
59        D: Director<S>,
60        BestCb: ProgressCallback<S>,
61    {
62        Some(self.pick_move_index(placement, step_scope.score_director_mut()))
63    }
64}
65
66/// First Fit forager - picks the first feasible move.
67///
68/// This is the fastest forager but may not produce optimal results.
69/// It simply takes the first move that can be executed.
70pub struct FirstFitForager<S, M> {
71    _phantom: PhantomData<fn() -> (S, M)>,
72}
73
74impl<S, M> Clone for FirstFitForager<S, M> {
75    fn clone(&self) -> Self {
76        *self
77    }
78}
79
80impl<S, M> Copy for FirstFitForager<S, M> {}
81
82impl<S, M> Default for FirstFitForager<S, M> {
83    fn default() -> Self {
84        Self::new()
85    }
86}
87
88impl<S, M> Debug for FirstFitForager<S, M> {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        f.debug_struct("FirstFitForager").finish()
91    }
92}
93
94impl<S, M> FirstFitForager<S, M> {
95    pub fn new() -> Self {
96        Self {
97            _phantom: PhantomData,
98        }
99    }
100}
101
102/// Best Fit forager - evaluates all moves and picks the best.
103///
104/// This forager evaluates each candidate move by executing it,
105/// calculating the score, and undoing it. The move with the best
106/// score is selected.
107pub struct BestFitForager<S, M> {
108    _phantom: PhantomData<fn() -> (S, M)>,
109}
110
111impl<S, M> Clone for BestFitForager<S, M> {
112    fn clone(&self) -> Self {
113        *self
114    }
115}
116
117impl<S, M> Copy for BestFitForager<S, M> {}
118
119impl<S, M> Default for BestFitForager<S, M> {
120    fn default() -> Self {
121        Self::new()
122    }
123}
124
125impl<S, M> Debug for BestFitForager<S, M> {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        f.debug_struct("BestFitForager").finish()
128    }
129}
130
131impl<S, M> BestFitForager<S, M> {
132    pub fn new() -> Self {
133        Self {
134            _phantom: PhantomData,
135        }
136    }
137}
138
139/// First Feasible forager - picks the first move that results in a feasible score.
140///
141/// This forager evaluates moves until it finds one that produces a feasible
142/// (non-negative hard score) solution.
143pub struct FirstFeasibleForager<S, M> {
144    _phantom: PhantomData<fn() -> (S, M)>,
145}
146
147impl<S, M> Clone for FirstFeasibleForager<S, M> {
148    fn clone(&self) -> Self {
149        *self
150    }
151}
152
153impl<S, M> Copy for FirstFeasibleForager<S, M> {}
154
155impl<S, M> Default for FirstFeasibleForager<S, M> {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161impl<S, M> Debug for FirstFeasibleForager<S, M> {
162    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163        f.debug_struct("FirstFeasibleForager").finish()
164    }
165}
166
167impl<S, M> FirstFeasibleForager<S, M> {
168    pub fn new() -> Self {
169        Self {
170            _phantom: PhantomData,
171        }
172    }
173}
174
175/// Weakest Fit forager - picks the move with the lowest strength value.
176///
177/// This forager evaluates each candidate move using a strength function
178/// and selects the move with the minimum strength. This is useful for
179/// assigning the "weakest" or least constraining values first.
180pub struct WeakestFitForager<S, M> {
181    // Function to evaluate strength of a move.
182    strength_fn: fn(&M, &S) -> i64,
183    _phantom: PhantomData<fn() -> S>,
184}
185
186impl<S, M> Clone for WeakestFitForager<S, M> {
187    fn clone(&self) -> Self {
188        *self
189    }
190}
191
192impl<S, M> Copy for WeakestFitForager<S, M> {}
193
194impl<S, M> Debug for WeakestFitForager<S, M> {
195    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196        f.debug_struct("WeakestFitForager").finish()
197    }
198}
199
200impl<S, M> WeakestFitForager<S, M> {
201    /// Creates a new Weakest Fit forager with the given strength function.
202    ///
203    /// The strength function evaluates how "strong" a move is. The forager
204    /// picks the move with the minimum strength value.
205    pub fn new(strength_fn: fn(&M, &S) -> i64) -> Self {
206        Self {
207            strength_fn,
208            _phantom: PhantomData,
209        }
210    }
211
212    pub(crate) fn strength(&self, mov: &M, solution: &S) -> i64 {
213        (self.strength_fn)(mov, solution)
214    }
215}
216
217/// Strongest Fit forager - picks the move with the highest strength value.
218///
219/// This forager evaluates each candidate move using a strength function
220/// and selects the move with the maximum strength. This is useful for
221/// assigning the "strongest" or most constraining values first.
222pub struct StrongestFitForager<S, M> {
223    // Function to evaluate strength of a move.
224    strength_fn: fn(&M, &S) -> i64,
225    _phantom: PhantomData<fn() -> S>,
226}
227
228impl<S, M> Clone for StrongestFitForager<S, M> {
229    fn clone(&self) -> Self {
230        *self
231    }
232}
233
234impl<S, M> Copy for StrongestFitForager<S, M> {}
235
236impl<S, M> Debug for StrongestFitForager<S, M> {
237    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238        f.debug_struct("StrongestFitForager").finish()
239    }
240}
241
242impl<S, M> StrongestFitForager<S, M> {
243    /// Creates a new Strongest Fit forager with the given strength function.
244    ///
245    /// The strength function evaluates how "strong" a move is. The forager
246    /// picks the move with the maximum strength value.
247    pub fn new(strength_fn: fn(&M, &S) -> i64) -> Self {
248        Self {
249            strength_fn,
250            _phantom: PhantomData,
251        }
252    }
253
254    pub(crate) fn strength(&self, mov: &M, solution: &S) -> i64 {
255        (self.strength_fn)(mov, solution)
256    }
257}
258
259#[cfg(test)]
260mod tests;