Skip to main content

solverforge_solver/heuristic/move/
dynamic_scalar_swap.rs

1use 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
15/// Exchanges two values in one descriptor-resolved dynamic scalar variable.
16pub 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}