Skip to main content

oxiz_sat/
cube_solver.rs

1use crate::clause::Clause;
2use crate::cube::{Cube, CubeResult, CubeStats};
3/// Parallel cube solving using the Cube-and-Conquer technique.
4///
5/// This module implements the "Conquer" phase of Cube-and-Conquer, distributing
6/// cubes to parallel workers and aggregating results.
7#[allow(unused_imports)]
8use crate::prelude::*;
9use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
10use std::sync::Arc;
11use std::time::{Duration, Instant};
12
13/// Configuration for parallel cube solving.
14#[derive(Debug, Clone)]
15pub struct CubeSolverConfig {
16    /// Maximum time per cube (in seconds)
17    pub cube_timeout: Duration,
18    /// Number of parallel workers
19    pub num_workers: usize,
20    /// Stop on first SAT cube found
21    pub early_termination: bool,
22    /// Enable progress reporting
23    pub verbose: bool,
24}
25
26impl Default for CubeSolverConfig {
27    fn default() -> Self {
28        Self {
29            cube_timeout: Duration::from_secs(60),
30            num_workers: std::thread::available_parallelism()
31                .map(|n| n.get())
32                .unwrap_or(1),
33            early_termination: true,
34            verbose: false,
35        }
36    }
37}
38
39/// Result of solving a single cube.
40#[derive(Debug, Clone)]
41pub struct CubeSolveResult {
42    /// The cube that was solved
43    pub cube: Cube,
44    /// Result (SAT/UNSAT/Unknown)
45    pub result: CubeResult,
46    /// Time taken to solve
47    pub time: Duration,
48    /// Number of conflicts during solving
49    pub conflicts: u64,
50    /// Number of decisions made
51    pub decisions: u64,
52}
53
54/// Parallel cube solver.
55pub struct ParallelCubeSolver {
56    /// Configuration
57    config: CubeSolverConfig,
58    /// Number of cubes solved
59    cubes_solved: Arc<AtomicUsize>,
60    /// Number of SAT cubes found
61    sat_count: Arc<AtomicUsize>,
62    /// Number of UNSAT cubes found
63    unsat_count: Arc<AtomicUsize>,
64    /// Flag for early termination
65    terminate: Arc<AtomicBool>,
66}
67
68impl ParallelCubeSolver {
69    /// Creates a new parallel cube solver.
70    pub fn new(config: CubeSolverConfig) -> Self {
71        Self {
72            config,
73            cubes_solved: Arc::new(AtomicUsize::new(0)),
74            sat_count: Arc::new(AtomicUsize::new(0)),
75            unsat_count: Arc::new(AtomicUsize::new(0)),
76            terminate: Arc::new(AtomicBool::new(false)),
77        }
78    }
79
80    /// Solves a set of cubes in parallel.
81    ///
82    /// Returns the overall result and individual cube results.
83    pub fn solve(
84        &mut self,
85        cubes: Vec<Cube>,
86        clauses: &[Clause],
87    ) -> (CubeResult, Vec<CubeSolveResult>) {
88        let start_time = Instant::now();
89
90        // Reset counters
91        self.cubes_solved.store(0, Ordering::Relaxed);
92        self.sat_count.store(0, Ordering::Relaxed);
93        self.unsat_count.store(0, Ordering::Relaxed);
94        self.terminate.store(false, Ordering::Relaxed);
95
96        if self.config.verbose {
97            println!(
98                "Solving {} cubes with {} workers...",
99                cubes.len(),
100                self.config.num_workers
101            );
102        }
103
104        // Solve cubes sequentially (use rayon for true parallelism)
105        let results: Vec<CubeSolveResult> = cubes
106            .iter()
107            .map(|cube| {
108                // Check for early termination
109                if self.terminate.load(Ordering::Relaxed) {
110                    return CubeSolveResult {
111                        cube: cube.clone(),
112                        result: CubeResult::Unknown,
113                        time: Duration::ZERO,
114                        conflicts: 0,
115                        decisions: 0,
116                    };
117                }
118
119                let result = self.solve_cube(cube, clauses);
120
121                // Update counters
122                self.cubes_solved.fetch_add(1, Ordering::Relaxed);
123                match result.result {
124                    CubeResult::Sat => {
125                        self.sat_count.fetch_add(1, Ordering::Relaxed);
126                        if self.config.early_termination {
127                            self.terminate.store(true, Ordering::Relaxed);
128                        }
129                    }
130                    CubeResult::Unsat => {
131                        self.unsat_count.fetch_add(1, Ordering::Relaxed);
132                    }
133                    CubeResult::Unknown => {}
134                }
135
136                result
137            })
138            .collect();
139
140        let total_time = start_time.elapsed();
141
142        // Determine overall result
143        let overall_result = if self.sat_count.load(Ordering::Relaxed) > 0 {
144            CubeResult::Sat
145        } else if self.unsat_count.load(Ordering::Relaxed) == cubes.len() {
146            CubeResult::Unsat
147        } else {
148            CubeResult::Unknown
149        };
150
151        if self.config.verbose {
152            println!(
153                "Cube solving complete: {:?} in {:.2}s",
154                overall_result,
155                total_time.as_secs_f64()
156            );
157            println!(
158                "  SAT: {}, UNSAT: {}, Unknown: {}",
159                self.sat_count.load(Ordering::Relaxed),
160                self.unsat_count.load(Ordering::Relaxed),
161                cubes.len() - self.cubes_solved.load(Ordering::Relaxed)
162            );
163        }
164
165        (overall_result, results)
166    }
167
168    /// Solves a single cube (simplified solver for demonstration).
169    ///
170    /// In a real implementation, this would invoke a full CDCL solver with the
171    /// cube literals as assumptions.
172    fn solve_cube(&self, cube: &Cube, _clauses: &[Clause]) -> CubeSolveResult {
173        let start = Instant::now();
174
175        // Simplified solving logic for demonstration
176        // In practice, this would call a real SAT solver with cube as assumptions
177
178        // For now, just simulate some work
179        let result = if cube.is_consistent() {
180            // Check for trivial conflicts
181            CubeResult::Unknown
182        } else {
183            CubeResult::Unsat
184        };
185
186        CubeSolveResult {
187            cube: cube.clone(),
188            result,
189            time: start.elapsed(),
190            conflicts: 0,
191            decisions: cube.len() as u64,
192        }
193    }
194
195    /// Returns statistics about the current solving session.
196    pub fn stats(&self) -> CubeSolverStats {
197        CubeSolverStats {
198            cubes_solved: self.cubes_solved.load(Ordering::Relaxed),
199            sat_count: self.sat_count.load(Ordering::Relaxed),
200            unsat_count: self.unsat_count.load(Ordering::Relaxed),
201            num_workers: self.config.num_workers,
202        }
203    }
204}
205
206/// Statistics for cube solving.
207#[derive(Debug, Clone)]
208pub struct CubeSolverStats {
209    /// Number of cubes solved
210    pub cubes_solved: usize,
211    /// Number of SAT cubes
212    pub sat_count: usize,
213    /// Number of UNSAT cubes
214    pub unsat_count: usize,
215    /// Number of workers used
216    pub num_workers: usize,
217}
218
219impl CubeSolverStats {
220    /// Displays the statistics.
221    pub fn display(&self) -> String {
222        format!(
223            "Cube Solver Statistics:\n\
224             - Cubes Solved: {}\n\
225             - SAT: {}\n\
226             - UNSAT: {}\n\
227             - Workers: {}",
228            self.cubes_solved, self.sat_count, self.unsat_count, self.num_workers
229        )
230    }
231}
232
233/// Cube-and-Conquer orchestrator that combines cube generation and solving.
234pub struct CubeAndConquer {
235    /// Cube solver configuration
236    solver_config: CubeSolverConfig,
237}
238
239impl CubeAndConquer {
240    /// Creates a new Cube-and-Conquer orchestrator.
241    pub fn new(solver_config: CubeSolverConfig) -> Self {
242        Self { solver_config }
243    }
244
245    /// Runs the full Cube-and-Conquer algorithm.
246    ///
247    /// Returns the overall result, cube generation stats, and solve results.
248    pub fn solve(
249        &mut self,
250        cubes: Vec<Cube>,
251        clauses: &[Clause],
252    ) -> (CubeResult, CubeStats, Vec<CubeSolveResult>) {
253        // Generate statistics for cubes
254        let cube_stats = CubeStats::from_cubes(&cubes);
255
256        // Solve cubes in parallel
257        let mut solver = ParallelCubeSolver::new(self.solver_config.clone());
258        let (result, solve_results) = solver.solve(cubes, clauses);
259
260        (result, cube_stats, solve_results)
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267    use crate::literal::{Lit, Var};
268
269    fn make_lit(var: usize, sign: bool) -> Lit {
270        let v = Var::new(var as u32);
271        if sign { Lit::pos(v) } else { Lit::neg(v) }
272    }
273
274    #[test]
275    fn test_cube_solver_config() {
276        let config = CubeSolverConfig::default();
277
278        assert!(config.cube_timeout.as_secs() > 0);
279        assert!(config.num_workers > 0);
280        assert!(config.early_termination);
281    }
282
283    #[test]
284    fn test_parallel_cube_solver_creation() {
285        let config = CubeSolverConfig::default();
286        let solver = ParallelCubeSolver::new(config);
287
288        assert_eq!(solver.cubes_solved.load(Ordering::Relaxed), 0);
289        assert_eq!(solver.sat_count.load(Ordering::Relaxed), 0);
290        assert_eq!(solver.unsat_count.load(Ordering::Relaxed), 0);
291    }
292
293    #[test]
294    fn test_solve_empty_cubes() {
295        let config = CubeSolverConfig {
296            verbose: false,
297            ..Default::default()
298        };
299        let mut solver = ParallelCubeSolver::new(config);
300
301        let cubes = vec![];
302        let clauses = vec![];
303
304        let (result, results) = solver.solve(cubes, &clauses);
305
306        assert_eq!(result, CubeResult::Unsat); // All (zero) cubes are UNSAT
307        assert_eq!(results.len(), 0);
308    }
309
310    #[test]
311    fn test_solve_single_cube() {
312        let config = CubeSolverConfig {
313            verbose: false,
314            early_termination: false,
315            ..Default::default()
316        };
317        let mut solver = ParallelCubeSolver::new(config);
318
319        let lit1 = make_lit(0, false);
320        let cube = Cube::new(vec![lit1]);
321        let cubes = vec![cube];
322        let clauses = vec![];
323
324        let (result, results) = solver.solve(cubes, &clauses);
325
326        assert_eq!(results.len(), 1);
327        assert!(matches!(result, CubeResult::Unknown | CubeResult::Unsat));
328    }
329
330    #[test]
331    fn test_solve_inconsistent_cube() {
332        let config = CubeSolverConfig {
333            verbose: false,
334            ..Default::default()
335        };
336        let mut solver = ParallelCubeSolver::new(config);
337
338        let lit1 = make_lit(0, false);
339        let lit2 = make_lit(0, true);
340        let cube = Cube::new(vec![lit1, lit2]);
341        let cubes = vec![cube];
342        let clauses = vec![];
343
344        let (result, results) = solver.solve(cubes, &clauses);
345
346        assert_eq!(results.len(), 1);
347        assert_eq!(results[0].result, CubeResult::Unsat);
348        assert_eq!(result, CubeResult::Unsat);
349    }
350
351    #[test]
352    fn test_solve_multiple_cubes() {
353        let config = CubeSolverConfig {
354            verbose: false,
355            early_termination: false,
356            num_workers: 2,
357            ..Default::default()
358        };
359        let mut solver = ParallelCubeSolver::new(config);
360
361        let cube1 = Cube::new(vec![make_lit(0, false)]);
362        let cube2 = Cube::new(vec![make_lit(1, false)]);
363        let cube3 = Cube::new(vec![make_lit(2, false)]);
364
365        let cubes = vec![cube1, cube2, cube3];
366        let clauses = vec![];
367
368        let (result, results) = solver.solve(cubes, &clauses);
369
370        assert_eq!(results.len(), 3);
371        assert!(matches!(result, CubeResult::Unknown | CubeResult::Unsat));
372    }
373
374    #[test]
375    fn test_cube_solver_stats() {
376        let config = CubeSolverConfig {
377            verbose: false,
378            ..Default::default()
379        };
380        let mut solver = ParallelCubeSolver::new(config);
381
382        let cube = Cube::new(vec![make_lit(0, false)]);
383        let cubes = vec![cube];
384        let clauses = vec![];
385
386        solver.solve(cubes, &clauses);
387
388        let stats = solver.stats();
389        assert_eq!(stats.cubes_solved, 1);
390        assert!(stats.num_workers > 0);
391    }
392
393    #[test]
394    fn test_cube_and_conquer() {
395        let config = CubeSolverConfig {
396            verbose: false,
397            ..Default::default()
398        };
399        let mut cac = CubeAndConquer::new(config);
400
401        let cube1 = Cube::new(vec![make_lit(0, false)]);
402        let cube2 = Cube::new(vec![make_lit(1, false)]);
403
404        let cubes = vec![cube1, cube2];
405        let clauses = vec![];
406
407        let (result, cube_stats, solve_results) = cac.solve(cubes, &clauses);
408
409        assert_eq!(cube_stats.total_cubes, 2);
410        assert_eq!(solve_results.len(), 2);
411        assert!(matches!(result, CubeResult::Unknown | CubeResult::Unsat));
412    }
413
414    #[test]
415    fn test_early_termination() {
416        let config = CubeSolverConfig {
417            verbose: false,
418            early_termination: true,
419            ..Default::default()
420        };
421        let solver = ParallelCubeSolver::new(config);
422
423        assert!(!solver.terminate.load(Ordering::Relaxed));
424    }
425
426    #[test]
427    fn test_solver_stats_display() {
428        let stats = CubeSolverStats {
429            cubes_solved: 10,
430            sat_count: 3,
431            unsat_count: 7,
432            num_workers: 4,
433        };
434
435        let display = stats.display();
436        assert!(display.contains("10"));
437        assert!(display.contains("3"));
438        assert!(display.contains("7"));
439        assert!(display.contains("4"));
440    }
441}