@@ -88,6 +88,21 @@ fn finalize_constants_with_min_base(context: &mut Context<impl IValue>, min_base
8888 // Decompose M31 constants not in the chain by expressing them in base `m31_base`.
8989 decompose_m31_constants ( context, & mut m31_constants, & mut m31_cache, m31_base) ;
9090 assert ! ( m31_constants. is_empty( ) ) ;
91+
92+ // Deal with the QM31 constants.
93+ // Build `i` and `i * u` to get the qm31_basis [i, u, iu].
94+ let two = Var { idx : * m31_cache. get ( & 2 . into ( ) ) . unwrap ( ) } ;
95+ let i_var = eval ! ( context, ( ( u_var) * ( u_var) ) - ( two) ) ;
96+ let iu_var = eval ! ( context, ( i_var) * ( u_var) ) ;
97+ let qm31_basis: [ Var ; 3 ] = [ i_var, u_var, iu_var] ;
98+ // Build the broadcast QM31 constants, i.e. constants of the form (x, x, x, x), x != 0.
99+ decompose_broadcast_constants (
100+ context,
101+ & mut qm31_constants,
102+ & mut m31_cache,
103+ m31_base,
104+ qm31_basis,
105+ ) ;
91106}
92107
93108/// Finds the largest integer N such that all values in [0, N] are present as constants.
@@ -208,3 +223,32 @@ fn build_m31_from_base(
208223 assert ! ( m31_cache. contains_key( & val) ) ;
209224 assert ! ( !m31_constants. contains_key( & val) ) ;
210225}
226+
227+ fn decompose_broadcast_constants (
228+ context : & mut Context < impl IValue > ,
229+ qm31_constants : & mut IndexMap < QM31 , Var > ,
230+ m31_cache : & mut HashMap < M31 , usize > ,
231+ base : M31 ,
232+ qm31_basis : [ Var ; 3 ] ,
233+ ) {
234+ let [ i_var, u_var, iu_var] = qm31_basis;
235+ let one = context. one ( ) ;
236+ let ones = eval ! ( context, ( ( one) + ( i_var) ) + ( ( u_var) + ( iu_var) ) ) ;
237+
238+ qm31_constants. retain ( |qm31_value, qm31_var| {
239+ let is_broadcast = qm31_value. to_m31_array ( ) . iter ( ) . tuple_windows ( ) . all ( |( x, y) | x == y) ;
240+ if !is_broadcast {
241+ return true ;
242+ }
243+ let m31_value = qm31_value. 0 . 0 ;
244+ // If m31_value is not in the cache, add it.
245+ if !m31_cache. contains_key ( & m31_value) {
246+ build_m31_from_base ( context, m31_cache, & mut IndexMap :: new ( ) , base, m31_value) ;
247+ }
248+ let m31_idx = * m31_cache. get ( & m31_value) . unwrap ( ) ;
249+ // Add a gate m31_val * (1, 1, 1, 1) = qm31_var.
250+ context. circuit . mul . push ( Mul { in0 : m31_idx, in1 : ones. idx , out : qm31_var. idx } ) ;
251+ // Remove the element from qm31_constants.
252+ false
253+ } ) ;
254+ }
0 commit comments