Skip to content

Commit 92692a2

Browse files
More quantum circuit library refactoring (#13353)
* GraphState -> GraphStateGate * adding GraphStateGate::__eq__ method * oops... fourier checking should not be a gate * FourierChecking -> fourier_checking * UnitaryOverlap -> unitary_overlap * visualization improvements (following quantum_volume code) * visualization improvements (following quantum_volume code) * HiddenLinearFunction -> hidden_linear_function * unused imports * PhaseEstimation -> phase_estimation * cleaning up phase estimation code * reno * Restoring to the original definition of the FourierChecking circuit * pass over fourier_checking function * remaining suggestions from code review; missing tests; missing API refs * pylint * reno fix * another small round of addressing review comments
1 parent 4c3f8c9 commit 92692a2

13 files changed

Lines changed: 725 additions & 111 deletions

qiskit/circuit/library/__init__.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -321,20 +321,29 @@
321321
:template: autosummary/class_no_inherited_members.rst
322322
323323
FourierChecking
324+
fourier_checking
324325
GraphState
326+
GraphStateGate
325327
HiddenLinearFunction
328+
hidden_linear_function
326329
IQP
327330
QuantumVolume
328331
quantum_volume
329332
PhaseEstimation
333+
phase_estimation
330334
GroverOperator
331335
PhaseOracle
332336
PauliEvolutionGate
333337
HamiltonianGate
334338
UnitaryOverlap
339+
unitary_overlap
335340
336341
.. autofunction:: iqp
337342
.. autofunction:: random_iqp
343+
.. autofunction:: fourier_checking
344+
.. autofunction:: hidden_linear_function
345+
.. autofunction:: unitary_overlap
346+
.. autofunction:: phase_estimation
338347
339348
340349
N-local circuits
@@ -582,11 +591,11 @@
582591
Initialize,
583592
)
584593
from .quantum_volume import QuantumVolume, quantum_volume
585-
from .fourier_checking import FourierChecking
586-
from .graph_state import GraphState
587-
from .hidden_linear_function import HiddenLinearFunction
594+
from .fourier_checking import FourierChecking, fourier_checking
595+
from .graph_state import GraphState, GraphStateGate
596+
from .hidden_linear_function import HiddenLinearFunction, hidden_linear_function
588597
from .iqp import IQP, iqp, random_iqp
589-
from .phase_estimation import PhaseEstimation
598+
from .phase_estimation import PhaseEstimation, phase_estimation
590599
from .grover_operator import GroverOperator
591600
from .phase_oracle import PhaseOracle
592-
from .overlap import UnitaryOverlap
601+
from .overlap import UnitaryOverlap, unitary_overlap

qiskit/circuit/library/fourier_checking.py

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@
1212

1313
"""Fourier checking circuit."""
1414

15-
from typing import List
16-
15+
from collections.abc import Sequence
1716
import math
17+
1818
from qiskit.circuit import QuantumCircuit
1919
from qiskit.circuit.exceptions import CircuitError
20+
from qiskit.utils.deprecation import deprecate_func
2021

21-
from .generalized_gates.diagonal import Diagonal
22+
from .generalized_gates.diagonal import Diagonal, DiagonalGate
2223

2324

2425
class FourierChecking(QuantumCircuit):
@@ -52,7 +53,12 @@ class FourierChecking(QuantumCircuit):
5253
`arXiv:1411.5729 <https://arxiv.org/abs/1411.5729>`_
5354
"""
5455

55-
def __init__(self, f: List[int], g: List[int]) -> None:
56+
@deprecate_func(
57+
since="1.3",
58+
additional_msg="Use qiskit.circuit.library.fourier_checking instead.",
59+
pending=True,
60+
)
61+
def __init__(self, f: Sequence[int], g: Sequence[int]) -> None:
5662
"""Create Fourier checking circuit.
5763
5864
Args:
@@ -81,17 +87,72 @@ def __init__(self, f: List[int], g: List[int]) -> None:
8187
"{1, -1}."
8288
)
8389

