1use acir::{
2 AcirField,
3 native_types::{Expression, Witness, WitnessMap},
4};
5
6use super::{ErrorLocation, OpcodeNotSolvable, OpcodeResolutionError, insert_value};
7
8pub(crate) struct ExpressionSolver;
11
12#[allow(clippy::enum_variant_names)]
13pub(super) enum OpcodeStatus<F> {
14 OpcodeSatisfied(F),
15 OpcodeSolvable(F, (F, Witness)),
16 OpcodeUnsolvable,
17}
18
19pub(crate) enum MulTerm<F> {
20 OneUnknown(F, Witness), TooManyUnknowns,
22 Solved(F),
23}
24
25impl ExpressionSolver {
26 pub(crate) fn solve<F: AcirField>(
33 initial_witness: &mut WitnessMap<F>,
34 opcode: &Expression<F>,
35 ) -> Result<(), OpcodeResolutionError<F>> {
36 let (mul_constant, mut unknown) = match opcode.mul_terms.len() {
39 0 => (F::zero(), None),
40 1 => {
41 match Self::solve_mul_term_helper(&opcode.mul_terms[0], initial_witness) {
42 MulTerm::Solved(val) => (val, None),
43 MulTerm::OneUnknown(coeff, witness) => {
44 let unknown = if coeff.is_zero() { None } else { Some((coeff, witness)) };
45 (F::zero(), unknown)
46 }
47 MulTerm::TooManyUnknowns => {
48 let (c, _, _) = opcode.mul_terms[0];
49 if c.is_zero() {
50 (F::zero(), None)
52 } else {
53 return Err(OpcodeResolutionError::OpcodeNotSolvable(
55 OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()),
56 ));
57 }
58 }
59 }
60 }
61 _ => return Self::solve_via_evaluate(initial_witness, opcode),
63 };
64
65 let mut sum = opcode.q_c + mul_constant;
67
68 for &(coeff, witness) in &opcode.linear_combinations {
69 if let Some(value) = initial_witness.get(&witness) {
70 sum += coeff * *value;
71 } else if !coeff.is_zero() {
72 if unknown.is_some() {
73 return Self::solve_via_evaluate(initial_witness, opcode);
75 }
76 unknown = Some((coeff, witness));
77 }
78 }
79
80 if let Some((coeff, witness)) = unknown {
81 Self::solve_single_unknown(sum, coeff, witness, initial_witness)
82 } else {
83 Self::verify_satisfied(sum)
84 }
85 }
86
87 fn verify_satisfied<F: AcirField>(sum: F) -> Result<(), OpcodeResolutionError<F>> {
89 if sum.is_zero() {
90 Ok(())
91 } else {
92 Err(OpcodeResolutionError::UnsatisfiedConstrain {
93 opcode_location: ErrorLocation::Unresolved,
94 payload: None,
95 })
96 }
97 }
98
99 fn solve_single_unknown<F: AcirField>(
101 sum: F,
102 coeff: F,
103 witness: Witness,
104 initial_witness: &mut WitnessMap<F>,
105 ) -> Result<(), OpcodeResolutionError<F>> {
106 if coeff.is_zero() {
107 Self::verify_satisfied(sum)
108 } else {
109 let assignment = -quick_invert(sum, coeff);
110 insert_value(&witness, assignment, initial_witness)
111 }
112 }
113
114 fn solve_via_evaluate<F: AcirField>(
118 initial_witness: &mut WitnessMap<F>,
119 opcode: &Expression<F>,
120 ) -> Result<(), OpcodeResolutionError<F>> {
121 let opcode = &ExpressionSolver::evaluate(opcode, initial_witness);
122
123 let mul_result = ExpressionSolver::mul_term_status(&opcode.mul_terms);
125
126 let mul_result = if mul_result.is_err() {
129 let mul_terms = ExpressionSolver::combine_mul_terms(&opcode.mul_terms);
130 ExpressionSolver::mul_term_status(&mul_terms)
131 } else {
132 mul_result
133 };
134
135 let mul_result = mul_result.map_err(|_| {
136 OpcodeResolutionError::OpcodeNotSolvable(
137 OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()),
138 )
139 })?;
140
141 let opcode_status = ExpressionSolver::fan_in_status(&opcode.linear_combinations);
143
144 let opcode_status = if matches!(
147 (&mul_result, &opcode_status),
148 (MulTerm::Solved(..), OpcodeStatus::OpcodeUnsolvable)
149 ) {
150 let linear_combinations =
151 ExpressionSolver::combine_linear_terms(&opcode.linear_combinations);
152 ExpressionSolver::fan_in_status(&linear_combinations)
153 } else {
154 opcode_status
155 };
156
157 match (mul_result, opcode_status) {
158 (
160 MulTerm::Solved(total_prod),
161 OpcodeStatus::OpcodeSolvable(partial_sum, (coeff, witness)),
162 ) => Self::solve_single_unknown(
163 total_prod + partial_sum + opcode.q_c,
164 coeff,
165 witness,
166 initial_witness,
167 ),
168 (MulTerm::Solved(a), OpcodeStatus::OpcodeSatisfied(b)) => {
170 Self::verify_satisfied(a + b + opcode.q_c)
171 }
172 (MulTerm::OneUnknown(coeff, witness), OpcodeStatus::OpcodeSatisfied(sum)) => {
174 Self::solve_single_unknown(sum + opcode.q_c, coeff, witness, initial_witness)
175 }
176 (MulTerm::OneUnknown(q, w1), OpcodeStatus::OpcodeSolvable(a, (b, w2))) => {
179 if w1 == w2 {
180 Self::solve_single_unknown(a + opcode.q_c, q + b, w1, initial_witness)
181 } else {
182 Err(OpcodeResolutionError::OpcodeNotSolvable(
184 OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()),
185 ))
186 }
187 }
188 (MulTerm::TooManyUnknowns, _) | (_, OpcodeStatus::OpcodeUnsolvable) => {
189 Err(OpcodeResolutionError::OpcodeNotSolvable(
190 OpcodeNotSolvable::ExpressionHasTooManyUnknowns(opcode.clone()),
191 ))
192 }
193 }
194 }
195
196 fn mul_term_status<F: AcirField>(
198 mul_terms: &[(F, Witness, Witness)],
199 ) -> Result<MulTerm<F>, OpcodeStatus<F>> {
200 match mul_terms.len() {
201 0 => Ok(MulTerm::Solved(F::zero())),
202 1 => Ok(MulTerm::TooManyUnknowns),
203 _ => Err(OpcodeStatus::OpcodeUnsolvable),
204 }
205 }
206
207 fn solve_mul_term_helper<F: AcirField>(
213 term: &(F, Witness, Witness),
214 witness_assignments: &WitnessMap<F>,
215 ) -> MulTerm<F> {
216 let (q_m, w_l, w_r) = term;
217 let w_l_value = witness_assignments.get(w_l);
219 let w_r_value = witness_assignments.get(w_r);
220
221 match (w_l_value, w_r_value) {
222 (None, None) => MulTerm::TooManyUnknowns,
223 (Some(w_l), Some(w_r)) => MulTerm::Solved(*q_m * *w_l * *w_r),
224 (None, Some(w_r)) => MulTerm::OneUnknown(*q_m * *w_r, *w_l),
225 (Some(w_l), None) => MulTerm::OneUnknown(*q_m * *w_l, *w_r),
226 }
227 }
228
229 fn solve_fan_in_term_helper<F: AcirField>(
232 term: &(F, Witness),
233 witness_assignments: &WitnessMap<F>,
234 ) -> Option<F> {
235 let (q_l, w_l) = term;
236 let w_l_value = witness_assignments.get(w_l);
238 w_l_value.map(|a| *q_l * *a)
239 }
240
241 pub(super) fn fan_in_status<F: AcirField>(
243 linear_combinations: &[(F, Witness)],
244 ) -> OpcodeStatus<F> {
245 match linear_combinations.len() {
246 0 => OpcodeStatus::OpcodeSatisfied(F::zero()),
247 1 => OpcodeStatus::OpcodeSolvable(F::zero(), linear_combinations[0]),
248 _ => OpcodeStatus::OpcodeUnsolvable,
249 }
250 }
251
252 pub(crate) fn evaluate<F: AcirField>(
260 expr: &Expression<F>,
261 initial_witness: &WitnessMap<F>,
262 ) -> Expression<F> {
263 let mut result = Expression::default();
264 for &(c, w1, w2) in &expr.mul_terms {
265 let mul_result = ExpressionSolver::solve_mul_term_helper(&(c, w1, w2), initial_witness);
266 match mul_result {
267 MulTerm::OneUnknown(v, w) => {
268 if !v.is_zero() {
269 result.linear_combinations.push((v, w));
270 }
271 }
272 MulTerm::TooManyUnknowns => {
273 if !c.is_zero() {
274 result.mul_terms.push((c, w1, w2));
275 }
276 }
277 MulTerm::Solved(f) => result.q_c += f,
278 }
279 }
280 for &(c, w) in &expr.linear_combinations {
281 if let Some(f) = ExpressionSolver::solve_fan_in_term_helper(&(c, w), initial_witness) {
282 result.q_c += f;
283 } else if !c.is_zero() {
284 result.linear_combinations.push((c, w));
285 }
286 }
287 result.q_c += expr.q_c;
288 result
289 }
290
291 pub(crate) fn combine_linear_terms<F: AcirField>(
294 linear_combinations: &[(F, Witness)],
295 ) -> Vec<(F, Witness)> {
296 let mut combined_linear_combinations = std::collections::HashMap::new();
297
298 for (c, w) in linear_combinations {
299 let existing_c = combined_linear_combinations.entry(*w).or_insert(F::zero());
300 *existing_c += *c;
301 }
302
303 combined_linear_combinations
304 .into_iter()
305 .filter_map(
306 |(witness, coeff)| {
307 if !coeff.is_zero() { Some((coeff, witness)) } else { None }
308 },
309 )
310 .collect()
311 }
312
313 pub(crate) fn combine_mul_terms<F: AcirField>(
317 mul_terms: &[(F, Witness, Witness)],
318 ) -> Vec<(F, Witness, Witness)> {
319 let mut hash_map = std::collections::HashMap::new();
322
323 for (scale, w_l, w_r) in mul_terms.iter().copied() {
325 let mut pair = [w_l, w_r];
326 pair.sort();
327
328 *hash_map.entry((pair[0], pair[1])).or_insert_with(F::zero) += scale;
329 }
330
331 hash_map
332 .into_iter()
333 .filter(|(_, scale)| !scale.is_zero())
334 .map(|((w_l, w_r), scale)| (scale, w_l, w_r))
335 .collect()
336 }
337}
338
339fn quick_invert<F: AcirField>(numerator: F, denominator: F) -> F {
345 if denominator == F::one() {
346 numerator
347 } else if denominator == -F::one() {
348 -numerator
349 } else {
350 assert!(
351 denominator != F::zero(),
352 "quick_invert: attempting to divide numerator by F::zero()"
353 );
354 numerator / denominator
355 }
356}
357
358#[cfg(test)]
359mod tests {
360 use super::*;
361 use acir::FieldElement;
362
363 #[test]
364 fn quick_invert_matches_slow_invert() {
366 let numerator = FieldElement::from_be_bytes_reduce("hello_world".as_bytes());
367 assert_eq!(quick_invert(numerator, FieldElement::one()), numerator / FieldElement::one());
368 assert_eq!(quick_invert(numerator, -FieldElement::one()), numerator / -FieldElement::one());
369 }
370
371 #[test]
372 #[should_panic(expected = "quick_invert: attempting to divide numerator by F::zero()")]
373 fn quick_invert_zero_denominator() {
374 quick_invert(FieldElement::one(), FieldElement::zero());
375 }
376
377 #[test]
378 fn solves_simple_assignment() {
379 let a = Witness(0);
380
381 let opcode_a = Expression::from_str(&format!("{a} - 1")).unwrap();
383
384 let mut values = WitnessMap::new();
385 assert_eq!(ExpressionSolver::solve(&mut values, &opcode_a), Ok(()));
386
387 assert_eq!(values.get(&a).unwrap(), &FieldElement::from(1_i128));
388 }
389
390 #[test]
391 fn solves_unknown_in_mul_term() {
392 let a = Witness(0);
393 let b = Witness(1);
394 let c = Witness(2);
395 let d = Witness(3);
396
397 let opcode_a = Expression::from_str(&format!("{a}*{b} - {b} - {c} - {d}")).unwrap();
399
400 let mut values = WitnessMap::new();
401 values.insert(b, FieldElement::from(2_i128));
402 values.insert(c, FieldElement::from(1_i128));
403 values.insert(d, FieldElement::from(1_i128));
404
405 assert_eq!(ExpressionSolver::solve(&mut values, &opcode_a), Ok(()));
406
407 assert_eq!(values.get(&a).unwrap(), &FieldElement::from(2_i128));
408 }
409
410 #[test]
411 fn solves_unknown_in_linear_term() {
412 let a = Witness(0);
413 let b = Witness(1);
414 let c = Witness(2);
415 let d = Witness(3);
416
417 let opcode_a = Expression::from_str(&format!("{a} - {b} - {c} - {d}")).unwrap();
419
420 let e = Witness(4);
421 let opcode_b = Expression::from_str(&format!("{e} - {a} - {b}")).unwrap();
422
423 let mut values = WitnessMap::new();
424 values.insert(b, FieldElement::from(2_i128));
425 values.insert(c, FieldElement::from(1_i128));
426 values.insert(d, FieldElement::from(1_i128));
427
428 assert_eq!(ExpressionSolver::solve(&mut values, &opcode_a), Ok(()));
429 assert_eq!(ExpressionSolver::solve(&mut values, &opcode_b), Ok(()));
430
431 assert_eq!(values.get(&a).unwrap(), &FieldElement::from(4_i128));
432 }
433
434 #[test]
435 fn solves_by_combining_linear_terms_after_they_have_been_multiplied_by_known_witnesses() {
436 let expr = Expression::from_str("w1 + w1*w0 - 4").unwrap();
437 let mut values = WitnessMap::new();
438 values.insert(Witness(0), FieldElement::from(1_i128));
439
440 let res = ExpressionSolver::solve(&mut values, &expr);
441 assert!(res.is_ok());
442
443 assert_eq!(values.get(&Witness(1)).unwrap(), &FieldElement::from(2_i128));
444 }
445
446 #[test]
447 fn solves_by_combining_mul_terms() {
448 let expr = Expression::from_str("w1*w2 - w2*w1 + w3 - 2").unwrap();
449 let mut values = WitnessMap::new();
450
451 let res = ExpressionSolver::solve(&mut values, &expr);
452 assert!(res.is_ok());
453
454 assert_eq!(values.get(&Witness(3)).unwrap(), &FieldElement::from(2_i128));
455 }
456}