"""Lookup2_12 AIR witness generation.
Lookup2_12 logup terms (from gsum_debug_data hints):
0. Lookup assumes, busid=4, sel=1, cols=[a1, b1] -- direct term in gsum
1. Lookup proves, busid=4, mul=1, cols=[c1, d1] -- stored_num = +1
2. Lookup assumes, busid=5, sel=1, cols=[a2, b2] -- stored_num = -1
3. Lookup assumes, busid=6, sel=sel1, cols=[a3, b3] -- stored_num = -sel1
4. Lookup proves, busid=6, mul=mul, cols=[c2, d2] -- stored_num = +mul
5. Lookup assumes, busid=7, sel=sel2, cols=[a4, b4] -- stored_num = -sel2
Intermediate columns clustering (from expressionsinfo constraint lines):
- im_cluster[0]: busid=4 proves [c1,d1] + busid=5 assumes [a2,b2] = terms 1,2
- im_cluster[1]: busid=6 assumes [a3,b3] + busid=6 proves [c2,d2] = terms 3,4
- im_single: busid=7 assumes [a4,b4] = term 5
Convention: stored_num = -selector for assumes, +multiplicity for proves.
Term 0 (busid=4 assumes) is used directly in gsum, not in intermediate columns.
"""
import numpy as np
from constraints.base import ConstraintContext
from primitives.field import FF3, GOLDILOCKS_PRIME, FF3Poly, batch_inverse
from .base import WitnessModule
[docs]
class Lookup2_12Witness(WitnessModule):
"""Witness generation for Lookup2_12 AIR.
Computes 2 im_cluster columns, 1 im_single column, and 1 gsum column.
"""
def _get_all_logup_terms(
self, ctx: ConstraintContext
) -> list[tuple[int, list[FF3Poly], int | FF3Poly]]:
"""Return all logup terms as (busid, cols, selector) tuples."""
# Get witness columns
a1 = ctx.col('a1')
b1 = ctx.col('b1')
a2 = ctx.col('a2')
b2 = ctx.col('b2')
a3 = ctx.col('a3')
b3 = ctx.col('b3')
a4 = ctx.col('a4')
b4 = ctx.col('b4')
c1 = ctx.col('c1')
d1 = ctx.col('d1')
c2 = ctx.col('c2')
d2 = ctx.col('d2')
sel1 = ctx.col('sel1')
sel2 = ctx.col('sel2')
mul = ctx.col('mul')
# Define all 6 logup terms with stored_num convention:
# stored_num = -selector for assumes, +multiplicity for proves
terms = [
(4, [a1, b1], 1), # term 0: assumes busid=4, used direct (selector=+1)
(4, [c1, d1], 1), # term 1: proves busid=4, stored_num = +1
(5, [a2, b2], -1), # term 2: assumes busid=5, stored_num = -1
(6, [a3, b3], -sel1), # term 3: assumes busid=6, stored_num = -sel1
(6, [c2, d2], mul), # term 4: proves busid=6, stored_num = +mul
(7, [a4, b4], -sel2), # term 5: assumes busid=7, stored_num = -sel2
]
return terms
[docs]
def compute_grand_sums(self, ctx: ConstraintContext) -> dict[str, FF3Poly]:
"""Compute gsum running sum polynomial.
From constraint 3:
(gsum - prev_gsum*(1-L1) - sum_ims) * direct_den + 1 = 0
This means:
gsum[i] = prev_gsum[i] * (1-L1[i]) + sum_ims[i] - 1/direct_den[i]
Where direct_den = compress(4, [a1, b1]).
Returns:
{'gsum': gsum_polynomial}
"""
alpha = ctx.challenge('std_alpha')
gamma = ctx.challenge('std_gamma')
# Get columns for direct_den
a1 = ctx.col('a1')
b1 = ctx.col('b1')
intermediates = self.compute_intermediates(ctx)
im_clusters = intermediates['im_cluster']
im_single = intermediates['im_single']
n = len(im_clusters[0])
def const(value: int) -> FF3:
return FF3(np.full(n, value % GOLDILOCKS_PRIME, dtype=np.uint64))
# Compute direct_den = compress(4, [a1, b1])
direct_den = (b1 * alpha + a1) * alpha + const(4) + gamma
# term0 contribution = -1 / direct_den
term0 = const(-1) * batch_inverse(direct_den)
# Sum all contributions: im_clusters + im_single + term0
sum_ims = im_clusters[0] + im_clusters[1] + im_single[0]
row_sum = sum_ims + term0
# Compute cumulative sum
gsum = self._compute_cumulative_sum(row_sum)
return {'gsum': gsum}