Skip to main content

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}