Skip to main content

solverforge_solver/heuristic/move/
dynamic_scalar_change.rs

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