"""Helper gates for circuit knitting."""
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Gate, Instruction, QuantumRegister
from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.dagcircuit import DAGCircuit
from qiskit.transpiler import PassManager
from qiskit.transpiler.basepasses import TransformationPass
from qiskit.transpiler.passes import BasisTranslator, UnrollCustomDefinitions
# define the cut location marker
cut_op = QuantumCircuit(1, name="Cut")
cut_op = cut_op.to_instruction(label="Cut")
cut_op.definition = None
[docs]
def cut() -> QuantumCircuit | Instruction:
"""Return a single qubit wire cut instruction."""
return cut_op
cutCZ_op = QuantumCircuit(2, name="CutCZ")
cutCZ_op = cutCZ_op.to_instruction(label="CutCZ")
cutCZ_op.definition = None
cutSWAP_op = QuantumCircuit(2, name="CutSWAP")
cutSWAP_op = cutSWAP_op.to_instruction(label="CutSWAP")
cutSWAP_op.definition = None
cutISWAP_op = QuantumCircuit(2, name="CutISWAP")
cutISWAP_op = cutISWAP_op.to_instruction(label="CutISWAP")
cutISWAP_op.definition = None
[docs]
def cutCZ() -> QuantumCircuit | Instruction:
"""Return a two qubit cutCZ gate instruction."""
return cutCZ_op
[docs]
def cutSWAP() -> QuantumCircuit | Instruction:
"""Return a two qubit cutSWAP gate instruction."""
return cutSWAP_op
[docs]
def cutISWAP() -> QuantumCircuit | Instruction:
"""Return a two qubit cutISWAP gate instruction."""
return cutISWAP_op
QPD_DECOMPOSITION_SQ_BASIS: list[str] = ["u"]
QPD_GATE_REGISTRY: dict[str, Instruction] = {
"cz": cutCZ_op,
"swap": cutSWAP_op,
"iswap": cutISWAP_op,
}
class _ReplaceWithCutGates(TransformationPass):
def __init__(self, registry: dict[str, Instruction]):
self.registry = registry
super().__init__()
def run(self, dag: DAGCircuit) -> DAGCircuit:
for node in dag.op_nodes():
if node.op.name not in self.registry:
continue
cut_op = self.registry[node.op.name]
mini_dag = DAGCircuit()
qreg = QuantumRegister(node.op.num_qubits)
mini_dag.add_qreg(qreg)
mini_dag.apply_operation_back(cut_op, list(qreg))
dag.substitute_node_with_dag(node, mini_dag)
return dag
[docs]
def cutGate(
gate: Gate,
control: int | list[int],
target: int | list[int],
single_qubit_basis: list[str] | None = None,
) -> dict:
"""Return a CutGate circuit decomposing the input gate with QPD cut markers.
control and target together define the ordered physical qubit mapping for
gate inputs 0..n-1: gate input i maps to physical qubit (controls + targets)[i].
"""
controls = [control] if isinstance(control, int) else list(control)
targets = [target] if isinstance(target, int) else list(target)
all_qargs = controls + targets
if gate.num_qubits < 2:
raise ValueError("Input gate must have at least 2 qubits.")
if len(all_qargs) != gate.num_qubits:
raise ValueError(
f"Expected {gate.num_qubits} qubit arguments, got {len(all_qargs)}."
)
if len(set(all_qargs)) != len(all_qargs):
raise ValueError("Qubit arguments must be unique.")
if any(q < 0 for q in all_qargs):
raise ValueError("Qubit arguments must be non-negative.")
qc = QuantumCircuit(gate.num_qubits, name=f"cut{gate.name.upper()}")
qc.append(gate, list(range(gate.num_qubits)))
sq_basis = (
single_qubit_basis
if single_qubit_basis is not None
else QPD_DECOMPOSITION_SQ_BASIS
)
basis = sq_basis + list(QPD_GATE_REGISTRY.keys())
tr = PassManager(
[
UnrollCustomDefinitions(SessionEquivalenceLibrary, basis_gates=basis),
BasisTranslator(SessionEquivalenceLibrary, basis),
_ReplaceWithCutGates(QPD_GATE_REGISTRY),
]
).run(qc)
return {
"instruction": tr.to_instruction(label="CutGate"),
"qargs": all_qargs,
}
# define measurements for different bases
xmeas = QuantumCircuit(1, 1, name="x-meas")
xmeas.h(0)
xmeas.measure(0, 0)
xmeas.h(0)
ymeas = QuantumCircuit(1, 1, name="y-meas")
ymeas.sdg(0)
ymeas.h(0)
ymeas.measure(0, 0)
ymeas.h(0)
ymeas.s(0)
idmeas = QuantumCircuit(1, name="id-meas")
idmeas.id(0)
zmeas = QuantumCircuit(1, 1, name="z-meas")
zmeas.measure(0, 0)
# define initialization operations
zero_init = QuantumCircuit(1, name="0-init")
zero_init.id(0)
one_init = QuantumCircuit(1, name="1-init")
one_init.x(0)
plus_init = QuantumCircuit(1, name="'+'-init")
plus_init.h(0)
minus_init = QuantumCircuit(1, name="'-'-init")
minus_init.h(0)
minus_init.z(0)
i_plus_init = QuantumCircuit(1, name="'i+'-init")
i_plus_init.h(0)
i_plus_init.s(0)
i_minus_init = QuantumCircuit(1, name="'i-'-init")
i_minus_init.h(0)
i_minus_init.z(0)
i_minus_init.s(0)
sdg_meas = QuantumCircuit(1, 1, name="sdg_meas")
sdg_meas.sdg(0)
sdg_meas.measure(0, 0)
i_gate = QuantumCircuit(1, name="I")
i_gate.id(0)
x_gate = QuantumCircuit(1, name="X")
x_gate.x(0)
y_gate = QuantumCircuit(1, name="Y")
y_gate.y(0)
z_gate = QuantumCircuit(1, name="Z")
z_gate.z(0)
axyp = QuantumCircuit(1, name="Axyp")
axyp.y(0)
axyp.s(0)
axym = QuantumCircuit(1, name="Axym")
axym.y(0)
axym.s(0)
axym.z(0)
ayzp = QuantumCircuit(1, name="Ayzp")
ayzp.z(0)
ayzp.sx(0)
ayzm = QuantumCircuit(1, name="Ayzm")
ayzm.z(0)
ayzm.sx(0)
ayzm.x(0)
azxp = QuantumCircuit(1, name="Azxp")
azxp.h(0)
azxm = QuantumCircuit(1, name="Azxm")
azxm.h(0)
azxm.y(0)
sx_gate = QuantumCircuit(1, name="SX")
sx_gate.sx(0)
sxdg_gate = QuantumCircuit(1, name="SXdg")
sxdg_gate.sxdg(0)
ry_p = QuantumCircuit(1, name="RY+")
ry_p.ry(np.pi / 2, 0)
ry_m = QuantumCircuit(1, name="RY-")
ry_m.ry(-np.pi / 2, 0)
s_gate = QuantumCircuit(1, name="S")
s_gate.s(0)
sdg_gate = QuantumCircuit(1, name="Sdg")
sdg_gate.sdg(0)
bxy = QuantumCircuit(1, 1, name="Bxy")
bxy.measure(0, 0)
bxy.x(0)
byz = QuantumCircuit(1, 1, name="Byz")
byz.h(0)
byz.measure(0, 0)
byz.h(0)
byz.y(0)
bzx = QuantumCircuit(1, 1, name="Bzx")
bzx.sx(0)
bzx.measure(0, 0)
bzx.sxdg(0)
bzx.z(0)