problemreductions/models/formula/
circuit.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry};
7use crate::traits::Problem;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11inventory::submit! {
12 ProblemSchemaEntry {
13 name: "CircuitSAT",
14 display_name: "Circuit SAT",
15 aliases: &[],
16 dimensions: &[],
17 module_path: module_path!(),
18 description: "Find satisfying input to a boolean circuit",
19 fields: &[
20 FieldInfo { name: "circuit", type_name: "Circuit", description: "The boolean circuit" },
21 ],
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
27pub enum BooleanOp {
28 Var(String),
30 Const(bool),
32 Not(Box<BooleanExpr>),
34 And(Vec<BooleanExpr>),
36 Or(Vec<BooleanExpr>),
38 Xor(Vec<BooleanExpr>),
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
44pub struct BooleanExpr {
45 pub op: BooleanOp,
46}
47
48impl BooleanExpr {
49 pub fn var(name: &str) -> Self {
51 BooleanExpr {
52 op: BooleanOp::Var(name.to_string()),
53 }
54 }
55
56 pub fn constant(value: bool) -> Self {
58 BooleanExpr {
59 op: BooleanOp::Const(value),
60 }
61 }
62
63 #[allow(clippy::should_implement_trait)]
65 pub fn not(expr: BooleanExpr) -> Self {
66 BooleanExpr {
67 op: BooleanOp::Not(Box::new(expr)),
68 }
69 }
70
71 pub fn and(args: Vec<BooleanExpr>) -> Self {
73 BooleanExpr {
74 op: BooleanOp::And(args),
75 }
76 }
77
78 pub fn or(args: Vec<BooleanExpr>) -> Self {
80 BooleanExpr {
81 op: BooleanOp::Or(args),
82 }
83 }
84
85 pub fn xor(args: Vec<BooleanExpr>) -> Self {
87 BooleanExpr {
88 op: BooleanOp::Xor(args),
89 }
90 }
91
92 pub fn variables(&self) -> Vec<String> {
94 let mut vars = Vec::new();
95 self.extract_variables(&mut vars);
96 vars.sort();
97 vars.dedup();
98 vars
99 }
100
101 fn extract_variables(&self, vars: &mut Vec<String>) {
102 match &self.op {
103 BooleanOp::Var(name) => vars.push(name.clone()),
104 BooleanOp::Const(_) => {}
105 BooleanOp::Not(inner) => inner.extract_variables(vars),
106 BooleanOp::And(args) | BooleanOp::Or(args) | BooleanOp::Xor(args) => {
107 for arg in args {
108 arg.extract_variables(vars);
109 }
110 }
111 }
112 }
113
114 pub fn evaluate(&self, assignments: &HashMap<String, bool>) -> bool {
116 match &self.op {
117 BooleanOp::Var(name) => *assignments.get(name).unwrap_or(&false),
118 BooleanOp::Const(value) => *value,
119 BooleanOp::Not(inner) => !inner.evaluate(assignments),
120 BooleanOp::And(args) => args.iter().all(|a| a.evaluate(assignments)),
121 BooleanOp::Or(args) => args.iter().any(|a| a.evaluate(assignments)),
122 BooleanOp::Xor(args) => args
123 .iter()
124 .fold(false, |acc, a| acc ^ a.evaluate(assignments)),
125 }
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
131pub struct Assignment {
132 pub outputs: Vec<String>,
134 pub expr: BooleanExpr,
136}
137
138impl Assignment {
139 pub fn new(outputs: Vec<String>, expr: BooleanExpr) -> Self {
141 Self { outputs, expr }
142 }
143
144 pub fn variables(&self) -> Vec<String> {
146 let mut vars = self.outputs.clone();
147 vars.extend(self.expr.variables());
148 vars.sort();
149 vars.dedup();
150 vars
151 }
152
153 pub fn is_satisfied(&self, assignments: &HashMap<String, bool>) -> bool {
155 let result = self.expr.evaluate(assignments);
156 self.outputs
157 .iter()
158 .all(|o| assignments.get(o).copied().unwrap_or(false) == result)
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
164pub struct Circuit {
165 pub assignments: Vec<Assignment>,
167}
168
169impl Circuit {
170 pub fn new(assignments: Vec<Assignment>) -> Self {
172 Self { assignments }
173 }
174
175 pub fn variables(&self) -> Vec<String> {
177 let mut vars = Vec::new();
178 for assign in &self.assignments {
179 vars.extend(assign.variables());
180 }
181 vars.sort();
182 vars.dedup();
183 vars
184 }
185
186 pub fn num_assignments(&self) -> usize {
188 self.assignments.len()
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
219pub struct CircuitSAT {
220 circuit: Circuit,
222 variables: Vec<String>,
224}
225
226impl CircuitSAT {
227 pub fn new(circuit: Circuit) -> Self {
229 let variables = circuit.variables();
230 Self { circuit, variables }
231 }
232
233 pub fn circuit(&self) -> &Circuit {
235 &self.circuit
236 }
237
238 pub fn variable_names(&self) -> &[String] {
240 &self.variables
241 }
242
243 pub fn num_variables(&self) -> usize {
245 self.variables.len()
246 }
247
248 pub fn num_assignments(&self) -> usize {
250 self.circuit.num_assignments()
251 }
252
253 pub fn is_valid_solution(&self, config: &[usize]) -> bool {
255 self.count_satisfied(config) == self.circuit.num_assignments()
256 }
257
258 fn config_to_assignments(&self, config: &[usize]) -> HashMap<String, bool> {
260 self.variables
261 .iter()
262 .enumerate()
263 .map(|(i, name)| (name.clone(), config.get(i).copied().unwrap_or(0) == 1))
264 .collect()
265 }
266
267 fn count_satisfied(&self, config: &[usize]) -> usize {
269 let assignments = self.config_to_assignments(config);
270 self.circuit
271 .assignments
272 .iter()
273 .filter(|a| a.is_satisfied(&assignments))
274 .count()
275 }
276}
277
278#[cfg(test)]
280pub(crate) fn is_circuit_satisfying(
281 circuit: &Circuit,
282 assignments: &HashMap<String, bool>,
283) -> bool {
284 circuit
285 .assignments
286 .iter()
287 .all(|a| a.is_satisfied(assignments))
288}
289
290impl Problem for CircuitSAT {
291 const NAME: &'static str = "CircuitSAT";
292 type Value = crate::types::Or;
293
294 fn dims(&self) -> Vec<usize> {
295 vec![2; self.variables.len()]
296 }
297
298 fn evaluate(&self, config: &[usize]) -> crate::types::Or {
299 crate::types::Or(self.count_satisfied(config) == self.circuit.num_assignments())
300 }
301
302 fn variant() -> Vec<(&'static str, &'static str)> {
303 crate::variant_params![]
304 }
305}
306
307crate::declare_variants! {
308 default CircuitSAT => "2^num_variables",
309}
310
311#[cfg(feature = "example-db")]
312pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
313 vec![crate::example_db::specs::ModelExampleSpec {
314 id: "circuit_sat",
315 instance: Box::new(CircuitSAT::new(Circuit::new(vec![
316 Assignment::new(
317 vec!["a".to_string()],
318 BooleanExpr::and(vec![BooleanExpr::var("x1"), BooleanExpr::var("x2")]),
319 ),
320 Assignment::new(
321 vec!["b".to_string()],
322 BooleanExpr::or(vec![BooleanExpr::var("x1"), BooleanExpr::var("x2")]),
323 ),
324 Assignment::new(
325 vec!["c".to_string()],
326 BooleanExpr::xor(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
327 ),
328 ]))),
329 optimal_config: vec![0, 0, 0, 0, 0],
330 optimal_value: serde_json::json!(true),
331 }]
332}
333
334#[cfg(test)]
335#[path = "../../unit_tests/models/formula/circuit.rs"]
336mod tests;