use std::time::{Duration, Instant};
use async_trait::async_trait;
use varisat::{CnfFormula, ExtendFormula, Lit, Solver, Var};
use crate::error::{SatError, SatResult};
use crate::sat::{
Clause, Literal, SatModel, SatProblem, SatSolution, SatSolver, SolverConfig, SolverStats,
};
#[allow(dead_code)] pub struct VarisatSolver {
config: SolverConfig,
stats: SolverStats,
variable_count: u32,
incremental_clauses: Vec<Clause>,
}
impl VarisatSolver {
pub fn new(config: SolverConfig) -> SatResult<Self> {
if let Some(_seed) = config.random_seed {
tracing::warn!("Random seed configuration not supported by varisat backend");
}
Ok(Self {
config,
stats: SolverStats::default(),
variable_count: 0,
incremental_clauses: Vec::new(),
})
}
fn convert_literal(literal: &Literal) -> SatResult<Lit> {
if literal.variable == 0 {
return Err(SatError::encoding_failed(
"Variable numbers must be >= 1 (DIMACS standard)".to_string(),
));
}
let var = Var::from_index((literal.variable - 1) as usize);
Ok(Lit::from_var(var, !literal.negated)) }
fn convert_clause(clause: &Clause) -> SatResult<Vec<Lit>> {
clause.literals.iter().map(Self::convert_literal).collect()
}
#[allow(dead_code)] fn convert_model(&self, model: &[bool]) -> SatModel {
let mut sat_model = SatModel::new();
for (i, &value) in model.iter().enumerate() {
sat_model.assign((i + 1) as u32, value);
}
sat_model
}
fn ensure_variables(&mut self, max_variable: u32) -> SatResult<()> {
if max_variable > self.variable_count {
self.variable_count = max_variable;
}
Ok(())
}
fn update_stats(&mut self, solve_time: Duration) {
self.stats.solve_time = solve_time;
self.stats.decisions = self.variable_count as u64;
self.stats.conflicts = 0; self.stats.restarts = 0;
self.stats.memory_usage = 0;
self.stats.learned_clauses = 0;
}
}
#[async_trait]
impl SatSolver for VarisatSolver {
async fn solve(&mut self, problem: &SatProblem) -> SatResult<SatSolution> {
let start = Instant::now();
self.ensure_variables(problem.num_variables)?;
let mut formula = CnfFormula::new();
for clause in &problem.clauses {
let varisat_clause = Self::convert_clause(clause)?;
if varisat_clause.is_empty() {
self.update_stats(start.elapsed());
return Ok(SatSolution::Unsatisfiable);
}
formula.add_clause(&varisat_clause);
}
let assumptions: SatResult<Vec<Lit>> = problem
.assumptions
.iter()
.map(Self::convert_literal)
.collect();
let assumptions = assumptions?;
tracing::debug!(
"Solving SAT problem with {} variables, {} clauses",
problem.num_variables,
problem.clauses.len()
);
let _num_variables = self.variable_count;
let _variable_count = self.variable_count;
let res = tokio::task::spawn_blocking(move || {
let mut solver = Solver::new();
solver.add_formula(&formula);
if !assumptions.is_empty() {
solver.assume(&assumptions);
}
match solver.solve() {
Ok(true) => {
let model_lits = solver.model().unwrap_or_default();
Ok::<_, varisat::solver::SolverError>((true, Some(model_lits)))
}
Ok(false) => Ok((false, None)),
Err(e) => Err(e),
}
})
.await
.map_err(|e| SatError::solver_failure(format!("Solver task failed: {}", e)))?;
let solve_time = start.elapsed();
self.update_stats(solve_time);
match res {
Ok((true, Some(model_lits))) => {
let mut sat_model = SatModel::new();
for lit in model_lits {
let var = (lit.var().index() + 1) as u32; let value = lit.is_positive();
sat_model.assign(var, value);
}
tracing::debug!(
"SAT problem solved as satisfiable in {:?} with {} assigned variables",
solve_time,
sat_model.assignments.len()
);
Ok(SatSolution::Satisfiable(sat_model))
}
Ok((false, _)) => {
tracing::debug!("SAT problem is unsatisfiable (solved in {:?})", solve_time);
Ok(SatSolution::Unsatisfiable)
}
Ok((true, None)) => {
tracing::error!("Varisat returned SAT but no model available");
Err(SatError::solver_failure(
"Varisat returned SAT without a model".to_string(),
))
}
Err(e) => {
tracing::error!("Varisat solver error: {:?}", e);
Err(SatError::solver_failure(format!("Varisat error: {:?}", e)))
}
}
}
async fn solve_with_timeout(
&mut self,
problem: &SatProblem,
timeout: Duration,
) -> SatResult<SatSolution> {
match tokio::time::timeout(timeout, self.solve(problem)).await {
Ok(result) => result,
Err(_) => {
tracing::warn!("SAT solver timed out after {:?}", timeout);
Ok(SatSolution::Unknown)
}
}
}
async fn add_clauses(&mut self, clauses: &[Clause]) -> SatResult<()> {
let max_var = clauses
.iter()
.flat_map(|clause| clause.literals.iter())
.map(|lit| lit.variable)
.max()
.unwrap_or(0);
self.ensure_variables(max_var)?;
for clause in clauses {
for literal in &clause.literals {
if literal.variable == 0 {
return Err(SatError::encoding_failed(
"Variable numbers must be >= 1 (DIMACS standard)".to_string(),
));
}
}
}
self.incremental_clauses.extend_from_slice(clauses);
tracing::debug!(
"Added {} clauses to Varisat solver (total stored: {})",
clauses.len(),
self.incremental_clauses.len()
);
Ok(())
}
async fn solve_under_assumptions(&mut self, assumptions: &[Literal]) -> SatResult<SatSolution> {
let assumption_max_var = assumptions
.iter()
.map(|lit| lit.variable)
.max()
.unwrap_or(0);
let num_vars = self.variable_count.max(assumption_max_var);
let mut problem = SatProblem::new(num_vars);
for clause in &self.incremental_clauses {
problem.add_clause(clause.clone());
}
for &assumption in assumptions {
problem.add_assumption(assumption);
}
self.solve(&problem).await
}
fn get_stats(&self) -> SolverStats {
self.stats.clone()
}
fn reset(&mut self) {
self.stats = SolverStats::default();
self.variable_count = 0;
self.incremental_clauses.clear();
tracing::debug!("Reset Varisat solver");
}
fn name(&self) -> &'static str {
"Varisat"
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sat::{Clause, Literal, SatProblem};
#[tokio::test]
async fn test_varisat_simple_satisfiable() {
let config = SolverConfig::default();
let mut solver = VarisatSolver::new(config).unwrap();
let mut problem = SatProblem::new(2);
problem.add_clause(Clause::binary(Literal::positive(1), Literal::positive(2)));
problem.add_clause(Clause::binary(Literal::negative(1), Literal::positive(2)));
problem.add_clause(Clause::binary(Literal::positive(1), Literal::negative(2)));
let result = solver.solve(&problem).await.unwrap();
match result {
SatSolution::Satisfiable(model) => {
assert!(model.is_assigned(1));
assert!(model.is_assigned(2));
println!("Solution: x1={:?}, x2={:?}", model.get(1), model.get(2));
}
_ => panic!("Expected satisfiable solution"),
}
}
#[tokio::test]
async fn test_varisat_unsatisfiable() {
let config = SolverConfig::default();
let mut solver = VarisatSolver::new(config).unwrap();
let mut problem = SatProblem::new(1);
problem.add_clause(Clause::unit(Literal::positive(1)));
problem.add_clause(Clause::unit(Literal::negative(1)));
let result = solver.solve(&problem).await.unwrap();
assert_eq!(result, SatSolution::Unsatisfiable);
}
#[tokio::test]
async fn test_varisat_with_assumptions() {
let config = SolverConfig::default();
let mut solver = VarisatSolver::new(config).unwrap();
let clauses = vec![Clause::binary(Literal::positive(1), Literal::positive(2))];
solver.add_clauses(&clauses).await.unwrap();
let assumptions = vec![Literal::negative(1)];
let result = solver.solve_under_assumptions(&assumptions).await.unwrap();
match result {
SatSolution::Satisfiable(model) => {
assert_eq!(model.get(1), Some(false));
assert_eq!(model.get(2), Some(true));
}
_ => panic!("Expected satisfiable solution"),
}
}
#[tokio::test]
async fn test_varisat_performance() {
let config = SolverConfig::default();
let mut solver = VarisatSolver::new(config).unwrap();
let num_vars = 100;
let mut problem = SatProblem::new(num_vars);
for i in 1..=num_vars {
for j in (i + 1)..=num_vars {
problem.add_clause(Clause::binary(Literal::negative(i), Literal::negative(j)));
}
}
let mut at_least_one_literals = Vec::new();
for i in 1..=num_vars {
at_least_one_literals.push(Literal::positive(i));
}
problem.add_clause(Clause::new(at_least_one_literals));
let start = std::time::Instant::now();
let result = solver.solve(&problem).await.unwrap();
let solve_time = start.elapsed();
println!("Solved {}-variable problem in {:?}", num_vars, solve_time);
match result {
SatSolution::Satisfiable(model) => {
let true_count = model.true_variables().len();
assert_eq!(true_count, 1); println!("Solution has {} true variables", true_count);
}
_ => panic!("Expected satisfiable solution"),
}
assert!(solve_time < Duration::from_secs(1));
}
}