84-
circuit = QuantumCircuit(num_qubits, name=f"fc: {f}, {g}")
85-
90+
# This definition circuit is not replaced by the circuit produced by fourier_checking,
91+
# as the latter produces a slightly different circuit, with DiagonalGates instead
92+
# of Diagonal circuits.
93+
circuit = QuantumCircuit(int(num_qubits), name=f"fc: {f}, {g}")
8694
circuit.h(circuit.qubits)
87-
8895
circuit.compose(Diagonal(f), inplace=True)
89-
9096
circuit.h(circuit.qubits)
91-
9297
circuit.compose(Diagonal(g), inplace=True)
93-
9498
circuit.h(circuit.qubits)
95-
9699
super().__init__(*circuit.qregs, name=circuit.name)
97100
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)
101+
102+
103+
def fourier_checking(f: Sequence[int], g: Sequence[int]) -> QuantumCircuit:
104+
"""Fourier checking circuit.
105+
106+
The circuit for the Fourier checking algorithm, introduced in [1],
107+
involves a layer of Hadamards, the function :math:`f`, another layer of
108+
Hadamards, the function :math:`g`, followed by a final layer of Hadamards.
109+
The functions :math:`f` and :math:`g` are classical functions realized
110+
as phase oracles (diagonal operators with {-1, 1} on the diagonal).
111+
112+
The probability of observing the all-zeros string is :math:`p(f,g)`.
113+
The algorithm solves the promise Fourier checking problem,
114+
which decides if f is correlated with the Fourier transform
115+
of g, by testing if :math:`p(f,g) <= 0.01` or :math:`p(f,g) >= 0.05`,
116+
promised that one or the other of these is true.
117+
118+
The functions :math:`f` and :math:`g` are currently implemented
119+
from their truth tables but could be represented concisely and
120+
implemented efficiently for special classes of functions.
121+
122+
Fourier checking is a special case of :math:`k`-fold forrelation [2].
123+
124+
**Reference Circuit:**
125+
126+
.. plot::
127+
:include-source:
128+
129+
from qiskit.circuit.library import fourier_checking
130+
circuit = fourier_checking([1, -1, -1, -1], [1, 1, -1, -1])
131+
circuit.draw('mpl')
132+
133+
**Reference:**
134+
135+
[1] S. Aaronson, BQP and the Polynomial Hierarchy, 2009 (Section 3.2).
136+
`arXiv:0910.4698 <https://arxiv.org/abs/0910.4698>`_
137+
138+
[2] S. Aaronson, A. Ambainis, Forrelation: a problem that
139+
optimally separates quantum from classical computing, 2014.
140+
`arXiv:1411.5729 <https://arxiv.org/abs/1411.5729>`_
141+
"""
142+
num_qubits = math.log2(len(f))
143+
144+
if len(f) != len(g) or num_qubits == 0 or not num_qubits.is_integer():
145+
raise CircuitError(
146+
"The functions f and g must be given as truth "
147+
"tables, each as a list of 2**n entries of "
148+
"{1, -1}."
149+
)
150+
num_qubits = int(num_qubits)
151+
152+
circuit = QuantumCircuit(num_qubits, name=f"fc: {f}, {g}")
153+
circuit.h(circuit.qubits)
154+
circuit.append(DiagonalGate(f), range(num_qubits))
155+
circuit.h(circuit.qubits)
156+
circuit.append(DiagonalGate(g), range(num_qubits))
157+
circuit.h(circuit.qubits)
158+
return circuit

qiskit/circuit/library/graph_state.py

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This code is part of Qiskit.
22
#
3-
# (C) Copyright IBM 2017, 2020.
3+
# (C) Copyright IBM 2017, 2024.
44
#
55
# This code is licensed under the Apache License, Version 2.0. You may
66
# obtain a copy of this license in the LICENSE.txt file in the root directory
@@ -10,13 +10,14 @@
1010
# copyright notice, and modified files need to carry a notice indicating
1111
# that they have been altered from the originals.
1212

13-
"""Graph State circuit."""
13+
"""Graph State circuit and gate."""
1414

1515
from __future__ import annotations
1616

1717
import numpy as np
18-
from qiskit.circuit.quantumcircuit import QuantumCircuit
18+
from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate
1919
from qiskit.circuit.exceptions import CircuitError
20+
from qiskit.utils.deprecation import deprecate_func
2021

2122

2223
class GraphState(QuantumCircuit):
@@ -56,6 +57,11 @@ class GraphState(QuantumCircuit):
5657
`arXiv:1512.07892 <https://arxiv.org/pdf/1512.07892.pdf>`_
5758
"""
5859

60+
@deprecate_func(
61+
since="1.3",
62+
additional_msg="Use qiskit.circuit.library.GraphStateGate instead.",
63+
pending=True,
64+
)
5965
def __init__(self, adjacency_matrix: list | np.ndarray) -> None:
6066
"""Create graph state preparation circuit.
6167
@@ -73,14 +79,91 @@ def __init__(self, adjacency_matrix: list | np.ndarray) -> None:
7379
if not np.allclose(adjacency_matrix, adjacency_matrix.transpose()):
7480
raise CircuitError("The adjacency matrix must be symmetric.")
7581

