sat_solver/sat/
restarter.rs

1//! Defines restart strategies for SAT solvers.
2//!
3//! Restart strategies determine when a SAT solver should abandon its current search path
4//! and restart the search from the root, typically with different heuristics or a randomized
5//! variable selection order. Restarts can help escape "stuck" regions of the search space
6//! and improve overall solver performance, especially on hard instances.
7//!
8//! This module provides:
9//! - The `Restarter` trait, defining a common interface for restart strategies.
10//! - Several concrete implementations:
11//!   - `Luby`: Implements a restart strategy based on the Luby sequence, which provides
12//!     a theoretically optimal restart schedule under certain assumptions. The interval
13//!     between restarts increases according to the Luby sequence.
14//!   - `Geometric`: Implements a geometric restart strategy where the interval between
15//!     restarts is multiplied by a constant factor `N` after each restart.
16//!   - `Fixed`: Implements a fixed interval restart strategy where restarts occur
17//!     every `N` conflicts (or other units, depending on when `should_restart` is called).
18//!   - `Linear`: Implements a linear restart strategy where the interval between
19//!     restarts increases by a fixed amount `N` after each restart.
20//!   - `Never`: A strategy that never triggers a restart.
21
22use clap::ValueEnum;
23use std::fmt::{Debug, Display};
24
25/// Trait defining the interface for restart strategies.
26///
27/// Implementors of this trait determine when the solver should perform a restart.
28/// The decision is typically based on a counter, often representing the number of conflicts
29/// encountered since the last restart.
30pub trait Restarter: Debug + Clone {
31    /// Creates a new instance of the restart strategy, initialised to its starting state.
32    fn new() -> Self;
33
34    /// Returns the number of conflicts (or other units) remaining until the next restart.
35    /// When this count reaches zero, a restart should occur.
36    fn restarts_in(&self) -> usize;
37
38    /// Decrements the count of conflicts (or units) remaining until the next restart.
39    /// This is called after each conflict in the solver.
40    fn increment_restarts_in(&mut self);
41
42    /// Performs the actions associated with a restart.
43    /// This usually involves resetting the `restarts_in` counter to a new interval
44    /// and potentially updating internal state to calculate the next interval.
45    /// It also increments the total count of restarts performed.
46    fn restart(&mut self);
47
48    /// Returns the total number of restarts performed by this strategy so far.
49    fn num_restarts(&self) -> usize;
50
51    /// Checks if a restart should occur based on the current state.
52    ///
53    /// If `restarts_in()` is 0, this method calls `restart()` and returns `true`.
54    /// Otherwise, it calls `increment_restarts_in()` (which typically decrements the counter)
55    /// and returns `false`.
56    ///
57    /// # Returns
58    /// `true` if a restart was triggered, `false` otherwise.
59    fn should_restart(&mut self) -> bool {
60        if self.restarts_in() == 0 {
61            self.restart();
62            true
63        } else {
64            self.increment_restarts_in();
65            false
66        }
67    }
68}
69
70/// A restart strategy based on the Luby sequence.
71///
72/// The Luby sequence (`u_i`) is defined as:
73/// `u_i` = 2^(k-1) if i = 2^k - 1
74/// `u_i` = u_(i - 2^(k-1) + 1) if 2^(k-1) <= i < 2^k - 1
75///
76/// This sequence has the property of being optimal for repeating an experiment with unknown
77/// probability distribution of success time. In SAT solvers, it means restarting after
78/// `u_1*N, u_2*N, u_3*N, ...` conflicts (or decision units).
79/// The sequence starts 1, 1, 2, 1, 1, 2, 4, 1, 1, 2, 1, 1, 2, 4, 8, ...
80///
81/// The generic constant `N` is a unit scaling factor for the intervals.
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct Luby<const N: usize> {
84    /// Total number of restarts performed.
85    restarts: usize,
86    /// Number of conflicts/units remaining until the next restart.
87    restarts_in: usize,
88    /// The current interval (number of conflicts/units) for the next restart,
89    /// calculated as `luby(restarts_next) * N`.
90    restarts_interval: usize,
91    /// The index into the Luby sequence for calculating the next interval.
92    restarts_next: usize,
93
94    first: usize,
95    second: usize,
96}
97
98/// The Luby sequence iterator generates the next Luby number multiplied by `N`.
99/// Taken from <https://docs.rs/luby/latest/luby/struct.Luby.html>
100/// This iterator generates the sequence of Luby numbers, which are used to determine
101/// the intervals for restarts in the Luby restart strategy.
102impl<const N: usize> Iterator for Luby<N> {
103    type Item = usize;
104
105    fn next(&mut self) -> Option<Self::Item> {
106        if self.first & self.first.wrapping_neg() == self.second {
107            self.first += 1;
108            self.second = 1;
109        } else {
110            self.second *= 2;
111        }
112
113        Some(self.second * N)
114    }
115}
116
117impl<const N: usize> Luby<N> {
118    /// Calculates the k-th element of the Luby sequence.
119    ///
120    /// # Parameters
121    /// * `x`: The index (1-based) for the Luby sequence.
122    ///
123    /// # Returns
124    /// The Luby number for the given index.
125    #[allow(dead_code)]
126    fn luby(x: usize) -> usize {
127        let mut k = 1_usize;
128        while (1 << (k - 1)) <= x {
129            k = k.wrapping_add(1);
130        }
131        if x == (1 << (k - 2)) {
132            1 << (k - 2)
133        } else {
134            Self::luby(x - (1 << (k - 2)))
135        }
136    }
137}
138
139impl<const N: usize> Restarter for Luby<N> {
140    /// Creates a new `Luby` restarter.
141    /// The first restart interval will be `N * luby(1)`.
142    /// `restarts_in` is initialised to 0, meaning it will restart on the first call to `should_restart()`.
143    fn new() -> Self {
144        Self {
145            restarts: 0,
146            restarts_in: 0,
147            restarts_interval: N,
148            restarts_next: 1,
149            first: 1,
150            second: 1,
151        }
152    }
153
154    fn restarts_in(&self) -> usize {
155        self.restarts_in
156    }
157
158    fn increment_restarts_in(&mut self) {
159        self.restarts_in = self.restarts_in.wrapping_sub(1);
160    }
161
162    /// Performs a restart:
163    /// 1. Increments the restart count.
164    /// 2. Sets `restarts_in` to the previously calculated `restarts_interval`.
165    /// 3. Calculates the next `restarts_interval` as `Self::luby(self.restarts_next) * N`.
166    /// 4. Increments `restarts_next` for the subsequent Luby sequence calculation.
167    fn restart(&mut self) {
168        self.restarts = self.restarts.wrapping_add(1);
169        self.restarts_in = self.restarts_interval;
170
171        if let Some(next_interval) = self.next() {
172            self.restarts_interval = next_interval;
173        } else {
174            self.restarts_interval = N;
175        }
176    }
177
178    fn num_restarts(&self) -> usize {
179        self.restarts
180    }
181}
182
183/// A geometric restart strategy.
184///
185/// The interval between restarts is multiplied by a constant factor `N` (where `N` > 1 for
186/// increasing intervals) after each restart.
187/// The sequence of intervals is `initial, initial*N, initial*N*N, ...`.
188/// Here, `initial` seems to be 1 based on `restarts_interval: 1` and then it's multiplied by `N`.
189/// The first interval will be 1 (due to `restarts_in: 0` and `restarts_interval: 1` upon first `restart`),
190/// then `N`, then `N*N`, etc.
191/// The generic constant `N` is the geometric factor.
192#[derive(Debug, Clone, PartialEq, Eq)]
193pub struct Geometric<const N: usize> {
194    /// Total number of restarts performed.
195    restarts: usize,
196    /// Number of conflicts/units remaining until the next restart.
197    restarts_in: usize,
198    /// The current interval length for the next restart.
199    restarts_interval: usize,
200}
201
202impl<const N: usize> Restarter for Geometric<N> {
203    /// Creates a new `Geometric` restarter.
204    /// `restarts_in` is initialised to 0, meaning it will restart on the first call to `should_restart()`.
205    /// The first interval after the initial restart will be 1, the next will be `1*N`, then `1*N*N`, etc.
206    fn new() -> Self {
207        Self {
208            restarts: 0,
209            restarts_in: 0,
210            restarts_interval: 1,
211        }
212    }
213
214    fn restarts_in(&self) -> usize {
215        self.restarts_in
216    }
217
218    fn increment_restarts_in(&mut self) {
219        self.restarts_in = self.restarts_in.wrapping_sub(1);
220    }
221
222    /// Performs a restart:
223    /// 1. Increments the restart count.
224    /// 2. Sets `restarts_in` to the current `restarts_interval`.
225    /// 3. Updates `restarts_interval` by multiplying it by `N` for the next cycle.
226    fn restart(&mut self) {
227        self.restarts = self.restarts.wrapping_add(1);
228        self.restarts_in = self.restarts_interval;
229        self.restarts_interval = self.restarts_interval.wrapping_mul(N);
230    }
231
232    fn num_restarts(&self) -> usize {
233        self.restarts
234    }
235
236    /// Overrides the default `should_restart` from the trait.
237    /// This implementation is identical to the default one but is explicitly written out.
238    /// If `restarts_in` is 0, it calls `restart()` and returns `true`.
239    /// Otherwise, it decrements `restarts_in` and returns `false`.
240    fn should_restart(&mut self) -> bool {
241        if self.restarts_in == 0 {
242            self.restart();
243            true
244        } else {
245            self.restarts_in = self.restarts_in.wrapping_sub(1);
246            false
247        }
248    }
249}
250
251/// A fixed interval restart strategy.
252///
253/// Restarts occur every `N` conflicts (or other units).
254/// The generic constant `N` defines this fixed interval.
255#[derive(Debug, Clone, PartialEq, Eq)]
256pub struct Fixed<const N: usize> {
257    /// Total number of restarts performed.
258    restarts: usize,
259    /// Number of conflicts/units remaining until the next restart.
260    restarts_in: usize,
261    /// The fixed interval `N` between restarts.
262    restarts_interval: usize,
263}
264
265impl<const N: usize> Restarter for Fixed<N> {
266    /// Creates a new `Fixed` restarter.
267    /// `restarts_in` is initialised to 0, meaning it will restart on the first call to `should_restart()`.
268    /// The interval for all subsequent restarts will be `N`.
269    fn new() -> Self {
270        assert!(N > 0, "Fixed interval N must be positive.");
271        Self {
272            restarts: 0,
273            restarts_in: 0,
274            restarts_interval: N,
275        }
276    }
277
278    fn restarts_in(&self) -> usize {
279        self.restarts_in
280    }
281
282    fn increment_restarts_in(&mut self) {
283        self.restarts_in = self.restarts_in.wrapping_sub(1);
284    }
285
286    /// Performs a restart:
287    /// 1. Increments the restart count.
288    /// 2. Resets `restarts_in` to `restarts_interval` (which is fixed at `N`).
289    fn restart(&mut self) {
290        self.restarts = self.restarts.wrapping_add(1);
291        self.restarts_in = self.restarts_interval;
292    }
293
294    fn num_restarts(&self) -> usize {
295        self.restarts
296    }
297}
298
299/// A strategy that never triggers a restart.
300#[derive(Debug, Clone, PartialEq, Eq)]
301pub struct Never {}
302
303impl Restarter for Never {
304    fn new() -> Self {
305        Self {}
306    }
307
308    /// Returns 0, but `should_restart` will always be false, so this value is not
309    /// practically used to count down to a restart.
310    fn restarts_in(&self) -> usize {
311        usize::MAX
312    }
313
314    /// Does nothing, as no restarts are pending.
315    fn increment_restarts_in(&mut self) {}
316
317    /// Does nothing, as restarts are disabled.
318    fn restart(&mut self) {}
319
320    /// Always returns 0, as no restarts are ever performed.
321    fn num_restarts(&self) -> usize {
322        0
323    }
324
325    /// Always returns `false`, indicating that a restart should never occur.
326    fn should_restart(&mut self) -> bool {
327        false
328    }
329}
330
331/// A linear restart strategy.
332///
333/// The interval between restarts increases by a fixed amount `N` after each restart.
334/// The sequence of intervals is `initial_N, initial_N + N, initial_N + 2N, ...`.
335/// The generic constant `N` is the fixed amount by which the interval increases.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct Linear<const N: usize> {
338    /// Total number of restarts performed.
339    restarts: usize,
340    /// Number of conflicts/units remaining until the next restart.
341    restarts_in: usize,
342    /// The current interval length, which increases by `N` after each restart.
343    restarts_interval: usize,
344}
345
346impl<const N: usize> Restarter for Linear<N> {
347    /// Creates a new `Linear` restarter.
348    /// `restarts_in` is initialised to 0, meaning it will restart on the first call to `should_restart()`.
349    /// The first interval after the initial restart will be `N`, the next `2N`, then `3N`, and so on.
350    fn new() -> Self {
351        assert!(N > 0, "Linear increment N must be positive.");
352        Self {
353            restarts: 0,
354            restarts_in: 0,
355            restarts_interval: N,
356        }
357    }
358
359    fn restarts_in(&self) -> usize {
360        self.restarts_in
361    }
362
363    fn increment_restarts_in(&mut self) {
364        self.restarts_in -= 1;
365    }
366
367    /// Performs a restart:
368    /// 1. Increments the restart count.
369    /// 2. Sets `restarts_in` to the current `restarts_interval`.
370    /// 3. Increases `restarts_interval` by `N` for the next cycle.
371    fn restart(&mut self) {
372        self.restarts = self.restarts.wrapping_add(1);
373        self.restarts_in = self.restarts_interval;
374        self.restarts_interval = self.restarts_interval.wrapping_add(N);
375    }
376
377    fn num_restarts(&self) -> usize {
378        self.restarts
379    }
380}
381
382/// An enum representing different restart strategies.
383#[derive(Debug, Clone, PartialEq, Eq)]
384pub enum RestarterImpls<const N: usize> {
385    /// The Luby restart strategy, which uses the Luby sequence for determining restart intervals.
386    Luby(Luby<N>),
387    /// A geometric restart strategy, where the interval between restarts is multiplied by a constant factor `N`.
388    Geometric(Geometric<N>),
389    /// A fixed interval restart strategy, where restarts occur every `N` conflicts (or other units).
390    Fixed(Fixed<N>),
391    /// A linear restart strategy, where the interval between restarts increases by a fixed amount `N` after each restart.
392    Linear(Linear<N>),
393    /// A strategy that never triggers a restart.
394    Never(Never),
395}
396
397impl<const N: usize> Restarter for RestarterImpls<N> {
398    fn new() -> Self {
399        Self::Luby(Luby::new())
400    }
401
402    fn restarts_in(&self) -> usize {
403        match self {
404            Self::Luby(r) => r.restarts_in(),
405            Self::Geometric(r) => r.restarts_in(),
406            Self::Fixed(r) => r.restarts_in(),
407            Self::Linear(r) => r.restarts_in(),
408            Self::Never(r) => r.restarts_in(),
409        }
410    }
411
412    fn increment_restarts_in(&mut self) {
413        match self {
414            Self::Luby(r) => r.increment_restarts_in(),
415            Self::Geometric(r) => r.increment_restarts_in(),
416            Self::Fixed(r) => r.increment_restarts_in(),
417            Self::Linear(r) => r.increment_restarts_in(),
418            Self::Never(_) => {}
419        }
420    }
421
422    fn restart(&mut self) {
423        match self {
424            Self::Luby(r) => r.restart(),
425            Self::Geometric(r) => r.restart(),
426            Self::Fixed(r) => r.restart(),
427            Self::Linear(r) => r.restart(),
428            Self::Never(_) => {}
429        }
430    }
431
432    fn num_restarts(&self) -> usize {
433        match self {
434            Self::Luby(r) => r.num_restarts(),
435            Self::Geometric(r) => r.num_restarts(),
436            Self::Fixed(r) => r.num_restarts(),
437            Self::Linear(r) => r.num_restarts(),
438            Self::Never(_) => 0,
439        }
440    }
441
442    fn should_restart(&mut self) -> bool {
443        match self {
444            Self::Luby(r) => r.should_restart(),
445            Self::Geometric(r) => r.should_restart(),
446            Self::Fixed(r) => r.should_restart(),
447            Self::Linear(r) => r.should_restart(),
448            Self::Never(_) => false,
449        }
450    }
451}
452
453/// An enum representing the type of restarter to use in a SAT solver.
454#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, Default, ValueEnum)]
455pub enum RestarterType {
456    /// The Luby restart strategy, which uses the Luby sequence for determining restart intervals.
457    #[default]
458    Luby,
459    /// A geometric restart strategy, where the interval between restarts is multiplied by a constant factor `N`.
460    Geometric,
461    /// A fixed interval restart strategy, where restarts occur every `N` conflicts (or other units).
462    Fixed,
463    /// A linear restart strategy, where the interval between restarts increases by a fixed amount `N` after each restart.
464    Linear,
465    /// A strategy that never triggers a restart.
466    Never,
467}
468
469impl Display for RestarterType {
470    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
471        match self {
472            Self::Luby => write!(f, "luby"),
473            Self::Geometric => write!(f, "geometric"),
474            Self::Fixed => write!(f, "fixed"),
475            Self::Linear => write!(f, "linear"),
476            Self::Never => write!(f, "never"),
477        }
478    }
479}
480
481impl RestarterType {
482    /// Returns a new instance of the corresponding restarter.
483    #[must_use]
484    pub fn to_impl(self) -> RestarterImpls<3> {
485        match self {
486            Self::Luby => RestarterImpls::Luby(Luby::new()),
487            Self::Geometric => RestarterImpls::Geometric(Geometric::new()),
488            Self::Fixed => RestarterImpls::Fixed(Fixed::new()),
489            Self::Linear => RestarterImpls::Linear(Linear::new()),
490            Self::Never => RestarterImpls::Never(Never::new()),
491        }
492    }
493}