Skip to main content

problemreductions/models/formula/
one_in_three_satisfiability.rs

1//! One-in-Three Satisfiability (1-in-3 SAT) problem implementation.
2//!
3//! 1-in-3 SAT is a variant of 3-SAT where each clause must have *exactly one*
4//! true literal (rather than *at least one*). This stronger constraint makes
5//! the problem NP-complete even without negations (monotone 1-in-3 SAT).
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11use super::{sat::validate_cnf_literals, CNFClause};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "OneInThreeSatisfiability",
16        display_name: "One-in-Three Satisfiability",
17        aliases: &[],
18        dimensions: &[],
19        category: crate::registry::ProblemCategory::Formula,
20        module_path: module_path!(),
21        description: "3-SAT variant where each clause has exactly one true literal",
22        fields: &[
23            FieldInfo { name: "num_vars", type_name: "usize", description: "Number of Boolean variables" },
24            FieldInfo { name: "clauses", type_name: "Vec<CNFClause>", description: "Clauses each with exactly 3 literals" },
25        ],
26    }
27}
28
29/// One-in-Three Satisfiability problem.
30///
31/// Given a CNF formula where each clause has exactly 3 literals, find a truth
32/// assignment such that each clause has *exactly one* true literal.
33///
34/// This is a well-known NP-complete problem introduced by Schaefer (1978).
35/// Unlike standard 3-SAT which requires at least one true literal per clause,
36/// 1-in-3 SAT requires exactly one.
37///
38/// # Example
39///
40/// ```
41/// use problemreductions::models::formula::{OneInThreeSatisfiability, CNFClause};
42/// use problemreductions::{Problem, BruteForce};
43///
44/// // (x1 OR x2 OR x3) AND (NOT x1 OR x3 OR x4) AND (x2 OR NOT x3 OR NOT x4)
45/// let problem = OneInThreeSatisfiability::new(
46///     4,
47///     vec![
48///         CNFClause::new(vec![1, 2, 3]),
49///         CNFClause::new(vec![-1, 3, 4]),
50///         CNFClause::new(vec![2, -3, -4]),
51///     ],
52/// );
53///
54/// let solver = BruteForce::new();
55/// let solution = solver.solve(&problem).unwrap();
56/// assert!(solution.is_some());
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(try_from = "OneInThreeSatisfiabilityDef")]
60pub struct OneInThreeSatisfiability {
61    /// Number of variables.
62    num_vars: usize,
63    /// Clauses in CNF, each with exactly 3 literals.
64    clauses: Vec<CNFClause>,
65}
66
67impl OneInThreeSatisfiability {
68    /// Create a new 1-in-3 SAT problem.
69    ///
70    /// # Panics
71    /// Panics if any clause does not have exactly 3 literals, or if any
72    /// literal references a variable outside the range [1, num_vars].
73    pub fn new(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
74        Self::try_new(num_vars, clauses).unwrap_or_else(|message| panic!("{message}"))
75    }
76
77    /// Create a new 1-in-3 SAT problem after validating its clauses.
78    pub fn try_new(
79        num_vars: usize,
80        clauses: Vec<CNFClause>,
81    ) -> Result<Self, crate::registry::ConstructionError> {
82        validate_cnf_literals(num_vars, &clauses)?;
83        for (i, clause) in clauses.iter().enumerate() {
84            if clause.len() != 3 {
85                return Err(format!("Clause {i} has {} literals, expected 3", clause.len()).into());
86            }
87        }
88        Ok(Self { num_vars, clauses })
89    }
90
91    /// Get the number of variables.
92    pub fn num_vars(&self) -> usize {
93        self.num_vars
94    }
95
96    /// Get the number of clauses.
97    pub fn num_clauses(&self) -> usize {
98        self.clauses.len()
99    }
100
101    /// Get the clauses.
102    pub fn clauses(&self) -> &[CNFClause] {
103        &self.clauses
104    }
105
106    /// Get a specific clause.
107    pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
108        self.clauses.get(index)
109    }
110
111    /// Check if exactly one literal is true in each clause.
112    pub fn is_one_in_three_satisfying(&self, assignment: &[bool]) -> bool {
113        self.clauses.iter().all(|clause| {
114            let true_count = clause
115                .literals
116                .iter()
117                .filter(|&&lit| {
118                    let var = lit.unsigned_abs() as usize - 1; // Convert to 0-indexed
119                    let value = assignment.get(var).copied().unwrap_or(false);
120                    if lit > 0 {
121                        value
122                    } else {
123                        !value
124                    }
125                })
126                .count();
127            true_count == 1
128        })
129    }
130}
131
132impl Problem for OneInThreeSatisfiability {
133    const NAME: &'static str = "OneInThreeSatisfiability";
134    type Solution = Vec<bool>;
135    type Value = crate::types::Or;
136
137    crate::problem_parameters![("num_clauses", num_clauses), ("num_vars", num_vars),];
138
139    fn evaluate(
140        &self,
141        config: &Self::Solution,
142    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
143        if config.len() != self.num_vars {
144            return Err(crate::traits::EvaluationError::InvalidConfiguration(
145                "assignment length does not match the formula variables".into(),
146            ));
147        }
148        Ok(crate::types::Or(self.is_one_in_three_satisfying(config)))
149    }
150
151    fn variant() -> Vec<(&'static str, &'static str)> {
152        crate::variant_params![]
153    }
154}
155
156impl crate::solvers::BruteForceProblem for OneInThreeSatisfiability {
157    fn dimensions(&self) -> Vec<usize> {
158        vec![2; self.num_vars]
159    }
160}
161
162crate::declare_variants! {
163    default OneInThreeSatisfiability => "1.307^num_vars",
164}
165
166crate::register_brute_force! {
167    OneInThreeSatisfiability decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
168}
169
170#[derive(Deserialize)]
171struct OneInThreeSatisfiabilityDef {
172    num_vars: usize,
173    clauses: Vec<CNFClause>,
174}
175
176impl TryFrom<OneInThreeSatisfiabilityDef> for OneInThreeSatisfiability {
177    type Error = crate::registry::ConstructionError;
178
179    fn try_from(value: OneInThreeSatisfiabilityDef) -> Result<Self, Self::Error> {
180        Self::try_new(value.num_vars, value.clauses)
181    }
182}
183
184#[cfg(feature = "example-db")]
185pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
186    vec![crate::example_db::specs::ModelExampleSpec {
187        id: "one_in_three_satisfiability",
188        instance: Box::new(OneInThreeSatisfiability::new(
189            4,
190            vec![
191                CNFClause::new(vec![1, 2, 3]),
192                CNFClause::new(vec![-1, 3, 4]),
193                CNFClause::new(vec![2, -3, -4]),
194            ],
195        )),
196        optimal_config: serde_json::json!(vec![true, false, false, true]),
197        optimal_value: serde_json::json!(true),
198    }]
199}
200
201#[cfg(test)]
202#[path = "../../unit_tests/models/formula/one_in_three_satisfiability.rs"]
203mod tests;