Coulomb potential

The inverse distance between two particles, the core of real-space chemistry simulation. The discovered circuit trades multiplications for a larger table, and then makes the table cheap to erase.

In brief

Both circuits approximate one curve, u−1/2 for u between 1 and 2. The baseline (Häner et al., 2018) evaluates quadratics on 16 intervals.

The discovered circuit builds the squared distance in one register, with no separate squares. It uses 128 straight lines instead: one short multiplication per line instead of two long ones. A larger table normally costs twice, once to look up and once to erase. Here measurement erases the table words, and a phase repair costs 17 Toffolis instead of 126.

TQ = 2·10⁵TQ = 5·10⁵TQ = 10⁶1502002501,0002,0005,000peak qubits Q →Toffoli count T →Discovered circuitHäner et al. (2018)Live qubits as Toffolis execute6.4k1.1k252142
Lines need fewer multiplications, and fewer words stay live at once.
1

Fit the line table at build time

build_tables

The file stores no coefficients. Before it emits any gate, the script recomputes the table that the lookup loads later. Each entry is one line: a slope s with two fractional bits and an 18-bit intercept g whose leading one is implicit.

The assertion records that every leaf sits at depth seven. The table therefore holds 128 lines, one per lookup address. Inside build_tables, the script also checks the fit exhaustively over every attainable squared distance from 4 upward.

_tabs = build_tables() # ~1.5 s, certified exhaustively inside
assert _tabs is not None, "table infeasible"
SLf, G0f, LEAF, SW, WP = _tabs
SMAX = max(max(SLf), 1)
# leaf_f indexes the leaves by the top 7 mantissa bits: the tree is full depth
assert all(depth == 7 for (depth, _p) in LEAF)
print(f"tables ok: leaves={len(LEAF)} SW={SW} WP={WP}")
2

Pull the output band backward

_acceptance_windows, _fit_line, _leaf_tree

The output may differ from round(224/√n2) by at most 32 units. _acceptance_windows pulls that band back through delta_of, the exact integer model of the final shift and rounding. Inverting the tail gives each n2 an interval of admissible line values. Inputs that share an address and a remainder must produce the same value, so their intervals intersect.

_fit_line searches slopes outward from an analytic seed, the local derivative of the curve. A slope fits when one intercept lies inside the windows of all 64 remainders at once; the loop stops early when the intersection empties. The midpoint of the surviving interval becomes the intercept.

grow tries one line per address block and splits a failing block in half. The remainder bits are disjoint from the address bits, so no line can serve two address slots, and every split reaches depth seven. Truncation, line arithmetic, rescaling, and rounding thus share one error budget.

def _acceptance_windows():
"""Per (QROM address, remainder) cell, the admissible gs interval [LO, HI].
Returns None when some n2 has an empty window.
"""
LO = [[0] * NR for _ in range(NF)]
HI = [[GMAX] * NR for _ in range(NF)]
for n2 in range(4, N2_MAX + 1):
e = n2.bit_length() - 1
h = e >> 1
t = REF(n2)
d = h - Q
# h >= 7: delta_of's round-to-nearest can add one ulp, so the top
# of the band tightens from +EPS to +(EPS-1); below that /4 is exact.
hi_eps = EPS - 1 if h >= 7 else EPS
# invert the tail: gs range whose DELTA stays in [t-EPS, t+hi_eps]
if d >= 0:
lo = (t - EPS) << d
hi = ((t + hi_eps + 1) << d) - 1
else:
lo = (t - EPS + (1 << -d) - 1) >> (-d)
hi = (t + hi_eps) >> (-d)
lo = max(lo, 0)
hi = min(hi, GMAX)
if lo > hi:
return None
f = FIDX[n2]
rem = (MSH[n2] >> RL) & (NR - 1)
LO[f][rem] = max(LO[f][rem], lo)
HI[f][rem] = min(HI[f][rem], hi)
return LO, HI
⋯ 2 lines
def _fit_line(LO, HI, f0, f1):
"""One (slope, intercept) line that serves addresses f0..f1-1, or None."""
LOn = [0] * NR
HIn = [GMAX] * NR
for f in range(f0, f1):
Lf = LO[f]
Hf = HI[f]
for r in range(NR):
LOn[r] = max(LOn[r], Lf[r])
HIn[r] = min(HIn[r], Hf[r])
# analytic seed: over this block gs ~= kap/sqrt(x) with x = m/2^17,
# so s0 = the expected per-remainder-step drop, in slope units of
# 2^-SH; then search slopes outward from the seed.
p = f0 >> A
a = f0 & 63
kap = (1 << 24) * (2**-0.5 if p else 1.0) / (1 << Q)
x0 = 1.0 + a / 64.0
span = NR * (1 << RL) / 131072.0
s0 = round((kap / math.sqrt(x0) - kap / math.sqrt(x0 + span)) / NR * (1 << SH))
for ds in range(700):
for s in (s0,) if ds == 0 else (s0 + ds, s0 - ds):
if s < 0:
continue
tb = make_psub(s)
# slope s is feasible iff one g satisfies
# LOn[r]+PS(s,r) <= g <= HIn[r]+PS(s,r) for ALL remainders;
# intersect and take the midpoint for symmetric margin.
glo = 0
ghi = GMAX
ok = True
for r in range(NR):
sub = tb[r]
l = LOn[r] + sub
glo = max(glo, l)
h2 = HIn[r] + sub
ghi = min(ghi, h2)
if glo > ghi:
ok = False
break
if ok:
return s, (glo + ghi) // 2
return None
⋯ 2 lines
def _leaf_tree(LO, HI):
"""Build the adaptive leaf tree {(depth, prefix): (slope, intercept)}, or None.
Each address block gets one leaf when feasible, else splits in half,
so the (depth, prefix) keys are exactly the QROM's early-terminating tree.
"""
LEAF = {}
def grow(depth, prefix):
rr = _fit_line(LO, HI, prefix << (7 - depth), (prefix + 1) << (7 - depth))
if rr is not None:
LEAF[(depth, prefix)] = rr
return True
if depth == 7:
return False
okl = grow(depth + 1, 2 * prefix)
okr = grow(depth + 1, 2 * prefix + 1)
return okl and okr
# a depth-0 leaf never occurs in practice and qrom() has no
# unconditional write path for it — reject rather than special-case
if not grow(0, 0) or (0, 0) in LEAF:
return None
return LEAF
3

