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]
class Domain(DataStructure, ABC, Generic[T]):
"""An object representing a range of values to loop over."""
"""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."""
"""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)