problemreductions/models/formula/
nae_satisfiability.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use serde::{Deserialize, Serialize};
9
10use super::CNFClause;
11
12inventory::submit! {
13 ProblemSchemaEntry {
14 name: "NAESatisfiability",
15 display_name: "Not-All-Equal Satisfiability",
16 aliases: &["NAESAT"],
17 dimensions: &[],
18 module_path: module_path!(),
19 description: "Find an assignment where every CNF clause has both a true and a false literal",
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 in conjunctive normal form with at least two literals each" },
23 ],
24 }
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
33#[serde(try_from = "NAESatisfiabilityDef")]
34pub struct NAESatisfiability {
35 num_vars: usize,
37 clauses: Vec<CNFClause>,
39}
40
41impl NAESatisfiability {
42 pub fn new(num_vars: usize, clauses: Vec<CNFClause>) -> Self {
47 Self::try_new(num_vars, clauses).unwrap_or_else(|err| panic!("{err}"))
48 }
49
50 pub fn try_new(num_vars: usize, clauses: Vec<CNFClause>) -> Result<Self, String> {
53 validate_clause_lengths(&clauses)?;
54 Ok(Self { num_vars, clauses })
55 }
56
57 pub fn num_vars(&self) -> usize {
59 self.num_vars
60 }
61
62 pub fn num_clauses(&self) -> usize {
64 self.clauses.len()
65 }
66
67 pub fn num_literals(&self) -> usize {
69 self.clauses.iter().map(|c| c.len()).sum()
70 }
71
72 pub fn num_literal_pairs(&self) -> usize {
76 self.clauses
77 .iter()
78 .map(|c| c.len() * (c.len() - 1) / 2)
79 .sum()
80 }
81
82 pub fn clauses(&self) -> &[CNFClause] {
84 &self.clauses
85 }
86
87 pub fn get_clause(&self, index: usize) -> Option<&CNFClause> {
89 self.clauses.get(index)
90 }
91
92 pub fn count_nae_satisfied(&self, assignment: &[bool]) -> usize {
94 self.clauses
95 .iter()
96 .filter(|clause| Self::clause_is_nae_satisfied(clause, assignment))
97 .count()
98 }
99
100 pub fn is_nae_satisfying(&self, assignment: &[bool]) -> bool {
102 self.clauses
103 .iter()
104 .all(|clause| Self::clause_is_nae_satisfied(clause, assignment))
105 }
106
107 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
109 self.evaluate(config).0
110 }
111
112 fn literal_value(lit: i32, assignment: &[bool]) -> bool {
113 let var = lit.unsigned_abs() as usize - 1;
114 let value = assignment.get(var).copied().unwrap_or(false);
115 if lit > 0 {
116 value
117 } else {
118 !value
119 }
120 }
121
122 fn clause_is_nae_satisfied(clause: &CNFClause, assignment: &[bool]) -> bool {
123 let mut has_true = false;
124 let mut has_false = false;
125
126 for &lit in &clause.literals {
127 if Self::literal_value(lit, assignment) {
128 has_true = true;
129 } else {
130 has_false = true;
131 }
132
133 if has_true && has_false {
134 return true;
135 }
136 }
137
138 false
139 }
140}
141
142impl Problem for NAESatisfiability {
143 const NAME: &'static str = "NAESatisfiability";
144 type Value = crate::types::Or;
145
146 fn dims(&self) -> Vec<usize> {
147 vec![2; self.num_vars]
148 }
149
150 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
151 crate::types::Or({
152 let assignment = super::config_to_assignment(config);
153 self.is_nae_satisfying(&assignment)
154 })
155 }
156
157 fn variant() -> Vec<(&'static str, &'static str)> {
158 crate::variant_params![]
159 }
160}
161
162crate::declare_variants! {
163 default NAESatisfiability => "2^num_variables",
164}
165
166#[derive(Debug, Clone, Deserialize)]
167struct NAESatisfiabilityDef {
168 num_vars: usize,
169 clauses: Vec<CNFClause>,
170}
171
172impl TryFrom<NAESatisfiabilityDef> for NAESatisfiability {
173 type Error = String;
174
175 fn try_from(value: NAESatisfiabilityDef) -> Result<Self, Self::Error> {
176 Self::try_new(value.num_vars, value.clauses)
177 }
178}
179
180fn validate_clause_lengths(clauses: &[CNFClause]) -> Result<(), String> {
181 for (index, clause) in clauses.iter().enumerate() {
182 if clause.len() < 2 {
183 return Err(format!(
184 "Clause {} has {} literals, expected at least 2",
185 index,
186 clause.len()
187 ));
188 }
189 }
190 Ok(())
191}
192
193#[cfg(feature = "example-db")]
194pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
195 vec![crate::example_db::specs::ModelExampleSpec {
196 id: "nae_satisfiability",
197 instance: Box::new(NAESatisfiability::new(
198 5,
199 vec![
200 CNFClause::new(vec![1, 2, -3]),
201 CNFClause::new(vec![-1, 3, 4]),
202 CNFClause::new(vec![2, -4, 5]),
203 CNFClause::new(vec![-2, 3, -5]),
204 CNFClause::new(vec![1, -3, 5]),
205 ],
206 )),
207 optimal_config: vec![0, 0, 0, 1, 1],
208 optimal_value: serde_json::json!(true),
209 }]
210}
211
212#[cfg(test)]
213#[path = "../../unit_tests/models/formula/nae_satisfiability.rs"]
214mod tests;