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