use std::{fmt, io, path::PathBuf};
use anyhow::Context;
use clap::{Parser, ValueEnum};
use itertools::Itertools;
use rustsat::{
instances::{
fio::{self, opb::Options as OpbOptions},
BasicVarManager, ManageVars, MultiOptInstance, Objective, ReindexingVarManager,
},
types::{constraints::PbConstraint, Var},
};
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
in_path: Option<PathBuf>,
out_path: Option<PathBuf>,
#[arg(long, value_enum, default_value_t = InputFormat::Infer)]
input_format: InputFormat,
#[arg(long, value_enum, default_value_t = OutputFormat::Fgt)]
output_format: OutputFormat,
#[arg(long, default_value_t = 1)]
first_var_idx: u32,
#[arg(long, value_enum, default_value_t = UnusedVars::AssignZero)]
unused_vars: UnusedVars,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum InputFormat {
Infer,
Mcnf,
Opb,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum OutputFormat {
Bauss,
Fgt,
}
#[derive(Copy, Clone, PartialEq, Eq, ValueEnum)]
enum UnusedVars {
Nothing,
Remove,
AssignZero,
}
fn main() -> anyhow::Result<()> {
let args = Args::parse();
let opb_opts = OpbOptions {
first_var_idx: args.first_var_idx,
..OpbOptions::default()
};
let inst = parse_instance(args.in_path, args.input_format, opb_opts)
.context("failed to parse input instance")?;
let inst = match args.unused_vars {
UnusedVars::Nothing => inst,
UnusedVars::Remove => {
let (mut constr, objs) = inst
.reindex_ordered(ReindexingVarManager::default())
.decompose();
let next_free = constr.new_var();
let (constr, _) =
constr.change_var_manager(|_| BasicVarManager::from_next_free(next_free));
MultiOptInstance::compose(constr, objs)
}
UnusedVars::AssignZero => {
let varset = inst.var_set();
let n_vars = inst.constraints_ref().n_vars();
let mut inst = inst;
for idx in 0..n_vars {
let var = Var::new(idx);
if !varset.contains(&var) {
inst.constraints_mut().add_unit(var.neg_lit());
}
}
inst
}
};
if let Some(path) = args.out_path {
write_instance(
inst,
args.output_format,
&mut fio::open_compressed_uncompressed_write(path)?,
)?;
} else {
write_instance(
inst,
args.output_format,
&mut io::BufWriter::new(io::stdout()),
)?;
}
Ok(())
}
macro_rules! is_one_of {
($a:expr, $($b:expr),*) => {
$( $a == $b || )* false
}
}
fn parse_instance(
path: Option<PathBuf>,
file_format: InputFormat,
opb_opts: OpbOptions,
) -> anyhow::Result<MultiOptInstance> {
match file_format {
InputFormat::Infer => {
let path = if let Some(path) = path {
path
} else {
anyhow::bail!("cannot infer file format from stdin");
};
if let Some(ext) = path.extension() {
let path_without_compr = path.with_extension("");
let ext = if is_one_of!(ext, "gz", "bz2", "xz") {
match path_without_compr.extension() {
Some(ext) => ext,
None => anyhow::bail!("no file extension after compression extension"),
}
} else {
ext
};
if is_one_of!(ext, "mcnf") {
MultiOptInstance::from_dimacs_path(path)
} else if is_one_of!(ext, "opb", "mopb", "pbmo") {
MultiOptInstance::from_opb_path(path, opb_opts)
} else {
anyhow::bail!("unknown file extension")
}
} else {
anyhow::bail!("no file extension")
}
}
InputFormat::Mcnf => {
if let Some(path) = path {
MultiOptInstance::from_dimacs_path(path)
} else {
MultiOptInstance::from_dimacs(&mut io::BufReader::new(io::stdin()))
}
}
InputFormat::Opb => {
if let Some(path) = path {
MultiOptInstance::from_opb_path(path, opb_opts)
} else {
MultiOptInstance::from_opb(&mut io::BufReader::new(io::stdin()), opb_opts)
}
}
}
}
fn write_instance<W: io::Write>(
inst: MultiOptInstance,
format: OutputFormat,
writer: &mut W,
) -> io::Result<()> {
let (constr, mut objs) = inst.decompose();
let (mut pbs, mut vm) = constr.into_pbs();
let omv = objs.iter().fold(Var::new(0), |v, o| {
if let Some(mv) = o.max_var() {
return std::cmp::max(v, mv);
}
v
});
vm.increase_next_free(omv + 1);
pbs.extend(
objs.iter_mut()
.flat_map(|o| o.convert_to_soft_lits(&mut vm))
.map(PbConstraint::from),
);
pbs.push(PbConstraint::new_lb([(vm.new_lit(), 1)], 1));
let non_zero_constrs: usize = pbs.iter().map(PbConstraint::len).sum();
let non_zero_objs: usize = objs.iter().map(Objective::n_softs).sum();
match format {
OutputFormat::Bauss => {
writeln!(
writer,
"{} {} {} {non_zero_constrs} {non_zero_objs}",
vm.n_used(),
pbs.len(),
objs.len(),
)?;
}
OutputFormat::Fgt => {
writeln!(writer, "{} {} {}", vm.n_used(), pbs.len(), objs.len(),)?;
}
}
writeln!(writer)?;
if matches!(format, OutputFormat::Fgt) {
writeln!(writer, "{}", (0..objs.len()).map(|_| "minsum").format(" "))?;
writeln!(writer)?;
}
for obj in objs {
write_objective(obj, vm.n_used(), writer)?;
}
writeln!(writer)?;
let bounds = pbs
.into_iter()
.map(|c| write_constraint(c, vm.n_used(), writer))
.collect::<io::Result<Vec<_>>>()?;
writeln!(writer)?;
for (op, bound) in bounds {
writeln!(writer, "{op} {bound}")?;
}
if matches!(format, OutputFormat::Fgt) {
writeln!(writer)?;
writeln!(writer, "{}", (0..vm.n_used()).map(|_| 0).format(" "))?;
writeln!(writer, "{}", (0..vm.n_used()).map(|_| 1).format(" "))?;
}
Ok(())
}
fn write_objective<W: io::Write>(obj: Objective, n_vars: u32, writer: &mut W) -> io::Result<()> {
let mut coefs = vec![0; n_vars as usize];
for (lit, coef) in obj.iter_soft_lits().unwrap() {
let mut coef =
isize::try_from(coef).expect("cannot handle coefficients larger than `isize::MAX`");
if lit.is_neg() {
*coefs.last_mut().unwrap() += coef;
coef = -coef;
}
coefs[lit.var().idx()] += coef;
}
*coefs.last_mut().unwrap() += obj.offset();
writeln!(writer, "{}", coefs.into_iter().format(" "))
}
fn write_constraint<W: io::Write>(
constr: PbConstraint,
n_vars: u32,
writer: &mut W,
) -> io::Result<(Operator, isize)> {
let mut coefs = vec![0; n_vars as usize];
let (lits, op, mut bound) = match constr {
PbConstraint::Ub(constr) => {
let (lits, b) = constr.decompose();
(lits, Operator::Lte, b)
}
PbConstraint::Lb(constr) => {
let (lits, b) = constr.decompose();
(lits, Operator::Gte, b)
}
PbConstraint::Eq(constr) => {
let (lits, b) = constr.decompose();
(lits, Operator::Eq, b)
}
};
for (lit, coef) in lits {
let mut coef =
isize::try_from(coef).expect("cannot handle coefficients larger than `isize::MAX`");
if lit.is_neg() {
bound -= coef;
coef = -coef;
}
coefs[lit.var().idx()] += coef;
}
writeln!(writer, "{}", coefs.into_iter().format(" "))?;
Ok((op, bound))
}
#[derive(Clone, Copy)]
enum Operator {
Gte,
Lte,
Eq,
}
impl fmt::Display for Operator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Operator::Gte => write!(f, "0"),
Operator::Lte => write!(f, "1"),
Operator::Eq => write!(f, "2"),
}
}
}