/ algorithms / src / r1cs / test_constraint_system.rs
test_constraint_system.rs
  1  // Copyright (c) 2025 ADnet Contributors
  2  // This file is part of the AlphaVM library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use crate::r1cs::{ConstraintSystem, Index, LinearCombination, OptionalVec, Variable, errors::SynthesisError};
 17  use alphavm_fields::Field;
 18  
 19  use cfg_if::cfg_if;
 20  use fxhash::{FxBuildHasher, FxHashMap};
 21  use indexmap::{IndexMap, IndexSet, map::Entry};
 22  use itertools::Itertools;
 23  
 24  /// This field is the scalar field (Fr) of BLS12-377.
 25  pub type Fr = alphavm_curves::bls12_377::Fr;
 26  
 27  #[allow(dead_code)]
 28  #[derive(Debug, Clone)]
 29  enum NamedObject {
 30      Constraint(usize),
 31      Var(Variable),
 32      // contains the list of named objects that belong to it
 33      Namespace(Namespace),
 34  }
 35  
 36  #[derive(Debug, Clone, Default)]
 37  struct Namespace {
 38      children: Vec<NamedObject>,
 39      idx: NamespaceIndex,
 40  }
 41  
 42  impl Namespace {
 43      fn push(&mut self, child: NamedObject) {
 44          self.children.push(child);
 45      }
 46  }
 47  
 48  type InternedField = usize;
 49  type InternedPathSegment = usize;
 50  type NamespaceIndex = usize;
 51  
 52  #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
 53  pub struct InternedPath {
 54      parent_namespace: NamespaceIndex,
 55      last_segment: InternedPathSegment,
 56  }
 57  
 58  #[derive(PartialEq, Eq, Hash)]
 59  pub struct TestConstraint {
 60      interned_path: InternedPath,
 61      a: Vec<(Variable, InternedField)>,
 62      b: Vec<(Variable, InternedField)>,
 63      c: Vec<(Variable, InternedField)>,
 64  }
 65  
 66  #[derive(Default, Debug)]
 67  pub struct CurrentNamespace {
 68      segments: Vec<InternedPathSegment>,
 69      indices: Vec<NamespaceIndex>,
 70  }
 71  
 72  impl CurrentNamespace {
 73      fn idx(&self) -> usize {
 74          self.indices.last().copied().unwrap_or(0)
 75      }
 76  
 77      fn pop(&mut self) {
 78          assert!(self.segments.pop().is_some());
 79          assert!(self.indices.pop().is_some());
 80      }
 81  }
 82  
 83  /// Constraint system for testing purposes.
 84  pub struct TestConstraintSystem<F: Field> {
 85      // used to intern full paths in test scenarios, for get and set purposes
 86      interned_full_paths: FxHashMap<Vec<InternedPathSegment>, InternedPath>,
 87      // used to intern namespace segments
 88      interned_path_segments: IndexSet<String, FxBuildHasher>,
 89      // used to intern fields belonging to F
 90      interned_fields: IndexSet<F, FxBuildHasher>,
 91      // contains named objects bound to their (interned) paths; the indices are
 92      // used for NamespaceIndex lookups
 93      named_objects: IndexMap<InternedPath, NamedObject, FxBuildHasher>,
 94      // a stack of current path's segments and the index of the current path's
 95      // index in the named_objects map
 96      current_namespace: CurrentNamespace,
 97      // the list of currently applicable constraints
 98      constraints: OptionalVec<TestConstraint>,
 99      // the list of currently applicable input variables
100      public_variables: OptionalVec<InternedField>,
101      // the list of currently applicable auxiliary variables
102      private_variables: OptionalVec<InternedField>,
103  }
104  
105  impl<F: Field> Default for TestConstraintSystem<F> {
106      fn default() -> Self {
107          let mut interned_path_segments = IndexSet::with_hasher(FxBuildHasher::default());
108          let path_segment = "ONE".to_owned();
109          let interned_path_segment = interned_path_segments.insert_full(path_segment).0;
110          let interned_path = InternedPath { parent_namespace: 0, last_segment: interned_path_segment };
111  
112          cfg_if! {
113              if #[cfg(debug_assertions)] {
114                  let mut interned_full_paths = FxHashMap::default();
115                  interned_full_paths.insert(vec![interned_path_segment], interned_path);
116              } else {
117                  let interned_full_paths = FxHashMap::default();
118              }
119          }
120  
121          let mut named_objects = IndexMap::with_hasher(FxBuildHasher::default());
122          named_objects.insert_full(interned_path, NamedObject::Var(TestConstraintSystem::<F>::one()));
123  
124          let mut interned_fields = IndexSet::with_hasher(FxBuildHasher::default());
125          let interned_field = interned_fields.insert_full(F::one()).0;
126  
127          let mut inputs: OptionalVec<InternedField> = Default::default();
128          inputs.insert(interned_field);
129  
130          let constraints = OptionalVec::default();
131  
132          TestConstraintSystem {
133              interned_full_paths,
134              interned_fields,
135              interned_path_segments,
136              named_objects,
137              current_namespace: Default::default(),
138              constraints,
139              public_variables: inputs,
140              private_variables: Default::default(),
141          }
142      }
143  }
144  
145  impl<F: Field> TestConstraintSystem<F> {
146      pub fn new() -> Self {
147          Self::default()
148      }
149  
150      /// Prints the constraint at which `self` and `other` differ.
151      pub fn diff(&self, other: &Self) {
152          for (i, (self_c, other_c)) in self.constraints.iter().zip(other.constraints.iter()).enumerate() {
153              let self_interned_path = self_c.interned_path;
154              let other_interned_path = other_c.interned_path;
155              if self_c.a != other_c.a {
156                  println!("A row {i} is different:");
157                  println!("self: {}", self.unintern_path(self_interned_path));
158                  println!("other: {}", other.unintern_path(other_interned_path));
159                  break;
160              }
161  
162              if self_c.b != other_c.b {
163                  println!("B row {i} is different:");
164                  println!("self: {}", self.unintern_path(self_interned_path));
165                  println!("other: {}", other.unintern_path(other_interned_path));
166                  break;
167              }
168  
169              if self_c.c != other_c.c {
170                  println!("C row {i} is different:");
171                  println!("self: {}", self.unintern_path(self_interned_path));
172                  println!("other: {}", other.unintern_path(other_interned_path));
173                  break;
174              }
175          }
176      }
177  
178      #[inline]
179      fn intern_path(&self, path: &str) -> InternedPath {
180          let mut vec = vec![];
181  
182          for segment in path.split('/') {
183              vec.push(self.interned_path_segments.get_index_of(segment).unwrap());
184          }
185  
186          *self.interned_full_paths.get(&vec).unwrap()
187      }
188  
189      fn unintern_path(&self, interned_path: InternedPath) -> String {
190          let last_segment = self.interned_path_segments.get_index(interned_path.last_segment).unwrap();
191          let mut reversed_uninterned_segments = vec![last_segment];
192  
193          let mut parent_ns = interned_path.parent_namespace;
194          while parent_ns != 0 {
195              let interned_parent_ns = self.named_objects.get_index(parent_ns).unwrap().0;
196              let parent_segment = self.interned_path_segments.get_index(interned_parent_ns.last_segment).unwrap();
197              reversed_uninterned_segments.push(parent_segment);
198              parent_ns = interned_parent_ns.parent_namespace;
199          }
200  
201          let segments = reversed_uninterned_segments.into_iter().map(|s| s.as_str()).rev();
202          Itertools::intersperse(segments, "/").collect()
203      }
204  
205      pub fn print_named_objects(&self) {
206          for TestConstraint { interned_path, .. } in self.constraints.iter() {
207              println!("{}", self.unintern_path(*interned_path));
208          }
209      }
210  
211      fn eval_lc(&self, terms: &[(Variable, InternedField)]) -> F {
212          let mut acc = F::zero();
213  
214          for &(var, interned_coeff) in terms {
215              let interned_tmp = match var.get_unchecked() {
216                  Index::Public(index) => self.public_variables[index],
217                  Index::Private(index) => self.private_variables[index],
218              };
219              let mut tmp = *self.interned_fields.get_index(interned_tmp).unwrap();
220              let coeff = self.interned_fields.get_index(interned_coeff).unwrap();
221  
222              tmp.mul_assign(coeff);
223              acc.add_assign(tmp);
224          }
225  
226          acc
227      }
228  
229      pub fn which_is_unsatisfied(&self) -> Option<String> {
230          for TestConstraint { interned_path, a, b, c } in self.constraints.iter() {
231              let mut a = self.eval_lc(a.as_ref());
232              let b = self.eval_lc(b.as_ref());
233              let c = self.eval_lc(c.as_ref());
234  
235              a.mul_assign(&b);
236  
237              if a != c {
238                  return Some(self.unintern_path(*interned_path));
239              }
240          }
241  
242          None
243      }
244  
245      #[inline]
246      pub fn is_satisfied(&self) -> bool {
247          self.which_is_unsatisfied().is_none()
248      }
249  
250      #[inline]
251      pub fn num_non_zero(&self) -> (usize, usize, usize) {
252          let mut non_zero_a = 0;
253          let mut non_zero_b = 0;
254          let mut non_zero_c = 0;
255          for TestConstraint { a, b, c, .. } in self.constraints.iter() {
256              non_zero_a += a.len();
257              non_zero_b += b.len();
258              non_zero_c += c.len();
259          }
260          (non_zero_a, non_zero_b, non_zero_c)
261      }
262  
263      #[inline]
264      pub fn num_constraints(&self) -> usize {
265          self.constraints.len()
266      }
267  
268      #[inline]
269      pub fn get_constraint_path(&self, i: usize) -> String {
270          self.unintern_path(self.constraints.iter().nth(i).unwrap().interned_path)
271      }
272  
273      pub fn set(&mut self, path: &str, to: F) {
274          let interned_path = self.intern_path(path);
275          let interned_field = self.interned_fields.insert_full(to).0;
276  
277          match self.named_objects.get(&interned_path) {
278              Some(NamedObject::Var(v)) => match v.get_unchecked() {
279                  Index::Public(index) => self.public_variables[index] = interned_field,
280                  Index::Private(index) => self.private_variables[index] = interned_field,
281              },
282              Some(e) => panic!("tried to set path `{path}` to value, but `{e:?}` already exists there."),
283              _ => panic!("no variable exists at path: {path}"),
284          }
285      }
286  
287      pub fn get(&mut self, path: &str) -> F {
288          let interned_path = self.intern_path(path);
289  
290          let interned_field = match self.named_objects.get(&interned_path) {
291              Some(NamedObject::Var(v)) => match v.get_unchecked() {
292                  Index::Public(index) => self.public_variables[index],
293                  Index::Private(index) => self.private_variables[index],
294              },
295              Some(e) => panic!("tried to get value of path `{path}`, but `{e:?}` exists there (not a variable)"),
296              _ => panic!("no variable exists at path: {path}"),
297          };
298  
299          *self.interned_fields.get_index(interned_field).unwrap()
300      }
301  
302      #[inline]
303      fn set_named_obj(&mut self, interned_path: InternedPath, to: NamedObject) -> NamespaceIndex {
304          match self.named_objects.entry(interned_path) {
305              Entry::Vacant(e) => {
306                  let ns_idx = e.index();
307                  e.insert(to);
308                  ns_idx
309              }
310              Entry::Occupied(e) => {
311                  let interned_segments = e.swap_remove_entry().0;
312                  panic!("tried to create object at existing path: {}", self.unintern_path(interned_segments));
313              }
314          }
315      }
316  
317      #[inline]
318      fn compute_path(&mut self, new_segment: &str) -> InternedPath {
319          let (interned_segment, new) = if let Some(index) = self.interned_path_segments.get_index_of(new_segment) {
320              (index, false)
321          } else {
322              self.interned_path_segments.insert_full(new_segment.to_owned())
323          };
324  
325          // only perform the check for segments not seen before
326          assert!(!new || !new_segment.contains('/'), "'/' is not allowed in names");
327  
328          let interned_path =
329              InternedPath { parent_namespace: self.current_namespace.idx(), last_segment: interned_segment };
330  
331          cfg_if! {
332              if #[cfg(debug_assertions)] {
333                  let mut full_path = self.current_namespace.segments.clone();
334                  full_path.push(interned_segment);
335                  self.interned_full_paths.insert(full_path, interned_path);
336              }
337          }
338  
339          interned_path
340      }
341  
342      #[cfg(not(debug_assertions))]
343      fn purge_namespace(&mut self, namespace: Namespace) {
344          for child_obj in namespace.children {
345              match child_obj {
346                  NamedObject::Var(var) => match var.get_unchecked() {
347                      Index::Private(idx) => {
348                          self.private_variables.remove(idx);
349                      }
350                      Index::Public(idx) => {
351                          self.public_variables.remove(idx);
352                      }
353                  },
354                  NamedObject::Constraint(idx) => {
355                      self.constraints.remove(idx);
356                  }
357                  NamedObject::Namespace(children) => {
358                      self.purge_namespace(children);
359                  }
360              }
361              self.named_objects.swap_remove_index(namespace.idx);
362          }
363      }
364  
365      #[inline]
366      fn register_object_in_namespace(&mut self, named_obj: NamedObject) {
367          if let NamedObject::Namespace(ns) = self.named_objects.get_index_mut(self.current_namespace.idx()).unwrap().1 {
368              ns.push(named_obj);
369          }
370      }
371  }
372  
373  impl<F: Field> ConstraintSystem<F> for TestConstraintSystem<F> {
374      type Root = Self;
375  
376      fn alloc<Fn, A, AR>(&mut self, annotation: A, f: Fn) -> Result<Variable, SynthesisError>
377      where
378          Fn: FnOnce() -> Result<F, SynthesisError>,
379          A: FnOnce() -> AR,
380          AR: AsRef<str>,
381      {
382          let interned_path = self.compute_path(annotation().as_ref());
383          let interned_field = self.interned_fields.insert_full(f()?).0;
384          let index = self.private_variables.insert(interned_field);
385          let var = Variable::new_unchecked(Index::Private(index));
386          let named_obj = NamedObject::Var(var);
387          self.register_object_in_namespace(named_obj.clone());
388          self.set_named_obj(interned_path, named_obj);
389  
390          Ok(var)
391      }
392  
393      fn alloc_input<Fn, A, AR>(&mut self, annotation: A, f: Fn) -> Result<Variable, SynthesisError>
394      where
395          Fn: FnOnce() -> Result<F, SynthesisError>,
396          A: FnOnce() -> AR,
397          AR: AsRef<str>,
398      {
399          let interned_path = self.compute_path(annotation().as_ref());
400          let interned_field = self.interned_fields.insert_full(f()?).0;
401          let index = self.public_variables.insert(interned_field);
402          let var = Variable::new_unchecked(Index::Public(index));
403          let named_obj = NamedObject::Var(var);
404          self.register_object_in_namespace(named_obj.clone());
405          self.set_named_obj(interned_path, named_obj);
406  
407          Ok(var)
408      }
409  
410      fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
411      where
412          A: FnOnce() -> AR,
413          AR: AsRef<str>,
414          LA: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
415          LB: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
416          LC: FnOnce(LinearCombination<F>) -> LinearCombination<F>,
417      {
418          let interned_path = self.compute_path(annotation().as_ref());
419          let index = self.constraints.next_idx();
420          let named_obj = NamedObject::Constraint(index);
421          self.register_object_in_namespace(named_obj.clone());
422          self.set_named_obj(interned_path, named_obj);
423  
424          let mut intern_fields = |uninterned: Vec<(Variable, F)>| -> Vec<(Variable, InternedField)> {
425              uninterned
426                  .into_iter()
427                  .map(|(var, field)| {
428                      let interned_field = self.interned_fields.insert_full(field).0;
429                      (var, interned_field)
430                  })
431                  .collect()
432          };
433  
434          let a = intern_fields(a(LinearCombination::zero()).0);
435          let b = intern_fields(b(LinearCombination::zero()).0);
436          let c = intern_fields(c(LinearCombination::zero()).0);
437  
438          self.constraints.insert(TestConstraint { interned_path, a, b, c });
439      }
440  
441      fn push_namespace<NR: AsRef<str>, N: FnOnce() -> NR>(&mut self, name_fn: N) {
442          let name = name_fn();
443          let interned_path = self.compute_path(name.as_ref());
444          let new_segment = interned_path.last_segment;
445          let named_obj = NamedObject::Namespace(Default::default());
446          self.register_object_in_namespace(named_obj.clone());
447          let namespace_idx = self.set_named_obj(interned_path, named_obj);
448          if let NamedObject::Namespace(ref mut ns) = self.named_objects[namespace_idx] {
449              ns.idx = namespace_idx;
450          };
451  
452          self.current_namespace.segments.push(new_segment);
453          self.current_namespace.indices.push(namespace_idx);
454      }
455  
456      #[cfg(not(debug_assertions))]
457      fn pop_namespace(&mut self) {
458          let namespace = if let NamedObject::Namespace(no) =
459              self.named_objects.swap_remove_index(self.current_namespace.idx()).unwrap().1
460          {
461              no
462          } else {
463              unreachable!()
464          };
465  
466          // remove object belonging to the popped namespace
467          self.purge_namespace(namespace);
468  
469          // update the current namespace
470          self.current_namespace.pop();
471      }
472  
473      #[cfg(debug_assertions)]
474      fn pop_namespace(&mut self) {
475          // don't perform a full cleanup in test conditions, so that all the variables,
476          // constraints and namespace indices remain available throughout the tests
477  
478          self.current_namespace.pop();
479      }
480  
481      #[inline]
482      fn get_root(&mut self) -> &mut Self::Root {
483          self
484      }
485  
486      #[inline]
487      fn num_constraints(&self) -> usize {
488          self.num_constraints()
489      }
490  
491      #[inline]
492      fn num_public_variables(&self) -> usize {
493          self.public_variables.len()
494      }
495  
496      #[inline]
497      fn num_private_variables(&self) -> usize {
498          self.private_variables.len()
499      }
500  
501      #[inline]
502      fn is_in_setup_mode(&self) -> bool {
503          false
504      }
505  }