diff --git a/guppylang/src/guppylang/emulator/builder.py b/guppylang/src/guppylang/emulator/builder.py index 6581c9328..b6cc5841b 100644 --- a/guppylang/src/guppylang/emulator/builder.py +++ b/guppylang/src/guppylang/emulator/builder.py @@ -5,7 +5,7 @@ from __future__ import annotations from dataclasses import dataclass, replace -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any import selene_sim from typing_extensions import Self @@ -39,6 +39,7 @@ class EmulatorBuilder: _progress_bar: bool = False _strict: bool = False _save_planner: bool = False + _custom_args: dict[str, Any] | None = None @property def name(self) -> str | None: @@ -56,6 +57,11 @@ def verbose(self) -> bool: """Whether to print verbose output during the build process.""" return self._verbose + @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 + def with_name(self, value: str | None) -> Self: """Set the name for the emulator instance.""" return replace(self, _name=value) @@ -69,6 +75,31 @@ def with_verbose(self, value: bool) -> Self: """Set whether to print verbose output during the build process.""" return replace(self, _verbose=value) + 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}) + def build(self, package: Package, n_qubits: int) -> EmulatorInstance: """Build an EmulatorInstance from a compiled package. @@ -91,6 +122,7 @@ def build(self, package: Package, n_qubits: int) -> EmulatorInstance: progress_bar=self._progress_bar, strict=self._strict, save_planner=self._save_planner, + **self._custom_args or {}, ) return EmulatorInstance(_instance=instance, _n_qubits=n_qubits) diff --git a/tests/emulator/test_builder.py b/tests/emulator/test_builder.py index 3140c6eba..0368dc3f1 100644 --- a/tests/emulator/test_builder.py +++ b/tests/emulator/test_builder.py @@ -26,6 +26,7 @@ def test_emulator_builder_default_initialization(): assert builder._progress_bar is False assert builder._strict is False assert builder._save_planner is False + assert builder._custom_args is None @patch("guppylang.emulator.builder.selene_sim") @@ -86,6 +87,7 @@ def test_emulator_builder_build_with_custom_parameters( .with_name("custom_emulator") .with_build_dir(build_dir) .with_verbose(True) + .with_build_arg("build_method", "via-llvm-ir") ) n_qubits = 10 @@ -104,6 +106,7 @@ def test_emulator_builder_build_with_custom_parameters( progress_bar=False, strict=False, save_planner=False, + build_method="via-llvm-ir", ) # Check that EmulatorInstance was created correctly @@ -138,18 +141,22 @@ def test_emulator_builder_immutability(): new_builder1 = builder.with_name("test1") new_builder2 = builder.with_build_dir(Path("./build")) new_builder3 = builder.with_verbose(True) + new_builder4 = builder.with_build_arg("custom_arg", 42) # All should be different objects assert new_builder1 is not builder assert new_builder2 is not builder assert new_builder3 is not builder + assert new_builder4 is not builder assert new_builder1 is not new_builder2 assert new_builder2 is not new_builder3 + assert new_builder3 is not new_builder4 # Original should be unchanged assert builder.name is None assert builder.build_dir is None assert builder.verbose is False + assert builder.custom_args is None def test_emulator_builder_reuse():