Skip to main content

solverforge_solver/heuristic/move/
dynamic_scalar_swap.rs

1use std::fmt::Debug;
2
3use solverforge_core::domain::{DynamicScalarVariableSlot, PlanningSolution};
4use solverforge_scoring::Director;
5
6use crate::stats::CandidateTraceIdentity;
7
8use super::metadata::{
9    encode_option_debug, encode_usize, ordered_coordinate_pair, scoped_move_identity,
10    MoveTabuScope, TABU_OP_SWAP,
11};
12use super::{Move, MoveTabuSignature};
13
14/// Exchanges two values in one descriptor-resolved dynamic scalar variable.
15pub struct DynamicScalarSwapMove<S> {
16    slot: DynamicScalarVariableSlot<S>,
17    left_entity_index: usize,
18    right_entity_index: usize,
19    indices: [usize; 2],
20}
21
22impl<S> Clone for DynamicScalarSwapMove<S> {
23    fn clone(&self) -> Self {
24        Self {
25            slot: self.slot.clone(),
26            left_entity_index: self.left_entity_index,
27            right_entity_index: self.right_entity_index,
28            indices: self.indices,
29        }
30    }
31}
32
33impl<S> Debug for DynamicScalarSwapMove<S> {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.debug_struct("DynamicScalarSwapMove")
36            .field("left_entity_index", &self.left_entity_index)
37            .field("right_entity_index", &self.right_entity_index)
38            .field("descriptor_index", &self.slot.descriptor_index())
39            .field("variable", &self.slot.variable)
40            .field("variable_name", &self.slot.variable_name)
41            .finish()
42    }
43}
44
45impl<S> DynamicScalarSwapMove<S> {
46    pub fn new(
47        slot: DynamicScalarVariableSlot<S>,
48        left_entity_index: usize,
49        right_entity_index: usize,
50    ) -> Self {
51        Self {
52            slot,
53            left_entity_index,
54            right_entity_index,
55            indices: [left_entity_index, right_entity_index],
56        }
57    }
58
59    pub fn left_entity_index(&self) -> usize {
60        self.left_entity_index
61    }
62
63    pub fn right_entity_index(&self) -> usize {
64        self.right_entity_index
65    }
66}
67
68impl<S> Move<S> for DynamicScalarSwapMove<S>
69where
70    S: PlanningSolution,
71{
72    type Undo = (Option<usize>, Option<usize>);
73
74    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
75        if self.left_entity_index == self.right_entity_index {
76            return false;
77        }
78
79        let solution = score_director.working_solution();
80        let left_value = self.slot.current_value(solution, self.left_entity_index);
81        let right_value = self.slot.current_value(solution, self.right_entity_index);
82        left_value != right_value
83            && self
84                .slot
85                .value_is_legal(solution, self.left_entity_index, right_value)
86            && self
87                .slot
88                .value_is_legal(solution, self.right_entity_index, left_value)
89    }
90
91    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
92        let left_value = self
93            .slot
94            .current_value(score_director.working_solution(), self.left_entity_index);
95        let right_value = self
96            .slot
97            .current_value(score_director.working_solution(), self.right_entity_index);
98        let descriptor_index = self.slot.descriptor_index();
99
100        score_director.before_variable_changed(descriptor_index, self.left_entity_index);
101        score_director.before_variable_changed(descriptor_index, self.right_entity_index);
102        self.slot.set_value(
103            score_director.working_solution_mut(),
104            self.left_entity_index,
105            right_value,
106        );
107        self.slot.set_value(
108            score_director.working_solution_mut(),
109            self.right_entity_index,
110            left_value,
111        );
112        score_director.after_variable_changed(descriptor_index, self.left_entity_index);
113        score_director.after_variable_changed(descriptor_index, self.right_entity_index);
114
115        (left_value, right_value)
116    }
117
118    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
119        let descriptor_index = self.slot.descriptor_index();
120        score_director.before_variable_changed(descriptor_index, self.left_entity_index);
121        score_director.before_variable_changed(descriptor_index, self.right_entity_index);
122        self.slot.set_value(
123            score_director.working_solution_mut(),
124            self.left_entity_index,
125            undo.0,
126        );
127        self.slot.set_value(
128            score_director.working_solution_mut(),
129            self.right_entity_index,
130            undo.1,
131        );
132        score_director.after_variable_changed(descriptor_index, self.left_entity_index);
133        score_director.after_variable_changed(descriptor_index, self.right_entity_index);
134    }
135
136    fn descriptor_index(&self) -> usize {
137        self.slot.descriptor_index()
138    }
139
140    fn entity_indices(&self) -> &[usize] {
141        &self.indices
142    }
143
144    fn variable_name(&self) -> &str {
145        self.slot.variable_name
146    }
147
148    fn telemetry_label(&self) -> &'static str {
149        "dynamic_scalar_swap"
150    }
151
152    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
153        let solution = score_director.working_solution();
154        let left_value = self.slot.current_value(solution, self.left_entity_index);
155        let right_value = self.slot.current_value(solution, self.right_entity_index);
156        let left_id = encode_option_debug(left_value.as_ref());
157        let right_id = encode_option_debug(right_value.as_ref());
158        let left_entity_id = encode_usize(self.left_entity_index);
159        let right_entity_id = encode_usize(self.right_entity_index);
160        let scope = MoveTabuScope::new(self.slot.descriptor_index(), self.slot.variable_name);
161        let entity_pair = ordered_coordinate_pair((left_entity_id, 0), (right_entity_id, 0));
162        let move_id = scoped_move_identity(
163            scope,
164            TABU_OP_SWAP,
165            entity_pair.into_iter().map(|(entity_id, _)| entity_id),
166        );
167
168        MoveTabuSignature::new(scope, move_id.clone(), move_id)
169            .with_entity_tokens([
170                scope.entity_token(left_entity_id),
171                scope.entity_token(right_entity_id),
172            ])
173            .with_destination_value_tokens([
174                scope.value_token(right_id),
175                scope.value_token(left_id),
176            ])
177    }
178
179    fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
180        Some(CandidateTraceIdentity::logical_move(
181            self.slot.descriptor_index(),
182            self.slot.variable_name,
183            "scalar_swap",
184            [self.left_entity_index, self.right_entity_index],
185        ))
186    }
187}