solverforge_solver/phase/construction/
placer.rs

1//! Entity placers for construction heuristic
2//!
3//! Placers enumerate the entities that need values assigned and
4//! generate candidate moves for each entity.
5
6use std::fmt::Debug;
7use std::marker::PhantomData;
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::ScoreDirector;
11
12use crate::heuristic::r#move::{ChangeMove, Move};
13use crate::heuristic::selector::{EntityReference, EntitySelector, TypedValueSelector};
14
15/// A placement represents an entity that needs a value assigned,
16/// along with the candidate moves to assign values.
17///
18/// # Type Parameters
19/// * `S` - The planning solution type
20/// * `M` - The move type
21pub struct Placement<S, M>
22where
23    S: PlanningSolution,
24    M: Move<S>,
25{
26    /// The entity reference.
27    pub entity_ref: EntityReference,
28    /// Candidate moves for this placement.
29    pub moves: Vec<M>,
30    _phantom: PhantomData<fn() -> S>,
31}
32
33impl<S, M> Placement<S, M>
34where
35    S: PlanningSolution,
36    M: Move<S>,
37{
38    /// Creates a new placement.
39    pub fn new(entity_ref: EntityReference, moves: Vec<M>) -> Self {
40        Self {
41            entity_ref,
42            moves,
43            _phantom: PhantomData,
44        }
45    }
46
47    /// Returns true if there are no candidate moves.
48    pub fn is_empty(&self) -> bool {
49        self.moves.is_empty()
50    }
51
52    /// Takes ownership of a move at the given index.
53    ///
54    /// Uses swap_remove for O(1) removal.
55    pub fn take_move(&mut self, index: usize) -> M {
56        self.moves.swap_remove(index)
57    }
58}
59
60impl<S, M> Debug for Placement<S, M>
61where
62    S: PlanningSolution,
63    M: Move<S>,
64{
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        f.debug_struct("Placement")
67            .field("entity_ref", &self.entity_ref)
68            .field("move_count", &self.moves.len())
69            .finish()
70    }
71}
72
73/// Trait for placing entities during construction.
74///
75/// Entity placers iterate over uninitialized entities and generate
76/// candidate moves for each.
77///
78/// # Type Parameters
79/// * `S` - The planning solution type
80/// * `M` - The move type
81pub trait EntityPlacer<S, M>: Send + Debug
82where
83    S: PlanningSolution,
84    M: Move<S>,
85{
86    /// Returns all placements (entities + their candidate moves).
87    fn get_placements<D: ScoreDirector<S>>(&self, score_director: &D) -> Vec<Placement<S, M>>;
88}
89
90/// A queued entity placer that processes entities in order.
91///
92/// For each uninitialized entity, generates change moves for all possible values.
93/// Uses typed function pointers for zero-erasure access.
94///
95/// # Type Parameters
96/// * `S` - The planning solution type
97/// * `V` - The value type
98/// * `ES` - The entity selector type
99/// * `VS` - The value selector type
100pub struct QueuedEntityPlacer<S, V, ES, VS>
101where
102    S: PlanningSolution,
103    ES: EntitySelector<S>,
104    VS: TypedValueSelector<S, V>,
105{
106    /// The entity selector.
107    entity_selector: ES,
108    /// The value selector.
109    value_selector: VS,
110    /// Typed getter function pointer.
111    getter: fn(&S, usize) -> Option<V>,
112    /// Typed setter function pointer.
113    setter: fn(&mut S, usize, Option<V>),
114    /// The variable name.
115    variable_name: &'static str,
116    /// The descriptor index.
117    descriptor_index: usize,
118    _phantom: PhantomData<V>,
119}
120
121impl<S, V, ES, VS> QueuedEntityPlacer<S, V, ES, VS>
122where
123    S: PlanningSolution,
124    ES: EntitySelector<S>,
125    VS: TypedValueSelector<S, V>,
126{
127    /// Creates a new queued entity placer with typed function pointers.
128    pub fn new(
129        entity_selector: ES,
130        value_selector: VS,
131        getter: fn(&S, usize) -> Option<V>,
132        setter: fn(&mut S, usize, Option<V>),
133        descriptor_index: usize,
134        variable_name: &'static str,
135    ) -> Self {
136        Self {
137            entity_selector,
138            value_selector,
139            getter,
140            setter,
141            variable_name,
142            descriptor_index,
143            _phantom: PhantomData,
144        }
145    }
146}
147
148impl<S, V, ES, VS> Debug for QueuedEntityPlacer<S, V, ES, VS>
149where
150    S: PlanningSolution,
151    ES: EntitySelector<S> + Debug,
152    VS: TypedValueSelector<S, V> + Debug,
153{
154    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155        f.debug_struct("QueuedEntityPlacer")
156            .field("entity_selector", &self.entity_selector)
157            .field("value_selector", &self.value_selector)
158            .field("variable_name", &self.variable_name)
159            .finish()
160    }
161}
162
163impl<S, V, ES, VS> EntityPlacer<S, ChangeMove<S, V>> for QueuedEntityPlacer<S, V, ES, VS>
164where
165    S: PlanningSolution,
166    V: Clone + PartialEq + Send + Sync + Debug + 'static,
167    ES: EntitySelector<S>,
168    VS: TypedValueSelector<S, V>,
169{
170    fn get_placements<D: ScoreDirector<S>>(
171        &self,
172        score_director: &D,
173    ) -> Vec<Placement<S, ChangeMove<S, V>>> {
174        let variable_name = self.variable_name;
175        let descriptor_index = self.descriptor_index;
176        let getter = self.getter;
177        let setter = self.setter;
178
179        self.entity_selector
180            .iter(score_director)
181            .filter_map(|entity_ref| {
182                // Check if entity is uninitialized using typed getter - zero erasure
183                let current_value =
184                    getter(score_director.working_solution(), entity_ref.entity_index);
185
186                // Only include uninitialized entities
187                if current_value.is_some() {
188                    return None;
189                }
190
191                // Generate moves for all possible values
192                let moves: Vec<ChangeMove<S, V>> = self
193                    .value_selector
194                    .iter_typed(
195                        score_director,
196                        entity_ref.descriptor_index,
197                        entity_ref.entity_index,
198                    )
199                    .map(|value| {
200                        ChangeMove::new(
201                            entity_ref.entity_index,
202                            Some(value),
203                            getter,
204                            setter,
205                            variable_name,
206                            descriptor_index,
207                        )
208                    })
209                    .collect();
210
211                if moves.is_empty() {
212                    None
213                } else {
214                    Some(Placement::new(entity_ref, moves))
215                }
216            })
217            .collect()
218    }
219}
220
221/// Entity placer that sorts placements by a comparator function.
222///
223/// Wraps an inner placer and sorts its placements using a typed comparator.
224/// This enables FIRST_FIT_DECREASING and similar construction variants.
225///
226/// # Example
227///
228/// ```
229/// use solverforge_solver::phase::construction::{SortedEntityPlacer, QueuedEntityPlacer, EntityPlacer};
230/// use solverforge_solver::heuristic::r#move::ChangeMove;
231/// use solverforge_solver::heuristic::selector::{FromSolutionEntitySelector, StaticTypedValueSelector};
232/// use solverforge_core::domain::PlanningSolution;
233/// use solverforge_core::score::SimpleScore;
234/// use solverforge_scoring::SimpleScoreDirector;
235/// use std::cmp::Ordering;
236///
237/// #[derive(Clone, Debug)]
238/// struct Task { difficulty: i32, assigned: Option<i32> }
239///
240/// #[derive(Clone, Debug)]
241/// struct Solution { tasks: Vec<Task>, score: Option<SimpleScore> }
242///
243/// impl PlanningSolution for Solution {
244///     type Score = SimpleScore;
245///     fn score(&self) -> Option<Self::Score> { self.score }
246///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
247/// }
248///
249/// fn get_assigned(s: &Solution, i: usize) -> Option<i32> {
250///     s.tasks.get(i).and_then(|t| t.assigned)
251/// }
252/// fn set_assigned(s: &mut Solution, i: usize, v: Option<i32>) {
253///     if let Some(t) = s.tasks.get_mut(i) { t.assigned = v; }
254/// }
255///
256/// // Sort entities by difficulty (descending) for FIRST_FIT_DECREASING
257/// fn difficulty_descending(s: &Solution, a: usize, b: usize) -> Ordering {
258///     let da = s.tasks.get(a).map(|t| t.difficulty).unwrap_or(0);
259///     let db = s.tasks.get(b).map(|t| t.difficulty).unwrap_or(0);
260///     db.cmp(&da)  // Descending order
261/// }
262/// ```
263pub struct SortedEntityPlacer<S, M, Inner>
264where
265    S: PlanningSolution,
266    M: Move<S>,
267    Inner: EntityPlacer<S, M>,
268{
269    inner: Inner,
270    /// Comparator function: takes (solution, entity_index_a, entity_index_b) -> Ordering
271    comparator: fn(&S, usize, usize) -> std::cmp::Ordering,
272    _phantom: PhantomData<fn() -> (S, M)>,
273}
274
275impl<S, M, Inner> SortedEntityPlacer<S, M, Inner>
276where
277    S: PlanningSolution,
278    M: Move<S>,
279    Inner: EntityPlacer<S, M>,
280{
281    /// Creates a new sorted entity placer.
282    ///
283    /// # Arguments
284    /// * `inner` - The inner placer to wrap
285    /// * `comparator` - Function to compare entities: `(solution, idx_a, idx_b) -> Ordering`
286    pub fn new(inner: Inner, comparator: fn(&S, usize, usize) -> std::cmp::Ordering) -> Self {
287        Self {
288            inner,
289            comparator,
290            _phantom: PhantomData,
291        }
292    }
293}
294
295impl<S, M, Inner> Debug for SortedEntityPlacer<S, M, Inner>
296where
297    S: PlanningSolution,
298    M: Move<S>,
299    Inner: EntityPlacer<S, M>,
300{
301    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302        f.debug_struct("SortedEntityPlacer")
303            .field("inner", &self.inner)
304            .finish()
305    }
306}
307
308impl<S, M, Inner> EntityPlacer<S, M> for SortedEntityPlacer<S, M, Inner>
309where
310    S: PlanningSolution,
311    M: Move<S>,
312    Inner: EntityPlacer<S, M>,
313{
314    fn get_placements<D: ScoreDirector<S>>(&self, score_director: &D) -> Vec<Placement<S, M>> {
315        let mut placements = self.inner.get_placements(score_director);
316        let solution = score_director.working_solution();
317        let cmp = self.comparator;
318
319        placements.sort_by(|a, b| {
320            cmp(
321                solution,
322                a.entity_ref.entity_index,
323                b.entity_ref.entity_index,
324            )
325        });
326
327        placements
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use crate::heuristic::selector::{FromSolutionEntitySelector, StaticTypedValueSelector};
335    use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
336    use solverforge_core::score::SimpleScore;
337    use solverforge_scoring::SimpleScoreDirector;
338    use std::any::TypeId;
339
340    #[derive(Clone, Debug)]
341    struct Queen {
342        row: Option<i32>,
343    }
344
345    #[derive(Clone, Debug)]
346    struct NQueensSolution {
347        queens: Vec<Queen>,
348        score: Option<SimpleScore>,
349    }
350
351    impl PlanningSolution for NQueensSolution {
352        type Score = SimpleScore;
353
354        fn score(&self) -> Option<Self::Score> {
355            self.score
356        }
357
358        fn set_score(&mut self, score: Option<Self::Score>) {
359            self.score = score;
360        }
361    }
362
363    fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
364        &s.queens
365    }
366
367    fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
368        &mut s.queens
369    }
370
371    // Typed getter - zero erasure
372    fn get_queen_row(s: &NQueensSolution, idx: usize) -> Option<i32> {
373        s.queens.get(idx).and_then(|q| q.row)
374    }
375
376    // Typed setter - zero erasure
377    fn set_queen_row(s: &mut NQueensSolution, idx: usize, v: Option<i32>) {
378        if let Some(queen) = s.queens.get_mut(idx) {
379            queen.row = v;
380        }
381    }
382
383    fn create_test_director(
384        initialized: &[bool],
385    ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
386        let queens: Vec<_> = initialized
387            .iter()
388            .enumerate()
389            .map(|(i, init)| Queen {
390                row: if *init { Some(i as i32) } else { None },
391            })
392            .collect();
393
394        let solution = NQueensSolution {
395            queens,
396            score: None,
397        };
398
399        let extractor = Box::new(TypedEntityExtractor::new(
400            "Queen",
401            "queens",
402            get_queens,
403            get_queens_mut,
404        ));
405        let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
406            .with_extractor(extractor);
407
408        let descriptor =
409            SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
410                .with_entity(entity_desc);
411
412        SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
413    }
414
415    #[test]
416    fn test_queued_placer_all_uninitialized() {
417        let director = create_test_director(&[false, false, false]);
418
419        let entity_selector = FromSolutionEntitySelector::new(0);
420        let value_selector = StaticTypedValueSelector::new(vec![0i32, 1, 2]);
421
422        let placer = QueuedEntityPlacer::new(
423            entity_selector,
424            value_selector,
425            get_queen_row,
426            set_queen_row,
427            0,
428            "row",
429        );
430
431        let placements = placer.get_placements(&director);
432
433        // All 3 entities should have placements
434        assert_eq!(placements.len(), 3);
435
436        // Each should have 3 moves (one per value)
437        for p in &placements {
438            assert_eq!(p.moves.len(), 3);
439        }
440    }
441
442    #[test]
443    fn test_queued_placer_some_initialized() {
444        // First and third are initialized, middle is not
445        let director = create_test_director(&[true, false, true]);
446
447        let entity_selector = FromSolutionEntitySelector::new(0);
448        let value_selector = StaticTypedValueSelector::new(vec![0i32, 1, 2]);
449
450        let placer = QueuedEntityPlacer::new(
451            entity_selector,
452            value_selector,
453            get_queen_row,
454            set_queen_row,
455            0,
456            "row",
457        );
458
459        let placements = placer.get_placements(&director);
460
461        // Only 1 entity (index 1) should have a placement
462        assert_eq!(placements.len(), 1);
463        assert_eq!(placements[0].entity_ref.entity_index, 1);
464    }
465
466    #[test]
467    fn test_queued_placer_all_initialized() {
468        let director = create_test_director(&[true, true, true]);
469
470        let entity_selector = FromSolutionEntitySelector::new(0);
471        let value_selector = StaticTypedValueSelector::new(vec![0i32, 1, 2]);
472
473        let placer = QueuedEntityPlacer::new(
474            entity_selector,
475            value_selector,
476            get_queen_row,
477            set_queen_row,
478            0,
479            "row",
480        );
481
482        let placements = placer.get_placements(&director);
483
484        // No placements - all already initialized
485        assert_eq!(placements.len(), 0);
486    }
487
488    #[test]
489    fn test_sorted_entity_placer_descending() {
490        // Create 3 uninitialized queens
491        let director = create_test_director(&[false, false, false]);
492
493        let entity_selector = FromSolutionEntitySelector::new(0);
494        let value_selector = StaticTypedValueSelector::new(vec![0i32, 1, 2]);
495
496        let inner = QueuedEntityPlacer::new(
497            entity_selector,
498            value_selector,
499            get_queen_row,
500            set_queen_row,
501            0,
502            "row",
503        );
504
505        // Sort by entity index descending (2, 1, 0)
506        fn descending_index(_s: &NQueensSolution, a: usize, b: usize) -> std::cmp::Ordering {
507            b.cmp(&a)
508        }
509
510        let sorted = SortedEntityPlacer::new(inner, descending_index);
511        let placements = sorted.get_placements(&director);
512
513        assert_eq!(placements.len(), 3);
514        assert_eq!(placements[0].entity_ref.entity_index, 2);
515        assert_eq!(placements[1].entity_ref.entity_index, 1);
516        assert_eq!(placements[2].entity_ref.entity_index, 0);
517    }
518}