binius_core/constraint_system/layout.rs
1// Copyright 2025 Irreducible Inc.
2// Copyright 2026 The Binius Developers
3use super::{ConstraintSystem, ValueIndex, ValueSegment};
4use crate::word::Word;
5
6/// Description of a layout of the value vector for a particular circuit.
7///
8/// This is the compiler's view of the value vector: it names every section the circuit allocates,
9/// including the ones a [`ConstraintSystem`] has no interest in — the split of the private segment
10/// into declared witness and gate-created internal values, and the scratch tail used only while
11/// evaluating the circuit.
12///
13/// The sections are stored back to back, with no padding between them:
14///
15/// ```text
16/// [ constants | inout ][ witness | internal ][ scratch ]
17/// \-- public values -/ \--- private values -/
18/// ```
19///
20/// The proving protocol pads the two committed segments to the widths its reductions need, which
21/// [`ConstraintSystem`] derives; none of that padding is stored here.
22#[derive(Clone, Debug, PartialEq, Eq)]
23pub struct ValueVecLayout {
24 /// The number of the constants declared by the circuit.
25 pub n_const: usize,
26 /// The number of the input output parameters declared by the circuit.
27 pub n_inout: usize,
28 /// The number of the witness parameters declared by the circuit.
29 pub n_witness: usize,
30 /// The number of the internal values declared by the circuit.
31 ///
32 /// Those are outputs and intermediaries created by the gates.
33 pub n_internal: usize,
34
35 /// The number of scratch values at the end of the value vec.
36 pub n_scratch: usize,
37}
38
39impl ValueVecLayout {
40 /// Returns the word at which the inout values start.
41 pub const fn offset_inout(&self) -> usize {
42 self.n_const
43 }
44
45 /// Returns the word at which the private values start, which is where the public ones end.
46 pub const fn offset_witness(&self) -> usize {
47 self.n_const + self.n_inout
48 }
49
50 /// Returns the number of private values: the declared witness values followed by the
51 /// gate-created internal ones, which share the segment.
52 pub const fn n_private(&self) -> usize {
53 self.n_witness + self.n_internal
54 }
55
56 /// Returns the combined number of public and private values, excluding scratch.
57 ///
58 /// This is the length of the value vector prefix that constraint operands can reference.
59 pub const fn combined_len(&self) -> usize {
60 self.offset_witness() + self.n_private()
61 }
62
63 /// Returns the flat position of the word a [`ValueIndex`] names, counting the scratch tail.
64 pub const fn word_offset(&self, index: ValueIndex) -> usize {
65 let segment_start = match index.segment() {
66 ValueSegment::Constant => 0,
67 ValueSegment::InOut => self.offset_inout(),
68 ValueSegment::Private => self.offset_witness(),
69 ValueSegment::Scratch => self.combined_len(),
70 };
71 segment_start + index.index() as usize
72 }
73
74 /// Returns the constraint system shape this layout realizes.
75 ///
76 /// The returned system has no constraints; the caller fills them in.
77 ///
78 /// # Panics
79 ///
80 /// Panics if the constant count does not match the layout's.
81 pub fn constraint_system_shape(&self, constants: Vec<Word>) -> ConstraintSystem {
82 assert!(constants.len() == self.n_const, "constants must match the layout's n_const");
83 ConstraintSystem {
84 constants,
85 n_inout: self.n_inout,
86 n_private: self.n_private(),
87 zero_constraints: Vec::new(),
88 and_constraints: Vec::new(),
89 imul_constraints: Vec::new(),
90 bmul_constraints: Vec::new(),
91 }
92 }
93}
94
95#[cfg(test)]
96mod tests {
97 use super::{super::InoutSegment, *};
98
99 /// A layout of two constants, two inout values and eight private values.
100 fn test_layout() -> ValueVecLayout {
101 ValueVecLayout {
102 n_const: 2, // constants at words 0-1
103 n_inout: 2, // inout at words 2-3
104 n_witness: 4, // witness at words 4-7
105 n_internal: 4, // internal at words 8-11
106 n_scratch: 3, // scratch at words 12-14
107 }
108 }
109
110 #[test]
111 fn sections_are_stored_back_to_back() {
112 let layout = test_layout();
113 assert_eq!(layout.offset_inout(), 2);
114 assert_eq!(layout.offset_witness(), 4);
115 assert_eq!(layout.n_private(), 8);
116 assert_eq!(layout.combined_len(), 12);
117
118 // Every section is reached by resolving an index of its own segment, with no gap between
119 // them. The internal values continue the private segment past the witness ones.
120 let offsets = [
121 ValueIndex::constant(0),
122 ValueIndex::inout(0),
123 ValueIndex::private(0),
124 ValueIndex::private(4),
125 ValueIndex::scratch(0),
126 ]
127 .map(|index| layout.word_offset(index));
128 assert_eq!(offsets, [0, 2, 4, 8, 12]);
129 }
130
131 #[test]
132 fn constraint_system_shape_carries_the_value_counts() {
133 let layout = test_layout();
134 let cs = layout.constraint_system_shape(vec![Word::ONE, Word::ALL_ONE]);
135
136 assert_eq!(cs.n_const(), 2);
137 assert_eq!(cs.n_inout, 2);
138 // The witness and internal values share the private segment.
139 assert_eq!(cs.n_private, 8);
140 assert_eq!(cs.n_public_values(), layout.offset_witness());
141
142 // The system pads the segments the protocol commits; the layout stores neither padding.
143 assert_eq!(cs.n_public_words(InoutSegment::Public), 4);
144 assert_eq!(cs.n_hidden_words(InoutSegment::Public), 8);
145 }
146}