problemreductions/models/formula/
one_in_three_satisfiability.rs1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(try_from = "OneInThreeSatisfiabilityDef")]
60pub struct OneInThreeSatisfiability {
61 num_vars: usize,
63 clauses: Vec<CNFClause>,
65}
66
67impl OneInThreeSatisfiability {
68 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 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 pub fn num_vars(&self) -> usize {
93 self.num_vars
94 }
95
96 pub fn num_clauses(&self) -> usize {
98 self.clauses.len()
99 }
100
101 pub fn clauses(&self) -> &[CNFClause] {
103 &self.clauses
104 }
105
106 pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
108 self.clauses.get(index)
109 }
110
111 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; 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;