Registers and coordinate magnitudes

init, diffs

For distinct points ri, rj on a 2563 grid, the circuit adds an approximation of round(224/‖ri − rj‖) to a 25-bit accumulator. It declares seven registers in the grader's order: three 8-bit coordinates for each point, then k. These 73 qubits carry input and output; every other qubit starts and ends in |0⟩.

Each coordinate of ri becomes |ri,c − rj,c|. A complement subtraction writes the sign onto one of three sign qubits. A conditional complement and an increment then form the magnitude. The sign qubits keep enough information to restore ri during the reverse pass. radd is a ripple adder with temporary AND carries.

_M0 = b.checkpoint() # forward-region start (everything until _MFWD mirrors)
Differences 45 Toffolis · peak 84 qubits
with b.phase("diffs/setup"):
SGN = [b.qubit() for _ in range(3)]
b.claim("sign", SGN)
for ax in range(3):
with b.phase(f"diffs/ax{ax}"):
riq = RI[ax]
rjq = RJ[ax]
sg = SGN[ax]
for q in riq:
b.x(q)
radd(riq, rjq, cout=sg)
for q in riq:
b.x(q)
for q in riq:
b.cx(sg, q)
radd(riq, [sg])
4

The squared distance in one word

squarer

For an 8-bit magnitude d, d2 = Σi di4i + Σi<j didj2i+j+1, and every term has a known column. The terms form four groups with at most one term per column. Each group is then a sparse integer that one ripple addition adds into the 18-bit word n2b. No register holds a single square, and no separate summation runs.

Three coordinates give twelve groups. The first group seeds the empty word without an adder. The other eleven follow SQ_SCHED, an order that a dynamic program chose. Each addition stops at the highest bit the partial sum can reach. This order uses 135 adder ANDs; the axis-at-a-time order uses 141 under the same rule.

Squared distance 222 Toffolis · peak 123 qubits
with b.phase("squarer/a0g1"):
b.claim("n2", n2b) # all 18 wires exist once a0g1 has seeded the word
for _ax, _g, _cap in SQ_SCHED:
with b.phase(f"squarer/a{_ax}g{_g + 1}"):
emit_group(RI[_ax], SPECS[_g][0], SPECS[_g][1], _cap)
5

One column group

sq_specs, g1_axis0, emit_group

sq_specs maps each column of a group to one term: a bare bit q or a pair product a. Column 8 holds two terms, d0d7 and d42 = d4. A half-adder pair resolves the collision: h8 keeps their XOR in column 8, and h9 carries their AND into column 9. The script checks over all 256 magnitudes that the four groups sum to d2.

emit_group forms the pair products with temporary ANDs, adds the sparse word with one ripple addition capped at column cap, and releases the products by measurement. g1_axis0 writes the first group onto fresh wires: bits by CX and products by AND, with no addition.

def sq_specs():
def Ap(i, j):
return ("a", i, j)
G1 = (
0,
{
0: ("q", 0),
2: Ap(0, 1),
3: Ap(0, 2),
4: Ap(0, 3),
5: Ap(0, 4),
6: Ap(0, 5),
7: Ap(0, 6),
8: ("h8",),
9: ("h9",),
10: Ap(4, 5),
11: Ap(4, 6),
12: Ap(4, 7),
14: Ap(6, 7),
},
)
G2 = (
2,
{
2: ("q", 1),
4: Ap(1, 2),
5: Ap(1, 3),
6: Ap(1, 4),
7: Ap(1, 5),
8: Ap(1, 6),
9: Ap(1, 7),
10: ("q", 5),
12: Ap(5, 6),
13: Ap(5, 7),
14: ("q", 7),
},
)
G3 = (
4,
{
4: ("q", 2),
6: Ap(2, 3),
7: Ap(2, 4),
8: Ap(2, 5),
9: Ap(2, 6),
10: Ap(2, 7),
12: ("q", 6),
},
)
G4 = (6, {6: ("q", 3), 8: Ap(3, 4), 9: Ap(3, 5), 10: Ap(3, 6), 11: Ap(3, 7)})
return [G1, G2, G3, G4]
⋯ 275 lines
def g1_axis0():
# Axis 0's group 1 goes straight into freshly allocated n2b wires — the
# first group needs no adder at all (CX/AND writes onto zeros). The
# wiring derives from the same G1 spec emit_group consumes: the 'q'
# bit lands by CX, the spec's holes become fresh |0> allocations
# (ascending), the pair products follow in spec order, and the h8/h9
# half-adder pair comes LAST — emit_group computes that pair FIRST;
# the certified stream pins each order.
spec = SPECS[0][1]
D = RI[0]
(qpos,) = (p for p, e in spec.items() if e[0] == "q")
n2b[qpos] = b.qubit()
b.cx(D[spec[qpos][1]], n2b[qpos])
for pos in sorted(set(range(len(n2b))) - set(spec)):
n2b[pos] = b.qubit()
for pos, e in spec.items():
if e[0] == "a":
n2b[pos] = b.and_compute(D[e[1]], D[e[2]])
(h8p,) = (p for p, e in spec.items() if e[0] == "h8")
(h9p,) = (p for p, e in spec.items() if e[0] == "h9")
n2b[h8p] = b.and_compute(D[0], D[7])
n2b[h9p] = b.and_compute(n2b[h8p], D[4])
b.cx(D[4], n2b[h8p])
⋯ 1 line
def emit_group(D, off, spec, cap):
# accumulate one column group into n2b[off:cap] with a single sparse
# radd; pair-product ANDs are released again right after (the h8/h9
# half-adder pair needs its own compute/uncompute choreography)
p07 = t89 = None
if 8 in spec and spec[8][0] == "h8":
p07 = b.and_compute(D[0], D[7])
t89 = b.and_compute(p07, D[4])
b.cx(D[4], p07)
prods = []
xb = []
for pos in range(off, cap):
e = spec.get(pos)
if e is None:
xb.append(None)
elif e[0] == "q":
xb.append(D[e[1]])
elif e[0] == "h8":
xb.append(p07)
elif e[0] == "h9":
xb.append(t89)
else:
t = b.and_compute(D[e[1]], D[e[2]])
prods.append((D[e[1]], D[e[2]], t))
xb.append(t)
radd(n2b[off:cap], xb)
for u, v, t in reversed(prods):
b.and_release(t, u, v)
if p07 is not None:
b.cx(D[4], p07)
b.and_release(t89, p07, D[4])
b.and_release(p07, D[0], D[7])
6

