Source code for deepquantum.qpd

"""Quasiprobability-decomposition gates"""

from torch import nn

from .gate import Hadamard, PauliX, SDaggerGate, SGate
from .operation import GateQPD, MeasureQPD


[docs] class SingleGateQPD(GateQPD): r"""A base class for single-qubit QPD gates. Args: bases: The probabilistic basis operations for the decomposition. A nested structure where: - `bases[i]` is the i-th term of the QPD (corresponding to `coeffs[i]`). - `bases[i][j]` is the `nn.Sequential` operations applied to the j-th qubit. coeffs: The coefficients for quasiprobability representation. label: The label of the gate. Default: ``None`` name: The name of the quantum operation. Default: ``None`` nqubit: The number of qubits that the quantum operation acts on. Default: 1 wires: The indices of the qubits that the quantum operation acts on. Default: ``None`` den_mat: Whether the quantum operation acts on density matrices or state vectors. Default: ``False`` (which means state vectors) tsr_mode: Whether the quantum operation is in tensor mode, which means the input and output are represented by a tensor of shape :math:`(\text{batch}, 2, ..., 2)`. Default: ``False`` """ def __init__( self, bases: 'nn.ModuleList[nn.ModuleList[nn.Sequential]]', coeffs: list[float], label: int | None = None, name: str | None = None, nqubit: int = 1, wires: list[int] | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: if wires is None: wires = [0] assert len(wires) == 1 for basis in bases: assert len(basis) == 1 super().__init__( bases=bases, coeffs=coeffs, label=label, name=name, nqubit=nqubit, wires=wires, den_mat=den_mat, tsr_mode=tsr_mode, )
[docs] class DoubleGateQPD(GateQPD): r"""A base class for two-qubit QPD gates. Args: bases: The probabilistic basis operations for the decomposition. A nested structure where: - `bases[i]` is the i-th term of the QPD (corresponding to `coeffs[i]`). - `bases[i][j]` is the `nn.Sequential` operations applied to the j-th qubit. coeffs: The coefficients for quasiprobability representation. label: The label of the gate. Default: ``None`` name: The name of the quantum operation. Default: ``None`` nqubit: The number of qubits that the quantum operation acts on. Default: 2 wires: The indices of the qubits that the quantum operation acts on. Default: ``None`` den_mat: Whether the quantum operation acts on density matrices or state vectors. Default: ``False`` (which means state vectors) tsr_mode: Whether the quantum operation is in tensor mode, which means the input and output are represented by a tensor of shape :math:`(\text{batch}, 2, ..., 2)`. Default: ``False`` """ def __init__( self, bases: 'nn.ModuleList[nn.ModuleList[nn.Sequential]]', coeffs: list[float], label: int | None = None, name: str | None = None, nqubit: int = 2, wires: list[int] | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: if wires is None: wires = [0, 1] assert len(wires) == 2 for basis in bases: assert len(basis) == 2 super().__init__( bases=bases, coeffs=coeffs, label=label, name=name, nqubit=nqubit, wires=wires, den_mat=den_mat, tsr_mode=tsr_mode, )
[docs] def decompose(self) -> tuple[SingleGateQPD, SingleGateQPD]: """Decompose the gate into two single-qubit QPD gates.""" bases1 = nn.ModuleList([]) bases2 = nn.ModuleList([]) for basis in self.bases: bases1.append(nn.ModuleList([basis[0]])) bases2.append(nn.ModuleList([basis[1]])) name = self.name + f'_label{self.label}_' gate1 = SingleGateQPD( bases1, self.coeffs, self.label, name + '1', self.nqubit, [self.wires[0]], self.den_mat, self.tsr_mode ) gate2 = SingleGateQPD( bases2, self.coeffs, self.label, name + '2', self.nqubit, [self.wires[1]], self.den_mat, self.tsr_mode ) return gate1, gate2
[docs] class MoveQPD(DoubleGateQPD): r"""QPD representation of the move operation. Args: nqubit: The number of qubits that the quantum operation acts on. Default: 2 wires: The indices of the qubits that the quantum operation acts on. Default: ``None`` label: The label of the gate. Default: ``None`` den_mat: Whether the quantum operation acts on density matrices or state vectors. Default: ``False`` (which means state vectors) tsr_mode: Whether the quantum operation is in tensor mode, which means the input and output are represented by a tensor of shape :math:`(\text{batch}, 2, ..., 2)`. Default: ``False`` """ def __init__( self, nqubit: int = 2, wires: list[int] | None = None, label: int | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: if wires is None: wires = [0, 1] h1 = Hadamard(nqubit=nqubit, wires=wires[0], den_mat=den_mat, tsr_mode=True) m1 = MeasureQPD(nqubit=nqubit, wires=wires[0]) sdg1 = SDaggerGate(nqubit=nqubit, wires=wires[0], den_mat=den_mat, tsr_mode=True) x2 = PauliX(nqubit=nqubit, wires=wires[1], den_mat=den_mat, tsr_mode=True) h2 = Hadamard(nqubit=nqubit, wires=wires[1], den_mat=den_mat, tsr_mode=True) s2 = SGate(nqubit=nqubit, wires=wires[1], den_mat=den_mat, tsr_mode=True) measure_i = nn.Sequential() measure_x = nn.Sequential(h1, m1) measure_y = nn.Sequential(sdg1, h1, m1) measure_z = nn.Sequential(m1) prep_0 = nn.Sequential() prep_1 = nn.Sequential(x2) prep_plus = nn.Sequential(h2) prep_minus = nn.Sequential(x2, h2) prep_iplus = nn.Sequential(h2, s2) prep_iminus = nn.Sequential(x2, h2, s2) bases = nn.ModuleList( [ nn.ModuleList([measure_i, prep_0]), nn.ModuleList([measure_i, prep_1]), nn.ModuleList([measure_x, prep_plus]), nn.ModuleList([measure_x, prep_minus]), nn.ModuleList([measure_y, prep_iplus]), nn.ModuleList([measure_y, prep_iminus]), nn.ModuleList([measure_z, prep_0]), nn.ModuleList([measure_z, prep_1]), ] ) coeffs = [0.5, 0.5, 0.5, -0.5, 0.5, -0.5, 0.5, -0.5] super().__init__( bases=bases, coeffs=coeffs, label=label, name='MoveQPD', nqubit=nqubit, wires=wires, den_mat=den_mat, tsr_mode=tsr_mode, )