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