Normalize without an exponent register

normalize

Writing n2 = 2eu with 1 ≤ u < 2 gives 1/√n2 = 2−e/2 u−1/2. The table then follows one curve on [1, 2), and the power of two returns as a shift at the end.

Four rows test whether the top 8, 4, 2, and 1 bits of n2b are zero. An AND ladder forms the flag, and one CX records it in ereg. A row of controlled swaps then shifts the word in place. The four flags encode both the shift and its parity, so no exponent register exists. The shift caps at 15, which normalizes every n2 ≥ 4.

_MNORM = b.checkpoint()
Normalize 62 Toffolis · peak 105 qubits
with b.phase("normalize/setup"):
ereg = [b.qubit() for _ in range(4)]
b.claim("exp-flag", ereg)
# Shared arith.mcx would close the ladder with b.ccx(ladder, last,
# fq) — same s_amt-1 Toffoli, one fewer transient AND wire; this
# full-ladder + CX form is what the certified stream pins, and the
# extra wire is metric-free (normalize peaks at 105 of the 142 peak).
for s_amt, jz, lo, hi in NORM_ROWS:
with b.phase(f"normalize/z{s_amt}"):
flag, rec = and_ladder(b, [(n2b[17 - t], 0) for t in range(s_amt)])
b.cx(flag, ereg[jz])
and_ladder_release(b, rec)
for i in reversed(range(lo, hi)):
# operand order fixes the stream encoding (swap is
# semantically symmetric; the certified stream pins it)
b.cswap(ereg[jz], n2b[i], n2b[i + s_amt])
7

Look up one of 128 lines

qrom

The address is the exponent parity and mantissa bits 16 to 11: seven bits, 128 slots. The walk descends a binary tree with one AND per internal node; walk_root splits the root with a CX parity wire instead. On this tree the walk costs 126 ANDs.

At each leaf, load_leaf copies the 17 stored intercept bits and the slope onto sreg and slope under the path flag. Mantissa bits 10 to 5 stay outside the address and form the six-bit local coordinate. A seven-bit coordinate that shared its top bit with the address would need 91 lookup ANDs. The disjoint choice costs 35 more, and in exchange it removes one product row on both passes, narrows the slope and the product by one bit each, and lowers the peak by two qubits.

_MQROM = b.checkpoint()
Line lookup 126 Toffolis · peak 129 qubits
with b.phase("qrom/setup"):
slope = [b.qubit() for _ in range(SW)]
sreg = [b.qubit() for _ in range(GLOW)]
b.claim("slope", slope)
b.claim("intercept", sreg)
addr_bits = [ereg[0]] + [n2b[16 - i] for i in range(A)]
# Early-terminating binary QROM over addr_bits = (e&1, top mantissa bits):
# each internal node costs one AND (Gidney) or one CX-pair (root); at a
# leaf, the intercept — msb stripped, it is implicit — and the slope are
# CX-copied onto sreg/slope under the path flag. Leaves sit exactly as
# shallow as build_tables' interval fitting allowed — under the R=6
# economics that is the full-depth 128-leaf tree.
# local: shared qrom.pruned_walk costs the same one AND per live
# internal node (126 on this 128-leaf tree) but roots on the bare address
# bit, clear child first; this root builds a CX-parity wire and takes
# the set-parity child first — the certified stream pins that order.
# The root parity wire is one extra transient, metric-free (qrom peaks
# below the 142 peak). The un-qrom repair walk reuses the same walker.
def walk(visit, flag, depth, prefix):
# visit(flag, depth, prefix) serves a leaf and returns True, or
# returns False to descend
if visit(flag, depth, prefix):
return
bq = addr_bits[depth]
ch = b.and_compute(flag, bq)
walk(visit, ch, depth + 1, 2 * prefix + 1)
b.cx(flag, ch)
walk(visit, ch, depth + 1, 2 * prefix)
b.cx(flag, ch)
b.and_release(ch, flag, bq)
def walk_root(visit, phase=None):
# phase names the sub-phases of the root's two children and close
b.x(ereg[0]) # address parity bit: e&1 = (17-z)&1 = NOT ereg[0]
rt = b.qubit()
b.claim("qrom-root", [rt])
b.cx(addr_bits[0], rt)
def sub(name):
return b.phase(f"{phase}/{name}") if phase else nullcontext()
with sub("parity1"):
walk(visit, rt, 1, 1)
b.x(rt)
with sub("parity0"):
walk(visit, rt, 1, 0)
b.x(rt)
with sub("close"):
b.cx(addr_bits[0], rt)
b.release_recycle("qrom-root", [rt])
b.x(ereg[0])
def load_leaf(flag, depth, prefix):
lf = LEAF.get((depth, prefix))
if lf is None:
return False
sl, g = lf
xor_word(b, flag, g - HBIT, sreg)
xor_word(b, flag, sl, slope)
return True
walk_root(load_leaf, "qrom")
8

Evaluate the line

multiply, subtract

The line value is g − Σ ⌊s·2i/4⌋ over the set coordinate bits bi. Row 0 lands as fresh ANDs, and rows 1 to 5 are controlled in-place additions into an 11-bit product. Six controlled additions take the place of a general multiplication. The bound _acc proves that each carry-out target is still zero.

The subtraction complements sreg around two chunked additions: an 8-bit low chunk and a 9-bit high chunk. The borrow wire bq1 stays live through the accumulation, and the mirror erases it; an early erase would compute the borrow twice. The register then widens, and the implicit leading bit is set.

