generated from pedrorrivero/pyproject-qiskit
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(circuits): add
QAOAPathCircuit
class (#14)
- Loading branch information
1 parent
671fd04
commit 967e1c2
Showing
3 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |