Skip to content

Commit

Permalink
Merge branch 'IBM-Quantum-Technical-Enablement:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
ritajitmajumdar1 committed Mar 28, 2024
2 parents 800973a + 9f28b81 commit 54881a9
Show file tree
Hide file tree
Showing 3 changed files with 230 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,10 +12,12 @@

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

from ._mbl import MBLChainCircuit
from ._qaoa import QAOAPathCircuit
from ._utils import compute_uncompute

__all__ = [
"MBLChainCircuit",
"QAOAPathCircuit",
"compute_uncompute",
]
123 changes: 123 additions & 0 deletions quantum_enablement/circuits/_mbl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# 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.

"""Many-body localization (MBL) quantum circuits.
[1] Shtanko et.al. Uncovering Local Integrability in Quantum Many-Body Dynamics,
https://arxiv.org/abs/2307.07552
"""


from numpy import pi
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit


class MBLChainCircuit(QuantumCircuit):
"""Parameterized MBL non-periodic chain (i.e. 1D) quantum circuit.
Parameters correspond to interaction strength (θ), and
disorders vector (φ) with one entry per qubit. In 1D,
θ < 0.16π ≈ 0.5 corresponds to the MBL regime; beyond such
critical value the dynamics become ergodic (i.e. thermal).
Disorders are random on a qubit by qubit basis [1].
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] Shtanko et.al. Uncovering Local Integrability in Quantum
Many-Body Dynamics, https://arxiv.org/abs/2307.07552
"""

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

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

self.x(range(1, num_qubits, 2)) # TODO: add optional initial state arg
if barriers and depth > 0:
self.barrier()
evolution = MBLChainEvolution(num_qubits, depth, barriers=barriers)
self.compose(evolution, inplace=True)
if measurements:
self.measure_all(inplace=True, add_bits=True)


class MBLChainEvolution(QuantumCircuit):
"""Parameterized MBL non-periodic chain (i.e. 1D) evolution quantum circuit.
Parameters correspond to interaction strength (θ), and
disorders vector (φ) with one entry per qubit. In 1D,
θ < 0.16π ≈ 0.5 corresponds to the MBL regime; beyond such
critical value the dynamics become ergodic (i.e. thermal).
Disorders are random on a qubit by qubit basis [1].
Args:
num_qubits: number of qubits (must be even).
depth: two-qubit depth.
barriers: if True adds barriers between layers.
Notes:
[1] Shtanko et.al. Uncovering Local Integrability in Quantum
Many-Body Dynamics, https://arxiv.org/abs/2307.07552
"""

def __init__(self, num_qubits: int, depth: int, *, barriers: bool = False) -> None:
num_qubits = _validate_mbl_num_qubits(num_qubits)
depth = _validate_mbl_depth(depth)
barriers = bool(barriers)

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

theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)

for layer in range(depth):
layer_parity = layer % 2
if barriers and layer > 0:
self.barrier()
for qubit in range(layer_parity, num_qubits - 1, 2):
self.cz(qubit, qubit + 1)
self.u(theta, 0, pi, qubit)
self.u(theta, 0, pi, qubit + 1)
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)


def _validate_mbl_num_qubits(num_qubits: int) -> int:
"""Validate number of qubits for MBL circuits."""
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_mbl_depth(depth: int) -> int:
"""Validate depth for MBL circuits."""
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_mbl.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 MBL circuits module."""

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

from quantum_enablement.circuits._mbl import MBLChainCircuit


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 TestMBLChainCircuit:
"""Test suite for the MBLChainCircuit class."""

@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_creation(self, num_qubits, depth):
"""Test MBLChainCircuit creation."""
circuit = MBLChainCircuit(num_qubits, depth, barriers=False, measurements=False)
op_counts = circuit.count_ops()
assert isinstance(circuit, QuantumCircuit), "MBLChainCircuit 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 == (
(num_qubits + 1) if depth else 0
), "Wrong number of parameters."
assert op_counts.get("x", 0) == num_qubits // 2, "Wrong number of X gates."
assert op_counts.get("cz", 0) == (num_qubits - 1) * depth // 2, "Wrong number of CZ gates."
assert op_counts.get("u", 0) == (num_qubits - 1) * depth, "Wrong number of U gates."
assert op_counts.get("p", 0) == num_qubits * depth, "Wrong number of P 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 MBLChainCircuit defaults."""
circuit = MBLChainCircuit(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."

# pylint: disable=duplicate-code
@mark.parametrize("num_qubits", [4, 6])
@mark.parametrize("depth", [0, 2, 4])
def test_barriers(self, num_qubits, depth):
"""Test MBLChainCircuit barriers."""
circuit = MBLChainCircuit(num_qubits, depth, measurements=False, barriers=True)
op_counts = circuit.count_ops()
assert op_counts.get("barrier", 0) == depth, "Wrong number of barriers."
# circuit.remove_operations("barrier")
# assert circuit == MBLChainCircuit(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 MBLChainCircuit measurements."""
circuit = MBLChainCircuit(num_qubits, depth, measurements=True, barriers=False)
op_counts = circuit.count_ops()
assert op_counts.get("measure", 0) == num_qubits, "Wrong number of measurements."
# circuit.remove_final_measurements()
# assert circuit == MBLChainCircuit(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 MBLChainCircuit name."""
circuit = MBLChainCircuit(num_qubits, depth)
assert circuit.name == f"MBLChainCircuit<{num_qubits}, {depth}>", "Wrong circuit name."

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

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

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

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

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

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

0 comments on commit 54881a9

Please sign in to comment.