acir/native_types/expression/mod.rs
1use crate::native_types::Witness;
2use acir_field::FieldElement;
3use serde::{Deserialize, Serialize};
4use std::cmp::Ordering;
5
6mod operators;
7mod ordering;
8
9// In the addition polynomial
10// We can have arbitrary fan-in/out, so we need more than wL,wR and wO
11// When looking at the assert-zero opcode for the quotient polynomial in standard plonk
12// You can think of it as fan-in 2 and fan out-1 , or you can think of it as fan-in 1 and fan-out 2
13//
14// In the multiplication polynomial
15// XXX: If we allow the degree of the quotient polynomial to be arbitrary, then we will need a vector of wire values
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)]
17pub struct Expression {
18 // To avoid having to create intermediate variables pre-optimization
19 // We collect all of the multiplication terms in the assert-zero opcode
20 // A multiplication term if of the form q_M * wL * wR
21 // Hence this vector represents the following sum: q_M1 * wL1 * wR1 + q_M2 * wL2 * wR2 + .. +
22 pub mul_terms: Vec<(FieldElement, Witness, Witness)>,
23
24 pub linear_combinations: Vec<(FieldElement, Witness)>,
25 // TODO: rename q_c to `constant` moreover q_X is not clear to those who
26 // TODO are not familiar with PLONK
27 pub q_c: FieldElement,
28}
29
30impl Default for Expression {
31 fn default() -> Expression {
32 Expression {
33 mul_terms: Vec::new(),
34 linear_combinations: Vec::new(),
35 q_c: FieldElement::zero(),
36 }
37 }
38}
39
40impl std::fmt::Display for Expression {
41 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
42 if let Some(witness) = self.to_witness() {
43 write!(f, "x{}", witness.witness_index())
44 } else {
45 write!(f, "%{:?}%", crate::circuit::opcodes::Opcode::AssertZero(self.clone()))
46 }
47 }
48}
49
50impl Expression {
51 // TODO: possibly remove, and move to noir repo.
52 pub const fn can_defer_constraint(&self) -> bool {
53 false
54 }
55
56 /// Returns the number of multiplication terms
57 pub fn num_mul_terms(&self) -> usize {
58 self.mul_terms.len()
59 }
60
61 pub fn from_field(q_c: FieldElement) -> Expression {
62 Self { q_c, ..Default::default() }
63 }
64
65 pub fn one() -> Expression {
66 Self::from_field(FieldElement::one())
67 }
68
69 pub fn zero() -> Expression {
70 Self::default()
71 }
72
73 /// Adds a new linear term to the `Expression`.
74 pub fn push_addition_term(&mut self, coefficient: FieldElement, variable: Witness) {
75 self.linear_combinations.push((coefficient, variable));
76 }
77
78 /// Adds a new quadratic term to the `Expression`.
79 pub fn push_multiplication_term(
80 &mut self,
81 coefficient: FieldElement,
82 lhs: Witness,
83 rhs: Witness,
84 ) {
85 self.mul_terms.push((coefficient, lhs, rhs));
86 }
87
88 /// Returns `true` if the expression represents a constant polynomial.
89 ///
90 /// Examples:
91 /// - f(x,y) = x + y would return false
92 /// - f(x,y) = xy would return false, the degree here is 2
93 /// - f(x,y) = 5 would return true, the degree is 0
94 pub fn is_const(&self) -> bool {
95 self.mul_terms.is_empty() && self.linear_combinations.is_empty()
96 }
97
98 /// Returns `true` if highest degree term in the expression is one or less.
99 ///
100 /// - `mul_term` in an expression contains degree-2 terms
101 /// - `linear_combinations` contains degree-1 terms
102 /// Hence, it is sufficient to check that there are no `mul_terms`
103 ///
104 /// Examples:
105 /// - f(x,y) = x + y would return true
106 /// - f(x,y) = xy would return false, the degree here is 2
107 /// - f(x,y) = 0 would return true, the degree is 0
108 pub fn is_linear(&self) -> bool {
109 self.mul_terms.is_empty()
110 }
111
112 /// Returns `true` if the expression can be seen as a degree-1 univariate polynomial
113 ///
114 /// - `mul_terms` in an expression can be univariate, however unless the coefficient
115 /// is zero, it is always degree-2.
116 /// - `linear_combinations` contains the sum of degree-1 terms, these terms do not
117 /// need to contain the same variable and so it can be multivariate. However, we
118 /// have thus far only checked if `linear_combinations` contains one term, so this
119 /// method will return false, if the `Expression` has not been simplified.
120 ///
121 /// Hence, we check in the simplest case if an expression is a degree-1 univariate,
122 /// by checking if it contains no `mul_terms` and it contains one `linear_combination` term.
123 ///
124 /// Examples:
125 /// - f(x,y) = x would return true
126 /// - f(x,y) = x + 6 would return true
127 /// - f(x,y) = 2*y + 6 would return true
128 /// - f(x,y) = x + y would return false
129 /// - f(x,y) = x + x should return true, but we return false *** (we do not simplify)
130 /// - f(x,y) = 5 would return false
131 pub fn is_degree_one_univariate(&self) -> bool {
132 self.is_linear() && self.linear_combinations.len() == 1
133 }
134
135 pub fn is_zero(&self) -> bool {
136 *self == Self::zero()
137 }
138
139 /// Returns a `FieldElement` if the expression represents a constant polynomial.
140 /// Otherwise returns `None`.
141 ///
142 /// Examples:
143 /// - f(x,y) = x would return `None`
144 /// - f(x,y) = x + 6 would return `None`
145 /// - f(x,y) = 2*y + 6 would return `None`
146 /// - f(x,y) = x + y would return `None`
147 /// - f(x,y) = 5 would return `FieldElement(5)`
148 pub fn to_const(&self) -> Option<FieldElement> {
149 self.is_const().then_some(self.q_c)
150 }
151
152 /// Returns a `Witness` if the `Expression` can be represented as a degree-1
153 /// univariate polynomial. Otherwise returns `None`.
154 ///
155 /// Note that `Witness` is only capable of expressing polynomials of the form
156 /// f(x) = x and not polynomials of the form f(x) = mx+c , so this method has
157 /// extra checks to ensure that m=1 and c=0
158 pub fn to_witness(&self) -> Option<Witness> {
159 if self.is_degree_one_univariate() {
160 // If we get here, we know that our expression is of the form `f(x) = mx+c`
161 // We want to now restrict ourselves to expressions of the form f(x) = x
162 // ie where the constant term is 0 and the coefficient in front of the variable is
163 // one.
164 let (coefficient, variable) = self.linear_combinations[0];
165 let constant = self.q_c;
166
167 if coefficient.is_one() && constant.is_zero() {
168 return Some(variable);
169 }
170 }
171 None
172 }
173
174 /// Sorts opcode in a deterministic order
175 /// XXX: We can probably make this more efficient by sorting on each phase. We only care if it is deterministic
176 pub fn sort(&mut self) {
177 self.mul_terms.sort_by(|a, b| a.1.cmp(&b.1).then(a.2.cmp(&b.2)));
178 self.linear_combinations.sort_by(|a, b| a.1.cmp(&b.1));
179 }
180
181 /// Checks if this expression can fit into one arithmetic identity
182 /// TODO: This needs to be reworded, arithmetic identity only makes sense in the context
183 /// TODO of PLONK, whereas we want expressions to be generic.
184 /// TODO: We just need to reword it to say exactly what its doing and
185 /// TODO then reference the fact that this is what plonk will accept.
186 /// TODO alternatively, we can define arithmetic identity in the context of expressions
187 /// TODO and then reference that.
188 pub fn fits_in_one_identity(&self, width: usize) -> bool {
189 // A Polynomial with more than one mul term cannot fit into one opcode
190 if self.mul_terms.len() > 1 {
191 return false;
192 };
193 // A Polynomial with more terms than fan-in cannot fit within a single opcode
194 if self.linear_combinations.len() > width {
195 return false;
196 }
197
198 // A polynomial with no mul term and a fan-in that fits inside of the width can fit into a single opcode
199 if self.mul_terms.is_empty() {
200 return true;
201 }
202
203 // A polynomial with width-2 fan-in terms and a single non-zero mul term can fit into one opcode
204 // Example: Axy + Dz . Notice, that the mul term places a constraint on the first two terms, but not the last term
205 // XXX: This would change if our arithmetic polynomial equation was changed to Axyz for example, but for now it is not.
206 if self.linear_combinations.len() <= (width - 2) {
207 return true;
208 }
209
210 // We now know that we have a single mul term. We also know that the mul term must match up with two other terms
211 // A polynomial whose mul terms are non zero which do not match up with two terms in the fan-in cannot fit into one opcode
212 // An example of this is: Axy + Bx + Cy + ...
213 // Notice how the bivariate monomial xy has two univariate monomials with their respective coefficients
214 // XXX: note that if x or y is zero, then we could apply a further optimization, but this would be done in another algorithm.
215 // It would be the same as when we have zero coefficients - Can only work if wire is constrained to be zero publicly
216 let mul_term = &self.mul_terms[0];
217
218 // The coefficient should be non-zero, as this method is ran after the compiler removes all zero coefficient terms
219 assert_ne!(mul_term.0, FieldElement::zero());
220
221 let mut found_x = false;
222 let mut found_y = false;
223
224 for term in self.linear_combinations.iter() {
225 let witness = &term.1;
226 let x = &mul_term.1;
227 let y = &mul_term.2;
228 if witness == x {
229 found_x = true;
230 };
231 if witness == y {
232 found_y = true;
233 };
234 if found_x & found_y {
235 break;
236 }
237 }
238
239 found_x & found_y
240 }
241
242 /// Returns `self + k*b`
243 pub fn add_mul(&self, k: FieldElement, b: &Expression) -> Expression {
244 if k.is_zero() {
245 return self.clone();
246 } else if self.is_const() {
247 return self.q_c + (k * b);
248 } else if b.is_const() {
249 return self.clone() + (k * b.q_c);
250 }
251
252 let mut mul_terms: Vec<(FieldElement, Witness, Witness)> =
253 Vec::with_capacity(self.mul_terms.len() + b.mul_terms.len());
254 let mut linear_combinations: Vec<(FieldElement, Witness)> =
255 Vec::with_capacity(self.linear_combinations.len() + b.linear_combinations.len());
256 let q_c = self.q_c + k * b.q_c;
257
258 //linear combinations
259 let mut i1 = 0; //a
260 let mut i2 = 0; //b
261 while i1 < self.linear_combinations.len() && i2 < b.linear_combinations.len() {
262 let (a_c, a_w) = self.linear_combinations[i1];
263 let (b_c, b_w) = b.linear_combinations[i2];
264
265 let (coeff, witness) = match a_w.cmp(&b_w) {
266 Ordering::Greater => {
267 i2 += 1;
268 (k * b_c, b_w)
269 }
270 Ordering::Less => {
271 i1 += 1;
272 (a_c, a_w)
273 }
274 Ordering::Equal => {
275 // Here we're taking both witnesses as the witness indices are equal.
276 // We then advance both `i1` and `i2`.
277 i1 += 1;
278 i2 += 1;
279 (a_c + k * b_c, a_w)
280 }
281 };
282
283 if !coeff.is_zero() {
284 linear_combinations.push((coeff, witness));
285 }
286 }
287
288 // Finally process all the remaining terms which we didn't handle in the above loop.
289 while i1 < self.linear_combinations.len() {
290 linear_combinations.push(self.linear_combinations[i1]);
291 i1 += 1;
292 }
293 while i2 < b.linear_combinations.len() {
294 let (b_c, b_w) = b.linear_combinations[i2];
295 let coeff = b_c * k;
296 if !coeff.is_zero() {
297 linear_combinations.push((coeff, b_w));
298 }
299 i2 += 1;
300 }
301
302 //mul terms
303
304 i1 = 0; //a
305 i2 = 0; //b
306 while i1 < self.mul_terms.len() && i2 < b.mul_terms.len() {
307 let (a_c, a_wl, a_wr) = self.mul_terms[i1];
308 let (b_c, b_wl, b_wr) = b.mul_terms[i2];
309
310 let (coeff, wl, wr) = match (a_wl, a_wr).cmp(&(b_wl, b_wr)) {
311 Ordering::Greater => {
312 i2 += 1;
313 (k * b_c, b_wl, b_wr)
314 }
315 Ordering::Less => {
316 i1 += 1;
317 (a_c, a_wl, a_wr)
318 }
319 Ordering::Equal => {
320 // Here we're taking both terms as the witness indices are equal.
321 // We then advance both `i1` and `i2`.
322 i2 += 1;
323 i1 += 1;
324 (a_c + k * b_c, a_wl, a_wr)
325 }
326 };
327
328 if !coeff.is_zero() {
329 mul_terms.push((coeff, wl, wr));
330 }
331 }
332
333 // Finally process all the remaining terms which we didn't handle in the above loop.
334 while i1 < self.mul_terms.len() {
335 mul_terms.push(self.mul_terms[i1]);
336 i1 += 1;
337 }
338 while i2 < b.mul_terms.len() {
339 let (b_c, b_wl, b_wr) = b.mul_terms[i2];
340 let coeff = b_c * k;
341 if coeff != FieldElement::zero() {
342 mul_terms.push((coeff, b_wl, b_wr));
343 }
344 i2 += 1;
345 }
346
347 Expression { mul_terms, linear_combinations, q_c }
348 }
349}
350
351impl From<FieldElement> for Expression {
352 fn from(constant: FieldElement) -> Expression {
353 Expression { q_c: constant, linear_combinations: Vec::new(), mul_terms: Vec::new() }
354 }
355}
356
357impl From<Witness> for Expression {
358 /// Creates an Expression from a Witness.
359 ///
360 /// This is infallible since an `Expression` is
361 /// a multi-variate polynomial and a `Witness`
362 /// can be seen as a univariate polynomial
363 fn from(wit: Witness) -> Expression {
364 Expression {
365 q_c: FieldElement::zero(),
366 linear_combinations: vec![(FieldElement::one(), wit)],
367 mul_terms: Vec::new(),
368 }
369 }
370}
371
372#[test]
373fn add_mul_smoketest() {
374 let a = Expression {
375 mul_terms: vec![(FieldElement::from(2u128), Witness(1), Witness(2))],
376 ..Default::default()
377 };
378
379 let k = FieldElement::from(10u128);
380
381 let b = Expression {
382 mul_terms: vec![
383 (FieldElement::from(3u128), Witness(0), Witness(2)),
384 (FieldElement::from(3u128), Witness(1), Witness(2)),
385 (FieldElement::from(4u128), Witness(4), Witness(5)),
386 ],
387 linear_combinations: vec![(FieldElement::from(4u128), Witness(4))],
388 q_c: FieldElement::one(),
389 };
390
391 let result = a.add_mul(k, &b);
392 assert_eq!(
393 result,
394 Expression {
395 mul_terms: vec![
396 (FieldElement::from(30u128), Witness(0), Witness(2)),
397 (FieldElement::from(32u128), Witness(1), Witness(2)),
398 (FieldElement::from(40u128), Witness(4), Witness(5)),
399 ],
400 linear_combinations: vec![(FieldElement::from(40u128), Witness(4))],
401 q_c: FieldElement::from(10u128)
402 }
403 );
404}