use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Literal {
pub var: u32,
pub negated: bool,
}
impl Literal {
#[inline]
pub const fn positive(var: u32) -> Self {
Self {
var,
negated: false,
}
}
#[inline]
pub const fn negative(var: u32) -> Self {
Self { var, negated: true }
}
#[inline]
pub const fn new(var: u32, negated: bool) -> Self {
Self { var, negated }
}
#[inline]
pub const fn negate(self) -> Self {
Self {
var: self.var,
negated: !self.negated,
}
}
#[inline]
pub fn eval(self, assignment: &[bool]) -> bool {
let value = assignment[self.var as usize];
if self.negated {
!value
} else {
value
}
}
#[inline]
pub fn to_dimacs(self) -> i32 {
let var_1indexed = (self.var + 1) as i32;
if self.negated {
-var_1indexed
} else {
var_1indexed
}
}
#[inline]
pub fn from_dimacs(dimacs: i32) -> Self {
assert!(dimacs != 0, "DIMACS literal cannot be zero");
let negated = dimacs < 0;
let var = dimacs.unsigned_abs() - 1;
Self { var, negated }
}
#[inline]
pub fn to_packed(self) -> u32 {
if self.negated {
self.var | 0x8000_0000
} else {
self.var
}
}
#[inline]
pub fn from_packed(packed: u32) -> Self {
let negated = (packed & 0x8000_0000) != 0;
let var = packed & 0x7FFF_FFFF;
Self { var, negated }
}
}
impl PartialOrd for Literal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Literal {
fn cmp(&self, other: &Self) -> Ordering {
match self.var.cmp(&other.var) {
Ordering::Equal => self.negated.cmp(&other.negated),
ord => ord,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Clause {
pub literals: Vec<Literal>,
}
impl Clause {
#[inline]
pub fn new(literals: Vec<Literal>) -> Self {
Self { literals }
}
#[inline]
pub fn unit(literal: Literal) -> Self {
Self {
literals: vec![literal],
}
}
#[inline]
pub fn binary(a: Literal, b: Literal) -> Self {
Self {
literals: vec![a, b],
}
}
#[inline]
pub fn ternary(a: Literal, b: Literal, c: Literal) -> Self {
Self {
literals: vec![a, b, c],
}
}
#[inline]
pub fn len(&self) -> usize {
self.literals.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.literals.is_empty()
}
#[inline]
pub fn is_satisfied(&self, assignment: &[bool]) -> bool {
self.literals.iter().any(|lit| lit.eval(assignment))
}
#[inline]
pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
self.literals
.iter()
.filter(|lit| lit.eval(assignment))
.count()
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = &Literal> {
self.literals.iter()
}
#[inline]
pub fn vars(&self) -> impl Iterator<Item = u32> + '_ {
self.literals.iter().map(|lit| lit.var)
}
}
impl IntoIterator for Clause {
type Item = Literal;
type IntoIter = std::vec::IntoIter<Literal>;
fn into_iter(self) -> Self::IntoIter {
self.literals.into_iter()
}
}
impl<'a> IntoIterator for &'a Clause {
type Item = &'a Literal;
type IntoIter = std::slice::Iter<'a, Literal>;
fn into_iter(self) -> Self::IntoIter {
self.literals.iter()
}
}
impl FromIterator<Literal> for Clause {
fn from_iter<I: IntoIterator<Item = Literal>>(iter: I) -> Self {
Self {
literals: iter.into_iter().collect(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum Objective {
#[default]
Satisfaction,
MaxSat,
MinUnsat,
}
#[derive(Debug, Clone)]
pub struct SolveInstance {
pub num_vars: u32,
pub clauses: Vec<Clause>,
pub weights: Option<Vec<f64>>,
pub objective: Objective,
}
impl SolveInstance {
#[inline]
pub fn new(num_vars: u32, clauses: Vec<Clause>) -> Self {
Self {
num_vars,
clauses,
weights: None,
objective: Objective::Satisfaction,
}
}
#[inline]
pub fn with_weights(num_vars: u32, clauses: Vec<Clause>, weights: Vec<f64>) -> Self {
assert_eq!(
clauses.len(),
weights.len(),
"Number of weights ({}) must match number of clauses ({})",
weights.len(),
clauses.len()
);
Self {
num_vars,
clauses,
weights: Some(weights),
objective: Objective::MaxSat,
}
}
#[inline]
pub fn add_clause(&mut self, clause: Clause) {
self.clauses.push(clause);
if let Some(ref mut weights) = self.weights {
weights.push(1.0);
}
}
#[inline]
pub fn add_weighted_clause(&mut self, clause: Clause, weight: f64) {
if self.weights.is_none() {
self.weights = Some(vec![1.0; self.clauses.len()]);
}
self.clauses.push(clause);
self.weights.as_mut().unwrap().push(weight);
}
#[inline]
pub fn num_clauses(&self) -> usize {
self.clauses.len()
}
#[inline]
pub fn is_satisfied(&self, assignment: &[bool]) -> bool {
self.clauses
.iter()
.all(|clause| clause.is_satisfied(assignment))
}
#[inline]
pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
self.clauses
.iter()
.filter(|clause| clause.is_satisfied(assignment))
.count()
}
#[inline]
pub fn weighted_satisfaction(&self, assignment: &[bool]) -> f64 {
match &self.weights {
Some(weights) => self
.clauses
.iter()
.zip(weights.iter())
.filter(|(clause, _)| clause.is_satisfied(assignment))
.map(|(_, weight)| *weight)
.sum(),
None => self.count_satisfied(assignment) as f64,
}
}
#[inline]
pub fn total_weight(&self) -> f64 {
match &self.weights {
Some(weights) => weights.iter().sum(),
None => self.clauses.len() as f64,
}
}
#[inline]
pub fn satisfaction_ratio(&self, assignment: &[bool]) -> f64 {
let total = self.total_weight();
if total == 0.0 {
0.0
} else {
self.weighted_satisfaction(assignment) / total
}
}
#[inline]
pub fn iter_clauses(&self) -> impl Iterator<Item = &Clause> {
self.clauses.iter()
}
pub fn iter_weighted(&self) -> impl Iterator<Item = (&Clause, f64)> {
let weights = self.weights.as_ref();
self.clauses.iter().enumerate().map(move |(i, clause)| {
let weight = weights.map_or(1.0, |w| w[i]);
(clause, weight)
})
}
pub fn validate(&self) -> bool {
self.clauses
.iter()
.all(|clause| clause.literals.iter().all(|lit| lit.var < self.num_vars))
}
pub fn max_var(&self) -> Option<u32> {
self.clauses
.iter()
.flat_map(|c| c.literals.iter())
.map(|lit| lit.var)
.max()
}
pub fn total_literals(&self) -> usize {
self.clauses.iter().map(|c| c.len()).sum()
}
}
impl Default for SolveInstance {
fn default() -> Self {
Self {
num_vars: 0,
clauses: Vec::new(),
weights: None,
objective: Objective::Satisfaction,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_literal_new() {
let pos = Literal::positive(5);
assert_eq!(pos.var, 5);
assert!(!pos.negated);
let neg = Literal::negative(3);
assert_eq!(neg.var, 3);
assert!(neg.negated);
}
#[test]
fn test_literal_eval() {
let pos = Literal::positive(1);
let neg = Literal::negative(1);
let assignment = vec![true, false, true];
assert!(!pos.eval(&assignment));
assert!(neg.eval(&assignment));
let pos2 = Literal::positive(2);
let neg2 = Literal::negative(2);
assert!(pos2.eval(&assignment));
assert!(!neg2.eval(&assignment));
}
#[test]
fn test_literal_to_dimacs() {
let pos = Literal::positive(3);
let neg = Literal::negative(7);
assert_eq!(pos.to_dimacs(), 4); assert_eq!(neg.to_dimacs(), -8); }
#[test]
fn test_literal_from_dimacs() {
let pos = Literal::from_dimacs(4);
assert_eq!(pos.var, 3);
assert!(!pos.negated);
let neg = Literal::from_dimacs(-8);
assert_eq!(neg.var, 7);
assert!(neg.negated);
}
#[test]
fn test_clause_new() {
let clause = Clause::new(vec![Literal::positive(1), Literal::negative(2)]);
assert_eq!(clause.literals.len(), 2);
}
#[test]
fn test_clause_is_satisfied() {
let clause = Clause::new(vec![Literal::positive(0), Literal::negative(1)]);
assert!(clause.is_satisfied(&[true, false]));
assert!(clause.is_satisfied(&[true, true]));
assert!(clause.is_satisfied(&[false, false]));
assert!(!clause.is_satisfied(&[false, true]));
}
#[test]
fn test_clause_count_satisfied() {
let clause = Clause::new(vec![
Literal::positive(0),
Literal::negative(1),
Literal::positive(2),
]);
assert_eq!(clause.count_satisfied(&[true, false, true]), 3);
assert_eq!(clause.count_satisfied(&[true, true, false]), 1);
assert_eq!(clause.count_satisfied(&[false, true, false]), 0);
}
#[test]
fn test_clause_empty() {
let empty = Clause::new(vec![]);
assert!(!empty.is_satisfied(&[true, false, true]));
assert_eq!(empty.count_satisfied(&[true, false, true]), 0);
}
#[test]
fn test_clause_unit() {
let unit_pos = Clause::unit(Literal::positive(0));
assert!(unit_pos.is_satisfied(&[true]));
assert!(!unit_pos.is_satisfied(&[false]));
let unit_neg = Clause::unit(Literal::negative(0));
assert!(!unit_neg.is_satisfied(&[true]));
assert!(unit_neg.is_satisfied(&[false]));
}
#[test]
fn test_objective_default() {
let obj = Objective::default();
assert!(matches!(obj, Objective::Satisfaction));
}
#[test]
fn test_instance_from_cnf() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
],
);
assert_eq!(instance.num_vars, 3);
assert_eq!(instance.clauses.len(), 2);
}
#[test]
fn test_instance_is_satisfied() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
],
);
let assignment = vec![true, false, true];
assert!(instance.is_satisfied(&assignment));
let assignment2 = vec![false, true, false];
assert!(!instance.is_satisfied(&assignment2));
}
#[test]
fn test_instance_count_satisfied() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
Clause::new(vec![Literal::negative(0), Literal::negative(2)]),
],
);
assert_eq!(instance.count_satisfied(&[true, false, true]), 2);
assert_eq!(instance.count_satisfied(&[false, false, false]), 2);
}
#[test]
fn test_instance_with_weights() {
let instance = SolveInstance::with_weights(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
Clause::new(vec![Literal::positive(2)]),
],
vec![1.0, 2.0, 3.0],
);
assert!(instance.weights.is_some());
assert_eq!(instance.weights.as_ref().unwrap().len(), 3);
}
#[test]
fn test_instance_weighted_satisfaction() {
let instance = SolveInstance::with_weights(
3,
vec![
Clause::new(vec![Literal::positive(0)]), Clause::new(vec![Literal::positive(1)]), Clause::new(vec![Literal::positive(2)]), ],
vec![1.0, 2.0, 3.0],
);
assert_eq!(instance.weighted_satisfaction(&[true, true, true]), 6.0);
assert_eq!(instance.weighted_satisfaction(&[false, true, false]), 2.0);
assert_eq!(instance.weighted_satisfaction(&[false, false, false]), 0.0);
}
#[test]
fn test_instance_weighted_satisfaction_unweighted() {
let instance = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
],
);
assert_eq!(instance.weighted_satisfaction(&[true, true]), 2.0);
assert_eq!(instance.weighted_satisfaction(&[true, false]), 1.0);
}
#[test]
fn test_instance_empty() {
let instance = SolveInstance::new(0, vec![]);
assert!(instance.is_satisfied(&[]));
assert_eq!(instance.count_satisfied(&[]), 0);
assert_eq!(instance.weighted_satisfaction(&[]), 0.0);
}
#[test]
fn test_literal_negate() {
let pos = Literal::positive(5);
let neg = pos.negate();
assert_eq!(neg.var, 5);
assert!(neg.negated);
let pos_again = neg.negate();
assert_eq!(pos_again.var, 5);
assert!(!pos_again.negated);
}
#[test]
fn test_clause_binary() {
let clause = Clause::binary(Literal::positive(0), Literal::negative(1));
assert_eq!(clause.literals.len(), 2);
assert!(clause.is_satisfied(&[true, true]));
assert!(clause.is_satisfied(&[true, false]));
assert!(clause.is_satisfied(&[false, false]));
assert!(!clause.is_satisfied(&[false, true]));
}
#[test]
fn test_clause_ternary() {
let clause = Clause::ternary(
Literal::positive(0),
Literal::positive(1),
Literal::positive(2),
);
assert_eq!(clause.literals.len(), 3);
assert!(clause.is_satisfied(&[true, false, false]));
assert!(clause.is_satisfied(&[false, true, false]));
assert!(clause.is_satisfied(&[false, false, true]));
assert!(!clause.is_satisfied(&[false, false, false]));
}
#[test]
fn test_instance_add_clause() {
let mut instance = SolveInstance::new(3, vec![Clause::new(vec![Literal::positive(0)])]);
assert_eq!(instance.clauses.len(), 1);
instance.add_clause(Clause::new(vec![Literal::positive(1)]));
assert_eq!(instance.clauses.len(), 2);
}
#[test]
fn test_instance_total_weight() {
let instance = SolveInstance::with_weights(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
Clause::new(vec![Literal::positive(2)]),
],
vec![1.0, 2.0, 3.0],
);
assert_eq!(instance.total_weight(), 6.0);
let unweighted = SolveInstance::new(
2,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
],
);
assert_eq!(unweighted.total_weight(), 2.0);
}
#[test]
fn test_instance_satisfaction_ratio() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
Clause::new(vec![Literal::positive(2)]),
Clause::new(vec![Literal::negative(0)]),
],
);
assert_eq!(instance.satisfaction_ratio(&[true, true, true]), 0.75);
}
#[test]
fn test_literal_ordering() {
let a = Literal::positive(1);
let b = Literal::positive(2);
let c = Literal::negative(1);
assert!(a < b); assert!(a < c); }
#[test]
fn test_clause_len_and_is_empty() {
let empty = Clause::new(vec![]);
assert!(empty.is_empty());
assert_eq!(empty.len(), 0);
let unit = Clause::unit(Literal::positive(0));
assert!(!unit.is_empty());
assert_eq!(unit.len(), 1);
let binary = Clause::binary(Literal::positive(0), Literal::positive(1));
assert_eq!(binary.len(), 2);
}
#[test]
fn test_instance_num_clauses() {
let instance = SolveInstance::new(
3,
vec![
Clause::new(vec![Literal::positive(0)]),
Clause::new(vec![Literal::positive(1)]),
],
);
assert_eq!(instance.num_clauses(), 2);
}
}