solverforge_solver/phase/localsearch/acceptor/
move_tabu.rs1use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_core::Score;
7
8use super::Acceptor;
9
10pub struct MoveTabuAcceptor {
28 move_tabu_size: usize,
30 move_tabu_list: Vec<u64>,
32 current_step_move: Option<u64>,
34 aspiration_enabled: bool,
36 best_score: Option<i64>,
38}
39
40impl Debug for MoveTabuAcceptor {
41 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
42 f.debug_struct("MoveTabuAcceptor")
43 .field("move_tabu_size", &self.move_tabu_size)
44 .field("tabu_list_len", &self.move_tabu_list.len())
45 .field("aspiration_enabled", &self.aspiration_enabled)
46 .finish()
47 }
48}
49
50impl Clone for MoveTabuAcceptor {
51 fn clone(&self) -> Self {
52 Self {
53 move_tabu_size: self.move_tabu_size,
54 move_tabu_list: self.move_tabu_list.clone(),
55 current_step_move: self.current_step_move,
56 aspiration_enabled: self.aspiration_enabled,
57 best_score: self.best_score,
58 }
59 }
60}
61
62impl MoveTabuAcceptor {
63 pub fn new(move_tabu_size: usize) -> Self {
72 assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
73 Self {
74 move_tabu_size,
75 move_tabu_list: Vec::with_capacity(move_tabu_size),
76 current_step_move: None,
77 aspiration_enabled: true,
78 best_score: None,
79 }
80 }
81
82 pub fn without_aspiration(move_tabu_size: usize) -> Self {
91 assert!(move_tabu_size > 0, "move_tabu_size must be > 0, got 0");
92 Self {
93 move_tabu_size,
94 move_tabu_list: Vec::with_capacity(move_tabu_size),
95 current_step_move: None,
96 aspiration_enabled: false,
97 best_score: None,
98 }
99 }
100
101 pub fn record_move(&mut self, move_hash: u64) {
105 self.current_step_move = Some(move_hash);
106 }
107
108 pub fn is_move_tabu(&self, move_hash: u64) -> bool {
109 self.move_tabu_list.contains(&move_hash)
110 }
111
112 pub fn aspiration_enabled(&self) -> bool {
113 self.aspiration_enabled
114 }
115
116 fn score_to_i64<S: PlanningSolution>(score: &S::Score) -> i64 {
117 let levels = score.to_level_numbers();
118 *levels.last().unwrap_or(&0)
120 }
121}
122
123impl Default for MoveTabuAcceptor {
124 fn default() -> Self {
125 Self::new(7)
126 }
127}
128
129impl<S: PlanningSolution> Acceptor<S> for MoveTabuAcceptor {
130 fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
131 if self.aspiration_enabled {
133 if let Some(best) = self.best_score {
134 let move_value = Self::score_to_i64::<S>(move_score);
135 if move_value > best {
136 return true; }
138 }
139 }
140
141 if move_score > last_step_score {
143 return true;
144 }
145
146 if move_score >= last_step_score {
148 return true;
149 }
150
151 false
152 }
153
154 fn phase_started(&mut self, initial_score: &S::Score) {
155 self.move_tabu_list.clear();
156 self.current_step_move = None;
157 self.best_score = Some(Self::score_to_i64::<S>(initial_score));
158 }
159
160 fn phase_ended(&mut self) {
161 self.move_tabu_list.clear();
162 }
163
164 fn step_started(&mut self) {
165 self.current_step_move = None;
166 }
167
168 fn step_ended(&mut self, step_score: &S::Score) {
169 if let Some(move_hash) = self.current_step_move {
171 if self.move_tabu_list.len() >= self.move_tabu_size {
172 self.move_tabu_list.remove(0);
173 }
174 self.move_tabu_list.push(move_hash);
175 }
176 self.current_step_move = None;
177
178 let step_value = Self::score_to_i64::<S>(step_score);
180 if let Some(best) = self.best_score {
181 if step_value > best {
182 self.best_score = Some(step_value);
183 }
184 } else {
185 self.best_score = Some(step_value);
186 }
187 }
188}
189
190#[cfg(test)]
191#[path = "move_tabu_tests.rs"]
192mod tests;