_MMUL = b.checkpoint()
Slope product 78 Toffolis · peak 140 qubits
with b.phase("multiply/row0"):
rb0 = n2b[RL]
prod = [b.and_compute(rb0, q2) for q2 in slope[SH:]]
prod += [b.qubit() for _ in range(WP - len(prod))]
b.claim("product", prod)
_acc = (SMAX << 0) >> SH
for i in range(1, R):
with b.phase(f"multiply/row{i}"):
rb = n2b[RL + i]
sh = i - SH
if sh < 0:
avw = slope[-sh:]
pos = 0
else:
avw = slope
pos = sh
n = len(avw)
assert _acc < (1 << (pos + n)), "cout target not zero"
cout = prod[pos + n] if pos + n < WP else None
cadd(rb, avw, prod[pos : pos + n], cout)
_acc += (SMAX << i) >> SH
assert _acc < (1 << WP)
# ---- 6. gs_low -= prod (17-bit chunked complement subtract, no overall
# borrow); the chunk-borrow wire bq1 stays LIVE through the accumulate and
# the exact mirror erases it for free — an early erase would pay the
# borrow recompute twice, and lo8 pins the 142 peak either way ----
_MSUB = b.checkpoint()
Line value 16 Toffolis · peak 142 qubits
with b.phase("subtract/lo8"):
for j in range(GLOW):
b.x(sreg[j])
bq1 = b.qubit()
b.claim("borrow", [bq1])
radd(sreg[0:8], prod[0:8], cout=bq1)
with b.phase("subtract/hi9"):
radd(sreg[8:GLOW], prod[8:WP], cin=bq1)
with b.phase("subtract/restore"):
for j in range(GLOW):
b.x(sreg[j])
with b.phase("subtract/widen"):
sreg += [b.qubit() for _ in range(SREGW - GLOW - 1)]
b.claim("widen", sreg[GLOW:])
b.x(sreg[GW - 1])
9

Patch, rescale, and add into k

patch, shift, accumulate

The shift cap sends n2 = 1, 2, 3 to three recognizable mantissa patterns. Their top mantissa bit is clear, and bits 15 and 16 tell them apart. Two temporary ANDs select XOR corrections that rewrite the intercept to the required pre-shift value.

Three rows of controlled swaps apply the variable shift that restores the scale 2−e/2. An ancilla-free ripple adder (Cuccaro et al., 2004) then adds the value into k. The first discarded bit, sreg[1], enters as the carry-in, which rounds to nearest. The X pair on n2b[17] adds 223 on the small-input branch.

A temporary-AND carry chain would use fewer Toffolis here. It would keep about 23 carry ANDs live while the circuit sits at its 142-qubit peak with no spare width, so the circuit uses the ancilla-free adder.

_MPATCH = b.checkpoint()
Small inputs 2 Toffolis · peak 142 qubits
with b.phase("patch"):
e4 = n2b[17]
b.x(e4)
t3f = b.and_compute(e4, n2b[15])
xor_word(b, t3f, XBc, sreg[:GW])
b.and_release(t3f, e4, n2b[15])
t3c = b.and_compute(e4, n2b[16])
xor_word(b, t3c, XCc, sreg[:GW])
b.and_release(t3c, e4, n2b[16])
xor_word(b, e4, XEc, sreg[:GW])
b.x(e4)
sreg.append(b.qubit())
b.claim("widen", [sreg[-1]])
assert len(sreg) == SREGW
# ---- 8. variable left shift ----
_MSHIFT = b.checkpoint()
for s_amt, jz, lim in SHIFT_ROWS:
Rescale 58 Toffolis · peak 142 qubits
with b.phase(f"shift/s{s_amt}"):
fq = ereg[jz]
for p in reversed(range(lim)):
# operands (lo, hi) — normalize's rows pass (hi, lo); the two
# stream encodings differ deliberately, the semantics do not
b.cswap(fq, sreg[p + s_amt], sreg[p])
_MFWD = b.checkpoint() # forward-region end; the accumulate is not mirrored
# ---- 9. accumulate into k (not mirrored) ----
# DELTA = round(sreg / 4): sreg[2:] is the integer part, sreg[1] enters as
# the carry-in (round-to-nearest) — this is the /4-with-round of delta_of.
# The b.x(n2b[17]) sandwich adds 2^23 exactly when the top mantissa bit is
# CLEAR, i.e. on the under-normalized tiny-n2 (n2 <= 3) branch.
Add to k 48 Toffolis · peak 142 qubits
with b.phase("accumulate"):
b.x(n2b[17])
# cin = sreg[1] is a LIVE data wire: add()'s MAJ seed carries cin's
# value (added, then UMA-restored) — that is the advertised
# round-to-nearest. The gadget docstring's |0> carry-in wording is its
# zero-ancilla corner, not a precondition here.
cdkm_add_co(b, sreg[2:25] + [n2b[17]], KQ[0:24], KQ[24], sreg[1])
10

Mirror the arithmetic, measure the words

un-shift, un-patch, un-subtract, un-multiply, un-qrom

b.mirror emits the exact inverse of the code between two checkpoints. Segment mirrors undo the shift, the patch, the subtraction, and the product rows in reverse order. After the product rows, slope and sreg again hold exactly the looked-up words, a function of the still-live address.

A reversed lookup would repeat all 126 ANDs. Instead, the circuit measures each word bit in the X basis (Berry et al., 2019). The measurement costs no Toffolis and resets the wire to |0⟩. It leaves the sign (−1)⟨y, word(a)⟩ on address a, where y holds the outcomes.

The repair splits the address into three high bits and four low bits. Under each high prefix, the sixteen low-bit signs form a truth table. gamma applies the Möbius transform at build time and rewrites each table as an XOR of Reed–Muller monomials. Each coefficient is an XOR of measurement outcomes, which costs nothing.

