Skip to content

Commit 42e8605

Browse files
authored
Add a graph_state_prep operation to the MBQC dialect (#1965)
**Context:** We require a more compact way to express graph states in the IR for MBQC workloads, and one that is more akin to eventual hardware-native instructions for allocating resources for a new graph state. **Description of the Change:** Adds a new operation to the MBQC dialect called `mbqc.graph_state_prep`, which logically is equivalent to the PennyLane operation [`qml.ftqc.GraphStatePrep`](https://docs.pennylane.ai/en/stable/code/api/pennylane.ftqc.GraphStatePrep.html), except that it only expresses the desired form of the graph state in the IR without decomposing it to its equivalent set of quantum gate operations (such a decomposition can be added in the future as a compilation pass). For more details on this new operation, please refer to the documentation included in the op definition. Some unit tests for other MBQC-dialect ops have also been renamed for better code organization. **Example** ```mlir %adj_matrix = arith.constant dense<[1, 0, 1, 0, 0, 1]> : tensor<6xi1> %graph_reg = mbqc.graph_state_prep (%adj_matrix : tensor<6xi1>) [init "Hadamard", entangle "CZ"] : !quantum.reg ``` [sc-97319]
1 parent 834b582 commit 42e8605

File tree

3 files changed

+103
-10
lines changed

3 files changed

+103
-10
lines changed

doc/releases/changelog-dev.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* Displays Catalyst version in `quantum-opt --version` output.
1111
[(#1922)](https://github.com/PennyLaneAI/catalyst/pull/1922)
1212

13-
* Snakecased keyword arguments to :func:`catalyst.passes.apply_pass()` are now correctly parsed
13+
* Snakecased keyword arguments to :func:`catalyst.passes.apply_pass()` are now correctly parsed
1414
to kebab-case pass options [(#1954)](https://github.com/PennyLaneAI/catalyst/pull/1954).
1515
For example:
1616

@@ -131,6 +131,11 @@
131131
...
132132
```
133133

134+
* The `mbqc.graph_state_prep` operation has been added to the MBQC dialect. This operation prepares
135+
a graph state with arbitrary qubit connectivity, specified by an input adjacency-matrix operand,
136+
for use in MBQC workloads.
137+
[(#1965)](https://github.com/PennyLaneAI/catalyst/pull/1965)
138+
134139
* `catalyst.accelerate`, `catalyst.debug.callback`, and `catalyst.pure_callback`, `catalyst.debug.print`, and `catalyst.debug.print_memref` now work when capture is enabled.
135140
[(#1902)](https://github.com/PennyLaneAI/catalyst/pull/1902)
136141

mlir/include/MBQC/IR/MBQCOps.td

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,84 @@ def MeasureInBasisOp : MBQC_Op<"measure_in_basis"> {
9191
}];
9292
}
9393

94+
def GraphStatePrepOp : MBQC_Op<"graph_state_prep"> {
95+
let summary = "Allocate resources for a new graph state.";
96+
let description = [{
97+
This operation allocates the resources for a new quantum *graph state* for use in MBQC
98+
workloads. A graph state is a highly entangled group of qubits arranged according to an
99+
arbitrary graph structure, where each qubit is represented by a vertex of the graph and
100+
where there is an edge between every interacting pair of neighbouring qubits.
101+
102+
A graph state requires three parameters to be fully expressed:
103+
104+
1. A graph representing the qubit connectivity.
105+
2. The qubit initial state. All qubits in the graph state are assumed to be initialized
106+
to the same state. This parameter is expressed as the name of the one-qubit gate that
107+
would have to be applied to the :math:`|0\rangle` state to realize the desired state.
108+
3. The type of entangling interaction along each edge. All interactions between
109+
neighbouring qubits are assumed to be the same. This parameter is expressed as the name
110+
of the two-qubit gate that would have to be applied to the pairs of neighbouring qubits
111+
to realize the desired entangled state.
112+
113+
A *densely packed adjacency matrix* is used to represent the graph structure. This
114+
representations is a more compact form of the familiar adjacency matrix graph
115+
representation, where only the lower-triangular part of the matrix is stored (since the
116+
graph is undirected) and where the diagonal elements are excluded (since there are no qubit
117+
self-interactions). The matrix values are then packed into a flat array. For example, the
118+
following graph structure,
119+
120+
.. code-block::
121+
122+
0 -- 1 -- 2 -- 3
123+
124+
has the adjacency matrix representation (with row and column indices shown for reference):
125+
126+
.. code-block::
127+
128+
0 1 2 3
129+
130+
0 0 1 0 0
131+
1 1 0 1 0
132+
2 0 1 0 1
133+
3 0 0 1 0
134+
135+
The equivalent densely packed adjacency matrix has the lower-triangular part of the above
136+
matrix packed into a flat array using row-major order as follows (with extra space added
137+
between sequences of values to indicate the rows of the matrix):
138+
139+
.. code-block::
140+
141+
[1, 0, 1, 0, 0, 1]
142+
143+
In this representation, the number of vertices, :math:`N`, in the graph is related to the
144+
number of elements, :math:`m`, in the densely packed adjacency matrix via
145+
146+
.. math::
147+
148+
m = N(N - 1) / 2,
149+
150+
or inversely,
151+
152+
.. math::
153+
154+
N = (1 + \sqrt{1 + 8m}) / 2.
155+
}];
156+
157+
let arguments = (ins
158+
AnyTypeOf<[
159+
1DTensorOf<[I1]>, MemRefRankOf<[I1], [1]>
160+
]>:$adj_matrix,
161+
StrAttr:$init_op,
162+
StrAttr:$entangle_op
163+
);
164+
165+
let results = (outs
166+
QuregType:$qreg
167+
);
168+
169+
let assemblyFormat = [{
170+
`(` $adj_matrix `:` type($adj_matrix) `)` `[` `init` $init_op `,` `entangle` $entangle_op `]` attr-dict `:` type(results)
171+
}];
172+
}
173+
94174
#endif // MBQC_OPS

mlir/test/MBQC/DialectTest.mlir

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,31 @@
1414

1515
// RUN: quantum-opt --split-input-file --verify-diagnostics %s
1616

17-
func.func @testXY(%q1 : !quantum.bit) {
17+
func.func @test_measure_in_basis_XY(%q1 : !quantum.bit) {
1818
%angle = arith.constant 3.141592653589793 : f64
1919
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 : i1, !quantum.bit
2020
func.return
2121
}
2222

2323
// -----
2424

25-
func.func @testYZ(%q1 : !quantum.bit) {
25+
func.func @test_measure_in_basis_YZ(%q1 : !quantum.bit) {
2626
%angle = arith.constant 3.141592653589793 : f64
2727
%res, %new_q = mbqc.measure_in_basis [YZ, %angle] %q1 : i1, !quantum.bit
2828
func.return
2929
}
3030

3131
// -----
3232

33-
func.func @testZX(%q1 : !quantum.bit) {
33+
func.func @test_measure_in_basis_ZX(%q1 : !quantum.bit) {
3434
%angle = arith.constant 3.141592653589793 : f64
3535
%res, %new_q = mbqc.measure_in_basis [ZX, %angle] %q1 : i1, !quantum.bit
3636
func.return
3737
}
3838

3939
// -----
4040

41-
func.func @testDynamicAngle(%q1 : !quantum.bit, %arg0: tensor<f64>) {
41+
func.func @test_measure_in_basis_dynamic_angle(%q1 : !quantum.bit, %arg0: tensor<f64>) {
4242
%1 = stablehlo.convert %arg0 : tensor<f64>
4343
%angle = tensor.extract %1[] : tensor<f64>
4444
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 : i1, !quantum.bit
@@ -47,7 +47,7 @@ func.func @testDynamicAngle(%q1 : !quantum.bit, %arg0: tensor<f64>) {
4747

4848
// -----
4949

50-
func.func @testInvalid(%q1 : !quantum.bit) {
50+
func.func @test_measure_in_basis_invalid_plane(%q1 : !quantum.bit) {
5151
%angle = arith.constant 3.141592653589793 : f64
5252
// expected-error@below {{expected catalyst::mbqc::MeasurementPlane to be one of: XY, YZ, ZX}}
5353
// expected-error@below {{failed to parse MeasurementPlaneAttr parameter}}
@@ -57,23 +57,23 @@ func.func @testInvalid(%q1 : !quantum.bit) {
5757

5858
// -----
5959

60-
func.func @testPostSelect0(%q1 : !quantum.bit) {
60+
func.func @test_measure_in_basis_postselect_0(%q1 : !quantum.bit) {
6161
%angle = arith.constant 3.141592653589793 : f64
6262
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 postselect 0 : i1, !quantum.bit
6363
func.return
6464
}
6565

6666
// -----
6767

68-
func.func @testPostSelect1(%q1 : !quantum.bit) {
68+
func.func @test_measure_in_basis_postselect_1(%q1 : !quantum.bit) {
6969
%angle = arith.constant 3.141592653589793 : f64
7070
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 postselect 1 : i1, !quantum.bit
7171
func.return
7272
}
7373

7474
// -----
7575

76-
func.func @testPostSelectInvalid(%q1 : !quantum.bit) {
76+
func.func @test_measure_in_basis_postselect_invalid(%q1 : !quantum.bit) {
7777
%angle = arith.constant 3.141592653589793 : f64
7878
// expected-error@below {{op attribute 'postselect' failed to satisfy constraint}}
7979
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 postselect -1 : i1, !quantum.bit
@@ -82,9 +82,17 @@ func.func @testPostSelectInvalid(%q1 : !quantum.bit) {
8282

8383
// -----
8484

85-
func.func @testPostSelectInvalid(%q1 : !quantum.bit) {
85+
func.func @test_measure_in_basis_postselect_invalid(%q1 : !quantum.bit) {
8686
%angle = arith.constant 3.141592653589793 : f64
8787
// expected-error@below {{op attribute 'postselect' failed to satisfy constraint}}
8888
%res, %new_q = mbqc.measure_in_basis [XY, %angle] %q1 postselect 2 : i1, !quantum.bit
8989
func.return
9090
}
91+
92+
// -----
93+
94+
func.func @test_graph_state_prep() {
95+
%adj_matrix = arith.constant dense<[1, 0, 1, 0, 0, 1]> : tensor<6xi1>
96+
%graph_reg = mbqc.graph_state_prep (%adj_matrix : tensor<6xi1>) [init "Hadamard", entangle "CZ"] : !quantum.reg
97+
func.return
98+
}

0 commit comments

Comments
 (0)