Skip to main content

xlog_solve/
instance.rs

1//! SAT instance representation types.
2//!
3//! This module provides the core types for representing SAT and MaxSAT instances
4//! in Conjunctive Normal Form (CNF). The representation is designed for efficient
5//! evaluation and GPU-friendly memory layouts.
6//!
7//! # Types
8//!
9//! - [`Literal`]: A propositional variable with optional negation
10//! - [`Clause`]: A disjunction (OR) of literals
11//! - [`Objective`]: Optimization objective (satisfaction, MaxSAT, etc.)
12//! - [`SolveInstance`]: A complete SAT/MaxSAT instance
13//!
14//! # Example
15//!
16//! ```
17//! use xlog_solve::{SolveInstance, Clause, Literal};
18//!
19//! // Encode: (x0 OR NOT x1) AND (x1 OR x2)
20//! let instance = SolveInstance::new(3, vec![
21//!     Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
22//!     Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
23//! ]);
24//!
25//! // Check if an assignment satisfies the formula
26//! let assignment = vec![true, false, true];
27//! assert!(instance.is_satisfied(&assignment));
28//! ```
29
30use std::cmp::Ordering;
31
32// =============================================================================
33// Literal - A propositional variable with optional negation
34// =============================================================================
35
36/// A propositional variable with optional negation.
37///
38/// Literals are the atomic units of SAT formulas. A literal is either a variable
39/// (positive literal) or the negation of a variable (negative literal).
40///
41/// Variables are 0-indexed internally but convert to 1-indexed for DIMACS format.
42///
43/// # Memory Layout
44///
45/// The struct is designed for efficient GPU transfer with a compact representation:
46/// - `var`: 4 bytes (u32 variable index)
47/// - `negated`: 1 byte (bool), with potential padding
48///
49/// For bulk GPU operations, consider using the packed representation via `to_packed()`.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
51pub struct Literal {
52    /// The variable index (0-indexed).
53    pub var: u32,
54    /// Whether this literal is negated.
55    pub negated: bool,
56}
57
58impl Literal {
59    /// Creates a positive literal (variable without negation).
60    ///
61    /// # Arguments
62    ///
63    /// * `var` - The variable index (0-indexed)
64    ///
65    /// # Example
66    ///
67    /// ```
68    /// use xlog_solve::Literal;
69    /// let lit = Literal::positive(3);
70    /// assert_eq!(lit.var, 3);
71    /// assert!(!lit.negated);
72    /// ```
73    #[inline]
74    pub const fn positive(var: u32) -> Self {
75        Self {
76            var,
77            negated: false,
78        }
79    }
80
81    /// Creates a negative literal (negated variable).
82    ///
83    /// # Arguments
84    ///
85    /// * `var` - The variable index (0-indexed)
86    ///
87    /// # Example
88    ///
89    /// ```
90    /// use xlog_solve::Literal;
91    /// let lit = Literal::negative(3);
92    /// assert_eq!(lit.var, 3);
93    /// assert!(lit.negated);
94    /// ```
95    #[inline]
96    pub const fn negative(var: u32) -> Self {
97        Self { var, negated: true }
98    }
99
100    /// Creates a literal from a variable and sign.
101    ///
102    /// # Arguments
103    ///
104    /// * `var` - The variable index (0-indexed)
105    /// * `negated` - Whether the literal is negated
106    #[inline]
107    pub const fn new(var: u32, negated: bool) -> Self {
108        Self { var, negated }
109    }
110
111    /// Returns the negation of this literal.
112    ///
113    /// # Example
114    ///
115    /// ```
116    /// use xlog_solve::Literal;
117    /// let pos = Literal::positive(5);
118    /// let neg = pos.negate();
119    /// assert!(neg.negated);
120    /// assert_eq!(neg.var, 5);
121    /// ```
122    #[inline]
123    pub const fn negate(self) -> Self {
124        Self {
125            var: self.var,
126            negated: !self.negated,
127        }
128    }
129
130    /// Evaluates this literal under a given assignment.
131    ///
132    /// # Arguments
133    ///
134    /// * `assignment` - A slice of boolean values where index i represents variable i
135    ///
136    /// # Returns
137    ///
138    /// `true` if the literal is satisfied by the assignment, `false` otherwise.
139    ///
140    /// # Panics
141    ///
142    /// Panics if `self.var >= assignment.len()` (variable index out of bounds).
143    ///
144    /// # Example
145    ///
146    /// ```
147    /// use xlog_solve::Literal;
148    /// let lit = Literal::positive(1);
149    /// assert!(lit.eval(&[false, true, false]));  // x1 = true
150    /// ```
151    #[inline]
152    pub fn eval(self, assignment: &[bool]) -> bool {
153        let value = assignment[self.var as usize];
154        if self.negated {
155            !value
156        } else {
157            value
158        }
159    }
160
161    /// Converts this literal to DIMACS format.
162    ///
163    /// DIMACS format uses 1-indexed variables, with negative numbers for negated literals.
164    ///
165    /// # Returns
166    ///
167    /// A signed integer where:
168    /// - Positive values represent positive literals (var + 1)
169    /// - Negative values represent negative literals (-(var + 1))
170    ///
171    /// # Example
172    ///
173    /// ```
174    /// use xlog_solve::Literal;
175    /// assert_eq!(Literal::positive(0).to_dimacs(), 1);
176    /// assert_eq!(Literal::negative(2).to_dimacs(), -3);
177    /// ```
178    #[inline]
179    pub fn to_dimacs(self) -> i32 {
180        let var_1indexed = (self.var + 1) as i32;
181        if self.negated {
182            -var_1indexed
183        } else {
184            var_1indexed
185        }
186    }
187
188    /// Creates a literal from DIMACS format.
189    ///
190    /// DIMACS format uses 1-indexed variables, with negative numbers for negated literals.
191    ///
192    /// # Arguments
193    ///
194    /// * `dimacs` - A non-zero signed integer in DIMACS format
195    ///
196    /// # Panics
197    ///
198    /// Panics if `dimacs == 0` (zero is not a valid DIMACS literal).
199    ///
200    /// # Example
201    ///
202    /// ```
203    /// use xlog_solve::Literal;
204    /// let pos = Literal::from_dimacs(3);
205    /// assert_eq!(pos.var, 2);
206    /// assert!(!pos.negated);
207    ///
208    /// let neg = Literal::from_dimacs(-5);
209    /// assert_eq!(neg.var, 4);
210    /// assert!(neg.negated);
211    /// ```
212    #[inline]
213    pub fn from_dimacs(dimacs: i32) -> Self {
214        assert!(dimacs != 0, "DIMACS literal cannot be zero");
215        let negated = dimacs < 0;
216        let var = dimacs.unsigned_abs() - 1;
217        Self { var, negated }
218    }
219
220    /// Returns a packed 32-bit representation suitable for GPU transfer.
221    ///
222    /// The packed format stores the variable in the lower 31 bits and the
223    /// negation flag in the most significant bit.
224    ///
225    /// # Returns
226    ///
227    /// A u32 where bit 31 is the negation flag and bits 0-30 are the variable.
228    #[inline]
229    pub fn to_packed(self) -> u32 {
230        if self.negated {
231            self.var | 0x8000_0000
232        } else {
233            self.var
234        }
235    }
236
237    /// Creates a literal from a packed 32-bit representation.
238    ///
239    /// # Arguments
240    ///
241    /// * `packed` - A u32 with negation in bit 31 and variable in bits 0-30
242    #[inline]
243    pub fn from_packed(packed: u32) -> Self {
244        let negated = (packed & 0x8000_0000) != 0;
245        let var = packed & 0x7FFF_FFFF;
246        Self { var, negated }
247    }
248}
249
250impl PartialOrd for Literal {
251    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
252        Some(self.cmp(other))
253    }
254}
255
256impl Ord for Literal {
257    /// Orders literals first by variable index, then by negation (positive before negative).
258    fn cmp(&self, other: &Self) -> Ordering {
259        match self.var.cmp(&other.var) {
260            Ordering::Equal => self.negated.cmp(&other.negated),
261            ord => ord,
262        }
263    }
264}
265
266// =============================================================================
267// Clause - A disjunction (OR) of literals
268// =============================================================================
269
270/// A clause is a disjunction (OR) of literals.
271///
272/// In CNF (Conjunctive Normal Form), a formula is a conjunction (AND) of clauses,
273/// where each clause is a disjunction (OR) of literals.
274///
275/// # Properties
276///
277/// - An empty clause is always unsatisfied (represents `false`)
278/// - A clause is satisfied if at least one of its literals is satisfied
279/// - Unit clauses (single literal) are important for unit propagation
280///
281/// # Memory Layout
282///
283/// The clause stores literals in a `Vec` which allows efficient iteration
284/// and provides good cache locality for clause evaluation.
285#[derive(Debug, Clone, PartialEq, Eq, Hash)]
286pub struct Clause {
287    /// The literals in this clause.
288    pub literals: Vec<Literal>,
289}
290
291impl Clause {
292    /// Creates a new clause from a vector of literals.
293    ///
294    /// # Arguments
295    ///
296    /// * `literals` - The literals forming this disjunction
297    ///
298    /// # Example
299    ///
300    /// ```
301    /// use xlog_solve::{Clause, Literal};
302    /// // Create clause: (x0 OR NOT x1)
303    /// let clause = Clause::new(vec![
304    ///     Literal::positive(0),
305    ///     Literal::negative(1),
306    /// ]);
307    /// ```
308    #[inline]
309    pub fn new(literals: Vec<Literal>) -> Self {
310        Self { literals }
311    }
312
313    /// Creates a unit clause (single literal).
314    ///
315    /// Unit clauses are important in SAT solving for unit propagation.
316    ///
317    /// # Arguments
318    ///
319    /// * `literal` - The single literal in this clause
320    #[inline]
321    pub fn unit(literal: Literal) -> Self {
322        Self {
323            literals: vec![literal],
324        }
325    }
326
327    /// Creates a binary clause (two literals).
328    ///
329    /// Binary clauses are common in many SAT encodings (implications, at-most-one, etc.).
330    ///
331    /// # Arguments
332    ///
333    /// * `a` - The first literal
334    /// * `b` - The second literal
335    #[inline]
336    pub fn binary(a: Literal, b: Literal) -> Self {
337        Self {
338            literals: vec![a, b],
339        }
340    }
341
342    /// Creates a ternary clause (three literals).
343    ///
344    /// # Arguments
345    ///
346    /// * `a` - The first literal
347    /// * `b` - The second literal
348    /// * `c` - The third literal
349    #[inline]
350    pub fn ternary(a: Literal, b: Literal, c: Literal) -> Self {
351        Self {
352            literals: vec![a, b, c],
353        }
354    }
355
356    /// Returns the number of literals in this clause.
357    #[inline]
358    pub fn len(&self) -> usize {
359        self.literals.len()
360    }
361
362    /// Returns true if this clause has no literals.
363    ///
364    /// An empty clause is always unsatisfied.
365    #[inline]
366    pub fn is_empty(&self) -> bool {
367        self.literals.is_empty()
368    }
369
370    /// Checks if this clause is satisfied by the given assignment.
371    ///
372    /// A clause is satisfied if at least one of its literals evaluates to true.
373    /// An empty clause is never satisfied.
374    ///
375    /// # Arguments
376    ///
377    /// * `assignment` - A slice of boolean values for each variable
378    ///
379    /// # Returns
380    ///
381    /// `true` if the clause is satisfied, `false` otherwise.
382    ///
383    /// # Example
384    ///
385    /// ```
386    /// use xlog_solve::{Clause, Literal};
387    /// let clause = Clause::new(vec![
388    ///     Literal::positive(0),
389    ///     Literal::negative(1),
390    /// ]);
391    /// assert!(clause.is_satisfied(&[true, true]));  // x0=true satisfies
392    /// assert!(!clause.is_satisfied(&[false, true])); // neither satisfied
393    /// ```
394    #[inline]
395    pub fn is_satisfied(&self, assignment: &[bool]) -> bool {
396        self.literals.iter().any(|lit| lit.eval(assignment))
397    }
398
399    /// Counts how many literals in this clause are satisfied by the assignment.
400    ///
401    /// This is useful for CLS solvers and MaxSAT evaluation.
402    ///
403    /// # Arguments
404    ///
405    /// * `assignment` - A slice of boolean values for each variable
406    ///
407    /// # Returns
408    ///
409    /// The number of literals that evaluate to true.
410    #[inline]
411    pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
412        self.literals
413            .iter()
414            .filter(|lit| lit.eval(assignment))
415            .count()
416    }
417
418    /// Returns an iterator over the literals in this clause.
419    #[inline]
420    pub fn iter(&self) -> impl Iterator<Item = &Literal> {
421        self.literals.iter()
422    }
423
424    /// Returns an iterator over the variable indices in this clause.
425    #[inline]
426    pub fn vars(&self) -> impl Iterator<Item = u32> + '_ {
427        self.literals.iter().map(|lit| lit.var)
428    }
429}
430
431impl IntoIterator for Clause {
432    type Item = Literal;
433    type IntoIter = std::vec::IntoIter<Literal>;
434
435    fn into_iter(self) -> Self::IntoIter {
436        self.literals.into_iter()
437    }
438}
439
440impl<'a> IntoIterator for &'a Clause {
441    type Item = &'a Literal;
442    type IntoIter = std::slice::Iter<'a, Literal>;
443
444    fn into_iter(self) -> Self::IntoIter {
445        self.literals.iter()
446    }
447}
448
449impl FromIterator<Literal> for Clause {
450    fn from_iter<I: IntoIterator<Item = Literal>>(iter: I) -> Self {
451        Self {
452            literals: iter.into_iter().collect(),
453        }
454    }
455}
456
457// =============================================================================
458// Objective - Optimization objective for solving
459// =============================================================================
460
461/// The optimization objective for a SAT/MaxSAT instance.
462///
463/// This determines what the solver should optimize for:
464/// - `Satisfaction`: Find any satisfying assignment (standard SAT)
465/// - `MaxSat`: Maximize the number/weight of satisfied clauses
466/// - `MinUnsat`: Minimize the number/weight of unsatisfied clauses
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
468pub enum Objective {
469    /// Find any satisfying assignment (decision problem).
470    ///
471    /// The solver returns SAT if an assignment exists that satisfies all clauses,
472    /// or UNSAT if no such assignment exists.
473    #[default]
474    Satisfaction,
475
476    /// Maximize the total weight of satisfied clauses (optimization).
477    ///
478    /// For unweighted instances, this maximizes the count of satisfied clauses.
479    /// The solver finds an assignment that maximizes the objective.
480    MaxSat,
481
482    /// Minimize the total weight of unsatisfied clauses (optimization).
483    ///
484    /// This is equivalent to MaxSAT for complete solvers but may differ
485    /// for incomplete/approximate solvers.
486    MinUnsat,
487}
488
489// =============================================================================
490// SolveInstance - A complete SAT/MaxSAT instance
491// =============================================================================
492
493/// A SAT or MaxSAT instance in Conjunctive Normal Form (CNF).
494///
495/// This struct represents the complete problem to be solved:
496/// - A set of propositional variables (indexed 0 to num_vars-1)
497/// - A conjunction of clauses (each clause is a disjunction of literals)
498/// - Optional weights for MaxSAT
499/// - An optimization objective
500///
501/// # CNF Semantics
502///
503/// The formula represented is:
504/// ```text
505/// clause[0] AND clause[1] AND ... AND clause[n-1]
506/// ```
507///
508/// where each clause is:
509/// ```text
510/// lit[0] OR lit[1] OR ... OR lit[k-1]
511/// ```
512///
513/// # Memory Layout
514///
515/// The instance is designed for efficient GPU transfer:
516/// - Clauses can be flattened to a contiguous literal array with offset indices
517/// - Weights align with clause indices for vectorized operations
518///
519/// # Example
520///
521/// ```
522/// use xlog_solve::{SolveInstance, Clause, Literal, Objective};
523///
524/// // (x0 OR NOT x1) AND (x1 OR x2)
525/// let instance = SolveInstance::new(3, vec![
526///     Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
527///     Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
528/// ]);
529///
530/// assert_eq!(instance.num_vars, 3);
531/// assert_eq!(instance.clauses.len(), 2);
532/// assert_eq!(instance.objective, Objective::Satisfaction);
533/// ```
534#[derive(Debug, Clone)]
535pub struct SolveInstance {
536    /// Number of propositional variables in this instance.
537    ///
538    /// Variables are indexed from 0 to `num_vars - 1`.
539    pub num_vars: u32,
540
541    /// The clauses forming this CNF formula.
542    ///
543    /// The formula is satisfied when ALL clauses are satisfied.
544    pub clauses: Vec<Clause>,
545
546    /// Optional weights for each clause (for weighted MaxSAT).
547    ///
548    /// If `Some`, must have the same length as `clauses`.
549    /// If `None`, all clauses have implicit weight 1.0.
550    pub weights: Option<Vec<f64>>,
551
552    /// The optimization objective.
553    pub objective: Objective,
554}
555
556impl SolveInstance {
557    /// Creates a new SAT instance with the given variables and clauses.
558    ///
559    /// The instance uses `Objective::Satisfaction` (standard SAT).
560    ///
561    /// # Arguments
562    ///
563    /// * `num_vars` - The number of propositional variables
564    /// * `clauses` - The clauses forming the CNF formula
565    ///
566    /// # Example
567    ///
568    /// ```
569    /// use xlog_solve::{SolveInstance, Clause, Literal};
570    ///
571    /// let instance = SolveInstance::new(3, vec![
572    ///     Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
573    ///     Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
574    /// ]);
575    /// ```
576    #[inline]
577    pub fn new(num_vars: u32, clauses: Vec<Clause>) -> Self {
578        Self {
579            num_vars,
580            clauses,
581            weights: None,
582            objective: Objective::Satisfaction,
583        }
584    }
585
586    /// Creates a weighted MaxSAT instance.
587    ///
588    /// # Arguments
589    ///
590    /// * `num_vars` - The number of propositional variables
591    /// * `clauses` - The clauses forming the CNF formula
592    /// * `weights` - Weights for each clause (must match clause count)
593    ///
594    /// # Panics
595    ///
596    /// Panics if `weights.len() != clauses.len()`.
597    ///
598    /// # Example
599    ///
600    /// ```
601    /// use xlog_solve::{SolveInstance, Clause, Literal, Objective};
602    ///
603    /// let instance = SolveInstance::with_weights(
604    ///     2,
605    ///     vec![
606    ///         Clause::new(vec![Literal::positive(0)]),
607    ///         Clause::new(vec![Literal::positive(1)]),
608    ///     ],
609    ///     vec![1.0, 2.0],  // Clause 1 has twice the weight
610    /// );
611    ///
612    /// assert_eq!(instance.objective, Objective::MaxSat);
613    /// ```
614    #[inline]
615    pub fn with_weights(num_vars: u32, clauses: Vec<Clause>, weights: Vec<f64>) -> Self {
616        assert_eq!(
617            clauses.len(),
618            weights.len(),
619            "Number of weights ({}) must match number of clauses ({})",
620            weights.len(),
621            clauses.len()
622        );
623        Self {
624            num_vars,
625            clauses,
626            weights: Some(weights),
627            objective: Objective::MaxSat,
628        }
629    }
630
631    /// Adds a clause to this instance.
632    ///
633    /// # Arguments
634    ///
635    /// * `clause` - The clause to add
636    ///
637    /// # Note
638    ///
639    /// If the instance has weights, the new clause gets weight 1.0.
640    #[inline]
641    pub fn add_clause(&mut self, clause: Clause) {
642        self.clauses.push(clause);
643        if let Some(ref mut weights) = self.weights {
644            weights.push(1.0);
645        }
646    }
647
648    /// Adds a weighted clause to this instance.
649    ///
650    /// If the instance doesn't have weights yet, this initializes weights
651    /// with 1.0 for all existing clauses.
652    ///
653    /// # Arguments
654    ///
655    /// * `clause` - The clause to add
656    /// * `weight` - The weight for this clause
657    #[inline]
658    pub fn add_weighted_clause(&mut self, clause: Clause, weight: f64) {
659        if self.weights.is_none() {
660            self.weights = Some(vec![1.0; self.clauses.len()]);
661        }
662        self.clauses.push(clause);
663        self.weights.as_mut().unwrap().push(weight);
664    }
665
666    /// Returns the number of clauses in this instance.
667    #[inline]
668    pub fn num_clauses(&self) -> usize {
669        self.clauses.len()
670    }
671
672    /// Checks if all clauses are satisfied by the given assignment.
673    ///
674    /// # Arguments
675    ///
676    /// * `assignment` - A slice of boolean values, one per variable
677    ///
678    /// # Returns
679    ///
680    /// `true` if all clauses are satisfied, `false` otherwise.
681    ///
682    /// # Example
683    ///
684    /// ```
685    /// use xlog_solve::{SolveInstance, Clause, Literal};
686    ///
687    /// let instance = SolveInstance::new(2, vec![
688    ///     Clause::new(vec![Literal::positive(0)]),
689    ///     Clause::new(vec![Literal::positive(1)]),
690    /// ]);
691    ///
692    /// assert!(instance.is_satisfied(&[true, true]));
693    /// assert!(!instance.is_satisfied(&[true, false]));
694    /// ```
695    #[inline]
696    pub fn is_satisfied(&self, assignment: &[bool]) -> bool {
697        self.clauses
698            .iter()
699            .all(|clause| clause.is_satisfied(assignment))
700    }
701
702    /// Counts how many clauses are satisfied by the given assignment.
703    ///
704    /// # Arguments
705    ///
706    /// * `assignment` - A slice of boolean values, one per variable
707    ///
708    /// # Returns
709    ///
710    /// The number of satisfied clauses.
711    #[inline]
712    pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
713        self.clauses
714            .iter()
715            .filter(|clause| clause.is_satisfied(assignment))
716            .count()
717    }
718
719    /// Computes the weighted satisfaction score for the given assignment.
720    ///
721    /// If weights are specified, returns the sum of weights of satisfied clauses.
722    /// If no weights are specified, each clause has implicit weight 1.0.
723    ///
724    /// # Arguments
725    ///
726    /// * `assignment` - A slice of boolean values, one per variable
727    ///
728    /// # Returns
729    ///
730    /// The total weight of satisfied clauses.
731    #[inline]
732    pub fn weighted_satisfaction(&self, assignment: &[bool]) -> f64 {
733        match &self.weights {
734            Some(weights) => self
735                .clauses
736                .iter()
737                .zip(weights.iter())
738                .filter(|(clause, _)| clause.is_satisfied(assignment))
739                .map(|(_, weight)| *weight)
740                .sum(),
741            None => self.count_satisfied(assignment) as f64,
742        }
743    }
744
745    /// Returns the total weight of all clauses.
746    ///
747    /// For unweighted instances, returns the number of clauses.
748    #[inline]
749    pub fn total_weight(&self) -> f64 {
750        match &self.weights {
751            Some(weights) => weights.iter().sum(),
752            None => self.clauses.len() as f64,
753        }
754    }
755
756    /// Returns the satisfaction ratio (satisfied weight / total weight).
757    ///
758    /// # Arguments
759    ///
760    /// * `assignment` - A slice of boolean values, one per variable
761    ///
762    /// # Returns
763    ///
764    /// A value in [0.0, 1.0] representing the fraction of weight satisfied.
765    /// Returns 0.0 for empty instances (no clauses).
766    #[inline]
767    pub fn satisfaction_ratio(&self, assignment: &[bool]) -> f64 {
768        let total = self.total_weight();
769        if total == 0.0 {
770            0.0
771        } else {
772            self.weighted_satisfaction(assignment) / total
773        }
774    }
775
776    /// Returns an iterator over all clauses.
777    #[inline]
778    pub fn iter_clauses(&self) -> impl Iterator<Item = &Clause> {
779        self.clauses.iter()
780    }
781
782    /// Returns an iterator over clauses with their weights.
783    ///
784    /// If no weights are specified, uses 1.0 for each clause.
785    pub fn iter_weighted(&self) -> impl Iterator<Item = (&Clause, f64)> {
786        let weights = self.weights.as_ref();
787        self.clauses.iter().enumerate().map(move |(i, clause)| {
788            let weight = weights.map_or(1.0, |w| w[i]);
789            (clause, weight)
790        })
791    }
792
793    /// Validates that all variable indices in clauses are within bounds.
794    ///
795    /// # Returns
796    ///
797    /// `true` if all variable indices are less than `num_vars`, `false` otherwise.
798    pub fn validate(&self) -> bool {
799        self.clauses
800            .iter()
801            .all(|clause| clause.literals.iter().all(|lit| lit.var < self.num_vars))
802    }
803
804    /// Returns the maximum variable index used in any clause.
805    ///
806    /// Returns `None` if there are no clauses or all clauses are empty.
807    pub fn max_var(&self) -> Option<u32> {
808        self.clauses
809            .iter()
810            .flat_map(|c| c.literals.iter())
811            .map(|lit| lit.var)
812            .max()
813    }
814
815    /// Returns the total number of literals across all clauses.
816    pub fn total_literals(&self) -> usize {
817        self.clauses.iter().map(|c| c.len()).sum()
818    }
819}
820
821impl Default for SolveInstance {
822    fn default() -> Self {
823        Self {
824            num_vars: 0,
825            clauses: Vec::new(),
826            weights: None,
827            objective: Objective::Satisfaction,
828        }
829    }
830}
831
832// Tests first (TDD approach)
833#[cfg(test)]
834mod tests {
835    use super::*;
836
837    #[test]
838    fn test_literal_new() {
839        let pos = Literal::positive(5);
840        assert_eq!(pos.var, 5);
841        assert!(!pos.negated);
842
843        let neg = Literal::negative(3);
844        assert_eq!(neg.var, 3);
845        assert!(neg.negated);
846    }
847
848    #[test]
849    fn test_literal_eval() {
850        let pos = Literal::positive(1);
851        let neg = Literal::negative(1);
852
853        // Assignment: x0=true, x1=false, x2=true
854        let assignment = vec![true, false, true];
855
856        // x1 is false, so positive literal evaluates to false
857        assert!(!pos.eval(&assignment));
858        // NOT x1 evaluates to true (since x1=false)
859        assert!(neg.eval(&assignment));
860
861        // Test with x2 (index 2, value true)
862        let pos2 = Literal::positive(2);
863        let neg2 = Literal::negative(2);
864        assert!(pos2.eval(&assignment));
865        assert!(!neg2.eval(&assignment));
866    }
867
868    #[test]
869    fn test_literal_to_dimacs() {
870        let pos = Literal::positive(3);
871        let neg = Literal::negative(7);
872
873        // DIMACS uses 1-indexed variables
874        assert_eq!(pos.to_dimacs(), 4); // var 3 -> 4 in DIMACS (1-indexed)
875        assert_eq!(neg.to_dimacs(), -8); // var 7 negated -> -8 in DIMACS
876    }
877
878    #[test]
879    fn test_literal_from_dimacs() {
880        // DIMACS variable 4 (positive) -> var index 3
881        let pos = Literal::from_dimacs(4);
882        assert_eq!(pos.var, 3);
883        assert!(!pos.negated);
884
885        // DIMACS variable -8 (negative) -> var index 7
886        let neg = Literal::from_dimacs(-8);
887        assert_eq!(neg.var, 7);
888        assert!(neg.negated);
889    }
890
891    #[test]
892    fn test_clause_new() {
893        let clause = Clause::new(vec![Literal::positive(1), Literal::negative(2)]);
894        assert_eq!(clause.literals.len(), 2);
895    }
896
897    #[test]
898    fn test_clause_is_satisfied() {
899        // Clause: (x0 OR NOT x1)
900        let clause = Clause::new(vec![Literal::positive(0), Literal::negative(1)]);
901
902        // x0=true, x1=false -> satisfied (both literals true)
903        assert!(clause.is_satisfied(&[true, false]));
904
905        // x0=true, x1=true -> satisfied (x0 is true)
906        assert!(clause.is_satisfied(&[true, true]));
907
908        // x0=false, x1=false -> satisfied (NOT x1 is true)
909        assert!(clause.is_satisfied(&[false, false]));
910
911        // x0=false, x1=true -> NOT satisfied (both literals false)
912        assert!(!clause.is_satisfied(&[false, true]));
913    }
914
915    #[test]
916    fn test_clause_count_satisfied() {
917        // Clause: (x0 OR NOT x1 OR x2)
918        let clause = Clause::new(vec![
919            Literal::positive(0),
920            Literal::negative(1),
921            Literal::positive(2),
922        ]);
923
924        // x0=true, x1=false, x2=true -> all 3 literals satisfied
925        assert_eq!(clause.count_satisfied(&[true, false, true]), 3);
926
927        // x0=true, x1=true, x2=false -> only x0 satisfied
928        assert_eq!(clause.count_satisfied(&[true, true, false]), 1);
929
930        // x0=false, x1=true, x2=false -> none satisfied
931        assert_eq!(clause.count_satisfied(&[false, true, false]), 0);
932    }
933
934    #[test]
935    fn test_clause_empty() {
936        // Empty clause is always unsatisfied (false)
937        let empty = Clause::new(vec![]);
938        assert!(!empty.is_satisfied(&[true, false, true]));
939        assert_eq!(empty.count_satisfied(&[true, false, true]), 0);
940    }
941
942    #[test]
943    fn test_clause_unit() {
944        // Unit clause (single literal)
945        let unit_pos = Clause::unit(Literal::positive(0));
946        assert!(unit_pos.is_satisfied(&[true]));
947        assert!(!unit_pos.is_satisfied(&[false]));
948
949        let unit_neg = Clause::unit(Literal::negative(0));
950        assert!(!unit_neg.is_satisfied(&[true]));
951        assert!(unit_neg.is_satisfied(&[false]));
952    }
953
954    #[test]
955    fn test_objective_default() {
956        let obj = Objective::default();
957        assert!(matches!(obj, Objective::Satisfaction));
958    }
959
960    #[test]
961    fn test_instance_from_cnf() {
962        // (x1 OR NOT x2) AND (x2 OR x3)
963        let instance = SolveInstance::new(
964            3,
965            vec![
966                Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
967                Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
968            ],
969        );
970        assert_eq!(instance.num_vars, 3);
971        assert_eq!(instance.clauses.len(), 2);
972    }
973
974    #[test]
975    fn test_instance_is_satisfied() {
976        let instance = SolveInstance::new(
977            3,
978            vec![
979                Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
980                Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
981            ],
982        );
983
984        // x0=true, x1=false, x2=true should satisfy both clauses
985        let assignment = vec![true, false, true];
986        assert!(instance.is_satisfied(&assignment));
987
988        // x0=false, x1=true, x2=false should fail clause 2
989        let assignment2 = vec![false, true, false];
990        assert!(!instance.is_satisfied(&assignment2));
991    }
992
993    #[test]
994    fn test_instance_count_satisfied() {
995        let instance = SolveInstance::new(
996            3,
997            vec![
998                Clause::new(vec![Literal::positive(0), Literal::negative(1)]),
999                Clause::new(vec![Literal::positive(1), Literal::positive(2)]),
1000                Clause::new(vec![Literal::negative(0), Literal::negative(2)]),
1001            ],
1002        );
1003
1004        // x0=true, x1=false, x2=true
1005        // Clause 1: x0=T or NOT x1=T -> satisfied
1006        // Clause 2: x1=F or x2=T -> satisfied
1007        // Clause 3: NOT x0=F or NOT x2=F -> NOT satisfied
1008        assert_eq!(instance.count_satisfied(&[true, false, true]), 2);
1009
1010        // x0=false, x1=false, x2=false
1011        // Clause 1: x0=F or NOT x1=T -> satisfied
1012        // Clause 2: x1=F or x2=F -> NOT satisfied
1013        // Clause 3: NOT x0=T or NOT x2=T -> satisfied
1014        assert_eq!(instance.count_satisfied(&[false, false, false]), 2);
1015    }
1016
1017    #[test]
1018    fn test_instance_with_weights() {
1019        let instance = SolveInstance::with_weights(
1020            3,
1021            vec![
1022                Clause::new(vec![Literal::positive(0)]),
1023                Clause::new(vec![Literal::positive(1)]),
1024                Clause::new(vec![Literal::positive(2)]),
1025            ],
1026            vec![1.0, 2.0, 3.0],
1027        );
1028
1029        assert!(instance.weights.is_some());
1030        assert_eq!(instance.weights.as_ref().unwrap().len(), 3);
1031    }
1032
1033    #[test]
1034    fn test_instance_weighted_satisfaction() {
1035        let instance = SolveInstance::with_weights(
1036            3,
1037            vec![
1038                Clause::new(vec![Literal::positive(0)]), // weight 1.0
1039                Clause::new(vec![Literal::positive(1)]), // weight 2.0
1040                Clause::new(vec![Literal::positive(2)]), // weight 3.0
1041            ],
1042            vec![1.0, 2.0, 3.0],
1043        );
1044
1045        // All true: 1.0 + 2.0 + 3.0 = 6.0
1046        assert_eq!(instance.weighted_satisfaction(&[true, true, true]), 6.0);
1047
1048        // Only x1=true: 2.0
1049        assert_eq!(instance.weighted_satisfaction(&[false, true, false]), 2.0);
1050
1051        // None satisfied: 0.0
1052        assert_eq!(instance.weighted_satisfaction(&[false, false, false]), 0.0);
1053    }
1054
1055    #[test]
1056    fn test_instance_weighted_satisfaction_unweighted() {
1057        // When no weights provided, each clause has implicit weight 1.0
1058        let instance = SolveInstance::new(
1059            2,
1060            vec![
1061                Clause::new(vec![Literal::positive(0)]),
1062                Clause::new(vec![Literal::positive(1)]),
1063            ],
1064        );
1065
1066        assert_eq!(instance.weighted_satisfaction(&[true, true]), 2.0);
1067        assert_eq!(instance.weighted_satisfaction(&[true, false]), 1.0);
1068    }
1069
1070    #[test]
1071    fn test_instance_empty() {
1072        let instance = SolveInstance::new(0, vec![]);
1073        assert!(instance.is_satisfied(&[]));
1074        assert_eq!(instance.count_satisfied(&[]), 0);
1075        assert_eq!(instance.weighted_satisfaction(&[]), 0.0);
1076    }
1077
1078    #[test]
1079    fn test_literal_negate() {
1080        let pos = Literal::positive(5);
1081        let neg = pos.negate();
1082        assert_eq!(neg.var, 5);
1083        assert!(neg.negated);
1084
1085        let pos_again = neg.negate();
1086        assert_eq!(pos_again.var, 5);
1087        assert!(!pos_again.negated);
1088    }
1089
1090    #[test]
1091    fn test_clause_binary() {
1092        let clause = Clause::binary(Literal::positive(0), Literal::negative(1));
1093        assert_eq!(clause.literals.len(), 2);
1094        assert!(clause.is_satisfied(&[true, true]));
1095        assert!(clause.is_satisfied(&[true, false]));
1096        assert!(clause.is_satisfied(&[false, false]));
1097        assert!(!clause.is_satisfied(&[false, true]));
1098    }
1099
1100    #[test]
1101    fn test_clause_ternary() {
1102        let clause = Clause::ternary(
1103            Literal::positive(0),
1104            Literal::positive(1),
1105            Literal::positive(2),
1106        );
1107        assert_eq!(clause.literals.len(), 3);
1108
1109        // Only satisfied when at least one is true
1110        assert!(clause.is_satisfied(&[true, false, false]));
1111        assert!(clause.is_satisfied(&[false, true, false]));
1112        assert!(clause.is_satisfied(&[false, false, true]));
1113        assert!(!clause.is_satisfied(&[false, false, false]));
1114    }
1115
1116    #[test]
1117    fn test_instance_add_clause() {
1118        let mut instance = SolveInstance::new(3, vec![Clause::new(vec![Literal::positive(0)])]);
1119        assert_eq!(instance.clauses.len(), 1);
1120
1121        instance.add_clause(Clause::new(vec![Literal::positive(1)]));
1122        assert_eq!(instance.clauses.len(), 2);
1123    }
1124
1125    #[test]
1126    fn test_instance_total_weight() {
1127        let instance = SolveInstance::with_weights(
1128            3,
1129            vec![
1130                Clause::new(vec![Literal::positive(0)]),
1131                Clause::new(vec![Literal::positive(1)]),
1132                Clause::new(vec![Literal::positive(2)]),
1133            ],
1134            vec![1.0, 2.0, 3.0],
1135        );
1136        assert_eq!(instance.total_weight(), 6.0);
1137
1138        // Unweighted instance: total weight = number of clauses
1139        let unweighted = SolveInstance::new(
1140            2,
1141            vec![
1142                Clause::new(vec![Literal::positive(0)]),
1143                Clause::new(vec![Literal::positive(1)]),
1144            ],
1145        );
1146        assert_eq!(unweighted.total_weight(), 2.0);
1147    }
1148
1149    #[test]
1150    fn test_instance_satisfaction_ratio() {
1151        let instance = SolveInstance::new(
1152            3,
1153            vec![
1154                Clause::new(vec![Literal::positive(0)]),
1155                Clause::new(vec![Literal::positive(1)]),
1156                Clause::new(vec![Literal::positive(2)]),
1157                Clause::new(vec![Literal::negative(0)]),
1158            ],
1159        );
1160
1161        // x0=true satisfies clauses 0, but not 3 -> 1 satisfied
1162        // x1, x2 satisfy clauses 1, 2
1163        // Total: 3 out of 4
1164        assert_eq!(instance.satisfaction_ratio(&[true, true, true]), 0.75);
1165    }
1166
1167    #[test]
1168    fn test_literal_ordering() {
1169        let a = Literal::positive(1);
1170        let b = Literal::positive(2);
1171        let c = Literal::negative(1);
1172
1173        assert!(a < b); // Lower var comes first
1174        assert!(a < c); // Same var, positive before negative
1175    }
1176
1177    #[test]
1178    fn test_clause_len_and_is_empty() {
1179        let empty = Clause::new(vec![]);
1180        assert!(empty.is_empty());
1181        assert_eq!(empty.len(), 0);
1182
1183        let unit = Clause::unit(Literal::positive(0));
1184        assert!(!unit.is_empty());
1185        assert_eq!(unit.len(), 1);
1186
1187        let binary = Clause::binary(Literal::positive(0), Literal::positive(1));
1188        assert_eq!(binary.len(), 2);
1189    }
1190
1191    #[test]
1192    fn test_instance_num_clauses() {
1193        let instance = SolveInstance::new(
1194            3,
1195            vec![
1196                Clause::new(vec![Literal::positive(0)]),
1197                Clause::new(vec![Literal::positive(1)]),
1198            ],
1199        );
1200        assert_eq!(instance.num_clauses(), 2);
1201    }
1202}
1203
1204// Implementation will go here after tests fail