graphqomb 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
graphqomb/__init__.py ADDED
@@ -0,0 +1 @@
1
+ """GraphQOMB library."""
graphqomb/circuit.py ADDED
@@ -0,0 +1,271 @@
1
+ """Circuit classes for encoding quantum operations.
2
+
3
+ This module provides:
4
+
5
+ - `BaseCircuit`: An abstract base class for quantum circuits.
6
+ - `MBQCCircuit`: A circuit class composed solely of a unit gate set.
7
+ - `Circuit`: A class for circuits that include macro instructions.
8
+ - `circuit2graph`: A function that converts a circuit to a graph state and gflow.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import copy
14
+ import itertools
15
+ from abc import ABC, abstractmethod
16
+ from typing import TYPE_CHECKING
17
+
18
+ import typing_extensions
19
+
20
+ from graphqomb.common import Plane, PlannerMeasBasis
21
+ from graphqomb.gates import CZ, Gate, J, PhaseGadget, UnitGate
22
+ from graphqomb.graphstate import GraphState
23
+
24
+ if TYPE_CHECKING:
25
+ from collections.abc import Sequence
26
+
27
+
28
+ class BaseCircuit(ABC):
29
+ """
30
+ Abstract base class for quantum circuits.
31
+
32
+ This class defines the interface for quantum circuit objects.
33
+ It enforces implementation of core methods that must be present
34
+ in any subclass representing a specific type of quantum circuit.
35
+ """
36
+
37
+ @property
38
+ @abstractmethod
39
+ def num_qubits(self) -> int:
40
+ """Get the number of qubits in the circuit.
41
+
42
+ Returns
43
+ -------
44
+ `int`
45
+ The number of qubits in the circuit
46
+ """
47
+ raise NotImplementedError
48
+
49
+ @abstractmethod
50
+ def instructions(self) -> list[Gate]:
51
+ r"""Get the list of gate instructions in the circuit.
52
+
53
+ Returns
54
+ -------
55
+ `list`\[`Gate`\]
56
+ List of gate instructions in the circuit.
57
+ """
58
+ raise NotImplementedError
59
+
60
+ @abstractmethod
61
+ def unit_instructions(self) -> list[UnitGate]:
62
+ r"""Get the list of unit gate instructions in the circuit.
63
+
64
+ Returns
65
+ -------
66
+ `list`\[`UnitGate`\]
67
+ List of unit gate instructions in the circuit.
68
+ """
69
+ raise NotImplementedError
70
+
71
+
72
+ class MBQCCircuit(BaseCircuit):
73
+ """A circuit class composed solely of a unit gate set."""
74
+
75
+ __num_qubits: int
76
+ __gate_instructions: list[UnitGate]
77
+
78
+ def __init__(self, num_qubits: int) -> None:
79
+ self.__num_qubits = num_qubits
80
+ self.__gate_instructions = []
81
+
82
+ @property
83
+ @typing_extensions.override
84
+ def num_qubits(self) -> int:
85
+ """Get the number of qubits in the circuit.
86
+
87
+ Returns
88
+ -------
89
+ `int`
90
+ The number of qubits in the circuit.
91
+ """
92
+ return self.__num_qubits
93
+
94
+ @typing_extensions.override
95
+ def instructions(self) -> list[Gate]:
96
+ r"""Get the list of gate instructions in the circuit.
97
+
98
+ Returns
99
+ -------
100
+ `list`\[`Gate`\]
101
+ List of gate instructions in the circuit.
102
+ """
103
+ # For MBQCCircuit, Gate and UnitGate are the same
104
+ return [copy.deepcopy(gate) for gate in self.__gate_instructions]
105
+
106
+ @typing_extensions.override
107
+ def unit_instructions(self) -> list[UnitGate]:
108
+ r"""Get the list of unit gate instructions in the circuit.
109
+
110
+ Returns
111
+ -------
112
+ `list`\[`UnitGate`\]
113
+ List of unit gate instructions in the circuit.
114
+ """
115
+ return [copy.deepcopy(gate) for gate in self.__gate_instructions]
116
+
117
+ def j(self, qubit: int, angle: float) -> None:
118
+ """Add a J gate to the circuit.
119
+
120
+ Parameters
121
+ ----------
122
+ qubit : `int`
123
+ The qubit index.
124
+ angle : `float`
125
+ The angle of the J gate.
126
+ """
127
+ self.__gate_instructions.append(J(qubit=qubit, angle=angle))
128
+
129
+ def cz(self, qubit1: int, qubit2: int) -> None:
130
+ """Add a CZ gate to the circuit.
131
+
132
+ Parameters
133
+ ----------
134
+ qubit1 : `int`
135
+ The first qubit index.
136
+ qubit2 : `int`
137
+ The second qubit index.
138
+ """
139
+ self.__gate_instructions.append(CZ(qubits=(qubit1, qubit2)))
140
+
141
+ def phase_gadget(self, qubits: Sequence[int], angle: float) -> None:
142
+ r"""Add a phase gadget to the circuit.
143
+
144
+ Parameters
145
+ ----------
146
+ qubits : `collections.abc.Sequence`\[`int`\]
147
+ The qubit indices.
148
+ angle : `float`
149
+ The angle of the phase gadget
150
+ """
151
+ self.__gate_instructions.append(PhaseGadget(qubits=list(qubits), angle=angle))
152
+
153
+
154
+ class Circuit(BaseCircuit):
155
+ """A class for circuits that include macro instructions."""
156
+
157
+ __num_qubits: int
158
+ __macro_gate_instructions: list[Gate]
159
+
160
+ def __init__(self, num_qubits: int) -> None:
161
+ self.__num_qubits = num_qubits
162
+ self.__macro_gate_instructions = []
163
+
164
+ @property
165
+ @typing_extensions.override
166
+ def num_qubits(self) -> int:
167
+ """Get the number of qubits in the circuit.
168
+
169
+ Returns
170
+ -------
171
+ `int`
172
+ The number of qubits in the circuit.
173
+ """
174
+ return self.__num_qubits
175
+
176
+ @typing_extensions.override
177
+ def instructions(self) -> list[Gate]:
178
+ r"""Get the list of gate instructions in the circuit.
179
+
180
+ Returns
181
+ -------
182
+ `list`\[`Gate`\]
183
+ List of gate instructions in the circuit.
184
+ """
185
+ return [copy.deepcopy(gate) for gate in self.__macro_gate_instructions]
186
+
187
+ @typing_extensions.override
188
+ def unit_instructions(self) -> list[UnitGate]:
189
+ r"""Get the list of unit gate instructions in the circuit.
190
+
191
+ Returns
192
+ -------
193
+ `list`\[`UnitGate`\]
194
+ The list of unit gate instructions in the circuit.
195
+ """
196
+ return list(
197
+ itertools.chain.from_iterable(macro_gate.unit_gates() for macro_gate in self.__macro_gate_instructions)
198
+ )
199
+
200
+ def apply_macro_gate(self, gate: Gate) -> None:
201
+ """Apply a macro gate to the circuit.
202
+
203
+ Parameters
204
+ ----------
205
+ gate : `Gate`
206
+ The macro gate to apply.
207
+ """
208
+ self.__macro_gate_instructions.append(gate)
209
+
210
+
211
+ def circuit2graph(circuit: BaseCircuit) -> tuple[GraphState, dict[int, set[int]]]:
212
+ r"""Convert a circuit to a graph state and gflow.
213
+
214
+ Parameters
215
+ ----------
216
+ circuit : `BaseCircuit`
217
+ The quantum circuit to convert.
218
+
219
+ Returns
220
+ -------
221
+ `tuple`\[`GraphState`, `dict`\[`int`, `set`\[`int`\]\]\]
222
+ The graph state and gflow converted from the circuit.
223
+
224
+ Raises
225
+ ------
226
+ TypeError
227
+ If the circuit contains an invalid instruction.
228
+ """
229
+ graph = GraphState()
230
+ gflow: dict[int, set[int]] = {}
231
+
232
+ qindex2front_nodes: dict[int, int] = {}
233
+
234
+ # input nodes
235
+ for i in range(circuit.num_qubits):
236
+ node = graph.add_physical_node()
237
+ graph.register_input(node, i)
238
+ qindex2front_nodes[i] = node
239
+
240
+ for instruction in circuit.unit_instructions():
241
+ if isinstance(instruction, J):
242
+ new_node = graph.add_physical_node()
243
+ graph.add_physical_edge(qindex2front_nodes[instruction.qubit], new_node)
244
+ graph.assign_meas_basis(
245
+ qindex2front_nodes[instruction.qubit],
246
+ PlannerMeasBasis(Plane.XY, -instruction.angle),
247
+ )
248
+
249
+ gflow[qindex2front_nodes[instruction.qubit]] = {new_node}
250
+ qindex2front_nodes[instruction.qubit] = new_node
251
+
252
+ elif isinstance(instruction, CZ):
253
+ graph.add_physical_edge(
254
+ qindex2front_nodes[instruction.qubits[0]],
255
+ qindex2front_nodes[instruction.qubits[1]],
256
+ )
257
+ elif isinstance(instruction, PhaseGadget):
258
+ new_node = graph.add_physical_node()
259
+ graph.assign_meas_basis(new_node, PlannerMeasBasis(Plane.YZ, instruction.angle))
260
+ for qubit in instruction.qubits:
261
+ graph.add_physical_edge(qindex2front_nodes[qubit], new_node)
262
+
263
+ gflow[new_node] = {new_node}
264
+ else:
265
+ msg = f"Invalid instruction: {instruction}"
266
+ raise TypeError(msg)
267
+
268
+ for qindex, node in qindex2front_nodes.items():
269
+ graph.register_output(node, qindex)
270
+
271
+ return graph, gflow
graphqomb/command.py ADDED
@@ -0,0 +1,110 @@
1
+ """Command module for measurement pattern.
2
+
3
+ This module provides:
4
+
5
+ - `N`: Preparation command.
6
+ - `E`: Entanglement command.
7
+ - `M`: Measurement command.
8
+ - `X`: X correction command.
9
+ - `Z`: Z correction command.
10
+ - `Command`: Type alias of all commands.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import dataclasses
16
+ import sys
17
+ from typing import TYPE_CHECKING, Union
18
+
19
+ if TYPE_CHECKING:
20
+ from graphqomb.common import MeasBasis
21
+
22
+
23
+ @dataclasses.dataclass
24
+ class N:
25
+ """Preparation command.
26
+
27
+ Attributes
28
+ ----------
29
+ node : `int`
30
+ The node index to be prepared.
31
+ """
32
+
33
+ node: int
34
+
35
+ def __str__(self) -> str:
36
+ return f"N: node={self.node}"
37
+
38
+
39
+ @dataclasses.dataclass
40
+ class E:
41
+ r"""Entanglement command.
42
+
43
+ Attributes
44
+ ----------
45
+ nodes : `tuple`\[`int`, `int`\]
46
+ The node indices to be entangled.
47
+ """
48
+
49
+ nodes: tuple[int, int]
50
+
51
+ def __str__(self) -> str:
52
+ return f"E: nodes={self.nodes}"
53
+
54
+
55
+ @dataclasses.dataclass
56
+ class M:
57
+ """Measurement command.
58
+
59
+ Attributes
60
+ ----------
61
+ node : `int`
62
+ The node index to be measured.
63
+ meas_basis : MeasBasis
64
+ The measurement basis.
65
+ """
66
+
67
+ node: int
68
+ meas_basis: MeasBasis
69
+
70
+ def __str__(self) -> str:
71
+ return f"M: node={self.node}, plane={self.meas_basis.plane}, angle={self.meas_basis.angle}"
72
+
73
+
74
+ @dataclasses.dataclass
75
+ class _Correction:
76
+ node: int
77
+
78
+
79
+ @dataclasses.dataclass
80
+ class X(_Correction):
81
+ """X correction command.
82
+
83
+ Attributes
84
+ ----------
85
+ node : `int`
86
+ The node index to apply the correction.
87
+ """
88
+
89
+ def __str__(self) -> str:
90
+ return f"X: node={self.node}"
91
+
92
+
93
+ @dataclasses.dataclass
94
+ class Z(_Correction):
95
+ """Z correction command.
96
+
97
+ Attributes
98
+ ----------
99
+ node : `int`
100
+ The node index to apply the correction.
101
+ """
102
+
103
+ def __str__(self) -> str:
104
+ return f"Z: node={self.node}"
105
+
106
+
107
+ if sys.version_info >= (3, 10):
108
+ Command = N | E | M | X | Z
109
+ else:
110
+ Command = Union[N, E, M, X, Z]