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(
40 &self,
41 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
42 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
43 crate::rules::traits::validate_target_witness(
44 self.target_problem(),
45 target_solution,
46 |value| value.value.is_some(),
47 "target ILP assignment is infeasible",
48 )?;
49
50 Ok({
51 self.source_variables
52 .iter()
53 .map(|name| target_solution[self.variable_map[name]] == 1)
54 .collect()
55 })
56 }
57}
58
59struct ILPBuilder {
61 num_vars: usize,
62 constraints: Vec<LinearConstraint>,
63 variable_map: HashMap<String, usize>,
64}
65
66impl ILPBuilder {
67 fn new() -> Self {
68 Self {
69 num_vars: 0,
70 constraints: Vec::new(),
71 variable_map: HashMap::new(),
72 }
73 }
74
75 fn get_or_create_var(&mut self, name: &str) -> Result<usize, crate::rules::ReductionError> {
77 if let Some(&idx) = self.variable_map.get(name) {
78 Ok(idx)
79 } else {
80 let idx = self.alloc_aux()?;
81 self.variable_map.insert(name.to_string(), idx);
82 Ok(idx)
83 }
84 }
85
86 fn alloc_aux(&mut self) -> Result<usize, crate::rules::ReductionError> {
88 let idx = self.num_vars;
89 self.num_vars = self.num_vars.checked_add(1).ok_or_else(|| {
90 crate::rules::ReductionError::integer_overflow::<CircuitSAT, ILP<bool>>(
91 "allocating a circuit ILP variable",
92 )
93 })?;
94 Ok(idx)
95 }
96
97 fn process_expr(&mut self, expr: &BooleanExpr) -> Result<usize, crate::rules::ReductionError> {
100 Ok(match &expr.op {
101 BooleanOp::Var(name) => self.get_or_create_var(name)?,
102 BooleanOp::Const(value) => {
103 let c = self.alloc_aux()?;
104 let v = if *value { 1 } else { 0 };
105 self.constraints.push(LinearConstraint::eq(vec![(c, 1)], v));
106 c
107 }
108 BooleanOp::Not(inner) => {
109 let a = self.process_expr(inner)?;
110 let c = self.alloc_aux()?;
111 self.constraints
113 .push(LinearConstraint::eq(vec![(c, 1), (a, 1)], 1));
114 c
115 }
116 BooleanOp::And(args) => {
117 let inputs: Vec<usize> = args
118 .iter()
119 .map(|arg| self.process_expr(arg))
120 .collect::<Result<_, _>>()?;
121 let c = self.alloc_aux()?;
122 let k = <CircuitSAT as ReduceTo<ILP<bool>>>::exact_i64(
123 inputs.len(),
124 "encoding a circuit gate arity",
125 )?;
126 for &a_i in &inputs {
128 self.constraints
129 .push(LinearConstraint::le(vec![(c, 1), (a_i, -1)], 0));
130 }
131 let mut terms: Vec<(usize, i64)> = vec![(c, 1)];
133 for &a_i in &inputs {
134 terms.push((a_i, -1));
135 }
136 self.constraints.push(LinearConstraint::ge(terms, 1 - k));
137 c
138 }
139 BooleanOp::Or(args) => {
140 let inputs: Vec<usize> = args
141 .iter()
142 .map(|arg| self.process_expr(arg))
143 .collect::<Result<_, _>>()?;
144 let c = self.alloc_aux()?;
145 for &a_i in &inputs {
147 self.constraints
148 .push(LinearConstraint::ge(vec![(c, 1), (a_i, -1)], 0));
149 }
150 let mut terms: Vec<(usize, i64)> = vec![(c, 1)];
152 for &a_i in &inputs {
153 terms.push((a_i, -1));
154 }
155 self.constraints.push(LinearConstraint::le(terms, 0));
156 c
157 }
158 BooleanOp::Xor(args) => {
159 let inputs: Vec<usize> = args
161 .iter()
162 .map(|arg| self.process_expr(arg))
163 .collect::<Result<_, _>>()?;
164 let mut inputs = inputs.into_iter();
165 let mut result = match inputs.next() {
166 Some(first) => first,
167 None => self.process_expr(&BooleanExpr::constant(false))?,
169 };
170 for next in inputs {
171 let c = self.alloc_aux()?;
172 let a = result;
173 let b = next;
174 self.constraints
176 .push(LinearConstraint::le(vec![(c, 1), (a, -1), (b, -1)], 0));
177 self.constraints
179 .push(LinearConstraint::ge(vec![(c, 1), (a, -1), (b, 1)], 0));
180 self.constraints
182 .push(LinearConstraint::ge(vec![(c, 1), (a, 1), (b, -1)], 0));
183 self.constraints
185 .push(LinearConstraint::le(vec![(c, 1), (a, 1), (b, 1)], 2));
186 result = c;
187 }
188 result
189 }
190 })
191 }
192}
193
194#[crate::aggregate_reduction(ilp_feasibility)]
195impl crate::rules::AggregateReductionResult for ReductionCircuitToILP {}
196
197#[reduction(transform = upper_bound {
202 max_constraint_magnitude_bits = "num_expression_nodes / 2 + 2",
203 num_vars = "num_variables + 2 * num_expression_nodes",
204 num_constraints = "5 * num_expression_nodes + num_assignment_outputs",
205 num_nonzeros = "13 * num_expression_nodes - 12 * num_assignments + 2 * num_assignment_outputs",
206})]
207impl ReduceTo<ILP<bool>> for CircuitSAT {
208 type Result = ReductionCircuitToILP;
209
210 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
211 let mut builder = ILPBuilder::new();
212
213 for name in self.variable_names() {
215 builder.get_or_create_var(name)?;
216 }
217
218 for assignment in &self.circuit().assignments {
220 let expr_var = builder.process_expr(&assignment.expr)?;
221 for output_name in &assignment.outputs {
223 let out_var = builder.get_or_create_var(output_name)?;
224 if out_var != expr_var {
225 builder
227 .constraints
228 .push(LinearConstraint::eq(vec![(out_var, 1), (expr_var, -1)], 0));
229 }
230 }
231 }
232
233 let objective = vec![];
235 let target = ILP::new(
236 builder.num_vars,
237 builder.constraints,
238 objective,
239 ObjectiveSense::Minimize,
240 )
241 .map_err(<Self as ReduceTo<ILP<bool>>>::target_construction)?;
242
243 Ok(ReductionCircuitToILP {
244 target,
245 source_variables: self.variable_names().to_vec(),
246 variable_map: builder.variable_map,
247 })
248 }
249}
250
251#[cfg(feature = "example-db")]
252pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
253 use crate::export::SolutionPair;
254 use crate::models::formula::{Assignment, BooleanExpr, Circuit};
255
256 fn full_adder_circuit_sat() -> CircuitSAT {
257 let circuit = Circuit::new(vec![
258 Assignment::new(
259 vec!["t".to_string()],
260 BooleanExpr::xor(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
261 ),
262 Assignment::new(
263 vec!["sum".to_string()],
264 BooleanExpr::xor(vec![BooleanExpr::var("t"), BooleanExpr::var("cin")]),
265 ),
266 Assignment::new(
267 vec!["ab".to_string()],
268 BooleanExpr::and(vec![BooleanExpr::var("a"), BooleanExpr::var("b")]),
269 ),
270 Assignment::new(
271 vec!["cin_t".to_string()],
272 BooleanExpr::and(vec![BooleanExpr::var("cin"), BooleanExpr::var("t")]),
273 ),
274 Assignment::new(
275 vec!["cout".to_string()],
276 BooleanExpr::or(vec![BooleanExpr::var("ab"), BooleanExpr::var("cin_t")]),
277 ),
278 ]);
279 CircuitSAT::new(circuit)
280 }
281
282 vec![crate::example_db::specs::RuleExampleSpec {
283 id: "circuitsat_to_ilp",
284 build: || {
285 crate::example_db::specs::rule_example_with_witness::<
286 _,
287 crate::models::algebraic::ILP<bool>,
288 >(
289 full_adder_circuit_sat(),
290 SolutionPair {
291 source_config: serde_json::json!(vec![
292 false, false, false, false, false, false, false, false
293 ]),
294 target_config: serde_json::json!(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
295 },
296 )
297 },
298 }]
299}
300
301#[cfg(test)]
302#[path = "../unit_tests/rules/circuit_ilp.rs"]
303mod tests;