From 4e542a0f092644e9e417c536f4c1cc8e42f6a3e9 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Mon, 6 Oct 2025 14:25:38 +0100 Subject: [PATCH 1/8] feat: add Helios Pecos Emulator noise --- quantinuum_schemas/__init__.py | 6 + .../models/quantinuum_systems_noise.py | 181 ++++++++++++++++++ uv.lock | 4 +- 3 files changed, 189 insertions(+), 2 deletions(-) diff --git a/quantinuum_schemas/__init__.py b/quantinuum_schemas/__init__.py index d41ba76..e8354fa 100644 --- a/quantinuum_schemas/__init__.py +++ b/quantinuum_schemas/__init__.py @@ -24,6 +24,10 @@ StabilizerSimulator, StatevectorSimulator, ) +from quantinuum_schemas.models.quantinuum_systems_noise import ( + HeliosErrorParams, + UserErrorParams, +) __all__ = [ "AerConfig", @@ -46,4 +50,6 @@ "CoinflipSimulator", "MatrixProductStateSimulator", "ClassicalReplaySimulator", + "HeliosErrorParams", + "UserErrorParams", ] diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index a96eea6..2e9744d 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -1,10 +1,191 @@ """Validation classes for Quantinuum Systems noise models.""" from typing import Optional, Tuple, Union +from typing_extensions import Self + +from pydantic import Field, model_validator from .base import BaseModel +_KEYS_1Q_PAULI = {"X", "Y", "Z"} +_KEYS_1Q_EMISSION = _KEYS_1Q_PAULI | {"L"} +_KEYS_2Q_PAULI = { + "IX", + "IY", + "IZ", + "XI", + "XX", + "XY", + "XZ", + "YI", + "YX", + "YY", + "YZ", + "ZI", + "ZX", + "ZY", + "ZZ", +} +_KEYS_2Q_EMISSION = _KEYS_2Q_PAULI | { + "IL", + "XL", + "YL", + "ZL", + "LI", + "LX", + "LY", + "LZ", + "LL", +} + + +def _validate_distribution( + name: str, distribution: dict[str, float], keys: set[str] +) -> None: + """ + Validate that the distribution keys are in the allowed set and that + all values are between 0 and 1. + """ + for k, v in distribution.items(): + assert k in keys, ( + f"{name} keys must be a subset of {keys}, " + f"but an invalid entry was provided with key '{k}'" + ) + assert 0 <= v <= 1, ( + f"{name} values must be between 0 and 1, but {k}={v} was provided" + ) + if distribution: # If non-empty + sum_values = sum(distribution.values()) + assert abs(1 - sum_values) < 1e-9, ( + f"{name} values must sum to 1 +/- 1e-9, but the provided values sum to {sum_values}" + ) + + +class HeliosErrorParams(BaseModel): + """ + Error model configuration for emulation of Quantinuum's Helios System. + + parameters: + p_prep: Probability of error during preparation. Alias: p_init. + p_meas_0: Probability of flipping 0 to 1 during measurement. + p_meas_1: Probability of flipping 1 to 0 during measurement. + p1: Probability of error after single-qubit gates. + p2: Probability of error after two-qubit gates. + p1_emission_ratio: Emission ratio for single-qubit gates. + p2_emission_ratio: The proportion of two-qubit errors that are emission faults. + p1_pauli_model: The pauli model for single-qubit gates, e.g. + `{"X": 0.1,"Y": 0.2,"Z": 0.3}`. + p1_emission_model: The emission model for single-qubit gates, e.g. + `{"X": 0.1,"Y": 0.2,"Z": 0.3}`. + p2_pauli_model: The pauli model for two-qubit gates, e.g. + `{"XX": 0.2, "YZ": 0.3, "ZI": 0.4}`. + p2_emission_model: The emission model for two-qubit gates, e.g. + `{"XX": 0.2, "YZ": 0.3, "ZI": 0.4}`. + p_prep_leak_ratio: Preparation leakage ratio. + p1_seepage_prob: Probability of a leaked qubit being seeped (released from leakage) for + single-qubit. + p2_seepage_prob: Probability of a leaked qubit being seeped (released from leakage) for + two-qubit. + scale: Overall scaling factor. + memory_scale: Memory scaling factor. + prep_scale: Initial scaling factor. Alias: init_scale. + meas_scale: Measurement scaling factor. + p1_scale: Single-qubit gate scaling factor. + p2_scale: Two-qubit gate scaling factor. + emission_scale: Emission scaling factor. + przz_a: Scaling parameters for RZZ gate error rate - coefficient a. + przz_b: Scaling parameters for RZZ gate error rate - coefficient b. + przz_c: Scaling parameters for RZZ gate error rate - coefficient c. + przz_d: Scaling parameters for RZZ gate error rate - coefficient d. + przz_power: Power parameter for RZZ error scaling. + p_crosstalk_meas: Probability of crosstalk during measurement operations. + Alias: p_meas_crosstalk. + p_crosstalk_init: Probability of crosstalk during initialization operations. + Alias: p_prep_crosstalk. + noiseless_gates: List of gates to be treated as noiseless. + coherent_dephasing: Whether to include coherent dephasing. + coherent_to_incoherent_factor: Coherent to incoherent conversion factor. + leak2depolar: Replace leakage with general noise. + p_meas_crosstalk_scale: Measurement crosstalk rescale factor. + p_prep_crosstalk_scale: Preparation crosstalk rescale factor. + crosstalk_per_gate: Whether to apply crosstalk on a per-gate basis. + p_idle_linear_rate: Linear rate for idle noise. + p_idle_quadratic_rate: Quadratic rate for idle noise. + p2_idle: Stochastic idle noise after each two-qubit gate. + p_idle_linear_model: Pauli model for linear idle noise in a comma-delimited format. + """ + + p_prep: float = Field(default=0.0, alias="p_init", ge=0.0, le=1.0) + p_meas_0: float = Field(default=0.0, ge=0.0, le=1.0) + p_meas_1: float = Field(default=0.0, ge=0.0, le=1.0) + p1: float = Field(default=0.0, ge=0.0, le=1.0) + p2: float = Field(default=0.0, ge=0.0, le=1.0) + p1_emission_ratio: float = Field(default=0.0, ge=0.0, le=1.0) + p2_emission_ratio: float = Field(default=0.0, ge=0.0, le=1.0) + p1_pauli_model: dict[str, float] = Field(default_factory=dict) + p1_emission_model: dict[str, float] = Field(default_factory=dict) + p2_pauli_model: dict[str, float] = Field(default_factory=dict) + p2_emission_model: dict[str, float] = Field(default_factory=dict) + p_prep_leak_ratio: float = Field(default=0.0, ge=0.0, le=1.0) + p1_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) + p2_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) + scale: float = Field(default=0.0, ge=0.0) + memory_scale: float = Field(default=0.0, ge=0.0) + prep_scale: float = Field(default=0.0, alias="init_scale", ge=0.0) + meas_scale: float = Field(default=0.0, ge=0.0) + p1_scale: float = Field(default=0.0, ge=0.0) + p2_scale: float = Field(default=0.0, ge=0.0) + emission_scale: float = Field(default=0.0, ge=0.0) + przz_a: float | None = None + przz_b: float | None = None + przz_c: float | None = None + przz_d: float | None = None + przz_power: float = 1.0 + p_crosstalk_meas: float = Field( + default=0.0, alias="p_meas_crosstalk", ge=0.0, le=1.0 + ) + p_crosstalk_init: float = Field( + default=0.0, alias="p_prep_crosstalk", ge=0.0, le=1.0 + ) + noiseless_gates: list[str] = Field(default_factory=list) + coherent_dephasing: bool = False + coherent_to_incoherent_factor: float = 1.5 + leak2depolar: bool = False + p_meas_crosstalk_scale: float = 1.0 + p_prep_crosstalk_scale: float = 1.0 + crosstalk_per_gate: bool | None = None + p_idle_linear_rate: float = Field(default=0.0, ge=0.0) + p_idle_quadratic_rate: float = Field(default=0.0, ge=0.0) + p2_idle: float = Field(default=0.0, ge=0.0) + p_idle_linear_model: dict[str, float] = Field(default_factory=dict) + + @model_validator(mode="after") + def check_valid_config(self) -> Self: + """Validate the error model configuration.""" + _validate_distribution("p1_pauli_model", self.p1_pauli_model, _KEYS_1Q_PAULI) + _validate_distribution( + "p1_emission_model", self.p1_emission_model, _KEYS_1Q_EMISSION + ) + _validate_distribution( + "p_idle_linear_model", self.p_idle_linear_model, _KEYS_1Q_EMISSION + ) + _validate_distribution("p2_pauli_model", self.p2_pauli_model, _KEYS_2Q_PAULI) + _validate_distribution( + "p2_emission_model", self.p2_emission_model, _KEYS_2Q_EMISSION + ) + + przz_params = [self.przz_a, self.przz_b, self.przz_c, self.przz_d] + if not ( + all(x is None for x in przz_params) + or all(x is not None for x in przz_params) + ): + raise ValueError( + "When setting przz_x, you must either set the four of them, or none." + ) + return self + + class UserErrorParams(BaseModel): """User provided error values that override machine values for emulation of Quantinuum Systems hardware. diff --git a/uv.lock b/uv.lock index 16da358..176c6e7 100644 --- a/uv.lock +++ b/uv.lock @@ -833,7 +833,7 @@ wheels = [ [[package]] name = "quantinuum-schemas" -version = "6.0.0" +version = "7.0.0" source = { editable = "." } dependencies = [ { name = "orjson" }, @@ -868,7 +868,7 @@ dev = [ { name = "pylint-pytest", specifier = ">=1.1.8" }, { name = "pyright", specifier = ">=1.1.302" }, { name = "pytest", specifier = ">=8.0.0,<9.0.0" }, - { name = "pytest-cov", specifier = ">=5.0.0,<6.0.0" }, + { name = "pytest-cov", specifier = ">=5.0.0,<7.0.0" }, { name = "ruff", specifier = ">0.11" }, ] From 47a0b93384f2f47ddb96ce33f4732140ed6f0461 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Mon, 6 Oct 2025 14:38:52 +0100 Subject: [PATCH 2/8] chore: fix Helios error model aliases --- .../models/quantinuum_systems_noise.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index 2e9744d..91ee7b9 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -67,7 +67,7 @@ class HeliosErrorParams(BaseModel): Error model configuration for emulation of Quantinuum's Helios System. parameters: - p_prep: Probability of error during preparation. Alias: p_init. + p_init: Probability of error during preparation. Alias: p_prep. p_meas_0: Probability of flipping 0 to 1 during measurement. p_meas_1: Probability of flipping 1 to 0 during measurement. p1: Probability of error after single-qubit gates. @@ -89,7 +89,7 @@ class HeliosErrorParams(BaseModel): two-qubit. scale: Overall scaling factor. memory_scale: Memory scaling factor. - prep_scale: Initial scaling factor. Alias: init_scale. + init_scale: Initial scaling factor. Alias: prep_scale. meas_scale: Measurement scaling factor. p1_scale: Single-qubit gate scaling factor. p2_scale: Two-qubit gate scaling factor. @@ -110,13 +110,15 @@ class HeliosErrorParams(BaseModel): p_meas_crosstalk_scale: Measurement crosstalk rescale factor. p_prep_crosstalk_scale: Preparation crosstalk rescale factor. crosstalk_per_gate: Whether to apply crosstalk on a per-gate basis. - p_idle_linear_rate: Linear rate for idle noise. - p_idle_quadratic_rate: Quadratic rate for idle noise. + linear_dephasing_rate: Linear rate for idle noise. + Alias: p_idle_linear_rate. + quadratic_dephasing_rate: Quadratic rate for idle noise. + Alias: p_idle_quadratic_rate. p2_idle: Stochastic idle noise after each two-qubit gate. p_idle_linear_model: Pauli model for linear idle noise in a comma-delimited format. """ - p_prep: float = Field(default=0.0, alias="p_init", ge=0.0, le=1.0) + p_init: float = Field(default=0.0, alias="p_prep", ge=0.0, le=1.0) p_meas_0: float = Field(default=0.0, ge=0.0, le=1.0) p_meas_1: float = Field(default=0.0, ge=0.0, le=1.0) p1: float = Field(default=0.0, ge=0.0, le=1.0) @@ -132,7 +134,7 @@ class HeliosErrorParams(BaseModel): p2_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) scale: float = Field(default=0.0, ge=0.0) memory_scale: float = Field(default=0.0, ge=0.0) - prep_scale: float = Field(default=0.0, alias="init_scale", ge=0.0) + init_scale: float = Field(default=0.0, alias="prep_scale", ge=0.0) meas_scale: float = Field(default=0.0, ge=0.0) p1_scale: float = Field(default=0.0, ge=0.0) p2_scale: float = Field(default=0.0, ge=0.0) @@ -155,8 +157,12 @@ class HeliosErrorParams(BaseModel): p_meas_crosstalk_scale: float = 1.0 p_prep_crosstalk_scale: float = 1.0 crosstalk_per_gate: bool | None = None - p_idle_linear_rate: float = Field(default=0.0, ge=0.0) - p_idle_quadratic_rate: float = Field(default=0.0, ge=0.0) + linear_dephasing_rate: float = Field( + default=0.0, alias="p_idle_linear_rate", ge=0.0 + ) + quadratic_dephasing_rate: float = Field( + default=0.0, alias="p_idle_quadratic_rate", ge=0.0 + ) p2_idle: float = Field(default=0.0, ge=0.0) p_idle_linear_model: dict[str, float] = Field(default_factory=dict) From 8b94bc7774f87cf174cdf13e3a03c36204c910ea Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Tue, 7 Oct 2025 14:59:23 +0100 Subject: [PATCH 3/8] feat: add Helios error parameter configuration --- quantinuum_schemas/__init__.py | 2 ++ quantinuum_schemas/models/backend_config.py | 34 ++++++++++++++++--- quantinuum_schemas/models/emulator_config.py | 19 ++++++++++- .../models/quantinuum_systems_noise.py | 2 +- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/quantinuum_schemas/__init__.py b/quantinuum_schemas/__init__.py index e8354fa..2ca81e0 100644 --- a/quantinuum_schemas/__init__.py +++ b/quantinuum_schemas/__init__.py @@ -16,6 +16,7 @@ ClassicalReplaySimulator, CoinflipSimulator, DepolarizingErrorModel, + HeliosErrorModel, HeliosRuntime, MatrixProductStateSimulator, NoErrorModel, @@ -50,6 +51,7 @@ "CoinflipSimulator", "MatrixProductStateSimulator", "ClassicalReplaySimulator", + "HeliosErrorModel", "HeliosErrorParams", "UserErrorParams", ] diff --git a/quantinuum_schemas/models/backend_config.py b/quantinuum_schemas/models/backend_config.py index 8a16df8..7736eed 100644 --- a/quantinuum_schemas/models/backend_config.py +++ b/quantinuum_schemas/models/backend_config.py @@ -5,7 +5,7 @@ as our backend credential classes handle those. """ -# pylint: disable=too-many-lines +# pylint: disable=too-many-lines,no-member import abc from typing import Any, Dict, Literal, Optional, Type, TypeVar, Union @@ -18,6 +18,7 @@ ClassicalReplaySimulator, CoinflipSimulator, DepolarizingErrorModel, + HeliosErrorModel, HeliosRuntime, MatrixProductStateSimulator, NoErrorModel, @@ -338,9 +339,34 @@ class SelenePlusConfig(BaseEmulatorConfig, BaseBackendConfig): | ClassicalReplaySimulator ) = Field(default_factory=StatevectorSimulator) runtime: SimpleRuntime | HeliosRuntime = Field(default_factory=HeliosRuntime) - error_model: NoErrorModel | DepolarizingErrorModel | QSystemErrorModel = Field( - default_factory=QSystemErrorModel - ) + error_model: ( + NoErrorModel | DepolarizingErrorModel | QSystemErrorModel | HeliosErrorModel + ) = Field(default_factory=QSystemErrorModel) + + @model_validator(mode="after") + def validate_runtime_and_error_model(self) -> Self: + """Validate that the runtime and error model are compatible.""" + if isinstance(self.error_model, (QSystemErrorModel, HeliosErrorModel)): + if not isinstance(self.runtime, HeliosRuntime): + raise ValueError( + f"error_model of type: {self.error_model.__class__.__name__} " + "can only be used with runtime of type: HeliosRuntime" + ) + if isinstance(self.error_model, HeliosErrorModel): + if isinstance(self.simulator, StabilizerSimulator): + if self.error_model.error_params.coherent_dephasing is False: + raise ValueError( + "HeliosErrorModel with StabilizerSimulator must have " + "coherent_dephasing set to True" + ) + else: + if self.error_model.error_params.coherent_dephasing is True: + raise ValueError( + "HeliosErrorModel with non-StabilizerSimulator must have " + "coherent_dephasing set to False" + ) + + return self BackendConfig = Annotated[ diff --git a/quantinuum_schemas/models/emulator_config.py b/quantinuum_schemas/models/emulator_config.py index 8d93b89..8300fde 100644 --- a/quantinuum_schemas/models/emulator_config.py +++ b/quantinuum_schemas/models/emulator_config.py @@ -5,6 +5,8 @@ from pydantic import BaseModel, Field, model_validator from typing_extensions import Self +from quantinuum_schemas.models.quantinuum_systems_noise import HeliosErrorParams + class SimpleRuntime(BaseModel): """A 'simple' runtime for the Selene emulator. @@ -70,7 +72,8 @@ class DepolarizingErrorModel(BaseModel): class QSystemErrorModel(BaseModel): - """Model for simulating error for a specific QSystem via Selene. + """Preconfigured Error Model for simulating error for a specific QSystem via Selene. + Will use a preconfiguration of the error model that is specified by the name parameter. Args: seed: Random seed for the error model. @@ -83,6 +86,20 @@ class QSystemErrorModel(BaseModel): name: str = "alpha" +class HeliosErrorModel(BaseModel): + """Configurable Error Model for simulating error for the Helios system via Selene. + + Args: + seed: Random seed for the error model. + error_params: Parameters for the Helios error model. + """ + + type: Literal["HeliosErrorModel"] = "HeliosErrorModel" + + seed: int | None = Field(default=None) + error_params: HeliosErrorParams = Field(default_factory=HeliosErrorParams) + + class StatevectorSimulator(BaseModel): """Statevector simulator built on a QuEST backend. diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index 91ee7b9..6da0779 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -151,7 +151,7 @@ class HeliosErrorParams(BaseModel): default=0.0, alias="p_prep_crosstalk", ge=0.0, le=1.0 ) noiseless_gates: list[str] = Field(default_factory=list) - coherent_dephasing: bool = False + coherent_dephasing: bool = True coherent_to_incoherent_factor: float = 1.5 leak2depolar: bool = False p_meas_crosstalk_scale: float = 1.0 From 652f4ce955ad8767795b0bd7962fbc0eaba6eacc Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Fri, 10 Oct 2025 18:01:45 +0100 Subject: [PATCH 4/8] feat: add HeliosConfig --- quantinuum_schemas/models/backend_config.py | 95 ++++++++++++++++++-- quantinuum_schemas/models/emulator_config.py | 4 +- tests/models/test_backend_config.py | 11 +++ 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/quantinuum_schemas/models/backend_config.py b/quantinuum_schemas/models/backend_config.py index 7736eed..1e3b7e4 100644 --- a/quantinuum_schemas/models/backend_config.py +++ b/quantinuum_schemas/models/backend_config.py @@ -33,6 +33,8 @@ ST = TypeVar("ST", bound="BaseModel") +KNOWN_NEXUS_HELIOS_EMULATORS = ["Helios-1E-lite"] + class BaseBackendConfig(BaseModel, abc.ABC): """Base class for all the backend configs. @@ -162,8 +164,8 @@ def check_local_remote_parameters_are_consistent( # pylint: disable=no-self-arg return values -class QuantinuumCompilerOptions(BaseModel): - """Class for Quantinuum Compiler Options. +class QuantinuumOptions(BaseModel): + """Class for Quantinuum additional options. Intentionally allows extra unknown flags to be defined. """ @@ -174,10 +176,10 @@ class QuantinuumCompilerOptions(BaseModel): def check_field_values_are_supported_types( # pylint: disable=no-self-argument, cls, values: Dict[str, Any] ) -> Dict[str, Any]: - """Check that compiler option values are supported types.""" + """Check that option values are supported types.""" for key in values: assert isinstance(values[key], (str, int, bool, float, list)), ( - "Compiler options must be str, bool int, float or a list of floats" + "Options must be str, bool int, float or a list of floats" ) if isinstance(values[key], list): for x in values[key]: @@ -185,6 +187,14 @@ def check_field_values_are_supported_types( # pylint: disable=no-self-argument, return values +# Alias via inheritance for backwards compatibility +class QuantinuumCompilerOptions(QuantinuumOptions): + """Class for Quantinuum compiler options. + + Intentionally allows extra unknown flags to be defined. + """ + + class QuantinuumConfig(BaseBackendConfig): """Runs circuits on Quantinuum's quantum devices and simulators. @@ -281,7 +291,7 @@ class BaseEmulatorConfig(BaseModel): n_qubits: The maximum number of qubits to simulate. """ - n_qubits: int = Field(ge=1) + n_qubits: int | None = None @model_validator(mode="after") def prevent_direct_instantiation(self) -> Self: @@ -369,6 +379,80 @@ def validate_runtime_and_error_model(self) -> Self: return self +class HeliosEmulatorConfig(BaseModel): + """Configuration for Helios emulator systems.""" + + n_qubits: int | None = None + + simulator: ( + StatevectorSimulator + | StabilizerSimulator + | MatrixProductStateSimulator + | CoinflipSimulator + | ClassicalReplaySimulator + ) = Field(default_factory=StatevectorSimulator) + error_model: ( + NoErrorModel | DepolarizingErrorModel | QSystemErrorModel | HeliosErrorModel + ) = Field(default_factory=QSystemErrorModel) + runtime: HeliosRuntime = Field(default_factory=HeliosRuntime) + + +class HeliosConfig(BaseBackendConfig): + """Configuration for Helios generation QPUs, emulators and checkers.""" + + type: Literal["HeliosConfig"] = "HeliosConfig" + + system_name: str = "Helios-1" + emulation_config: HeliosEmulatorConfig | None = None + + max_cost: int | None = None + + attempt_batching: bool = False + max_batch_cost: int = 2000 + + options: QuantinuumOptions | None = None + + @model_validator(mode="after") + def check_valid_config(self) -> Self: + """Perform simple configuration validation.""" + + if not self.system_name.endswith("SC") and self.max_cost is None: + raise ValueError("max_cost must be set for non-checker systems.") + + if self.emulation_config is not None: + if self.attempt_batching: + raise ValueError("Batching not available for emulators.") + if self.system_name in KNOWN_NEXUS_HELIOS_EMULATORS: + if self.max_cost: + raise ValueError( + f"max_cost not currently supported for {self.system_name}" + ) + if self.system_name not in KNOWN_NEXUS_HELIOS_EMULATORS: + if self.emulation_config.simulator.type == "ClassicalReplaySimulator": + raise ValueError( + f"ClassicalReplaySimulator is only available for " + f"emulators in: {KNOWN_NEXUS_HELIOS_EMULATORS}" + ) + if self.emulation_config.error_model.type == "DepolarizingErrorModel": + raise ValueError( + f"DepolarizingErrorModel is only available for " + f"emulators in: {KNOWN_NEXUS_HELIOS_EMULATORS}" + ) + if self.emulation_config.runtime.seed is not None: + raise ValueError( + f"runtime.seed will be ignored for {self.system_name}" + ) + if self.emulation_config.simulator.seed is not None: + raise ValueError( + f"simulator.seed will be ignored for {self.system_name}" + ) + if self.emulation_config.error_model.seed is not None: + raise ValueError( + f"error_model.seed will be ignored for {self.system_name}" + ) + return self + + BackendConfig = Annotated[ Union[ AerConfig, @@ -381,6 +465,7 @@ def validate_runtime_and_error_model(self) -> Self: QulacsConfig, SeleneConfig, SelenePlusConfig, + HeliosConfig, ], Field(discriminator="type"), ] diff --git a/quantinuum_schemas/models/emulator_config.py b/quantinuum_schemas/models/emulator_config.py index 8300fde..fe17539 100644 --- a/quantinuum_schemas/models/emulator_config.py +++ b/quantinuum_schemas/models/emulator_config.py @@ -73,7 +73,7 @@ class DepolarizingErrorModel(BaseModel): class QSystemErrorModel(BaseModel): """Preconfigured Error Model for simulating error for a specific QSystem via Selene. - Will use a preconfiguration of the error model that is specified by the name parameter. + Will use a preconfiguration of the error model as specified by the name parameter. Args: seed: Random seed for the error model. @@ -166,6 +166,8 @@ def check_valid_config(self) -> Self: raise ValueError("CPU backend does not support chi > 256.") if self.chi and self.truncation_fidelity: raise ValueError("Cannot set both chi and truncation_fidelity.") + if self.backend != "auto": + raise ValueError("Only backend='auto' is supported at this time.") return self diff --git a/tests/models/test_backend_config.py b/tests/models/test_backend_config.py index f1142d9..e868c5d 100644 --- a/tests/models/test_backend_config.py +++ b/tests/models/test_backend_config.py @@ -107,6 +107,17 @@ def test_selene_plus_config_roundtrip( """Test roundtrip of SelenePlusConfig, importantly the ability to discriminate the error model and the runtime.""" + + if error_model_class is QSystemErrorModel and runtime_class is not HeliosRuntime: + with pytest.raises(ValidationError): + SelenePlusConfig( + runtime=runtime_class(), + simulator=simulator_class(), + error_model=error_model_class(), + n_qubits=4, + ) + return + config = SelenePlusConfig( runtime=runtime_class(), simulator=simulator_class(), From 5c933435e6ca7709bc872def105d57705f3ef6a2 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Mon, 13 Oct 2025 13:18:03 +0100 Subject: [PATCH 5/8] WIP --- .../models/quantinuum_systems_noise.py | 14 +++++++------- tests/models/test_backend_config.py | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index 6da0779..c480589 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -132,13 +132,13 @@ class HeliosErrorParams(BaseModel): p_prep_leak_ratio: float = Field(default=0.0, ge=0.0, le=1.0) p1_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) p2_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) - scale: float = Field(default=0.0, ge=0.0) - memory_scale: float = Field(default=0.0, ge=0.0) - init_scale: float = Field(default=0.0, alias="prep_scale", ge=0.0) - meas_scale: float = Field(default=0.0, ge=0.0) - p1_scale: float = Field(default=0.0, ge=0.0) - p2_scale: float = Field(default=0.0, ge=0.0) - emission_scale: float = Field(default=0.0, ge=0.0) + scale: float = Field(default=1.0, ge=0.0) + memory_scale: float = Field(default=1.0, ge=0.0) + init_scale: float = Field(default=1.0, alias="prep_scale", ge=0.0) + meas_scale: float = Field(default=1.0, ge=0.0) + p1_scale: float = Field(default=1.0, ge=0.0) + p2_scale: float = Field(default=1.0, ge=0.0) + emission_scale: float = Field(default=1.0, ge=0.0) przz_a: float | None = None przz_b: float | None = None przz_c: float | None = None diff --git a/tests/models/test_backend_config.py b/tests/models/test_backend_config.py index e868c5d..3f56a72 100644 --- a/tests/models/test_backend_config.py +++ b/tests/models/test_backend_config.py @@ -107,7 +107,6 @@ def test_selene_plus_config_roundtrip( """Test roundtrip of SelenePlusConfig, importantly the ability to discriminate the error model and the runtime.""" - if error_model_class is QSystemErrorModel and runtime_class is not HeliosRuntime: with pytest.raises(ValidationError): SelenePlusConfig( From c46b7f26ccd619ac24d5579a1f4a7b2d1391dd31 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Tue, 21 Oct 2025 16:45:11 +0100 Subject: [PATCH 6/8] WIP --- quantinuum_schemas/__init__.py | 4 ++++ quantinuum_schemas/models/backend_config.py | 24 +++++++++++-------- .../models/quantinuum_systems_noise.py | 14 +++++------ 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/quantinuum_schemas/__init__.py b/quantinuum_schemas/__init__.py index 2ca81e0..ba512b7 100644 --- a/quantinuum_schemas/__init__.py +++ b/quantinuum_schemas/__init__.py @@ -11,6 +11,8 @@ QuantinuumConfig, QulacsConfig, SelenePlusConfig, + HeliosConfig, + HeliosEmulatorConfig, ) from quantinuum_schemas.models.emulator_config import ( ClassicalReplaySimulator, @@ -42,6 +44,8 @@ "SeleneConfig", "SelenePlusConfig", "SimpleRuntime", + "HeliosConfig", + "HeliosEmulatorConfig", "HeliosRuntime", "NoErrorModel", "DepolarizingErrorModel", diff --git a/quantinuum_schemas/models/backend_config.py b/quantinuum_schemas/models/backend_config.py index 1e3b7e4..78f0303 100644 --- a/quantinuum_schemas/models/backend_config.py +++ b/quantinuum_schemas/models/backend_config.py @@ -379,7 +379,7 @@ def validate_runtime_and_error_model(self) -> Self: return self -class HeliosEmulatorConfig(BaseModel): +class HeliosEmulatorConfig(BaseEmulatorConfig): """Configuration for Helios emulator systems.""" n_qubits: int | None = None @@ -403,7 +403,7 @@ class HeliosConfig(BaseBackendConfig): type: Literal["HeliosConfig"] = "HeliosConfig" system_name: str = "Helios-1" - emulation_config: HeliosEmulatorConfig | None = None + emulator_config: HeliosEmulatorConfig | None = None max_cost: int | None = None @@ -416,10 +416,14 @@ class HeliosConfig(BaseBackendConfig): def check_valid_config(self) -> Self: """Perform simple configuration validation.""" - if not self.system_name.endswith("SC") and self.max_cost is None: - raise ValueError("max_cost must be set for non-checker systems.") + if self.max_cost is None: + if ( + not self.system_name.endswith("SC") + and self.system_name not in KNOWN_NEXUS_HELIOS_EMULATORS + ): + raise ValueError(f"max_cost must be set for {self.system_name}.") - if self.emulation_config is not None: + if self.emulator_config is not None: if self.attempt_batching: raise ValueError("Batching not available for emulators.") if self.system_name in KNOWN_NEXUS_HELIOS_EMULATORS: @@ -428,25 +432,25 @@ def check_valid_config(self) -> Self: f"max_cost not currently supported for {self.system_name}" ) if self.system_name not in KNOWN_NEXUS_HELIOS_EMULATORS: - if self.emulation_config.simulator.type == "ClassicalReplaySimulator": + if self.emulator_config.simulator.type == "ClassicalReplaySimulator": raise ValueError( f"ClassicalReplaySimulator is only available for " f"emulators in: {KNOWN_NEXUS_HELIOS_EMULATORS}" ) - if self.emulation_config.error_model.type == "DepolarizingErrorModel": + if self.emulator_config.error_model.type == "DepolarizingErrorModel": raise ValueError( f"DepolarizingErrorModel is only available for " f"emulators in: {KNOWN_NEXUS_HELIOS_EMULATORS}" ) - if self.emulation_config.runtime.seed is not None: + if self.emulator_config.runtime.seed is not None: raise ValueError( f"runtime.seed will be ignored for {self.system_name}" ) - if self.emulation_config.simulator.seed is not None: + if self.emulator_config.simulator.seed is not None: raise ValueError( f"simulator.seed will be ignored for {self.system_name}" ) - if self.emulation_config.error_model.seed is not None: + if self.emulator_config.error_model.seed is not None: raise ValueError( f"error_model.seed will be ignored for {self.system_name}" ) diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index c480589..21e5047 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -3,7 +3,7 @@ from typing import Optional, Tuple, Union from typing_extensions import Self -from pydantic import Field, model_validator +from pydantic import AliasChoices, Field, model_validator from .base import BaseModel @@ -118,7 +118,7 @@ class HeliosErrorParams(BaseModel): p_idle_linear_model: Pauli model for linear idle noise in a comma-delimited format. """ - p_init: float = Field(default=0.0, alias="p_prep", ge=0.0, le=1.0) + p_init: float = Field(default=0.0, alias="p_prep", ge=0.0, le=1.0, validation_alias= AliasChoices("p_init", "p_prep")) p_meas_0: float = Field(default=0.0, ge=0.0, le=1.0) p_meas_1: float = Field(default=0.0, ge=0.0, le=1.0) p1: float = Field(default=0.0, ge=0.0, le=1.0) @@ -134,7 +134,7 @@ class HeliosErrorParams(BaseModel): p2_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) scale: float = Field(default=1.0, ge=0.0) memory_scale: float = Field(default=1.0, ge=0.0) - init_scale: float = Field(default=1.0, alias="prep_scale", ge=0.0) + init_scale: float = Field(default=1.0, alias="prep_scale", ge=0.0, validation_alias= AliasChoices("init_scale", "prep_scale")) meas_scale: float = Field(default=1.0, ge=0.0) p1_scale: float = Field(default=1.0, ge=0.0) p2_scale: float = Field(default=1.0, ge=0.0) @@ -145,10 +145,10 @@ class HeliosErrorParams(BaseModel): przz_d: float | None = None przz_power: float = 1.0 p_crosstalk_meas: float = Field( - default=0.0, alias="p_meas_crosstalk", ge=0.0, le=1.0 + default=0.0, alias="p_meas_crosstalk", ge=0.0, le=1.0, validation_alias= AliasChoices("p_crosstalk_meas", "p_meas_crosstalk") ) p_crosstalk_init: float = Field( - default=0.0, alias="p_prep_crosstalk", ge=0.0, le=1.0 + default=0.0, alias="p_prep_crosstalk", ge=0.0, le=1.0, validation_alias= AliasChoices("p_crosstalk_init", "p_prep_crosstalk") ) noiseless_gates: list[str] = Field(default_factory=list) coherent_dephasing: bool = True @@ -158,10 +158,10 @@ class HeliosErrorParams(BaseModel): p_prep_crosstalk_scale: float = 1.0 crosstalk_per_gate: bool | None = None linear_dephasing_rate: float = Field( - default=0.0, alias="p_idle_linear_rate", ge=0.0 + default=0.0, alias="p_idle_linear_rate", ge=0.0, validation_alias= AliasChoices("linear_dephasing_rate", "p_idle_linear_rate") ) quadratic_dephasing_rate: float = Field( - default=0.0, alias="p_idle_quadratic_rate", ge=0.0 + default=0.0, alias="p_idle_quadratic_rate", ge=0.0, validation_alias= AliasChoices("quadratic_dephasing_rate", "p_idle_quadratic_rate") ) p2_idle: float = Field(default=0.0, ge=0.0) p_idle_linear_model: dict[str, float] = Field(default_factory=dict) From 1b884630d882f6a6f5026d8c5009de193a02d0d9 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Tue, 21 Oct 2025 18:53:02 +0100 Subject: [PATCH 7/8] chore: aliasing for helios error params --- quantinuum_schemas/models/backend_config.py | 2 +- .../models/quantinuum_systems_noise.py | 39 ++++++++++++++++--- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/quantinuum_schemas/models/backend_config.py b/quantinuum_schemas/models/backend_config.py index 78f0303..c373160 100644 --- a/quantinuum_schemas/models/backend_config.py +++ b/quantinuum_schemas/models/backend_config.py @@ -418,7 +418,7 @@ def check_valid_config(self) -> Self: if self.max_cost is None: if ( - not self.system_name.endswith("SC") + not self.system_name.endswith("SC") and self.system_name not in KNOWN_NEXUS_HELIOS_EMULATORS ): raise ValueError(f"max_cost must be set for {self.system_name}.") diff --git a/quantinuum_schemas/models/quantinuum_systems_noise.py b/quantinuum_schemas/models/quantinuum_systems_noise.py index 21e5047..26d1207 100644 --- a/quantinuum_schemas/models/quantinuum_systems_noise.py +++ b/quantinuum_schemas/models/quantinuum_systems_noise.py @@ -118,7 +118,13 @@ class HeliosErrorParams(BaseModel): p_idle_linear_model: Pauli model for linear idle noise in a comma-delimited format. """ - p_init: float = Field(default=0.0, alias="p_prep", ge=0.0, le=1.0, validation_alias= AliasChoices("p_init", "p_prep")) + p_init: float = Field( + default=0.0, + ge=0.0, + le=1.0, + validation_alias=AliasChoices("p_init", "p_prep"), + serialization_alias="p_prep", + ) p_meas_0: float = Field(default=0.0, ge=0.0, le=1.0) p_meas_1: float = Field(default=0.0, ge=0.0, le=1.0) p1: float = Field(default=0.0, ge=0.0, le=1.0) @@ -134,7 +140,12 @@ class HeliosErrorParams(BaseModel): p2_seepage_prob: float = Field(default=0.0, ge=0.0, le=1.0) scale: float = Field(default=1.0, ge=0.0) memory_scale: float = Field(default=1.0, ge=0.0) - init_scale: float = Field(default=1.0, alias="prep_scale", ge=0.0, validation_alias= AliasChoices("init_scale", "prep_scale")) + init_scale: float = Field( + default=1.0, + ge=0.0, + validation_alias=AliasChoices("init_scale", "prep_scale"), + serialization_alias="prep_scale", + ) meas_scale: float = Field(default=1.0, ge=0.0) p1_scale: float = Field(default=1.0, ge=0.0) p2_scale: float = Field(default=1.0, ge=0.0) @@ -145,10 +156,18 @@ class HeliosErrorParams(BaseModel): przz_d: float | None = None przz_power: float = 1.0 p_crosstalk_meas: float = Field( - default=0.0, alias="p_meas_crosstalk", ge=0.0, le=1.0, validation_alias= AliasChoices("p_crosstalk_meas", "p_meas_crosstalk") + default=0.0, + ge=0.0, + le=1.0, + validation_alias=AliasChoices("p_crosstalk_meas", "p_meas_crosstalk"), + serialization_alias="p_meas_crosstalk", ) p_crosstalk_init: float = Field( - default=0.0, alias="p_prep_crosstalk", ge=0.0, le=1.0, validation_alias= AliasChoices("p_crosstalk_init", "p_prep_crosstalk") + default=0.0, + ge=0.0, + le=1.0, + validation_alias=AliasChoices("p_crosstalk_init", "p_prep_crosstalk"), + serialization_alias="p_prep_crosstalk", ) noiseless_gates: list[str] = Field(default_factory=list) coherent_dephasing: bool = True @@ -158,10 +177,18 @@ class HeliosErrorParams(BaseModel): p_prep_crosstalk_scale: float = 1.0 crosstalk_per_gate: bool | None = None linear_dephasing_rate: float = Field( - default=0.0, alias="p_idle_linear_rate", ge=0.0, validation_alias= AliasChoices("linear_dephasing_rate", "p_idle_linear_rate") + default=0.0, + ge=0.0, + validation_alias=AliasChoices("linear_dephasing_rate", "p_idle_linear_rate"), + serialization_alias="p_idle_linear_rate", ) quadratic_dephasing_rate: float = Field( - default=0.0, alias="p_idle_quadratic_rate", ge=0.0, validation_alias= AliasChoices("quadratic_dephasing_rate", "p_idle_quadratic_rate") + default=0.0, + ge=0.0, + validation_alias=AliasChoices( + "quadratic_dephasing_rate", "p_idle_quadratic_rate" + ), + serialization_alias="p_idle_quadratic_rate", ) p2_idle: float = Field(default=0.0, ge=0.0) p_idle_linear_model: dict[str, float] = Field(default_factory=dict) From 8d3294e89f37af0d86cccf55b824fd1caadb8149 Mon Sep 17 00:00:00 2001 From: vanyae-cqc Date: Fri, 24 Oct 2025 14:52:01 +0100 Subject: [PATCH 8/8] chore: rename HeliosErrorModel to HeliosCustomErrorModel --- quantinuum_schemas/__init__.py | 4 ++-- quantinuum_schemas/models/backend_config.py | 16 +++++++++++----- quantinuum_schemas/models/emulator_config.py | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/quantinuum_schemas/__init__.py b/quantinuum_schemas/__init__.py index ba512b7..315c2aa 100644 --- a/quantinuum_schemas/__init__.py +++ b/quantinuum_schemas/__init__.py @@ -18,7 +18,7 @@ ClassicalReplaySimulator, CoinflipSimulator, DepolarizingErrorModel, - HeliosErrorModel, + HeliosCustomErrorModel, HeliosRuntime, MatrixProductStateSimulator, NoErrorModel, @@ -55,7 +55,7 @@ "CoinflipSimulator", "MatrixProductStateSimulator", "ClassicalReplaySimulator", - "HeliosErrorModel", + "HeliosCustomErrorModel", "HeliosErrorParams", "UserErrorParams", ] diff --git a/quantinuum_schemas/models/backend_config.py b/quantinuum_schemas/models/backend_config.py index c373160..09553fa 100644 --- a/quantinuum_schemas/models/backend_config.py +++ b/quantinuum_schemas/models/backend_config.py @@ -18,7 +18,7 @@ ClassicalReplaySimulator, CoinflipSimulator, DepolarizingErrorModel, - HeliosErrorModel, + HeliosCustomErrorModel, HeliosRuntime, MatrixProductStateSimulator, NoErrorModel, @@ -350,19 +350,22 @@ class SelenePlusConfig(BaseEmulatorConfig, BaseBackendConfig): ) = Field(default_factory=StatevectorSimulator) runtime: SimpleRuntime | HeliosRuntime = Field(default_factory=HeliosRuntime) error_model: ( - NoErrorModel | DepolarizingErrorModel | QSystemErrorModel | HeliosErrorModel + NoErrorModel + | DepolarizingErrorModel + | QSystemErrorModel + | HeliosCustomErrorModel ) = Field(default_factory=QSystemErrorModel) @model_validator(mode="after") def validate_runtime_and_error_model(self) -> Self: """Validate that the runtime and error model are compatible.""" - if isinstance(self.error_model, (QSystemErrorModel, HeliosErrorModel)): + if isinstance(self.error_model, (QSystemErrorModel, HeliosCustomErrorModel)): if not isinstance(self.runtime, HeliosRuntime): raise ValueError( f"error_model of type: {self.error_model.__class__.__name__} " "can only be used with runtime of type: HeliosRuntime" ) - if isinstance(self.error_model, HeliosErrorModel): + if isinstance(self.error_model, HeliosCustomErrorModel): if isinstance(self.simulator, StabilizerSimulator): if self.error_model.error_params.coherent_dephasing is False: raise ValueError( @@ -392,7 +395,10 @@ class HeliosEmulatorConfig(BaseEmulatorConfig): | ClassicalReplaySimulator ) = Field(default_factory=StatevectorSimulator) error_model: ( - NoErrorModel | DepolarizingErrorModel | QSystemErrorModel | HeliosErrorModel + NoErrorModel + | DepolarizingErrorModel + | QSystemErrorModel + | HeliosCustomErrorModel ) = Field(default_factory=QSystemErrorModel) runtime: HeliosRuntime = Field(default_factory=HeliosRuntime) diff --git a/quantinuum_schemas/models/emulator_config.py b/quantinuum_schemas/models/emulator_config.py index fe17539..2631144 100644 --- a/quantinuum_schemas/models/emulator_config.py +++ b/quantinuum_schemas/models/emulator_config.py @@ -86,7 +86,7 @@ class QSystemErrorModel(BaseModel): name: str = "alpha" -class HeliosErrorModel(BaseModel): +class HeliosCustomErrorModel(BaseModel): """Configurable Error Model for simulating error for the Helios system via Selene. Args: @@ -94,7 +94,7 @@ class HeliosErrorModel(BaseModel): error_params: Parameters for the Helios error model. """ - type: Literal["HeliosErrorModel"] = "HeliosErrorModel" + type: Literal["HeliosCustomErrorModel"] = "HeliosCustomErrorModel" seed: int | None = Field(default=None) error_params: HeliosErrorParams = Field(default_factory=HeliosErrorParams)