solverforge_solver/phase/localsearch/acceptor/great_deluge.rs
1// Great Deluge acceptor.
2
3use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_core::score::Score;
7
8use super::Acceptor;
9
10/// Great Deluge acceptor - accepts moves above a rising water level.
11///
12/// The water level starts at the initial solution's score and rises over time.
13/// A move is accepted if its resulting score is at or above the current water level.
14/// This allows temporary score degradation while gradually tightening acceptance.
15///
16/// # Algorithm
17///
18/// 1. Water level starts at initial score
19/// 2. Each step, water level rises by `rain_speed * |initial_score|`
20/// 3. Accept if `move_score >= water_level`
21///
22/// # Example
23///
24/// ```
25/// use solverforge_solver::phase::localsearch::GreatDelugeAcceptor;
26/// use solverforge_core::score::SoftScore;
27/// use solverforge_core::domain::PlanningSolution;
28///
29/// #[derive(Clone)]
30/// struct MySolution;
31/// impl PlanningSolution for MySolution {
32/// type Score = SoftScore;
33/// fn score(&self) -> Option<Self::Score> { None }
34/// fn set_score(&mut self, _: Option<Self::Score>) {}
35/// }
36///
37/// // Rain speed of 0.001 means water level rises by 0.1% of |initial| per step
38/// let acceptor = GreatDelugeAcceptor::<MySolution>::new(0.001);
39/// ```
40pub struct GreatDelugeAcceptor<S: PlanningSolution> {
41 // Rain speed - ratio of |initial_score| to add per step.
42 rain_speed: f64,
43 // Current water level.
44 water_level: Option<S::Score>,
45 // Absolute value of initial score, used to compute increment.
46 initial_abs_score: Option<S::Score>,
47}
48
49impl<S: PlanningSolution> Debug for GreatDelugeAcceptor<S> {
50 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
51 f.debug_struct("GreatDelugeAcceptor")
52 .field("rain_speed", &self.rain_speed)
53 .finish()
54 }
55}
56
57impl<S: PlanningSolution> Clone for GreatDelugeAcceptor<S> {
58 fn clone(&self) -> Self {
59 Self {
60 rain_speed: self.rain_speed,
61 water_level: self.water_level,
62 initial_abs_score: self.initial_abs_score,
63 }
64 }
65}
66
67impl<S: PlanningSolution> GreatDelugeAcceptor<S> {
68 /// Creates a new Great Deluge acceptor.
69 ///
70 /// # Arguments
71 /// * `rain_speed` - Ratio of |initial_score| to raise water level per step.
72 /// Typical values: 0.0001 to 0.01
73 pub fn new(rain_speed: f64) -> Self {
74 Self {
75 rain_speed,
76 water_level: None,
77 initial_abs_score: None,
78 }
79 }
80}
81
82impl<S: PlanningSolution> Default for GreatDelugeAcceptor<S> {
83 fn default() -> Self {
84 Self::new(0.001)
85 }
86}
87
88impl<S: PlanningSolution> Acceptor<S> for GreatDelugeAcceptor<S> {
89 fn is_accepted(&mut self, last_step_score: &S::Score, move_score: &S::Score) -> bool {
90 // Always accept improving moves
91 if move_score > last_step_score {
92 return true;
93 }
94
95 // Accept if at or above water level
96 match &self.water_level {
97 Some(water_level) => move_score >= water_level,
98 None => true, // No water level yet, accept
99 }
100 }
101
102 fn phase_started(&mut self, initial_score: &S::Score) {
103 self.water_level = Some(*initial_score);
104 self.initial_abs_score = Some(initial_score.abs());
105 }
106
107 fn step_ended(&mut self, _step_score: &S::Score) {
108 // Raise water level by rain_speed * |initial_score|
109 if let (Some(water), Some(abs_score)) = (&self.water_level, &self.initial_abs_score) {
110 let increment = abs_score.multiply(self.rain_speed);
111 self.water_level = Some(*water + increment);
112 }
113 }
114
115 fn phase_ended(&mut self) {
116 self.water_level = None;
117 self.initial_abs_score = None;
118 }
119}
120
121#[cfg(test)]
122#[path = "great_deluge_tests.rs"]
123mod tests;