use log::warn;
use tempfile::NamedTempFile;
use std::{
io::{Read, Write},
path::{Path, PathBuf},
time::Duration,
};
use crate::minisat_path;
use super::{SATResult, SATSolverError, SolverExecutor, execute_with_timeout};
pub struct MiniSatExecutor;
impl SolverExecutor for MiniSatExecutor {
fn get_solver_path(&self) -> PathBuf {
minisat_path()
}
fn execute(
&self,
dimacs: &str,
timeout: Option<Duration>,
) -> Result<SATResult, SATSolverError> {
let mut input_file = NamedTempFile::new()?;
input_file.write_all(dimacs.as_bytes())?;
input_file.flush()?;
let output_file = NamedTempFile::new()?;
let mut args = self.get_args(input_file.path());
args.push(output_file.path().to_string_lossy().to_string());
let (_, stderr) = execute_with_timeout(&self.get_solver_path(), &args, timeout)?;
let mut stdout = String::new();
output_file
.into_file()
.read_to_string(&mut stdout)
.map_err(|e| {
SATSolverError::OutputParseError(format!(
"Error reading minisat output file: {:?}",
e
))
})?;
self.parse_output(&stdout, &stderr)
}
fn get_args(&self, input_file: &Path) -> Vec<String> {
vec![
"-verb=0".to_string(), input_file.to_string_lossy().to_string(), ]
}
fn parse_output(&self, stdout: &str, stderr: &str) -> Result<SATResult, SATSolverError> {
let mut is_sat = None;
let mut assignment = Vec::new();
let mut found_assignment_line = false;
for line in stdout.lines() {
let line = line.trim();
match line {
"SAT" | "UNSAT" => {
if is_sat.is_some() {
return Err(SATSolverError::OutputParseError(
"Multiple satisfiability results found".to_string(),
));
}
is_sat = Some(line == "SAT");
}
_ if !line.is_empty()
&& !line.starts_with('c')
&& is_sat == Some(true)
&& !found_assignment_line =>
{
found_assignment_line = true;
let vars: Vec<i32> = line
.split_whitespace()
.filter_map(|s| s.parse().ok())
.filter(|&x| x != 0) .collect();
for var in vars {
assignment.push(var);
}
}
_ => continue,
}
}
for line in stderr.lines() {
if line.trim().starts_with("c ") {
continue;
}
warn!("{}", line.trim());
}
match is_sat {
Some(true) => Ok(SATResult::Satisfiable(assignment)),
Some(false) => Ok(SATResult::Unsatisfiable),
None => Err(SATSolverError::OutputParseError(
"No satisfiability result found".to_string(),
)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_output() {
let executor = MiniSatExecutor;
let stdout = "SAT\n1 -2 3 0\n";
let stderr = "";
let result = executor.parse_output(stdout, stderr).unwrap();
match result {
SATResult::Satisfiable(assignment) => {
assert_eq!(assignment.len(), 3);
}
_ => panic!("Expected satisfiable result"),
}
}
#[test]
fn test_parse_unsat_output() {
let executor = MiniSatExecutor;
let stdout = "UNSAT\n";
let stderr = "";
let result = executor.parse_output(stdout, stderr).unwrap();
assert!(matches!(result, SATResult::Unsatisfiable));
}
#[test]
fn test_parse_invalid_output() {
let executor = MiniSatExecutor;
let stdout = "UNSAT\nSAT\n";
let stderr = "";
assert!(executor.parse_output(stdout, stderr).is_err());
let stdout = "v 1 -2 3 0\n";
let stderr = "";
assert!(executor.parse_output(stdout, stderr).is_err());
let stdout = "c This is just a comment\n";
let stderr = "";
assert!(executor.parse_output(stdout, stderr).is_err());
}
}