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 concrete 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::{ConstraintMetadata, 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    ) -> (M1::Undo, M2::Undo) {
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        let first = m1.do_move(score_director);
92        let second = m2.do_move(score_director);
93        (first, second)
94    }
95}
96
97impl<S, M1, M2> Clone for CompositeMove<S, M1, M2>
98where
99    S: PlanningSolution,
100    M1: Move<S>,
101    M2: Move<S>,
102{
103    fn clone(&self) -> Self {
104        *self
105    }
106}
107
108impl<S, M1, M2> Copy for CompositeMove<S, M1, M2>
109where
110    S: PlanningSolution,
111    M1: Move<S>,
112    M2: Move<S>,
113{
114}
115
116impl<S, M1, M2> Debug for CompositeMove<S, M1, M2>
117where
118    S: PlanningSolution,
119    M1: Move<S>,
120    M2: Move<S>,
121{
122    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123        f.debug_struct("CompositeMove")
124            .field("index_1", &self.index_1)
125            .field("index_2", &self.index_2)
126            .finish()
127    }
128}
129
130pub(crate) struct SequentialPreviewDirector<'a, S: PlanningSolution> {
131    working_solution: S,
132    descriptor: &'a SolutionDescriptor,
133    constraint_metadata: Vec<ConstraintMetadata<'a>>,
134    entity_counts: Vec<Option<usize>>,
135    total_entity_count: Option<usize>,
136}
137
138impl<'a, S: PlanningSolution> SequentialPreviewDirector<'a, S> {
139    pub(crate) fn from_director<D: Director<S>>(score_director: &'a D) -> Self {
140        let descriptor = score_director.solution_descriptor();
141        let entity_counts = (0..descriptor.entity_descriptor_count())
142            .map(|descriptor_index| score_director.entity_count(descriptor_index))
143            .collect();
144
145        Self {
146            working_solution: score_director.clone_working_solution(),
147            descriptor,
148            constraint_metadata: score_director.constraint_metadata(),
149            entity_counts,
150            total_entity_count: score_director.total_entity_count(),
151        }
152    }
153}
154
155impl<S: PlanningSolution> Director<S> for SequentialPreviewDirector<'_, S> {
156    fn working_solution(&self) -> &S {
157        &self.working_solution
158    }
159
160    fn working_solution_mut(&mut self) -> &mut S {
161        &mut self.working_solution
162    }
163
164    fn calculate_score(&mut self) -> S::Score {
165        panic!("preview directors are only for selector generation")
166    }
167
168    fn solution_descriptor(&self) -> &SolutionDescriptor {
169        self.descriptor
170    }
171
172    fn clone_working_solution(&self) -> S {
173        self.working_solution.clone()
174    }
175
176    fn before_variable_changed(&mut self, _descriptor_index: usize, _entity_index: usize) {
177        self.working_solution.set_score(None);
178    }
179
180    fn after_variable_changed(&mut self, descriptor_index: usize, entity_index: usize) {
181        self.working_solution
182            .update_entity_shadows(descriptor_index, entity_index);
183        self.working_solution.set_score(None);
184    }
185
186    fn entity_count(&self, descriptor_index: usize) -> Option<usize> {
187        self.entity_counts.get(descriptor_index).copied().flatten()
188    }
189
190    fn total_entity_count(&self) -> Option<usize> {
191        self.total_entity_count
192    }
193
194    fn constraint_metadata(&self) -> Vec<ConstraintMetadata<'_>> {
195        self.constraint_metadata.to_vec()
196    }
197
198    fn is_incremental(&self) -> bool {
199        false
200    }
201
202    fn snapshot_score_state(&self) -> DirectorScoreState<S::Score> {
203        DirectorScoreState {
204            solution_score: self.working_solution.score(),
205            committed_score: self.working_solution.score(),
206            initialized: self.working_solution.score().is_some(),
207        }
208    }
209
210    fn restore_score_state(&mut self, state: DirectorScoreState<S::Score>) {
211        self.working_solution.set_score(state.solution_score);
212    }
213}
214
215/// A cached sequential composite that owns both child moves.
216///
217/// This keeps cartesian selector output valid even after the selector is
218/// reused or dropped.
219pub struct SequentialCompositeMove<S, M> {
220    moves: MoveArena<M>,
221    descriptor_index: usize,
222    entity_indices: SmallVec<[usize; 8]>,
223    variable_name: String,
224    tabu_signature: MoveTabuSignature,
225    require_hard_improvement: bool,
226    _phantom: PhantomData<fn() -> S>,
227}
228
229impl<S, M> SequentialCompositeMove<S, M>
230where
231    S: PlanningSolution,
232    M: Move<S>,
233{
234    pub fn new(
235        first: M,
236        second: M,
237        descriptor_index: usize,
238        entity_indices: SmallVec<[usize; 8]>,
239        variable_name: impl Into<String>,
240        tabu_signature: MoveTabuSignature,
241    ) -> Self {
242        let mut moves = MoveArena::with_capacity(2);
243        moves.push(first);
244        moves.push(second);
245
246        Self {
247            moves,
248            descriptor_index,
249            entity_indices,
250            variable_name: variable_name.into(),
251            tabu_signature,
252            require_hard_improvement: false,
253            _phantom: PhantomData,
254        }
255    }
256
257    pub fn with_require_hard_improvement(mut self, require_hard_improvement: bool) -> Self {
258        self.require_hard_improvement = require_hard_improvement;
259        self
260    }
261
262    fn first_move(&self) -> &M {
263        self.moves
264            .get(0)
265            .expect("sequential composite first move must remain valid")
266    }
267
268    fn second_move(&self) -> &M {
269        self.moves
270            .get(1)
271            .expect("sequential composite second move must remain valid")
272    }
273}
274
275pub struct SequentialCompositeMoveRef<'a, S, M>
276where
277    S: PlanningSolution,
278    M: Move<S>,
279{
280    first: &'a M,
281    second: &'a M,
282    descriptor_index: usize,
283    entity_indices: &'a [usize],
284    variable_name: &'a str,
285    tabu_signature: &'a MoveTabuSignature,
286    require_hard_improvement: bool,
287    _phantom: PhantomData<fn() -> S>,
288}
289
290impl<S, M> Debug for SequentialCompositeMoveRef<'_, S, M>
291where
292    S: PlanningSolution,
293    M: Move<S>,
294{
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        f.debug_struct("SequentialCompositeMoveRef")
297            .field("descriptor_index", &self.descriptor_index)
298            .field("variable_name", &self.variable_name)
299            .field("entity_indices", &self.entity_indices)
300            .finish()
301    }
302}
303
304impl<'a, S, M> SequentialCompositeMoveRef<'a, S, M>
305where
306    S: PlanningSolution,
307    M: Move<S>,
308{
309    pub fn new(
310        first: &'a M,
311        second: &'a M,
312        descriptor_index: usize,
313        entity_indices: &'a [usize],
314        variable_name: &'a str,
315        tabu_signature: &'a MoveTabuSignature,
316        require_hard_improvement: bool,
317    ) -> Self {
318        Self {
319            first,
320            second,
321            descriptor_index,
322            entity_indices,
323            variable_name,
324            tabu_signature,
325            require_hard_improvement,
326            _phantom: PhantomData,
327        }
328    }
329
330    pub fn first(&self) -> &'a M {
331        self.first
332    }
333
334    pub fn second(&self) -> &'a M {
335        self.second
336    }
337}
338
339impl<S, M> Clone for SequentialCompositeMoveRef<'_, S, M>
340where
341    S: PlanningSolution,
342    M: Move<S>,
343{
344    fn clone(&self) -> Self {
345        Self {
346            first: self.first,
347            second: self.second,
348            descriptor_index: self.descriptor_index,
349            entity_indices: self.entity_indices,
350            variable_name: self.variable_name,
351            tabu_signature: self.tabu_signature,
352            require_hard_improvement: self.require_hard_improvement,
353            _phantom: PhantomData,
354        }
355    }
356}
357
358impl<S, M> Move<S> for SequentialCompositeMoveRef<'_, S, M>
359where
360    S: PlanningSolution,
361    M: Move<S>,
362{
363    type Undo = (M::Undo, M::Undo);
364
365    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
366        if !self.first.is_doable(score_director) {
367            return false;
368        }
369
370        let mut preview = SequentialPreviewDirector::from_director(score_director);
371        let _ = self.first.do_move(&mut preview);
372        self.second.is_doable(&preview)
373    }
374
375    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
376        let first = self.first.do_move(score_director);
377        let second = self.second.do_move(score_director);
378        (first, second)
379    }
380
381    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
382        self.second.undo_move(score_director, undo.1);
383        self.first.undo_move(score_director, undo.0);
384    }
385
386    fn descriptor_index(&self) -> usize {
387        self.descriptor_index
388    }
389
390    fn entity_indices(&self) -> &[usize] {
391        self.entity_indices
392    }
393
394    fn variable_name(&self) -> &str {
395        self.variable_name
396    }
397
398    fn requires_hard_improvement(&self) -> bool {
399        self.require_hard_improvement
400            || self.first.requires_hard_improvement()
401            || self.second.requires_hard_improvement()
402    }
403
404    fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
405        self.tabu_signature.clone()
406    }
407}
408
409impl<S, M> Clone for SequentialCompositeMove<S, M>
410where
411    S: PlanningSolution,
412    M: Move<S> + Clone,
413{
414    fn clone(&self) -> Self {
415        Self::new(
416            self.first_move().clone(),
417            self.second_move().clone(),
418            self.descriptor_index,
419            self.entity_indices.clone(),
420            self.variable_name.clone(),
421            self.tabu_signature.clone(),
422        )
423        .with_require_hard_improvement(self.require_hard_improvement)
424    }
425}
426
427impl<S, M> Debug for SequentialCompositeMove<S, M>
428where
429    S: PlanningSolution,
430    M: Move<S>,
431{
432    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
433        f.debug_struct("SequentialCompositeMove")
434            .field("descriptor_index", &self.descriptor_index)
435            .field("variable_name", &self.variable_name)
436            .field("entity_indices", &self.entity_indices)
437            .finish()
438    }
439}
440
441impl<S, M> Move<S> for SequentialCompositeMove<S, M>
442where
443    S: PlanningSolution,
444    M: Move<S>,
445{
446    type Undo = (M::Undo, M::Undo);
447
448    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
449        let first = self.first_move();
450        if !first.is_doable(score_director) {
451            return false;
452        }
453
454        let mut preview = SequentialPreviewDirector::from_director(score_director);
455        let _ = first.do_move(&mut preview);
456        self.second_move().is_doable(&preview)
457    }
458
459    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
460        let first = self.first_move().do_move(score_director);
461        let second = self.second_move().do_move(score_director);
462        (first, second)
463    }
464
465    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
466        self.second_move().undo_move(score_director, undo.1);
467        self.first_move().undo_move(score_director, undo.0);
468    }
469
470    fn descriptor_index(&self) -> usize {
471        self.descriptor_index
472    }
473
474    fn entity_indices(&self) -> &[usize] {
475        &self.entity_indices
476    }
477
478    fn variable_name(&self) -> &str {
479        &self.variable_name
480    }
481
482    fn requires_hard_improvement(&self) -> bool {
483        self.require_hard_improvement
484            || self.first_move().requires_hard_improvement()
485            || self.second_move().requires_hard_improvement()
486    }
487
488    fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
489        self.tabu_signature.clone()
490    }
491}