@@ -7,6 +7,7 @@ use stwo::core::fields::qm31::QM31;
77
88use crate :: circuit:: { Add , Mul } ;
99use crate :: context:: { Context , Var } ;
10+ use crate :: eval;
1011use crate :: ivalue:: { IValue , qm31_from_u32s} ;
1112use crate :: ops:: { add, output} ;
1213
@@ -83,6 +84,23 @@ fn finalize_constants_with_min_base(context: &mut Context<impl IValue>, min_base
8384 // Decompose M31 constants not in the chain by expressing them in base `m31_base`.
8485 decompose_m31_constants ( context, & mut m31_constants, & mut m31_cache, m31_base) ;
8586 assert ! ( m31_constants. is_empty( ) ) ;
87+
88+ // Deal with the QM31 constants.
89+ // Build `i` and `i * u` to get the qm31_basis [i, u, iu].
90+ // We know `2` is already produced because `min_base >= 2`.
91+ let two = Var { idx : * m31_cache. get ( & 2 . into ( ) ) . unwrap ( ) } ;
92+ // `u * u = 2 + i`.
93+ let i_var = eval ! ( context, ( ( u_var) * ( u_var) ) - ( two) ) ;
94+ let iu_var = eval ! ( context, ( i_var) * ( u_var) ) ;
95+ let qm31_basis: [ Var ; 3 ] = [ i_var, u_var, iu_var] ;
96+ // Build the broadcast QM31 constants, i.e. constants of the form (x, x, x, x), x != 0.
97+ decompose_broadcast_constants (
98+ context,
99+ & mut qm31_constants,
100+ & mut m31_cache,
101+ m31_base,
102+ qm31_basis,
103+ ) ;
86104}
87105
88106/// Finds the largest integer N such that all values in [0, N] are present as constants.
@@ -210,3 +228,50 @@ fn build_m31_from_base(
210228 assert ! ( m31_cache. contains_key( & val) ) ;
211229 assert ! ( !m31_constants. contains_key( & val) ) ;
212230}
231+
232+ /// Yields and constrains every "broadcast" QM31 constant in `qm31_constants` (i.e. constants of
233+ /// the form `(x, x, x, x)` with `x != 0`) by expressing them as `x * (1, 1, 1, 1)`.
234+ ///
235+ /// For each broadcast constant, the M31 scalar `x` is retrieved from `m31_cache` (and built via
236+ /// `build_m31_from_base` if missing), and a single Mul gate `x * (1,1,1,1) = (x,x,x,x)` yields and
237+ /// constrains the constant's wire.
238+ ///
239+ /// Non-broadcast entries of `qm31_constants` are left untouched; broadcast entries are removed
240+ /// once yielded.
241+ fn decompose_broadcast_constants (
242+ context : & mut Context < impl IValue > ,
243+ qm31_constants : & mut IndexMap < QM31 , Var > ,
244+ m31_cache : & mut HashMap < M31 , usize > ,
245+ base : M31 ,
246+ qm31_basis : [ Var ; 3 ] ,
247+ ) {
248+ let one = context. one ( ) ;
249+ let [ i_var, u_var, iu_var] = qm31_basis;
250+ // Build and constrain the wire corresponding to (1, 1, 1, 1).
251+ let ones_value = qm31_from_u32s ( 1 , 1 , 1 , 1 ) ;
252+ let ones_var = if let Some ( var) = qm31_constants. swap_remove ( & ones_value) {
253+ var
254+ } else {
255+ context. new_var ( IValue :: from_qm31 ( ones_value) )
256+ } ;
257+ let one_plus_i = add ( context, one, i_var) ;
258+ let u_plus_iu = add ( context, u_var, iu_var) ;
259+ context. circuit . add . push ( Add { in0 : one_plus_i. idx , in1 : u_plus_iu. idx , out : ones_var. idx } ) ;
260+
261+ qm31_constants. retain ( |qm31_value, qm31_var| {
262+ let is_broadcast = qm31_value. to_m31_array ( ) . iter ( ) . tuple_windows ( ) . all ( |( x, y) | x == y) ;
263+ if !is_broadcast {
264+ return true ;
265+ }
266+ let m31_value = qm31_value. 0 . 0 ;
267+ // If m31_value is not in the cache, add it.
268+ if !m31_cache. contains_key ( & m31_value) {
269+ build_m31_from_base ( context, m31_cache, & mut IndexMap :: new ( ) , base, m31_value) ;
270+ }
271+ let m31_idx = * m31_cache. get ( & m31_value) . unwrap ( ) ;
272+ // Add a gate m31_val * (1, 1, 1, 1) = qm31_var.
273+ context. circuit . mul . push ( Mul { in0 : m31_idx, in1 : ones_var. idx , out : qm31_var. idx } ) ;
274+ // Remove the element from qm31_constants.
275+ false
276+ } ) ;
277+ }
0 commit comments