Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(circuits): add MBLChainCircuit class #15

Merged
merged 5 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
miamico marked this conversation as resolved.
Show resolved Hide resolved
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
Loading