Source code for qailab.circuit.base

  1"""ABC structure for circuit building blocks"""
  2from abc import ABC, abstractmethod
  3from typing import Literal
  4from collections.abc import Sequence
  5
  6from qiskit import QuantumCircuit
  7from qiskit.circuit import ParameterVector, Parameter, QuantumRegister
  8from qiskit.circuit.gate import Gate
  9from qiskit.circuit.quantumcircuit import QubitSpecifier
 10
 11
[docs] 12class CircuitBlock(ABC): 13 """ 14 Base class for any circuit building block 15 16 Attributes: 17 name (str): Block (and block circuit) name. 18 """ 19 20 def __init__(self, name: str = 'unknown') -> None: 21 self.name = name 22
[docs] 23 def to_gate(self, num_qubits: int) -> Gate: 24 """ 25 Get a gate form of of this block. 26 27 Args: 28 num_qubits (int): Desired width. 29 30 Returns: 31 Gate: Block defined circuit as a single gate. 32 """ 33 qc = self._build_circuit(num_qubits) 34 return qc.to_gate(label=self.name)
35
[docs] 36 def add_to_circuit(self, circuit: QuantumCircuit, qargs: Sequence[QubitSpecifier] | None = None) -> None: 37 """ 38 Add this block to a circuit (number of qubits must match) 39 40 Args: 41 circuit (QuantumCircuit): The circuit that will receive this block (in place). 42 qargs (list[QubitSpecifier] | None, optional): Which qubits to apply this circuit to. If None apply to all. Defaults to None. 43 """ 44 if qargs is None: 45 qargs = list(range(circuit.num_qubits)) 46 else: 47 qargs = list(qargs) 48 49 gate = self.to_gate(len(qargs)) 50 51 if gate.num_qubits > len(qargs): 52 num_qb_before = circuit.num_qubits 53 circuit.add_register(QuantumRegister(gate.num_qubits - len(qargs))) 54 qargs += list(range(num_qb_before, circuit.num_qubits)) 55 56 circuit.append(gate, qargs)
57 58 @abstractmethod 59 def _build_circuit(self, num_qubits: int) -> QuantumCircuit: 60 pass
61 62
[docs] 63class ParameterizedBlock(CircuitBlock, ABC): 64 """ 65 Blocks generating parametrized circuits 66 """ 67 68 def __init__(self, name: str = 'unknown') -> None: 69 self._parameters: Sequence[Parameter] | None = None 70 super().__init__(name) 71 72 @property 73 def parameters(self) -> Sequence[Parameter]: 74 """Get this block's parameter vector""" 75 if self._parameters is None: 76 raise ValueError("No parameters, the block was not added to any circuit.") 77 return self._parameters
78 79
[docs] 80class EntanglingBlock(CircuitBlock, ABC): 81 """Blocks entangling qubits together"""
82 83
[docs] 84class EncodingBlock(ParameterizedBlock, CircuitBlock, ABC): 85 """ 86 Blocks encoding some parameter vector (trainable or not) 87 88 Attributes: 89 block_type (Literal['input', 'weight']): Whether this block encodes weights or inputs. 90 """ 91 92 def __init__(self, name: str = 'unknown', block_type: Literal['input', 'weight'] = 'input') -> None: 93 self.block_type = block_type 94 super().__init__(name) 95 96 def _create_parameters(self, size: int) -> Sequence[Parameter]: 97 parameter_vector = ParameterVector(f"{self.block_type}_{self.__class__.__name__}_Params_{hex(id(super()))}", size) 98 return parameter_vector.params
99 100
[docs] 101class NonGateBlock(CircuitBlock, ABC): 102 """Blocks that cannot be converted to gates, e.g. measurement.""" 103
[docs] 104 def to_gate(self, num_qubits: int) -> Gate: 105 raise ValueError("This block cannot be converted to a gate.")
106
[docs] 107 def add_to_circuit(self, circuit: QuantumCircuit, qargs: Sequence[QubitSpecifier] | None = None) -> None: 108 if qargs is None: 109 qargs = list(range(circuit.num_qubits)) 110 else: 111 qargs = list(qargs) 112 113 c = self._build_circuit(len(qargs)) 114 115 circuit.compose(c, qargs, inplace=True)