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