Source code for pennylane.concurrency.executors.base

# Copyright 2018-2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
r"""
Contains concurrent executor abstractions for task-based workloads.

All of the base abstractions for building an executor follow a simplified `concurrent.futures.Executor <https://docs.python.org/3/library/concurrent.futures.html#executor-objects>`_ interface. Given the differences observed in support for ``*args`` and ``**kwargs`` in various modes of execution, the abstractions provide a fixed API to interface with each backend, performing function and argument transformations, where necessary.

To build a new executor backend, the following classes provide scaffolding to simplify abstracting the function call signatures between each backend interface layer.

.. currentmodule:: pennylane.concurrency.executors.base

.. autosummary::
    :toctree: api

    ExecBackendConfig
    RemoteExec
    IntExec
    ExtExec

"""

import abc
import os
import sys
from collections.abc import Callable, Sequence
from dataclasses import dataclass
from typing import Optional


[docs] @dataclass class ExecBackendConfig: r""" Executor backend configuration data-class. To allow for differences in each executor backend implementation, this class dynamically defines overloads to the main API functions. For explicitly-defined executors, this class is optional, and is provided for convenience with hierarchical inheritance class structures, where subtle differences are best resolved dynamically, rather than with API modifications. All initial values default to ``None``. Args: submit_fn (str, None): The backend function that best matches the ``submit`` API call. map_fn (str): The backend function that best matches the ``map`` API call. starmap_fn (str, None): The backend function that best matches the ``starmap`` API call. shutdown_fn (str, None): The backend function that best matches the ``shutdown`` API call. submit_unpack (bool, None): Whether the arguments to ``submit`` are to be unpacked (``*args``) or directly passed (``args``) to ``submit_fn``. map_unpack (bool): Whether the arguments to ``map`` are to be unpacked (``*args``) or directly passed (``args``) to ``map_unpack``. blocking (bool, None): Whether the return values from ``submit``, ``map`` and ``starmap`` are blocking (synchronous) or non-blocking (asynchronous). """ submit_fn: Optional[str] = None map_fn: Optional[str] = None starmap_fn: Optional[str] = None shutdown_fn: Optional[str] = None submit_unpack: Optional[bool] = None map_unpack: Optional[bool] = None blocking: Optional[bool] = None
[docs] class RemoteExec(abc.ABC): r""" Abstract base class for defining a task-based parallel executor backend. This ABC is intended to provide the highest-layer abstraction in the inheritance tree. Args: max_workers (int): The size of the worker pool. This value will directly control (given backend support) the number of concurrent executions that the backend can avail of. Generally, this value should match the number of physical cores on the executing system, or with the executing remote environment. Defaults to ``None``, which defers to support provided by the child class. persist (bool): Indicates to the executor backend that the state should persist between calls. If supported, this allows a pre-configured device to be reused for several computations but removing the need to automatically shutdown. The pool may require manual shutdown upon completion of the work, even if the executor goes out-of-scope. *args: Non keyword arguments to pass through to executor backend. **kwargs: Keyword arguments to pass through to executor backend. """ def __init__(self, max_workers: Optional[int] = None, persist: bool = False, **kwargs): self._size = max_workers self._persist = persist self._inputs = kwargs self._cfg = ExecBackendConfig() self._persistent_backend = None def __call__(self, dispatch: str, fn: Callable, *args, **kwargs): r""" dispatch: the named method to pass the function parameters fn: the callable function to run on the executor backend args: the arguments to pass to ``fn`` kwargs: the keyword arguments to pass to ``fn`` """ return getattr(self, dispatch)(fn, *args, **kwargs) @property def size(self): """ The size of the worker pool for the given executor. """ return self._size @property def persist(self): """ Indicates whether the executor will maintain its configured state between calls. """ return self._persist def __enter__(self): """Context-manager entry point for executor. Returns: RemoteExec: this instance """ return self def __exit__(self, exception_type, exception_value, traceback): """Context-manager clean-up for executor.""" if not self._persist: self.shutdown()
[docs] @abc.abstractmethod def submit(self, fn: Callable, *args, **kwargs): """ Single function submission for remote execution with provided args. """
[docs] @abc.abstractmethod def map(self, fn: Callable, *args, **kwargs): r""" Single iterable map for batching execution of fn over data entries. Length of every entry in ``*args`` must be consistent. kwargs are assumed as broadcastable to each function call. """
[docs] @abc.abstractmethod def starmap(self, fn: Callable, args: Sequence, **kwargs): """ Single iterable map for batching execution of fn over data entries, with each entry being a tuple of arguments to fn. """
[docs] @abc.abstractmethod def shutdown(self): """ Disconnect from executor backend and release acquired resources. """
def _submit_fn(self, backend): # pragma: no cover "Helper utility to return the config-defined submit function for the given backend." return getattr(backend, self._cfg.submit_fn) def _map_fn(self, backend): # pragma: no cover "Helper utility to return the config-defined map function for the given backend." return getattr(backend, self._cfg.map_fn) def _starmap_fn(self, backend): # pragma: no cover "Helper utility to return the config-defined starmap function for the given backend." return getattr(backend, self._cfg.starmap_fn) def _shutdown_fn(self, backend): # pragma: no cover "Helper utility to return the config-defined shutdown function for the given backend." return getattr(backend, self._cfg.shutdown_fn) def _get_backend(self): # pragma: no cover "Convenience method to return the existing backend if persistence is enabled, or to create a new temporary backend with the defined size if not." if self._persist: return self._persistent_backend return self._exec_backend()(self._size) @classmethod @abc.abstractmethod def _exec_backend(cls): "Return the class type of the given backend variant." @staticmethod def _get_system_core_count(): # pragma: no cover if sys.version_info.minor >= 13: return os.process_cpu_count() # pylint: disable=no-member return os.cpu_count()
[docs] class IntExec(RemoteExec, abc.ABC): r""" Executor class for native Python library concurrency support. This class is intended to be used as the parent-class for building Python-native executors, allowing an ease of distinction from the external-based classes implemented using :class:`~.ExtExec`. Args: max_workers (int): The size of the worker pool. This value will directly control (given backend support) the number of concurrent executions that the backend can avail of. Generally, this value should match the number of physical cores on the executing system, or with the executing remote environment. Defaults to ``None``, leaving interpretation to the child class. persist (bool): Indicates to the executor backend that the state should persist between calls. If supported, this allows a pre-configured device to be reused for several computations but removing the need to automatically shutdown. The pool may require manual shutdown upon completion of the work, even if the executor goes out-of-scope. *args: Non keyword arguments to pass through to executor backend. **kwargs: Keyword arguments to pass through to executor backend. """
[docs] class ExtExec(RemoteExec, abc.ABC): r""" Executor class for external packages providing concurrency support. This class is intended to be used as the parent-class for building external package-based executors, allowing an ease of distinction from the Python-native classes implemented using :class:`~.IntExec`. Args: max_workers (int): The size of the worker pool. This value will directly control (given backend support) the number of concurrent executions that the backend can avail of. Generally, this value should match the number of physical cores on the executing system, or with the executing remote environment. Defaults to ``None``, leaving interpretation to the child class. persist (bool): Indicates to the executor backend that the state should persist between calls. If supported, this allows a pre-configured device to be reused for several computations but removing the need to automatically shutdown. The pool may require manual shutdown upon completion of the work, even if the executor goes out-of-scope. *args: Non keyword arguments to pass through to executor backend. **kwargs: Keyword arguments to pass through to executor backend. """