From 9f28b8155086e78ec61b7b35edee0469ee613804 Mon Sep 17 00:00:00 2001 From: Pedro Rivero Date: Wed, 27 Mar 2024 13:51:22 -0400 Subject: [PATCH] feat(circuits): add `MBLChainCircuit` class (#15) --- quantum_enablement/circuits/__init__.py | 2 + quantum_enablement/circuits/_mbl.py | 123 ++++++++++++++++++++++++ test/unit/circuits/test_mbl.py | 105 ++++++++++++++++++++ 3 files changed, 230 insertions(+) create mode 100644 quantum_enablement/circuits/_mbl.py create mode 100644 test/unit/circuits/test_mbl.py diff --git a/quantum_enablement/circuits/__init__.py b/quantum_enablement/circuits/__init__.py index a49e6e5..3e37700 100644 --- a/quantum_enablement/circuits/__init__.py +++ b/quantum_enablement/circuits/__init__.py @@ -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", ] diff --git a/quantum_enablement/circuits/_mbl.py b/quantum_enablement/circuits/_mbl.py new file mode 100644 index 0000000..4725321 --- /dev/null +++ b/quantum_enablement/circuits/_mbl.py @@ -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 .") + 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 .") + if depth < 0: + raise ValueError(f"Depth ({depth}) must be positive.") + if depth % 2: + raise ValueError(f"Depth ({depth}) must be even.") + return depth diff --git a/test/unit/circuits/test_mbl.py b/test/unit/circuits/test_mbl.py new file mode 100644 index 0000000..bb078c2 --- /dev/null +++ b/test/unit/circuits/test_mbl.py @@ -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 \."): + 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 \."): + MBLChainCircuit(4, "2") # type: ignore