Source code for deepquantum.operation

"""Base classes for quantum operations"""

from copy import copy
from typing import Any

import numpy as np
import torch
from torch import nn, vmap

from .distributed import dist_many_targ_gate
from .qmath import evolve_den_mat, evolve_state, inverse_permutation, state_to_tensors
from .state import DistributedQubitState, MatrixProductState
from .utils import apply_complex_fix


[docs] class Operation(nn.Module): r"""A base class for quantum operations. Args: 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, name: str | None = None, nqubit: int = 1, wires: int | list[int] | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: super().__init__() self.name = name self.nqubit = nqubit self.wires = wires self.den_mat = den_mat self.tsr_mode = tsr_mode self.npara = 0
[docs] def tensor_rep(self, x: torch.Tensor) -> torch.Tensor: """Get the tensor representation of the state.""" if self.den_mat: assert x.shape[-1] == x.shape[-2] == 2**self.nqubit return x.reshape([-1] + [2] * 2 * self.nqubit) else: if x.ndim == 1: assert x.shape[-1] == 2**self.nqubit else: assert x.shape[-1] == 2**self.nqubit or x.shape[-2] == 2**self.nqubit return x.reshape([-1] + [2] * self.nqubit)
[docs] def vector_rep(self, x: torch.Tensor) -> torch.Tensor: """Get the vector representation of the state.""" return x.reshape(-1, 2**self.nqubit, 1)
[docs] def matrix_rep(self, x: torch.Tensor) -> torch.Tensor: """Get the density matrix representation of the state.""" return x.reshape(-1, 2**self.nqubit, 2**self.nqubit)
[docs] def get_unitary(self) -> torch.Tensor: """Get the global unitary matrix.""" raise NotImplementedError
[docs] def init_para(self) -> None: """Initialize the parameters.""" pass
[docs] def set_nqubit(self, nqubit: int) -> None: """Set the number of qubits of the ``Operation``.""" self.nqubit = nqubit
[docs] def set_wires(self, wires: int | list[int]) -> None: """Set the wires of the ``Operation``.""" self.wires = self._convert_indices(wires)
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor: """Perform a forward pass.""" if self.tsr_mode: return self.tensor_rep(x) else: if self.den_mat: return self.matrix_rep(x) else: return self.vector_rep(x)
def _convert_indices(self, indices: int | list[int]) -> list[int]: """Convert and check the indices of the qubits.""" if isinstance(indices, int): indices = [indices] assert isinstance(indices, list), 'Invalid input type' assert all(isinstance(i, int) for i in indices), 'Invalid input type' if len(indices) > 0: assert min(indices) > -1 and max(indices) < self.nqubit, 'Invalid input' assert len(set(indices)) == len(indices), 'Invalid input' return indices def _check_minmax(self, minmax: list[int]) -> None: """Check the minimum and maximum indices of the qubits.""" assert isinstance(minmax, list) assert len(minmax) == 2 assert all(isinstance(i, int) for i in minmax) assert -1 < minmax[0] <= minmax[1] < self.nqubit
[docs] class Gate(Operation): r"""A base class for quantum gates. Args: name: The name of the gate. 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`` controls: The indices of the control qubits. Default: ``None`` condition: Whether to use ``controls`` as conditional measurement. Default: ``False`` 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`` """ # include default names in QASM _qasm_new_gate = ['c3x', 'c4x'] def __init__( self, name: str | None = None, nqubit: int = 1, wires: int | list[int] | None = None, controls: int | list[int] | None = None, condition: bool = False, den_mat: bool = False, tsr_mode: bool = False, ) -> None: self.nqubit = nqubit if wires is None: wires = [0] if controls is None: controls = [] wires = self._convert_indices(wires) controls = self._convert_indices(controls) for wire in wires: assert wire not in controls, 'Use repeated wires' if condition: assert len(controls) > 0 super().__init__(name=name, nqubit=nqubit, wires=wires, den_mat=den_mat, tsr_mode=tsr_mode) self.controls = controls self.condition = condition # MBQC self.nodes = self.wires self.nancilla = 1 def _apply(self, fn: Any) -> 'Gate': if self.npara > 0: super()._apply(fn) elif self.npara == 0: tensors_dict = {} name = 'matrix' tensor = self._buffers.pop(name) if name in self._buffers else None # Consider Reset and Barrier if tensor is not None: tensors_dict[name] = tensor super()._apply(fn) corrected = apply_complex_fix(fn, tensors_dict) for key, value in corrected.items(): self.register_buffer(key, value) return self
[docs] def set_controls(self, controls: int | list[int]) -> None: """Set the control wires of the ``Operation``.""" self.controls = self._convert_indices(controls)
[docs] def get_matrix(self, inputs: Any) -> torch.Tensor: """Get the local unitary matrix.""" return self.matrix
[docs] def update_matrix(self) -> torch.Tensor: """Update the local unitary matrix.""" return self.matrix
def _real_wrapper(self, x: Any) -> torch.Tensor: mat = self.get_matrix(x) return torch.view_as_real(mat)
[docs] def get_derivative(self, inputs: Any) -> torch.Tensor: """Get the derivative of the local unitary matrix.""" return torch.zeros_like(self.matrix)
[docs] def op_state(self, x: torch.Tensor) -> torch.Tensor: """Perform a forward pass for state vectors.""" matrix = self.update_matrix() x = self.op_state_base(x, matrix) if self.controls == [] else self.op_state_control(x, matrix) if not self.tsr_mode: x = self.vector_rep(x).squeeze(0) return x
[docs] def op_state_base(self, x: torch.Tensor, matrix: torch.Tensor) -> torch.Tensor: """Perform a forward pass of a gate for state vectors.""" return evolve_state(x, matrix, self.nqubit, self.wires)
[docs] def op_state_control(self, x: torch.Tensor, matrix: torch.Tensor) -> torch.Tensor: """Perform a forward pass of a controlled gate for state vectors.""" nt = len(self.wires) nc = len(self.controls) wires = [i + 1 for i in self.wires] controls = [i + 1 for i in self.controls] pm_shape = list(range(self.nqubit + 1)) for i in wires: pm_shape.remove(i) for i in controls: pm_shape.remove(i) pm_shape = wires + pm_shape + controls x = x.permute(pm_shape).reshape(2**nt, -1, 2**nc) x = torch.cat([x[:, :, :-1], (matrix @ x[:, :, -1]).unsqueeze(-1)], dim=-1) x = x.reshape([2] * nt + [-1] + [2] * (self.nqubit - nt - nc) + [2] * nc) x = x.permute(inverse_permutation(pm_shape)) return x
[docs] def op_den_mat(self, x: torch.Tensor) -> torch.Tensor: """Perform a forward pass for density matrices.""" matrix = self.update_matrix() x = self.op_den_mat_base(x, matrix) if self.controls == [] else self.op_den_mat_control(x, matrix) if not self.tsr_mode: x = self.matrix_rep(x).squeeze(0) return x
[docs] def op_den_mat_base(self, x: torch.Tensor, matrix: torch.Tensor) -> torch.Tensor: """Perform a forward pass of a gate for density matrices.""" return evolve_den_mat(x, matrix, self.nqubit, self.wires)
[docs] def op_den_mat_control(self, x: torch.Tensor, matrix: torch.Tensor) -> torch.Tensor: """Perform a forward pass of a controlled gate for density matrices.""" nt = len(self.wires) nc = len(self.controls) # left multiply wires = [i + 1 for i in self.wires] controls = [i + 1 for i in self.controls] pm_shape = list(range(2 * self.nqubit + 1)) for i in wires: pm_shape.remove(i) for i in controls: pm_shape.remove(i) pm_shape = wires + pm_shape + controls x = x.permute(pm_shape).reshape(2**nt, -1, 2**nc) x = torch.cat([x[:, :, :-1], (matrix @ x[:, :, -1]).unsqueeze(-1)], dim=-1) x = x.reshape([2] * nt + [-1] + [2] * (2 * self.nqubit - nt - nc) + [2] * nc) x = x.permute(inverse_permutation(pm_shape)) # right multiply wires = [i + 1 + self.nqubit for i in self.wires] controls = [i + 1 + self.nqubit for i in self.controls] pm_shape = list(range(2 * self.nqubit + 1)) for i in wires: pm_shape.remove(i) for i in controls: pm_shape.remove(i) pm_shape = wires + pm_shape + controls x = x.permute(pm_shape).reshape(2**nt, -1, 2**nc) x = torch.cat([x[:, :, :-1], (matrix.conj() @ x[:, :, -1]).unsqueeze(-1)], dim=-1) x = x.reshape([2] * nt + [-1] + [2] * (2 * self.nqubit - nt - nc) + [2] * nc) x = x.permute(inverse_permutation(pm_shape)) return x
[docs] def op_dist_state(self, x: DistributedQubitState) -> DistributedQubitState: """Perform a forward pass of a gate for a distributed state vector.""" wires = self.controls + self.wires matrix = self.update_matrix() identity = matrix.new_ones(2 ** len(wires) - 2 ** len(self.wires)).diag_embed() unitary = torch.block_diag(identity, matrix) targets = [self.nqubit - wire - 1 for wire in wires] return dist_many_targ_gate(x, targets, unitary)
[docs] def forward( self, x: torch.Tensor | MatrixProductState | DistributedQubitState ) -> torch.Tensor | MatrixProductState | DistributedQubitState: """Perform a forward pass.""" if isinstance(x, MatrixProductState): return self.op_mps(x) elif isinstance(x, DistributedQubitState): return self.op_dist_state(x) if not self.tsr_mode: x = self.tensor_rep(x) if self.den_mat: assert x.ndim == 2 * self.nqubit + 1 return self.op_den_mat(x) else: assert x.ndim == self.nqubit + 1 return self.op_state(x)
[docs] def inverse(self) -> 'Gate': """Get the inversed gate.""" return self
[docs] def qpd(self, label: int | None = None) -> 'Gate': """Get the quasiprobability-decomposition representation.""" return self
[docs] def extra_repr(self) -> str: s = f'wires={self.wires}' if self.controls == []: return s else: return s + f', controls={self.controls}'
@staticmethod def _reset_qasm_new_gate() -> None: Gate._qasm_new_gate = ['c3x', 'c4x'] def _qasm_cond_measure(self) -> str: qasm_str = '' for control in self.controls: qasm_str += f'measure q[{control}] -> c[{control}];\n' qasm_str += 'if(c==1) ' return qasm_str def _qasm_customized(self, name: str) -> str: """Get QASM for multi-controlled gates.""" prefix = f'c{len(self.controls)}' if len(self.controls) > 2 else 'c' * len(self.controls) name = prefix + name.lower() qasm_lst1 = [f'opaque {name} '] qasm_lst2 = [f'{name} '] for i, wire in enumerate(self.controls + self.wires): qasm_lst1.append(f'q{i},') qasm_lst2.append(f'q[{wire}],') qasm_str1 = ''.join(qasm_lst1)[:-1] + ';\n' qasm_str2 = ''.join(qasm_lst2)[:-1] + ';\n' if name not in Gate._qasm_new_gate: Gate._qasm_new_gate.append(name) return qasm_str1 + qasm_str2 else: return qasm_str2 def _qasm(self) -> str: return self._qasm_customized(self.name)
[docs] def get_mpo(self) -> tuple[list[torch.Tensor], int]: r"""Convert gate to MPO form with identities at empty sites. Note: If sites are not adjacent, insert identities in the middle, i.e., >>> | | | | | >>> --A---x---B-- -> --A---I---B-- >>> | | | | | where >>> a >>> | >>> --i--I--j-- >>> | >>> b means :math:`\delta_{i,j} \delta_{a,b}` """ index = self.wires + self.controls index_left = min(index) nindex = len(index) index_sort = sorted(index) # convert index to a list of integers from 0 to nindex-1 s = {x: i for i, x in enumerate(index_sort)} index_local = [s[x] for x in index] # use shallow copy to share parameters gate_copy = copy(self) gate_copy.nqubit = nindex gate_copy.wires = index_local[: len(gate_copy.wires)] gate_copy.controls = index_local[len(gate_copy.wires) :] u = gate_copy.get_unitary() # transform gate from (out1, out2, ..., in1, in2 ...) to (out1, in1, out2, in2, ...) order = list(np.arange(2 * nindex).reshape((2, nindex)).T.flatten()) u = u.reshape([2] * 2 * nindex).permute(order).reshape([4] * nindex) main_tensors = state_to_tensors(u, nsite=nindex, qudit=4) # each tensor is in shape of (i, a, b, j) tensors = [] previous_i = None for i, main_tensor in zip(index_sort, main_tensors, strict=True): # insert identities in the middle if previous_i is not None: for _ in range(previous_i + 1, i): chi = tensors[-1].shape[-1] identity = torch.eye(chi * 2, dtype=u.dtype, device=u.device) tensors.append(identity.reshape(chi, 2, chi, 2).permute(0, 1, 3, 2)) nleft, _, nright = main_tensor.shape tensors.append(main_tensor.reshape(nleft, 2, 2, nright)) previous_i = i return tensors, index_left
[docs] def op_mps(self, mps: MatrixProductState) -> MatrixProductState: """Perform a forward pass for the ``MatrixProductState``.""" mpo_tensors, left = self.get_mpo() right = left + len(mpo_tensors) - 1 diff_left = abs(left - mps.center) diff_right = abs(right - mps.center) center_left = diff_left < diff_right if center_left: end1 = left end2 = right else: end1 = right end2 = left wires = list(range(left, right + 1)) out = MatrixProductState(nsite=mps.nsite, state=mps.tensors, chi=mps.chi, normalize=mps.normalize) out.center = mps.center out.center_orthogonalization(end1, dc=-1, normalize=out.normalize) out.apply_mpo(mpo_tensors, wires) out.center_orthogonalization(end2, dc=-1, normalize=out.normalize) out.center_orthogonalization(end1, dc=out.chi, normalize=out.normalize) return out
[docs] class Layer(Operation): r"""A base class for quantum layers. Args: name: The name of the layer. 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, name: str | None = None, nqubit: int = 1, wires: int | list[int] | list[list[int]] | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: super().__init__(name=name, nqubit=nqubit, wires=None, den_mat=den_mat, tsr_mode=tsr_mode) if wires is None: wires = [[0]] self.wires = self._convert_indices(wires) self.gates = nn.Sequential() # MBQC self.nodes = copy(self.wires)
[docs] def get_unitary(self) -> torch.Tensor: """Get the global unitary matrix.""" u = None for gate in self.gates: u = gate.get_unitary() if u is None else gate.get_unitary() @ u return u
[docs] def init_para(self, inputs: Any = None) -> None: """Initialize the parameters.""" count = 0 for gate in self.gates: if inputs is None: gate.init_para() else: gate.init_para(inputs[count : count + gate.npara]) count += gate.npara
[docs] def update_npara(self) -> None: """Update the number of parameters.""" self.npara = 0 for gate in self.gates: self.npara += gate.npara
[docs] def set_nqubit(self, nqubit: int) -> None: """Set the number of qubits of the ``Layer``.""" self.nqubit = nqubit for gate in self.gates: gate.nqubit = nqubit
[docs] def set_wires(self, wires: int | list[int] | list[list[int]]) -> None: """Set the wires of the ``Layer``.""" self.wires = self._convert_indices(wires) for i, gate in enumerate(self.gates): gate.wires = self.wires[i]
[docs] def forward( self, x: torch.Tensor | MatrixProductState | DistributedQubitState ) -> torch.Tensor | MatrixProductState | DistributedQubitState: """Perform a forward pass.""" if isinstance(x, (MatrixProductState, DistributedQubitState)): return self.gates(x) if not self.tsr_mode: x = self.tensor_rep(x) x = self.gates(x) if not self.tsr_mode: if self.den_mat: return self.matrix_rep(x).squeeze(0) else: return self.vector_rep(x).squeeze(0) return x
[docs] def inverse(self) -> 'Layer': """Get the inversed layer.""" return self
def _convert_indices(self, indices: int | list) -> list[list[int]]: if isinstance(indices, int): indices = [[indices]] assert isinstance(indices, list), 'Invalid input type' if all(isinstance(i, int) for i in indices): indices = [[i] for i in indices] assert all(isinstance(i, list) for i in indices), 'Invalid input type' for idx in indices: assert all(isinstance(i, int) for i in idx), 'Invalid input type' assert min(idx) > -1 and max(idx) < self.nqubit, 'Invalid input' assert len(set(idx)) == len(idx), 'Invalid input' return indices def _qasm(self) -> str: lst = [] for gate in self.gates: lst.append(gate._qasm()) return ''.join(lst)
[docs] def pattern(self, nodes: list[list[int]], ancilla: list[list[int]]) -> nn.Sequential: """Get the MBQC pattern.""" assert len(nodes) == len(ancilla) == len(self.gates) cmds = nn.Sequential() for i, gate in enumerate(self.gates): cmds.extend(gate.pattern(nodes[i], ancilla[i])) self.nodes[i] = gate.nodes return cmds
[docs] class Channel(Operation): r"""A base class for quantum channels. Args: inputs: The parameter of the channel. Default: ``None`` name: The name of the channel. 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`` 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`` requires_grad: Whether the parameter is ``nn.Parameter`` or ``buffer``. Default: ``False`` (which means ``buffer``) """ # include default names in QASM _qasm_new_gate = [] def __init__( self, inputs: Any = None, name: str | None = None, nqubit: int = 1, wires: int | list[int] | None = None, tsr_mode: bool = False, requires_grad: bool = False, ) -> None: self.nqubit = nqubit if wires is None: wires = [0] wires = self._convert_indices(wires) super().__init__(name=name, nqubit=nqubit, wires=wires, den_mat=True, tsr_mode=tsr_mode) self.npara = 1 self.requires_grad = requires_grad self.init_para(inputs) @property def prob(self): """The error probability.""" return torch.sin(self.theta) ** 2
[docs] def inputs_to_tensor(self, inputs: Any = None) -> torch.Tensor: """Convert inputs to torch.Tensor.""" while isinstance(inputs, list): inputs = inputs[0] if inputs is None: inputs = torch.rand(1)[0] * torch.pi elif not isinstance(inputs, torch.Tensor): inputs = torch.tensor(inputs, dtype=torch.float) return inputs
[docs] def get_matrix(self, theta: Any) -> torch.Tensor: """Update the local Kraus matrices acting on density matrices.""" raise self.matrix
[docs] def update_matrix(self) -> torch.Tensor: """Update the local Kraus matrices acting on density matrices.""" matrix = self.get_matrix(self.theta) self.matrix = matrix.detach() return matrix
[docs] def init_para(self, inputs: Any = None) -> None: """Initialize the parameters.""" theta = self.inputs_to_tensor(inputs) if self.requires_grad: self.theta = nn.Parameter(theta) else: self.register_buffer('theta', theta) self.update_matrix()
[docs] def op_den_mat(self, x: torch.Tensor) -> torch.Tensor: """Perform a forward pass for density matrices.""" matrix = self.update_matrix() x = vmap(evolve_den_mat, in_dims=(None, 0, None, None))(x, matrix, self.nqubit, self.wires).sum(0) if not self.tsr_mode: x = self.matrix_rep(x).squeeze(0) return x
[docs] def forward(self, x: torch.Tensor) -> torch.Tensor: """Perform a forward pass.""" if not self.tsr_mode: x = self.tensor_rep(x) assert x.ndim == 2 * self.nqubit + 1 return self.op_den_mat(x)
[docs] def extra_repr(self) -> str: return f'wires={self.wires}, probability={self.prob.item()}'
@staticmethod def _reset_qasm_new_gate() -> None: Channel._qasm_new_gate = [] def _qasm_customized(self, name: str) -> str: """Get QASM for channels.""" name = name.lower() qasm_lst1 = [f'opaque {name} '] qasm_lst2 = [f'{name} '] for i, wire in enumerate(self.wires): qasm_lst1.append(f'q{i},') qasm_lst2.append(f'q[{wire}],') qasm_str1 = ''.join(qasm_lst1)[:-1] + ';\n' qasm_str2 = ''.join(qasm_lst2)[:-1] + ';\n' if name not in Channel._qasm_new_gate: Channel._qasm_new_gate.append(name) return qasm_str1 + qasm_str2 else: return qasm_str2 def _qasm(self) -> str: return self._qasm_customized(self.name)
[docs] class GateQPD(Gate): r"""A base class for quasiprobability-decomposition 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: int | list[int] | None = None, den_mat: bool = False, tsr_mode: bool = False, ) -> None: self.nqubit = nqubit if wires is None: wires = [0] wires = self._convert_indices(wires) super().__init__(name=name, nqubit=nqubit, wires=wires, den_mat=den_mat, tsr_mode=tsr_mode) self.bases = bases self.coeffs = coeffs self.label = label self.idx = 0
[docs] def set_nqubit(self, nqubit: int) -> None: """Set the number of qubits of the ``GateQPD``.""" self.nqubit = nqubit for basis in self.bases: for ops in basis: for op in ops: op.nqubit = nqubit
[docs] def set_wires(self, wires: int | list[int]) -> None: """Set the wires of the ``GateQPD``.""" self.wires = self._convert_indices(wires) for basis in self.bases: for i, ops in enumerate(basis): for op in ops: op.set_wires(self.wires[i])
[docs] def forward(self, x: torch.Tensor, idx: int | None = None) -> torch.Tensor: """Perform a forward pass. Args: x: The input tensor. idx: The index of the operation to be applied. Default: ``None`` """ if idx is not None: self.idx = idx if not self.tsr_mode: x = self.tensor_rep(x) for ops in self.bases[self.idx]: x = ops(x) if not self.tsr_mode: x = self.matrix_rep(x).squeeze(0) if self.den_mat else self.vector_rep(x).squeeze(0) return x
[docs] class MeasureQPD(Operation): """A operation for denoting a QPD measurement location. Args: 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`` """ def __init__(self, nqubit: int = 1, wires: int | list[int] | None = None) -> None: self.nqubit = nqubit if wires is None: wires = [0] wires = self._convert_indices(wires) super().__init__(name='MeasureQPD', nqubit=nqubit, wires=wires)
[docs] def forward(self, x: Any) -> Any: """Perform a forward pass.""" return x