1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
18use crate::models::formula::{BooleanExpr, BooleanOp, CircuitSAT};
19use crate::reduction;
20use crate::rules::traits::{ReduceTo, ReductionResult};
21use std::collections::HashMap;
22
23#[derive(Debug, Clone)]
25pub struct ReductionCircuitToILP {
26 target: ILP<bool>,
27 source_variables: Vec<String>,
28 variable_map: HashMap<String, usize>,
29}
30
31impl ReductionResult for ReductionCircuitToILP {
32 type Source = CircuitSAT;
33 type Target = ILP<bool>;
34
35 fn target_problem(&self) -> &ILP<bool> {
36 &self.target
37 }
38
39 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
40 self.source_variables
41 .iter()
42 .map(|name| target_solution[self.variable_map[name]])
43 .collect()
44 }
45}
46
47struct ILPBuilder {
49 num_vars: usize,
50 constraints: Vec<LinearConstraint>,
51 variable_map: HashMap<String, usize>,
52}
53
54impl ILPBuilder {
55 fn new() -> Self {
56 Self {
57 num_vars: 0,
58 constraints: Vec::new(),
59 variable_map: HashMap::new(),
60 }
61 }
62
63 fn get_or_create_var(&mut self, name: &str) -> usize {
65 if let Some(&idx) = self.variable_map.get(name) {
66 idx
67 } else {
68 let idx = self.num_vars;
69 self.variable_map.insert(name.to_string(), idx);
70 self.num_vars += 1;
71 idx
72 }
73 }
74
75 fn alloc_aux(&mut self) -> usize {
77 let idx = self.num_vars;
78 self.num_vars += 1;
79 idx
80 }
81
82 fn process_expr(&mut self, expr: &BooleanExpr) -> usize {
85 match &expr.op {
86 BooleanOp::Var(name) => self.get_or_create_var(name),
87 BooleanOp::Const(value) => {
88 let c = self.alloc_aux();
89 let v = if *value { 1.0 } else { 0.0 };
90 self.constraints
91 .push(LinearConstraint::eq(vec![(c, 1.0)], v));
92 c
93 }
94 BooleanOp::Not(inner) => {
95 let a = self.process_expr(inner);
96 let c = self.alloc_aux();
97 self.constraints
99 .push(LinearConstraint::eq(vec![(c, 1.0), (a, 1.0)], 1.0));
100 c
101 }
102 BooleanOp::And(args) => {
103 let inputs: Vec<usize> = args.iter().map(|a| self.process_expr(a)).collect();
104 let c = self.alloc_aux();
105 let k = inputs.len() as f64;
106 for &a_i in &inputs {
108 self.constraints
109 .push(LinearConstraint::le(vec![(c, 1.0), (a_i, -1.0)], 0.0));
110 }
111 let mut terms: Vec<(usize, f64)> = vec![(c, 1.0)];
113 for &a_i in &inputs {
114 terms.push((a_i, -1.0));
115 }
116 self.constraints
117 .push(LinearConstraint::ge(terms, -(k - 1.0)));
118 c
119 }
120 BooleanOp::Or(args) => {
121 let inputs: Vec<usize> = args.iter().map(|a| self.process_expr(a)).collect();
122 let c = self.alloc_aux();
123 for &a_i in &inputs {
125 self.constraints
126 .push(LinearConstraint::ge(vec![(c, 1.0), (a_i, -1.0)], 0.0));
127 }
128 let mut terms: Vec<(usize, f64)> = vec![(c, 1.0)];
130 for &a_i in &inputs {
131 terms.push((a_i, -1.0));
132 }
133 self.constraints.push(LinearConstraint::le(terms, 0.0));
134 c
135 }
136 BooleanOp::Xor(args) => {
137 let inputs: Vec<usize> = args.iter().map(|a| self.process_expr(a)).collect();
139 assert!(!inputs.is_empty());
140 let mut result = inputs[0];
141 for &next in &inputs[1..] {
142 let c = self.alloc_aux();
143 let a = result;
144 let b = next;
145 self.constraints.push(LinearConstraint::le(
147 vec![(c, 1.0), (a, -1.0), (b, -1.0)],
148 0.0,
149 ));
150 self.constraints.push(LinearConstraint::ge(
152 vec![(c, 1.0), (a, -1.0), (b, 1.0)],
153 0.0,
154 ));
155 self.constraints.push(LinearConstraint::ge(
157 vec![(c, 1.0), (a, 1.0), (b, -1.0)],
158 0.0,
159 ));
160 self.constraints.push(LinearConstraint::le(
162 vec![(c, 1.0), (a, 1.0), (b, 1.0)],
163 2.0,
164 ));
165 result = c;
166 }
167 result
168 }
169 }
170 }
171}
172
173#[reduction(
174 overhead = {
175 num_vars = "num_variables + num_assignments",
176 num_constraints = "num_variables + num_assignments",
177 }
178)]
179impl ReduceTo<ILP<bool>> for CircuitSAT {
180 type Result = ReductionCircuitToILP;
181
182 fn reduce_to(&self) -> Self::Result {
183 let mut builder = ILPBuilder::new();
184
185 for name in self.variable_names() {
187 builder.get_or_create_var(name);
188 }
189
190 for assignment in &self.circuit().assignments {
192 let expr_var = builder.process_expr(&assignment.expr);
193 for output_name in &assignment.outputs {
195 let out_var = builder.get_or_create_var(output_name);
196 if out_var != expr_var {
197 builder.constraints.push(LinearConstraint::eq(
199 vec![(out_var, 1.0), (expr_var, -1.0)],
200 0.0,
201 ));
202 }
203 }
204 }
205
206 let objective = vec![];
208 let target = ILP::new(
209 builder.num_vars,
210 builder.constraints,
211 objective,
212 ObjectiveSense::Minimize,
213 );
214
215 ReductionCircuitToILP {
216 target,
217 source_variables: self.variable_names().to_vec(),
218 variable_map: builder.variable_map,
219 }
220 }
221}
222
223#[cfg(test)]
224#[path = "../unit_tests/rules/circuit_ilp.rs"]
225mod tests;