Skip to main content

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}