Source code for qblox_scheduler.device_under_test.device_element
# SPDX-FileCopyrightText: Copyright 2020-2025, Quantify Consortium
# SPDX-FileCopyrightText: © 2022 Qblox <https://qblox.com>
# SPDX-License-Identifier: BSD-3-Clause
"""The module contains definitions for device elements."""
from __future__ import annotations
import abc
import warnings
from typing import TYPE_CHECKING, Annotated, Any, ClassVar
from typing_extensions import Self
from pydantic import AfterValidator, Field, ModelWrapValidatorHandler, model_validator
from qblox_scheduler.structure.model import SchedulerBaseModel, is_identifier
if TYPE_CHECKING:
from qblox_scheduler.backends.graph_compilation import DeviceCompilationConfig
def is_identifier_without_underscore(value: str) -> str:
"""Pydantic validator for names that are valid identifiers but without underscore."""
value = is_identifier(value)
if "_" in value:
raise ValueError(f"{value} may not contain underscores")
return value
[docs]
class DeviceElement(abc.ABC, SchedulerBaseModel):
"""
Create a device element for managing parameters.
The :class:`~DeviceElement` is responsible for compiling operations applied to that
specific device element from the composite to the quantum-device layer.
"""
__model_registry__: ClassVar[dict[str, type[DeviceElement]]] = {}
element_type: str
name: Annotated[str, AfterValidator(is_identifier_without_underscore)] = Field(kw_only=False)
@classmethod
def __pydantic_init_subclass__(cls, **kwargs: Any) -> None: # noqa: ANN401
"""Store new subclasses of :class:`~DeviceElement` into a class-level registry."""
super().__pydantic_init_subclass__(**kwargs)
DeviceElement.__model_registry__[cls.__name__] = cls
def __getstate__(self) -> dict[str, Any]:
"""Get the state of :class:`~DeviceElement` (used for YAML serialization)."""
return self.model_dump(exclude={"element_type"})
[docs]
@model_validator(mode="before")
@classmethod
def include_submodule_names(cls, data: Any) -> Any: # noqa: ANN401
"""
Fill in the ``name`` attribute of :class:`~DeviceElement` submodules when missing
(used for YAML deserialization, they are omitted at serialization).
"""
if isinstance(data, dict):
for submodule_name, submodule_data in data.items():
if submodule_name in ("name", "element_type"):
continue
if "name" not in submodule_data:
submodule_data["name"] = submodule_name
return data
[docs]
@model_validator(mode="before")
@classmethod
def _drop_reference_magnitude(cls, data: Any) -> Any: # noqa: ANN401
if isinstance(data, dict) and "reference_magnitude" in data:
data = dict(data)
data.pop("reference_magnitude")
warnings.warn(
f"{cls.__name__!r} in this configuration contains a "
"'reference_magnitude' entry. This option was removed and will be "
"ignored. Remove it from your configuration to silence this warning; "
"all other unrecognized keys remain errors.",
DeprecationWarning,
stacklevel=2,
)
return data
[docs]
@model_validator(mode="wrap")
@classmethod
def dispatch_concrete_model(
cls,
data: Any, # noqa: ANN401
handler: ModelWrapValidatorHandler[Self],
) -> Self:
"""
When deserializing a dict representation of a concrete :class:`~DeviceElement`,
infer the matching class by looking its `element_type` into the model registry
and return a validated instance of the concrete device element.
"""
if cls is DeviceElement and isinstance(data, dict):
element_type = data.get("element_type")
if element_type in cls.__model_registry__:
return cls.__model_registry__[element_type].model_validate(data) # type: ignore[reportReturnType]
else:
raise ValueError(f"unknown element type '{element_type}'")
return handler(data)
[docs]
@abc.abstractmethod
def generate_device_config(self) -> DeviceCompilationConfig:
"""Generate the device configuration."""
raise NotImplementedError