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}