Skip to content

Commit 0078f06

Browse files
authored
Add concurrency support for effective_hamiltonian in labs (#8081)
**Context:** Implements concurrency in `effective_hamiltonian` to support running parallel jobs. **Description of the Change:** **Benefits:** **Possible Drawbacks:** **Related GitHub Issues:**
1 parent 68998c5 commit 0078f06

File tree

4 files changed

+90
-12
lines changed

4 files changed

+90
-12
lines changed

doc/releases/changelog-dev.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,12 @@
515515
[(#8156)](https://github.com/PennyLaneAI/pennylane/pull/8156)
516516
<h3>Labs: a place for unified and rapid prototyping of research software 🧪</h3>
517517

518-
* Fixed a queueing issue in `ResourceOperator` tests.
518+
* Added concurrency support for `effective_hamiltonian` in labs.
519+
[(#8081)](https://github.com/PennyLaneAI/pennylane/pull/8081)
520+
521+
* Fixed a queueing issue in `ResourceOperator` tests.
519522
[(#8204)](https://github.com/PennyLaneAI/pennylane/pull/8204)
520-
523+
521524
* The module `qml.labs.zxopt` has been removed as its functionalities are now available in the
522525
submodule :mod:`~.transforms.zx`. The same functions are available, but their signature
523526
may have changed.

pennylane/labs/tests/trotter_error/product_formulas/test_bch.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,3 +277,45 @@ def test_against_matrix_log(fragments, product_formula):
277277
log_error = log - (1j * t * ham) / t**order
278278

279279
assert np.allclose(np.linalg.norm(bch_error - log_error), 0)
280+
281+
282+
params = [
283+
("serial", 1),
284+
("mp_pool", 1),
285+
("mp_pool", 2),
286+
("cf_procpool", 1),
287+
("cf_procpool", 2),
288+
("cf_threadpool", 1),
289+
("cf_threadpool", 2),
290+
("mpi4py_pool", 1),
291+
("mpi4py_pool", 2),
292+
("mpi4py_comm", 1),
293+
("mpi4py_comm", 2),
294+
]
295+
296+
297+
@pytest.mark.parametrize("backend, num_workers", params)
298+
def test_effective_hamiltonian_backend(backend, num_workers, mpi4py_support):
299+
"""Test that effective_hamiltonian function runs without errors for different backends."""
300+
301+
if backend in {"mpi4py_pool", "mpi4py_comm"} and not mpi4py_support:
302+
pytest.skip(f"Skipping test: '{backend}' requires mpi4py, which is not installed.")
303+
304+
r, delta = 1, 0.5
305+
306+
fragments = {0: np.random.random(size=(3, 3)), 1: np.random.random(size=(3, 3))}
307+
n_frags = len(fragments)
308+
309+
first_order = ProductFormula(list(range(n_frags)), coeffs=[delta / r] * n_frags) ** r
310+
actual = effective_hamiltonian(
311+
first_order, fragments, order=2, num_workers=num_workers, backend=backend
312+
)
313+
314+
ham = sum(fragments.values(), np.zeros(shape=(3, 3)))
315+
expected = np.zeros(shape=(3, 3), dtype=np.complex128)
316+
for j in range(n_frags - 1):
317+
for k in range(j + 1, n_frags):
318+
expected += (1 / 2) * nested_commutator([fragments[j], fragments[k]])
319+
expected *= 1j / r * delta
320+
321+
assert np.allclose(1j * delta * (expected + ham), actual)

pennylane/labs/tests/trotter_error/product_formulas/test_error.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@
3232
def test_perturbation_error(backend, parallel_mode, mpi4py_support):
3333
"""Test that perturbation error function runs without errors for different backends."""
3434

35-
print(f"{backend}, {mpi4py_support}")
36-
3735
if backend in {"mpi4py_pool", "mpi4py_comm"} and not mpi4py_support:
3836
pytest.skip(f"Skipping test: '{backend}' requires mpi4py, which is not installed.")
3937

pennylane/labs/trotter_error/product_formulas/error.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from pennylane.labs.trotter_error.product_formulas.bch import bch_expansion
2424
from pennylane.labs.trotter_error.product_formulas.product_formula import ProductFormula
2525

26+
# pylint: disable=too-many-arguments, too-many-positional-arguments
27+
2628

2729
class _AdditiveIdentity:
2830
"""Only used to initialize accumulators for summing Fragments"""
@@ -39,14 +41,25 @@ def effective_hamiltonian(
3941
fragments: dict[Hashable, Fragment],
4042
order: int,
4143
timestep: float = 1.0,
44+
num_workers: int = 1,
45+
backend: str = "serial",
4246
):
43-
r"""Compute the effective Hamiltonian :math:`\hat{H}_{eff} = \hat{H} + \hat{\epsilon}` that corresponds to a given product formula.
47+
r"""Compute the effective Hamiltonian :math:`\hat{H}_{eff} = \hat{H} + \hat{\epsilon}` that
48+
corresponds to a given product formula.
4449
4550
Args:
46-
product_formula (ProductFormula): A product formula used to approximate the time-evolution operator for a Hamiltonian.
47-
fragments (Dict[Hashable, :class:`~.pennylane.labs.trotter_error.Fragment`): The fragments that sum to the Hamiltonian. The keys in the dictionary must match the labels used to build the :class:`~.pennylane.labs.trotter_error.ProductFormula` object.
51+
product_formula (ProductFormula): A product formula used to approximate the time-evolution
52+
operator for a Hamiltonian.
53+
fragments (Dict[Hashable, :class:`~.pennylane.labs.trotter_error.Fragment`): The fragments
54+
that sum to the Hamiltonian. The keys in the dictionary must match the labels used to
55+
build the :class:`~.pennylane.labs.trotter_error.ProductFormula` object.
4856
order (int): The order of the approximatation.
4957
timestep (float): The timestep for simulation.
58+
num_workers (int): the number of concurrent units used for the computation. Default value is
59+
set to 1.
60+
backend (string): the executor backend from the list of supported backends.
61+
Available options : "mp_pool", "cf_procpool", "cf_threadpool", "serial", "mpi4py_pool",
62+
"mpi4py_comm". Default value is set to "serial".
5063
5164
**Example**
5265
@@ -78,15 +91,38 @@ def effective_hamiltonian(
7891
raise ValueError("Fragments do not match product formula")
7992

8093
bch = bch_expansion(product_formula(1j * timestep), order)
81-
eff = _AdditiveIdentity()
8294

83-
for ith_order in bch:
84-
for commutator, coeff in ith_order.items():
85-
eff += coeff * nested_commutator(_insert_fragments(commutator, fragments))
95+
executor = concurrency.backends.get_executor(backend)
96+
with executor(max_workers=num_workers) as ex:
97+
partial_sum = ex.starmap(
98+
_eval_commutator,
99+
(
100+
(commutator, coeff, fragments)
101+
for ith_order in bch
102+
for commutator, coeff in ith_order.items()
103+
),
104+
)
86105

106+
eff = _AdditiveIdentity()
107+
for term in partial_sum:
108+
eff += term
87109
return eff
88110

89111

112+
def _eval_commutator(commutator, coeff, fragments):
113+
r"""Computes a commutator after replacing symbols in the commutator with concrete fragments.
114+
115+
Args:
116+
commutator: commutator to be evaluated
117+
coeff (complex): coefficient associated with the commutator
118+
fragments (dict): dictionary representing a sequence of fragments
119+
120+
Returns:
121+
ndarray: the evaluated form of the commutator
122+
"""
123+
return coeff * nested_commutator(_insert_fragments(commutator, fragments))
124+
125+
90126
def _insert_fragments(
91127
commutator: tuple[Hashable], fragments: dict[Hashable, Fragment]
92128
) -> tuple[Fragment]:
@@ -100,7 +136,6 @@ def _insert_fragments(
100136
)
101137

102138

103-
# pylint: disable=too-many-arguments, too-many-positional-arguments
104139
def perturbation_error(
105140
product_formula: ProductFormula,
106141
fragments: dict[Hashable, Fragment],

0 commit comments

Comments
 (0)