Skip to content

Commit

Permalink
feat(circuits): add QAOAPathCircuit class (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
pedrorrivero committed Mar 26, 2024
1 parent 671fd04 commit 967e1c2
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
2 changes: 2 additions & 0 deletions quantum_enablement/circuits/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@

"""Quantum circuit library and tools."""

from ._qaoa import QAOAPathCircuit
from ._utils import compute_uncompute

__all__ = [
"QAOAPathCircuit",
"compute_uncompute",
]
92 changes: 92 additions & 0 deletions quantum_enablement/circuits/_qaoa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 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
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Quantum Approximate Optimization Algorithm (QAOA) quantum circuits.
[1] Farhi et.al. A Quantum Approximate Optimization Algorithm,
https://arxiv.org/abs/1411.4028
"""


from qiskit import QuantumCircuit
from qiskit.circuit import ParameterVector


# TODO: add graph max-cut weights input arg
class QAOAPathCircuit(QuantumCircuit):
"""Parameterized QAOA acyclic line graph quantum circuit.
The cost parameter-vector is labeled γ, and the mixer
parameter-vector β. Overall, there will be one parameter per
unit of two-qubit depth: half in γ and half in ß [1].
Weights in the generating max-cut graph are all equal to one.
Args:
num_qubits: number of qubits (must be even).
depth: two-qubit depth (must be even).
barriers: if True adds barriers between layers.
measurements: if True adds measurements at the end.
Notes:
[1] Farhi et.al. A Quantum Approximate Optimization Algorithm,
https://arxiv.org/abs/1411.4028
"""

def __init__(
self, num_qubits: int, depth: int, *, barriers: bool = False, measurements: bool = False
) -> None:
num_qubits = _validate_qaoa_num_qubits(num_qubits)
depth = _validate_qaoa_depth(depth)
barriers = bool(barriers)
measurements = bool(measurements)

super().__init__(num_qubits, name=f"QAOAPathCircuit<{num_qubits}, {depth}>")

gammas = ParameterVector("γ", depth // 2)
betas = ParameterVector("β", depth // 2)

self.h(range(num_qubits))
for layer in range(depth // 2):
if barriers:
self.barrier()
for qubit in range(0, num_qubits - 1, 2):
self.rzz(gammas[layer], qubit, qubit + 1)
for qubit in range(1, num_qubits - 1, 2):
self.rzz(gammas[layer], qubit, qubit + 1)
for qubit in range(num_qubits):
self.rx(betas[layer], qubit)
if measurements:
self.measure_all()


def _validate_qaoa_num_qubits(num_qubits: int) -> int:
"""Validate number of qubits for QAOA circuits."""
# pylint: disable=duplicate-code
if not isinstance(num_qubits, int):
raise TypeError(f"Invalid num. qubits type {type(num_qubits)}, expected <int>.")
if num_qubits <= 2:
raise ValueError(f"Number of qubits ({num_qubits}) must be greater than two.")
if num_qubits % 2:
raise ValueError(f"Number of qubits ({num_qubits}) must be even.")
return num_qubits


def _validate_qaoa_depth(depth: int) -> int:
"""Validate depth for QAOA circuits."""
# pylint: disable=duplicate-code
if not isinstance(depth, int):
raise TypeError(f"Invalid depth type {type(depth)}, expected <int>.")
if depth < 0:
raise ValueError(f"Depth ({depth}) must be positive.")
if depth % 2:
raise ValueError(f"Depth ({depth}) must be even.")
return depth
105 changes: 105 additions & 0 deletions test/unit/circuits/test_qaoa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 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
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Test QAOA circuits module."""


from pytest import mark, raises # pylint: disable=import-error
from qiskit import QuantumCircuit

from quantum_enablement.circuits._qaoa import QAOAPathCircuit


def two_qubit_depth_filter(instruction):
"""Filter instructions which do not contribute to two-qubit depth."""
num_qubits = instruction.operation.num_qubits
name = instruction.operation.name
return num_qubits > 1 and name != "barrier"


class TestQAOAPathCircuit:
"""Test suite for the QAOAPathCircuit class."""

@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_creation(self, num_qubits, depth):
"""Test QAOAPathCircuit creation."""
circuit = QAOAPathCircuit(num_qubits, depth, barriers=False, measurements=False)
op_counts = circuit.count_ops()
assert isinstance(circuit, QuantumCircuit), "QAOAPathCircuit must be a QuantumCircuit."
assert circuit.num_qubits == num_qubits, "Wrong number of qubits."
assert circuit.depth(two_qubit_depth_filter) == depth, "Wrong two-qubit depth."
assert circuit.num_parameters == depth, "Wrong number of parameters."
assert op_counts.get("h", 0) == num_qubits, "Wrong number of H gates."
assert op_counts.get("rzz", 0) == (num_qubits - 1) * (
depth // 2
), "Wrong number of RZZ gates."
assert op_counts.get("rx", 0) == num_qubits * (depth // 2), "Wrong number of RX gates."
assert op_counts.get("barrier", 0) == 0, "Wrong number of barriers."
assert op_counts.get("measure", 0) == 0, "Wrong number of measurements."

def test_defaults(self):
"""Test QAOAPathCircuit defaults."""
circuit = QAOAPathCircuit(4, 4) # Note: default barriers and measurements are False.
op_counts = circuit.count_ops()
assert op_counts.get("barrier", 0) == 0, "By default barriers should not be present."
assert op_counts.get("measure", 0) == 0, "By default measurements should not be present."

@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_barriers(self, num_qubits, depth):
"""Test QAOAPathCircuit barriers."""
circuit = QAOAPathCircuit(num_qubits, depth, barriers=True, measurements=False)
op_counts = circuit.count_ops()
assert op_counts.get("barrier", 0) == depth // 2, "Wrong number of barriers."
# circuit.remove_operations("barrier")
# assert circuit == QAOAPathCircuit(num_qubits, depth, measurements=False, barriers=False)

@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_measurements(self, num_qubits, depth):
"""Test QAOAPathCircuit measurements."""
circuit = QAOAPathCircuit(num_qubits, depth, barriers=False, measurements=True)
op_counts = circuit.count_ops()
assert op_counts.get("measure", 0) == num_qubits, "Wrong number of measurements."
assert op_counts.get("barrier", 0) == 1, "Measurements should be preceded by a barrier."
# circuit.remove_final_measurements()
# assert circuit == QAOAPathCircuit(num_qubits, depth, measurements=False, barriers=False)

@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_name(self, num_qubits, depth):
"""Test QAOAPathCircuit name."""
circuit = QAOAPathCircuit(num_qubits, depth)
assert circuit.name == f"QAOAPathCircuit<{num_qubits}, {depth}>", "Wrong circuit name."

def test_invalid_num_qubits(self):
"""Test QAOAPathCircuit with invalid number of qubits."""
with raises(ValueError, match=r"Number of qubits \(2\) must be greater than two."):
QAOAPathCircuit(2, 2)

with raises(ValueError, match=r"Number of qubits \(3\) must be even\."):
QAOAPathCircuit(3, 2)

with raises(TypeError, match=r"Invalid num\. qubits type .*, expected <int>\."):
QAOAPathCircuit("4", 2) # type: ignore

def test_invalid_depth(self):
"""Test QAOAPathCircuit with invalid depth."""
with raises(ValueError, match=r"Depth \(-1\) must be positive\."):
QAOAPathCircuit(4, -1)

with raises(ValueError, match=r"Depth \(1\) must be even\."):
QAOAPathCircuit(4, 1)

with raises(TypeError, match=r"Invalid depth type .*, expected <int>\."):
QAOAPathCircuit(4, "2") # type: ignore

0 comments on commit 967e1c2

Please sign in to comment.