Source code for semi_cr.core.lab.utils.qharbor_tools.schemas
"""Measurement schemas shared by all local measurement backends."""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
import numpy as np
from .errors import ConfigurationError
from .models import require_text
if TYPE_CHECKING:
from collections.abc import Mapping, Sequence
[docs]
@dataclass(frozen=True, slots=True)
class ParameterSpec:
"""A backend-independent measurement parameter definition."""
name: str
label: str | None = None
unit: str = ""
setpoints: tuple[str, ...] = ()
paramtype: str = "numeric"
def __post_init__(self) -> None:
object.__setattr__(self, "name", require_text(self.name, "parameter.name"))
if self.label is not None:
object.__setattr__(self, "label", require_text(self.label, "parameter.label"))
object.__setattr__(self, "setpoints", tuple(self.setpoints))
object.__setattr__(self, "paramtype", require_text(self.paramtype, "parameter.paramtype"))
[docs]
@dataclass(frozen=True, slots=True)
class MeasurementSchema:
"""Backend-independent description of one saved measurement."""
name: str
parameters: tuple[ParameterSpec, ...] | Sequence[ParameterSpec]
def __post_init__(self) -> None:
object.__setattr__(self, "name", require_text(self.name, "schema.name"))
parameters = tuple(self.parameters)
if not parameters:
raise ConfigurationError("schema.parameters must contain at least one parameter.", operation="validate")
names = [parameter.name for parameter in parameters]
if len(names) != len(set(names)):
raise ConfigurationError("schema parameter names must be unique.", operation="validate")
known = set(names)
for parameter in parameters:
missing = set(parameter.setpoints) - known
if missing:
raise ConfigurationError(
f"Parameter '{parameter.name}' references unknown setpoints: {sorted(missing)}.",
operation="validate",
)
if parameter.name in parameter.setpoints:
raise ConfigurationError(
f"Parameter '{parameter.name}' cannot be its own setpoint.",
operation="validate",
)
object.__setattr__(self, "parameters", parameters)
@property
def parameter_names(self) -> tuple[str, ...]:
"""Return parameter names in registration order."""
return tuple(parameter.name for parameter in self.parameters)
[docs]
def validate_results(self, results: Mapping[str, Any]) -> None:
"""Validate keys and array shapes before a backend has side effects."""
expected = set(self.parameter_names)
received = set(results)
missing = expected - received
extra = received - expected
if missing or extra:
details = []
if missing:
details.append(f"missing={sorted(missing)}")
if extra:
details.append(f"unexpected={sorted(extra)}")
raise ConfigurationError(f"Results do not match the schema ({', '.join(details)}).", operation="validate")
for parameter in self.parameters:
result_shape = np.shape(results[parameter.name])
for setpoint_name in parameter.setpoints:
setpoint_shape = np.shape(results[setpoint_name])
if setpoint_shape != result_shape:
raise ConfigurationError(
f"Parameter '{parameter.name}' has shape {result_shape}, but setpoint '{setpoint_name}' has shape {setpoint_shape}.",
operation="validate",
)