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