solverforge_solver/heuristic/move/traits.rs
1// Move trait definition.
2
3use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_scoring::Director;
7
8use super::MoveTabuSignature;
9use crate::stats::CandidateTraceIdentity;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub struct MoveAffectedEntity<'a> {
13 pub descriptor_index: usize,
14 pub entity_index: usize,
15 pub variable_name: &'a str,
16}
17
18/// A move that modifies one or more planning variables.
19///
20/// Moves are fully monomorphized for maximum performance - no boxing, no virtual dispatch.
21/// Undo is handled by move-owned typed data, not boxed director callbacks.
22///
23/// # Type Parameters
24/// * `S` - The planning solution type
25///
26/// # Implementation Notes
27/// - Moves should be lightweight
28/// - Moves are NEVER cloned - ownership transfers via arena indices
29/// - Methods are generic over D to allow use with concrete directors
30pub trait Move<S: PlanningSolution>: Send + Sync + Debug {
31 type Undo: Send;
32
33 /* Returns true if this move can be executed in the current state.
34
35 A move is not doable if:
36 - The source value equals the destination value (no change)
37 - Required entities are pinned
38 - The move would violate hard constraints that can be detected early
39 */
40 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool;
41
42 /* Executes this move, modifying the working solution.
43
44 This method modifies the planning variables through the score director.
45 Returns concrete undo data that can be passed back to `undo_move`.
46 */
47 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo;
48
49 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo);
50
51 fn descriptor_index(&self) -> usize;
52
53 fn entity_indices(&self) -> &[usize];
54
55 fn variable_name(&self) -> &str;
56
57 fn telemetry_label(&self) -> &'static str {
58 "move"
59 }
60
61 fn requires_hard_improvement(&self) -> bool {
62 false
63 }
64
65 fn requires_score_improvement(&self) -> bool {
66 false
67 }
68
69 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature;
70
71 /// Returns the canonical identity used by opt-in candidate-pull tracing.
72 ///
73 /// Returns an explicit logical-coordinate identity for candidate tracing.
74 ///
75 /// This intentionally does *not* derive from tabu metadata: tabu keys may
76 /// contain representation-specific hashes. A move family that has not
77 /// supplied a cross-representation coordinate grammar returns `None`, and
78 /// the resulting trace is explicitly non-qualifying rather than faking a
79 /// comparable key.
80 fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
81 None
82 }
83
84 fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
85 let descriptor_index = self.descriptor_index();
86 let variable_name = self.variable_name();
87 for &entity_index in self.entity_indices() {
88 visitor(MoveAffectedEntity {
89 descriptor_index,
90 entity_index,
91 variable_name,
92 });
93 }
94 }
95}