Skip to main content

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