problemreductions/models/formula/
ksat.rs

1//! K-Satisfiability (K-SAT) problem implementation.
2//!
3//! K-SAT is a special case of SAT where each clause has exactly K literals.
4//! Common variants include 3-SAT (K=3) and 2-SAT (K=2). This is the decision
5//! version - for the optimization variant (MAX-K-SAT), see the separate
6//! MaxKSatisfiability type (if available).
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::{Problem, SatisfactionProblem};
10use crate::variant::{KValue, K2, K3, KN};
11use serde::{Deserialize, Serialize};
12
13use super::CNFClause;
14
15inventory::submit! {
16    ProblemSchemaEntry {
17        name: "KSatisfiability",
18        module_path: module_path!(),
19        description: "SAT with exactly k literals per clause",
20        fields: &[
21            FieldInfo { name: "num_vars", type_name: "usize", description: "Number of Boolean variables" },
22            FieldInfo { name: "clauses", type_name: "Vec<CNFClause>", description: "Clauses each with exactly K literals" },
23        ],
24    }
25}
26
27/// K-Satisfiability problem where each clause has exactly K literals.
28///
29/// This is a restricted form of SAT where every clause must contain
30/// exactly K literals. The most famous variant is 3-SAT (K=3), which
31/// is NP-complete, while 2-SAT (K=2) is solvable in polynomial time.
32/// This is the decision version of the problem.
33///
34/// # Type Parameters
35/// * `K` - A type implementing `KValue` that specifies the number of literals per clause
36///
37/// # Example
38///
39/// ```
40/// use problemreductions::models::formula::{KSatisfiability, CNFClause};
41/// use problemreductions::variant::K3;
42/// use problemreductions::{Problem, Solver, BruteForce};
43///
44/// // 3-SAT formula: (x1 OR x2 OR x3) AND (NOT x1 OR x2 OR NOT x3)
45/// let problem = KSatisfiability::<K3>::new(
46///     3,
47///     vec![
48///         CNFClause::new(vec![1, 2, 3]),       // x1 OR x2 OR x3
49///         CNFClause::new(vec![-1, 2, -3]),     // NOT x1 OR x2 OR NOT x3
50///     ],
51/// );
52///
53/// let solver = BruteForce::new();
54/// let solutions = solver.find_all_satisfying(&problem);
55/// assert!(!solutions.is_empty());
56/// ```
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(bound(deserialize = ""))]
59pub struct KSatisfiability<K: KValue> {
60    /// Number of variables.
61    num_vars: usize,
62    /// Clauses in CNF, each with exactly K literals.
63    clauses: Vec<CNFClause>,
64    #[serde(skip)]
65    _phantom: std::marker::PhantomData<K>,
66}
67
68impl<K: KValue> KSatisfiability<K> {
69    /// Create a new K-SAT problem.
70    ///
71    /// # Panics
72    /// Panics if any clause does not have exactly K literals (when K is a
73    /// concrete value like K2, K3). When K is KN (arbitrary), no clause-length
74    /// validation is performed.
75    pub fn new(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
76        if let Some(k) = K::K {
77            for (i, clause) in clauses.iter().enumerate() {
78                assert!(
79                    clause.len() == k,
80                    "Clause {} has {} literals, expected {}",
81                    i,
82                    clause.len(),
83                    k
84                );
85            }
86        }
87        Self {
88            num_vars,
89            clauses,
90            _phantom: std::marker::PhantomData,
91        }
92    }
93
94    /// Create a new K-SAT problem allowing clauses with fewer than K literals.
95    ///
96    /// This is useful when the reduction algorithm produces clauses with
97    /// fewer literals (e.g., when allow_less is true in the Julia implementation).
98    ///
99    /// # Panics
100    /// Panics if any clause has more than K literals (when K is a concrete
101    /// value like K2, K3). When K is KN (arbitrary), no clause-length
102    /// validation is performed.
103    pub fn new_allow_less(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
104        if let Some(k) = K::K {
105            for (i, clause) in clauses.iter().enumerate() {
106                assert!(
107                    clause.len() <= k,
108                    "Clause {} has {} literals, expected at most {}",
109                    i,
110                    clause.len(),
111                    k
112                );
113            }
114        }
115        Self {
116            num_vars,
117            clauses,
118            _phantom: std::marker::PhantomData,
119        }
120    }
121
122    /// Get the number of variables.
123    pub fn num_vars(&self) -> usize {
124        self.num_vars
125    }
126
127    /// Get the number of clauses.
128    pub fn num_clauses(&self) -> usize {
129        self.clauses.len()
130    }
131
132    /// Get the clauses.
133    pub fn clauses(&self) -> &[CNFClause] {
134        &self.clauses
135    }
136
137    /// Get a specific clause.
138    pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
139        self.clauses.get(index)
140    }
141
142    /// Get the total number of literals across all clauses.
143    pub fn num_literals(&self) -> usize {
144        self.clauses().iter().map(|c| c.len()).sum()
145    }
146
147    /// Count satisfied clauses for an assignment.
148    pub fn count_satisfied(&self, assignment: &[bool]) -> usize {
149        self.clauses
150            .iter()
151            .filter(|c| c.is_satisfied(assignment))
152            .count()
153    }
154
155    /// Check if an assignment satisfies all clauses.
156    pub fn is_satisfying(&self, assignment: &[bool]) -> bool {
157        self.clauses.iter().all(|c| c.is_satisfied(assignment))
158    }
159
160    /// Convert a usize config to boolean assignment.
161    fn config_to_assignment(config: &[usize]) -> Vec<bool> {
162        config.iter().map(|&v| v == 1).collect()
163    }
164}
165
166impl<K: KValue> Problem for KSatisfiability<K> {
167    const NAME: &'static str = "KSatisfiability";
168    type Metric = bool;
169
170    fn dims(&self) -> Vec<usize> {
171        vec![2; self.num_vars]
172    }
173
174    fn evaluate(&self, config: &[usize]) -> bool {
175        let assignment = Self::config_to_assignment(config);
176        self.is_satisfying(&assignment)
177    }
178
179    fn variant() -> Vec<(&'static str, &'static str)> {
180        crate::variant_params![K]
181    }
182}
183
184impl<K: KValue> SatisfactionProblem for KSatisfiability<K> {}
185
186crate::declare_variants! {
187    KSatisfiability<KN> => "2^num_variables",
188    KSatisfiability<K2> => "num_variables + num_clauses",
189    KSatisfiability<K3> => "1.307^num_variables",
190}
191
192#[cfg(test)]
193#[path = "../../unit_tests/models/formula/ksat.rs"]
194mod tests;