Skip to main content

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}