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)