sat_solvers/solvers/
minisat.rs1use log::warn;
22use tempfile::NamedTempFile;
23
24use std::{
25 io::{Read, Write},
26 path::{Path, PathBuf},
27 time::Duration,
28};
29
30use crate::minisat_path;
31
32use super::{SATResult, SATSolverError, SolverExecutor, execute_with_timeout};
33
34pub struct MiniSatExecutor;
68
69impl SolverExecutor for MiniSatExecutor {
70 fn get_solver_path(&self) -> PathBuf {
71 minisat_path()
72 }
73
74 fn execute(
75 &self,
76 dimacs: &str,
77 timeout: Option<Duration>,
78 ) -> Result<SATResult, SATSolverError> {
79 let mut input_file = NamedTempFile::new()?;
80 input_file.write_all(dimacs.as_bytes())?;
81 input_file.flush()?;
82
83 let output_file = NamedTempFile::new()?;
84
85 let mut args = self.get_args(input_file.path());
86 args.push(output_file.path().to_string_lossy().to_string());
87
88 let (_, stderr) = execute_with_timeout(&self.get_solver_path(), &args, timeout)?;
89 let mut stdout = String::new();
90 output_file
91 .into_file()
92 .read_to_string(&mut stdout)
93 .map_err(|e| {
94 SATSolverError::OutputParseError(format!(
95 "Error reading minisat output file: {:?}",
96 e
97 ))
98 })?;
99 self.parse_output(&stdout, &stderr)
100 }
101
102 fn get_args(&self, input_file: &Path) -> Vec<String> {
103 vec![
104 "-verb=0".to_string(), input_file.to_string_lossy().to_string(), ]
107 }
108
109 fn parse_output(&self, stdout: &str, stderr: &str) -> Result<SATResult, SATSolverError> {
110 let mut is_sat = None;
111 let mut assignment = Vec::new();
112 let mut found_assignment_line = false;
113
114 for line in stdout.lines() {
115 let line = line.trim();
116
117 match line {
118 "SAT" | "UNSAT" => {
119 if is_sat.is_some() {
120 return Err(SATSolverError::OutputParseError(
121 "Multiple satisfiability results found".to_string(),
122 ));
123 }
124 is_sat = Some(line == "SAT");
125 }
126 _ if !line.is_empty()
127 && !line.starts_with('c')
128 && is_sat == Some(true)
129 && !found_assignment_line =>
130 {
131 found_assignment_line = true;
134
135 let vars: Vec<i32> = line
136 .split_whitespace()
137 .filter_map(|s| s.parse().ok())
138 .filter(|&x| x != 0) .collect();
140
141 for var in vars {
142 assignment.push(var);
143 }
144 }
145 _ => continue,
146 }
147 }
148
149 for line in stderr.lines() {
151 if line.trim().starts_with("c ") {
152 continue;
153 }
154
155 warn!("{}", line.trim());
156 }
157
158 match is_sat {
159 Some(true) => Ok(SATResult::Satisfiable(assignment)),
160 Some(false) => Ok(SATResult::Unsatisfiable),
161 None => Err(SATSolverError::OutputParseError(
162 "No satisfiability result found".to_string(),
163 )),
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn test_parse_output() {
174 let executor = MiniSatExecutor;
175 let stdout = "SAT\n1 -2 3 0\n";
176 let stderr = "";
177
178 let result = executor.parse_output(stdout, stderr).unwrap();
179 match result {
180 SATResult::Satisfiable(assignment) => {
181 assert_eq!(assignment.len(), 3);
182 }
183 _ => panic!("Expected satisfiable result"),
184 }
185 }
186
187 #[test]
188 fn test_parse_unsat_output() {
189 let executor = MiniSatExecutor;
190 let stdout = "UNSAT\n";
191 let stderr = "";
192
193 let result = executor.parse_output(stdout, stderr).unwrap();
194 assert!(matches!(result, SATResult::Unsatisfiable));
195 }
196
197 #[test]
198 fn test_parse_invalid_output() {
199 let executor = MiniSatExecutor;
200
201 let stdout = "UNSAT\nSAT\n";
203 let stderr = "";
204 assert!(executor.parse_output(stdout, stderr).is_err());
205
206 let stdout = "v 1 -2 3 0\n";
208 let stderr = "";
209 assert!(executor.parse_output(stdout, stderr).is_err());
210
211 let stdout = "c This is just a comment\n";
213 let stderr = "";
214 assert!(executor.parse_output(stdout, stderr).is_err());
215 }
216}