1use std::fmt::Debug;
12use std::marker::PhantomData;
13
14use smallvec::SmallVec;
15use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
16use solverforge_core::ConstraintRef;
17use solverforge_scoring::{ConstraintMetadata, Director, DirectorScoreState};
18
19use crate::stats::CandidateTraceIdentity;
20
21use super::{Move, MoveArena, MoveTabuSignature};
22
23pub struct CompositeMove<S, M1, M2>
33where
34 S: PlanningSolution,
35 M1: Move<S>,
36 M2: Move<S>,
37{
38 index_1: usize,
39 index_2: usize,
40 _phantom: PhantomData<(fn() -> S, fn() -> M1, fn() -> M2)>,
41}
42
43impl<S, M1, M2> CompositeMove<S, M1, M2>
44where
45 S: PlanningSolution,
46 M1: Move<S>,
47 M2: Move<S>,
48{
49 pub fn new(index_1: usize, index_2: usize) -> Self {
50 Self {
51 index_1,
52 index_2,
53 _phantom: PhantomData,
54 }
55 }
56
57 pub fn index_1(&self) -> usize {
58 self.index_1
59 }
60
61 pub fn index_2(&self) -> usize {
62 self.index_2
63 }
64
65 pub fn is_doable_with_arenas<D: Director<S>>(
66 &self,
67 arena_1: &MoveArena<M1>,
68 arena_2: &MoveArena<M2>,
69 score_director: &D,
70 ) -> bool {
71 let m1 = arena_1.get(self.index_1);
72 let m2 = arena_2.get(self.index_2);
73
74 match (m1, m2) {
75 (Some(m1), Some(m2)) => m1.is_doable(score_director) && m2.is_doable(score_director),
76 _ => false,
77 }
78 }
79
80 pub fn do_move_with_arenas<D: Director<S>>(
82 &self,
83 arena_1: &MoveArena<M1>,
84 arena_2: &MoveArena<M2>,
85 score_director: &mut D,
86 ) -> (M1::Undo, M2::Undo) {
87 let m1 = arena_1
88 .get(self.index_1)
89 .expect("composite move first arena index must remain valid");
90 let m2 = arena_2
91 .get(self.index_2)
92 .expect("composite move second arena index must remain valid");
93
94 let first = m1.do_move(score_director);
95 let second = m2.do_move(score_director);
96 (first, second)
97 }
98}
99
100impl<S, M1, M2> Clone for CompositeMove<S, M1, M2>
101where
102 S: PlanningSolution,
103 M1: Move<S>,
104 M2: Move<S>,
105{
106 fn clone(&self) -> Self {
107 *self
108 }
109}
110
111impl<S, M1, M2> Copy for CompositeMove<S, M1, M2>
112where
113 S: PlanningSolution,
114 M1: Move<S>,
115 M2: Move<S>,
116{
117}
118
119impl<S, M1, M2> Debug for CompositeMove<S, M1, M2>
120where
121 S: PlanningSolution,
122 M1: Move<S>,
123 M2: Move<S>,
124{
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 f.debug_struct("CompositeMove")
127 .field("index_1", &self.index_1)
128 .field("index_2", &self.index_2)
129 .finish()
130 }
131}
132
133pub(crate) struct SequentialPreviewDirector<S: PlanningSolution> {
134 working_solution: S,
135 descriptor: SolutionDescriptor,
136 constraint_metadata: Vec<(ConstraintRef, bool)>,
137 entity_counts: Vec<Option<usize>>,
138 total_entity_count: Option<usize>,
139}
140
141impl<S: PlanningSolution> SequentialPreviewDirector<S> {
142 pub(crate) fn from_director<D: Director<S>>(score_director: &D) -> Self {
143 let descriptor = score_director.solution_descriptor();
144 let entity_counts = (0..descriptor.entity_descriptor_count())
145 .map(|descriptor_index| score_director.entity_count(descriptor_index))
146 .collect();
147
148 Self {
149 working_solution: score_director.clone_working_solution(),
150 descriptor: descriptor.clone(),
151 constraint_metadata: score_director
152 .constraint_metadata()
153 .into_iter()
154 .map(|metadata| (metadata.constraint_ref.clone(), metadata.is_hard))
155 .collect(),
156 entity_counts,
157 total_entity_count: score_director.total_entity_count(),
158 }
159 }
160}
161
162impl<S: PlanningSolution> Director<S> for SequentialPreviewDirector<S> {
163 fn working_solution(&self) -> &S {
164 &self.working_solution
165 }
166
167 fn working_solution_mut(&mut self) -> &mut S {
168 &mut self.working_solution
169 }
170
171 fn calculate_score(&mut self) -> S::Score {
172 panic!("preview directors are only for selector generation")
173 }
174
175 fn solution_descriptor(&self) -> &SolutionDescriptor {
176 &self.descriptor
177 }
178
179 fn clone_working_solution(&self) -> S {
180 self.working_solution.clone()
181 }
182
183 fn before_variable_changed(&mut self, _descriptor_index: usize, _entity_index: usize) {
184 self.working_solution.set_score(None);
185 }
186
187 fn after_variable_changed(&mut self, descriptor_index: usize, entity_index: usize) {
188 self.working_solution
189 .update_entity_shadows(descriptor_index, entity_index);
190 self.working_solution.set_score(None);
191 }
192
193 fn entity_count(&self, descriptor_index: usize) -> Option<usize> {
194 self.entity_counts.get(descriptor_index).copied().flatten()
195 }
196
197 fn total_entity_count(&self) -> Option<usize> {
198 self.total_entity_count
199 }
200
201 fn constraint_metadata(&self) -> Vec<ConstraintMetadata<'_>> {
202 self.constraint_metadata
203 .iter()
204 .map(|(constraint_ref, is_hard)| ConstraintMetadata::new(constraint_ref, *is_hard))
205 .collect()
206 }
207
208 fn is_incremental(&self) -> bool {
209 false
210 }
211
212 fn snapshot_score_state(&self) -> DirectorScoreState<S::Score> {
213 DirectorScoreState {
214 solution_score: self.working_solution.score(),
215 committed_score: self.working_solution.score(),
216 initialized: self.working_solution.score().is_some(),
217 }
218 }
219
220 fn restore_score_state(&mut self, state: DirectorScoreState<S::Score>) {
221 self.working_solution.set_score(state.solution_score);
222 }
223}
224
225pub struct SequentialCompositeMove<S, M> {
230 moves: MoveArena<M>,
231 descriptor_index: usize,
232 entity_indices: SmallVec<[usize; 8]>,
233 variable_name: String,
234 tabu_signature: MoveTabuSignature,
235 require_hard_improvement: bool,
236 _phantom: PhantomData<fn() -> S>,
237}
238
239impl<S, M> SequentialCompositeMove<S, M>
240where
241 S: PlanningSolution,
242 M: Move<S>,
243{
244 pub fn new(
245 first: M,
246 second: M,
247 descriptor_index: usize,
248 entity_indices: SmallVec<[usize; 8]>,
249 variable_name: impl Into<String>,
250 tabu_signature: MoveTabuSignature,
251 ) -> Self {
252 let mut moves = MoveArena::with_capacity(2);
253 moves.push(first);
254 moves.push(second);
255
256 Self {
257 moves,
258 descriptor_index,
259 entity_indices,
260 variable_name: variable_name.into(),
261 tabu_signature,
262 require_hard_improvement: false,
263 _phantom: PhantomData,
264 }
265 }
266
267 pub fn with_require_hard_improvement(mut self, require_hard_improvement: bool) -> Self {
268 self.require_hard_improvement = require_hard_improvement;
269 self
270 }
271
272 fn first_move(&self) -> &M {
273 self.moves
274 .get(0)
275 .expect("sequential composite first move must remain valid")
276 }
277
278 fn second_move(&self) -> &M {
279 self.moves
280 .get(1)
281 .expect("sequential composite second move must remain valid")
282 }
283}
284
285pub struct SequentialCompositeMoveRef<'a, S, M>
286where
287 S: PlanningSolution,
288 M: Move<S>,
289{
290 first: &'a M,
291 second: &'a M,
292 descriptor_index: usize,
293 entity_indices: &'a [usize],
294 variable_name: &'a str,
295 tabu_signature: &'a MoveTabuSignature,
296 require_hard_improvement: bool,
297 _phantom: PhantomData<fn() -> S>,
298}
299
300impl<S, M> Debug for SequentialCompositeMoveRef<'_, S, M>
301where
302 S: PlanningSolution,
303 M: Move<S>,
304{
305 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306 f.debug_struct("SequentialCompositeMoveRef")
307 .field("descriptor_index", &self.descriptor_index)
308 .field("variable_name", &self.variable_name)
309 .field("entity_indices", &self.entity_indices)
310 .finish()
311 }
312}
313
314impl<'a, S, M> SequentialCompositeMoveRef<'a, S, M>
315where
316 S: PlanningSolution,
317 M: Move<S>,
318{
319 pub fn new(
320 first: &'a M,
321 second: &'a M,
322 descriptor_index: usize,
323 entity_indices: &'a [usize],
324 variable_name: &'a str,
325 tabu_signature: &'a MoveTabuSignature,
326 require_hard_improvement: bool,
327 ) -> Self {
328 Self {
329 first,
330 second,
331 descriptor_index,
332 entity_indices,
333 variable_name,
334 tabu_signature,
335 require_hard_improvement,
336 _phantom: PhantomData,
337 }
338 }
339
340 pub fn first(&self) -> &'a M {
341 self.first
342 }
343
344 pub fn second(&self) -> &'a M {
345 self.second
346 }
347}
348
349impl<S, M> Clone for SequentialCompositeMoveRef<'_, S, M>
350where
351 S: PlanningSolution,
352 M: Move<S>,
353{
354 fn clone(&self) -> Self {
355 Self {
356 first: self.first,
357 second: self.second,
358 descriptor_index: self.descriptor_index,
359 entity_indices: self.entity_indices,
360 variable_name: self.variable_name,
361 tabu_signature: self.tabu_signature,
362 require_hard_improvement: self.require_hard_improvement,
363 _phantom: PhantomData,
364 }
365 }
366}
367
368impl<S, M> Move<S> for SequentialCompositeMoveRef<'_, S, M>
369where
370 S: PlanningSolution,
371 M: Move<S>,
372{
373 type Undo = (M::Undo, M::Undo);
374
375 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
376 if !self.first.is_doable(score_director) {
377 return false;
378 }
379
380 let mut preview = SequentialPreviewDirector::from_director(score_director);
381 let _ = self.first.do_move(&mut preview);
382 self.second.is_doable(&preview)
383 }
384
385 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
386 let first = self.first.do_move(score_director);
387 let second = self.second.do_move(score_director);
388 (first, second)
389 }
390
391 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
392 self.second.undo_move(score_director, undo.1);
393 self.first.undo_move(score_director, undo.0);
394 }
395
396 fn descriptor_index(&self) -> usize {
397 self.descriptor_index
398 }
399
400 fn entity_indices(&self) -> &[usize] {
401 self.entity_indices
402 }
403
404 fn variable_name(&self) -> &str {
405 self.variable_name
406 }
407
408 fn requires_hard_improvement(&self) -> bool {
409 self.require_hard_improvement
410 || self.first.requires_hard_improvement()
411 || self.second.requires_hard_improvement()
412 }
413
414 fn requires_score_improvement(&self) -> bool {
415 self.first.requires_score_improvement() || self.second.requires_score_improvement()
416 }
417
418 fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
419 self.tabu_signature.clone()
420 }
421
422 fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
423 Some(CandidateTraceIdentity::composite(
424 "sequential_composite",
425 [
426 self.first.candidate_trace_identity()?,
427 self.second.candidate_trace_identity()?,
428 ],
429 ))
430 }
431}
432
433impl<S, M> Clone for SequentialCompositeMove<S, M>
434where
435 S: PlanningSolution,
436 M: Move<S> + Clone,
437{
438 fn clone(&self) -> Self {
439 Self::new(
440 self.first_move().clone(),
441 self.second_move().clone(),
442 self.descriptor_index,
443 self.entity_indices.clone(),
444 self.variable_name.clone(),
445 self.tabu_signature.clone(),
446 )
447 .with_require_hard_improvement(self.require_hard_improvement)
448 }
449}
450
451impl<S, M> Debug for SequentialCompositeMove<S, M>
452where
453 S: PlanningSolution,
454 M: Move<S>,
455{
456 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
457 f.debug_struct("SequentialCompositeMove")
458 .field("descriptor_index", &self.descriptor_index)
459 .field("variable_name", &self.variable_name)
460 .field("entity_indices", &self.entity_indices)
461 .finish()
462 }
463}
464
465impl<S, M> Move<S> for SequentialCompositeMove<S, M>
466where
467 S: PlanningSolution,
468 M: Move<S>,
469{
470 type Undo = (M::Undo, M::Undo);
471
472 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
473 let first = self.first_move();
474 if !first.is_doable(score_director) {
475 return false;
476 }
477
478 let mut preview = SequentialPreviewDirector::from_director(score_director);
479 let _ = first.do_move(&mut preview);
480 self.second_move().is_doable(&preview)
481 }
482
483 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
484 let first = self.first_move().do_move(score_director);
485 let second = self.second_move().do_move(score_director);
486 (first, second)
487 }
488
489 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
490 self.second_move().undo_move(score_director, undo.1);
491 self.first_move().undo_move(score_director, undo.0);
492 }
493
494 fn descriptor_index(&self) -> usize {
495 self.descriptor_index
496 }
497
498 fn entity_indices(&self) -> &[usize] {
499 &self.entity_indices
500 }
501
502 fn variable_name(&self) -> &str {
503 &self.variable_name
504 }
505
506 fn requires_hard_improvement(&self) -> bool {
507 self.require_hard_improvement
508 || self.first_move().requires_hard_improvement()
509 || self.second_move().requires_hard_improvement()
510 }
511
512 fn requires_score_improvement(&self) -> bool {
513 self.first_move().requires_score_improvement()
514 || self.second_move().requires_score_improvement()
515 }
516
517 fn tabu_signature<D: Director<S>>(&self, _score_director: &D) -> MoveTabuSignature {
518 self.tabu_signature.clone()
519 }
520
521 fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
522 Some(CandidateTraceIdentity::composite(
523 "sequential_composite",
524 [
525 self.first_move().candidate_trace_identity()?,
526 self.second_move().candidate_trace_identity()?,
527 ],
528 ))
529 }
530}