Skip to main content

sat_solvers/solvers/
cadical.rs

1//! CaDiCaL SAT solver executor.
2//!
3//! This module provides the [`CaDiCaLExecutor`] for running the CaDiCaL SAT solver.
4//! CaDiCaL is a state-of-the-art CDCL (Conflict-Driven Clause Learning) SAT solver
5//! developed by Armin Biere, winner of multiple SAT Competitions.
6//!
7//! # Example
8//!
9//! ```no_run
10//! use sat_solvers::solvers::cadical::CaDiCaLExecutor;
11//! use sat_solvers::solvers::SolverExecutor;
12//! use std::time::Duration;
13//!
14//! let executor = CaDiCaLExecutor;
15//! let dimacs = "p cnf 3 2\n1 -2 0\n2 3 0\n";
16//!
17//! let result = executor.execute(dimacs, Some(Duration::from_secs(30))).unwrap();
18//! println!("Result: {}", result);
19//! ```
20
21use log::warn;
22use tempfile::NamedTempFile;
23
24use std::{
25    io::Write,
26    path::{Path, PathBuf},
27    time::Duration,
28};
29
30use crate::cadical_path;
31
32use super::{SATResult, SATSolverError, SolverExecutor, execute_with_timeout};
33
34/// Executor for the CaDiCaL SAT solver.
35///
36/// CaDiCaL is a high-performance CDCL SAT solver known for its excellent
37/// performance on industrial SAT instances. It has won multiple tracks
38/// in SAT Competitions.
39///
40/// # Features
41///
42/// - State-of-the-art CDCL algorithm
43/// - Efficient clause learning and deletion strategies
44/// - Inprocessing techniques
45///
46/// # Example
47///
48/// ```no_run
49/// use sat_solvers::solvers::cadical::CaDiCaLExecutor;
50/// use sat_solvers::solvers::SolverExecutor;
51/// use sat_solvers::SATResult;
52/// use std::time::Duration;
53///
54/// let executor = CaDiCaLExecutor;
55///
56/// // Simple satisfiable formula: (x1 OR NOT x2) AND (x2 OR x3)
57/// let dimacs = "p cnf 3 2\n1 -2 0\n2 3 0\n";
58///
59/// match executor.execute(dimacs, Some(Duration::from_secs(60))) {
60///     Ok(SATResult::Satisfiable(assignment)) => {
61///         println!("SAT! Assignment: {:?}", assignment);
62///     }
63///     Ok(SATResult::Unsatisfiable) => println!("UNSAT"),
64///     Ok(SATResult::Unknown) => println!("Unknown"),
65///     Err(e) => eprintln!("Error: {}", e),
66/// }
67/// ```
68pub struct CaDiCaLExecutor;
69
70impl SolverExecutor for CaDiCaLExecutor {
71    fn get_solver_path(&self) -> PathBuf {
72        cadical_path()
73    }
74
75    fn execute(
76        &self,
77        dimacs: &str,
78        timeout: Option<Duration>,
79    ) -> Result<SATResult, SATSolverError> {
80        let mut temp_file = NamedTempFile::new()?;
81        temp_file.write_all(dimacs.as_bytes())?;
82        temp_file.flush()?;
83
84        let args = self.get_args(temp_file.path());
85        let (stdout, stderr) = execute_with_timeout(&self.get_solver_path(), &args, timeout)?;
86        self.parse_output(&stdout, &stderr)
87    }
88
89    fn get_args(&self, input_file: &Path) -> Vec<String> {
90        // Note: CaDiCaL prints witness by default, and uses "s SATISFIABLE"/"s UNSATISFIABLE" format
91        vec![input_file.to_string_lossy().to_string()]
92    }
93
94    fn parse_output(&self, stdout: &str, stderr: &str) -> Result<SATResult, SATSolverError> {
95        let mut is_sat = None;
96        let mut assignment = Vec::new();
97
98        for line in stdout.lines() {
99            let line = line.trim();
100
101            match line {
102                "s SATISFIABLE" | "s UNSATISFIABLE" => {
103                    if is_sat.is_some() {
104                        return Err(SATSolverError::OutputParseError(
105                            "Multiple satisfiability results found".to_string(),
106                        ));
107                    }
108
109                    is_sat = Some(line == "s SATISFIABLE");
110                }
111                _ if line.starts_with("v ") => {
112                    if let Some(false) = is_sat {
113                        return Err(SATSolverError::OutputParseError(
114                            "Variable assignment found before satisfiability result".to_string(),
115                        ));
116                    }
117
118                    if is_sat.is_none() {
119                        return Err(SATSolverError::OutputParseError(
120                            "Variable assignment found without satisfiability result".to_string(),
121                        ));
122                    }
123
124                    let vars: Vec<i32> = line[2..]
125                        .split_whitespace()
126                        .filter_map(|s| s.parse().ok())
127                        .filter(|&x| x != 0) // Remove terminating 0
128                        .collect();
129
130                    for var in vars {
131                        assignment.push(var);
132                    }
133                }
134                _ => continue,
135            }
136        }
137
138        // Process stderr for any warnings or errors
139        for line in stderr.lines() {
140            if line.trim().starts_with("c ") {
141                continue;
142            }
143
144            warn!("{}", line.trim());
145        }
146
147        match is_sat {
148            Some(true) => Ok(SATResult::Satisfiable(assignment)),
149            Some(false) => Ok(SATResult::Unsatisfiable),
150            None => Err(SATSolverError::OutputParseError(
151                "No satisfiability result found".to_string(),
152            )),
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_parse_output() {
163        let executor = CaDiCaLExecutor;
164        let stdout = "s SATISFIABLE\nv 1 -2 3 0\n";
165        let stderr = "";
166
167        let result = executor.parse_output(stdout, stderr).unwrap();
168        match result {
169            SATResult::Satisfiable(assignment) => {
170                assert_eq!(assignment.len(), 3);
171                assert!(assignment.contains(&1));
172                assert!(assignment.contains(&-2));
173                assert!(assignment.contains(&3));
174            }
175            _ => panic!("Expected satisfiable result"),
176        }
177    }
178
179    #[test]
180    fn test_parse_unsat_output() {
181        let executor = CaDiCaLExecutor;
182        let stdout = "s UNSATISFIABLE\n";
183        let stderr = "";
184
185        let result = executor.parse_output(stdout, stderr).unwrap();
186        assert!(matches!(result, SATResult::Unsatisfiable));
187    }
188
189    #[test]
190    fn test_parse_invalid_output() {
191        let executor = CaDiCaLExecutor;
192
193        // Test multiple satisfiability results
194        let stdout = "s SATISFIABLE\ns UNSATISFIABLE\n";
195        let stderr = "";
196        assert!(executor.parse_output(stdout, stderr).is_err());
197
198        // Test assignment without satisfiability result
199        let stdout = "v 1 -2 3 0\n";
200        let stderr = "";
201        assert!(executor.parse_output(stdout, stderr).is_err());
202
203        // Test no result
204        let stdout = "c This is just a comment\n";
205        let stderr = "";
206        assert!(executor.parse_output(stdout, stderr).is_err());
207    }
208}