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> 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}