nova_snark/frontend/util_cs/
test_cs.rs1use std::collections::HashMap;
4
5use crate::frontend::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable};
6
7use ff::PrimeField;
8
9#[derive(Debug)]
10enum NamedObject {
11 Constraint,
12 Var,
13 Namespace,
14}
15
16#[derive(Debug)]
18pub struct TestConstraintSystem<Scalar: PrimeField> {
19 named_objects: HashMap<String, NamedObject>,
20 current_namespace: Vec<String>,
21 #[allow(clippy::type_complexity)]
22 constraints: Vec<(
23 LinearCombination<Scalar>,
24 LinearCombination<Scalar>,
25 LinearCombination<Scalar>,
26 String,
27 )>,
28 inputs: Vec<(Scalar, String)>,
29 aux: Vec<(Scalar, String)>,
30}
31
32fn _eval_lc2<Scalar: PrimeField>(
33 terms: &LinearCombination<Scalar>,
34 inputs: &[Scalar],
35 aux: &[Scalar],
36) -> Scalar {
37 let mut acc = Scalar::ZERO;
38
39 for (var, coeff) in terms.iter() {
40 let mut tmp = match var.get_unchecked() {
41 Index::Input(index) => inputs[index],
42 Index::Aux(index) => aux[index],
43 };
44
45 tmp.mul_assign(coeff);
46 acc.add_assign(&tmp);
47 }
48
49 acc
50}
51
52fn eval_lc<Scalar: PrimeField>(
53 terms: &LinearCombination<Scalar>,
54 inputs: &[(Scalar, String)],
55 aux: &[(Scalar, String)],
56) -> Scalar {
57 let mut acc = Scalar::ZERO;
58
59 for (var, coeff) in terms.iter() {
60 let mut tmp = match var.get_unchecked() {
61 Index::Input(index) => inputs[index].0,
62 Index::Aux(index) => aux[index].0,
63 };
64
65 tmp.mul_assign(coeff);
66 acc.add_assign(&tmp);
67 }
68
69 acc
70}
71
72impl<Scalar: PrimeField> Default for TestConstraintSystem<Scalar> {
73 fn default() -> Self {
74 let mut map = HashMap::new();
75 map.insert("ONE".into(), NamedObject::Var);
76
77 TestConstraintSystem {
78 named_objects: map,
79 current_namespace: vec![],
80 constraints: vec![],
81 inputs: vec![(Scalar::ONE, "ONE".into())],
82 aux: vec![],
83 }
84 }
85}
86
87impl<Scalar: PrimeField> TestConstraintSystem<Scalar> {
88 pub fn new() -> Self {
90 Default::default()
91 }
92
93 pub fn num_constraints(&self) -> usize {
95 self.constraints.len()
96 }
97
98 pub fn which_is_unsatisfied(&self) -> Option<&str> {
100 for (a, b, c, path) in &self.constraints {
101 let mut a = eval_lc::<Scalar>(a, &self.inputs, &self.aux);
102 let b = eval_lc::<Scalar>(b, &self.inputs, &self.aux);
103 let c = eval_lc::<Scalar>(c, &self.inputs, &self.aux);
104
105 a.mul_assign(&b);
106
107 if a != c {
108 return Some(path);
109 }
110 }
111
112 None
113 }
114
115 pub fn is_satisfied(&self) -> bool {
117 match self.which_is_unsatisfied() {
118 Some(b) => {
119 #[allow(clippy::print_stdout)]
120 {
121 println!("fail: {b:?}");
122 }
123 false
124 }
125 None => true,
126 }
127 }
128
129 fn set_named_obj(&mut self, path: String, to: NamedObject) {
130 assert!(
131 !self.named_objects.contains_key(&path),
132 "tried to create object at existing path: {path}"
133 );
134
135 self.named_objects.insert(path, to);
136 }
137}
138
139fn compute_path(ns: &[String], this: &str) -> String {
140 assert!(
141 !this.chars().any(|a| a == '/'),
142 "'/' is not allowed in names"
143 );
144
145 if ns.is_empty() {
146 return this.to_string();
147 }
148
149 let name = ns.join("/");
150 format!("{name}/{this}")
151}
152
153impl<Scalar: PrimeField> ConstraintSystem<Scalar> for TestConstraintSystem<Scalar> {
154 type Root = Self;
155
156 fn alloc<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
157 where
158 F: FnOnce() -> Result<Scalar, SynthesisError>,
159 A: FnOnce() -> AR,
160 AR: Into<String>,
161 {
162 let index = self.aux.len();
163 let path = compute_path(&self.current_namespace, &annotation().into());
164 self.aux.push((f()?, path.clone()));
165 let var = Variable::new_unchecked(Index::Aux(index));
166 self.set_named_obj(path, NamedObject::Var);
167
168 Ok(var)
169 }
170
171 fn alloc_input<F, A, AR>(&mut self, annotation: A, f: F) -> Result<Variable, SynthesisError>
172 where
173 F: FnOnce() -> Result<Scalar, SynthesisError>,
174 A: FnOnce() -> AR,
175 AR: Into<String>,
176 {
177 let index = self.inputs.len();
178 let path = compute_path(&self.current_namespace, &annotation().into());
179 self.inputs.push((f()?, path.clone()));
180 let var = Variable::new_unchecked(Index::Input(index));
181 self.set_named_obj(path, NamedObject::Var);
182
183 Ok(var)
184 }
185
186 fn enforce<A, AR, LA, LB, LC>(&mut self, annotation: A, a: LA, b: LB, c: LC)
187 where
188 A: FnOnce() -> AR,
189 AR: Into<String>,
190 LA: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
191 LB: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
192 LC: FnOnce(LinearCombination<Scalar>) -> LinearCombination<Scalar>,
193 {
194 let path = compute_path(&self.current_namespace, &annotation().into());
195 self.set_named_obj(path.clone(), NamedObject::Constraint);
196
197 let a = a(LinearCombination::zero());
198 let b = b(LinearCombination::zero());
199 let c = c(LinearCombination::zero());
200
201 self.constraints.push((a, b, c, path));
202 }
203
204 fn push_namespace<NR, N>(&mut self, name_fn: N)
205 where
206 NR: Into<String>,
207 N: FnOnce() -> NR,
208 {
209 let name = name_fn().into();
210 let path = compute_path(&self.current_namespace, &name);
211 self.set_named_obj(path, NamedObject::Namespace);
212 self.current_namespace.push(name);
213 }
214
215 fn pop_namespace(&mut self) {
216 assert!(self.current_namespace.pop().is_some());
217 }
218
219 fn get_root(&mut self) -> &mut Self::Root {
220 self
221 }
222}