solverforge_solver/heuristic/move/
compound_scalar.rs1use std::fmt::{self, Debug};
2
3use smallvec::{smallvec, SmallVec};
4use solverforge_core::domain::PlanningSolution;
5use solverforge_scoring::Director;
6
7use super::metadata::{
8 encode_option_usize, encode_usize, hash_str, MoveTabuScope, MoveTabuSignature,
9};
10use super::{Move, MoveAffectedEntity};
11
12pub const COMPOUND_SCALAR_VARIABLE: &str = "compound_scalar";
13
14pub type ScalarEditLegality<S> = fn(&S, usize, usize, Option<usize>) -> bool;
15
16#[derive(Clone)]
17pub struct CompoundScalarEdit<S> {
18 pub descriptor_index: usize,
19 pub entity_index: usize,
20 pub variable_index: usize,
21 pub variable_name: &'static str,
22 pub to_value: Option<usize>,
23 pub getter: fn(&S, usize, usize) -> Option<usize>,
24 pub setter: fn(&mut S, usize, usize, Option<usize>),
25 pub value_is_legal: Option<ScalarEditLegality<S>>,
26}
27
28impl<S> Debug for CompoundScalarEdit<S> {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.debug_struct("CompoundScalarEdit")
31 .field("descriptor_index", &self.descriptor_index)
32 .field("entity_index", &self.entity_index)
33 .field("variable_index", &self.variable_index)
34 .field("variable_name", &self.variable_name)
35 .field("to_value", &self.to_value)
36 .finish()
37 }
38}
39
40#[derive(Clone)]
41pub struct CompoundScalarMove<S> {
42 reason: &'static str,
43 variable_label: &'static str,
44 edits: Vec<CompoundScalarEdit<S>>,
45 entity_indices: Vec<usize>,
46 require_hard_improvement: bool,
47 construction_value_order_key: Option<i64>,
48}
49
50impl<S> CompoundScalarMove<S> {
51 pub fn new(reason: &'static str, edits: Vec<CompoundScalarEdit<S>>) -> Self {
52 Self::with_label(reason, COMPOUND_SCALAR_VARIABLE, edits)
53 }
54
55 pub fn with_label(
56 reason: &'static str,
57 variable_label: &'static str,
58 edits: Vec<CompoundScalarEdit<S>>,
59 ) -> Self {
60 let mut entity_indices = edits
61 .iter()
62 .map(|edit| edit.entity_index)
63 .collect::<Vec<_>>();
64 entity_indices.sort_unstable();
65 entity_indices.dedup();
66 Self {
67 reason,
68 variable_label,
69 edits,
70 entity_indices,
71 require_hard_improvement: false,
72 construction_value_order_key: None,
73 }
74 }
75
76 pub fn with_require_hard_improvement(mut self, require_hard_improvement: bool) -> Self {
77 self.require_hard_improvement = require_hard_improvement;
78 self
79 }
80
81 pub(crate) fn with_construction_value_order_key(mut self, order_key: Option<i64>) -> Self {
82 self.construction_value_order_key = order_key;
83 self
84 }
85
86 pub(crate) fn construction_value_order_key(&self) -> Option<i64> {
87 self.construction_value_order_key
88 }
89
90 pub fn edits(&self) -> &[CompoundScalarEdit<S>] {
91 &self.edits
92 }
93
94 pub fn reason(&self) -> &'static str {
95 self.reason
96 }
97}
98
99impl<S> Debug for CompoundScalarMove<S> {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 f.debug_struct("CompoundScalarMove")
102 .field("reason", &self.reason)
103 .field("variable_label", &self.variable_label)
104 .field("edits", &self.edits)
105 .field("require_hard_improvement", &self.require_hard_improvement)
106 .field(
107 "construction_value_order_key",
108 &self.construction_value_order_key,
109 )
110 .finish()
111 }
112}
113
114impl<S> Move<S> for CompoundScalarMove<S>
115where
116 S: PlanningSolution,
117{
118 type Undo = Vec<Option<usize>>;
119
120 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
121 if self.edits.is_empty() {
122 return false;
123 }
124
125 let solution = score_director.working_solution();
126 let mut changes_value = false;
127 for edit in &self.edits {
128 if let Some(value_is_legal) = edit.value_is_legal {
129 if !value_is_legal(
130 solution,
131 edit.entity_index,
132 edit.variable_index,
133 edit.to_value,
134 ) {
135 return false;
136 }
137 }
138 let current = (edit.getter)(solution, edit.entity_index, edit.variable_index);
139 changes_value |= current != edit.to_value;
140 }
141
142 changes_value
143 }
144
145 fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
146 let mut undo = Vec::with_capacity(self.edits.len());
147 let affected = unique_affected_entities(&self.edits);
148 for edit in &self.edits {
149 let old_value = (edit.getter)(
150 score_director.working_solution(),
151 edit.entity_index,
152 edit.variable_index,
153 );
154 undo.push(old_value);
155 }
156 for (descriptor_index, entity_index) in &affected {
157 score_director.before_variable_changed(*descriptor_index, *entity_index);
158 }
159 for edit in &self.edits {
160 (edit.setter)(
161 score_director.working_solution_mut(),
162 edit.entity_index,
163 edit.variable_index,
164 edit.to_value,
165 );
166 }
167 for (descriptor_index, entity_index) in affected.iter().rev() {
168 score_director.after_variable_changed(*descriptor_index, *entity_index);
169 }
170 undo
171 }
172
173 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
174 let affected = unique_affected_entities(&self.edits);
175 for (descriptor_index, entity_index) in &affected {
176 score_director.before_variable_changed(*descriptor_index, *entity_index);
177 }
178 for (edit, old_value) in self.edits.iter().zip(undo) {
179 (edit.setter)(
180 score_director.working_solution_mut(),
181 edit.entity_index,
182 edit.variable_index,
183 old_value,
184 );
185 }
186 for (descriptor_index, entity_index) in affected.iter().rev() {
187 score_director.after_variable_changed(*descriptor_index, *entity_index);
188 }
189 }
190
191 fn descriptor_index(&self) -> usize {
192 self.edits
193 .first()
194 .map(|edit| edit.descriptor_index)
195 .unwrap_or(usize::MAX)
196 }
197
198 fn entity_indices(&self) -> &[usize] {
199 &self.entity_indices
200 }
201
202 fn variable_name(&self) -> &str {
203 self.variable_label
204 }
205
206 fn telemetry_label(&self) -> &'static str {
207 self.reason
208 }
209
210 fn requires_hard_improvement(&self) -> bool {
211 self.require_hard_improvement
212 }
213
214 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
215 let scope = MoveTabuScope::new(self.descriptor_index(), self.variable_label);
216 let mut move_id = smallvec![hash_str(self.reason)];
217 let mut undo_move_id = smallvec![hash_str(self.reason)];
218 let mut entity_tokens: SmallVec<[_; 8]> = SmallVec::new();
219 let mut destination_tokens: SmallVec<[_; 8]> = SmallVec::new();
220
221 for edit in &self.edits {
222 let current = (edit.getter)(
223 score_director.working_solution(),
224 edit.entity_index,
225 edit.variable_index,
226 );
227 let descriptor = encode_usize(edit.descriptor_index);
228 let entity = encode_usize(edit.entity_index);
229 let variable = hash_str(edit.variable_name);
230 let from = encode_option_usize(current);
231 let to = encode_option_usize(edit.to_value);
232 let edit_scope = MoveTabuScope::new(edit.descriptor_index, edit.variable_name);
233
234 move_id.extend([descriptor, entity, variable, from, to]);
235 undo_move_id.extend([descriptor, entity, variable, to, from]);
236 entity_tokens.push(edit_scope.entity_token(entity));
237 destination_tokens.push(edit_scope.value_token(to));
238 }
239
240 MoveTabuSignature::new(scope, move_id, undo_move_id)
241 .with_entity_tokens(entity_tokens)
242 .with_destination_value_tokens(destination_tokens)
243 }
244
245 fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
246 for edit in &self.edits {
247 visitor(MoveAffectedEntity {
248 descriptor_index: edit.descriptor_index,
249 entity_index: edit.entity_index,
250 variable_name: edit.variable_name,
251 });
252 }
253 }
254}
255
256fn unique_affected_entities<S>(edits: &[CompoundScalarEdit<S>]) -> Vec<(usize, usize)> {
257 let mut affected = Vec::new();
258 for edit in edits {
259 let entity = (edit.descriptor_index, edit.entity_index);
260 if !affected.contains(&entity) {
261 affected.push(entity);
262 }
263 }
264 affected
265}