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, cast, Union

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: Union[list[int], int], targets: Union[list[int], int], non_controlled_gate: Gate, label: Optional[str] = None, ): if isinstance(controls, int): controls = [controls] if isinstance(targets, int): targets = [targets] 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}" ) if any(control < 0 for control in controls): raise ValueError(f"Negative index in controls: {controls}") if any(target < 0 for target in targets): raise ValueError(f"Negative index in targets: {targets}") 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"Total number of 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]) one = np.diag([0, 1]) 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 cast(Matrix, 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})"