1use crate::ProgramCell;
32use crate::constraint::{ConstraintArena, ConstraintAst, ExprId};
33use alloc::vec::Vec;
34use core::cell::RefCell;
35use core::ops::{Add, Mul, Sub};
36use hekate_math::TowerField;
37
38pub struct ConstraintSystem<F: TowerField> {
44 inner: RefCell<Inner<F>>,
45}
46
47struct Inner<F: TowerField> {
48 arena: ConstraintArena<F>,
49 roots: Vec<ExprId>,
50 labels: Vec<Option<&'static str>>,
51}
52
53impl<F: TowerField> ConstraintSystem<F> {
54 pub fn new() -> Self {
56 Self {
57 inner: RefCell::new(Inner {
58 arena: ConstraintArena::new(),
59 roots: Vec::new(),
60 labels: Vec::new(),
61 }),
62 }
63 }
64
65 pub fn from_ast(ast: ConstraintAst<F>) -> Self {
67 Self {
68 inner: RefCell::new(Inner {
69 arena: ast.arena,
70 roots: ast.roots,
71 labels: ast.labels,
72 }),
73 }
74 }
75
76 pub fn col(&self, idx: usize) -> Expr<'_, F> {
82 let id = self
83 .inner
84 .borrow_mut()
85 .arena
86 .cell(ProgramCell::current(idx));
87 Expr { id, cs: self }
88 }
89
90 pub fn next(&self, idx: usize) -> Expr<'_, F> {
92 let id = self.inner.borrow_mut().arena.cell(ProgramCell::next(idx));
93 Expr { id, cs: self }
94 }
95
96 pub fn constant(&self, val: F) -> Expr<'_, F> {
102 let id = self.inner.borrow_mut().arena.constant(val);
103 Expr { id, cs: self }
104 }
105
106 pub fn one(&self) -> Expr<'_, F> {
108 self.constant(F::ONE)
109 }
110
111 pub fn scale(&self, coeff: F, expr: Expr<'_, F>) -> Expr<'_, F> {
122 let id = self.inner.borrow_mut().arena.scale(coeff, expr.id);
123 Expr { id, cs: self }
124 }
125
126 pub fn sum(&self, children: &[Expr<'_, F>]) -> Expr<'_, F> {
129 let ids: Vec<ExprId> = children.iter().map(|e| e.id).collect();
130 let id = self.inner.borrow_mut().arena.sum(ids);
131
132 Expr { id, cs: self }
133 }
134
135 pub fn constrain(&self, expr: Expr<'_, F>) {
145 let mut inner = self.inner.borrow_mut();
146 inner.roots.push(expr.id);
147 inner.labels.push(None);
148 }
149
150 pub fn constrain_named(&self, label: &'static str, expr: Expr<'_, F>) {
151 let mut inner = self.inner.borrow_mut();
152 inner.roots.push(expr.id);
153 inner.labels.push(Some(label));
154 }
155
156 pub fn assert_boolean(&self, s: Expr<'_, F>) {
166 let sq = s * s;
168 let expr = sq + s;
169
170 self.constrain_named("boolean", expr);
171 }
172
173 pub fn assert_zero_when(&self, sel: Expr<'_, F>, body: Expr<'_, F>) {
179 self.constrain_named("zero_when", sel * body);
180 }
181
182 pub fn assert_one_hot(&self, selectors: &[Expr<'_, F>]) {
190 let s = self.sum(selectors);
191 let one = self.one();
192
193 self.constrain_named("one_hot", s + one);
194 }
195
196 pub fn num_roots(&self) -> usize {
198 self.inner.borrow().roots.len()
199 }
200
201 pub fn merge_ast(&self, other: ConstraintAst<F>) {
204 let mut inner = self.inner.borrow_mut();
205 let mut ast = ConstraintAst {
206 arena: core::mem::take(&mut inner.arena),
207 roots: core::mem::take(&mut inner.roots),
208 labels: core::mem::take(&mut inner.labels),
209 };
210
211 ast.merge(other);
212
213 inner.arena = ast.arena;
214 inner.roots = ast.roots;
215 inner.labels = ast.labels;
216 }
217
218 pub fn build(self) -> ConstraintAst<F> {
230 let inner = self.inner.into_inner();
231 ConstraintAst {
232 arena: inner.arena,
233 roots: inner.roots,
234 labels: inner.labels,
235 }
236 }
237}
238
239impl<F: TowerField> Default for ConstraintSystem<F> {
240 fn default() -> Self {
241 Self::new()
242 }
243}
244
245#[derive(Clone, Copy)]
254pub struct Expr<'a, F: TowerField> {
255 pub(crate) id: ExprId,
256 pub(crate) cs: &'a ConstraintSystem<F>,
257}
258
259impl<'a, F: TowerField> Add for Expr<'a, F> {
261 type Output = Expr<'a, F>;
262
263 fn add(self, rhs: Self) -> Self::Output {
264 let id = self.cs.inner.borrow_mut().arena.add(self.id, rhs.id);
265 Expr { id, cs: self.cs }
266 }
267}
268
269impl<'a, F: TowerField> Mul for Expr<'a, F> {
271 type Output = Expr<'a, F>;
272
273 fn mul(self, rhs: Self) -> Self::Output {
274 let id = self.cs.inner.borrow_mut().arena.mul(self.id, rhs.id);
275 Expr { id, cs: self.cs }
276 }
277}
278
279impl<'a, F: TowerField> Sub for Expr<'a, F> {
281 type Output = Expr<'a, F>;
282
283 fn sub(self, rhs: Self) -> Self::Output {
284 let id = self.cs.inner.borrow_mut().arena.add(self.id, rhs.id);
288 Expr { id, cs: self.cs }
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use crate::constraint::ConstraintExpr;
296 use hekate_math::Block128;
297
298 type F = Block128;
299
300 #[test]
301 fn basic_fibonacci_builder() {
302 let cs = ConstraintSystem::<F>::new();
303
304 let a = cs.col(0);
305 let b = cs.col(1);
306 let q = cs.col(2);
307 let na = cs.next(0);
308 let nb = cs.next(1);
309
310 cs.constrain(q * (na + b));
312 cs.constrain(q * (nb + a + b));
314
315 let ast = cs.build();
316
317 assert_eq!(ast.roots.len(), 2);
318 assert!(!ast.arena.is_empty());
319
320 for &root in &ast.roots {
322 match ast.arena.get(root) {
323 ConstraintExpr::Mul(_, _) => {}
324 other => panic!("Expected Mul root, got {:?}", other),
325 }
326 }
327 }
328
329 #[test]
330 fn cell_dedup_through_builder() {
331 let cs = ConstraintSystem::<F>::new();
332
333 let a1 = cs.col(0);
334 let a2 = cs.col(0);
335 let b = cs.col(1);
336
337 assert_eq!(a1.id, a2.id);
339 assert_ne!(a1.id, b.id);
341 }
342
343 #[test]
344 fn sub_equals_add_in_char2() {
345 let cs = ConstraintSystem::<F>::new();
346
347 let a = cs.col(0);
348 let b = cs.col(1);
349
350 let sum = a + b;
351 let diff = a - b;
352
353 let ast_sum = cs.inner.borrow();
356 match (ast_sum.arena.get(sum.id), ast_sum.arena.get(diff.id)) {
357 (ConstraintExpr::Add(la, ra), ConstraintExpr::Add(lb, rb)) => {
358 assert_eq!(la, lb);
359 assert_eq!(ra, rb);
360 }
361 _ => panic!("Expected Add nodes for both + and -"),
362 }
363 }
364
365 #[test]
366 fn assert_boolean_structure() {
367 let cs = ConstraintSystem::<F>::new();
368 let s = cs.col(5);
369
370 cs.assert_boolean(s);
371
372 let ast = cs.build();
373 assert_eq!(ast.roots.len(), 1);
374
375 match ast.arena.get(ast.roots[0]) {
377 ConstraintExpr::Add(lhs, rhs) => {
378 match ast.arena.get(*lhs) {
380 ConstraintExpr::Mul(a, b) => {
381 assert_eq!(a, b); }
383 other => panic!("Expected Mul for s², got {:?}", other),
384 }
385
386 match ast.arena.get(*rhs) {
388 ConstraintExpr::Cell(cell) => {
389 assert_eq!(cell.col_idx, 5);
390 assert!(!cell.next_row);
391 }
392 other => panic!("Expected Cell for s, got {:?}", other),
393 }
394 }
395 other => panic!("Expected Add for s²+s, got {:?}", other),
396 }
397 }
398
399 #[test]
400 fn assert_zero_when_structure() {
401 let cs = ConstraintSystem::<F>::new();
402 let sel = cs.col(0);
403 let body = cs.col(1) + cs.col(2);
404
405 cs.assert_zero_when(sel, body);
406
407 let ast = cs.build();
408 assert_eq!(ast.roots.len(), 1);
409
410 match ast.arena.get(ast.roots[0]) {
412 ConstraintExpr::Mul(_, _) => {}
413 other => panic!("Expected Mul, got {:?}", other),
414 }
415 }
416
417 #[test]
418 fn scale_produces_scale_node() {
419 let cs = ConstraintSystem::<F>::new();
420 let a = cs.col(0);
421 let scaled = cs.scale(F::from(8u128), a);
422
423 let a_id = a.id;
425 let scaled_id = scaled.id;
426
427 let ast = cs.build();
428 match ast.arena.get(scaled_id) {
429 ConstraintExpr::Scale(coeff, inner) => {
430 assert_eq!(*coeff, F::from(8u128));
431 assert_eq!(*inner, a_id);
432 }
433 other => panic!("Expected Scale, got {:?}", other),
434 }
435 }
436
437 #[test]
438 fn sum_produces_sum_node() {
439 let cs = ConstraintSystem::<F>::new();
440 let a = cs.col(0);
441 let b = cs.col(1);
442 let c = cs.col(2);
443 let s = cs.sum(&[a, b, c]);
444
445 let (a_id, b_id, c_id) = (a.id, b.id, c.id);
447
448 let s_id = s.id;
449 let ast = cs.build();
450
451 match ast.arena.get(s_id) {
452 ConstraintExpr::Sum(children) => {
453 assert_eq!(children.len(), 3);
454 assert_eq!(children[0], a_id);
455 assert_eq!(children[1], b_id);
456 assert_eq!(children[2], c_id);
457 }
458 other => panic!("Expected Sum, got {:?}", other),
459 }
460 }
461
462 #[test]
463 fn dag_sharing_via_expr_reuse() {
464 let cs = ConstraintSystem::<F>::new();
465
466 let a = cs.col(0);
467 let b = cs.col(1);
468 let c = cs.col(2);
469
470 let theta = cs.sum(&[a, b, c]);
472
473 let d = cs.col(3);
475 cs.constrain(theta * d);
476 cs.constrain(theta * a);
477
478 let ast = cs.build();
479 assert_eq!(ast.roots.len(), 2);
480
481 match (ast.arena.get(ast.roots[0]), ast.arena.get(ast.roots[1])) {
483 (ConstraintExpr::Mul(lhs0, _), ConstraintExpr::Mul(lhs1, _)) => {
484 assert_eq!(lhs0, lhs1); }
486 _ => panic!("Expected Mul roots"),
487 }
488 }
489
490 #[test]
491 fn empty_system_produces_empty_ast() {
492 let cs = ConstraintSystem::<F>::new();
493 let ast = cs.build();
494 assert!(ast.roots.is_empty());
495 assert!(ast.arena.is_empty());
496 }
497
498 #[test]
499 fn builder_matches_manual_structure() {
500 let cs = ConstraintSystem::<F>::new();
502 let _a = cs.col(0);
503 let b = cs.col(1);
504 let q = cs.col(2);
505 let na = cs.next(0);
506
507 cs.constrain(q * (na + b));
508
509 let ast = cs.build();
510
511 assert_eq!(ast.roots.len(), 1);
513
514 match ast.arena.get(ast.roots[0]) {
515 ConstraintExpr::Mul(lhs, rhs) => {
516 match ast.arena.get(*lhs) {
517 ConstraintExpr::Cell(cell) => {
518 assert_eq!(cell.col_idx, 2);
519 assert!(!cell.next_row);
520 }
521 other => panic!("Expected Cell for q, got {:?}", other),
522 }
523 match ast.arena.get(*rhs) {
524 ConstraintExpr::Add(a, b) => {
525 match ast.arena.get(*a) {
526 ConstraintExpr::Cell(cell) => {
527 assert_eq!(cell.col_idx, 0);
528 assert!(cell.next_row);
529 }
530 other => panic!("Expected Cell for next_a, got {:?}", other),
531 }
532 match ast.arena.get(*b) {
533 ConstraintExpr::Cell(cell) => {
534 assert_eq!(cell.col_idx, 1);
535 assert!(!cell.next_row);
536 }
537 other => panic!("Expected Cell for b, got {:?}", other),
538 }
539 }
540 other => panic!("Expected Add, got {:?}", other),
541 }
542 }
543 other => panic!("Expected Mul root, got {:?}", other),
544 }
545 }
546
547 #[test]
548 fn labels_round_trip_through_build() {
549 let cs = ConstraintSystem::<F>::new();
550 let a = cs.col(0);
551 let b = cs.col(1);
552
553 cs.constrain(a + b);
554 cs.constrain_named("transition", a * b);
555 cs.assert_boolean(a);
556
557 let ast = cs.build();
558
559 assert_eq!(ast.roots.len(), 3);
560 assert_eq!(ast.labels.len(), 3);
561 assert_eq!(ast.labels[0], None);
562 assert_eq!(ast.labels[1], Some("transition"));
563 assert_eq!(ast.labels[2], Some("boolean"));
564 }
565
566 #[test]
567 fn labels_preserved_through_merge() {
568 let cs1 = ConstraintSystem::<F>::new();
569 cs1.constrain_named("first", cs1.col(0));
570
571 let mut ast1 = cs1.build();
572
573 let cs2 = ConstraintSystem::<F>::new();
574
575 cs2.constrain(cs2.col(0));
576 cs2.constrain_named("second", cs2.col(1));
577
578 let ast2 = cs2.build();
579
580 ast1.merge(ast2);
581
582 assert_eq!(ast1.roots.len(), 3);
583 assert_eq!(ast1.labels.len(), 3);
584 assert_eq!(ast1.labels[0], Some("first"));
585 assert_eq!(ast1.labels[1], None);
586 assert_eq!(ast1.labels[2], Some("second"));
587 }
588
589 #[test]
590 fn labels_preserved_through_from_ast() {
591 let cs = ConstraintSystem::<F>::new();
592 cs.constrain_named("original", cs.col(0));
593
594 let ast = cs.build();
595
596 let cs2 = ConstraintSystem::from_ast(ast);
597 cs2.constrain_named("added", cs2.col(1));
598
599 let ast2 = cs2.build();
600
601 assert_eq!(ast2.labels.len(), 2);
602 assert_eq!(ast2.labels[0], Some("original"));
603 assert_eq!(ast2.labels[1], Some("added"));
604 }
605
606 #[test]
607 fn builtin_gadgets_have_labels() {
608 let cs = ConstraintSystem::<F>::new();
609
610 let a = cs.col(0);
611 let b = cs.col(1);
612
613 cs.assert_boolean(a);
614 cs.assert_zero_when(a, b);
615 cs.assert_one_hot(&[a, b]);
616
617 let ast = cs.build();
618
619 assert_eq!(ast.labels.len(), 3);
620 assert_eq!(ast.labels[0], Some("boolean"));
621 assert_eq!(ast.labels[1], Some("zero_when"));
622 assert_eq!(ast.labels[2], Some("one_hot"));
623 }
624}