Skip to content

fix(gate-decompose): correct the sign on the first RZ of the RX sequence - #4

Merged
rustnew merged 2 commits into
rustnew:mainfrom
cleitonaugusto:fix-rx-decomposition-sign
Aug 25, 2026
Merged

fix(gate-decompose): correct the sign on the first RZ of the RX sequence#4
rustnew merged 2 commits into
rustnew:mainfrom
cleitonaugusto:fix-rx-decomposition-sign

Conversation

@cleitonaugusto

Copy link
Copy Markdown
Contributor

Fixes the RX half of #3.

The RX arm of the decomposition table emits, in circuit order:

rz(-pi/2), sx, rz(pi + theta), sx, rz(pi/2)

That does not implement RX(theta). I compared it against RX(theta) up to global
phase at theta in {0, 0.3, 1.0, pi/2, pi, 2.2, -0.7, 3.9} and it fails at all of
them. The clearest case is theta = 0, which should be the identity and instead
comes out as

[[1,  0],
 [0, -1]]

which is Z.

Changing the first rotation to rz(+pi/2) makes the sequence equal RX(theta) up
to global phase at every angle I tried.

The test I added builds the operator from whatever the table returns rather than
from a copy of the sequence, so it keeps checking the real entry if the table
changes later. It fails on the old sign and passes on the new one. I ran the
whole workspace suite and nothing else moved.

The comment above that arm gave two different formulas, one with six gates and
one with five. I replaced it with the sequence that is actually emitted.

This is only the sign. The other half of #3, that gate-decomposition never
removes the original op, is untouched here since it needs a different change.
Happy to send that one too if you want it.

The RX arm emitted rz(-pi/2), sx, rz(pi + theta), sx, rz(pi/2), which does
not implement RX(theta) for any angle. The clearest case is theta = 0: the
identity compiled to diag(1, -1), which is Z.

Changing the first rotation to rz(+pi/2) makes the sequence equal RX(theta)
up to global phase.

Adds test_rx_decomposition_matches_rx, which builds the operator from
whatever the table returns and compares it against RX(theta) at eight
angles, so it keeps checking the real entry rather than a copy. The test
fails on the old sign and passes on the new one.
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 77680cbe-2fe7-45f3-89d5-32970bb0db68


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…order

The first version of this branch failed both CI gates: rustfmt reformatted
the matrix literals, and clippy rejected map_or under -D warnings. Both are
fixed here, and I ran fmt, clippy and the workspace suite locally this time.

The ordering comment in the rx test was also claiming something the test
could not check. Every entry in the decomposition table is a single gate, a
palindrome or a conjugation, so the product is the same in either order and
no table entry can pin the convention down.

test_composition_is_in_circuit_order does it directly, with an asymmetric
pair and an expected matrix written out by hand rather than composed. That
second part matters: comparing mmul against mmul passes even when mmul
itself is inverted, since the error cancels on both sides. With the literal
matrix the test fails when the multiplication order is flipped.

Also loosens the phase comparison to be relative to the entry size, since an
absolute bound fails on correct code when a matrix entry is near zero, and
names the sx convention the test assumes.
@rustnew
rustnew merged commit 52fc962 into rustnew:main Aug 25, 2026
1 check passed
rustnew added a commit that referenced this pull request Aug 25, 2026
…aining it

Fixes the other half of #3 (the RX sign was already fixed by #4). The pass
built a native decomposition chain, inserted it before the original op, and
re-pointed the original op's *inputs* to the chain's output — but the
original op itself stayed in the block and still produced its own result,
which every downstream consumer kept using. So decomposing T didn't replace
it: it composed Rz(pi/4) with the still-present T, which is S, not T's actual
decomposition. Same for every other entry in the table.

Now the original op's results are redirected to the decomposition chain's
final results (scanning all ops' inputs, same pattern gate-cancellation
already uses for its own redirects), and the original op is deleted from
both its block and the ops slotmap. Strengthened test_ibm_decomposes_h to
assert H is actually gone (it previously only checked the decomposition's
gates were present, which passed whether or not H survived), and added
test_original_gate_is_removed_not_chained.

539/539 tests, examples/validate_all.sh 105/105, clippy -D warnings and fmt
--check clean.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rustnew added a commit that referenced this pull request Aug 25, 2026
Several code examples used APIs that don't exist and would not compile:
- ctx.type_interner -> ctx.types (real field name)
- lift_quantum::dialect::Provider -> lift_quantum::gates::Provider (wrong
  module path)
- Provider::Ibm -> Provider::IbmEagle/IbmKyoto/etc. (no bare Ibm variant)
- GateDecomposition::new(Some(Provider::Ibm)) -> the real signature takes
  Provider directly, not Option<Provider> (2 occurrences)
- tensor_info.size_bytes() doesn't exist on TensorTypeInfo; replaced with
  the real element-count x dtype-size computation

Other fixes, all verified against source and/or the CLI:
- "tensor/quantum/hybrid dialects registered automatically" was false —
  only core is automatic via Context::new(); the others need explicit
  register_*_dialect() calls (this is what lift-cli's verify command does).
  Replaced the claim with a working example.
- LayoutMapping was described as inserting SWAP gates in two places; it's
  annotation-only (needs_swap = true) — RealRouting does the actual
  insertion. Fixed both, and updated §5.2's "Combine with" pointer.
- The O0-O3 pass table (§12.3) contradicted both §7.4 and the real
  OptimisationConfig::passes_for_level: O1 was missing constant-folding,
  O2/O3 were wrong about which passes they add. Rewritten to match.
- The RX decomposition comment still showed the pre-fix buggy sign
  (RZ(-pi/2)...) from before this session's #4 fix landed.
- The OpenQASM export note repeated the "10/50+ gates supported" claim that
  turned out to be false everywhere it appeared this session (see the
  CAPABILITIES.md/LIFT_design.md fix) — corrected to all 48.
- 3 tensor ops (Clamp, SparseEmbedding, Where) and 2 quantum gates (GPI,
  GPI2) were missing from the "110 ops"/"48 gates" reference tables, and the
  ops table's numbering had a pre-existing bug (jumped 106->110). Added the
  5 missing entries and renumbered the whole table 1-110 against the real
  TensorOp enum, zero diff.

Verified working via the CLI: the MLP example, the Bell-state example, and
both .lith config examples all pass verify/optimise as shown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants