Skip to main content

solverforge_solver/heuristic/move/
composite.rs

1/* CompositeMove - applies two moves in sequence by arena indices.
2
3This move stores indices into two arenas. The moves themselves
4live in their respective arenas - CompositeMove just references them.
5
6# Zero-Erasure Design
7
8No cloning, no boxing - just typed arena indices.
9*/
10
11use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use smallvec::SmallVec;
15use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
16use solverforge_scoring::{Director, DirectorScoreState};
17
18use super::{Move, MoveArena, MoveTabuSignature};
19
20/// A move that applies two moves in sequence via arena indices.
21///
22/// The moves live in separate arenas. CompositeMove stores the indices
23/// and arena references needed to execute both moves.
24///
25/// # Type Parameters
26/// * `S` - The planning solution type
27/// * `M1` - The first move type
28/// * `M2` - The second move type
29pub struct CompositeMove<S, M1, M2>
30where
31    S: PlanningSolution,
32    M1: Move<S>,
33    M2: Move<S>,
34{
35    index_1: usize,
36    index_2: usize,
37    _phantom: PhantomData<(fn() -> S, fn() -> M1, fn() -> M2)>,
38}
39
40impl<S, M1, M2> CompositeMove<S, M1, M2>
41where
42    S: PlanningSolution,
43    M1: Move<S>,
44    M2: Move<S>,
45{
46    pub fn new(index_1: usize, index_2: usize) -> Self {
47        Self {
48            index_1,
49            index_2,
50            _phantom: PhantomData,
51        }
52    }
53
54    pub fn index_1(&self) -> usize {
55        self.index_1
56    }
57
58    pub fn index_2(&self) -> usize {
59        self.index_2
60    }
61
62    pub fn is_doable_with_arenas<D: Director<S>>(
63        &self,
64        arena_1: &MoveArena<M1>,
65        arena_2: &MoveArena<M2>,
66        score_director: &D,
67    ) -> bool {
68        let m1 = arena_1.get(self.index_1);
69        let m2 = arena_2.get(self.index_2);
70
71        match (m1, m2) {
72            (Some(m1), Some(m2)) => m1.is_doable(score_director) && m2.is_doable(score_director),
73            _ => false,
74        }
75    }
76
77    /// Executes both moves using the arenas.
78    pub fn do_move_with_arenas<D: Director<S>>(
79        &self,
80        arena_1: &MoveArena<M1>,
81        arena_2: &MoveArena<M2>,
82        score_director: &mut D,
83    ) {
84        let m1 = arena_1
85            .get(self.index_1)
86            .expect("composite move first arena index must remain valid");
87        let m2 = arena_2
88            .get(self.index_2)
89            .expect("composite move second arena index must remain valid");
90
91        m1.do_move(score_director);
92        m2.do_move(score_director);
93    }
94}
95
96impl<S, M1, M2> Clone for CompositeMove<S, M1, M2>
97where
98    S: PlanningSolution,
99    M1: Move<S>,
100    M2: Move<S>,
101{
102    fn clone(&self) -> Self {
103        *self
104    }
105}
106
107impl<S, M1, M2> Copy for CompositeMove<S, M1, M2>
108where
109    S: PlanningSolution,
110    M1: Move<S>,
111    M2: Move<S>,
112{
113}
114
115impl<S, M1, M2> Debug for CompositeMove<S, M1, M2>
116where
117    S: PlanningSolution,
118    M1: Move<S>,
119    M2: Move<S>,
120{
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.debug_struct("CompositeMove")
123            .field("index_1", &self.index_1)
124            .field("index_2", &self.index_2)
125            .finish()
126    }
127}
128
129pub(crate) struct SequentialPreviewDirector<'a, S: PlanningSolution> {
130    working_solution: S,
131    descriptor: &'a SolutionDescriptor,
132    entity_counts: Vec<Option<usize>>,
133    total_entity_count: Option<usize>,
134}
135
136impl<'a, S: PlanningSolution> SequentialPreviewDirector<'a, S> {
137    pub(crate) fn from_director<D: Director<S>>(score_director: &'a D) -> Self {
138        let descriptor = score_director.solution_descriptor();
139        let entity_counts = (0..descriptor.entity_descriptor_count())
140            .map(|descriptor_index| score_director.entity_count(descriptor_index))
141            .collect();
142
143        Self {
144            working_solution: score_director.clone_working_solution(),
145            descriptor,
146            entity_counts,
147            total_entity_count: score_director.total_entity_count(),
148        }
149    }
150}
151
152impl<S: PlanningSolution> Director<S> for SequentialPreviewDirector<'_, S> {
153    fn working_solution(&self) -> &S {
154        &self.working_solution
155    }
156
157    fn working_solution_mut(&mut self) -> &mut S {
158        &mut self.working_solution
159    }
160
161    fn calculate_score(&mut self) -> S::Score {
162        panic!("preview directors are only for selector generation")
163    }
164
165    fn solution_descriptor(&self) -> &SolutionDescriptor {
166        self.descriptor
167    }
168
169    fn clone_working_solution(&self) -> S {
170        self.working_solution.clone()
171    }
172
173    fn before_variable_changed(&mut self, _descriptor_index: usize, _entity_index: usize) {
174        self.working_solution.set_score(None);
175    }
176
177    fn after_variable_changed(&mut self, descriptor_index: usize, entity_index: usize) {
178        self.working_solution
179            .update_entity_shadows(descriptor_index, entity_index);
180        self.working_solution.set_score(None);
181    }
182
183    fn entity_count(&self, descriptor_index: usize) -> Option<usize> {
184        self.entity_counts.get(descriptor_index).copied().flatten()
185    }
186
187    fn total_entity_count(&self) -> Option<usize> {
188        self.total_entity_count
189    }
190
191    fn is_incremental(&self) -> bool {
192        false
193    }
194
195    fn snapshot_score_state(&self) -> DirectorScoreState<S::Score> {
196        DirectorScoreState {
197            solution_score: self.working_solution.score(),
198            committed_score: self.working_solution.score(),
199            initialized: self.working_solution.score().is_some(),
200        }
201    }
202
203    fn restore_score_state(&mut self, state: DirectorScoreState<S::Score>) {
204        self.working_solution.set_score(state.solution_score);
205    }
206
207    fn register_undo(&mut self, _undo: Box<dyn FnOnce(&mut S) + Send>) {}
208}
209
210/// A cached sequential composite that owns both child moves.
211///
212/// This keeps cartesian selector output valid even after the selector is
213/// reused or dropped.
214pub struct SequentialCompositeMove<S, M> {
215    moves: MoveArena<M>,
216    descriptor_index: usize,
217    entity_indices: SmallVec<[usize; 8]>,
218    variable_name: String,
219    tabu_signature: MoveTabuSignature,
220    _phantom: PhantomData<fn() -> S>,
221}
222
223impl<S, M> SequentialCompositeMove<S, M>
224where
225    S: PlanningSolution,
226    M: Move<S>,
227{
228    pub fn new(
229        first: M,
230        second: M,
231        descriptor_index: usize,
232        entity_indices: SmallVec<[usize; 8]>,
233        variable_name: impl Into<String>,
234        tabu_signature: MoveTabuSignature,
235    ) -> Self {
236        let mut moves = MoveArena::with_capacity(2);
237        moves.push(first);
238        moves.push(second);
239
240        Self {
241            moves,
242            descriptor_index,
243            entity_indices,
244            variable_name: variable_name.into(),
245            tabu_signature,
246            _phantom: PhantomData,
247        }
248    }
249
250    fn first_move(&self) -> &M {
251        self.moves
252            .get(0)
253            .expect("sequential composite first move must remain valid")
254    }
255
256    fn second_move(&self) -> &M {
257        self.moves
258            .get(1)
259            .expect("sequential composite second move must remain valid")
260    }
261}
262
263pub struct SequentialCompositeMoveRef<'a, S, M>
264where
265    S: PlanningSolution,
266    M: Move<S>,
267{
268    first: &'a M,
269    second: &'a M,
270    descriptor_index: usize,
271    entity_indices: &'a [usize],
272    variable_name: &'a str,
273    tabu_signature: &'a MoveTabuSignature,
274    _phantom: PhantomData<fn() -> S>,
275}
276
277impl<S, M> Debug for SequentialCompositeMoveRef<'_, S, M>
278where
279    S: PlanningSolution,
280    M: Move<S>,
281{
282    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283        f.debug_struct("SequentialCompositeMoveRef")
284            .field("descriptor_index", &self.descriptor_index)
285            .field("variable_name", &self.variable_name)
286            .field("entity_indices", &self.entity_indices)
287            .finish()
288    }
289}
290
291impl<'a, S, M> SequentialCompositeMoveRef<'a, S, M>
292where
293    S: PlanningSolution,
294    M: Move<S>,
295{
296    pub fn new(
297        first: &'a M,
298        second: &'a M,
299        descriptor_index: usize,
300        entity_indices: &'a [usize],
301        variable_name: &'a str,
302        tabu_signature: &'a MoveTabuSignature,
303    ) -> Self {
304        Self {
305            first,
306            second,
307            descriptor_index,
308            entity_indices,
309            variable_name,
310            tabu_signature,
311            _phantom: PhantomData,
312        }
313    }
314
315    pub fn first(&self) -> &'a M {
316        self.first
317    }
318
319    pub fn second(&self) -> &'a M {
320        self.second
321    }
322}
323
324impl<S, M> Clone for SequentialCompositeMoveRef<'_, S, M>
325where
326    S: PlanningSolution,
327    M: Move<S>,
328{
329    fn clone(&self) -> Self {
330        Self {
331            first: self.first,
332            second: self.second,
333            descriptor_index: self.descriptor_index,
334            entity_indices: self.entity_indices,
335            variable_name: self.variable_name,
336            tabu_signature: self.tabu_signature,
337            _phantom: PhantomData,
338        }
339    }
340}
341
342impl<S, M> Move<S> for SequentialCompositeMoveRef<'_, S, M>
343where
344    S: PlanningSolution,
345    M: Move<S>,
346{
347    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
348        if !self.first.is_doable(score_director) {
349            return false;
350        }
351
352        let mut preview = SequentialPreviewDirector::from_director(score_director);
353        self.first.do_move(&mut preview);
354        self.second.is_doable(&preview)
355    }
356
357    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
358        self.first.do_move(score_director);
359        self.second.do_move(score_director);
360    }
361
362    fn descriptor_index(&self) -> usize {
363        self.descriptor_index
364    }
365
366    fn entity_indices(&self) -> &[usize] {
367        self.entity_indices
368    }
369
370    fn variable_name(&self) -> &str {
371        self.variable_name
372    }
373
374    fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
375        self.tabu_signature.clone()
376    }
377}
378
379impl<S, M> Clone for SequentialCompositeMove<S, M>
380where
381    S: PlanningSolution,
382    M: Move<S> + Clone,
383{
384    fn clone(&self) -> Self {
385        Self::new(
386            self.first_move().clone(),
387            self.second_move().clone(),
388            self.descriptor_index,
389            self.entity_indices.clone(),
390            self.variable_name.clone(),
391            self.tabu_signature.clone(),
392        )
393    }
394}
395
396impl<S, M> Debug for SequentialCompositeMove<S, M>
397where
398    S: PlanningSolution,
399    M: Move<S>,
400{
401    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
402        f.debug_struct("SequentialCompositeMove")
403            .field("descriptor_index", &self.descriptor_index)
404            .field("variable_name", &self.variable_name)
405            .field("entity_indices", &self.entity_indices)
406            .finish()
407    }
408}
409
410impl<S, M> Move<S> for SequentialCompositeMove<S, M>
411where
412    S: PlanningSolution,
413    M: Move<S>,
414{
415    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
416        let first = self.first_move();
417        if !first.is_doable(score_director) {
418            return false;
419        }
420
421        let mut preview = SequentialPreviewDirector::from_director(score_director);
422        first.do_move(&mut preview);
423        self.second_move().is_doable(&preview)
424    }
425
426    fn do_move<D: Director<S>>(&self, score_director: &mut D) {
427        self.first_move().do_move(score_director);
428        self.second_move().do_move(score_director);
429    }
430
431    fn descriptor_index(&self) -> usize {
432        self.descriptor_index
433    }
434
435    fn entity_indices(&self) -> &[usize] {
436        &self.entity_indices
437    }
438
439    fn variable_name(&self) -> &str {
440        &self.variable_name
441    }
442
443    fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
444        self.tabu_signature.clone()
445    }
446}