problemreductions/models/formula/
circuit.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::{Problem, SatisfactionProblem};
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "CircuitSAT",
14 module_path: module_path!(),
15 description: "Find satisfying input to a boolean circuit",
16 fields: &[
17 FieldInfo { name: "circuit", type_name: "Circuit", description: "The boolean circuit" },
18 ],
19 }
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
24pub enum BooleanOp {
25 Var(String),
27 Const(bool),
29 Not(Box<BooleanExpr>),
31 And(Vec<BooleanExpr>),
33 Or(Vec<BooleanExpr>),
35 Xor(Vec<BooleanExpr>),
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
41pub struct BooleanExpr {
42 pub op: BooleanOp,
43}
44
45impl BooleanExpr {
46 pub fn var(name: &str) -> Self {
48 BooleanExpr {
49 op: BooleanOp::Var(name.to_string()),
50 }
51 }
52
53 pub fn constant(value: bool) -> Self {
55 BooleanExpr {
56 op: BooleanOp::Const(value),
57 }
58 }
59
60 #[allow(clippy::should_implement_trait)]
62 pub fn not(expr: BooleanExpr) -> Self {
63 BooleanExpr {
64 op: BooleanOp::Not(Box::new(expr)),
65 }
66 }
67
68 pub fn and(args: Vec<BooleanExpr>) -> Self {
70 BooleanExpr {
71 op: BooleanOp::And(args),
72 }
73 }
74
75 pub fn or(args: Vec<BooleanExpr>) -> Self {
77 BooleanExpr {
78 op: BooleanOp::Or(args),
79 }
80 }
81
82 pub fn xor(args: Vec<BooleanExpr>) -> Self {
84 BooleanExpr {
85 op: BooleanOp::Xor(args),
86 }
87 }
88
89 pub fn variables(&self) -> Vec<String> {
91 let mut vars = Vec::new();
92 self.extract_variables(&mut vars);
93 vars.sort();
94 vars.dedup();
95 vars
96 }
97
98 fn extract_variables(&self, vars: &mut Vec<String>) {
99 match &self.op {
100 BooleanOp::Var(name) => vars.push(name.clone()),
101 BooleanOp::Const(_) => {}
102 BooleanOp::Not(inner) => inner.extract_variables(vars),
103 BooleanOp::And(args) | BooleanOp::Or(args) | BooleanOp::Xor(args) => {
104 for arg in args {
105 arg.extract_variables(vars);
106 }
107 }
108 }
109 }
110
111 pub fn evaluate(&self, assignments: &HashMap<String, bool>) -> bool {
113 match &self.op {
114 BooleanOp::Var(name) => *assignments.get(name).unwrap_or(&false),
115 BooleanOp::Const(value) => *value,
116 BooleanOp::Not(inner) => !inner.evaluate(assignments),
117 BooleanOp::And(args) => args.iter().all(|a| a.evaluate(assignments)),
118 BooleanOp::Or(args) => args.iter().any(|a| a.evaluate(assignments)),
119 BooleanOp::Xor(args) => args
120 .iter()
121 .fold(false, |acc, a| acc ^ a.evaluate(assignments)),
122 }
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
128pub struct Assignment {
129 pub outputs: Vec<String>,
131 pub expr: BooleanExpr,
133}
134
135impl Assignment {
136 pub fn new(outputs: Vec<String>, expr: BooleanExpr) -> Self {
138 Self { outputs, expr }
139 }
140
141 pub fn variables(&self) -> Vec<String> {
143 let mut vars = self.outputs.clone();
144 vars.extend(self.expr.variables());
145 vars.sort();
146 vars.dedup();
147 vars
148 }
149
150 pub fn is_satisfied(&self, assignments: &HashMap<String, bool>) -> bool {
152 let result = self.expr.evaluate(assignments);
153 self.outputs
154 .iter()
155 .all(|o| assignments.get(o).copied().unwrap_or(false) == result)
156 }
157}
158
159#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
161pub struct Circuit {
162 pub assignments: Vec<Assignment>,
164}
165
166impl Circuit {
167 pub fn new(assignments: Vec<Assignment>) -> Self {
169 Self { assignments }
170 }
171
172 pub fn variables(&self) -> Vec<String> {
174 let mut vars = Vec::new();
175 for assign in &self.assignments {
176 vars.extend(assign.variables());
177 }
178 vars.sort();
179 vars.dedup();
180 vars
181 }
182
183 pub fn num_assignments(&self) -> usize {
185 self.assignments.len()
186 }
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct CircuitSAT {
217 circuit: Circuit,
219 variables: Vec<String>,
221}
222
223impl CircuitSAT {
224 pub fn new(circuit: Circuit) -> Self {
226 let variables = circuit.variables();
227 Self { circuit, variables }
228 }
229
230 pub fn circuit(&self) -> &Circuit {
232 &self.circuit
233 }
234
235 pub fn variable_names(&self) -> &[String] {
237 &self.variables
238 }
239
240 pub fn num_variables(&self) -> usize {
242 self.variables.len()
243 }
244
245 pub fn num_assignments(&self) -> usize {
247 self.circuit.num_assignments()
248 }
249
250 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
252 self.count_satisfied(config) == self.circuit.num_assignments()
253 }
254
255 fn config_to_assignments(&self, config: &[usize]) -> HashMap<String, bool> {
257 self.variables
258 .iter()
259 .enumerate()
260 .map(|(i, name)| (name.clone(), config.get(i).copied().unwrap_or(0) == 1))
261 .collect()
262 }
263
264 fn count_satisfied(&self, config: &[usize]) -> usize {
266 let assignments = self.config_to_assignments(config);
267 self.circuit
268 .assignments
269 .iter()
270 .filter(|a| a.is_satisfied(&assignments))
271 .count()
272 }
273}
274
275#[cfg(test)]
277pub(crate) fn is_circuit_satisfying(
278 circuit: &Circuit,
279 assignments: &HashMap<String, bool>,
280) -> bool {
281 circuit
282 .assignments
283 .iter()
284 .all(|a| a.is_satisfied(assignments))
285}
286
287impl Problem for CircuitSAT {
288 const NAME: &'static str = "CircuitSAT";
289 type Metric = bool;
290
291 fn dims(&self) -> Vec<usize> {
292 vec![2; self.variables.len()]
293 }
294
295 fn evaluate(&self, config: &[usize]) -> bool {
296 self.count_satisfied(config) == self.circuit.num_assignments()
297 }
298
299 fn variant() -> Vec<(&'static str, &'static str)> {
300 crate::variant_params![]
301 }
302}
303
304impl SatisfactionProblem for CircuitSAT {}
305
306crate::declare_variants! {
307 CircuitSAT => "2^num_variables",
308}
309
310#[cfg(test)]
311#[path = "../../unit_tests/models/formula/circuit.rs"]
312mod tests;