Source code for guppylang.emulator.builder

"""
Compiling and building emulator instances for guppy programs.
"""

from __future__ import annotations

from dataclasses import dataclass, replace
from typing import TYPE_CHECKING, Any, Literal

import selene_sim
from typing_extensions import Self

from .instance import EmulatorInstance

if TYPE_CHECKING:
    from collections.abc import Sequence
    from pathlib import Path

    from hugr.package import Package
    from selene_core import BuildPlanner, QuantumInterface, Utility

    from ._args import EntrypointArgSpec


#: The quantum platform to target when building an emulator instance.
#: ``"helios"`` is the default and targets the Helios QIS.
#: ``"sol"`` targets the Sol QIS.
Platform = Literal["helios", "sol"]


[docs] @dataclass(frozen=True) class EmulatorBuilder: """A builder class for creating EmulatorInstance objects. Supports configuration parameters for compilation of emulator instances. """ # interface supported parameters _name: str | None = None _build_dir: Path | None = None _verbose: bool = False # selene_sim supported parameters, may be added in the future: _planner: BuildPlanner | None = None _utilities: Sequence[Utility] | None = None _interface: QuantumInterface | None = None _progress_bar: bool = False _strict: bool = False _save_planner: bool = False _custom_args: dict[str, Any] | None = None # further interface parameter (appending to preserve backwards compatibility). _platform: Platform = "helios" @property def name(self) -> str | None: """User specified name for the emulator instance. Defaults to None.""" return self._name @property def build_dir(self) -> Path | None: """Directory to store intermediate build files and execution results. Defaults to None, in which case a temporary directory is used.""" return self._build_dir @property def verbose(self) -> bool: """Whether to print verbose output during the build process.""" return self._verbose @property def platform(self) -> Platform: """The quantum platform to target. Defaults to ``"helios"``.""" return self._platform @property def custom_args(self) -> dict[str, Any] | None: """Custom build arguments passed to selene_sim.build.""" return dict(self._custom_args) if self._custom_args is not None else None
[docs] def with_name(self, value: str | None) -> Self: """Set the name for the emulator instance.""" return replace(self, _name=value)
[docs] def with_build_dir(self, value: Path | None) -> Self: """Set the build directory for the emulator instance, see `EmulatorBuilder.build_dir`.""" return replace(self, _build_dir=value)
[docs] def with_verbose(self, value: bool) -> Self: """Set whether to print verbose output during the build process.""" return replace(self, _verbose=value)
[docs] def with_platform(self, value: Platform) -> Self: """Set the quantum platform to target when building the emulator. ``"helios"`` (the default) targets the Helios QIS. ``"sol"`` targets the Sol QIS. """ return replace(self, _platform=value)
[docs] def with_build_arg(self, key: str, value: Any) -> Self: """Selene builds may support additional customisable arguments, e.g. to override the choice of compilation route. This is passed to selene_sim.build as kwargs. For example: .. code-block:: python .with_build_arg("build_method", "via-llvm_ir") is equivalent to .. code-block:: python selene_sim.build(..., build_method="via-llvm_ir") which saves LLVM IR into the build directory rather than saving bitcode. """ if self._custom_args is None: return replace(self, _custom_args={key: value}) else: return replace(self, _custom_args=self._custom_args | {key: value})
[docs] def build( self, package: Package, n_qubits: int, arg_specs: Sequence[EntrypointArgSpec] = (), ) -> EmulatorInstance: """Build an EmulatorInstance from a compiled package. Args: package: The compiled HUGR package to build the emulator from. n_qubits: The number of qubits to allocate for the emulator instance. arg_specs: The runtime argument schema of the (wrapped) entrypoint, if it takes arguments. Used to validate values passed to ``run``. Returns: An EmulatorInstance that can be used to run the compiled program. """ custom_args = self._custom_args or {} instance = selene_sim.build( # type: ignore[attr-defined] package, name=self._name, build_dir=self._build_dir, interface=self._interface, utilities=self._utilities, verbose=self._verbose, planner=self._planner, progress_bar=self._progress_bar, strict=self._strict, save_planner=self._save_planner, platform=self._platform, **custom_args, ) return EmulatorInstance( _instance=instance, _n_qubits=n_qubits, _arg_specs=tuple(arg_specs) )