Skip to content

Commit

Permalink
More quantum circuit library refactoring (#13353)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
alexanderivrii authored Nov 4, 2024
1 parent 4c3f8c9 commit 92692a2
Show file tree
Hide file tree
Showing 13 changed files with 725 additions and 111 deletions.
19 changes: 14 additions & 5 deletions qiskit/circuit/library/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,20 +321,29 @@
:template: autosummary/class_no_inherited_members.rst
FourierChecking
fourier_checking
GraphState
GraphStateGate
HiddenLinearFunction
hidden_linear_function
IQP
QuantumVolume
quantum_volume
PhaseEstimation
phase_estimation
GroverOperator
PhaseOracle
PauliEvolutionGate
HamiltonianGate
UnitaryOverlap
unitary_overlap
.. autofunction:: iqp
.. autofunction:: random_iqp
.. autofunction:: fourier_checking
.. autofunction:: hidden_linear_function
.. autofunction:: unitary_overlap
.. autofunction:: phase_estimation
N-local circuits
Expand Down Expand Up @@ -582,11 +591,11 @@
Initialize,
)
from .quantum_volume import QuantumVolume, quantum_volume
from .fourier_checking import FourierChecking
from .graph_state import GraphState
from .hidden_linear_function import HiddenLinearFunction
from .fourier_checking import FourierChecking, fourier_checking
from .graph_state import GraphState, GraphStateGate
from .hidden_linear_function import HiddenLinearFunction, hidden_linear_function
from .iqp import IQP, iqp, random_iqp
from .phase_estimation import PhaseEstimation
from .phase_estimation import PhaseEstimation, phase_estimation
from .grover_operator import GroverOperator
from .phase_oracle import PhaseOracle
from .overlap import UnitaryOverlap
from .overlap import UnitaryOverlap, unitary_overlap
83 changes: 72 additions & 11 deletions qiskit/circuit/library/fourier_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@

"""Fourier checking circuit."""

from typing import List

from collections.abc import Sequence
import math

from qiskit.circuit import QuantumCircuit
from qiskit.circuit.exceptions import CircuitError
from qiskit.utils.deprecation import deprecate_func

from .generalized_gates.diagonal import Diagonal
from .generalized_gates.diagonal import Diagonal, DiagonalGate


class FourierChecking(QuantumCircuit):
Expand Down Expand Up @@ -52,7 +53,12 @@ class FourierChecking(QuantumCircuit):
`arXiv:1411.5729 <https://arxiv.org/abs/1411.5729>`_
"""

def __init__(self, f: List[int], g: List[int]) -> None:
@deprecate_func(
since="1.3",
additional_msg="Use qiskit.circuit.library.fourier_checking instead.",
pending=True,
)
def __init__(self, f: Sequence[int], g: Sequence[int]) -> None:
"""Create Fourier checking circuit.
Args:
Expand Down Expand Up @@ -81,17 +87,72 @@ def __init__(self, f: List[int], g: List[int]) -> None:
"{1, -1}."
)

circuit = QuantumCircuit(num_qubits, name=f"fc: {f}, {g}")

# This definition circuit is not replaced by the circuit produced by fourier_checking,
# as the latter produces a slightly different circuit, with DiagonalGates instead
# of Diagonal circuits.
circuit = QuantumCircuit(int(num_qubits), name=f"fc: {f}, {g}")
circuit.h(circuit.qubits)

circuit.compose(Diagonal(f), inplace=True)

circuit.h(circuit.qubits)

circuit.compose(Diagonal(g), inplace=True)

circuit.h(circuit.qubits)

super().__init__(*circuit.qregs, name=circuit.name)
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)


def fourier_checking(f: Sequence[int], g: Sequence[int]) -> QuantumCircuit:
"""Fourier checking circuit.
The circuit for the Fourier checking algorithm, introduced in [1],
involves a layer of Hadamards, the function :math:`f`, another layer of
Hadamards, the function :math:`g`, followed by a final layer of Hadamards.
The functions :math:`f` and :math:`g` are classical functions realized
as phase oracles (diagonal operators with {-1, 1} on the diagonal).
The probability of observing the all-zeros string is :math:`p(f,g)`.
The algorithm solves the promise Fourier checking problem,
which decides if f is correlated with the Fourier transform
of g, by testing if :math:`p(f,g) <= 0.01` or :math:`p(f,g) >= 0.05`,
promised that one or the other of these is true.
The functions :math:`f` and :math:`g` are currently implemented
from their truth tables but could be represented concisely and
implemented efficiently for special classes of functions.
Fourier checking is a special case of :math:`k`-fold forrelation [2].
**Reference Circuit:**
.. plot::
:include-source:
from qiskit.circuit.library import fourier_checking
circuit = fourier_checking([1, -1, -1, -1], [1, 1, -1, -1])
circuit.draw('mpl')
**Reference:**
[1] S. Aaronson, BQP and the Polynomial Hierarchy, 2009 (Section 3.2).
`arXiv:0910.4698 <https://arxiv.org/abs/0910.4698>`_
[2] S. Aaronson, A. Ambainis, Forrelation: a problem that
optimally separates quantum from classical computing, 2014.
`arXiv:1411.5729 <https://arxiv.org/abs/1411.5729>`_
"""
num_qubits = math.log2(len(f))