82+
graph_state_gate = GraphStateGate(adjacency_matrix)
83+
super().__init__(graph_state_gate.num_qubits, name=f"graph: {adjacency_matrix}")
84+
self.compose(graph_state_gate, qubits=self.qubits, inplace=True)
85+
86+
87+
class GraphStateGate(Gate):
88+
r"""A gate representing a graph state.
89+
90+
Given a graph G = (V, E), with the set of vertices V and the set of edges E,
91+
the corresponding graph state is defined as
92+
93+
.. math::
94+
95+
|G\rangle = \prod_{(a,b) \in E} CZ_{(a,b)} {|+\rangle}^{\otimes V}
96+
97+
Such a state can be prepared by first preparing all qubits in the :math:`+`
98+
state, then applying a :math:`CZ` gate for each corresponding graph edge.
99+
100+
Graph state preparation circuits are Clifford circuits, and thus
101+
easy to simulate classically. However, by adding a layer of measurements
102+
in a product basis at the end, there is evidence that the circuit becomes
103+
hard to simulate [2].
104+
105+
**Reference Circuit:**
106+
107+
.. plot::
108+
:include-source:
109+
110+
from qiskit.circuit import QuantumCircuit
111+
from qiskit.circuit.library import GraphStateGate
112+
import rustworkx as rx
113+
114+
G = rx.generators.cycle_graph(5)
115+
circuit = QuantumCircuit(5)
116+
circuit.append(GraphStateGate(rx.adjacency_matrix(G)), [0, 1, 2, 3, 4])
117+
circuit.decompose().draw('mpl')
118+
119+
**References:**
120+
121+
[1] M. Hein, J. Eisert, H.J. Briegel, Multi-party Entanglement in Graph States,
122+
`arXiv:0307130 <https://arxiv.org/pdf/quant-ph/0307130.pdf>`_
123+
[2] D. Koh, Further Extensions of Clifford Circuits & their Classical Simulation Complexities.
124+
`arXiv:1512.07892 <https://arxiv.org/pdf/1512.07892.pdf>`_
125+
"""
126+
127+
def __init__(self, adjacency_matrix: list | np.ndarray) -> None:
128+
"""
129+
Args:
130+
adjacency_matrix: input graph as n-by-n list of 0-1 lists
131+
132+
Raises:
133+
CircuitError: If adjacency_matrix is not symmetric.
134+
135+
The gate represents a graph state with the given adjacency matrix.
136+
"""
137+
138+
adjacency_matrix = np.asarray(adjacency_matrix)
139+
if not np.allclose(adjacency_matrix, adjacency_matrix.transpose()):
140+
raise CircuitError("The adjacency matrix must be symmetric.")
76141
num_qubits = len(adjacency_matrix)
77-
circuit = QuantumCircuit(num_qubits, name=f"graph: {adjacency_matrix}")
78142

79-
circuit.h(range(num_qubits))
80-
for i in range(num_qubits):
81-
for j in range(i + 1, num_qubits):
143+
super().__init__(name="graph_state", num_qubits=num_qubits, params=[adjacency_matrix])
144+
145+
def _define(self):
146+
adjacency_matrix = self.adjacency_matrix
147+
circuit = QuantumCircuit(self.num_qubits, name=self.name)
148+
circuit.h(range(self.num_qubits))
149+
for i in range(self.num_qubits):
150+
for j in range(i + 1, self.num_qubits):
82151
if adjacency_matrix[i][j] == 1:
83152
circuit.cz(i, j)
84-
85-
super().__init__(*circuit.qregs, name=circuit.name)
86-
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)
153+
self.definition = circuit
154+
155+
def validate_parameter(self, parameter):
156+
"""Parameter validation"""
157+
return parameter
158+
159+
@property
160+
def adjacency_matrix(self):
161+
"""Returns the adjacency matrix."""
162+
return self.params[0]
163+
164+
def __eq__(self, other):
165+
return (
166+
isinstance(other, GraphStateGate)
167+
and self.num_qubits == other.num_qubits
168+
and np.all(self.adjacency_matrix == other.adjacency_matrix)
169+
)

0 commit comments

Comments
 (0)