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 category: crate::registry::ProblemCategory::Formula,
18 module_path: module_path!(),
19 description: "Find satisfying input to a boolean circuit",
20 fields: &[
21 FieldInfo { name: "circuit", type_name: "Circuit", description: "The boolean circuit" },
22 ],
23 }
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
28pub enum BooleanOp {
29 Var(String),
31 Const(bool),
33 Not(Box<BooleanExpr>),
35 And(Vec<BooleanExpr>),
37 Or(Vec<BooleanExpr>),
39 Xor(Vec<BooleanExpr>),
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
45pub struct BooleanExpr {
46 pub op: BooleanOp,
47}
48
49impl BooleanExpr {
50 pub fn var(name: &str) -> Self {
52 BooleanExpr {
53 op: BooleanOp::Var(name.to_string()),
54 }
55 }
56
57 pub fn constant(value: bool) -> Self {
59 BooleanExpr {
60 op: BooleanOp::Const(value),
61 }
62 }
63
64 #[allow(clippy::should_implement_trait)]
66 pub fn not(expr: BooleanExpr) -> Self {
67 BooleanExpr {
68 op: BooleanOp::Not(Box::new(expr)),
69 }
70 }
71
72 pub fn and(args: Vec<BooleanExpr>) -> Self {
74 BooleanExpr {
75 op: BooleanOp::And(args),
76 }
77 }
78
79 pub fn or(args: Vec<BooleanExpr>) -> Self {
81 BooleanExpr {
82 op: BooleanOp::Or(args),
83 }
84 }
85
86 pub fn xor(args: Vec<BooleanExpr>) -> Self {
88 BooleanExpr {
89 op: BooleanOp::Xor(args),
90 }
91 }
92
93 pub fn variables(&self) -> Vec<String> {
95 let mut vars = Vec::new();
96 self.extract_variables(&mut vars);
97 vars.sort();
98 vars.dedup();
99 vars
100 }
101
102 fn extract_variables(&self, vars: &mut Vec<String>) {
103 match &self.op {
104 BooleanOp::Var(name) => vars.push(name.clone()),
105 BooleanOp::Const(_) => {}
106 BooleanOp::Not(inner) => inner.extract_variables(vars),
107 BooleanOp::And(args) | BooleanOp::Or(args) | BooleanOp::Xor(args) => {
108 for arg in args {
109 arg.extract_variables(vars);
110 }
111 }
112 }
113 }
114
115 pub fn num_nodes(&self) -> usize {
117 match &self.op {
118 BooleanOp::Var(_) | BooleanOp::Const(_) => 1,
119 BooleanOp::Not(inner) => 1 + inner.num_nodes(),
120 BooleanOp::And(args) | BooleanOp::Or(args) | BooleanOp::Xor(args) => {
121 1 + args.iter().map(BooleanExpr::num_nodes).sum::<usize>()
122 }
123 }
124 }
125
126 pub fn evaluate(&self, assignments: &HashMap<String, bool>) -> bool {
128 match &self.op {
129 BooleanOp::Var(name) => *assignments.get(name).unwrap_or(&false),
130 BooleanOp::Const(value) => *value,
131 BooleanOp::Not(inner) => !inner.evaluate(assignments),
132 BooleanOp::And(args) => args.iter().all(|a| a.evaluate(assignments)),
133 BooleanOp::Or(args) => args.iter().any(|a| a.evaluate(assignments)),
134 BooleanOp::Xor(args) => args
135 .iter()
136 .fold(false, |acc, a| acc ^ a.evaluate(assignments)),
137 }
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
143pub struct Assignment {
144 pub outputs: Vec<String>,
146 pub expr: BooleanExpr,
148}
149
150impl Assignment {
151 pub fn new(outputs: Vec<String>, expr: BooleanExpr) -> Self {
153 Self { outputs, expr }
154 }
155
156 pub fn variables(&self) -> Vec<String> {
158 let mut vars = self.outputs.clone();
159 vars.extend(self.expr.variables());
160 vars.sort();
161 vars.dedup();
162 vars
163 }
164
165 pub fn is_satisfied(&self, assignments: &HashMap<String, bool>) -> bool {
167 let result = self.expr.evaluate(assignments);
168 self.outputs
169 .iter()
170 .all(|o| assignments.get(o).copied().unwrap_or(false) == result)
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
176pub struct Circuit {
177 pub assignments: Vec<Assignment>,
179}
180
181impl Circuit {
182 pub fn new(assignments: Vec<Assignment>) -> Self {
184 Self { assignments }
185 }
186
187 pub fn variables(&self) -> Vec<String> {
189 let mut vars = Vec::new();
190 for assign in &self.assignments {
191 vars.extend(assign.variables());
192 }
193 vars.sort();
194 vars.dedup();
195 vars
196 }
197
198 pub fn num_assignments(&self) -> usize {
200 self.assignments.len()
201 }
202
203 pub fn num_expression_nodes(&self) -> usize {
205 self.assignments
206 .iter()
207 .map(|assignment| assignment.expr.num_nodes())
208 .sum()
209 }
210
211 pub fn num_assignment_outputs(&self) -> usize {
213 self.assignments
214 .iter()
215 .map(|assignment| assignment.outputs.len())
216 .sum()
217 }
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct CircuitSAT {
248 circuit: Circuit,
250 variables: Vec<String>,
252}
253
254impl CircuitSAT {
255 pub fn new(circuit: Circuit) -> Self {
257 let variables = circuit.variables();
258 Self { circuit, variables }
259 }
260
261 pub fn circuit(&self) -> &Circuit {
263 &self.circuit
264 }
265
266 pub fn variable_names(&self) -> &[String] {
268 &self.variables
269 }
270
271 pub fn num_variables(&self) -> usize {
273 self.variables.len()
274 }
275
276 pub fn num_assignments(&self) -> usize {
278 self.circuit.num_assignments()
279 }
280
281 pub fn num_expression_nodes(&self) -> usize {
283 self.circuit.num_expression_nodes()
284 }
285
286 pub fn num_assignment_outputs(&self) -> usize {
288 self.circuit.num_assignment_outputs()
289 }
290
291 pub fn is_valid_solution(
293 &self,
294 config: &[bool],
295 ) -> Result<bool, crate::traits::EvaluationError> {
296 if config.len() != self.variables.len() {
297 return Err(crate::traits::EvaluationError::InvalidConfiguration(
298 "assignment length does not match the circuit variables".into(),
299 ));
300 }
301 Ok(self.count_satisfied(config) == self.circuit.num_assignments())
302 }
303
304 fn config_to_assignments(&self, config: &[bool]) -> HashMap<String, bool> {
306 self.variables
307 .iter()
308 .enumerate()
309 .map(|(i, name)| (name.clone(), config[i]))
310 .collect()
311 }
312
313 fn count_satisfied(&self, config: &[bool]) -> usize {
315 let assignments = self.config_to_assignments(config);
316 self.circuit
317 .assignments
318 .iter()
319 .filter(|a| a.is_satisfied(&assignments))
320 .count()
321 }
322}
323
324#[cfg(test)]
326pub(crate) fn is_circuit_satisfying(
327 circuit: &Circuit,
328 assignments: &HashMap<String, bool>,
329) -> bool {
330 circuit
331 .assignments
332 .iter()
333 .all(|a| a.is_satisfied(assignments))
334}
335
336impl Problem for CircuitSAT {
337 const NAME: &'static str = "CircuitSAT";
338 type Solution = Vec<bool>;
339 type Value = crate::types::Or;
340
341 crate::problem_parameters![
342 ("num_assignment_outputs", num_assignment_outputs),
343 ("num_assignments", num_assignments),
344 ("num_expression_nodes", num_expression_nodes),
345 ("num_variables", num_variables),
346 ];
347
348 fn evaluate(
349 &self,
350 config: &Self::Solution,
351 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
352 Ok(crate::types::Or(self.is_valid_solution(config)?))
353 }
354
355 fn variant() -> Vec<(&'static str, &'static str)> {
356 crate::variant_params![]
357 }
358}
359
360impl crate::solvers::BruteForceProblem for CircuitSAT {
361 fn dimensions(&self) -> Vec<usize> {
362 vec![2; self.variables.len()]
363 }
364}
365
366crate::declare_variants! {
367 default CircuitSAT => "2^num_variables",
368}
369
370crate::register_brute_force! {
371 CircuitSAT decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
372}
373
374#[cfg(feature = "example-db")]
375pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
376 vec![crate::example_db::specs::ModelExampleSpec {
377 id: "circuit_sat",
378 instance: Box::new(CircuitSAT::new(Circuit::new(vec![
379 Assignment::new(
380 vec!["a".to_string()],
381 BooleanExpr::and(vec![BooleanExpr::var("x1"), BooleanExpr::var("x2")]),
382 ),
383 Assignment::new(
384 vec!["b".to_string()],
385 BooleanExpr::or(vec![BooleanExpr::var("x1"), BooleanExpr::var("x2")]),
386 ),
387 Assignment::new(
388 vec!["c".to_string()],
389 BooleanExpr::xor(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
390 ),
391 ]))),
392 optimal_config: serde_json::json!(vec![false, false, false, false, false]),
393 optimal_value: serde_json::json!(true),
394 }]
395}
396
397#[cfg(test)]
398#[path = "../../unit_tests/models/formula/circuit.rs"]
399mod tests;