@@ -260,10 +260,10 @@ impl<'a, T: FieldElement> Machine<'a, T> for FixedLookup<'a, T> {
260
260
& mut self ,
261
261
identity_id : u64 ,
262
262
known_arguments : & BitVec ,
263
- _range_constraints : & [ RangeConstraint < T > ] ,
264
- ) -> bool {
263
+ range_constraints : & [ RangeConstraint < T > ] ,
264
+ ) -> Option < Vec < RangeConstraint < T > > > {
265
265
if !Self :: is_responsible ( & self . connections [ & identity_id] ) {
266
- return false ;
266
+ return None ;
267
267
}
268
268
let index = self
269
269
. indices
@@ -274,8 +274,58 @@ impl<'a, T: FieldElement> Machine<'a, T> for FixedLookup<'a, T> {
274
274
. or_insert_with_key ( |application| {
275
275
create_index ( self . fixed_data , application, & self . connections )
276
276
} ) ;
277
- // Check that if there is a match, it is unique.
278
- index. values ( ) . all ( |value| value. 0 . is_some ( ) )
277
+ let input_range_constraints = known_arguments
278
+ . iter ( )
279
+ . zip_eq ( range_constraints)
280
+ . filter_map ( |( is_known, range_constraint) | is_known. then_some ( range_constraint. clone ( ) ) )
281
+ . collect_vec ( ) ;
282
+
283
+ // Now only consider the index entries that match the input range constraints,
284
+ // see that the result is unique and determine new output range constraints.
285
+ let mut values = index
286
+ . iter ( )
287
+ . filter ( |( key, _) | matches_range_constraint ( key, & input_range_constraints) )
288
+ . map ( |( _, value) | {
289
+ let ( _, values) = value. get ( ) ?;
290
+ Some ( values)
291
+ } ) ;
292
+ let Some ( first) = values. next ( ) else {
293
+ // If the iterator is empty, we have no match, but it can always be answered,
294
+ // so this is successful.
295
+ // Unfortunately, we cannot express the "empty" range constraint.
296
+ return Some ( vec ! [ Default :: default ( ) ; range_constraints. len( ) ] ) ;
297
+ } ;
298
+ // If an item (including the first) is None, it means the match was not unique.
299
+ let mut new_output_range_constraints = first?
300
+ . iter ( )
301
+ // min, max, mask
302
+ . map ( |v| ( * v, * v, v. to_integer ( ) ) )
303
+ . collect_vec ( ) ;
304
+ for v in values {
305
+ // If any value is None, the match was not unique.
306
+ let v = v?;
307
+ for ( ( min, max, mask) , v) in new_output_range_constraints. iter_mut ( ) . zip_eq ( v) {
308
+ * min = ( * min) . min ( * v) ;
309
+ * max = ( * max) . max ( * v) ;
310
+ * mask |= v. to_integer ( ) ;
311
+ }
312
+ }
313
+ let mut new_output_range_constraints = new_output_range_constraints. into_iter ( ) ;
314
+ Some (
315
+ known_arguments
316
+ . iter ( )
317
+ . map ( |is_known| {
318
+ if is_known {
319
+ // We could also copy the input range constraint.
320
+ RangeConstraint :: default ( )
321
+ } else {
322
+ let ( min, max, mask) = new_output_range_constraints. next ( ) . unwrap ( ) ;
323
+ RangeConstraint :: from_range ( min, max)
324
+ . conjunction ( & RangeConstraint :: from_mask ( mask) )
325
+ }
326
+ } )
327
+ . collect ( ) ,
328
+ )
279
329
}
280
330
281
331
fn process_plookup < Q : crate :: witgen:: QueryCallback < T > > (
@@ -398,3 +448,14 @@ impl<'a, T: FieldElement> RangeConstraintSet<AlgebraicVariable<'a>, T>
398
448
}
399
449
}
400
450
}
451
+
452
+ /// Checks that an array of values satisfies a set of range constraints.
453
+ fn matches_range_constraint < T : FieldElement > (
454
+ values : & [ T ] ,
455
+ range_constraints : & [ RangeConstraint < T > ] ,
456
+ ) -> bool {
457
+ values
458
+ . iter ( )
459
+ . zip_eq ( range_constraints)
460
+ . all ( |( value, range_constraint) | range_constraint. allows_value ( * value) )
461
+ }
0 commit comments