Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ classifiers = [
]
dependencies = [
"cirq-core>=1.4.1,<2.0.0",
"nwqec>=0.1.1",
"ply>=3.11,<4.0.0",
"pytket>=1.40.0,<3.0.0",
"qbraid>=0.9.3,<1.0.0",
Expand Down
32 changes: 32 additions & 0 deletions ucc/tests/test_compile.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
from ucc import compile
from ucc.transpilers.ucc_defaults import UCCDefault1
from ucc.transpilers.aqc.mps_pass import MPSPass
from ucc.transpilers.ucc_nwqec import (
CliffordTPass,
CliffordReduction,
is_clifford_or_t,
)
import numpy as np


Expand Down Expand Up @@ -644,3 +649,30 @@ def test_compiled_circuits_equivalent(circuit_function, num_qubits, seed):
sv1 = Statevector(circuit)
sv2 = Statevector(transpiled)
assert sv1.equiv(sv2)


def test_compile_clifford_t_pass():
circuit = qcnn_circuit(5, seed=42)
compiled_circuit = compile(circuit, custom_passes=[CliffordTPass()])

fidelity = np.abs(
np.vdot(Statevector(circuit).data, Statevector(compiled_circuit).data)
)

# Ensure fidelity is large, but not perfect since its approximate
assert np.abs(fidelity) > 0.95

for op in set(op.name for op in compiled_circuit):
assert is_clifford_or_t(op), f"Found non-Clifford+T gate: {op}"


def test_compile_clifford_reduction_pass():
circuit = qcnn_circuit(5, seed=42)
compiled_circuit = compile(circuit, custom_passes=[CliffordReduction()])

fidelity = np.abs(
np.vdot(Statevector(circuit).data, Statevector(compiled_circuit).data)
)

# Ensure fidelity is large, but not perfect since its approximate
assert np.abs(fidelity) > 0.95
103 changes: 103 additions & 0 deletions ucc/transpilers/ucc_nwqec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import tempfile
import nwqec
import qiskit
from qiskit.qasm2 import dump, loads
from qiskit.qasm2 import LEGACY_CUSTOM_INSTRUCTIONS


def is_clifford_or_t(gate_name: str) -> bool:
"""Check if a gate is a Clifford or T gate using qiskit naming conventions."""
clifford_gates = {
"x",
"y",
"z",
"h",
"sx",
"sxdg",
"s",
"sdg",
"cx",
"cz",
"swap",
"id",
}
t_gates = {"t", "tdg"}
return gate_name in clifford_gates or gate_name in t_gates


class CliffordTPass(qiskit.transpiler.basepasses.TransformationPass):
"""
A transpiler pass that applies NWQEC's Clifford+T translation to a Qiskit DAGCircuit, lowering
the circuit to Clifford+T form.
"""

def __init__(
self,
keep_ccx: bool = False,
epsilon: float | None = None,
):
"""
Args:
keep_ccx: preserve CCX gates when True.
epsilon: absolute error tolerance for RZ synthesis;
defaults to abs(theta) * nwqec.DEFAULT_EPSILON_MULTIPLIER per angle.
"""
super().__init__()
self._keep_ccx = keep_ccx
self._epsilon = epsilon

def run(self, dag):
circuit = qiskit.converters.dag_to_circuit(dag)

with tempfile.NamedTemporaryFile(suffix=".qasm") as tmp:
tmp_qasm = tmp.name
dump(circuit, tmp_qasm)
nwqec_circuit = nwqec.load_qasm(tmp_qasm)

nwqec_circuit = nwqec.to_clifford_t(
nwqec_circuit, epsilon=self._epsilon, keep_ccx=self._keep_ccx
)
return qiskit.converters.circuit_to_dag(
loads(
nwqec_circuit.to_qasm(),
custom_instructions=LEGACY_CUSTOM_INSTRUCTIONS,
)
)


class CliffordReduction(qiskit.transpiler.basepasses.TransformationPass):
"""
A transpiler pass that applies NWQEC's Clifford Reduction optimization to a Qiskit DAGCircuit.
Applies the Clifford reduction optimization (preserves parallelism while reducing non-T overhead).
Based on the technique from Wang et al. "Optimizing FTQC Programs through QEC Transpiler and Architecture Codesign" (2024).
"""

def __init__(
self,
epsilon: float | None = None,
):
"""
Args:
epsilon: absolute error tolerance for RZ synthesis;
defaults to abs(theta) * nwqec.DEFAULT_EPSILON_MULTIPLIER per angle.
"""
super().__init__()
self._epsilon = epsilon

def run(self, dag):
circuit = qiskit.converters.dag_to_circuit(dag)

with tempfile.NamedTemporaryFile(suffix=".qasm") as tmp:
tmp_qasm = tmp.name
dump(circuit, tmp_qasm)
nwqec_circuit = nwqec.load_qasm(tmp_qasm)

nwqec_circuit = nwqec.to_clifford_reduction(
nwqec_circuit, epsilon=self._epsilon
)
return qiskit.converters.circuit_to_dag(
loads(
nwqec_circuit.to_qasm(),
custom_instructions=LEGACY_CUSTOM_INSTRUCTIONS,
)
)
14 changes: 14 additions & 0 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.