xlog_solve/proof.rs
1//! Proof artifacts and solve results for SAT/MaxSAT solving.
2//!
3//! This module provides types for representing solver outputs:
4//! - [`SolveProof`]: Proof artifacts with checksums for verification
5//! - [`SolveStatus`]: Result status (SAT, UNSAT, Unknown, Optimal)
6//! - [`SolveStats`]: Solver statistics (iterations, timing, memory)
7//! - [`SolveResult`]: Complete solver result combining status, proof, and stats
8//!
9//! # Proof Checksums
10//!
11//! Satisfying assignments include FNV-1a checksums to enable lightweight verification
12//! that an assignment hasn't been corrupted during transfer or storage. This is
13//! particularly useful for GPU-based solving where results are transferred across
14//! memory boundaries.
15//!
16//! # Example
17//!
18//! ```
19//! use xlog_solve::{SolveResult, SolveStatus, SolveStats};
20//!
21//! // Create a satisfying result
22//! let result = SolveResult::satisfiable(vec![true, false, true]);
23//! assert!(result.is_sat());
24//!
25//! // Add statistics
26//! let stats = SolveStats::new(100, 5000, 1024);
27//! let result = result.with_stats(stats);
28//! assert_eq!(result.stats.iterations, 100);
29//! ```
30
31// =============================================================================
32// FNV-1a Checksum Helper
33// =============================================================================
34
35/// Computes an FNV-1a checksum for a boolean assignment.
36///
37/// FNV-1a (Fowler-Noll-Vo) is a fast, non-cryptographic hash function
38/// suitable for integrity verification. The implementation incorporates
39/// both the index and value to ensure position-sensitivity.
40///
41/// # Arguments
42///
43/// * `assignment` - The boolean assignment to checksum
44///
45/// # Returns
46///
47/// A 64-bit FNV-1a hash of the assignment.
48///
49/// # Example
50///
51/// ```ignore
52/// let checksum = compute_checksum(&[true, false, true]);
53/// assert_ne!(checksum, 0);
54/// ```
55#[inline]
56pub fn compute_checksum(assignment: &[bool]) -> u64 {
57 // FNV-1a constants for 64-bit hash
58 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
59 const FNV_PRIME: u64 = 0x100000001b3;
60
61 let mut hash = FNV_OFFSET_BASIS;
62 for (i, &val) in assignment.iter().enumerate() {
63 // Combine index (upper 32 bits) and value (lower bit) for position-sensitivity
64 hash ^= (i as u64) << 32 | (val as u64);
65 hash = hash.wrapping_mul(FNV_PRIME);
66 }
67 hash
68}
69
70// =============================================================================
71// SolveProof - Proof artifact from solver
72// =============================================================================
73
74/// Proof artifact from a SAT/MaxSAT solver.
75///
76/// Proofs provide evidence of the solver's conclusion. For satisfiable instances,
77/// this includes the satisfying assignment with a checksum for verification.
78/// For unsatisfiable instances, a proof of unsatisfiability is included.
79///
80/// # Variants
81///
82/// - [`Satisfying`](SolveProof::Satisfying): Contains a satisfying assignment with checksum
83/// - [`Unsatisfiable`](SolveProof::Unsatisfiable): Proof that no satisfying assignment exists
84/// - [`Approximate`](SolveProof::Approximate): Best-effort assignment that may not satisfy all clauses
85/// - [`None`](SolveProof::None): No proof available (e.g., timeout before any result)
86///
87/// # Checksums
88///
89/// The checksum field uses FNV-1a hashing to enable lightweight verification that
90/// an assignment hasn't been corrupted. This is particularly useful for:
91/// - Verifying GPU-to-CPU transfers
92/// - Detecting memory corruption
93/// - Validating serialized/deserialized results
94///
95/// # Example
96///
97/// ```
98/// use xlog_solve::SolveProof;
99///
100/// // Create a satisfying proof with automatic checksum
101/// let proof = SolveProof::satisfying(vec![true, false, true]);
102/// assert!(proof.is_satisfying());
103///
104/// // Extract the assignment
105/// if let Some(assignment) = proof.assignment() {
106/// assert_eq!(assignment, &[true, false, true]);
107/// }
108/// ```
109#[derive(Debug, Clone, PartialEq, Eq, Default)]
110pub enum SolveProof {
111 /// The instance is satisfiable with the given assignment.
112 ///
113 /// The checksum can be used to verify the assignment's integrity.
114 Satisfying {
115 /// The satisfying assignment (one boolean per variable).
116 assignment: Vec<bool>,
117 /// FNV-1a checksum of the assignment for integrity verification.
118 checksum: u64,
119 },
120
121 /// The instance is unsatisfiable.
122 ///
123 /// The checksum can be used to verify the proof's integrity
124 /// (e.g., if the solver produces resolution proofs).
125 Unsatisfiable {
126 /// Checksum for proof integrity verification.
127 checksum: u64,
128 },
129
130 /// An approximate solution from an incomplete solver.
131 ///
132 /// This is typically produced by local search or heuristic solvers
133 /// that may not find a complete satisfying assignment.
134 Approximate {
135 /// The best assignment found.
136 assignment: Vec<bool>,
137 /// Number of clauses satisfied by this assignment.
138 satisfied_clauses: u32,
139 /// Total number of clauses in the instance.
140 total_clauses: u32,
141 /// Number of solver iterations performed.
142 iterations: u32,
143 },
144
145 /// No proof available.
146 ///
147 /// This occurs when the solver times out or is interrupted
148 /// before producing any meaningful result.
149 #[default]
150 None,
151}
152
153impl SolveProof {
154 /// Creates a satisfying proof with automatic checksum computation.
155 ///
156 /// # Arguments
157 ///
158 /// * `assignment` - The satisfying assignment
159 ///
160 /// # Example
161 ///
162 /// ```
163 /// use xlog_solve::SolveProof;
164 ///
165 /// let proof = SolveProof::satisfying(vec![true, false, true]);
166 /// assert!(proof.is_satisfying());
167 /// ```
168 #[inline]
169 pub fn satisfying(assignment: Vec<bool>) -> Self {
170 let checksum = compute_checksum(&assignment);
171 Self::Satisfying {
172 assignment,
173 checksum,
174 }
175 }
176
177 /// Creates an unsatisfiability proof.
178 ///
179 /// The checksum is computed as a sentinel value for the empty proof.
180 ///
181 /// # Example
182 ///
183 /// ```
184 /// use xlog_solve::SolveProof;
185 ///
186 /// let proof = SolveProof::unsatisfiable();
187 /// assert!(!proof.is_satisfying());
188 /// ```
189 #[inline]
190 pub fn unsatisfiable() -> Self {
191 // Use a sentinel value indicating UNSAT - we compute checksum of empty
192 // assignment and XOR with a sentinel to distinguish from SAT with empty formula
193 let checksum = compute_checksum(&[]).wrapping_mul(0xDEADBEEFCAFEBABE);
194 Self::Unsatisfiable { checksum }
195 }
196
197 /// Creates an approximate solution proof.
198 ///
199 /// This is typically used by incomplete solvers (like local search)
200 /// that may not find a fully satisfying assignment.
201 ///
202 /// # Arguments
203 ///
204 /// * `assignment` - The best assignment found
205 /// * `satisfied_clauses` - Number of clauses satisfied
206 /// * `total_clauses` - Total number of clauses
207 /// * `iterations` - Number of solver iterations performed
208 ///
209 /// # Example
210 ///
211 /// ```
212 /// use xlog_solve::SolveProof;
213 ///
214 /// let proof = SolveProof::approximate(vec![true, false], 8, 10, 1000);
215 /// if let SolveProof::Approximate { satisfied_clauses, total_clauses, .. } = proof {
216 /// assert_eq!(satisfied_clauses, 8);
217 /// assert_eq!(total_clauses, 10);
218 /// }
219 /// ```
220 #[inline]
221 pub fn approximate(
222 assignment: Vec<bool>,
223 satisfied_clauses: u32,
224 total_clauses: u32,
225 iterations: u32,
226 ) -> Self {
227 Self::Approximate {
228 assignment,
229 satisfied_clauses,
230 total_clauses,
231 iterations,
232 }
233 }
234
235 /// Returns true if this is a satisfying proof.
236 ///
237 /// # Example
238 ///
239 /// ```
240 /// use xlog_solve::SolveProof;
241 ///
242 /// assert!(SolveProof::satisfying(vec![true]).is_satisfying());
243 /// assert!(!SolveProof::unsatisfiable().is_satisfying());
244 /// ```
245 #[inline]
246 pub fn is_satisfying(&self) -> bool {
247 matches!(self, Self::Satisfying { .. })
248 }
249
250 /// Returns the assignment if this proof contains one.
251 ///
252 /// Returns `Some` for [`Satisfying`](SolveProof::Satisfying) and
253 /// [`Approximate`](SolveProof::Approximate) variants, `None` otherwise.
254 ///
255 /// # Example
256 ///
257 /// ```
258 /// use xlog_solve::SolveProof;
259 ///
260 /// let proof = SolveProof::satisfying(vec![true, false]);
261 /// assert_eq!(proof.assignment(), Some(&[true, false][..]));
262 ///
263 /// let unsat = SolveProof::unsatisfiable();
264 /// assert_eq!(unsat.assignment(), None);
265 /// ```
266 #[inline]
267 pub fn assignment(&self) -> Option<&[bool]> {
268 match self {
269 Self::Satisfying { assignment, .. } => Some(assignment),
270 Self::Approximate { assignment, .. } => Some(assignment),
271 Self::Unsatisfiable { .. } | Self::None => None,
272 }
273 }
274
275 /// Returns the checksum if this proof has one.
276 ///
277 /// # Example
278 ///
279 /// ```
280 /// use xlog_solve::SolveProof;
281 ///
282 /// let proof = SolveProof::satisfying(vec![true]);
283 /// assert!(proof.checksum().is_some());
284 /// ```
285 #[inline]
286 pub fn checksum(&self) -> Option<u64> {
287 match self {
288 Self::Satisfying { checksum, .. } => Some(*checksum),
289 Self::Unsatisfiable { checksum } => Some(*checksum),
290 Self::Approximate { .. } | Self::None => None,
291 }
292 }
293
294 /// Verifies the integrity of a satisfying assignment by recomputing its checksum.
295 ///
296 /// Returns `true` if the checksum matches, `false` if corrupted.
297 /// Returns `None` for non-satisfying proofs.
298 ///
299 /// # Example
300 ///
301 /// ```
302 /// use xlog_solve::SolveProof;
303 ///
304 /// let proof = SolveProof::satisfying(vec![true, false]);
305 /// assert_eq!(proof.verify_checksum(), Some(true));
306 /// ```
307 #[inline]
308 pub fn verify_checksum(&self) -> Option<bool> {
309 match self {
310 Self::Satisfying {
311 assignment,
312 checksum,
313 } => Some(compute_checksum(assignment) == *checksum),
314 _ => None,
315 }
316 }
317
318 /// Returns the satisfaction ratio for approximate proofs.
319 ///
320 /// Returns `Some(ratio)` for [`Approximate`](SolveProof::Approximate) where
321 /// ratio = satisfied_clauses / total_clauses. Returns `None` for other variants.
322 ///
323 /// # Example
324 ///
325 /// ```
326 /// use xlog_solve::SolveProof;
327 ///
328 /// let proof = SolveProof::approximate(vec![true], 8, 10, 100);
329 /// assert_eq!(proof.satisfaction_ratio(), Some(0.8));
330 /// ```
331 #[inline]
332 pub fn satisfaction_ratio(&self) -> Option<f64> {
333 match self {
334 Self::Approximate {
335 satisfied_clauses,
336 total_clauses,
337 ..
338 } => {
339 if *total_clauses == 0 {
340 Some(0.0)
341 } else {
342 Some(*satisfied_clauses as f64 / *total_clauses as f64)
343 }
344 }
345 _ => None,
346 }
347 }
348}
349
350// =============================================================================
351// SolveStatus - Result status from solver
352// =============================================================================
353
354/// Status of a solve operation.
355///
356/// Represents the outcome of a SAT/MaxSAT solver execution.
357///
358/// # Variants
359///
360/// - [`Sat`](SolveStatus::Sat): Instance is satisfiable
361/// - [`Unsat`](SolveStatus::Unsat): Instance is unsatisfiable
362/// - [`Unknown`](SolveStatus::Unknown): Could not determine (timeout/resource limit)
363/// - [`Optimal`](SolveStatus::Optimal): Found optimal solution with given value (MaxSAT)
364///
365/// # Example
366///
367/// ```
368/// use xlog_solve::SolveStatus;
369///
370/// let status = SolveStatus::Sat;
371/// assert!(status.is_satisfiable());
372/// assert!(status.is_determined());
373///
374/// let optimal = SolveStatus::Optimal(42);
375/// assert_eq!(optimal.optimal_value(), Some(42));
376/// ```
377#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
378pub enum SolveStatus {
379 /// Instance is satisfiable.
380 ///
381 /// A satisfying assignment exists and should be available in the proof.
382 Sat,
383
384 /// Instance is unsatisfiable.
385 ///
386 /// No assignment can satisfy all clauses.
387 Unsat,
388
389 /// Could not determine satisfiability.
390 ///
391 /// The solver did not reach a definitive conclusion, typically due to:
392 /// - Timeout
393 /// - Memory limit
394 /// - Iteration limit
395 /// - Incomplete search (for approximate solvers)
396 #[default]
397 Unknown,
398
399 /// Found optimal solution with given value.
400 ///
401 /// For MaxSAT problems, this indicates the optimal objective value
402 /// (e.g., maximum number of satisfied clauses or weighted sum).
403 Optimal(u64),
404}
405
406impl SolveStatus {
407 /// Returns true if the status indicates satisfiability.
408 ///
409 /// Returns `true` for [`Sat`](SolveStatus::Sat) and [`Optimal`](SolveStatus::Optimal).
410 ///
411 /// # Example
412 ///
413 /// ```
414 /// use xlog_solve::SolveStatus;
415 ///
416 /// assert!(SolveStatus::Sat.is_satisfiable());
417 /// assert!(SolveStatus::Optimal(5).is_satisfiable());
418 /// assert!(!SolveStatus::Unsat.is_satisfiable());
419 /// ```
420 #[inline]
421 pub fn is_satisfiable(&self) -> bool {
422 matches!(self, Self::Sat | Self::Optimal(_))
423 }
424
425 /// Returns true if the status indicates unsatisfiability.
426 ///
427 /// # Example
428 ///
429 /// ```
430 /// use xlog_solve::SolveStatus;
431 ///
432 /// assert!(SolveStatus::Unsat.is_unsatisfiable());
433 /// assert!(!SolveStatus::Sat.is_unsatisfiable());
434 /// ```
435 #[inline]
436 pub fn is_unsatisfiable(&self) -> bool {
437 matches!(self, Self::Unsat)
438 }
439
440 /// Returns true if the solver reached a definitive conclusion.
441 ///
442 /// Returns `true` for all variants except [`Unknown`](SolveStatus::Unknown).
443 ///
444 /// # Example
445 ///
446 /// ```
447 /// use xlog_solve::SolveStatus;
448 ///
449 /// assert!(SolveStatus::Sat.is_determined());
450 /// assert!(SolveStatus::Unsat.is_determined());
451 /// assert!(SolveStatus::Optimal(5).is_determined());
452 /// assert!(!SolveStatus::Unknown.is_determined());
453 /// ```
454 #[inline]
455 pub fn is_determined(&self) -> bool {
456 !matches!(self, Self::Unknown)
457 }
458
459 /// Returns the optimal value if this is an [`Optimal`](SolveStatus::Optimal) status.
460 ///
461 /// # Example
462 ///
463 /// ```
464 /// use xlog_solve::SolveStatus;
465 ///
466 /// assert_eq!(SolveStatus::Optimal(42).optimal_value(), Some(42));
467 /// assert_eq!(SolveStatus::Sat.optimal_value(), None);
468 /// ```
469 #[inline]
470 pub fn optimal_value(&self) -> Option<u64> {
471 match self {
472 Self::Optimal(v) => Some(*v),
473 _ => None,
474 }
475 }
476}
477
478// =============================================================================
479// SolveStats - Statistics from solving
480// =============================================================================
481
482/// Statistics from a solve operation.
483///
484/// Tracks performance metrics from solver execution including
485/// iteration count, timing, and memory usage.
486///
487/// # Builder Pattern
488///
489/// `SolveStats` supports a builder pattern for convenient construction:
490///
491/// ```
492/// use xlog_solve::SolveStats;
493///
494/// let stats = SolveStats::default()
495/// .with_iterations(100)
496/// .with_duration_us(5000)
497/// .with_peak_memory(1024);
498///
499/// assert_eq!(stats.iterations, 100);
500/// assert_eq!(stats.duration_us, 5000);
501/// assert_eq!(stats.peak_memory, 1024);
502/// ```
503#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
504pub struct SolveStats {
505 /// Number of iterations performed by the solver.
506 ///
507 /// For iterative solvers (like CLS), this is the number of
508 /// gradient descent steps. For CDCL solvers, this might be
509 /// the number of decisions or conflicts.
510 pub iterations: u32,
511
512 /// Wall-clock time spent solving, in microseconds.
513 ///
514 /// This includes all solver operations but typically excludes
515 /// instance setup and result extraction.
516 pub duration_us: u64,
517
518 /// Peak memory usage during solving, in bytes.
519 ///
520 /// This tracks the maximum memory allocated by the solver,
521 /// useful for profiling and resource management.
522 pub peak_memory: u64,
523}
524
525impl SolveStats {
526 /// Creates new statistics with all fields specified.
527 ///
528 /// # Arguments
529 ///
530 /// * `iterations` - Number of solver iterations
531 /// * `duration_us` - Solve time in microseconds
532 /// * `peak_memory` - Peak memory usage in bytes
533 ///
534 /// # Example
535 ///
536 /// ```
537 /// use xlog_solve::SolveStats;
538 ///
539 /// let stats = SolveStats::new(100, 5000, 1024);
540 /// assert_eq!(stats.iterations, 100);
541 /// ```
542 #[inline]
543 pub const fn new(iterations: u32, duration_us: u64, peak_memory: u64) -> Self {
544 Self {
545 iterations,
546 duration_us,
547 peak_memory,
548 }
549 }
550
551 /// Sets the iteration count, consuming and returning self.
552 ///
553 /// # Example
554 ///
555 /// ```
556 /// use xlog_solve::SolveStats;
557 ///
558 /// let stats = SolveStats::default().with_iterations(100);
559 /// assert_eq!(stats.iterations, 100);
560 /// ```
561 #[inline]
562 pub const fn with_iterations(mut self, iterations: u32) -> Self {
563 self.iterations = iterations;
564 self
565 }
566
567 /// Sets the duration in microseconds, consuming and returning self.
568 ///
569 /// # Example
570 ///
571 /// ```
572 /// use xlog_solve::SolveStats;
573 ///
574 /// let stats = SolveStats::default().with_duration_us(5000);
575 /// assert_eq!(stats.duration_us, 5000);
576 /// ```
577 #[inline]
578 pub const fn with_duration_us(mut self, duration_us: u64) -> Self {
579 self.duration_us = duration_us;
580 self
581 }
582
583 /// Sets the peak memory usage in bytes, consuming and returning self.
584 ///
585 /// # Example
586 ///
587 /// ```
588 /// use xlog_solve::SolveStats;
589 ///
590 /// let stats = SolveStats::default().with_peak_memory(1024);
591 /// assert_eq!(stats.peak_memory, 1024);
592 /// ```
593 #[inline]
594 pub const fn with_peak_memory(mut self, peak_memory: u64) -> Self {
595 self.peak_memory = peak_memory;
596 self
597 }
598
599 /// Returns the duration in milliseconds (rounded down).
600 ///
601 /// # Example
602 ///
603 /// ```
604 /// use xlog_solve::SolveStats;
605 ///
606 /// let stats = SolveStats::new(0, 5500, 0);
607 /// assert_eq!(stats.duration_ms(), 5);
608 /// ```
609 #[inline]
610 pub const fn duration_ms(&self) -> u64 {
611 self.duration_us / 1000
612 }
613
614 /// Returns the duration in seconds as a floating-point value.
615 ///
616 /// # Example
617 ///
618 /// ```
619 /// use xlog_solve::SolveStats;
620 ///
621 /// let stats = SolveStats::new(0, 1_500_000, 0);
622 /// assert_eq!(stats.duration_secs(), 1.5);
623 /// ```
624 #[inline]
625 pub fn duration_secs(&self) -> f64 {
626 self.duration_us as f64 / 1_000_000.0
627 }
628
629 /// Returns iterations per second (throughput).
630 ///
631 /// Returns 0.0 if duration is zero.
632 ///
633 /// # Example
634 ///
635 /// ```
636 /// use xlog_solve::SolveStats;
637 ///
638 /// let stats = SolveStats::new(1000, 1_000_000, 0);
639 /// assert_eq!(stats.iterations_per_sec(), 1000.0);
640 /// ```
641 #[inline]
642 pub fn iterations_per_sec(&self) -> f64 {
643 if self.duration_us == 0 {
644 0.0
645 } else {
646 (self.iterations as f64 * 1_000_000.0) / self.duration_us as f64
647 }
648 }
649}
650
651// =============================================================================
652// SolveResult - Complete solver result
653// =============================================================================
654
655/// Complete result from a solve operation.
656///
657/// Combines the solve status, proof artifact, and statistics into
658/// a single comprehensive result structure.
659///
660/// # Construction
661///
662/// Use the constructor methods for common result types:
663///
664/// ```
665/// use xlog_solve::{SolveResult, SolveStats};
666///
667/// // Satisfiable result
668/// let sat = SolveResult::satisfiable(vec![true, false, true]);
669/// assert!(sat.is_sat());
670///
671/// // Unsatisfiable result
672/// let unsat = SolveResult::unsatisfiable();
673/// assert!(unsat.is_unsat());
674///
675/// // Unknown result (e.g., timeout)
676/// let unknown = SolveResult::unknown(1000);
677/// assert!(!unknown.is_sat());
678///
679/// // With custom statistics
680/// let stats = SolveStats::new(100, 5000, 1024);
681/// let result = SolveResult::satisfiable(vec![true]).with_stats(stats);
682/// ```
683///
684/// # Example
685///
686/// ```
687/// use xlog_solve::SolveResult;
688///
689/// let result = SolveResult::satisfiable(vec![true, false]);
690///
691/// if result.is_sat() {
692/// if let Some(assignment) = result.assignment() {
693/// println!("Found assignment: {:?}", assignment);
694/// }
695/// }
696/// ```
697#[derive(Debug, Clone)]
698pub struct SolveResult {
699 /// The solve status (SAT, UNSAT, Unknown, Optimal).
700 pub status: SolveStatus,
701
702 /// The proof artifact from solving.
703 pub proof: SolveProof,
704
705 /// Statistics from the solve operation.
706 pub stats: SolveStats,
707}
708
709impl SolveResult {
710 /// Creates a satisfiable result with the given assignment.
711 ///
712 /// Automatically computes a checksum for the assignment.
713 ///
714 /// # Arguments
715 ///
716 /// * `assignment` - The satisfying assignment
717 ///
718 /// # Example
719 ///
720 /// ```
721 /// use xlog_solve::SolveResult;
722 ///
723 /// let result = SolveResult::satisfiable(vec![true, false, true]);
724 /// assert!(result.is_sat());
725 /// ```
726 #[inline]
727 pub fn satisfiable(assignment: Vec<bool>) -> Self {
728 Self {
729 status: SolveStatus::Sat,
730 proof: SolveProof::satisfying(assignment),
731 stats: SolveStats::default(),
732 }
733 }
734
735 /// Creates an unsatisfiable result.
736 ///
737 /// # Example
738 ///
739 /// ```
740 /// use xlog_solve::SolveResult;
741 ///
742 /// let result = SolveResult::unsatisfiable();
743 /// assert!(result.is_unsat());
744 /// ```
745 #[inline]
746 pub fn unsatisfiable() -> Self {
747 Self {
748 status: SolveStatus::Unsat,
749 proof: SolveProof::unsatisfiable(),
750 stats: SolveStats::default(),
751 }
752 }
753
754 /// Creates an unknown result (e.g., timeout).
755 ///
756 /// # Arguments
757 ///
758 /// * `iterations` - Number of iterations performed before giving up
759 ///
760 /// # Example
761 ///
762 /// ```
763 /// use xlog_solve::SolveResult;
764 ///
765 /// let result = SolveResult::unknown(1000);
766 /// assert!(!result.is_sat());
767 /// assert!(!result.is_unsat());
768 /// ```
769 #[inline]
770 pub fn unknown(iterations: u32) -> Self {
771 Self {
772 status: SolveStatus::Unknown,
773 proof: SolveProof::None,
774 stats: SolveStats::default().with_iterations(iterations),
775 }
776 }
777
778 /// Creates an optimal result (for MaxSAT).
779 ///
780 /// # Arguments
781 ///
782 /// * `assignment` - The optimal assignment
783 /// * `optimal_value` - The optimal objective value
784 ///
785 /// # Example
786 ///
787 /// ```
788 /// use xlog_solve::{SolveResult, SolveStatus};
789 ///
790 /// let result = SolveResult::optimal(vec![true, false], 42);
791 /// assert!(matches!(result.status, SolveStatus::Optimal(42)));
792 /// ```
793 #[inline]
794 pub fn optimal(assignment: Vec<bool>, optimal_value: u64) -> Self {
795 Self {
796 status: SolveStatus::Optimal(optimal_value),
797 proof: SolveProof::satisfying(assignment),
798 stats: SolveStats::default(),
799 }
800 }
801
802 /// Creates an approximate result from an incomplete solver.
803 ///
804 /// # Arguments
805 ///
806 /// * `assignment` - The best assignment found
807 /// * `satisfied_clauses` - Number of satisfied clauses
808 /// * `total_clauses` - Total number of clauses
809 /// * `iterations` - Number of solver iterations
810 ///
811 /// # Example
812 ///
813 /// ```
814 /// use xlog_solve::SolveResult;
815 ///
816 /// let result = SolveResult::approximate(vec![true, false], 8, 10, 1000);
817 /// assert!(!result.is_sat()); // Not definitively SAT
818 /// ```
819 #[inline]
820 pub fn approximate(
821 assignment: Vec<bool>,
822 satisfied_clauses: u32,
823 total_clauses: u32,
824 iterations: u32,
825 ) -> Self {
826 Self {
827 status: SolveStatus::Unknown,
828 proof: SolveProof::approximate(
829 assignment,
830 satisfied_clauses,
831 total_clauses,
832 iterations,
833 ),
834 stats: SolveStats::default().with_iterations(iterations),
835 }
836 }
837
838 /// Updates the statistics, consuming and returning self.
839 ///
840 /// # Arguments
841 ///
842 /// * `stats` - The new statistics
843 ///
844 /// # Example
845 ///
846 /// ```
847 /// use xlog_solve::{SolveResult, SolveStats};
848 ///
849 /// let stats = SolveStats::new(100, 5000, 1024);
850 /// let result = SolveResult::satisfiable(vec![true]).with_stats(stats);
851 /// assert_eq!(result.stats.iterations, 100);
852 /// ```
853 #[inline]
854 pub fn with_stats(mut self, stats: SolveStats) -> Self {
855 self.stats = stats;
856 self
857 }
858
859 /// Returns true if the result is satisfiable.
860 ///
861 /// # Example
862 ///
863 /// ```
864 /// use xlog_solve::SolveResult;
865 ///
866 /// assert!(SolveResult::satisfiable(vec![true]).is_sat());
867 /// assert!(!SolveResult::unsatisfiable().is_sat());
868 /// ```
869 #[inline]
870 pub fn is_sat(&self) -> bool {
871 self.status.is_satisfiable()
872 }
873
874 /// Returns true if the result is unsatisfiable.
875 ///
876 /// # Example
877 ///
878 /// ```
879 /// use xlog_solve::SolveResult;
880 ///
881 /// assert!(SolveResult::unsatisfiable().is_unsat());
882 /// assert!(!SolveResult::satisfiable(vec![true]).is_unsat());
883 /// ```
884 #[inline]
885 pub fn is_unsat(&self) -> bool {
886 self.status.is_unsatisfiable()
887 }
888
889 /// Returns the assignment if one is available.
890 ///
891 /// # Example
892 ///
893 /// ```
894 /// use xlog_solve::SolveResult;
895 ///
896 /// let result = SolveResult::satisfiable(vec![true, false]);
897 /// assert_eq!(result.assignment(), Some(&[true, false][..]));
898 /// ```
899 #[inline]
900 pub fn assignment(&self) -> Option<&[bool]> {
901 self.proof.assignment()
902 }
903
904 /// Returns the optimal value if this is a MaxSAT result.
905 ///
906 /// # Example
907 ///
908 /// ```
909 /// use xlog_solve::SolveResult;
910 ///
911 /// let result = SolveResult::optimal(vec![true], 42);
912 /// assert_eq!(result.optimal_value(), Some(42));
913 /// ```
914 #[inline]
915 pub fn optimal_value(&self) -> Option<u64> {
916 self.status.optimal_value()
917 }
918
919 /// Verifies the integrity of the proof checksum.
920 ///
921 /// Returns `Some(true)` if the checksum is valid, `Some(false)` if corrupted,
922 /// or `None` if verification is not applicable.
923 ///
924 /// # Example
925 ///
926 /// ```
927 /// use xlog_solve::SolveResult;
928 ///
929 /// let result = SolveResult::satisfiable(vec![true, false]);
930 /// assert_eq!(result.verify_proof(), Some(true));
931 /// ```
932 #[inline]
933 pub fn verify_proof(&self) -> Option<bool> {
934 self.proof.verify_checksum()
935 }
936}
937
938impl Default for SolveResult {
939 fn default() -> Self {
940 Self {
941 status: SolveStatus::Unknown,
942 proof: SolveProof::None,
943 stats: SolveStats::default(),
944 }
945 }
946}
947
948// =============================================================================
949// Tests
950// =============================================================================
951
952#[cfg(test)]
953mod tests {
954 use super::*;
955
956 // ==========================================================================
957 // SolveProof Tests
958 // ==========================================================================
959
960 #[test]
961 fn test_solve_proof_satisfying() {
962 let assignment = vec![true, false, true];
963 let proof = SolveProof::satisfying(assignment.clone());
964 match proof {
965 SolveProof::Satisfying {
966 assignment: a,
967 checksum,
968 } => {
969 assert_eq!(a, assignment);
970 assert_ne!(checksum, 0); // Checksum should be computed
971 }
972 _ => panic!("Expected Satisfying proof"),
973 }
974 }
975
976 #[test]
977 fn test_solve_proof_unsatisfiable() {
978 let proof = SolveProof::unsatisfiable();
979 match proof {
980 SolveProof::Unsatisfiable { checksum } => {
981 // Checksum for empty unsatisfiability proof should be consistent
982 assert_ne!(checksum, 0);
983 }
984 _ => panic!("Expected Unsatisfiable proof"),
985 }
986 }
987
988 #[test]
989 fn test_solve_proof_approximate() {
990 let assignment = vec![true, false];
991 let proof = SolveProof::approximate(assignment.clone(), 5, 10, 100);
992 match proof {
993 SolveProof::Approximate {
994 assignment: a,
995 satisfied_clauses,
996 total_clauses,
997 iterations,
998 } => {
999 assert_eq!(a, assignment);
1000 assert_eq!(satisfied_clauses, 5);
1001 assert_eq!(total_clauses, 10);
1002 assert_eq!(iterations, 100);
1003 }
1004 _ => panic!("Expected Approximate proof"),
1005 }
1006 }
1007
1008 #[test]
1009 fn test_solve_proof_none() {
1010 let proof = SolveProof::None;
1011 assert!(matches!(proof, SolveProof::None));
1012 }
1013
1014 #[test]
1015 fn test_solve_proof_checksum_deterministic() {
1016 // Same assignment should produce same checksum
1017 let assignment = vec![true, false, true, false];
1018 let proof1 = SolveProof::satisfying(assignment.clone());
1019 let proof2 = SolveProof::satisfying(assignment);
1020
1021 let checksum1 = match proof1 {
1022 SolveProof::Satisfying { checksum, .. } => checksum,
1023 _ => panic!("Expected Satisfying"),
1024 };
1025 let checksum2 = match proof2 {
1026 SolveProof::Satisfying { checksum, .. } => checksum,
1027 _ => panic!("Expected Satisfying"),
1028 };
1029
1030 assert_eq!(checksum1, checksum2);
1031 }
1032
1033 #[test]
1034 fn test_solve_proof_checksum_different_assignments() {
1035 // Different assignments should produce different checksums
1036 let proof1 = SolveProof::satisfying(vec![true, false]);
1037 let proof2 = SolveProof::satisfying(vec![false, true]);
1038
1039 let checksum1 = match proof1 {
1040 SolveProof::Satisfying { checksum, .. } => checksum,
1041 _ => panic!("Expected Satisfying"),
1042 };
1043 let checksum2 = match proof2 {
1044 SolveProof::Satisfying { checksum, .. } => checksum,
1045 _ => panic!("Expected Satisfying"),
1046 };
1047
1048 assert_ne!(checksum1, checksum2);
1049 }
1050
1051 #[test]
1052 fn test_solve_proof_checksum() {
1053 // Test from the task spec
1054 let proof = SolveProof::Satisfying {
1055 assignment: vec![true, false],
1056 checksum: 12345,
1057 };
1058 match proof {
1059 SolveProof::Satisfying { checksum, .. } => {
1060 assert_eq!(checksum, 12345);
1061 }
1062 _ => panic!("Wrong proof type"),
1063 }
1064 }
1065
1066 #[test]
1067 fn test_solve_proof_is_satisfying() {
1068 let sat_proof = SolveProof::satisfying(vec![true]);
1069 assert!(sat_proof.is_satisfying());
1070
1071 let unsat_proof = SolveProof::unsatisfiable();
1072 assert!(!unsat_proof.is_satisfying());
1073
1074 let approx_proof = SolveProof::approximate(vec![true], 1, 1, 1);
1075 assert!(!approx_proof.is_satisfying());
1076
1077 assert!(!SolveProof::None.is_satisfying());
1078 }
1079
1080 #[test]
1081 fn test_solve_proof_assignment() {
1082 let assignment = vec![true, false, true];
1083 let sat_proof = SolveProof::satisfying(assignment.clone());
1084 assert_eq!(sat_proof.assignment(), Some(&assignment[..]));
1085
1086 let approx_proof = SolveProof::approximate(assignment.clone(), 2, 3, 10);
1087 assert_eq!(approx_proof.assignment(), Some(&assignment[..]));
1088
1089 let unsat_proof = SolveProof::unsatisfiable();
1090 assert_eq!(unsat_proof.assignment(), None);
1091
1092 assert_eq!(SolveProof::None.assignment(), None);
1093 }
1094
1095 #[test]
1096 fn test_solve_proof_verify_checksum() {
1097 let proof = SolveProof::satisfying(vec![true, false, true]);
1098 assert_eq!(proof.verify_checksum(), Some(true));
1099
1100 // Manually create a corrupted proof
1101 let corrupted = SolveProof::Satisfying {
1102 assignment: vec![true, false, true],
1103 checksum: 12345, // Wrong checksum
1104 };
1105 assert_eq!(corrupted.verify_checksum(), Some(false));
1106 }
1107
1108 #[test]
1109 fn test_solve_proof_satisfaction_ratio() {
1110 let proof = SolveProof::approximate(vec![true], 8, 10, 100);
1111 assert_eq!(proof.satisfaction_ratio(), Some(0.8));
1112
1113 let full = SolveProof::approximate(vec![true], 10, 10, 100);
1114 assert_eq!(full.satisfaction_ratio(), Some(1.0));
1115
1116 let empty = SolveProof::approximate(vec![], 0, 0, 100);
1117 assert_eq!(empty.satisfaction_ratio(), Some(0.0));
1118
1119 assert_eq!(
1120 SolveProof::satisfying(vec![true]).satisfaction_ratio(),
1121 None
1122 );
1123 }
1124
1125 #[test]
1126 fn test_solve_proof_default() {
1127 let proof = SolveProof::default();
1128 assert!(matches!(proof, SolveProof::None));
1129 }
1130
1131 // ==========================================================================
1132 // SolveStatus Tests
1133 // ==========================================================================
1134
1135 #[test]
1136 fn test_solve_status_sat() {
1137 let status = SolveStatus::Sat;
1138 assert!(status.is_satisfiable());
1139 assert!(!status.is_unsatisfiable());
1140 assert!(status.is_determined());
1141 }
1142
1143 #[test]
1144 fn test_solve_status_unsat() {
1145 let status = SolveStatus::Unsat;
1146 assert!(!status.is_satisfiable());
1147 assert!(status.is_unsatisfiable());
1148 assert!(status.is_determined());
1149 }
1150
1151 #[test]
1152 fn test_solve_status_unknown() {
1153 let status = SolveStatus::Unknown;
1154 assert!(!status.is_satisfiable());
1155 assert!(!status.is_unsatisfiable());
1156 assert!(!status.is_determined());
1157 }
1158
1159 #[test]
1160 fn test_solve_status_optimal() {
1161 let status = SolveStatus::Optimal(42);
1162 assert!(status.is_satisfiable());
1163 assert!(!status.is_unsatisfiable());
1164 assert!(status.is_determined());
1165 assert_eq!(status.optimal_value(), Some(42));
1166 }
1167
1168 #[test]
1169 fn test_solve_status_optimal_value() {
1170 assert_eq!(SolveStatus::Sat.optimal_value(), None);
1171 assert_eq!(SolveStatus::Unsat.optimal_value(), None);
1172 assert_eq!(SolveStatus::Unknown.optimal_value(), None);
1173 assert_eq!(SolveStatus::Optimal(100).optimal_value(), Some(100));
1174 }
1175
1176 #[test]
1177 fn test_solve_status_eq() {
1178 assert_eq!(SolveStatus::Sat, SolveStatus::Sat);
1179 assert_eq!(SolveStatus::Unsat, SolveStatus::Unsat);
1180 assert_eq!(SolveStatus::Unknown, SolveStatus::Unknown);
1181 assert_eq!(SolveStatus::Optimal(5), SolveStatus::Optimal(5));
1182 assert_ne!(SolveStatus::Optimal(5), SolveStatus::Optimal(6));
1183 assert_ne!(SolveStatus::Sat, SolveStatus::Unsat);
1184 }
1185
1186 #[test]
1187 fn test_solve_status_default() {
1188 let status = SolveStatus::default();
1189 assert!(matches!(status, SolveStatus::Unknown));
1190 }
1191
1192 // ==========================================================================
1193 // SolveStats Tests
1194 // ==========================================================================
1195
1196 #[test]
1197 fn test_solve_stats_default() {
1198 let stats = SolveStats::default();
1199 assert_eq!(stats.iterations, 0);
1200 assert_eq!(stats.duration_us, 0);
1201 assert_eq!(stats.peak_memory, 0);
1202 }
1203
1204 #[test]
1205 fn test_solve_stats_new() {
1206 let stats = SolveStats::new(100, 5000, 1024);
1207 assert_eq!(stats.iterations, 100);
1208 assert_eq!(stats.duration_us, 5000);
1209 assert_eq!(stats.peak_memory, 1024);
1210 }
1211
1212 #[test]
1213 fn test_solve_stats_with_iterations() {
1214 let stats = SolveStats::default().with_iterations(500);
1215 assert_eq!(stats.iterations, 500);
1216 assert_eq!(stats.duration_us, 0);
1217 assert_eq!(stats.peak_memory, 0);
1218 }
1219
1220 #[test]
1221 fn test_solve_stats_with_duration_us() {
1222 let stats = SolveStats::default().with_duration_us(10000);
1223 assert_eq!(stats.iterations, 0);
1224 assert_eq!(stats.duration_us, 10000);
1225 assert_eq!(stats.peak_memory, 0);
1226 }
1227
1228 #[test]
1229 fn test_solve_stats_with_peak_memory() {
1230 let stats = SolveStats::default().with_peak_memory(2048);
1231 assert_eq!(stats.iterations, 0);
1232 assert_eq!(stats.duration_us, 0);
1233 assert_eq!(stats.peak_memory, 2048);
1234 }
1235
1236 #[test]
1237 fn test_solve_stats_chained_builders() {
1238 let stats = SolveStats::default()
1239 .with_iterations(100)
1240 .with_duration_us(5000)
1241 .with_peak_memory(1024);
1242 assert_eq!(stats.iterations, 100);
1243 assert_eq!(stats.duration_us, 5000);
1244 assert_eq!(stats.peak_memory, 1024);
1245 }
1246
1247 #[test]
1248 fn test_solve_stats_duration_ms() {
1249 let stats = SolveStats::new(0, 5500, 0);
1250 assert_eq!(stats.duration_ms(), 5);
1251 }
1252
1253 #[test]
1254 fn test_solve_stats_duration_secs() {
1255 let stats = SolveStats::new(0, 1_500_000, 0);
1256 assert_eq!(stats.duration_secs(), 1.5);
1257 }
1258
1259 #[test]
1260 fn test_solve_stats_iterations_per_sec() {
1261 let stats = SolveStats::new(1000, 1_000_000, 0);
1262 assert_eq!(stats.iterations_per_sec(), 1000.0);
1263
1264 let zero = SolveStats::new(1000, 0, 0);
1265 assert_eq!(zero.iterations_per_sec(), 0.0);
1266 }
1267
1268 // ==========================================================================
1269 // SolveResult Tests
1270 // ==========================================================================
1271
1272 #[test]
1273 fn test_solve_result_sat() {
1274 let result = SolveResult::satisfiable(vec![true, false, true]);
1275 assert!(matches!(result.status, SolveStatus::Sat));
1276 assert!(result.proof.is_satisfying());
1277 }
1278
1279 #[test]
1280 fn test_solve_result_unsat() {
1281 let result = SolveResult::unsatisfiable();
1282 assert!(matches!(result.status, SolveStatus::Unsat));
1283 assert!(matches!(result.proof, SolveProof::Unsatisfiable { .. }));
1284 }
1285
1286 #[test]
1287 fn test_solve_result_unknown() {
1288 let result = SolveResult::unknown(500);
1289 assert!(matches!(result.status, SolveStatus::Unknown));
1290 assert_eq!(result.stats.iterations, 500);
1291 }
1292
1293 #[test]
1294 fn test_solve_result_optimal() {
1295 let result = SolveResult::optimal(vec![true, false], 42);
1296 assert!(matches!(result.status, SolveStatus::Optimal(42)));
1297 assert!(result.proof.is_satisfying());
1298 }
1299
1300 #[test]
1301 fn test_solve_result_with_stats() {
1302 let stats = SolveStats::new(100, 5000, 1024);
1303 let result = SolveResult::satisfiable(vec![true]).with_stats(stats);
1304 assert_eq!(result.stats.iterations, 100);
1305 assert_eq!(result.stats.duration_us, 5000);
1306 assert_eq!(result.stats.peak_memory, 1024);
1307 }
1308
1309 #[test]
1310 fn test_solve_result_is_sat() {
1311 assert!(SolveResult::satisfiable(vec![true]).is_sat());
1312 assert!(!SolveResult::unsatisfiable().is_sat());
1313 assert!(!SolveResult::unknown(0).is_sat());
1314 assert!(SolveResult::optimal(vec![true], 5).is_sat());
1315 }
1316
1317 #[test]
1318 fn test_solve_result_is_unsat() {
1319 assert!(!SolveResult::satisfiable(vec![true]).is_unsat());
1320 assert!(SolveResult::unsatisfiable().is_unsat());
1321 assert!(!SolveResult::unknown(0).is_unsat());
1322 assert!(!SolveResult::optimal(vec![true], 5).is_unsat());
1323 }
1324
1325 #[test]
1326 fn test_solve_result_assignment() {
1327 let assignment = vec![true, false, true];
1328 let sat_result = SolveResult::satisfiable(assignment.clone());
1329 assert_eq!(sat_result.assignment(), Some(&assignment[..]));
1330
1331 let unsat_result = SolveResult::unsatisfiable();
1332 assert_eq!(unsat_result.assignment(), None);
1333 }
1334
1335 #[test]
1336 fn test_solve_result_approximate() {
1337 let result = SolveResult::approximate(vec![true, false], 8, 10, 1000);
1338 assert!(matches!(result.status, SolveStatus::Unknown));
1339 match &result.proof {
1340 SolveProof::Approximate {
1341 satisfied_clauses,
1342 total_clauses,
1343 iterations,
1344 ..
1345 } => {
1346 assert_eq!(*satisfied_clauses, 8);
1347 assert_eq!(*total_clauses, 10);
1348 assert_eq!(*iterations, 1000);
1349 }
1350 _ => panic!("Expected Approximate proof"),
1351 }
1352 }
1353
1354 #[test]
1355 fn test_solve_result_optimal_value() {
1356 let result = SolveResult::optimal(vec![true], 42);
1357 assert_eq!(result.optimal_value(), Some(42));
1358
1359 let sat = SolveResult::satisfiable(vec![true]);
1360 assert_eq!(sat.optimal_value(), None);
1361 }
1362
1363 #[test]
1364 fn test_solve_result_verify_proof() {
1365 let result = SolveResult::satisfiable(vec![true, false]);
1366 assert_eq!(result.verify_proof(), Some(true));
1367 }
1368
1369 #[test]
1370 fn test_solve_result_default() {
1371 let result = SolveResult::default();
1372 assert!(matches!(result.status, SolveStatus::Unknown));
1373 assert!(matches!(result.proof, SolveProof::None));
1374 }
1375
1376 // ==========================================================================
1377 // FNV-1a Checksum Tests
1378 // ==========================================================================
1379
1380 #[test]
1381 fn test_compute_checksum_empty() {
1382 let checksum = compute_checksum(&[]);
1383 // FNV-1a offset basis
1384 assert_eq!(checksum, 0xcbf29ce484222325);
1385 }
1386
1387 #[test]
1388 fn test_compute_checksum_single() {
1389 let checksum_true = compute_checksum(&[true]);
1390 let checksum_false = compute_checksum(&[false]);
1391 assert_ne!(checksum_true, checksum_false);
1392 }
1393
1394 #[test]
1395 fn test_compute_checksum_deterministic() {
1396 let assignment = vec![true, false, true, false, true];
1397 let c1 = compute_checksum(&assignment);
1398 let c2 = compute_checksum(&assignment);
1399 assert_eq!(c1, c2);
1400 }
1401
1402 #[test]
1403 fn test_compute_checksum_order_sensitive() {
1404 // Different order should produce different checksum
1405 let c1 = compute_checksum(&[true, false]);
1406 let c2 = compute_checksum(&[false, true]);
1407 assert_ne!(c1, c2);
1408 }
1409}