# ---- mirror: the conjugation F . accumulate . F^-1 ----
# Segment mirrors, one per forward stage in reverse order, emit the exact
# Bennett inverse of every forward stage EXCEPT the QROM: the un-qrom
# segment below erases the table words by X-basis demolition and repays
# the phase with one floored repair walk, so the second tree walk the
# mirror would re-bill never runs. The segmentation carries the
# ownership discipline: each set releases just before the segment whose
# frees consume its wires, so release precedes free through the whole
# teardown. Each b.phase block names one teardown step and opens before
# its releases (emits no op), so the releases and the segment's tail
# frees attribute to the un-* phase rather than to accumulate; the
# reflected sub-phase labels come from each segment's own marks.
Rescale reversed 58 Toffolis · peak 142 qubits
with b.phase("un-shift"):
b.mirror(_MSHIFT, until=_MFWD) # frees nothing
Patch reversed 2 Toffolis · peak 142 qubits
with b.phase("un-patch"):
b.release("widen", [sreg[SREGW - 1]])
b.mirror(_MPATCH, until=_MSHIFT) # frees the patch widen wire
Line value reversed 16 Toffolis · peak 142 qubits
with b.phase("un-subtract"):
b.release("widen", sreg[GLOW : SREGW - 1])
b.release("borrow", [bq1])
b.mirror(_MSUB, until=_MPATCH) # frees bq1 + widen wires
Product reversed 73 Toffolis · peak 140 qubits
with b.phase("un-multiply"):
b.release("product", prod)
b.mirror(_MMUL, until=_MSUB) # releases/frees prod
# ---- un-qrom: measurement-based unlookup + floored phase-repair walk ----
# After un-multiply the slope/intercept wires hold exactly the looked-up
# table words again — a pure function of the address bits, all still
# live. Each X-basis demolition (hmr) is free, forces its wire to |0>,
# and leaves the shot's sign multiplied by (-1)^<outcomes, word(addr)>;
# the walk below repays that mask, so clearing the words costs 17
# Toffoli instead of the mirror's second 126-AND tree walk. Frees run
# in reverse alloc order — the LIFO free-list state the later mirrors
# assert is exactly the one the mirrored unlookup would have left.
Lookup erased 17 Toffolis · peak 122 qubits
with b.phase("un-qrom/measure"):
b.release("intercept", sreg[:GLOW])
b.release("slope", slope)
m_sreg = [None] * GLOW
m_slope = [None] * SW
for j in reversed(range(GLOW)):
m_sreg[j] = b.hmr(sreg[j])
b.recycle(sreg[j])
for j in reversed(range(SW)):
m_slope[j] = b.hmr(slope[j])
b.recycle(slope[j])
# Per-flag repair coefficients in the monomial (Reed-Muller) basis.
# word(f) packs the 24 measured table bits of leaf f; W(p, S) XORs the
# words over the low-nibble subcube v <= S, so the phase to repay at
# flag p factors as sum_S <outcomes, W(p, S)> * mono_S(low bits).
# The Mobius transform runs on the classical table at build time, and
# the runtime coefficients stay plain outcome XOR chains (free OXORs;
# None = zero mask, nothing to repay). Nibble bit FL_M-1-t of v and
# of S corresponds to low[t], matching the walk's msb-first prefixes.
FL_M = 4 # low address bits served by monomials
FL_D = 7 - FL_M # repair-walk depth (high address bits)
low = addr_bits[FL_D:]
def word(f):
return (G0f[f] - HBIT) | (SLf[f] << GLOW)
def gamma(p, S):
w = 0
for v in range(1 << FL_M):
if v & ~S == 0:
w ^= word((p << FL_M) | v)
ms = [m_sreg[j] for j in range(GLOW) if (w >> j) & 1]
ms += [m_slope[j] for j in range(SW) if (w >> (GLOW + j)) & 1]
acc = None
for m2 in ms:
acc = m2 if acc is None else acc ^ m2
return acc
GAM = [[gamma(p, S) for S in range(1 << FL_M)] for p in range(1 << FL_D)]
11

Repair the phase with 17 Toffolis

un-qrom, un-normalize, un-squarer, un-diffs

A monomial bank forms every product of two or more low address bits: six pairs, four triples, and one quadruple, for 11 Toffolis. The single bits are the address wires themselves. The repair walk reuses the forward walker but stops at depth three, for six path ANDs.

At each depth-three flag, repair_leaf applies Z or CZ gates conditioned on the coefficient bits. These phase gates are free, so 6 + 11 = 17 Toffolis repay the whole unlookup. Corrections below depth three change only free gates, so the repair does not grow with the number of leaves.

The bank is released by measurement. The last three mirrors undo normalization, the squarer, and the differences. They return ereg, n2b, and the sign qubits to |0⟩ and restore ri.

