Source code for qblox_scheduler.operations.loop_domains

# Repository: https://gitlab.com/qblox/packages/software/qblox-scheduler
# Licensed according to the LICENSE file on the main branch
#
# Copyright 2025, Qblox B.V.
"""Domains to loop over with ``Schedule.loop``."""

from __future__ import annotations

import math
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Generic, TypeVar, Union

from pydantic import field_validator

from qblox_scheduler.helpers.collections import make_hash
from qblox_scheduler.operations.expressions import DType
from qblox_scheduler.structure.model import DataStructure

if TYPE_CHECKING:
    from collections.abc import Iterator


[docs] T = TypeVar("T")
[docs] class Domain(DataStructure, ABC, Generic[T]): """An object representing a range of values to loop over."""
[docs] dtype: DType
"""Data type of the linear domain.""" def __hash__(self) -> int: return make_hash(tuple(Domain.model_fields)) @abstractmethod def __len__(self) -> int: ... @abstractmethod def __getitem__(self, index: int) -> T: ... @abstractmethod
[docs] def values(self) -> Iterator[T]: """Return iterator over all values in this domain."""
@property @abstractmethod
[docs] def num_steps(self) -> int: """Return the number of steps in this domain."""
[docs] class LinearDomain(Domain[Union[complex, float]]): """ Linear range of values to loop over, specified with a start value, an inclusive stop value and the number of linearly spaced points to generate. """
[docs] start: complex | float
"""The starting value of the sequence."""
[docs] stop: complex | float
"""The end value of the sequence."""
[docs] num: int
"""Number of samples to generate. Must be non-negative.""" def __len__(self) -> int: return self.num def __getitem__(self, index: int) -> complex | float: return self.start + index * self.step_size
[docs] def values(self) -> Iterator[complex | float]: """Return iterator over all values in this domain.""" return iter(self[idx] for idx in range(len(self)))
@field_validator("num", mode="before") @classmethod
[docs] def _num_is_strictly_positive(cls, num: int) -> int: if num < 1: raise ValueError("A domain must have at least one point.") return num
@property
[docs] def num_steps(self) -> int: """The number of steps in this domain.""" return self.num
@property
[docs] def step_size(self) -> complex | float: """The step size of the range of values.""" return ( self.stop - self.start if self.num == 1 else (self.stop - self.start) / (self.num - 1) )
# These functions exists to have more flexibility with positional/keyword arguments, # since pydantic models only accept keyword arguments.
[docs] def linspace(start: complex | float, stop: complex | float, num: int, dtype: DType) -> LinearDomain: """ Linear range of values to loop over, specified with a start value, an inclusive stop value and the number of linearly spaced points to generate. Parameters ---------- start The starting value of the sequence. stop The end value of the sequence. num Number of samples to generate. Must be non-negative. dtype Data type of the linear domain. """ return LinearDomain(start=start, stop=stop, num=num, dtype=dtype)
[docs] def arange(start: float, stop: float, step: float, dtype: DType) -> LinearDomain: """ Linear range of values to loop over, specified with a start value, an exclusive stop value and a step size. Parameters ---------- start Start of interval. The interval includes this value. stop End of interval. The interval does not include this value, except in some cases where step is not an integer and floating point round-off affects the length of out. step Spacing between values. For any output out, this is the distance between two adjacent values, out[i+1] - out[i]. dtype Data type of the linear domain. """ num = math.ceil((stop - start) / step) return LinearDomain(start=start, stop=start + (num - 1) * step, num=num, dtype=dtype)