/ algorithms / src / r1cs / linear_combination.rs
linear_combination.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphavm library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  use crate::r1cs::Variable;
 20  use alphavm_fields::Field;
 21  
 22  use std::{
 23      cmp::Ordering,
 24      ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub},
 25  };
 26  
 27  /// This represents a linear combination of some variables, with coefficients
 28  /// in the field `F`.
 29  /// The `(coeff, var)` pairs in a `LinearCombination` are kept sorted according
 30  /// to the index of the variable in its constraint system.
 31  #[derive(Debug, Clone, PartialEq, Eq, Hash)]
 32  pub struct LinearCombination<F: Field>(pub Vec<(Variable, F)>);
 33  
 34  impl<F: Field> AsRef<[(Variable, F)]> for LinearCombination<F> {
 35      #[inline]
 36      fn as_ref(&self) -> &[(Variable, F)] {
 37          &self.0
 38      }
 39  }
 40  
 41  impl<F: Field> From<(F, Variable)> for LinearCombination<F> {
 42      #[inline]
 43      fn from((coeff, var): (F, Variable)) -> Self {
 44          LinearCombination(vec![(var, coeff)])
 45      }
 46  }
 47  
 48  impl<F: Field> From<Variable> for LinearCombination<F> {
 49      #[inline]
 50      fn from(var: Variable) -> Self {
 51          LinearCombination(vec![(var, F::one())])
 52      }
 53  }
 54  
 55  impl<F: Field> LinearCombination<F> {
 56      /// Outputs an empty linear combination.
 57      #[inline]
 58      pub fn zero() -> LinearCombination<F> {
 59          LinearCombination(Vec::new())
 60      }
 61  
 62      /// Replaces the contents of `self` with those of `other`.
 63      #[inline]
 64      pub fn replace_in_place(&mut self, other: Self) {
 65          self.0.clear();
 66          self.0.extend_from_slice(&other.0)
 67      }
 68  
 69      /// Negate the coefficients of all variables in `self`.
 70      #[inline]
 71      pub fn negate_in_place(&mut self) {
 72          self.0.iter_mut().for_each(|(_, coeff)| *coeff = -(*coeff));
 73      }
 74  
 75      /// Double the coefficients of all variables in `self`.
 76      #[inline]
 77      pub fn double_in_place(&mut self) {
 78          self.0.iter_mut().for_each(|(_, coeff)| {
 79              coeff.double_in_place();
 80          });
 81      }
 82  
 83      /// Get the location of a variable in `self`.
 84      #[inline]
 85      pub fn get_var_loc(&self, search_var: &Variable) -> Result<usize, usize> {
 86          if self.0.len() < 6 {
 87              let mut found_index = 0;
 88              for (i, (var, _)) in self.0.iter().enumerate() {
 89                  if var >= search_var {
 90                      found_index = i;
 91                      break;
 92                  } else {
 93                      found_index += 1;
 94                  }
 95              }
 96              if self.0.get(found_index).map(|x| &x.0 == search_var).unwrap_or_default() {
 97                  Ok(found_index)
 98              } else {
 99                  Err(found_index)
100              }
101          } else {
102              self.0.binary_search_by_key(search_var, |&(cur_var, _)| cur_var)
103          }
104      }
105  }
106  
107  impl<F: Field> Add<(F, Variable)> for LinearCombination<F> {
108      type Output = Self;
109  
110      #[inline]
111      fn add(mut self, coeff_var: (F, Variable)) -> Self {
112          self += coeff_var;
113          self
114      }
115  }
116  
117  impl<F: Field> AddAssign<(F, Variable)> for LinearCombination<F> {
118      #[inline]
119      fn add_assign(&mut self, (coeff, var): (F, Variable)) {
120          match self.get_var_loc(&var) {
121              Ok(found) => {
122                  self.0[found].1 += &coeff;
123                  if self.0[found].1.is_zero() {
124                      self.0.remove(found);
125                  }
126              }
127              Err(not_found) => self.0.insert(not_found, (var, coeff)),
128          }
129      }
130  }
131  
132  impl<F: Field> Sub<(F, Variable)> for LinearCombination<F> {
133      type Output = Self;
134  
135      #[inline]
136      fn sub(self, (coeff, var): (F, Variable)) -> Self {
137          self + (-coeff, var)
138      }
139  }
140  
141  impl<F: Field> Neg for LinearCombination<F> {
142      type Output = Self;
143  
144      #[inline]
145      fn neg(mut self) -> Self {
146          self.negate_in_place();
147          self
148      }
149  }
150  
151  impl<F: Field> Mul<F> for LinearCombination<F> {
152      type Output = Self;
153  
154      #[inline]
155      fn mul(mut self, scalar: F) -> Self {
156          self *= scalar;
157          self
158      }
159  }
160  
161  impl<F: Field> MulAssign<F> for LinearCombination<F> {
162      #[inline]
163      fn mul_assign(&mut self, scalar: F) {
164          self.0.iter_mut().for_each(|(_, coeff)| *coeff *= &scalar);
165      }
166  }
167  
168  impl<F: Field> Add<Variable> for LinearCombination<F> {
169      type Output = Self;
170  
171      #[inline]
172      fn add(self, other: Variable) -> LinearCombination<F> {
173          self + (F::one(), other)
174      }
175  }
176  
177  impl<F: Field> Sub<Variable> for LinearCombination<F> {
178      type Output = LinearCombination<F>;
179  
180      #[inline]
181      fn sub(self, other: Variable) -> LinearCombination<F> {
182          self - (F::one(), other)
183      }
184  }
185  
186  fn op_impl<F: Field, F1, F2>(
187      cur: &LinearCombination<F>,
188      other: &LinearCombination<F>,
189      push_fn: F1,
190      combine_fn: F2,
191  ) -> LinearCombination<F>
192  where
193      F1: Fn(F) -> F,
194      F2: Fn(F, F) -> F,
195  {
196      let mut new_vec = Vec::with_capacity(cur.0.len() + other.0.len());
197      let mut i = 0;
198      let mut j = 0;
199      while i < cur.0.len() && j < other.0.len() {
200          let self_cur = &cur.0[i];
201          let other_cur = &other.0[j];
202          match self_cur.0.cmp(&other_cur.0) {
203              Ordering::Greater => {
204                  new_vec.push((other_cur.0, push_fn(other_cur.1)));
205                  j += 1;
206              }
207              Ordering::Less => {
208                  new_vec.push(*self_cur);
209                  i += 1;
210              }
211              Ordering::Equal => {
212                  new_vec.push((self_cur.0, combine_fn(self_cur.1, other_cur.1)));
213                  i += 1;
214                  j += 1;
215              }
216          }
217      }
218      new_vec.extend_from_slice(&cur.0[i..]);
219      while j < other.0.len() {
220          new_vec.push((other.0[j].0, push_fn(other.0[j].1)));
221          j += 1;
222      }
223      LinearCombination(new_vec)
224  }
225  
226  impl<F: Field> Add<&LinearCombination<F>> for &LinearCombination<F> {
227      type Output = LinearCombination<F>;
228  
229      fn add(self, other: &LinearCombination<F>) -> LinearCombination<F> {
230          if other.0.is_empty() {
231              return self.clone();
232          } else if self.0.is_empty() {
233              return other.clone();
234          }
235          op_impl(self, other, |coeff| coeff, |cur_coeff, other_coeff| cur_coeff + other_coeff)
236      }
237  }
238  
239  impl<F: Field> Add<LinearCombination<F>> for &LinearCombination<F> {
240      type Output = LinearCombination<F>;
241  
242      fn add(self, other: LinearCombination<F>) -> LinearCombination<F> {
243          if self.0.is_empty() {
244              return other;
245          } else if other.0.is_empty() {
246              return self.clone();
247          }
248          op_impl(self, &other, |coeff| coeff, |cur_coeff, other_coeff| cur_coeff + other_coeff)
249      }
250  }
251  
252  impl<'a, F: Field> Add<&'a LinearCombination<F>> for LinearCombination<F> {
253      type Output = LinearCombination<F>;
254  
255      fn add(self, other: &'a LinearCombination<F>) -> LinearCombination<F> {
256          if other.0.is_empty() {
257              return self;
258          } else if self.0.is_empty() {
259              return other.clone();
260          }
261          op_impl(&self, other, |coeff| coeff, |cur_coeff, other_coeff| cur_coeff + other_coeff)
262      }
263  }
264  
265  impl<F: Field> Add<LinearCombination<F>> for LinearCombination<F> {
266      type Output = Self;
267  
268      fn add(self, other: Self) -> Self {
269          if other.0.is_empty() {
270              return self;
271          } else if self.0.is_empty() {
272              return other;
273          }
274          op_impl(&self, &other, |coeff| coeff, |cur_coeff, other_coeff| cur_coeff + other_coeff)
275      }
276  }
277  
278  impl<F: Field> Sub<&LinearCombination<F>> for &LinearCombination<F> {
279      type Output = LinearCombination<F>;
280  
281      fn sub(self, other: &LinearCombination<F>) -> LinearCombination<F> {
282          if other.0.is_empty() {
283              let cur = self.clone();
284              return cur;
285          } else if self.0.is_empty() {
286              let mut other = other.clone();
287              other.negate_in_place();
288              return other;
289          }
290  
291          op_impl(self, other, |coeff| -coeff, |cur_coeff, other_coeff| cur_coeff - other_coeff)
292      }
293  }
294  
295  impl<'a, F: Field> Sub<&'a LinearCombination<F>> for LinearCombination<F> {
296      type Output = LinearCombination<F>;
297  
298      fn sub(self, other: &'a LinearCombination<F>) -> LinearCombination<F> {
299          if other.0.is_empty() {
300              return self;
301          } else if self.0.is_empty() {
302              let mut other = other.clone();
303              other.negate_in_place();
304              return other;
305          }
306          op_impl(&self, other, |coeff| -coeff, |cur_coeff, other_coeff| cur_coeff - other_coeff)
307      }
308  }
309  
310  impl<F: Field> Sub<LinearCombination<F>> for &LinearCombination<F> {
311      type Output = LinearCombination<F>;
312  
313      fn sub(self, mut other: LinearCombination<F>) -> LinearCombination<F> {
314          if self.0.is_empty() {
315              other.negate_in_place();
316              return other;
317          } else if other.0.is_empty() {
318              return self.clone();
319          }
320  
321          op_impl(self, &other, |coeff| -coeff, |cur_coeff, other_coeff| cur_coeff - other_coeff)
322      }
323  }
324  
325  impl<F: Field> Sub<LinearCombination<F>> for LinearCombination<F> {
326      type Output = LinearCombination<F>;
327  
328      fn sub(self, mut other: LinearCombination<F>) -> LinearCombination<F> {
329          if other.0.is_empty() {
330              return self;
331          } else if self.0.is_empty() {
332              other.negate_in_place();
333              return other;
334          }
335          op_impl(&self, &other, |coeff| -coeff, |cur_coeff, other_coeff| cur_coeff - other_coeff)
336      }
337  }
338  
339  impl<F: Field> Add<(F, &LinearCombination<F>)> for &LinearCombination<F> {
340      type Output = LinearCombination<F>;
341  
342      #[allow(clippy::suspicious_arithmetic_impl)]
343      fn add(self, (mul_coeff, other): (F, &LinearCombination<F>)) -> LinearCombination<F> {
344          if other.0.is_empty() {
345              return self.clone();
346          } else if self.0.is_empty() {
347              let mut other = other.clone();
348              other.mul_assign(mul_coeff);
349              return other;
350          }
351          op_impl(self, other, |coeff| mul_coeff * coeff, |cur_coeff, other_coeff| cur_coeff + (mul_coeff * other_coeff))
352      }
353  }
354  
355  impl<'a, F: Field> Add<(F, &'a LinearCombination<F>)> for LinearCombination<F> {
356      type Output = LinearCombination<F>;
357  
358      #[allow(clippy::suspicious_arithmetic_impl)]
359      fn add(self, (mul_coeff, other): (F, &'a LinearCombination<F>)) -> LinearCombination<F> {
360          if other.0.is_empty() {
361              return self;
362          } else if self.0.is_empty() {
363              let mut other = other.clone();
364              other.mul_assign(mul_coeff);
365              return other;
366          }
367          op_impl(&self, other, |coeff| mul_coeff * coeff, |cur_coeff, other_coeff| cur_coeff + (mul_coeff * other_coeff))
368      }
369  }
370  
371  impl<F: Field> Add<(F, LinearCombination<F>)> for &LinearCombination<F> {
372      type Output = LinearCombination<F>;
373  
374      #[allow(clippy::suspicious_arithmetic_impl)]
375      fn add(self, (mul_coeff, mut other): (F, LinearCombination<F>)) -> LinearCombination<F> {
376          if other.0.is_empty() {
377              return self.clone();
378          } else if self.0.is_empty() {
379              other.mul_assign(mul_coeff);
380              return other;
381          }
382          op_impl(self, &other, |coeff| mul_coeff * coeff, |cur_coeff, other_coeff| cur_coeff + (mul_coeff * other_coeff))
383      }
384  }
385  
386  impl<F: Field> Add<(F, Self)> for LinearCombination<F> {
387      type Output = Self;
388  
389      #[allow(clippy::suspicious_arithmetic_impl)]
390      fn add(self, (mul_coeff, other): (F, Self)) -> Self {
391          if other.0.is_empty() {
392              return self;
393          } else if self.0.is_empty() {
394              let mut other = other;
395              other.mul_assign(mul_coeff);
396              return other;
397          }
398          op_impl(
399              &self,
400              &other,
401              |coeff| mul_coeff * coeff,
402              |cur_coeff, other_coeff| cur_coeff + (mul_coeff * other_coeff),
403          )
404      }
405  }
406  
407  impl<F: Field> Sub<(F, &LinearCombination<F>)> for &LinearCombination<F> {
408      type Output = LinearCombination<F>;
409  
410      fn sub(self, (coeff, other): (F, &LinearCombination<F>)) -> LinearCombination<F> {
411          self + (-coeff, other)
412      }
413  }
414  
415  impl<'a, F: Field> Sub<(F, &'a LinearCombination<F>)> for LinearCombination<F> {
416      type Output = LinearCombination<F>;
417  
418      fn sub(self, (coeff, other): (F, &'a LinearCombination<F>)) -> LinearCombination<F> {
419          self + (-coeff, other)
420      }
421  }
422  
423  impl<F: Field> Sub<(F, LinearCombination<F>)> for &LinearCombination<F> {
424      type Output = LinearCombination<F>;
425  
426      fn sub(self, (coeff, other): (F, LinearCombination<F>)) -> LinearCombination<F> {
427          self + (-coeff, other)
428      }
429  }
430  
431  impl<F: Field> Sub<(F, LinearCombination<F>)> for LinearCombination<F> {
432      type Output = LinearCombination<F>;
433  
434      fn sub(self, (coeff, other): (F, LinearCombination<F>)) -> LinearCombination<F> {
435          self + (-coeff, other)
436      }
437  }
438  
439  #[cfg(test)]
440  mod tests {
441      use crate::r1cs::Index;
442  
443      use super::*;
444      use alphavm_curves::bls12_377::Fr;
445  
446      #[test]
447      fn linear_combination_append() {
448          let mut combo = LinearCombination::<Fr>::zero();
449          for i in 0..100u64 {
450              combo += (i.into(), Variable::new_unchecked(Index::Public(0)));
451          }
452          assert_eq!(combo.0.len(), 1);
453      }
454  }