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