Repository navigation
Conversation
…ax_k add_rescaling covered k = floor(|k_out - k_target| / 2) only below Synthesizer.max_k, which is computed once from the input. Past the input depth (incremental search, -d, restricted gate sets, custom vector pairs) the rescaled vectors were left unconstrained, so any circuit passed the equivalence check: t;x;t;x with -d 1 gave h;h;h;h. encode_equivalence now covers every gap the encoded depth can produce (each layer raises k by at most 2), and add_rescaling asserts k < d, so a gap outside the case split is UNSAT instead of unconstrained. Part of #65 (the MILP counterpart, F15, is not addressed here). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177AhE9SXm3iJ1KP8GTSk6o
The rescaling bound is computed from the pairs' k, so a custom pair with k=4.0 raised a TypeError in range(). Integral floats are now accepted and anything else is rejected with a ValueError. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0177AhE9SXm3iJ1KP8GTSk6o
add_milp_rescaling took its k bound and both big-Ms from Synthesizer.max_k,
which is fixed from the input. Past the input depth, or with custom pairs
whose k is not reduced, the correct circuit was infeasible: CBC found no
sx;sx for X from {sx, z}, nor h;sx;sx;h for Z from {h, sx}.
- The MILP rescaling uses the same per-depth gap bound as the SMT one, with
a parity big-M of 2*max_gap + 1 and a value big-M from the vectors' bounds.
- k of the rescaling has no ub: incremental search gets back the first
depth's PuLP variable with its stale bound, the one-hot bounds k anyway.
- The intermediate states' k bound includes the input's k.
- The per-layer increase of k comes from a k_increments table in gates.py,
used by encode_layer and by the bounds, instead of a constant 2.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This was referenced Oct 17, 2026
ondrik
force-pushed
the
fix/65-rescaling-bound
branch
from
October 22, 2026 12:23
f269ec7 to
16f2ae8
Compare
Contributor
|
Consolidated into #88 at @ondrik's request: this PR's commit is now on #88's branch, rebased onto 🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #88: it reuses
pair_kand the per-depth gap bound. Together with #88 this fixes #65 (F1 there, F15 here). F58 continues in #104.Problem
add_milp_rescalingtook its k bound and both big-Ms fromSynthesizer.max_k, which is fixed from the input. Past the input depth, and with custom pairs whose k is not reduced, the correct circuit was infeasible. CBC never returned a wrong circuit here; it missed valid ones:{sx, z}: nosx;sx{h, sx}: noh;sx;sx;hxxFix
max_gap = max(k_in + inc·depth − k_target, k_target − k_in)bounds |k1 − k2|. The rescaling k is in[0, max_gap // 2], the parity big-M is2·max_gap + 1, and the value big-M isB + R, with B the vectors' entry bound andR = 2B·2^(max_gap // 2)the bound of theRescaledvectors (no longer2**max_k).k{pair}at each depth and gets back the first depth's variable, so itsubstayed stale even afterpop(). k has noubnow; the one-hot bounds it.Inter's k bound was2·(d+1), ignoring the input's own k. It isk_in + inc·dnow. This is the k half of MILP/Gurobi state bounds ignore the input state #69 F4.incis the largest k increment in the gate set, from ak_incrementstable ingates.py.encode_layerreads the same table, so a new gate that raises k must be added there (KeyError otherwise) instead of silently breaking a hardcoded2. Gate sets without h/sx get a gap of 0 and smaller big-Ms.Why it is sound
With Δ = k1 − k2, K = ⌊|Δ|/2⌋ and q = |Δ| mod 2, the equality is
v1·2^K·√2^q = v2(or swapped), and√2·(a,b,c,d) = (b−d, a+c, b+d, c−a), as encoded.ExactlyOne), so |Δ| ≤ max_gap and K ≤ max_gap // 2. A larger gap is infeasible, never unconstrained.lb=0rules out the wrongsleq.constrained_rescaling(2·tmp, 2c·tmp, 3c·tmp, with tmp = B + R) cover each case.This holds in exact arithmetic. MILP solvers accept values within a tolerance, so very large big-Ms are a numerical risk; that is #104.
Testing
tests/test_rescaling_bound.py: the four MILP cases above (all fail on Bound the FiveTuple rescaling by the encoded depth, in SMT and MILP (#65) #88's branch), andmax_k_incrementfor four gate sets.add_milp_rescalingwith fixed vectors and compares CBC's feasibility with complex-float ground truth from the definition. 1,848 solves (630 equal pairs, 870 not), with tight, off-by-one-parity and loosemax_gap, plus too-small bounds that must be infeasible: 0 mismatches. As a check on the checker, a parity big-M of2·max_gap − 1gives 58 mismatches and a value big-M of(B+R)/4gives 11.Not in this PR
IntFeasTolof 1e-5.