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}
48
49impl<S> CompoundScalarMove<S> {
50 pub fn new(reason: &'static str, edits: Vec<CompoundScalarEdit<S>>) -> Self {
51 Self::with_label(reason, COMPOUND_SCALAR_VARIABLE, edits)
52 }
53
54 pub fn with_label(
55 reason: &'static str,
56 variable_label: &'static str,
57 edits: Vec<CompoundScalarEdit<S>>,
58 ) -> Self {
59 let mut entity_indices = edits
60 .iter()
61 .map(|edit| edit.entity_index)
62 .collect::<Vec<_>>();
63 entity_indices.sort_unstable();
64 entity_indices.dedup();
65 Self {
66 reason,
67 variable_label,
68 edits,
69 entity_indices,
70 require_hard_improvement: false,
71 }
72 }
73
74 pub fn with_require_hard_improvement(mut self, require_hard_improvement: bool) -> Self {
75 self.require_hard_improvement = require_hard_improvement;
76 self
77 }
78
79 pub fn edits(&self) -> &[CompoundScalarEdit<S>] {
80 &self.edits
81 }
82
83 pub fn reason(&self) -> &'static str {
84 self.reason
85 }
86}
87
88impl<S> Debug for CompoundScalarMove<S> {
89 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90 f.debug_struct("CompoundScalarMove")
91 .field("reason", &self.reason)
92 .field("variable_label", &self.variable_label)
93 .field("edits", &self.edits)
94 .field("require_hard_improvement", &self.require_hard_improvement)
95 .finish()
96 }
97}
98
99impl<S> Move<S> for CompoundScalarMove<S>
100where
101 S: PlanningSolution,
102{
103 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
104 if self.edits.is_empty() {
105 return false;
106 }
107
108 let solution = score_director.working_solution();
109 let mut changes_value = false;
110 for edit in &self.edits {
111 if let Some(value_is_legal) = edit.value_is_legal {
112 if !value_is_legal(
113 solution,
114 edit.entity_index,
115 edit.variable_index,
116 edit.to_value,
117 ) {
118 return false;
119 }
120 }
121 let current = (edit.getter)(solution, edit.entity_index, edit.variable_index);
122 changes_value |= current != edit.to_value;
123 }
124
125 changes_value
126 }
127
128 fn do_move<D: Director<S>>(&self, score_director: &mut D) {
129 for edit in &self.edits {
130 let old_value = (edit.getter)(
131 score_director.working_solution(),
132 edit.entity_index,
133 edit.variable_index,
134 );
135 score_director.before_variable_changed(edit.descriptor_index, edit.entity_index);
136 (edit.setter)(
137 score_director.working_solution_mut(),
138 edit.entity_index,
139 edit.variable_index,
140 edit.to_value,
141 );
142 score_director.after_variable_changed(edit.descriptor_index, edit.entity_index);
143
144 let setter = edit.setter;
145 let entity_index = edit.entity_index;
146 let variable_index = edit.variable_index;
147 score_director.register_undo(Box::new(move |solution: &mut S| {
148 setter(solution, entity_index, variable_index, old_value);
149 }));
150 }
151 }
152
153 fn descriptor_index(&self) -> usize {
154 self.edits
155 .first()
156 .map(|edit| edit.descriptor_index)
157 .unwrap_or(usize::MAX)
158 }
159
160 fn entity_indices(&self) -> &[usize] {
161 &self.entity_indices
162 }
163
164 fn variable_name(&self) -> &str {
165 self.variable_label
166 }
167
168 fn requires_hard_improvement(&self) -> bool {
169 self.require_hard_improvement
170 }
171
172 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
173 let scope = MoveTabuScope::new(self.descriptor_index(), self.variable_label);
174 let mut move_id = smallvec![hash_str(self.reason)];
175 let mut undo_move_id = smallvec![hash_str(self.reason)];
176 let mut entity_tokens: SmallVec<[_; 8]> = SmallVec::new();
177 let mut destination_tokens: SmallVec<[_; 8]> = SmallVec::new();
178
179 for edit in &self.edits {
180 let current = (edit.getter)(
181 score_director.working_solution(),
182 edit.entity_index,
183 edit.variable_index,
184 );
185 let descriptor = encode_usize(edit.descriptor_index);
186 let entity = encode_usize(edit.entity_index);
187 let variable = hash_str(edit.variable_name);
188 let from = encode_option_usize(current);
189 let to = encode_option_usize(edit.to_value);
190 let edit_scope = MoveTabuScope::new(edit.descriptor_index, edit.variable_name);
191
192 move_id.extend([descriptor, entity, variable, from, to]);
193 undo_move_id.extend([descriptor, entity, variable, to, from]);
194 entity_tokens.push(edit_scope.entity_token(entity));
195 destination_tokens.push(edit_scope.value_token(to));
196 }
197
198 MoveTabuSignature::new(scope, move_id, undo_move_id)
199 .with_entity_tokens(entity_tokens)
200 .with_destination_value_tokens(destination_tokens)
201 }
202
203 fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
204 for edit in &self.edits {
205 visitor(MoveAffectedEntity {
206 descriptor_index: edit.descriptor_index,
207 entity_index: edit.entity_index,
208 variable_name: edit.variable_name,
209 });
210 }
211 }
212}