pub mod bool_linear;
pub mod cardinality;
pub mod cardinality_one;
pub(crate) mod helpers;
mod integer;
pub mod propositional_logic;
pub mod solver;
mod sorted;
#[cfg(any(feature = "tracing", test))]
pub mod trace;
use std::{
clone::Clone,
cmp::{Eq, Ordering},
error::Error,
fmt::{self, Display},
fs::File,
hash::Hash,
io::{self, BufRead, BufReader, Write},
iter::{repeat_n, FusedIterator},
num::NonZeroI32,
ops::{Add, BitAnd, BitOr, BitXor, Bound, Mul, Not, RangeBounds, RangeInclusive},
path::Path,
slice,
};
use itertools::{traits::HomogeneousTuple, Itertools};
pub use crate::helpers::AsDynClauseDatabase;
use crate::{
bool_linear::BoolLinExp, helpers::subscript_number, propositional_logic::Formula,
solver::VarFactory,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[expect(
variant_size_differences,
reason = "bool is 1 byte, but Lit will always require more"
)]
pub enum BoolVal {
Const(bool),
Lit(Lit),
}
pub trait Checker {
fn check<F: Valuation + ?Sized>(&self, value: &F) -> Result<(), Unsatisfiable>;
}
pub trait ClauseDatabase {
fn add_clause_from_slice(&mut self, clause: &[Lit]) -> Result;
fn new_var_range(&mut self, len: usize) -> VarRange;
}
pub trait ClauseDatabaseTools: ClauseDatabase {
fn add_clause<Iter>(&mut self, clause: Iter) -> Result
where
Iter: IntoIterator,
Iter::Item: Into<BoolVal>,
{
let result: Result<Vec<_>, ()> = clause
.into_iter()
.filter_map(|v| match v.into() {
BoolVal::Const(false) => None, BoolVal::Const(true) => Some(Err(())), BoolVal::Lit(lit) => Some(Ok(lit)), })
.collect();
match result {
Ok(clause) => {
let result = self.add_clause_from_slice(&clause);
#[cfg(any(feature = "tracing", test))]
{
tracing::info!(clause = ?&clause, fail = result.is_err(), "emit clause");
}
result
}
Err(()) => Ok(()),
}
}
fn contradiction(&mut self) -> Result {
let err = self.add_clause_from_slice(&[]);
debug_assert_eq!(err, Err(Unsatisfiable));
err
}
fn encode<C, E>(&mut self, constraint: &C, encoder: &E) -> Result
where
C: ?Sized,
E: Encoder<Self, C> + ?Sized,
{
encoder.encode(self, constraint)
}
fn encode_implied<C, E>(&mut self, conditions: &[Lit], constraint: &C, encoder: &E) -> Result
where
C: ?Sized,
E: Encoder<Self, C> + ?Sized + for<'a> Encoder<dyn ClauseDatabase + 'a, C>,
{
encoder.encode_implied(self, conditions, constraint)
}
fn new_lit(&mut self) -> Lit {
self.new_var().into()
}
fn new_lits<T>(&mut self) -> T
where
T: HomogeneousTuple<Item = Lit>,
{
let range = self.new_var_range(T::num_items());
range.map(Lit::from).collect_tuple().unwrap()
}
#[cfg(any(feature = "tracing", test))]
#[inline]
fn new_named_lit(&mut self, name: &str) -> Lit {
self.new_named_var(name).into()
}
#[cfg(any(feature = "tracing", test))]
#[inline]
fn new_named_var(&mut self, name: &str) -> Var {
let var = self.new_var();
tracing::info!(var = ?i32::from(var), label = name, "new variable");
var
}
fn new_var(&mut self) -> Var {
let mut range = self.new_var_range(1);
debug_assert_eq!(range.len(), 1);
range.next().unwrap()
}
fn new_vars<T>(&mut self) -> T
where
T: HomogeneousTuple<Item = Var>,
{
let range = self.new_var_range(T::num_items());
range.collect_tuple().unwrap()
}
}
#[derive(Clone, Debug, Default)]
pub struct Cnf {
nvar: VarFactory,
lits: Vec<Lit>,
size: Vec<usize>,
}
#[derive(Debug, Clone)]
struct CnfIterator<'a> {
lits: &'a Vec<Lit>,
size: slice::Iter<'a, usize>,
index: usize,
}
pub(crate) type Coeff = i64;
enum Dimacs {
Cnf(Cnf),
Wcnf(Wcnf),
}
pub trait Encoder<Db: ClauseDatabase + ?Sized, Constraint: ?Sized> {
fn encode(&self, db: &mut Db, con: &Constraint) -> Result;
fn encode_implied(&self, db: &mut Db, conditions: &[Lit], con: &Constraint) -> Result
where
Self: for<'a> Encoder<dyn ClauseDatabase + 'a, Constraint>,
{
if conditions.is_empty() {
return self.encode(db, con);
}
struct ClauseBuffer<'a, Db: ClauseDatabase + ?Sized> {
db: &'a mut Db,
lits: Vec<Lit>,
size: Vec<usize>,
}
impl<Db: ClauseDatabase + ?Sized> ClauseDatabase for ClauseBuffer<'_, Db> {
fn add_clause_from_slice(&mut self, clause: &[Lit]) -> Result {
let start = self.lits.len();
self.lits.extend_from_slice(clause);
let len = self.lits.len() - start;
self.size.push(len);
if len == 0 {
Err(Unsatisfiable)
} else {
Ok(())
}
}
fn new_var_range(&mut self, len: usize) -> VarRange {
self.db.new_var_range(len)
}
}
let (result, lits, sizes) = {
let mut cdb = ClauseBuffer {
db,
lits: Vec::new(),
size: Vec::new(),
};
let result = {
let cdb_dyn: &mut (dyn ClauseDatabase + '_) = &mut cdb;
self.encode(cdb_dyn, con)
};
(result, cdb.lits, cdb.size)
};
let conditions: Vec<_> = conditions.iter().map(|&l| !l).collect();
let (lits, sizes) = match result {
Ok(()) => (lits, sizes),
Err(Unsatisfiable) => return db.add_clause_from_slice(&conditions),
};
let mut index = 0;
let mut clause = Vec::with_capacity(conditions.len());
for size in sizes {
clause.clear();
clause.extend_from_slice(&conditions);
clause.extend_from_slice(&lits[index..index + size]);
db.add_clause_from_slice(&clause)?;
index += size;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum IntEncoding<'a> {
Direct {
first: Coeff,
vals: &'a [Lit],
},
Order {
first: Coeff,
vals: &'a [Lit],
},
Log {
signed: bool,
bits: &'a [Lit],
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Lit(NonZeroI32);
type Result<T = (), E = Unsatisfiable> = std::result::Result<T, E>;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd)]
pub struct Unsatisfiable;
pub trait Valuation {
fn value(&self, lit: Lit) -> bool;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Var(pub(crate) NonZeroI32);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct VarRange {
start: Var,
end: Var,
}
#[derive(Clone, Debug, Default)]
pub struct Wcnf {
cnf: Cnf,
weights: Vec<Option<Coeff>>,
}
fn parse_dimacs_file<const WEIGHTED: bool>(path: &Path) -> Result<Dimacs, io::Error> {
let file = File::open(path)?;
let mut had_header = false;
let mut wcnf = Wcnf::default();
let mut cl: Vec<Lit> = Vec::new();
let mut top: Option<Coeff> = None;
for line in BufReader::new(file).lines() {
match line {
Ok(line) if line.is_empty() || line.starts_with('c') => (),
Ok(line) if had_header => {
for seg in line.split(' ') {
if WEIGHTED {
if let Ok(weight) = seg.parse::<Coeff>() {
wcnf.weights.push(match weight.cmp(&top.unwrap()) {
Ordering::Less => Some(weight),
Ordering::Equal => None,
Ordering::Greater => panic!(
"Found weight weight {weight} greater than top {top:?} from header"
),
});
} else {
panic!("Cannot parse line {line}");
}
}
if let Ok(lit) = seg.parse::<i32>() {
if lit == 0 {
wcnf.add_clause(cl.drain(..)).unwrap();
} else {
cl.push(Lit(NonZeroI32::new(lit).unwrap()));
}
}
}
}
Ok(line) => {
let vec: Vec<&str> = line.split_whitespace().collect();
if !WEIGHTED && (vec.len() != 4 || vec[0..2] != ["p", "cnf"]) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"expected DIMACS CNF header formatted \"p cnf {variables} {clauses}\"",
));
} else if WEIGHTED && (vec.len() != 4 || vec[0..2] != ["p", "wcnf"]) {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"expected DIMACS WCNF header formatted \"p wcnf {variables} {clauses} {top}\"",
));
}
wcnf.cnf.nvar = VarFactory {
next_var: Some(Var(vec[2].parse::<NonZeroI32>().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"unable to parse number of variables",
)
})?)),
};
let num_clauses: usize = vec[3].parse().map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidInput,
"unable to parse number of clauses",
)
})?;
wcnf.cnf.lits.reserve(num_clauses);
wcnf.cnf.size.reserve(num_clauses);
if WEIGHTED {
top = Some(vec[4].parse().map_err(|_| {
io::Error::new(io::ErrorKind::InvalidInput, "unable to parse top weight")
})?);
}
had_header = true;
}
Err(e) => return Err(e),
}
}
if WEIGHTED {
Ok(Dimacs::Wcnf(wcnf))
} else {
Ok(Dimacs::Cnf(wcnf.cnf))
}
}
impl BitAnd<BoolVal> for BoolVal {
type Output = Formula<BoolVal>;
fn bitand(self, rhs: BoolVal) -> Self::Output {
match (self, rhs) {
(BoolVal::Const(a), BoolVal::Const(b)) => Formula::Atom((a & b).into()),
(BoolVal::Lit(a), BoolVal::Lit(b)) => (a & b).into(),
(BoolVal::Lit(a), BoolVal::Const(b)) | (BoolVal::Const(b), BoolVal::Lit(a)) => {
Formula::Atom(a & b)
}
}
}
}
impl BitAnd<Lit> for BoolVal {
type Output = Formula<BoolVal>;
fn bitand(self, rhs: Lit) -> Self::Output {
self & BoolVal::Lit(rhs)
}
}
impl BitAnd<bool> for BoolVal {
type Output = BoolVal;
fn bitand(self, rhs: bool) -> Self::Output {
match self {
BoolVal::Const(b) => (b & rhs).into(),
BoolVal::Lit(l) if rhs => (l).into(),
BoolVal::Lit(_) => false.into(),
}
}
}
impl BitOr<BoolVal> for BoolVal {
type Output = Formula<BoolVal>;
fn bitor(self, rhs: BoolVal) -> Self::Output {
match (self, rhs) {
(BoolVal::Const(a), BoolVal::Const(b)) => Formula::Atom((a | b).into()),
(BoolVal::Lit(a), BoolVal::Lit(b)) => (a | b).into(),
(BoolVal::Lit(a), BoolVal::Const(b)) | (BoolVal::Const(b), BoolVal::Lit(a)) => {
Formula::Atom(a | b)
}
}
}
}
impl BitOr<Lit> for BoolVal {
type Output = Formula<BoolVal>;
fn bitor(self, rhs: Lit) -> Self::Output {
self | BoolVal::Lit(rhs)
}
}
impl BitOr<bool> for BoolVal {
type Output = BoolVal;
fn bitor(self, rhs: bool) -> Self::Output {
match self {
BoolVal::Const(b) => (b | rhs).into(),
BoolVal::Lit(_) if rhs => true.into(),
BoolVal::Lit(_) => self,
}
}
}
impl BitXor<BoolVal> for BoolVal {
type Output = Formula<BoolVal>;
fn bitxor(self, rhs: BoolVal) -> Self::Output {
match (self, rhs) {
(BoolVal::Const(a), BoolVal::Const(b)) => Formula::Atom((a ^ b).into()),
(BoolVal::Lit(a), BoolVal::Lit(b)) => {
Formula::Xor(vec![Formula::Atom(a.into()), Formula::Atom(b.into())])
}
(BoolVal::Lit(a), BoolVal::Const(b)) | (BoolVal::Const(b), BoolVal::Lit(a)) => {
Formula::Atom((a ^ b).into())
}
}
}
}
impl BitXor<Lit> for BoolVal {
type Output = Formula<BoolVal>;
fn bitxor(self, rhs: Lit) -> Self::Output {
self ^ BoolVal::Lit(rhs)
}
}
impl BitXor<bool> for BoolVal {
type Output = BoolVal;
fn bitxor(self, rhs: bool) -> Self::Output {
if rhs {
!self
} else {
self
}
}
}
impl Display for BoolVal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
BoolVal::Const(b) => write!(f, "{b}"),
BoolVal::Lit(l) => write!(f, "{l}"),
}
}
}
impl From<Lit> for BoolVal {
fn from(value: Lit) -> Self {
BoolVal::Lit(value)
}
}
impl From<Var> for BoolVal {
fn from(value: Var) -> Self {
BoolVal::Lit(value.into())
}
}
impl From<bool> for BoolVal {
fn from(value: bool) -> Self {
BoolVal::Const(value)
}
}
impl Not for BoolVal {
type Output = BoolVal;
fn not(self) -> Self::Output {
match self {
BoolVal::Lit(l) => (!l).into(),
BoolVal::Const(b) => (!b).into(),
}
}
}
impl Cnf {
pub fn from_file(path: &Path) -> Result<Self, io::Error> {
match parse_dimacs_file::<false>(path)? {
Dimacs::Cnf(cnf) => Ok(cnf),
_ => unreachable!(),
}
}
#[cfg(test)]
pub(crate) fn get_variables(&self) -> VarRange {
VarRange::new(
Var(NonZeroI32::new(1).unwrap()),
self.nvar.next_var.unwrap().prev_var().unwrap(),
)
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &[Lit]> + '_ {
CnfIterator {
lits: &self.lits,
size: self.size.iter(),
index: 0,
}
}
pub fn literals(&self) -> usize {
self.size.iter().sum()
}
pub fn num_clauses(&self) -> usize {
self.size.len()
}
pub fn num_vars(&self) -> usize {
self.nvar.num_emitted_vars()
}
pub fn to_file(&self, path: &Path, comment: Option<&str>) -> Result<(), io::Error> {
let mut file = File::create(path)?;
if let Some(comment) = comment {
for line in comment.lines() {
writeln!(file, "c {line}")?;
}
}
write!(file, "{self}")
}
pub fn variables(&self) -> VarRange {
self.nvar.emitted_vars()
}
}
impl ClauseDatabase for Cnf {
fn add_clause_from_slice(&mut self, clause: &[Lit]) -> Result {
let size = self.lits.len();
self.lits.extend(clause);
let len = self.lits.len() - size;
self.size.push(len);
if len == 0 {
Err(Unsatisfiable)
} else {
Ok(())
}
}
fn new_var_range(&mut self, len: usize) -> VarRange {
self.nvar.next_var_range(len)
}
}
impl Display for Cnf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let num_var = &self.num_vars();
let num_clauses = self.size.len();
writeln!(f, "p cnf {num_var} {num_clauses}")?;
let mut start = 0;
for size in self.size.iter() {
let cl = self.lits.iter().skip(start).take(*size);
for &lit in cl {
write!(f, "{} ", i32::from(lit))?;
}
writeln!(f, "0")?;
start += size;
}
Ok(())
}
}
impl ExactSizeIterator for CnfIterator<'_> {}
impl<'a> Iterator for CnfIterator<'a> {
type Item = &'a [Lit];
fn count(self) -> usize {
self.size.count()
}
fn next(&mut self) -> Option<Self::Item> {
if let Some(size) = self.size.next() {
let start = self.index;
self.index += size;
Some(&self.lits[start..self.index])
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.size.size_hint()
}
}
impl Add<Lit> for Coeff {
type Output = BoolLinExp;
fn add(self, rhs: Lit) -> Self::Output {
rhs + self
}
}
impl Mul<Lit> for Coeff {
type Output = BoolLinExp;
fn mul(self, rhs: Lit) -> Self::Output {
rhs * self
}
}
impl<Db: ClauseDatabase + ?Sized> ClauseDatabaseTools for Db {}
impl<F: Fn(Lit) -> bool> Valuation for F {
fn value(&self, lit: Lit) -> bool {
self(lit)
}
}
impl Lit {
pub fn from_raw(value: NonZeroI32) -> Lit {
Lit(value)
}
pub fn is_negated(&self) -> bool {
self.0.is_negative()
}
pub fn var(&self) -> Var {
Var(self.0.abs())
}
}
impl Add for Lit {
type Output = BoolLinExp;
fn add(self, rhs: Self) -> Self::Output {
BoolLinExp::from_terms(&[(self, 1), (rhs, 1)])
}
}
impl Add<Coeff> for Lit {
type Output = BoolLinExp;
fn add(self, rhs: Coeff) -> Self::Output {
BoolLinExp::from_terms(&[(self, 1)]) + rhs
}
}
impl BitAnd<BoolVal> for Lit {
type Output = Formula<BoolVal>;
fn bitand(self, rhs: BoolVal) -> Self::Output {
rhs & self
}
}
impl BitAnd<Lit> for Lit {
type Output = Formula<Lit>;
fn bitand(self, rhs: Lit) -> Self::Output {
Formula::And(vec![Formula::Atom(self), Formula::Atom(rhs)])
}
}
impl BitAnd<bool> for Lit {
type Output = BoolVal;
fn bitand(self, rhs: bool) -> Self::Output {
if rhs {
self.into()
} else {
false.into()
}
}
}
impl BitOr<BoolVal> for Lit {
type Output = Formula<BoolVal>;
fn bitor(self, rhs: BoolVal) -> Self::Output {
rhs | self
}
}
impl BitOr<Lit> for Lit {
type Output = Formula<Lit>;
fn bitor(self, rhs: Lit) -> Self::Output {
Formula::Or(vec![Formula::Atom(self), Formula::Atom(rhs)])
}
}
impl BitOr<bool> for Lit {
type Output = BoolVal;
fn bitor(self, rhs: bool) -> Self::Output {
if rhs {
true.into()
} else {
self.into()
}
}
}
impl BitXor<BoolVal> for Lit {
type Output = Formula<BoolVal>;
fn bitxor(self, rhs: BoolVal) -> Self::Output {
rhs ^ self
}
}
impl BitXor<Lit> for Lit {
type Output = Formula<Lit>;
fn bitxor(self, rhs: Lit) -> Self::Output {
Formula::Xor(vec![Formula::Atom(self), Formula::Atom(rhs)])
}
}
impl BitXor<bool> for Lit {
type Output = Lit;
fn bitxor(self, rhs: bool) -> Self::Output {
if rhs {
!self
} else {
self
}
}
}
impl Display for Lit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}{}",
if self.is_negated() { "¬" } else { "" },
self.var()
)
}
}
impl From<Var> for Lit {
fn from(value: Var) -> Self {
Lit(value.0)
}
}
impl Mul<Coeff> for Lit {
type Output = BoolLinExp;
fn mul(self, rhs: Coeff) -> Self::Output {
BoolLinExp::from_terms(&[(self, rhs)])
}
}
impl Not for Lit {
type Output = Lit;
fn not(self) -> Self::Output {
Lit(-self.0)
}
}
impl Ord for Lit {
fn cmp(&self, other: &Self) -> Ordering {
match self.var().cmp(&other.var()) {
Ordering::Equal => (self.is_negated()).cmp(&other.is_negated()),
r => r,
}
}
}
impl PartialOrd for Lit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl From<Lit> for NonZeroI32 {
fn from(val: Lit) -> Self {
val.0
}
}
impl From<Var> for NonZeroI32 {
fn from(val: Var) -> Self {
val.0
}
}
impl Display for Unsatisfiable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Problem inconsistency detected")
}
}
impl Error for Unsatisfiable {}
impl Var {
const MAX_VARS: usize = NonZeroI32::MAX.get() as usize;
fn checked_add(&self, b: NonZeroI32) -> Option<Var> {
self.0
.get()
.checked_add(b.get())
.map(|v| Var(NonZeroI32::new(v).unwrap()))
}
fn next_var(&self) -> Option<Var> {
const ONE: NonZeroI32 = NonZeroI32::new(1).unwrap();
self.checked_add(ONE)
}
fn prev_var(&self) -> Option<Var> {
let prev = self.0.get() - 1;
if prev > 0 {
Some(Var(NonZeroI32::new(prev).unwrap()))
} else {
None
}
}
}
impl Display for Var {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "x{}", subscript_number(self.0.get() as usize).format(""))
}
}
impl Not for Var {
type Output = Lit;
fn not(self) -> Self::Output {
!Lit::from(self)
}
}
impl VarRange {
pub fn empty() -> Self {
Self {
start: Var(NonZeroI32::new(2).unwrap()),
end: Var(NonZeroI32::new(1).unwrap()),
}
}
pub fn end(&self) -> Var {
self.end
}
pub fn find(&self, var: Var) -> Option<usize> {
if !self.contains(&var) {
None
} else {
let offset = (var.0.get() - self.start.0.get()) as usize;
debug_assert!(offset <= self.len());
Some(offset)
}
}
pub fn index(&self, index: usize) -> Var {
if index >= self.len() {
panic!("out of bounds access");
}
if index == 0 {
self.start
} else {
let index = NonZeroI32::new(index as i32).unwrap();
self.start.checked_add(index).unwrap()
}
}
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn iter_lits(&mut self) -> impl Iterator<Item = Lit> + '_ {
self.map(Lit::from)
}
pub const fn len(&self) -> usize {
let len = self.end.0.get() - self.start.0.get() + 1;
if len < 0 {
return 0;
}
len as usize
}
pub fn new(start: Var, end: Var) -> Self {
Self { start, end }
}
pub fn start(&self) -> Var {
self.start
}
}
impl DoubleEndedIterator for VarRange {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start <= self.end {
let item = self.end;
if let Some(prev) = self.end.prev_var() {
self.end = prev;
} else {
*self = VarRange::empty();
}
Some(item)
} else {
None
}
}
}
impl ExactSizeIterator for VarRange {
fn len(&self) -> usize {
self.len()
}
}
impl From<RangeInclusive<Var>> for VarRange {
fn from(value: RangeInclusive<Var>) -> Self {
VarRange::new(*value.start(), *value.end())
}
}
impl FusedIterator for VarRange {}
impl Iterator for VarRange {
type Item = Var;
fn count(self) -> usize {
let (lower, upper) = self.size_hint();
debug_assert_eq!(upper, Some(lower));
lower
}
fn next(&mut self) -> Option<Self::Item> {
if self.start <= self.end {
let item = self.start;
self.start = self.start.next_var().unwrap();
Some(item)
} else {
None
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl RangeBounds<Var> for VarRange {
fn end_bound(&self) -> Bound<&Var> {
Bound::Included(&self.end)
}
fn start_bound(&self) -> Bound<&Var> {
Bound::Included(&self.start)
}
}
impl Wcnf {
pub fn add_weighted_clause<I>(&mut self, clause: I, weight: Coeff) -> Result
where
I: IntoIterator,
I::Item: Into<BoolVal>,
{
let clauses = self.cnf.num_clauses();
self.cnf.add_clause(clause)?;
if self.cnf.num_clauses() > clauses {
self.weights.push(Some(weight));
}
Ok(())
}
pub fn from_file(path: &Path) -> Result<Self, io::Error> {
match parse_dimacs_file::<true>(path)? {
Dimacs::Wcnf(wcnf) => Ok(wcnf),
_ => unreachable!(),
}
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&[Lit], &Option<Coeff>)> {
self.cnf.iter().zip(self.weights.iter())
}
pub fn literals(&self) -> usize {
self.cnf.literals()
}
pub fn num_clauses(&self) -> usize {
self.cnf.num_clauses()
}
pub fn num_vars(&self) -> usize {
self.cnf.num_vars()
}
pub fn to_file(&self, path: &Path, comment: Option<&str>) -> Result<(), io::Error> {
let mut file = File::create(path)?;
if let Some(comment) = comment {
for line in comment.lines() {
writeln!(file, "c {line}")?;
}
}
write!(file, "{self}")
}
pub fn variables(&self) -> VarRange {
self.cnf.variables()
}
}
impl ClauseDatabase for Wcnf {
fn add_clause_from_slice(&mut self, clause: &[Lit]) -> Result {
let clauses = self.cnf.num_clauses();
self.cnf.add_clause_from_slice(clause)?;
if self.cnf.num_clauses() > clauses {
self.weights.push(None);
}
Ok(())
}
fn new_var_range(&mut self, len: usize) -> VarRange {
self.cnf.new_var_range(len)
}
}
impl Display for Wcnf {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let num_var = &self.cnf.nvar.num_emitted_vars();
let num_clauses = self.cnf.size.len();
let top = self.weights.iter().flatten().fold(1, |a, b| a + *b);
writeln!(f, "p wcnf {num_var} {num_clauses} {top}")?;
let mut start = 0;
for (size, weight) in self.cnf.size.iter().zip(self.weights.iter()) {
let cl = self.cnf.lits.iter().skip(start).take(*size);
let weight = weight.unwrap_or(top);
write!(f, "{weight} ")?;
for lit in cl {
write!(f, "{} ", lit.0)?;
}
writeln!(f, "0")?;
start += size;
}
Ok(())
}
}
impl From<Cnf> for Wcnf {
fn from(cnf: Cnf) -> Self {
let weights = repeat_n(None, cnf.num_clauses()).collect();
Wcnf { cnf, weights }
}
}
impl From<Lit> for i32 {
fn from(val: Lit) -> Self {
val.0.get()
}
}
impl From<Var> for i32 {
fn from(val: Var) -> Self {
val.0.get()
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroI32;
use crate::{solver::VarFactory, Lit, Var};
#[test]
fn var_range() {
let mut factory = VarFactory::default();
let range = factory.next_var_range(0);
assert_eq!(range.len(), 0);
assert_eq!(factory.next_var, Some(Var(NonZeroI32::new(1).unwrap())));
let range = factory.next_var_range(1);
assert_eq!(range.len(), 1);
assert_eq!(factory.next_var, Some(Var(NonZeroI32::new(2).unwrap())));
let range = factory.next_var_range(2);
assert_eq!(range.len(), 2);
assert_eq!(factory.next_var, Some(Var(NonZeroI32::new(4).unwrap())));
let range = factory.next_var_range(100);
assert_eq!(range.len(), 100);
assert_eq!(factory.next_var, Some(Var(NonZeroI32::new(104).unwrap())));
}
impl From<i32> for Lit {
fn from(value: i32) -> Self {
Lit(NonZeroI32::new(value).expect("cannot create literal with value zero"))
}
}
}