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;