Source code for bqskit.ir.interval

"""This module implements the CycleInterval class."""
from __future__ import annotations

import logging
from typing import Any
from typing import Iterator
from typing import Tuple
from typing import Union

from typing_extensions import TypeGuard

from bqskit.utils.typing import is_integer
_logger = logging.getLogger(__name__)


[docs] class CycleInterval(Tuple[int, int]): """ The CycleInterval class. Represents an inclusive range of cycles in a given circuit. """ def __new__( cls, lower_or_tuple: int | tuple[int, int], upper: int | None = None, ) -> CycleInterval: """ CycleInterval Constructor. Allows constructing a CycleInterval with either a tuple of ints or two ints. Args: lower_or_tuple (int | tuple[int, int]): Either the lower bound for the interval or the tuple of lower and upper bounds. upper (int | None): The upper bound for the interval. If a tuple is passed in for `lower_or_tuple` then this should be None. Returns: (CycleInterval): The constructed CycleInterval. Raises: ValueError: If `lower_or_tuple` is a tuple and 'upper' is not None, or if `lower_or_tuple` is an integer and `upper` is None. ValueError: If the lower bound is greater than the upper bound. ValueError: If either bound is negative. Notes: The lower and upper bounds are inclusive. """ if upper is not None and not is_integer(upper): raise TypeError( f'Expected int or None for upper, got {type(upper)}.', ) if isinstance(lower_or_tuple, tuple): if not CycleInterval.is_interval(lower_or_tuple): raise TypeError('Expected two integer arguments.') if upper is not None: raise ValueError('Unable to handle extra argument.') lower = lower_or_tuple[0] upper = lower_or_tuple[1] elif is_integer(lower_or_tuple) and is_integer(upper): lower = lower_or_tuple elif is_integer(lower_or_tuple) and upper is None: raise ValueError('Expected two integer arguments.') else: raise TypeError('Expected two integer arguments.') if lower > upper: raise ValueError( 'Expected lower to be <= upper, got {lower} <= {upper}.', ) if lower < 0: raise ValueError( 'Expected positive integers, got {lower} and {upper}.', ) return super().__new__(cls, (lower, upper)) # type: ignore @property def lower(self) -> int: """The interval's inclusive lower bound.""" return self[0] @property def upper(self) -> int: """The interval's inclusive upper bound.""" return self[1] @property def indices(self) -> list[int]: """The indices contained within the interval.""" return list(range(self.lower, self.upper + 1)) def __contains__(self, cycle_index: object) -> bool: """Return true if `cycle_index` is inside this interval.""" return self.lower <= cycle_index <= self.upper # type: ignore def __iter__(self) -> Iterator[int]: """Return an iterator for all indices contained in the interval.""" return range(self.lower, self.upper + 1).__iter__() def __len__(self) -> int: """The length of the interval.""" return self.upper - self.lower + 1
[docs] def overlaps(self, other: IntervalLike) -> bool: """Return true if `other` overlaps with this interval.""" if not CycleInterval.is_interval(other): raise TypeError(f'Expected CycleInterval, got {type(other)}.') other = CycleInterval(other) return self.lower <= other.upper and self.upper >= other.lower
[docs] def intersection(self, other: IntervalLike) -> CycleInterval: """Return the range defined by both `self` and `other` interval.""" if not CycleInterval.is_interval(other): raise TypeError(f'Expected CycleInterval, got {type(other)}.') other = CycleInterval(other) if not self.overlaps(other): raise ValueError('Empty intersection in interval.') return CycleInterval( max(self.lower, other.lower), min(self.upper, other.upper), )
[docs] def union(self, other: IntervalLike) -> CycleInterval: """Return the range defined by `self` or `other` interval.""" if not CycleInterval.is_interval(other): raise TypeError(f'Expected CycleInterval, got {type(other)}.') other = CycleInterval(other) if not self.overlaps(other) and ( self.upper + 1 != other[0] and self.lower - 1 != other[1] ): raise ValueError('Union would lead to invalid interval.') return CycleInterval( min(self.lower, other.lower), max(self.upper, other.upper), )
def __lt__(self, other: tuple[int, ...]) -> bool: """ Return true if `self` comes before `other`. The less than operator defines a partial ordering. """ if CycleInterval.is_interval(other): return self.upper < other[0] return NotImplemented
[docs] @staticmethod def is_interval(interval: Any) -> TypeGuard[IntervalLike]: """Return true if `interval` is a IntervalLike.""" if isinstance(interval, CycleInterval): return True if not isinstance(interval, tuple): _logger.log(0, 'Bounds is not a tuple.') return False if len(interval) != 2: _logger.log( 0, 'Expected interval to contain two values' f', got {len(interval)}.', ) return False if not is_integer(interval[0]): _logger.log( 0, 'Expected integer values in interval' f', got {type(interval[0])}.', ) return False if not is_integer(interval[1]): _logger.log( 0, 'Expected integer values in interval' f', got {type(interval[1])}.', ) return False return True
def __repr__(self) -> str: """Return a string representation of the interval.""" return f'Interval(lower={self.lower}, upper={self.upper})'
IntervalLike = Union[Tuple[int, int], CycleInterval]