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 for edit in &self.edits {
148 let old_value = (edit.getter)(
149 score_director.working_solution(),
150 edit.entity_index,
151 edit.variable_index,
152 );
153 undo.push(old_value);
154 score_director.before_variable_changed(edit.descriptor_index, edit.entity_index);
155 (edit.setter)(
156 score_director.working_solution_mut(),
157 edit.entity_index,
158 edit.variable_index,
159 edit.to_value,
160 );
161 score_director.after_variable_changed(edit.descriptor_index, edit.entity_index);
162 }
163 undo
164 }
165
166 fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
167 for edit in &self.edits {
168 score_director.before_variable_changed(edit.descriptor_index, edit.entity_index);
169 }
170 for (edit, old_value) in self.edits.iter().zip(undo) {
171 (edit.setter)(
172 score_director.working_solution_mut(),
173 edit.entity_index,
174 edit.variable_index,
175 old_value,
176 );
177 }
178 for edit in self.edits.iter().rev() {
179 score_director.after_variable_changed(edit.descriptor_index, edit.entity_index);
180 }
181 }
182
183 fn descriptor_index(&self) -> usize {
184 self.edits
185 .first()
186 .map(|edit| edit.descriptor_index)
187 .unwrap_or(usize::MAX)
188 }
189
190 fn entity_indices(&self) -> &[usize] {
191 &self.entity_indices
192 }
193
194 fn variable_name(&self) -> &str {
195 self.variable_label
196 }
197
198 fn requires_hard_improvement(&self) -> bool {
199 self.require_hard_improvement
200 }
201
202 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
203 let scope = MoveTabuScope::new(self.descriptor_index(), self.variable_label);
204 let mut move_id = smallvec![hash_str(self.reason)];
205 let mut undo_move_id = smallvec![hash_str(self.reason)];
206 let mut entity_tokens: SmallVec<[_; 8]> = SmallVec::new();
207 let mut destination_tokens: SmallVec<[_; 8]> = SmallVec::new();
208
209 for edit in &self.edits {
210 let current = (edit.getter)(
211 score_director.working_solution(),
212 edit.entity_index,
213 edit.variable_index,
214 );
215 let descriptor = encode_usize(edit.descriptor_index);
216 let entity = encode_usize(edit.entity_index);
217 let variable = hash_str(edit.variable_name);
218 let from = encode_option_usize(current);
219 let to = encode_option_usize(edit.to_value);
220 let edit_scope = MoveTabuScope::new(edit.descriptor_index, edit.variable_name);
221
222 move_id.extend([descriptor, entity, variable, from, to]);
223 undo_move_id.extend([descriptor, entity, variable, to, from]);
224 entity_tokens.push(edit_scope.entity_token(entity));
225 destination_tokens.push(edit_scope.value_token(to));
226 }
227
228 MoveTabuSignature::new(scope, move_id, undo_move_id)
229 .with_entity_tokens(entity_tokens)
230 .with_destination_value_tokens(destination_tokens)
231 }
232
233 fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
234 for edit in &self.edits {
235 visitor(MoveAffectedEntity {
236 descriptor_index: edit.descriptor_index,
237 entity_index: edit.entity_index,
238 variable_name: edit.variable_name,
239 });
240 }
241 }
242}