Source code for mpqp.core.instruction.gates.controlled_gate

from __future__ import annotations

from abc import ABC
from functools import reduce
from typing import Optional

from typeguard import typechecked

from mpqp.tools.generics import Matrix

from .gate import Gate


[docs]@typechecked class ControlledGate(Gate, ABC): """Abstract class representing a controlled gate, that can be controlled by one or several qubits. Args: controls: List of indices referring to the qubits used to control the gate. targets: List of indices referring to the qubits on which the gate will be applied. non_controlled_gate: The original, non controlled, gate. label: Label used to identify the gate. """ def __init__( self, controls: list[int], targets: list[int], non_controlled_gate: Gate, label: Optional[str] = None, ): if len(set(controls)) != len(controls): raise ValueError(f"Duplicate registers in controls: {controls}") if len(set(controls).intersection(set(targets))): raise ValueError( f"Common registers between targets {targets} and controls {controls}" ) self.controls = controls """See parameter description.""" self.non_controlled_gate = non_controlled_gate """See parameter description.""" Gate.__init__(self, targets, label)
[docs] def to_matrix(self, desired_gate_size: int = 0) -> Matrix: import numpy as np if len(self.controls) != 1 or len(self.targets) != 1: from mpqp.core.instruction.gates.native_gates import SWAP controls, targets = self.controls, self.targets min_qubit, max_qubit = min(self.connections()), max(self.connections()) # If nb_qubits is not provided, calculate the necessary number of minimal qubits if desired_gate_size == 0: desired_gate_size = max_qubit - min_qubit + 1 controls = [x - min_qubit for x in controls] targets = [x - min_qubit for x in targets] elif desired_gate_size < max_qubit + 1: raise ValueError(f"nb_qubits must be at least {max_qubit + 1}") canonical_matrix = np.kron( self.to_canonical_matrix(), np.eye(2 ** (desired_gate_size - self.nb_qubits)), ) permutations = set( tuple(sorted(idx)) for idx in enumerate(controls + targets) if idx[0] != idx[1] and not set(idx).issubset(controls) ) swaps = [ SWAP(canonical_index, actual_index).to_matrix(desired_gate_size) for canonical_index, actual_index in permutations ] return reduce(np.dot, swaps[::-1] + [canonical_matrix] + swaps) control, target = self.controls[0], self.targets[0] if desired_gate_size != 0: max_qubit = max(control, target) + 1 if desired_gate_size < max_qubit: raise ValueError(f"nb_qubits must be at least {max_qubit}") else: min_qubit = min(control, target) control -= min_qubit target -= min_qubit desired_gate_size = abs(control - target) + 1 zero = np.diag([1, 0]).astype(np.complex64) one = np.diag([0, 1]).astype(np.complex64) non_controlled_gate = self.non_controlled_gate.to_matrix() I2 = np.eye(2, dtype=np.complex64) control_matrix = zero if control == 0 else I2 target_matrix = ( one if control == 0 else (non_controlled_gate if target == 0 else I2) ) for i in range(1, desired_gate_size): if i == control: target_matrix = np.kron(target_matrix, one) control_matrix = np.kron(control_matrix, zero) elif i == target: target_matrix = np.kron(target_matrix, non_controlled_gate) control_matrix = np.kron(control_matrix, I2) else: target_matrix = np.kron(target_matrix, I2) control_matrix = np.kron(control_matrix, I2) return control_matrix + target_matrix
def __repr__(self) -> str: c = self.controls if len(self.controls) > 1 else self.controls[0] t = self.targets if len(self.targets) > 1 else self.targets[0] return f"{type(self).__name__}({c}, {t})"