Skip to main content

oxiz_sat/
activity.rs

1//! Activity management for variables and clauses
2//!
3//! This module provides efficient activity tracking and decay for both
4//! variables and clauses. Activity scores are used for decision heuristics
5//! and clause deletion policies.
6//!
7//! References:
8//! - "Chaff: Engineering an Efficient SAT Solver" (VSIDS)
9//! - "Glucose: Combining Fast Local Search and Heuristics"
10
11use crate::clause::ClauseId;
12use crate::literal::Var;
13#[allow(unused_imports)]
14use crate::prelude::*;
15
16/// Statistics for activity management
17#[derive(Debug, Clone, Default)]
18pub struct ActivityStats {
19    /// Total number of bumps performed
20    pub total_bumps: u64,
21    /// Total number of decays performed
22    pub total_decays: u64,
23    /// Number of rescales performed
24    pub rescales: u64,
25    /// Current activity increment
26    pub current_increment: f64,
27}
28
29impl ActivityStats {
30    /// Display statistics
31    pub fn display(&self) {
32        println!("Activity Manager Statistics:");
33        println!("  Total bumps: {}", self.total_bumps);
34        println!("  Total decays: {}", self.total_decays);
35        println!("  Rescales: {}", self.rescales);
36        println!("  Current increment: {:.2e}", self.current_increment);
37    }
38}
39
40/// Variable activity manager
41///
42/// Manages activity scores for variables using VSIDS-style decay.
43#[derive(Debug)]
44pub struct VariableActivityManager {
45    /// Activity score for each variable
46    activities: Vec<f64>,
47    /// Activity increment (increased on each decay)
48    increment: f64,
49    /// Decay factor (< 1.0)
50    decay: f64,
51    /// Threshold for rescaling
52    rescale_threshold: f64,
53    /// Statistics
54    stats: ActivityStats,
55}
56
57impl Default for VariableActivityManager {
58    fn default() -> Self {
59        Self::new()
60    }
61}
62
63impl VariableActivityManager {
64    /// Create a new variable activity manager
65    ///
66    /// Default decay factor: 0.95
67    #[must_use]
68    pub fn new() -> Self {
69        Self::with_decay(0.95)
70    }
71
72    /// Create with custom decay factor
73    ///
74    /// Decay should be in range (0.0, 1.0):
75    /// - Higher values (e.g., 0.99) decay more slowly
76    /// - Lower values (e.g., 0.8) decay more quickly
77    #[must_use]
78    pub fn with_decay(decay: f64) -> Self {
79        Self {
80            activities: Vec::new(),
81            increment: 1.0,
82            decay: decay.clamp(0.0, 1.0),
83            rescale_threshold: 1e100,
84            stats: ActivityStats {
85                current_increment: 1.0,
86                ..Default::default()
87            },
88        }
89    }
90
91    /// Resize for new number of variables
92    pub fn resize(&mut self, num_vars: usize) {
93        self.activities.resize(num_vars, 0.0);
94    }
95
96    /// Bump (increase) activity of a variable
97    pub fn bump(&mut self, var: Var) {
98        let idx = var.index();
99
100        // Ensure we have space
101        if idx >= self.activities.len() {
102            self.activities.resize(idx + 1, 0.0);
103        }
104
105        self.activities[idx] += self.increment;
106        self.stats.total_bumps += 1;
107
108        // Rescale if needed
109        if self.activities[idx] > self.rescale_threshold {
110            self.rescale();
111        }
112    }
113
114    /// Decay all activities and increase increment
115    pub fn decay(&mut self) {
116        self.increment /= self.decay;
117        self.stats.total_decays += 1;
118        self.stats.current_increment = self.increment;
119
120        // Rescale if increment gets too large
121        if self.increment > self.rescale_threshold {
122            self.rescale();
123        }
124    }
125
126    /// Rescale all activities to prevent overflow
127    fn rescale(&mut self) {
128        const RESCALE_FACTOR: f64 = 1e-100;
129
130        for activity in &mut self.activities {
131            *activity *= RESCALE_FACTOR;
132        }
133        self.increment *= RESCALE_FACTOR;
134        self.stats.rescales += 1;
135        self.stats.current_increment = self.increment;
136    }
137
138    /// Get activity of a variable
139    #[must_use]
140    pub fn activity(&self, var: Var) -> f64 {
141        let idx = var.index();
142        if idx < self.activities.len() {
143            self.activities[idx]
144        } else {
145            0.0
146        }
147    }
148
149    /// Set activity of a variable
150    pub fn set_activity(&mut self, var: Var, activity: f64) {
151        let idx = var.index();
152        if idx >= self.activities.len() {
153            self.activities.resize(idx + 1, 0.0);
154        }
155        self.activities[idx] = activity;
156    }
157
158    /// Get all activities
159    #[must_use]
160    pub fn activities(&self) -> &[f64] {
161        &self.activities
162    }
163
164    /// Get statistics
165    #[must_use]
166    pub fn stats(&self) -> &ActivityStats {
167        &self.stats
168    }
169
170    /// Reset all activities
171    pub fn reset(&mut self) {
172        for activity in &mut self.activities {
173            *activity = 0.0;
174        }
175        self.increment = 1.0;
176        self.stats = ActivityStats {
177            current_increment: 1.0,
178            ..Default::default()
179        };
180    }
181}
182
183/// Clause activity manager
184///
185/// Manages activity scores for clauses, used in clause deletion policies.
186#[derive(Debug)]
187pub struct ClauseActivityManager {
188    /// Activity score for each clause
189    activities: Vec<f64>,
190    /// Activity increment
191    increment: f64,
192    /// Decay factor
193    decay: f64,
194    /// Rescale threshold
195    rescale_threshold: f64,
196    /// Statistics
197    stats: ActivityStats,
198}
199
200impl Default for ClauseActivityManager {
201    fn default() -> Self {
202        Self::new()
203    }
204}
205
206impl ClauseActivityManager {
207    /// Create a new clause activity manager
208    ///
209    /// Default decay factor: 0.999 (slower than variable decay)
210    #[must_use]
211    pub fn new() -> Self {
212        Self::with_decay(0.999)
213    }
214
215    /// Create with custom decay factor
216    #[must_use]
217    pub fn with_decay(decay: f64) -> Self {
218        Self {
219            activities: Vec::new(),
220            increment: 1.0,
221            decay: decay.clamp(0.0, 1.0),
222            rescale_threshold: 1e20,
223            stats: ActivityStats {
224                current_increment: 1.0,
225                ..Default::default()
226            },
227        }
228    }
229
230    /// Bump activity of a clause
231    pub fn bump(&mut self, clause_id: ClauseId) {
232        let idx = clause_id.0 as usize;
233
234        // Ensure we have space
235        if idx >= self.activities.len() {
236            self.activities.resize(idx + 1, 0.0);
237        }
238
239        self.activities[idx] += self.increment;
240        self.stats.total_bumps += 1;
241
242        if self.activities[idx] > self.rescale_threshold {
243            self.rescale();
244        }
245    }
246
247    /// Decay all clause activities
248    pub fn decay(&mut self) {
249        self.increment /= self.decay;
250        self.stats.total_decays += 1;
251        self.stats.current_increment = self.increment;
252
253        if self.increment > self.rescale_threshold {
254            self.rescale();
255        }
256    }
257
258    /// Rescale to prevent overflow
259    fn rescale(&mut self) {
260        const RESCALE_FACTOR: f64 = 1e-20;
261
262        for activity in &mut self.activities {
263            *activity *= RESCALE_FACTOR;
264        }
265        self.increment *= RESCALE_FACTOR;
266        self.stats.rescales += 1;
267        self.stats.current_increment = self.increment;
268    }
269
270    /// Get activity of a clause
271    #[must_use]
272    pub fn activity(&self, clause_id: ClauseId) -> f64 {
273        let idx = clause_id.0 as usize;
274        if idx < self.activities.len() {
275            self.activities[idx]
276        } else {
277            0.0
278        }
279    }
280
281    /// Set activity of a clause
282    pub fn set_activity(&mut self, clause_id: ClauseId, activity: f64) {
283        let idx = clause_id.0 as usize;
284        if idx >= self.activities.len() {
285            self.activities.resize(idx + 1, 0.0);
286        }
287        self.activities[idx] = activity;
288    }
289
290    /// Get statistics
291    #[must_use]
292    pub fn stats(&self) -> &ActivityStats {
293        &self.stats
294    }
295
296    /// Reset all activities
297    pub fn reset(&mut self) {
298        self.activities.clear();
299        self.increment = 1.0;
300        self.stats = ActivityStats {
301            current_increment: 1.0,
302            ..Default::default()
303        };
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310
311    #[test]
312    fn test_variable_activity_manager_creation() {
313        let manager = VariableActivityManager::new();
314        assert_eq!(manager.decay, 0.95);
315        assert_eq!(manager.increment, 1.0);
316    }
317
318    #[test]
319    fn test_bump_variable() {
320        let mut manager = VariableActivityManager::new();
321        manager.resize(10);
322
323        manager.bump(Var::new(0));
324        assert_eq!(manager.activity(Var::new(0)), 1.0);
325
326        manager.bump(Var::new(0));
327        assert_eq!(manager.activity(Var::new(0)), 2.0);
328    }
329
330    #[test]
331    fn test_decay_variable() {
332        let mut manager = VariableActivityManager::with_decay(0.5);
333        manager.resize(10);
334
335        manager.bump(Var::new(0));
336        let initial_activity = manager.activity(Var::new(0));
337
338        manager.decay();
339        manager.bump(Var::new(1));
340
341        // After decay, new bumps should have higher increment
342        assert!(manager.activity(Var::new(1)) > initial_activity);
343        assert_eq!(manager.stats().total_decays, 1);
344    }
345
346    #[test]
347    fn test_rescale_variable() {
348        let mut manager = VariableActivityManager::new();
349        manager.resize(10);
350        manager.rescale_threshold = 10.0; // Low threshold for testing
351
352        // Bump until rescale
353        for _ in 0..20 {
354            manager.bump(Var::new(0));
355        }
356
357        // Should have rescaled
358        assert!(manager.stats().rescales > 0);
359        assert!(manager.activity(Var::new(0)) < 100.0);
360    }
361
362    #[test]
363    fn test_set_activity() {
364        let mut manager = VariableActivityManager::new();
365        manager.resize(10);
366
367        manager.set_activity(Var::new(0), 42.0);
368        assert_eq!(manager.activity(Var::new(0)), 42.0);
369    }
370
371    #[test]
372    fn test_reset_variable() {
373        let mut manager = VariableActivityManager::new();
374        manager.resize(10);
375
376        manager.bump(Var::new(0));
377        manager.decay();
378
379        manager.reset();
380
381        assert_eq!(manager.activity(Var::new(0)), 0.0);
382        assert_eq!(manager.increment, 1.0);
383        assert_eq!(manager.stats().total_bumps, 0);
384    }
385
386    #[test]
387    fn test_clause_activity_manager_creation() {
388        let manager = ClauseActivityManager::new();
389        assert_eq!(manager.decay, 0.999);
390    }
391
392    #[test]
393    fn test_bump_clause() {
394        let mut manager = ClauseActivityManager::new();
395
396        manager.bump(ClauseId(0));
397        assert_eq!(manager.activity(ClauseId(0)), 1.0);
398
399        manager.bump(ClauseId(0));
400        assert_eq!(manager.activity(ClauseId(0)), 2.0);
401    }
402
403    #[test]
404    fn test_decay_clause() {
405        let mut manager = ClauseActivityManager::with_decay(0.5);
406
407        manager.bump(ClauseId(0));
408        manager.decay();
409        manager.bump(ClauseId(1));
410
411        assert!(manager.activity(ClauseId(1)) > manager.activity(ClauseId(0)));
412    }
413
414    #[test]
415    fn test_clause_rescale() {
416        let mut manager = ClauseActivityManager::new();
417        manager.rescale_threshold = 5.0;
418
419        for _ in 0..10 {
420            manager.bump(ClauseId(0));
421        }
422
423        assert!(manager.stats().rescales > 0);
424    }
425
426    #[test]
427    fn test_reset_clause() {
428        let mut manager = ClauseActivityManager::new();
429
430        manager.bump(ClauseId(0));
431        manager.reset();
432
433        assert_eq!(manager.activity(ClauseId(0)), 0.0);
434        assert_eq!(manager.increment, 1.0);
435    }
436}