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