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