Skip to main content

solverforge_solver/heuristic/move/
dynamic_scalar_change.rs

1use std::borrow::Cow;
2use std::fmt::Debug;
3
4use smallvec::smallvec;
5use solverforge_core::domain::{DynamicScalarVariableSlot, PlanningSolution};
6use solverforge_scoring::Director;
7
8use crate::stats::{CandidateTraceCoordinate, CandidateTraceIdentity};
9
10use super::metadata::{encode_option_debug, encode_usize, hash_str, MoveTabuScope};
11use super::{Move, MoveTabuSignature};
12
13pub struct DynamicScalarChangeMove<S> {
14    slot: DynamicScalarVariableSlot<S>,
15    entity_index: usize,
16    to_value: Option<usize>,
17}
18
19impl<S> Debug for DynamicScalarChangeMove<S> {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_struct("DynamicScalarChangeMove")
22            .field("entity_index", &self.entity_index)
23            .field("descriptor_index", &self.slot.descriptor_index())
24            .field("variable", &self.slot.variable)
25            .field("variable_name", &self.slot.variable_name)
26            .field("to_value", &self.to_value)
27            .finish()
28    }
29}
30
31impl<S> DynamicScalarChangeMove<S> {
32    pub fn new(
33        slot: DynamicScalarVariableSlot<S>,
34        entity_index: usize,
35        to_value: Option<usize>,
36    ) -> Self {
37        Self {
38            slot,
39            entity_index,
40            to_value,
41        }
42    }
43
44    pub fn entity_index(&self) -> usize {
45        self.entity_index
46    }
47
48    pub fn to_value(&self) -> Option<usize> {
49        self.to_value
50    }
51}
52
53impl<S> Move<S> for DynamicScalarChangeMove<S>
54where
55    S: PlanningSolution,
56{
57    type Undo = Option<usize>;
58
59    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
60        if !self.slot.value_is_legal(
61            score_director.working_solution(),
62            self.entity_index,
63            self.to_value,
64        ) {
65            return false;
66        }
67        self.slot
68            .current_value(score_director.working_solution(), self.entity_index)
69            != self.to_value
70    }
71
72    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
73        let old_value = self
74            .slot
75            .current_value(score_director.working_solution(), self.entity_index);
76        let descriptor_index = self.slot.descriptor_index();
77
78        score_director.before_variable_changed(descriptor_index, self.entity_index);
79        self.slot.set_value(
80            score_director.working_solution_mut(),
81            self.entity_index,
82            self.to_value,
83        );
84        score_director.after_variable_changed(descriptor_index, self.entity_index);
85
86        old_value
87    }
88
89    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
90        let descriptor_index = self.slot.descriptor_index();
91        score_director.before_variable_changed(descriptor_index, self.entity_index);
92        self.slot.set_value(
93            score_director.working_solution_mut(),
94            self.entity_index,
95            undo,
96        );
97        score_director.after_variable_changed(descriptor_index, self.entity_index);
98    }
99
100    fn descriptor_index(&self) -> usize {
101        self.slot.descriptor_index()
102    }
103
104    fn entity_indices(&self) -> &[usize] {
105        std::slice::from_ref(&self.entity_index)
106    }
107
108    fn variable_name(&self) -> &str {
109        self.slot.variable_name
110    }
111
112    fn telemetry_label(&self) -> &'static str {
113        "dynamic_scalar_change"
114    }
115
116    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> Cow<'_, MoveTabuSignature> {
117        let current = self
118            .slot
119            .current_value(score_director.working_solution(), self.entity_index);
120        let from_id = encode_option_debug(current.as_ref());
121        let to_id = encode_option_debug(self.to_value.as_ref());
122        let entity_id = encode_usize(self.entity_index);
123        let variable_id = hash_str(self.slot.variable_name);
124        let scope = MoveTabuScope::new(self.slot.descriptor_index(), self.slot.variable_name);
125
126        Cow::Owned(
127            MoveTabuSignature::new(
128                scope,
129                smallvec![
130                    encode_usize(self.slot.descriptor_index()),
131                    variable_id,
132                    entity_id,
133                    from_id,
134                    to_id
135                ],
136                smallvec![
137                    encode_usize(self.slot.descriptor_index()),
138                    variable_id,
139                    entity_id,
140                    to_id,
141                    from_id
142                ],
143            )
144            .with_entity_tokens([scope.entity_token(entity_id)])
145            .with_destination_value_tokens([scope.value_token(to_id)]),
146        )
147    }
148
149    fn candidate_trace_identity(&self) -> Option<CandidateTraceIdentity> {
150        Some(CandidateTraceIdentity::logical_move(
151            self.slot.descriptor_index(),
152            self.slot.variable_name,
153            "scalar_change",
154            vec![
155                CandidateTraceCoordinate::from(self.entity_index),
156                CandidateTraceCoordinate::from(self.to_value),
157            ],
158        ))
159    }
160}