problemreductions/models/formula/
nae_satisfiability.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use serde::{Deserialize, Serialize};
9
10use super::{sat::validate_cnf_literals, CNFClause};
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "NAESatisfiability",
15 display_name: "Not-All-Equal Satisfiability",
16 aliases: &["NAESAT"],
17 dimensions: &[],
18 category: crate::registry::ProblemCategory::Formula,
19 module_path: module_path!(),
20 description: "Find an assignment where every CNF clause has both a true and a false literal",
21 fields: &[
22 FieldInfo { name: "num_vars", type_name: "usize", description: "Number of Boolean variables" },
23 FieldInfo { name: "clauses", type_name: "Vec<CNFClause>", description: "Clauses in conjunctive normal form with at least two literals each" },
24 ],
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(try_from = "NAESatisfiabilityDef")]
35pub struct NAESatisfiability {
36 num_vars: usize,
38 clauses: Vec<CNFClause>,
40}
41
42impl NAESatisfiability {
43 pub fn new(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
48 Self::try_new(num_vars, clauses).unwrap_or_else(|err| panic!("{err}"))
49 }
50
51 pub fn try_new(
54 num_vars: usize,
55 clauses: Vec<CNFClause>,
56 ) -> Result<Self, crate::registry::ConstructionError> {
57 validate_cnf_literals(num_vars, &clauses)?;
58 validate_clause_lengths(&clauses)?;
59 Ok(Self { num_vars, clauses })
60 }
61
62 pub fn num_vars(&self) -> usize {
64 self.num_vars
65 }
66
67 pub fn num_clauses(&self) -> usize {
69 self.clauses.len()
70 }
71
72 pub fn num_literals(&self) -> usize {
74 self.clauses.iter().map(|c| c.len()).sum()
75 }
76
77 pub fn num_clause_variables(&self) -> usize {
82 self.clauses
83 .iter()
84 .map(|clause| {
85 clause
86 .literals
87 .iter()
88 .map(|lit| lit.unsigned_abs())
89 .collect::<std::collections::HashSet<_>>()
90 .len()
91 })
92 .sum()
93 }
94
95 pub fn num_literal_pairs(&self) -> usize {
99 self.clauses
100 .iter()
101 .map(|c| c.len() * (c.len() - 1) / 2)
102 .sum()
103 }
104
105 pub fn clauses(&self) -> &[CNFClause] {
107 &self.clauses
108 }
109
110 pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
112 self.clauses.get(index)
113 }
114
115 pub fn count_nae_satisfied(
117 &self,
118 assignment: &[bool],
119 ) -> Result<i64, crate::traits::EvaluationError> {
120 let count = self
121 .clauses
122 .iter()
123 .filter(|clause| Self::clause_is_nae_satisfied(clause, assignment))
124 .count();
125 i64::try_from(count).map_err(|_| {
126 crate::traits::EvaluationError::IntegerOverflow(
127 "converting NAE-satisfied-clause count to i64".into(),
128 )
129 })
130 }
131
132 pub fn is_nae_satisfying(&self, assignment: &[bool]) -> bool {
134 self.clauses
135 .iter()
136 .all(|clause| Self::clause_is_nae_satisfied(clause, assignment))
137 }
138
139 pub fn is_valid_solution(
141 &self,
142 config: &[bool],
143 ) -> Result<bool, crate::traits::EvaluationError> {
144 if config.len() != self.num_vars {
145 return Err(crate::traits::EvaluationError::InvalidConfiguration(
146 "assignment length does not match the formula variables".into(),
147 ));
148 }
149 Ok(self.is_nae_satisfying(config))
150 }
151
152 fn literal_value(lit: i64, assignment: &[bool]) -> bool {
153 let var = lit.unsigned_abs() as usize - 1;
154 let value = assignment.get(var).copied().unwrap_or(false);
155 if lit > 0 {
156 value
157 } else {
158 !value
159 }
160 }
161
162 fn clause_is_nae_satisfied(clause: &CNFClause, assignment: &[bool]) -> bool {
163 let mut has_true = false;
164 let mut has_false = false;
165
166 for &lit in &clause.literals {
167 if Self::literal_value(lit, assignment) {
168 has_true = true;
169 } else {
170 has_false = true;
171 }
172
173 if has_true && has_false {
174 return true;
175 }
176 }
177
178 false
179 }
180}
181
182impl Problem for NAESatisfiability {
183 const NAME: &'static str = "NAESatisfiability";
184 type Solution = Vec<bool>;
185 type Value = crate::types::Or;
186
187 crate::problem_parameters![
188 ("num_clauses", num_clauses),
189 ("num_clause_variables", num_clause_variables),
190 ("num_literal_pairs", num_literal_pairs),
191 ("num_literals", num_literals),
192 ("num_vars", num_vars),
193 ];
194
195 fn evaluate(
196 &self,
197 config: &Self::Solution,
198 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
199 Ok(crate::types::Or(self.is_valid_solution(config)?))
200 }
201
202 fn variant() -> Vec<(&'static str, &'static str)> {
203 crate::variant_params![]
204 }
205}
206
207impl crate::solvers::BruteForceProblem for NAESatisfiability {
208 fn dimensions(&self) -> Vec<usize> {
209 vec![2; self.num_vars]
210 }
211}
212
213crate::declare_variants! {
214 default NAESatisfiability => "2^num_vars",
215}
216
217crate::register_brute_force! {
218 NAESatisfiability decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
219}
220
221#[derive(Debug, Clone, Deserialize)]
222struct NAESatisfiabilityDef {
223 num_vars: usize,
224 clauses: Vec<CNFClause>,
225}
226
227impl TryFrom<NAESatisfiabilityDef> for NAESatisfiability {
228 type Error = crate::registry::ConstructionError;
229
230 fn try_from(value: NAESatisfiabilityDef) -> Result<Self, Self::Error> {
231 Self::try_new(value.num_vars, value.clauses)
232 }
233}
234
235fn validate_clause_lengths(
236 clauses: &[CNFClause],
237) -> Result<(), crate::registry::ConstructionError> {
238 for (index, clause) in clauses.iter().enumerate() {
239 if clause.len() < 2 {
240 return Err(format!(
241 "Clause {} has {} literals, expected at least 2",
242 index,
243 clause.len()
244 )
245 .into());
246 }
247 }
248 Ok(())
249}
250
251#[cfg(feature = "example-db")]
252pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
253 vec![crate::example_db::specs::ModelExampleSpec {
254 id: "nae_satisfiability",
255 instance: Box::new(NAESatisfiability::new(
256 5,
257 vec![
258 CNFClause::new(vec![1, 2, -3]),
259 CNFClause::new(vec![-1, 3, 4]),
260 CNFClause::new(vec![2, -4, 5]),
261 CNFClause::new(vec![-2, 3, -5]),
262 CNFClause::new(vec![1, -3, 5]),
263 ],
264 )),
265 optimal_config: serde_json::json!(vec![false, false, false, true, true]),
266 optimal_value: serde_json::json!(true),
267 }]
268}
269
270#[cfg(test)]
271#[path = "../../unit_tests/models/formula/nae_satisfiability.rs"]
272mod tests;