Skip to main content

solverforge_solver/heuristic/move/
compound_scalar.rs

1use 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}