with b.phase("un-qrom/monomials"):
def smask(ts): # subset of low-bit indices -> nibble-style mask
v = 0
for t in ts:
v |= 1 << (FL_M - 1 - t)
return v
MONO = {}
MORDER = []
for ts in (
(0, 1),
(0, 2),
(0, 3),
(1, 2),
(1, 3),
(2, 3),
(0, 1, 2),
(0, 1, 3),
(0, 2, 3),
(1, 2, 3),
(0, 1, 2, 3),
):
base = MONO[smask(ts[:-1])] if len(ts) > 2 else low[ts[0]]
w = b.and_compute(base, low[ts[-1]])
MONO[smask(ts)] = w
MORDER.append((w, base, low[ts[-1]]))
# Floored repair walk: path ANDs over the three high address bits only
# (2 + 4 = 6); at each depth-3 flag the sixteen Reed-Muller
# coefficients condition free phase gates — Z on the flag for S = 0,
# CZ against a bare low address wire or a bank monomial otherwise.
# 6 + 11 = 17 Toffoli repays the whole unlookup. The forward walker
# runs it, so the root CX-parity trick and child order match.
with b.phase("un-qrom/fix"):
def repair_leaf(flag, depth, prefix):
if depth != FL_D:
return False
g = GAM[prefix]
if g[0] is not None:
b.z(flag, cond=g[0])
for S in range(1, 1 << FL_M):
if g[S] is None:
continue
if S & (S - 1) == 0:
b.cz(flag, low[FL_M - S.bit_length()], cond=g[S])
else:
b.cz(flag, MONO[S], cond=g[S])
return True
walk_root(repair_leaf)
# Unwind the monomial bank: free measurement-based releases in exact
# reverse alloc order (bases outlive their products), restoring the
# free-list for the mirrors below.
with b.phase("un-qrom/close"):
for w, c1, c2 in reversed(MORDER):
b.and_release(w, c1, c2)
Normalize reversed 62 Toffolis · peak 105 qubits
with b.phase("un-normalize"):
b.release("exp-flag", ereg)
b.mirror(_MNORM, until=_MQROM) # frees ereg
Squares reversed 210 Toffolis · peak 123 qubits
with b.phase("un-squarer"):
b.release("n2", n2b)
b.mirror(_MSQ, until=_MNORM) # releases/frees n2
Differences reversed 45 Toffolis · peak 84 qubits
with b.phase("un-diffs"):
b.release("sign", SGN)
b.mirror(_M0, until=_MSQ) # frees the sign wires
return b, RI, RJ, KQ, n2b
Start (init): 0 Toffolis, peak 73 qubitsDifferences (diffs): 45 Toffolis, peak 84 qubitsSquared distance (squarer): 222 Toffolis, peak 123 qubitsNormalize (normalize): 62 Toffolis, peak 105 qubitsLine lookup (qrom): 126 Toffolis, peak 129 qubitsSlope product (multiply): 78 Toffolis, peak 140 qubitsLine value (subtract): 16 Toffolis, peak 142 qubitsSmall inputs (patch): 2 Toffolis, peak 142 qubitsRescale (shift): 58 Toffolis, peak 142 qubitsAdd to k (accumulate): 48 Toffolis, peak 142 qubitsRescale reversed (un-shift): 58 Toffolis, peak 142 qubitsPatch reversed (un-patch): 2 Toffolis, peak 142 qubitsLine value reversed (un-subtract): 16 Toffolis, peak 142 qubitsProduct reversed (un-multiply): 73 Toffolis, peak 140 qubitsLookup erased (un-qrom): 17 Toffolis, peak 122 qubitsNormalize reversed (un-normalize): 62 Toffolis, peak 105 qubitsSquares reversed (un-squarer): 210 Toffolis, peak 123 qubitsDifferences reversed (un-diffs): 45 Toffolis, peak 84 qubits14273peak live qubits per phase, above the 73 I/O qubits1,140 Toffolis →
_tabs = build_tables() # ~1.5 s, certified exhaustively inside
assert _tabs is not None, "table infeasible"
SLf, G0f, LEAF, SW, WP = _tabs
SMAX = max(max(SLf), 1)
# leaf_f indexes the leaves by the top 7 mantissa bits: the tree is full depth
assert all(depth == 7 for (depth, _p) in LEAF)
print(f"tables ok: leaves={len(LEAF)} SW={SW} WP={WP}")
⋯ 224 lines
b = Builder()
_r = declare_registers(
b, [(n, B_C) for n in ("rix", "riy", "riz", "rjx", "rjy", "rjz")] + [("k", M_K)]
)
RI = [_r["rix"], _r["riy"], _r["riz"]]
RJ = [_r["rjx"], _r["rjy"], _r["rjz"]]
KQ = _r["k"]
b.claim("ri", RI[0] + RI[1] + RI[2])
b.claim("rj", RJ[0] + RJ[1] + RJ[2])
b.claim("k", KQ)
⋯ 107 lines
_M0 = b.checkpoint() # forward-region start (everything until _MFWD mirrors)
Differences 45 Toffolis · peak 84 qubits
with b.phase("diffs/setup"):
SGN = [b.qubit() for _ in range(3)]
b.claim("sign", SGN)
for ax in range(3):
with b.phase(f"diffs/ax{ax}"):
riq = RI[ax]
rjq = RJ[ax]
sg = SGN[ax]
for q in riq:
b.x(q)
radd(riq, rjq, cout=sg)
for q in riq:
b.x(q)
for q in riq:
b.cx(sg, q)
radd(riq, [sg])
⋯ 62 lines
Squared distance 222 Toffolis · peak 123 qubits
with b.phase("squarer/a0g1"):
b.claim("n2", n2b) # all 18 wires exist once a0g1 has seeded the word
for _ax, _g, _cap in SQ_SCHED:
with b.phase(f"squarer/a{_ax}g{_g + 1}"):
emit_group(RI[_ax], SPECS[_g][0], SPECS[_g][1], _cap)
# ---- 3. normalize: iterative binary-search flags + staged cond. shifts ----
# z = 8*ereg[3] + 4*ereg[2] + 2*ereg[1] + ereg[0] is the left shift that
# brings the top set bit of n2 to bit 17; e = 17 - z. The shift is capped
# at 15 (= 8+4+2+1), enough for every n2 >= 4; n2 <= 3 under-shifts and is
# corrected by the tiny-n2 patch in stage 7. Each row's AND ladder
# flags that the current top s_amt bits of n2b are all zero, and one CX
# flips the row's ereg bit under the flag.
_MNORM = b.checkpoint()
Normalize 62 Toffolis · peak 105 qubits
with b.phase("normalize/setup"):
ereg = [b.qubit() for _ in range(4)]
b.claim("exp-flag", ereg)
# Shared arith.mcx would close the ladder with b.ccx(ladder, last,
# fq) — same s_amt-1 Toffoli, one fewer transient AND wire; this
# full-ladder + CX form is what the certified stream pins, and the
# extra wire is metric-free (normalize peaks at 105 of the 142 peak).
for s_amt, jz, lo, hi in NORM_ROWS:
with b.phase(f"normalize/z{s_amt}"):
flag, rec = and_ladder(b, [(n2b[17 - t], 0) for t in range(s_amt)])
b.cx(flag, ereg[jz])
and_ladder_release(b, rec)
for i in reversed(range(lo, hi)):
# operand order fixes the stream encoding (swap is
# semantically symmetric; the certified stream pins it)
b.cswap(ereg[jz], n2b[i], n2b[i + s_amt])
# ---- 4. adaptive QROM ----
_MQROM = b.checkpoint()
Line lookup 126 Toffolis · peak 129 qubits
with b.phase("qrom/setup"):
slope = [b.qubit() for _ in range(SW)]
sreg = [b.qubit() for _ in range(GLOW)]
b.claim("slope", slope)
b.claim("intercept", sreg)
addr_bits = [ereg[0]] + [n2b[16 - i] for i in range(A)]
# Early-terminating binary QROM over addr_bits = (e&1, top mantissa bits):
# each internal node costs one AND (Gidney) or one CX-pair (root); at a
# leaf, the intercept — msb stripped, it is implicit — and the slope are
# CX-copied onto sreg/slope under the path flag. Leaves sit exactly as
# shallow as build_tables' interval fitting allowed — under the R=6
# economics that is the full-depth 128-leaf tree.
# local: shared qrom.pruned_walk costs the same one AND per live
# internal node (126 on this 128-leaf tree) but roots on the bare address
# bit, clear child first; this root builds a CX-parity wire and takes
# the set-parity child first — the certified stream pins that order.
# The root parity wire is one extra transient, metric-free (qrom peaks
# below the 142 peak). The un-qrom repair walk reuses the same walker.
def walk(visit, flag, depth, prefix):
# visit(flag, depth, prefix) serves a leaf and returns True, or
# returns False to descend
if visit(flag, depth, prefix):
return
bq = addr_bits[depth]
ch = b.and_compute(flag, bq)
walk(visit, ch, depth + 1, 2 * prefix + 1)
b.cx(flag, ch)
walk(visit, ch, depth + 1, 2 * prefix)
b.cx(flag, ch)
b.and_release(ch, flag, bq)
def walk_root(visit, phase=None):
# phase names the sub-phases of the root's two children and close
b.x(ereg[0]) # address parity bit: e&1 = (17-z)&1 = NOT ereg[0]
rt = b.qubit()
b.claim("qrom-root", [rt])
b.cx(addr_bits[0], rt)
def sub(name):
return b.phase(f"{phase}/{name}") if phase else nullcontext()
with sub("parity1"):
walk(visit, rt, 1, 1)
b.x(rt)
with sub("parity0"):
walk(visit, rt, 1, 0)
b.x(rt)
with sub("close"):
b.cx(addr_bits[0], rt)
b.release_recycle("qrom-root", [rt])
b.x(ereg[0])
def load_leaf(flag, depth, prefix):
lf = LEAF.get((depth, prefix))
if lf is None:
return False
sl, g = lf
xor_word(b, flag, g - HBIT, sreg)
xor_word(b, flag, sl, slope)
return True
walk_root(load_leaf, "qrom")
# ---- 5. multiply: prod = sum_i rem_i * floor(slope*2^i/4) ----
# Row 0 lands as fresh ANDs onto the new product wires; rows 1..5 are
# in-place controlled adds (cadd). _acc tracks the maximum achievable
# running sum, proving each row's carry-out target is still zero when used.
_MMUL = b.checkpoint()
Slope product 78 Toffolis · peak 140 qubits
with b.phase("multiply/row0"):
rb0 = n2b[RL]
prod = [b.and_compute(rb0, q2) for q2 in slope[SH:]]
prod += [b.qubit() for _ in range(WP - len(prod))]
b.claim("product", prod)
_acc = (SMAX << 0) >> SH
for i in range(1, R):
with b.phase(f"multiply/row{i}"):
rb = n2b[RL + i]
sh = i - SH
if sh < 0:
avw = slope[-sh:]
pos = 0
else:
avw = slope
pos = sh
n = len(avw)
assert _acc < (1 << (pos + n)), "cout target not zero"
cout = prod[pos + n] if pos + n < WP else None
cadd(rb, avw, prod[pos : pos + n], cout)
_acc += (SMAX << i) >> SH
assert _acc < (1 << WP)
# ---- 6. gs_low -= prod (17-bit chunked complement subtract, no overall
# borrow); the chunk-borrow wire bq1 stays LIVE through the accumulate and
# the exact mirror erases it for free — an early erase would pay the
# borrow recompute twice, and lo8 pins the 142 peak either way ----
_MSUB = b.checkpoint()
Line value 16 Toffolis · peak 142 qubits
with b.phase("subtract/lo8"):
for j in range(GLOW):
b.x(sreg[j])
bq1 = b.qubit()
b.claim("borrow", [bq1])
radd(sreg[0:8], prod[0:8], cout=bq1)
with b.phase("subtract/hi9"):
radd(sreg[8:GLOW], prod[8:WP], cin=bq1)
with b.phase("subtract/restore"):
for j in range(GLOW):
b.x(sreg[j])
with b.phase("subtract/widen"):
sreg += [b.qubit() for _ in range(SREGW - GLOW - 1)]
b.claim("widen", sreg[GLOW:])
b.x(sreg[GW - 1])
# ---- 7. tiny-n2 patch (pre-shift): rewrite sreg from the deterministic
# leaf intercept G_v to Q_v. Flags are AND-allocated wires released by
# free measurement-based uncomputes. Discriminators (post-normalization,
# n2<=3 only): n2=1 -> n2b[15]=1,n2b[16]=0; n2=2 -> 0,1; n2=3 -> 1,1. ----
_MPATCH = b.checkpoint()
Small inputs 2 Toffolis · peak 142 qubits
with b.phase("patch"):
e4 = n2b[17]
b.x(e4)
t3f = b.and_compute(e4, n2b[15])
xor_word(b, t3f, XBc, sreg[:GW])
b.and_release(t3f, e4, n2b[15])
t3c = b.and_compute(e4, n2b[16])
xor_word(b, t3c, XCc, sreg[:GW])
b.and_release(t3c, e4, n2b[16])
xor_word(b, e4, XEc, sreg[:GW])
b.x(e4)
sreg.append(b.qubit())
b.claim("widen", [sreg[-1]])
assert len(sreg) == SREGW
# ---- 8. variable left shift ----
_MSHIFT = b.checkpoint()
for s_amt, jz, lim in SHIFT_ROWS:
Rescale 58 Toffolis · peak 142 qubits
with b.phase(f"shift/s{s_amt}"):
fq = ereg[jz]
for p in reversed(range(lim)):
# operands (lo, hi) — normalize's rows pass (hi, lo); the two
# stream encodings differ deliberately, the semantics do not
b.cswap(fq, sreg[p + s_amt], sreg[p])
_MFWD = b.checkpoint() # forward-region end; the accumulate is not mirrored
# ---- 9. accumulate into k (not mirrored) ----
# DELTA = round(sreg / 4): sreg[2:] is the integer part, sreg[1] enters as
# the carry-in (round-to-nearest) — this is the /4-with-round of delta_of.
# The b.x(n2b[17]) sandwich adds 2^23 exactly when the top mantissa bit is
# CLEAR, i.e. on the under-normalized tiny-n2 (n2 <= 3) branch.
Add to k 48 Toffolis · peak 142 qubits
with b.phase("accumulate"):
b.x(n2b[17])
# cin = sreg[1] is a LIVE data wire: add()'s MAJ seed carries cin's
# value (added, then UMA-restored) — that is the advertised
# round-to-nearest. The gadget docstring's |0> carry-in wording is its
# zero-ancilla corner, not a precondition here.
cdkm_add_co(b, sreg[2:25] + [n2b[17]], KQ[0:24], KQ[24], sreg[1])
b.x(n2b[17])
# ---- mirror: the conjugation F . accumulate . F^-1 ----
# Segment mirrors, one per forward stage in reverse order, emit the exact
# Bennett inverse of every forward stage EXCEPT the QROM: the un-qrom
# segment below erases the table words by X-basis demolition and repays
# the phase with one floored repair walk, so the second tree walk the
# mirror would re-bill never runs. The segmentation carries the
# ownership discipline: each set releases just before the segment whose
# frees consume its wires, so release precedes free through the whole
# teardown. Each b.phase block names one teardown step and opens before
# its releases (emits no op), so the releases and the segment's tail
# frees attribute to the un-* phase rather than to accumulate; the
# reflected sub-phase labels come from each segment's own marks.
Rescale reversed 58 Toffolis · peak 142 qubits
with b.phase("un-shift"):
b.mirror(_MSHIFT, until=_MFWD) # frees nothing
Patch reversed 2 Toffolis · peak 142 qubits
with b.phase("un-patch"):
b.release("widen", [sreg[SREGW - 1]])
b.mirror(_MPATCH, until=_MSHIFT) # frees the patch widen wire
Line value reversed 16 Toffolis · peak 142 qubits
with b.phase("un-subtract"):
b.release("widen", sreg[GLOW : SREGW - 1])
b.release("borrow", [bq1])
b.mirror(_MSUB, until=_MPATCH) # frees bq1 + widen wires
Product reversed 73 Toffolis · peak 140 qubits
with b.phase("un-multiply"):
b.release("product", prod)
b.mirror(_MMUL, until=_MSUB) # releases/frees prod
# ---- un-qrom: measurement-based unlookup + floored phase-repair walk ----
# After un-multiply the slope/intercept wires hold exactly the looked-up
# table words again — a pure function of the address bits, all still
# live. Each X-basis demolition (hmr) is free, forces its wire to |0>,
# and leaves the shot's sign multiplied by (-1)^<outcomes, word(addr)>;
# the walk below repays that mask, so clearing the words costs 17
# Toffoli instead of the mirror's second 126-AND tree walk. Frees run
# in reverse alloc order — the LIFO free-list state the later mirrors
# assert is exactly the one the mirrored unlookup would have left.
Lookup erased 17 Toffolis · peak 122 qubits
with b.phase("un-qrom/measure"):
b.release("intercept", sreg[:GLOW])
b.release("slope", slope)
m_sreg = [None] * GLOW
m_slope = [None] * SW
for j in reversed(range(GLOW)):
m_sreg[j] = b.hmr(sreg[j])
b.recycle(sreg[j])
for j in reversed(range(SW)):
m_slope[j] = b.hmr(slope[j])
b.recycle(slope[j])
# Per-flag repair coefficients in the monomial (Reed-Muller) basis.
# word(f) packs the 24 measured table bits of leaf f; W(p, S) XORs the
# words over the low-nibble subcube v <= S, so the phase to repay at
# flag p factors as sum_S <outcomes, W(p, S)> * mono_S(low bits).
# The Mobius transform runs on the classical table at build time, and
# the runtime coefficients stay plain outcome XOR chains (free OXORs;
# None = zero mask, nothing to repay). Nibble bit FL_M-1-t of v and
# of S corresponds to low[t], matching the walk's msb-first prefixes.
FL_M = 4 # low address bits served by monomials
FL_D = 7 - FL_M # repair-walk depth (high address bits)
low = addr_bits[FL_D:]
def word(f):
return (G0f[f] - HBIT) | (SLf[f] << GLOW)
def gamma(p, S):
w = 0
for v in range(1 << FL_M):
if v & ~S == 0:
w ^= word((p << FL_M) | v)
ms = [m_sreg[j] for j in range(GLOW) if (w >> j) & 1]
ms += [m_slope[j] for j in range(SW) if (w >> (GLOW + j)) & 1]
acc = None
for m2 in ms:
acc = m2 if acc is None else acc ^ m2
return acc
GAM = [[gamma(p, S) for S in range(1 << FL_M)] for p in range(1 << FL_D)]
# Monomial bank over the FL_M low address bits (m[14..11]): one AND
# wire per product of two or more bits — six pairs, four triples (a
# pair wire AND one more bit), one quad — 11 Toffoli. Singleton
# monomials are the address wires themselves and the bank floors the
# repair walk: leaves below depth FL_D are served entirely by free
# conditioned phase gates, so repair cost stops growing with leaf
# count — the economics the R=6 refit banks on.
with b.phase("un-qrom/monomials"):
def smask(ts): # subset of low-bit indices -> nibble-style mask
v = 0
for t in ts:
v |= 1 << (FL_M - 1 - t)
return v
MONO = {}
MORDER = []
for ts in (
(0, 1),
(0, 2),
(0, 3),
(1, 2),
(1, 3),
(2, 3),
(0, 1, 2),
(0, 1, 3),
(0, 2, 3),
(1, 2, 3),
(0, 1, 2, 3),
):
base = MONO[smask(ts[:-1])] if len(ts) > 2 else low[ts[0]]
w = b.and_compute(base, low[ts[-1]])
MONO[smask(ts)] = w
MORDER.append((w, base, low[ts[-1]]))
# Floored repair walk: path ANDs over the three high address bits only
# (2 + 4 = 6); at each depth-3 flag the sixteen Reed-Muller
# coefficients condition free phase gates — Z on the flag for S = 0,
# CZ against a bare low address wire or a bank monomial otherwise.
# 6 + 11 = 17 Toffoli repays the whole unlookup. The forward walker
# runs it, so the root CX-parity trick and child order match.
with b.phase("un-qrom/fix"):
def repair_leaf(flag, depth, prefix):
if depth != FL_D:
return False
g = GAM[prefix]
if g[0] is not None:
b.z(flag, cond=g[0])
for S in range(1, 1 << FL_M):
if g[S] is None:
continue
if S & (S - 1) == 0:
b.cz(flag, low[FL_M - S.bit_length()], cond=g[S])
else:
b.cz(flag, MONO[S], cond=g[S])
return True
walk_root(repair_leaf)
# Unwind the monomial bank: free measurement-based releases in exact
# reverse alloc order (bases outlive their products), restoring the
# free-list for the mirrors below.
with b.phase("un-qrom/close"):
for w, c1, c2 in reversed(MORDER):
b.and_release(w, c1, c2)
Normalize reversed 62 Toffolis · peak 105 qubits
with b.phase("un-normalize"):
b.release("exp-flag", ereg)
b.mirror(_MNORM, until=_MQROM) # frees ereg
Squares reversed 210 Toffolis · peak 123 qubits
with b.phase("un-squarer"):
b.release("n2", n2b)
b.mirror(_MSQ, until=_MNORM) # releases/frees n2
Differences reversed 45 Toffolis · peak 84 qubits
with b.phase("un-diffs"):
b.release("sign", SGN)
b.mirror(_M0, until=_MSQ) # frees the sign wires
return b, RI, RJ, KQ, n2b