Add R_XX, R_YY, R_ZZ, and R_PAULI parametric Pauli-product rotation gates#161
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 64f4ee4aa4
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if gate_name in ("R_PAULI", "R_XX", "R_YY", "R_ZZ"): | ||
| if not is_half_pi_multiple(params["theta"]): | ||
| return False |
There was a problem hiding this comment.
Expand Clifford Pauli rotations before declaring them Clifford
For half-π Pauli rotations other than theta=0.5, this makes Circuit.is_clifford return true even though Circuit.stim_circuit cannot produce an equivalent Clifford circuit: stim_circuit calls expand_clifford_rotations, but _try_clifford_expansion only expands tagged I instructions, so tagged SPP rotations are returned unchanged. For example, R_XX(0) 0 1 and R_XX(1) 0 1 are now reported Clifford, but the exported Stim circuit is still native SPP[...] X0*X1, which Stim interprets as the fixed sqrt-Pauli rotation instead of identity/XX.
Useful? React with 👍 / 👎.
Closes #142.
What this adds
Four new gates following the semantics of the clifft two-qubit Pauli rotations reference:
R_XXR_XX(alpha) q0 q1exp(-i alpha pi/2 XX)R_YYR_YY(alpha) q0 q1exp(-i alpha pi/2 YY)R_ZZR_ZZ(alpha) q0 q1exp(-i alpha pi/2 ZZ)R_PAULIR_PAULI(alpha) X0*Y1*Z2exp(-i alpha pi/2 P)for any Pauli product PR_XX,R_YY, andR_ZZare just the two-qubit special cases ofR_PAULI. All four accept arbitrary real angles, not just Clifford multiples.Implementation overview
Storage encoding
The gates reuse the existing
SPP/tag encoding thatTPPandTalready rely on. Each gate is stored as a stimSPPinstruction whose tag carries the gate name and angle:This means no new stim instructions are needed and the full existing infrastructure (tagging, canonicalization, round-trip) works without modification.
ZX graph backend:
r_pauliininstructions.pyA single new function
r_pauli(b, paulis, theta, dagger=False)handles all four gates. It reuses_pauli_product_phase, the same ladder-of-CNOTs decomposition already used bysppandtpp:r_z(theta)(or its negative) on the parity qubit.The
daggerflag negates theta before dispatch, which is the cleanest way to handleSPP_DAG-tagged instructions that come from stim when building an inverse circuit.Parse dispatch:
parse.py_PARAMETRIC_GATE_PARAMSgains entries for all four gate names. Theparse_stim_circuitfunction gets a new branch that fires before the existingSPP[T]branch:This correctly handles both
SPPandSPP_DAGforms, including inverted Pauli targets.Shorthand text:
program_text.pyshorthand_to_stimgains two new regex substitutions (applied before the single-qubitR_X/R_Y/R_Zrule to avoid partial matches):R_XX(alpha) q0 q1->SPP[R_XX(theta=alpha*pi)] Xq0*Xq1R_PAULI(alpha) X0*Y1*...->SPP[R_PAULI(theta=alpha*pi)] X0*Y1*...stim_to_shorthandgains matching back-conversion rules so thatstr(Circuit(...))always produces the human-readable shorthand. TheR_XX/R_YY/R_ZZrule fires before the generalR_PAULIrule so that a two-qubit same-axis product round-trips asR_ZZ(0.3) 0 1, not asR_PAULI(0.3) Z0*Z1.Duplicate qubit detection for
R_XX/R_YY/R_ZZis done at the regex replacement step, so the error is raised at circuit construction time rather than during later graph traversal.Inverse:
circuit.pyStim's own
.inverse()method already handlesSPPcorrectly by flipping it toSPP_DAGand reversing the target order, leaving the tag unchanged. Because ourparse.pydispatch treatsSPP_DAG[R_PAULI(theta)]asexp(+i theta pi/2 P), which is exactly the inverse ofexp(-i theta pi/2 P), stim's output is already correct. Thefix_tagsloop inCircuit.inverse()simply passes these instructions through without modification.circuit.pyappend APICircuit.appendgains branches for all four gate names. TheR_XX/R_YY/R_ZZbranch validates target count and duplicate qubits, builds the Pauli targets list, and re-encodes toSPP. TheR_PAULIbranch does the same without qubit validation since the caller provides raw Pauli targets directly.Clifford detection:
clifford.pyis_cliffordis extended to recognizeSPP/SPP_DAGinstructions tagged withR_PAULI/R_XX/R_YY/R_ZZ: if the theta is a half-pi multiple the gate is Clifford, otherwise it is not.Files changed
src/tsim/core/instructions.pyr_paulifunctionsrc/tsim/core/parse.pyr_pauli, extend_PARAMETRIC_GATE_PARAMS, add SPP dispatch branchsrc/tsim/utils/program_text.pysrc/tsim/circuit.pyappendbranches for all four gates,inversepass-throughsrc/tsim/utils/clifford.pyis_cliffordto cover SPP-encoded parametric rotationstest/unit/test_r_pauli_rotations.pyTests
91 new tests in
test/unit/test_r_pauli_rotations.pyorganized into six classes:TestShorthandRoundTrip-- text -> stim -> text stability for all four gate syntaxes, including negative angles, scientific notation, and multi-qubit products.TestAppendAPI--Circuit.appendfor all four gates, including error cases for missing angles, duplicate qubits, and wrong target counts.TestCliffordAngleParity-- at every integer alpha,R_PP(alpha) 0 1matches stim's reference tableau for the corresponding Clifford gate. Parametrized over all three two-qubit gates and alpha in {0, 1, 2, 3}.TestAnalyticCorrectness-- unitary matrices compared against closed-form formulas:R_XX(alpha) = cos(alpha pi/2) I - i sin(alpha pi/2) XXR_ZZ(alpha) = diag(e^{-i alpha pi/2}, e^{+i alpha pi/2}, e^{+i alpha pi/2}, e^{-i alpha pi/2})R_YYverified via S-gate conjugationR_PAULI Z0matchesR_Z, single-qubitR_PAULI X0matchesR_XTestInverse--(C + C.inverse()).to_matrix()is identity up to global phase for all four gates, including negative angles, non-Clifford angles, and theSPP_DAGform that comes out of stim's inverse.TestMixedCircuits-- composition with CNOT, three-qubit R_PAULI, back-to-back cancellation, REPEAT blocks, and mixed R_XX + R_ZZ circuits.All 841 previously passing tests still pass.
Usage examples