#[inline]
pub fn compute_checksum(assignment: &[bool]) -> u64 {
const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let mut hash = FNV_OFFSET_BASIS;
for (i, &val) in assignment.iter().enumerate() {
hash ^= (i as u64) << 32 | (val as u64);
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum SolveProof {
Satisfying {
assignment: Vec<bool>,
checksum: u64,
},
Unsatisfiable {
checksum: u64,
},
Approximate {
assignment: Vec<bool>,
satisfied_clauses: u32,
total_clauses: u32,
iterations: u32,
},
#[default]
None,
}
impl SolveProof {
#[inline]
pub fn satisfying(assignment: Vec<bool>) -> Self {
let checksum = compute_checksum(&assignment);
Self::Satisfying {
assignment,
checksum,
}
}
#[inline]
pub fn unsatisfiable() -> Self {
let checksum = compute_checksum(&[]).wrapping_mul(0xDEADBEEFCAFEBABE);
Self::Unsatisfiable { checksum }
}
#[inline]
pub fn approximate(
assignment: Vec<bool>,
satisfied_clauses: u32,
total_clauses: u32,
iterations: u32,
) -> Self {
Self::Approximate {
assignment,
satisfied_clauses,
total_clauses,
iterations,
}
}
#[inline]
pub fn is_satisfying(&self) -> bool {
matches!(self, Self::Satisfying { .. })
}
#[inline]
pub fn assignment(&self) -> Option<&[bool]> {
match self {
Self::Satisfying { assignment, .. } => Some(assignment),
Self::Approximate { assignment, .. } => Some(assignment),
Self::Unsatisfiable { .. } | Self::None => None,
}
}
#[inline]
pub fn checksum(&self) -> Option<u64> {
match self {
Self::Satisfying { checksum, .. } => Some(*checksum),
Self::Unsatisfiable { checksum } => Some(*checksum),
Self::Approximate { .. } | Self::None => None,
}
}
#[inline]
pub fn verify_checksum(&self) -> Option<bool> {
match self {
Self::Satisfying {
assignment,
checksum,
} => Some(compute_checksum(assignment) == *checksum),
_ => None,
}
}
#[inline]
pub fn satisfaction_ratio(&self) -> Option<f64> {
match self {
Self::Approximate {
satisfied_clauses,
total_clauses,
..
} => {
if *total_clauses == 0 {
Some(0.0)
} else {
Some(*satisfied_clauses as f64 / *total_clauses as f64)
}
}
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SolveStatus {
Sat,
Unsat,
#[default]
Unknown,
Optimal(u64),
}
impl SolveStatus {
#[inline]
pub fn is_satisfiable(&self) -> bool {
matches!(self, Self::Sat | Self::Optimal(_))
}
#[inline]
pub fn is_unsatisfiable(&self) -> bool {
matches!(self, Self::Unsat)
}
#[inline]
pub fn is_determined(&self) -> bool {
!matches!(self, Self::Unknown)
}
#[inline]
pub fn optimal_value(&self) -> Option<u64> {
match self {
Self::Optimal(v) => Some(*v),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SolveStats {
pub iterations: u32,
pub duration_us: u64,
pub peak_memory: u64,
}
impl SolveStats {
#[inline]
pub const fn new(iterations: u32, duration_us: u64, peak_memory: u64) -> Self {
Self {
iterations,
duration_us,
peak_memory,
}
}
#[inline]
pub const fn with_iterations(mut self, iterations: u32) -> Self {
self.iterations = iterations;
self
}
#[inline]
pub const fn with_duration_us(mut self, duration_us: u64) -> Self {
self.duration_us = duration_us;
self
}
#[inline]
pub const fn with_peak_memory(mut self, peak_memory: u64) -> Self {
self.peak_memory = peak_memory;
self
}
#[inline]
pub const fn duration_ms(&self) -> u64 {
self.duration_us / 1000
}
#[inline]
pub fn duration_secs(&self) -> f64 {
self.duration_us as f64 / 1_000_000.0
}
#[inline]
pub fn iterations_per_sec(&self) -> f64 {
if self.duration_us == 0 {
0.0
} else {
(self.iterations as f64 * 1_000_000.0) / self.duration_us as f64
}
}
}
#[derive(Debug, Clone)]
pub struct SolveResult {
pub status: SolveStatus,
pub proof: SolveProof,
pub stats: SolveStats,
}
impl SolveResult {
#[inline]
pub fn satisfiable(assignment: Vec<bool>) -> Self {
Self {
status: SolveStatus::Sat,
proof: SolveProof::satisfying(assignment),
stats: SolveStats::default(),
}
}
#[inline]
pub fn unsatisfiable() -> Self {
Self {
status: SolveStatus::Unsat,
proof: SolveProof::unsatisfiable(),
stats: SolveStats::default(),
}
}
#[inline]
pub fn unknown(iterations: u32) -> Self {
Self {
status: SolveStatus::Unknown,
proof: SolveProof::None,
stats: SolveStats::default().with_iterations(iterations),
}
}
#[inline]
pub fn optimal(assignment: Vec<bool>, optimal_value: u64) -> Self {
Self {
status: SolveStatus::Optimal(optimal_value),
proof: SolveProof::satisfying(assignment),
stats: SolveStats::default(),
}
}
#[inline]
pub fn approximate(
assignment: Vec<bool>,
satisfied_clauses: u32,
total_clauses: u32,
iterations: u32,
) -> Self {
Self {
status: SolveStatus::Unknown,
proof: SolveProof::approximate(
assignment,
satisfied_clauses,
total_clauses,
iterations,
),
stats: SolveStats::default().with_iterations(iterations),
}
}
#[inline]
pub fn with_stats(mut self, stats: SolveStats) -> Self {
self.stats = stats;
self
}
#[inline]
pub fn is_sat(&self) -> bool {
self.status.is_satisfiable()
}
#[inline]
pub fn is_unsat(&self) -> bool {
self.status.is_unsatisfiable()
}
#[inline]
pub fn assignment(&self) -> Option<&[bool]> {
self.proof.assignment()
}
#[inline]
pub fn optimal_value(&self) -> Option<u64> {
self.status.optimal_value()
}
#[inline]
pub fn verify_proof(&self) -> Option<bool> {
self.proof.verify_checksum()
}
}
impl Default for SolveResult {
fn default() -> Self {
Self {
status: SolveStatus::Unknown,
proof: SolveProof::None,
stats: SolveStats::default(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_solve_proof_satisfying() {
let assignment = vec![true, false, true];
let proof = SolveProof::satisfying(assignment.clone());
match proof {
SolveProof::Satisfying {
assignment: a,
checksum,
} => {
assert_eq!(a, assignment);
assert_ne!(checksum, 0); }
_ => panic!("Expected Satisfying proof"),
}
}
#[test]
fn test_solve_proof_unsatisfiable() {
let proof = SolveProof::unsatisfiable();
match proof {
SolveProof::Unsatisfiable { checksum } => {
assert_ne!(checksum, 0);
}
_ => panic!("Expected Unsatisfiable proof"),
}
}
#[test]
fn test_solve_proof_approximate() {
let assignment = vec![true, false];
let proof = SolveProof::approximate(assignment.clone(), 5, 10, 100);
match proof {
SolveProof::Approximate {
assignment: a,
satisfied_clauses,
total_clauses,
iterations,
} => {
assert_eq!(a, assignment);
assert_eq!(satisfied_clauses, 5);
assert_eq!(total_clauses, 10);
assert_eq!(iterations, 100);
}
_ => panic!("Expected Approximate proof"),
}
}
#[test]
fn test_solve_proof_none() {
let proof = SolveProof::None;
assert!(matches!(proof, SolveProof::None));
}
#[test]
fn test_solve_proof_checksum_deterministic() {
let assignment = vec![true, false, true, false];
let proof1 = SolveProof::satisfying(assignment.clone());
let proof2 = SolveProof::satisfying(assignment);
let checksum1 = match proof1 {
SolveProof::Satisfying { checksum, .. } => checksum,
_ => panic!("Expected Satisfying"),
};
let checksum2 = match proof2 {
SolveProof::Satisfying { checksum, .. } => checksum,
_ => panic!("Expected Satisfying"),
};
assert_eq!(checksum1, checksum2);
}
#[test]
fn test_solve_proof_checksum_different_assignments() {
let proof1 = SolveProof::satisfying(vec![true, false]);
let proof2 = SolveProof::satisfying(vec![false, true]);
let checksum1 = match proof1 {
SolveProof::Satisfying { checksum, .. } => checksum,
_ => panic!("Expected Satisfying"),
};
let checksum2 = match proof2 {
SolveProof::Satisfying { checksum, .. } => checksum,
_ => panic!("Expected Satisfying"),
};
assert_ne!(checksum1, checksum2);
}
#[test]
fn test_solve_proof_checksum() {
let proof = SolveProof::Satisfying {
assignment: vec![true, false],
checksum: 12345,
};
match proof {
SolveProof::Satisfying { checksum, .. } => {
assert_eq!(checksum, 12345);
}
_ => panic!("Wrong proof type"),
}
}
#[test]
fn test_solve_proof_is_satisfying() {
let sat_proof = SolveProof::satisfying(vec![true]);
assert!(sat_proof.is_satisfying());
let unsat_proof = SolveProof::unsatisfiable();
assert!(!unsat_proof.is_satisfying());
let approx_proof = SolveProof::approximate(vec![true], 1, 1, 1);
assert!(!approx_proof.is_satisfying());
assert!(!SolveProof::None.is_satisfying());
}
#[test]
fn test_solve_proof_assignment() {
let assignment = vec![true, false, true];
let sat_proof = SolveProof::satisfying(assignment.clone());
assert_eq!(sat_proof.assignment(), Some(&assignment[..]));
let approx_proof = SolveProof::approximate(assignment.clone(), 2, 3, 10);
assert_eq!(approx_proof.assignment(), Some(&assignment[..]));
let unsat_proof = SolveProof::unsatisfiable();
assert_eq!(unsat_proof.assignment(), None);
assert_eq!(SolveProof::None.assignment(), None);
}
#[test]
fn test_solve_proof_verify_checksum() {
let proof = SolveProof::satisfying(vec![true, false, true]);
assert_eq!(proof.verify_checksum(), Some(true));
let corrupted = SolveProof::Satisfying {
assignment: vec![true, false, true],
checksum: 12345, };
assert_eq!(corrupted.verify_checksum(), Some(false));
}
#[test]
fn test_solve_proof_satisfaction_ratio() {
let proof = SolveProof::approximate(vec![true], 8, 10, 100);
assert_eq!(proof.satisfaction_ratio(), Some(0.8));
let full = SolveProof::approximate(vec![true], 10, 10, 100);
assert_eq!(full.satisfaction_ratio(), Some(1.0));
let empty = SolveProof::approximate(vec![], 0, 0, 100);
assert_eq!(empty.satisfaction_ratio(), Some(0.0));
assert_eq!(
SolveProof::satisfying(vec![true]).satisfaction_ratio(),
None
);
}
#[test]
fn test_solve_proof_default() {
let proof = SolveProof::default();
assert!(matches!(proof, SolveProof::None));
}
#[test]
fn test_solve_status_sat() {
let status = SolveStatus::Sat;
assert!(status.is_satisfiable());
assert!(!status.is_unsatisfiable());
assert!(status.is_determined());
}
#[test]
fn test_solve_status_unsat() {
let status = SolveStatus::Unsat;
assert!(!status.is_satisfiable());
assert!(status.is_unsatisfiable());
assert!(status.is_determined());
}
#[test]
fn test_solve_status_unknown() {
let status = SolveStatus::Unknown;
assert!(!status.is_satisfiable());
assert!(!status.is_unsatisfiable());
assert!(!status.is_determined());
}
#[test]
fn test_solve_status_optimal() {
let status = SolveStatus::Optimal(42);
assert!(status.is_satisfiable());
assert!(!status.is_unsatisfiable());
assert!(status.is_determined());
assert_eq!(status.optimal_value(), Some(42));
}
#[test]
fn test_solve_status_optimal_value() {
assert_eq!(SolveStatus::Sat.optimal_value(), None);
assert_eq!(SolveStatus::Unsat.optimal_value(), None);
assert_eq!(SolveStatus::Unknown.optimal_value(), None);
assert_eq!(SolveStatus::Optimal(100).optimal_value(), Some(100));
}
#[test]
fn test_solve_status_eq() {
assert_eq!(SolveStatus::Sat, SolveStatus::Sat);
assert_eq!(SolveStatus::Unsat, SolveStatus::Unsat);
assert_eq!(SolveStatus::Unknown, SolveStatus::Unknown);
assert_eq!(SolveStatus::Optimal(5), SolveStatus::Optimal(5));
assert_ne!(SolveStatus::Optimal(5), SolveStatus::Optimal(6));
assert_ne!(SolveStatus::Sat, SolveStatus::Unsat);
}
#[test]
fn test_solve_status_default() {
let status = SolveStatus::default();
assert!(matches!(status, SolveStatus::Unknown));
}
#[test]
fn test_solve_stats_default() {
let stats = SolveStats::default();
assert_eq!(stats.iterations, 0);
assert_eq!(stats.duration_us, 0);
assert_eq!(stats.peak_memory, 0);
}
#[test]
fn test_solve_stats_new() {
let stats = SolveStats::new(100, 5000, 1024);
assert_eq!(stats.iterations, 100);
assert_eq!(stats.duration_us, 5000);
assert_eq!(stats.peak_memory, 1024);
}
#[test]
fn test_solve_stats_with_iterations() {
let stats = SolveStats::default().with_iterations(500);
assert_eq!(stats.iterations, 500);
assert_eq!(stats.duration_us, 0);
assert_eq!(stats.peak_memory, 0);
}
#[test]
fn test_solve_stats_with_duration_us() {
let stats = SolveStats::default().with_duration_us(10000);
assert_eq!(stats.iterations, 0);
assert_eq!(stats.duration_us, 10000);
assert_eq!(stats.peak_memory, 0);
}
#[test]
fn test_solve_stats_with_peak_memory() {
let stats = SolveStats::default().with_peak_memory(2048);
assert_eq!(stats.iterations, 0);
assert_eq!(stats.duration_us, 0);
assert_eq!(stats.peak_memory, 2048);
}
#[test]
fn test_solve_stats_chained_builders() {
let stats = SolveStats::default()
.with_iterations(100)
.with_duration_us(5000)
.with_peak_memory(1024);
assert_eq!(stats.iterations, 100);
assert_eq!(stats.duration_us, 5000);
assert_eq!(stats.peak_memory, 1024);
}
#[test]
fn test_solve_stats_duration_ms() {
let stats = SolveStats::new(0, 5500, 0);
assert_eq!(stats.duration_ms(), 5);
}
#[test]
fn test_solve_stats_duration_secs() {
let stats = SolveStats::new(0, 1_500_000, 0);
assert_eq!(stats.duration_secs(), 1.5);
}
#[test]
fn test_solve_stats_iterations_per_sec() {
let stats = SolveStats::new(1000, 1_000_000, 0);
assert_eq!(stats.iterations_per_sec(), 1000.0);
let zero = SolveStats::new(1000, 0, 0);
assert_eq!(zero.iterations_per_sec(), 0.0);
}
#[test]
fn test_solve_result_sat() {
let result = SolveResult::satisfiable(vec![true, false, true]);
assert!(matches!(result.status, SolveStatus::Sat));
assert!(result.proof.is_satisfying());
}
#[test]
fn test_solve_result_unsat() {
let result = SolveResult::unsatisfiable();
assert!(matches!(result.status, SolveStatus::Unsat));
assert!(matches!(result.proof, SolveProof::Unsatisfiable { .. }));
}
#[test]
fn test_solve_result_unknown() {
let result = SolveResult::unknown(500);
assert!(matches!(result.status, SolveStatus::Unknown));
assert_eq!(result.stats.iterations, 500);
}
#[test]
fn test_solve_result_optimal() {
let result = SolveResult::optimal(vec![true, false], 42);
assert!(matches!(result.status, SolveStatus::Optimal(42)));
assert!(result.proof.is_satisfying());
}
#[test]
fn test_solve_result_with_stats() {
let stats = SolveStats::new(100, 5000, 1024);
let result = SolveResult::satisfiable(vec![true]).with_stats(stats);
assert_eq!(result.stats.iterations, 100);
assert_eq!(result.stats.duration_us, 5000);
assert_eq!(result.stats.peak_memory, 1024);
}
#[test]
fn test_solve_result_is_sat() {
assert!(SolveResult::satisfiable(vec![true]).is_sat());
assert!(!SolveResult::unsatisfiable().is_sat());
assert!(!SolveResult::unknown(0).is_sat());
assert!(SolveResult::optimal(vec![true], 5).is_sat());
}
#[test]
fn test_solve_result_is_unsat() {
assert!(!SolveResult::satisfiable(vec![true]).is_unsat());
assert!(SolveResult::unsatisfiable().is_unsat());
assert!(!SolveResult::unknown(0).is_unsat());
assert!(!SolveResult::optimal(vec![true], 5).is_unsat());
}
#[test]
fn test_solve_result_assignment() {
let assignment = vec![true, false, true];
let sat_result = SolveResult::satisfiable(assignment.clone());
assert_eq!(sat_result.assignment(), Some(&assignment[..]));
let unsat_result = SolveResult::unsatisfiable();
assert_eq!(unsat_result.assignment(), None);
}
#[test]
fn test_solve_result_approximate() {
let result = SolveResult::approximate(vec![true, false], 8, 10, 1000);
assert!(matches!(result.status, SolveStatus::Unknown));
match &result.proof {
SolveProof::Approximate {
satisfied_clauses,
total_clauses,
iterations,
..
} => {
assert_eq!(*satisfied_clauses, 8);
assert_eq!(*total_clauses, 10);
assert_eq!(*iterations, 1000);
}
_ => panic!("Expected Approximate proof"),
}
}
#[test]
fn test_solve_result_optimal_value() {
let result = SolveResult::optimal(vec![true], 42);
assert_eq!(result.optimal_value(), Some(42));
let sat = SolveResult::satisfiable(vec![true]);
assert_eq!(sat.optimal_value(), None);
}
#[test]
fn test_solve_result_verify_proof() {
let result = SolveResult::satisfiable(vec![true, false]);
assert_eq!(result.verify_proof(), Some(true));
}
#[test]
fn test_solve_result_default() {
let result = SolveResult::default();
assert!(matches!(result.status, SolveStatus::Unknown));
assert!(matches!(result.proof, SolveProof::None));
}
#[test]
fn test_compute_checksum_empty() {
let checksum = compute_checksum(&[]);
assert_eq!(checksum, 0xcbf29ce484222325);
}
#[test]
fn test_compute_checksum_single() {
let checksum_true = compute_checksum(&[true]);
let checksum_false = compute_checksum(&[false]);
assert_ne!(checksum_true, checksum_false);
}
#[test]
fn test_compute_checksum_deterministic() {
let assignment = vec![true, false, true, false, true];
let c1 = compute_checksum(&assignment);
let c2 = compute_checksum(&assignment);
assert_eq!(c1, c2);
}
#[test]
fn test_compute_checksum_order_sensitive() {
let c1 = compute_checksum(&[true, false]);
let c2 = compute_checksum(&[false, true]);
assert_ne!(c1, c2);
}
}