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::SimpleScore;
27/// use solverforge_core::domain::PlanningSolution;
28///
29/// #[derive(Clone)]
30/// struct MySolution;
31/// impl PlanningSolution for MySolution {
32///     type Score = SimpleScore;
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(&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)]
122mod tests {
123    use super::*;
124    use solverforge_core::score::SimpleScore;
125
126    #[derive(Clone)]
127    struct TestSolution {
128        score: Option<SimpleScore>,
129    }
130
131    impl PlanningSolution for TestSolution {
132        type Score = SimpleScore;
133        fn score(&self) -> Option<Self::Score> {
134            self.score
135        }
136        fn set_score(&mut self, score: Option<Self::Score>) {
137            self.score = score;
138        }
139    }
140
141    #[test]
142    fn test_accepts_improving_moves() {
143        let mut acceptor = GreatDelugeAcceptor::<TestSolution>::new(0.001);
144        acceptor.phase_started(&SimpleScore::of(-100));
145
146        // Improving move: -100 -> -50
147        assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-50)));
148    }
149
150    #[test]
151    fn test_accepts_above_water_level() {
152        let mut acceptor = GreatDelugeAcceptor::<TestSolution>::new(0.001);
153        acceptor.phase_started(&SimpleScore::of(-100));
154
155        // Equal to water level
156        assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-100)));
157
158        // Above water level (less negative)
159        assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-90)));
160    }
161
162    #[test]
163    fn test_rejects_below_water_level() {
164        let mut acceptor = GreatDelugeAcceptor::<TestSolution>::new(0.001);
165        acceptor.phase_started(&SimpleScore::of(-100));
166
167        // Below water level (more negative)
168        assert!(!acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-110)));
169    }
170
171    #[test]
172    fn test_water_rises_over_time() {
173        let mut acceptor = GreatDelugeAcceptor::<TestSolution>::new(0.1);
174        acceptor.phase_started(&SimpleScore::of(-100));
175
176        // Initially water_level = -100
177        // Score -100 is at water level (accepted)
178        assert!(acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-100)));
179        // Score -101 is below water level and not improving (-101 < -100)
180        assert!(!acceptor.is_accepted(&SimpleScore::of(-100), &SimpleScore::of(-101)));
181
182        // After one step, water rises by 0.1 * 100 = 10, so water_level = -90
183        acceptor.step_ended(&SimpleScore::of(-100));
184        // Score -90 is at water level
185        assert!(acceptor.is_accepted(&SimpleScore::of(-90), &SimpleScore::of(-90)));
186        // Score -91 is below water level and not improving (-91 < -90)
187        assert!(!acceptor.is_accepted(&SimpleScore::of(-90), &SimpleScore::of(-91)));
188
189        // After another step, water_level = -80
190        acceptor.step_ended(&SimpleScore::of(-90));
191        // Score -80 is at water level
192        assert!(acceptor.is_accepted(&SimpleScore::of(-80), &SimpleScore::of(-80)));
193        // Score -81 is below water level and not improving (-81 < -80)
194        assert!(!acceptor.is_accepted(&SimpleScore::of(-80), &SimpleScore::of(-81)));
195    }
196
197    #[test]
198    fn test_phase_reset() {
199        let mut acceptor = GreatDelugeAcceptor::<TestSolution>::new(0.1);
200        acceptor.phase_started(&SimpleScore::of(-100));
201        acceptor.step_ended(&SimpleScore::of(-100));
202        acceptor.phase_ended();
203
204        // After phase ends, should reset
205        acceptor.phase_started(&SimpleScore::of(-50));
206        // Water level should be -50, not -90
207        assert!(acceptor.is_accepted(&SimpleScore::of(-50), &SimpleScore::of(-50)));
208        assert!(!acceptor.is_accepted(&SimpleScore::of(-50), &SimpleScore::of(-51)));
209    }
210}