sat_solvers/solvers/
cadical.rs1use 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
34pub 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 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) .collect();
129
130 for var in vars {
131 assignment.push(var);
132 }
133 }
134 _ => continue,
135 }
136 }
137
138 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 let stdout = "s SATISFIABLE\ns UNSATISFIABLE\n";
195 let stderr = "";
196 assert!(executor.parse_output(stdout, stderr).is_err());
197
198 let stdout = "v 1 -2 3 0\n";
200 let stderr = "";
201 assert!(executor.parse_output(stdout, stderr).is_err());
202
203 let stdout = "c This is just a comment\n";
205 let stderr = "";
206 assert!(executor.parse_output(stdout, stderr).is_err());
207 }
208}