Skip to content
Draft
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
49 changes: 49 additions & 0 deletions src/qutip_qip/operations/decomposer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from qutip_qip.operations.gateclass import Gate

__all__ = ["resolve_decomposition", "fallback_decomposition"]

def resolve_decomposition(gate: Gate, basis_1q: list[str], basis_2q: list[str]) -> list[Gate]:
"""
Attempt to decompose a gate into the given basis.
"""
try:
return gate.decompose_to_basis(basis_1q, basis_2q)
except NotImplementedError:
return fallback_decomposition(gate, basis_1q, basis_2q)


# Optional graph-based fallback resolver
GATE_DECOMP_GRAPH = {
"MS": ["CNOT"],
"CNOT": ["ISWAP"],
# You can add more conversion steps here
}


def fallback_decomposition(gate: Gate, basis_1q: list[str], basis_2q: list[str]) -> list[Gate]:
from collections import deque

visited = set()
queue = deque([(gate.name, [])])

while queue:
current_gate, path = queue.popleft()
if current_gate in basis_2q:
# Reconstruct intermediate decompositions
decomposed = gate
for intermediate in path:
decomposed = decomposed.decompose_to_basis(basis_1q, [intermediate])
decomposed = sum(
(g.decompose_to_basis(basis_1q, basis_2q) for g in decomposed),
start=[]
)
return decomposed

for neighbor in GATE_DECOMP_GRAPH.get(current_gate, []):
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))

raise NotImplementedError(
f"No valid decomposition path from {gate.name} to basis {basis_2q}"
)
18 changes: 18 additions & 0 deletions src/qutip_qip/operations/gateclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,15 @@ def get_qobj(self, num_qubits=None, dims=None):
dims=dims,
targets=all_targets,
)
def decompose_to_basis(self, basis_1q: list[str], basis_2q: list[str]) -> list["Gate"]:
"""
Return a decomposition of this gate into the given basis.
This default method raises an error unless overridden in subclasses.
"""
raise NotImplementedError(
f"Decomposition not implemented for {self.name} to basis {basis_2q}"
)



class SingleQubitGate(Gate):
Expand Down Expand Up @@ -580,6 +589,14 @@ def __init__(self, targets, **kwargs):

def get_compact_qobj(self):
return snot()

def decompose_to_basis(self, basis_1q, basis_2q):
if "RX" in basis_1q and "RY" in basis_1q:
return [
RY(self.targets[0], arg_value=np.pi / 2, arg_label=r"\pi/2"),
RX(self.targets[0], arg_value=np.pi, arg_label=r"\pi")
]
raise NotImplementedError("Cannot decompose Hadamard to given basis")


SNOT = H
Expand All @@ -606,6 +623,7 @@ def __init__(self, targets, **kwargs):

def get_compact_qobj(self):
return sqrtnot()



class S(SingleQubitGate):
Expand Down
14 changes: 14 additions & 0 deletions tests/test_dec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import numpy as np
from qutip_qip.operations import H, RX, RY
from qutip_qip.operations.decomposer import resolve_decomposition


def test_hadamard_decomposition():
gate = H(0)
decomposed = resolve_decomposition(gate, basis_1q=["RX", "RY"], basis_2q=["CNOT"])

assert len(decomposed) == 2
assert isinstance(decomposed[0], RY)
assert isinstance(decomposed[1], RX)
np.testing.assert_allclose(decomposed[0].arg_value, np.pi / 2)
np.testing.assert_allclose(decomposed[1].arg_value, np.pi)
Loading