use crate::literal::{Lit, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use crate::solver::{Solver, SolverResult};
use core::fmt;
use std::fs::File;
use std::io::{self, BufRead, BufReader, Write};
use std::path::Path;
#[derive(Debug)]
pub enum DimacsError {
Io(io::Error),
Parse(String),
InvalidProblem,
LiteralOutOfRange(i32),
}
impl fmt::Display for DimacsError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "I/O error: {e}"),
Self::Parse(msg) => write!(f, "Parse error: {msg}"),
Self::InvalidProblem => write!(f, "Invalid problem line"),
Self::LiteralOutOfRange(lit) => write!(f, "Literal out of range: {lit}"),
}
}
}
impl core::error::Error for DimacsError {}
impl From<io::Error> for DimacsError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
pub struct DimacsParser {
num_vars: usize,
num_clauses: usize,
}
impl DimacsParser {
#[must_use]
pub fn new() -> Self {
Self {
num_vars: 0,
num_clauses: 0,
}
}
pub fn parse_file<P: AsRef<Path>>(
&mut self,
path: P,
solver: &mut Solver,
) -> Result<(), DimacsError> {
let file = File::open(path)?;
let reader = BufReader::new(file);
self.parse_reader(reader, solver)
}
pub fn parse_reader<R: BufRead>(
&mut self,
reader: R,
solver: &mut Solver,
) -> Result<(), DimacsError> {
let mut current_clause = Vec::new();
let mut _clauses_read = 0;
for line in reader.lines() {
let line = line?;
let line = line.trim();
if line.is_empty() {
continue;
}
if line.starts_with('c') {
continue;
}
if line.starts_with('p') {
self.parse_problem_line(line)?;
solver.ensure_vars(self.num_vars);
continue;
}
for token in line.split_whitespace() {
let lit_val: i32 = token
.parse()
.map_err(|_| DimacsError::Parse(format!("Invalid literal: {token}")))?;
if lit_val == 0 {
if !current_clause.is_empty() {
solver.add_clause(current_clause.iter().copied());
current_clause.clear();
_clauses_read += 1;
}
} else {
let lit = self.dimacs_to_lit(lit_val)?;
current_clause.push(lit);
}
}
}
if !current_clause.is_empty() {
solver.add_clause(current_clause.iter().copied());
_clauses_read += 1;
}
#[cfg(feature = "analyze-debug")]
if self.num_clauses > 0 && _clauses_read != self.num_clauses {
eprintln!(
"Warning: Expected {} clauses but read {}",
self.num_clauses, _clauses_read
);
}
Ok(())
}
fn parse_problem_line(&mut self, line: &str) -> Result<(), DimacsError> {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() != 4 || parts[0] != "p" || parts[1] != "cnf" {
return Err(DimacsError::InvalidProblem);
}
self.num_vars = parts[2]
.parse()
.map_err(|_| DimacsError::Parse("Invalid number of variables".to_string()))?;
self.num_clauses = parts[3]
.parse()
.map_err(|_| DimacsError::Parse("Invalid number of clauses".to_string()))?;
Ok(())
}
fn dimacs_to_lit(&self, dimacs_lit: i32) -> Result<Lit, DimacsError> {
if dimacs_lit == 0 {
return Err(DimacsError::Parse("Literal cannot be 0".to_string()));
}
let abs_val = dimacs_lit.unsigned_abs();
if abs_val as usize > self.num_vars {
return Err(DimacsError::LiteralOutOfRange(dimacs_lit));
}
let var = Var::new(abs_val - 1);
Ok(if dimacs_lit > 0 {
Lit::pos(var)
} else {
Lit::neg(var)
})
}
#[must_use]
pub const fn num_vars(&self) -> usize {
self.num_vars
}
#[must_use]
pub const fn num_clauses(&self) -> usize {
self.num_clauses
}
}
impl Default for DimacsParser {
fn default() -> Self {
Self::new()
}
}
pub struct DimacsWriter;
impl DimacsWriter {
pub fn write_cnf<P: AsRef<Path>>(
path: P,
num_vars: usize,
clauses: &[Vec<Lit>],
) -> Result<(), DimacsError> {
let mut file = File::create(path)?;
Self::write_cnf_to(&mut file, num_vars, clauses)
}
pub fn write_cnf_to<W: Write>(
writer: &mut W,
num_vars: usize,
clauses: &[Vec<Lit>],
) -> Result<(), DimacsError> {
writeln!(writer, "c DIMACS CNF")?;
writeln!(writer, "p cnf {} {}", num_vars, clauses.len())?;
for clause in clauses {
for &lit in clause {
write!(writer, "{} ", Self::lit_to_dimacs(lit))?;
}
writeln!(writer, "0")?;
}
Ok(())
}
pub fn write_model<P: AsRef<Path>>(
path: P,
solver: &Solver,
result: SolverResult,
) -> Result<(), DimacsError> {
let mut file = File::create(path)?;
Self::write_model_to(&mut file, solver, result)
}
pub fn write_model_to<W: Write>(
writer: &mut W,
solver: &Solver,
result: SolverResult,
) -> Result<(), DimacsError> {
use crate::literal::LBool;
match result {
SolverResult::Sat => {
writeln!(writer, "s SATISFIABLE")?;
write!(writer, "v ")?;
for i in 0..solver.num_vars() {
let var = Var::new(i as u32);
let value = solver.model_value(var);
let dimacs_lit = if value == LBool::True {
(i + 1) as i32
} else {
-((i + 1) as i32)
};
write!(writer, "{dimacs_lit} ")?;
}
writeln!(writer, "0")?;
}
SolverResult::Unsat => {
writeln!(writer, "s UNSATISFIABLE")?;
}
SolverResult::Unknown => {
writeln!(writer, "s UNKNOWN")?;
}
}
Ok(())
}
fn lit_to_dimacs(lit: Lit) -> i32 {
let var_index = lit.var().index() as i32;
if lit.is_pos() {
var_index + 1
} else {
-(var_index + 1)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_cnf() {
let cnf = "c Simple test\n\
p cnf 3 2\n\
1 -3 0\n\
2 3 -1 0\n";
let mut parser = DimacsParser::new();
let mut solver = Solver::new();
parser
.parse_reader(cnf.as_bytes(), &mut solver)
.expect("test operation should succeed");
assert_eq!(parser.num_vars(), 3);
assert_eq!(parser.num_clauses(), 2);
}
#[test]
fn test_parse_with_comments() {
let cnf = "c This is a comment\n\
c Another comment\n\
p cnf 2 1\n\
c Comment in the middle\n\
1 2 0\n";
let mut parser = DimacsParser::new();
let mut solver = Solver::new();
parser
.parse_reader(cnf.as_bytes(), &mut solver)
.expect("test operation should succeed");
assert_eq!(parser.num_vars(), 2);
assert_eq!(parser.num_clauses(), 1);
}
#[test]
fn test_write_cnf() {
let mut buffer = Vec::new();
let clauses = vec![
vec![Lit::pos(Var::new(0)), Lit::neg(Var::new(2))],
vec![
Lit::pos(Var::new(1)),
Lit::pos(Var::new(2)),
Lit::neg(Var::new(0)),
],
];
DimacsWriter::write_cnf_to(&mut buffer, 3, &clauses)
.expect("test operation should succeed");
let output = String::from_utf8(buffer).expect("test operation should succeed");
assert!(output.contains("p cnf 3 2"));
assert!(output.contains("1 -3 0"));
assert!(output.contains("2 3 -1 0"));
}
#[test]
fn test_roundtrip() {
let original_clauses = vec![
vec![Lit::pos(Var::new(0)), Lit::neg(Var::new(1))],
vec![Lit::pos(Var::new(1)), Lit::neg(Var::new(2))],
vec![Lit::pos(Var::new(2)), Lit::neg(Var::new(0))],
];
let mut buffer = Vec::new();
DimacsWriter::write_cnf_to(&mut buffer, 3, &original_clauses)
.expect("test operation should succeed");
let mut parser = DimacsParser::new();
let mut solver = Solver::new();
parser
.parse_reader(buffer.as_slice(), &mut solver)
.expect("test operation should succeed");
assert_eq!(parser.num_vars(), 3);
assert_eq!(parser.num_clauses(), 3);
}
}