Skip to content

Add Circuit.is_clifford property and replace U3 gates with Clifford equivalents for pi/2 rotations#69

Open
rafaelha wants to merge 1 commit intomainfrom
rafaelha/is_clifford_property
Open

Add Circuit.is_clifford property and replace U3 gates with Clifford equivalents for pi/2 rotations#69
rafaelha wants to merge 1 commit intomainfrom
rafaelha/is_clifford_property

Conversation

@rafaelha
Copy link
Collaborator

@rafaelha rafaelha commented Mar 10, 2026

Closes #68

Implements a new property Circuit.is_clifford. Additionally, Circuit.stim_circuit now returns a Stim circuit where rotation gates have been replaced with equivalent Clifford gates wherever possible.

@github-actions
Copy link
Contributor

☂️ Python Coverage

current status: ✅

Overall Coverage

Lines Covered Coverage Threshold Status
1997 1940 97% 0% 🟢

New Files

File Coverage Status
src/tsim/utils/clifford.py 100% 🟢
TOTAL 100% 🟢

Modified Files

File Coverage Status
src/tsim/circuit.py 99% 🟢
TOTAL 99% 🟢

updated for commit: b2da63f by action🐍

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Clifford-detection and Clifford-expansion support to tsim.Circuit to enable fast stabilizer-style workflows when circuits are Clifford-only (or have parametric gates reducible to Clifford gates).

Changes:

  • Introduces Circuit.is_clifford to classify circuits containing only Clifford operations (including half-π parametric rotations).
  • Updates Circuit.stim_circuit to expand half-π R_*/U3 parametric tags into equivalent stim Clifford gate sequences.
  • Adds unit tests validating conversion tables, unitary equivalence, and the new stim_circuit/is_clifford behavior.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/tsim/circuit.py Implements stim_circuit expansion pass and the new is_clifford property.
src/tsim/utils/clifford.py Adds mapping tables and conversion helper for half-π parametric gates → stim Clifford gates.
test/unit/test_circuit.py Adds coverage for Circuit.is_clifford across Clifford and non-Clifford examples.
test/unit/utils/test_clifford.py Adds unitary-level tests validating parametric-to-Clifford conversions and stim_circuit expansion behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +285 to +290
if not is_half_pi_multiple(params["theta"]):
return False
elif gate_name == "U3":
if not all(
is_half_pi_multiple(params[name])
for name in ("theta", "phi", "lambda")
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_clifford can raise KeyError on malformed-but-parseable parametric tags (e.g. I[R_Z(phi=0.5*pi)]), because parse_parametric_tag doesn't validate parameter names but this code unconditionally indexes params["theta"]/params["phi"]/params["lambda"]. Since this is a boolean probe, it should return False instead of throwing; consider guarding missing keys (or reusing parametric_to_clifford_gates and treating failures as non-Clifford).

Suggested change
if not is_half_pi_multiple(params["theta"]):
return False
elif gate_name == "U3":
if not all(
is_half_pi_multiple(params[name])
for name in ("theta", "phi", "lambda")
theta = params.get("theta")
if theta is None or not is_half_pi_multiple(theta):
return False
elif gate_name == "U3":
required_params = ("theta", "phi", "lambda")
if not all(
(name in params) and is_half_pi_multiple(params[name])
for name in required_params

Copilot uses AI. Check for mistakes.
Comment on lines +261 to +293
A circuit is a Clifford circuit if it only contains Clifford gates (i.e. half-pi
multiples of the rotation angles).

Returns:
True if the circuit is a Clifford circuit, otherwise False.

"""

def is_half_pi_multiple(phase: Fraction) -> bool:
return phase.denominator <= 2

for instr in self._stim_circ:
assert not isinstance(instr, stim.CircuitRepeatBlock)

if instr.name in {"S", "S_DAG"} and instr.tag == "T":
return False

if instr.name == "I" and instr.tag:
result = parse_parametric_tag(instr.tag)
if result is None:
return False

gate_name, params = result
if gate_name in {"R_X", "R_Y", "R_Z"}:
if not is_half_pi_multiple(params["theta"]):
return False
elif gate_name == "U3":
if not all(
is_half_pi_multiple(params[name])
for name in ("theta", "phi", "lambda")
):
return False
else:
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_clifford currently only checks that U3/R_* parameters are half-π multiples (via denominator<=2). To keep this property consistent with stim_circuit’s actual expansion logic (and avoid reporting True while leaving a tagged I[...] unexpanded), consider determining Clifford-ness by calling parametric_to_clifford_gates(gate_name, params) and requiring a non-None result.

Suggested change
A circuit is a Clifford circuit if it only contains Clifford gates (i.e. half-pi
multiples of the rotation angles).
Returns:
True if the circuit is a Clifford circuit, otherwise False.
"""
def is_half_pi_multiple(phase: Fraction) -> bool:
return phase.denominator <= 2
for instr in self._stim_circ:
assert not isinstance(instr, stim.CircuitRepeatBlock)
if instr.name in {"S", "S_DAG"} and instr.tag == "T":
return False
if instr.name == "I" and instr.tag:
result = parse_parametric_tag(instr.tag)
if result is None:
return False
gate_name, params = result
if gate_name in {"R_X", "R_Y", "R_Z"}:
if not is_half_pi_multiple(params["theta"]):
return False
elif gate_name == "U3":
if not all(
is_half_pi_multiple(params[name])
for name in ("theta", "phi", "lambda")
):
return False
else:
A circuit is a Clifford circuit if it only contains Clifford gates.
Returns:
True if the circuit is a Clifford circuit, otherwise False.
"""
for instr in self._stim_circ:
assert not isinstance(instr, stim.CircuitRepeatBlock)
# S / S_DAG tagged as T are treated as non-Clifford.
if instr.name in {"S", "S_DAG"} and instr.tag == "T":
return False
# Parametric single-qubit gates are encoded as tagged identity operations.
if instr.name == "I" and instr.tag:
result = parse_parametric_tag(instr.tag)
if result is None:
# Unrecognized parametric tag; conservatively treat as non-Clifford.
return False
gate_name, params = result
# Delegate Clifford-ness to the same logic used for expansion.
expanded = parametric_to_clifford_gates(gate_name, params)
if expanded is None:

Copilot uses AI. Check for mistakes.
gates = U3_CLIFFORD.get(key)
if gates is None:
gates = U3_CLIFFORD.get(_equivalent_u3_key(*key))
assert gates is not None
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parametric_to_clifford_gates uses assert gates is not None for the U3 mapping. Asserts can be stripped with Python optimizations (-O), which would turn this into a less clear failure later (e.g., attempting list(None)). Prefer an explicit runtime check that returns None (or raises a descriptive exception) when a mapping is missing.

Suggested change
assert gates is not None
if gates is None:
raise KeyError(f"No Clifford decomposition found for U3 key {key!r}.")

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://queracomputing.github.io/tsim/pr-preview/pr-69/

Built to branch gh-pages at 2026-03-10 20:07 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

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.

Switch to stabilizer simulator for Clifford-only circuits

2 participants