use std::{
fs,
io::{self, BufRead},
path::{Path, PathBuf},
process::{self, Command},
};
use crate::{
instances::{
fio::{self, SolverOutput},
Cnf,
},
types::{Assignment, Cl, Clause},
};
use super::Solve;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
enum InstanceArg {
First,
#[default]
Last,
}
#[derive(Debug, Clone)]
pub struct InputVia(InputViaInt);
#[derive(Debug, Clone)]
enum InputViaInt {
File(PathBuf, InstanceArg),
TempFile(InstanceArg),
Pipe,
}
impl InputVia {
#[must_use]
pub fn file_last<P: AsRef<Path>>(path: P) -> Self {
InputVia(InputViaInt::File(
path.as_ref().to_path_buf(),
InstanceArg::Last,
))
}
#[must_use]
pub fn file_first<P: AsRef<Path>>(path: P) -> Self {
InputVia(InputViaInt::File(
path.as_ref().to_path_buf(),
InstanceArg::First,
))
}
#[must_use]
pub fn tempfile_last() -> Self {
InputVia(InputViaInt::TempFile(InstanceArg::Last))
}
#[must_use]
pub fn tempfile_first() -> Self {
InputVia(InputViaInt::TempFile(InstanceArg::First))
}
#[must_use]
pub fn pipe() -> Self {
InputVia(InputViaInt::Pipe)
}
}
impl Default for InputVia {
fn default() -> Self {
InputVia(InputViaInt::TempFile(InstanceArg::default()))
}
}
#[derive(Debug, Clone, Default)]
pub struct OutputVia(OutputViaInt);
#[derive(Debug, Clone, Default)]
enum OutputViaInt {
File(PathBuf),
#[default]
Pipe,
}
impl OutputVia {
#[must_use]
pub fn file<P: AsRef<Path>>(path: P) -> Self {
OutputVia(OutputViaInt::File(path.as_ref().to_path_buf()))
}
#[must_use]
pub fn pipe() -> Self {
OutputVia(OutputViaInt::Pipe)
}
}
#[derive(Debug)]
pub struct Solver {
signature: &'static str,
state: SolverState,
}
#[derive(Debug)]
enum SolverState {
Pre(Box<SolverPre>),
Post(fio::SolverOutput),
}
#[derive(Debug)]
struct SolverPre {
cmd: Command,
input: InputVia,
output: OutputVia,
cnf: Cnf,
n_vars: u32,
}
impl Solver {
#[must_use]
pub fn new(cmd: Command, input: InputVia, output: OutputVia, signature: &'static str) -> Self {
Solver {
signature,
state: SolverState::Pre(Box::new(SolverPre {
cmd,
input,
output,
cnf: Cnf::default(),
n_vars: 0,
})),
}
}
#[must_use]
pub fn new_default(cmd: Command, signature: &'static str) -> Self {
Solver::new(cmd, InputVia::default(), OutputVia::default(), signature)
}
}
impl Solve for Solver {
fn signature(&self) -> &'static str {
self.signature
}
fn solve(&mut self) -> anyhow::Result<super::SolverResult> {
if let SolverState::Post(state) = &self.state {
anyhow::bail!(super::StateError {
required_state: super::SolverState::Input,
actual_state: match state {
fio::SolverOutput::Sat(_) => super::SolverState::Sat,
fio::SolverOutput::Unsat => super::SolverState::Unsat,
fio::SolverOutput::Unknown => super::SolverState::Unknown,
}
});
}
let SolverState::Pre(config) =
std::mem::replace(&mut self.state, SolverState::Post(SolverOutput::Unknown))
else {
unreachable!()
};
let post = call_external(*config)?;
let res = post.result();
self.state = SolverState::Post(post);
Ok(res)
}
fn lit_val(&self, lit: crate::types::Lit) -> anyhow::Result<crate::types::TernaryVal> {
match &self.state {
SolverState::Pre(_) => anyhow::bail!(super::StateError {
required_state: super::SolverState::Sat,
actual_state: super::SolverState::Input
}),
SolverState::Post(SolverOutput::Sat(sol)) => Ok(sol.lit_value(lit)),
SolverState::Post(state) => anyhow::bail!(super::StateError {
required_state: super::SolverState::Sat,
actual_state: state.state()
}),
}
}
fn add_clause_ref<C>(&mut self, clause: &C) -> anyhow::Result<()>
where
C: AsRef<Cl> + ?Sized,
{
self.add_clause(clause.as_ref().iter().copied().collect())
}
fn solution(&self, high_var: crate::types::Var) -> anyhow::Result<Assignment> {
match &self.state {
SolverState::Pre(_) => anyhow::bail!(super::StateError {
required_state: super::SolverState::Sat,
actual_state: super::SolverState::Input
}),
SolverState::Post(SolverOutput::Sat(sol)) => Ok(sol.clone().truncate(high_var)),
SolverState::Post(state) => anyhow::bail!(super::StateError {
required_state: super::SolverState::Sat,
actual_state: state.state()
}),
}
}
fn add_clause(&mut self, clause: Clause) -> anyhow::Result<()> {
let state = match &mut self.state {
SolverState::Pre(state) => state,
SolverState::Post(state) => anyhow::bail!(super::StateError {
required_state: super::SolverState::Input,
actual_state: state.state()
}),
};
for lit in &clause {
state.n_vars = std::cmp::max(lit.var().idx32() + 1, state.n_vars);
}
state.cnf.add_clause(clause);
Ok(())
}
}
impl Extend<Clause> for Solver {
fn extend<T: IntoIterator<Item = Clause>>(&mut self, iter: T) {
iter.into_iter().for_each(|cl| {
self.add_clause(cl).expect("Error adding clause in extend");
});
}
}
impl<'a> Extend<&'a Clause> for Solver {
fn extend<T: IntoIterator<Item = &'a Clause>>(&mut self, iter: T) {
iter.into_iter().for_each(|cl| {
self.add_clause_ref(cl)
.expect("Error adding clause in extend");
});
}
}
macro_rules! check_exit_code {
($status:expr) => {
match $status.code() {
Some(0 | 10 | 20) => (),
Some(x) => anyhow::bail!("solver returned unexpected code {x}"),
None => anyhow::bail!("solver process terminated by signal"),
};
};
}
fn call_external(config: SolverPre) -> anyhow::Result<SolverOutput> {
let mut temppath = None;
let mut cmd = match config.input.0 {
InputViaInt::File(in_path, argpos) => {
fio::dimacs::write_cnf_annotated(
&mut fio::open_compressed_uncompressed_write(&in_path)?,
&config.cnf,
config.n_vars,
)?;
construct_command_path(config.cmd, in_path, argpos)
}
InputViaInt::TempFile(argpos) => {
let mut writer = io::BufWriter::new(tempfile::NamedTempFile::new()?);
fio::dimacs::write_cnf_annotated(&mut writer, &config.cnf, config.n_vars)?;
let path = writer.into_inner()?.into_temp_path();
let cmd = construct_command_path(config.cmd, path.to_path_buf(), argpos);
temppath = Some(path);
cmd
}
InputViaInt::Pipe => {
let mut cmd = config.cmd;
cmd.stdin(process::Stdio::piped());
match config.output.0 {
OutputViaInt::File(path) => {
let mut child = cmd.stdout(fs::File::create(&path)?).spawn()?;
let mut stdin = io::BufWriter::new(child.stdin.take().unwrap());
fio::dimacs::write_cnf_annotated(&mut stdin, &config.cnf, config.n_vars)?;
drop(stdin);
let exit = child.wait()?;
let output = fio::parse_sat_solver_output(&mut io::BufReader::new(
fs::File::open(&path)?,
))?;
check_exit_code!(exit);
return Ok(output);
}
OutputViaInt::Pipe => {
let mut child = cmd.stdout(process::Stdio::piped()).spawn()?;
let mut stdin = io::BufWriter::new(child.stdin.take().unwrap());
let mut stdout = io::BufReader::new(child.stdout.take().unwrap());
let output_handle =
std::thread::spawn(move || -> anyhow::Result<fio::SolverOutput> {
let output = fio::parse_sat_solver_output(&mut stdout)?;
for _ in stdout.lines() {}
Ok(output)
});
fio::dimacs::write_cnf_annotated(&mut stdin, &config.cnf, config.n_vars)?;
drop(stdin);
let exit = child.wait()?;
let output = output_handle
.join()
.expect("could not join output parsing thread")?;
check_exit_code!(exit);
return Ok(output);
}
}
}
};
let output = match config.output.0 {
OutputViaInt::File(path) => {
cmd.stdout(fs::File::create(&path)?);
let exit = cmd.status()?;
let output =
fio::parse_sat_solver_output(&mut io::BufReader::new(fs::File::open(&path)?))?;
check_exit_code!(exit);
output
}
OutputViaInt::Pipe => {
let mut child = cmd.stdout(process::Stdio::piped()).spawn()?;
let mut stdout = io::BufReader::new(child.stdout.take().unwrap());
let output = fio::parse_sat_solver_output(&mut stdout)?;
for _ in stdout.lines() {}
check_exit_code!(child.wait()?);
output
}
};
if let Some(temppath) = temppath {
temppath.close()?;
}
Ok(output)
}
fn construct_command_path(mut cmd: Command, path: PathBuf, argpos: InstanceArg) -> Command {
match argpos {
InstanceArg::First => {
let mut new_cmd = Command::new(cmd.get_program());
new_cmd.arg(path).args(cmd.get_args());
for (key, val) in cmd.get_envs() {
if let Some(val) = val {
new_cmd.env(key, val);
} else {
new_cmd.env_remove(key);
}
}
if let Some(dir) = cmd.get_current_dir() {
new_cmd.current_dir(dir);
}
new_cmd
}
InstanceArg::Last => {
cmd.arg(path);
cmd
}
}
}