if len(f) != len(g) or num_qubits == 0 or not num_qubits.is_integer():
raise CircuitError(
"The functions f and g must be given as truth "
"tables, each as a list of 2**n entries of "
"{1, -1}."
)
num_qubits = int(num_qubits)

circuit = QuantumCircuit(num_qubits, name=f"fc: {f}, {g}")
circuit.h(circuit.qubits)
circuit.append(DiagonalGate(f), range(num_qubits))
circuit.h(circuit.qubits)
circuit.append(DiagonalGate(g), range(num_qubits))
circuit.h(circuit.qubits)
return circuit
103 changes: 93 additions & 10 deletions qiskit/circuit/library/graph_state.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
# (C) Copyright IBM 2017, 2024.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -10,13 +10,14 @@
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Graph State circuit."""
"""Graph State circuit and gate."""

from __future__ import annotations

import numpy as np
from qiskit.circuit.quantumcircuit import QuantumCircuit
from qiskit.circuit.quantumcircuit import QuantumCircuit, Gate
from qiskit.circuit.exceptions import CircuitError
from qiskit.utils.deprecation import deprecate_func


class GraphState(QuantumCircuit):
Expand Down Expand Up @@ -56,6 +57,11 @@ class GraphState(QuantumCircuit):
`arXiv:1512.07892 <https://arxiv.org/pdf/1512.07892.pdf>`_
"""

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

graph_state_gate = GraphStateGate(adjacency_matrix)
super().__init__(graph_state_gate.num_qubits, name=f"graph: {adjacency_matrix}")
self.compose(graph_state_gate, qubits=self.qubits, inplace=True)


class GraphStateGate(Gate):
r"""A gate representing a graph state.
Given a graph G = (V, E), with the set of vertices V and the set of edges E,
the corresponding graph state is defined as
.. math::
|G\rangle = \prod_{(a,b) \in E} CZ_{(a,b)} {|+\rangle}^{\otimes V}
Such a state can be prepared by first preparing all qubits in the :math:`+`
state, then applying a :math:`CZ` gate for each corresponding graph edge.
Graph state preparation circuits are Clifford circuits, and thus
easy to simulate classically. However, by adding a layer of measurements
in a product basis at the end, there is evidence that the circuit becomes
hard to simulate [2].
**Reference Circuit:**
.. plot::
:include-source:
from qiskit.circuit import QuantumCircuit
from qiskit.circuit.library import GraphStateGate
import rustworkx as rx
G = rx.generators.cycle_graph(5)
circuit = QuantumCircuit(5)
circuit.append(GraphStateGate(rx.adjacency_matrix(G)), [0, 1, 2, 3, 4])
circuit.decompose().draw('mpl')
**References:**
[1] M. Hein, J. Eisert, H.J. Briegel, Multi-party Entanglement in Graph States,
`arXiv:0307130 <https://arxiv.org/pdf/quant-ph/0307130.pdf>`_
[2] D. Koh, Further Extensions of Clifford Circuits & their Classical Simulation Complexities.
`arXiv:1512.07892 <https://arxiv.org/pdf/1512.07892.pdf>`_
"""

def __init__(self, adjacency_matrix: list | np.ndarray) -> None:
"""
Args:
adjacency_matrix: input graph as n-by-n list of 0-1 lists
Raises:
CircuitError: If adjacency_matrix is not symmetric.
The gate represents a graph state with the given adjacency matrix.
"""

adjacency_matrix = np.asarray(adjacency_matrix)
if not np.allclose(adjacency_matrix, adjacency_matrix.transpose()):
raise CircuitError("The adjacency matrix must be symmetric.")
num_qubits = len(adjacency_matrix)
circuit = QuantumCircuit(num_qubits, name=f"graph: {adjacency_matrix}")

circuit.h(range(num_qubits))
for i in range(num_qubits):
for j in range(i + 1, num_qubits):
super().__init__(name="graph_state", num_qubits=num_qubits, params=[adjacency_matrix])

def _define(self):
adjacency_matrix = self.adjacency_matrix
circuit = QuantumCircuit(self.num_qubits, name=self.name)
circuit.h(range(self.num_qubits))
for i in range(self.num_qubits):
for j in range(i + 1, self.num_qubits):
if adjacency_matrix[i][j] == 1:
circuit.cz(i, j)

super().__init__(*circuit.qregs, name=circuit.name)
self.compose(circuit.to_gate(), qubits=self.qubits, inplace=True)
self.definition = circuit

def validate_parameter(self, parameter):
"""Parameter validation"""
return parameter

@property
def adjacency_matrix(self):
"""Returns the adjacency matrix."""
return self.params[0]

def __eq__(self, other):
return (
isinstance(other, GraphStateGate)
and self.num_qubits == other.num_qubits
and np.all(self.adjacency_matrix == other.adjacency_matrix)
)
Loading

0 comments on commit 92692a2

Please sign in to comment.