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