Source code for base.base_interval
"""
A single interval with a begin and and end, either open or closed at its ends.
"""
from clothesline.algebra.symbols import (
is_symbol,
x_equals,
x_lt,
x_gt,
x_repr,
x_subtract,
)
#
from clothesline.exceptions import (
InvalidValueError,
MetricNotImplementedError,
UnserializableItemError,
)
[docs]class BaseInterval:
"""
A single uninterrupted interval over the domain field:
[a,b] or (a,b) or (a,b] or [a,b)
defined by two IntervalPeg objects. It can span to infinities.
Concrete classes must provide builder() and utils() in a standard way
(see the real-interval case) and, if desired,
define metric and serializability properties as well.
"""
# What follows should be considered by subclassers:
metric = None
value_encoder = None
value_decoder = None
serializing_class = None
serializing_version = None
[docs] @staticmethod
def builder():
"""Create and return a "builder" for these intervals."""
[docs] @staticmethod
def utils():
"""Create an "interval utils" object for these intervals."""
# Subclassers may stop reading here.
def __init__(self, begin, end):
"""
begin and end are IntervalPeg instances
"""
if x_gt(begin.value, end.value):
raise InvalidValueError("Interval begin must come before its end")
if x_equals(begin.value, end.value):
if begin.included != end.included:
raise InvalidValueError(
"Contradicting inclusion for point-like interval"
)
if not begin.included and not end.included:
raise InvalidValueError("Empty point-like open set is invalid")
self.begin = begin
self.end = end
def __eq__(self, other):
if isinstance(other, self.__class__): # noqa: PLR1705
return self.begin == other.begin and self.end == other.end
else:
return False
def __hash__(self):
return hash((self.__class__, hash(self.begin), hash(self.end)))
def __repr__(self):
begin_name = x_repr(self.begin.value)
begin_paren = "[" if self.begin.included else "("
end_name = x_repr(self.end.value)
end_paren = "]" if self.end.included else ")"
return f"{begin_paren}{begin_name}, {end_name}{end_paren}"
[docs] def to_dict(self):
"""
Return a json-encodable representation of this interval.
"""
if self.value_encoder: # noqa: PLR1705
return {
"class": self.serializing_class,
"version": self.serializing_version,
"pegs": [
self.begin.to_dict(v_encoder=self.value_encoder),
self.end.to_dict(v_encoder=self.value_encoder),
],
}
else:
raise UnserializableItemError
[docs] def contains(self, value): # noqa: PLR0911
"""
Test whether a value belongs to the interval.
Infinities are allowed as 'value' argument, but never belong.
"""
if is_symbol(value): # noqa: PLR1705
return False
else:
# value is a regular number:
if x_lt(self.begin.value, value):
# value to the right of begin
if x_lt(value, self.end.value): # noqa: PLR1705
# value to the left of end
return True
else:
# value to the right, or equal to, end
if x_equals(self.end.value, value): # noqa: PLR1705
# value sits at end
return self.end.included
else:
# value to the right of end
return False
else:
# value to the left, or equal to begin
if x_equals(self.begin.value, value): # noqa: PLR1705
# value sits at begin
return self.begin.included
else:
# value to the left of begin
return False
[docs] def extension(self):
"""
If a metric is defined for this interval type,
use it to compute this interval's 'extension'.
"""
if self.metric: # noqa: PLR1705
return x_subtract(
self.end.value,
self.begin.value,
subtracter=self.metric.subtracter,
)
else:
raise MetricNotImplementedError
[docs] def pegs(self):
"""Return the two ends, iterably."""
yield self.begin
yield self.end
[docs] def intervals(self):
"""
Return an 'iterable' over a single element, this interval.
This is only to enable quick-syntax for those IntervalSet
set-wise operations whereby the second operand is a puny Interval.
"""
yield self