import numpy as np
[docs]
class Interval:
"""
A class handling [A, B] line segments and numpy operations on them.
The following magic methods have been implemented for `Interval`:
- creation using `Interval(A, B)` syntax (if `A == B`, represents a point)
- comparison operations with numbers (True if holds for the whole Interval)
- arithmetic operations, which act element-wise for left and right edge
- access methods: `A == Interval(A, B)[0] == Interval(A, B).left`
- container methods: 5 and 6.2 are `in Interval(5, 7)`, but 7. might not be
"""
# CREATION METHODS
def __init__(self, left, right):
self.left = min(left, right)
self.right = max(left, right)
# COMPARISON METHODS
[docs]
def __eq__(self, other):
return self.left == other.left and self.right == other.right
[docs]
def __lt__(self, other):
return self.right < other
[docs]
def __gt__(self, other):
return self.left > other
[docs]
def __le__(self, other):
return self.right <= other
[docs]
def __ge__(self, other):
return self.left >= other
# UNARY OPERATIONS
[docs]
def __pos__(self):
return self
[docs]
def __neg__(self):
return Interval(-self.right, -self.left)
# ARITHMETIC METHODS
[docs]
def __add__(self, other):
return Interval(self.left + other, self.right + other)
[docs]
def __sub__(self, other):
return Interval(self.left - other, self.right - other)
[docs]
def __mul__(self, other):
return Interval(self.left * other, self.right * other)
[docs]
def __truediv__(self, other):
return Interval(self.left / other, self.right / other)
# REPRESENTATION METHODS
[docs]
def __str__(self):
return f'[{self.left}, {self.right}]'
[docs]
def __repr__(self):
return f'Interval({self.left}, {self.right})'
# CONTAINER METHODS
[docs]
def __iter__(self):
yield self.left
yield self.right
[docs]
def __getitem__(self, key):
return [self.left, self.right][key]
[docs]
def __setitem__(self, key, value):
new_limits = [self.left, self.right]
new_limits[key] = value
self.left, self.right = new_limits
[docs]
def __contains__(self, item):
return self.left <= self._min(item) and self._max(item) <= self.right
[docs]
def __len__(self):
return 2
[docs]
@staticmethod
def _min(item):
"""Recursively returns minimum of args if possible, otherwise args"""
try:
return min(item)
except TypeError:
return item
[docs]
@staticmethod
def _max(item):
"""Recursively returns maximum of args if possible, otherwise args"""
try:
return max(item)
except TypeError:
return item
[docs]
def arange(self, step=1):
"""
Return a 1D-list of values from left to right every step
:param step: spacing between adjacent values, default 1.
:type step: int or float
:return: array of values from left to right (including right) every step
:rtype: np.array
"""
epsilon = step * 1e-7
size = 1 + int((self.right - self.left + epsilon) / step)
return np.array([self.left + step * i for i in range(size)])
[docs]
def comb_with(self, *others, step=1):
"""
Return combinations of self.arange(step) with every other.arange(step)
:param others: interval or iterable of intervals to comb self with
:type others: Interval or tuple or list
:param step: spacing between adjacent values, default 1.
:type step: int or float
:return: array of all combinations found in numpy.meshgrid every step
:rtype: np.array
"""
arrays = [self.arange(step)] + [other.arange(step) for other in others]
return np.array(np.meshgrid(*arrays)).reshape(len(arrays), -1)
[docs]
def mesh_with(self, *others, step=1):
"""
Return a numpy.mesh of self.arange(step) with every other.arange(step)
:param others: interval or iterable of intervals to mesh self with
:type others: Interval or tuple or list
:param step: spacing between adjacent values, default 1.
:type step: int or float
:return: array of values meshed by numpy.meshgrid every step
:rtype: np.array
"""
arrays = [self.arange(step)] + [other.arange(step) for other in others]
return np.meshgrid(*arrays)