Source code for tno.quantum.utils._noise_config

"""This module contains the ``NoiseConfig`` class."""

from __future__ import annotations

import importlib.util
from collections.abc import Callable, Mapping
from typing import Any

from tno.quantum.utils._base_config import BaseConfig
from tno.quantum.utils.noise import depolarizing
from tno.quantum.utils.serialization import Serializable
from tno.quantum.utils.validation import check_instance

if importlib.util.find_spec("pennylane") is not None:
    import pennylane as qml
    from pennylane.devices import Device

    if int(qml.__version__.split(".")[1]) >= 39:
        from pennylane.transforms import decompose
    else:
        from pennylane.devices.preprocess import decompose

    def device_prepend_decompose(device: Device, gate_set: list[str]) -> Device:
        """Create a transformed device with prepended decompose preprocessing."""

        class TransformedDevice(type(device)):  # type: ignore[misc]
            """A transformed device with updated decompose method."""

            def __init__(
                self,
                original_device: Device,
                transform: qml.transforms.core.TransformDispatcher,
                gate_set: list[str],
            ) -> None:
                for key, value in original_device.__dict__.items():
                    self.__setattr__(key, value)
                self._transform = transform
                self._original_device = original_device
                self._gate_set = gate_set

            def __repr__(self) -> str:
                return (
                    f"Transformed Device({self._original_device.__repr__()}"
                    f" with additional initial preprocess transform"
                    f" {self._transform})"
                )

            def _stopping_condition(self, op: qml.operation.Operator) -> bool:
                return op.name in self._gate_set

            def preprocess(
                self,
                execution_config: qml.devices.ExecutionConfig
                | None = qml.devices.DefaultExecutionConfig,
            ) -> tuple[
                qml.transforms.core.TransformProgram,
                qml.devices.ExecutionConfig,
            ]:
                program, config = self._original_device.preprocess(execution_config)

                if int(qml.__version__.split(".")[1]) >= 39:
                    program.insert_front_transform(
                        self._transform, gate_set=self._stopping_condition
                    )
                else:
                    program.insert_front_transform(
                        self._transform,
                        stopping_condition=self._stopping_condition,
                    )
                return program, config

        return TransformedDevice(device, decompose, gate_set)

[docs] class NoiseConfig(BaseConfig[Device]): """Configuration class for creating noise models for PennyLane devices. The :py:meth:`get_instance` method of :py:class:`NoiseConfig` class can be used to transform a device into a noisy device. Example: >>> import pennylane as qml >>> from tno.quantum.utils import NoiseConfig >>> >>> # List all supported noise models >>> list(NoiseConfig.supported_items()) ['depolarizing'] >>> >>> # Create a noisy device from a device >>> device = qml.device("default.mixed", wires=1) >>> config = NoiseConfig(name="depolarizing", options={ ... "p": 0.01, ... }) >>> noisy_device = config.get_instance(device) >>> >>> # Or create a noise configuration from a `qml.NoiseModel` >>> model = qml.NoiseModel(...) # doctest: +SKIP >>> config = NoiseConfig.from_model(model) # doctest: +SKIP >>> noisy_device = config.get_instance(device) # doctest: +SKIP """
[docs] def __init__( self, name: str, options: Mapping[str, Any] | None = None, *, custom: Callable[[Device], Device] | None = None, ) -> None: """Init :py:class:`NoiseConfig`. Args: name: Name used to determine the name of the to instantiate class. options: Keyword arguments to be passed to the constructor of the class. custom: Custom device transform function. """ if custom is None: super().__init__(name, options) else: self._name = "" self._options = {} self._custom = custom
[docs] @staticmethod def from_model( model: qml.NoiseModel, gates: list[str] | None = None ) -> NoiseConfig: """Create noise configuration from custom model. Args: model: PennyLane noise model. gates: List of gates into which the circuit is decomposed before executed on the device. See the `list of operations <https://docs.pennylane.ai/en/stable/introduction/operations.html>`_. """ # Validate arguments model = check_instance(model, "model", qml.NoiseModel) if gates is not None and ( not isinstance(gates, list) or any(not isinstance(gate, str) for gate in gates) ): msg = "Argument `gates` must be a list of strings" raise ValueError(msg) # Define transform function based on model and gates def transform(device: Device) -> Device: if gates is not None: device = device_prepend_decompose(device, gates) return qml.add_noise(device, model) return NoiseConfig("", custom=transform)
[docs] def get_instance(self, device: Device, *args: Any, **kwargs: Any) -> Device: if self._custom is not None: return self._custom(device, *args, **kwargs) return super().get_instance(device, *args, **kwargs)
[docs] @staticmethod def supported_items() -> dict[str, Callable[..., Device]]: return {"depolarizing": depolarizing}
def _serialize(self) -> dict[str, Any]: if self._custom is not None: msg = ( "Serialization of noise configurations with custom transform " "method is not supported yet." ) raise NotImplementedError(msg) return {"name": self.name, "options": Serializable.serialize(self.options)}