Spaces:
Sleeping
Sleeping
""" | |
Interval Arithmetic for plotting. | |
This module does not implement interval arithmetic accurately and | |
hence cannot be used for purposes other than plotting. If you want | |
to use interval arithmetic, use mpmath's interval arithmetic. | |
The module implements interval arithmetic using numpy and | |
python floating points. The rounding up and down is not handled | |
and hence this is not an accurate implementation of interval | |
arithmetic. | |
The module uses numpy for speed which cannot be achieved with mpmath. | |
""" | |
# Q: Why use numpy? Why not simply use mpmath's interval arithmetic? | |
# A: mpmath's interval arithmetic simulates a floating point unit | |
# and hence is slow, while numpy evaluations are orders of magnitude | |
# faster. | |
# Q: Why create a separate class for intervals? Why not use SymPy's | |
# Interval Sets? | |
# A: The functionalities that will be required for plotting is quite | |
# different from what Interval Sets implement. | |
# Q: Why is rounding up and down according to IEEE754 not handled? | |
# A: It is not possible to do it in both numpy and python. An external | |
# library has to used, which defeats the whole purpose i.e., speed. Also | |
# rounding is handled for very few functions in those libraries. | |
# Q Will my plots be affected? | |
# A It will not affect most of the plots. The interval arithmetic | |
# module based suffers the same problems as that of floating point | |
# arithmetic. | |
from sympy.core.numbers import int_valued | |
from sympy.core.logic import fuzzy_and | |
from sympy.simplify.simplify import nsimplify | |
from .interval_membership import intervalMembership | |
class interval: | |
""" Represents an interval containing floating points as start and | |
end of the interval | |
The is_valid variable tracks whether the interval obtained as the | |
result of the function is in the domain and is continuous. | |
- True: Represents the interval result of a function is continuous and | |
in the domain of the function. | |
- False: The interval argument of the function was not in the domain of | |
the function, hence the is_valid of the result interval is False | |
- None: The function was not continuous over the interval or | |
the function's argument interval is partly in the domain of the | |
function | |
A comparison between an interval and a real number, or a | |
comparison between two intervals may return ``intervalMembership`` | |
of two 3-valued logic values. | |
""" | |
def __init__(self, *args, is_valid=True, **kwargs): | |
self.is_valid = is_valid | |
if len(args) == 1: | |
if isinstance(args[0], interval): | |
self.start, self.end = args[0].start, args[0].end | |
else: | |
self.start = float(args[0]) | |
self.end = float(args[0]) | |
elif len(args) == 2: | |
if args[0] < args[1]: | |
self.start = float(args[0]) | |
self.end = float(args[1]) | |
else: | |
self.start = float(args[1]) | |
self.end = float(args[0]) | |
else: | |
raise ValueError("interval takes a maximum of two float values " | |
"as arguments") | |
def mid(self): | |
return (self.start + self.end) / 2.0 | |
def width(self): | |
return self.end - self.start | |
def __repr__(self): | |
return "interval(%f, %f)" % (self.start, self.end) | |
def __str__(self): | |
return "[%f, %f]" % (self.start, self.end) | |
def __lt__(self, other): | |
if isinstance(other, (int, float)): | |
if self.end < other: | |
return intervalMembership(True, self.is_valid) | |
elif self.start > other: | |
return intervalMembership(False, self.is_valid) | |
else: | |
return intervalMembership(None, self.is_valid) | |
elif isinstance(other, interval): | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
if self.end < other. start: | |
return intervalMembership(True, valid) | |
if self.start > other.end: | |
return intervalMembership(False, valid) | |
return intervalMembership(None, valid) | |
else: | |
return NotImplemented | |
def __gt__(self, other): | |
if isinstance(other, (int, float)): | |
if self.start > other: | |
return intervalMembership(True, self.is_valid) | |
elif self.end < other: | |
return intervalMembership(False, self.is_valid) | |
else: | |
return intervalMembership(None, self.is_valid) | |
elif isinstance(other, interval): | |
return other.__lt__(self) | |
else: | |
return NotImplemented | |
def __eq__(self, other): | |
if isinstance(other, (int, float)): | |
if self.start == other and self.end == other: | |
return intervalMembership(True, self.is_valid) | |
if other in self: | |
return intervalMembership(None, self.is_valid) | |
else: | |
return intervalMembership(False, self.is_valid) | |
if isinstance(other, interval): | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
if self.start == other.start and self.end == other.end: | |
return intervalMembership(True, valid) | |
elif self.__lt__(other)[0] is not None: | |
return intervalMembership(False, valid) | |
else: | |
return intervalMembership(None, valid) | |
else: | |
return NotImplemented | |
def __ne__(self, other): | |
if isinstance(other, (int, float)): | |
if self.start == other and self.end == other: | |
return intervalMembership(False, self.is_valid) | |
if other in self: | |
return intervalMembership(None, self.is_valid) | |
else: | |
return intervalMembership(True, self.is_valid) | |
if isinstance(other, interval): | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
if self.start == other.start and self.end == other.end: | |
return intervalMembership(False, valid) | |
if not self.__lt__(other)[0] is None: | |
return intervalMembership(True, valid) | |
return intervalMembership(None, valid) | |
else: | |
return NotImplemented | |
def __le__(self, other): | |
if isinstance(other, (int, float)): | |
if self.end <= other: | |
return intervalMembership(True, self.is_valid) | |
if self.start > other: | |
return intervalMembership(False, self.is_valid) | |
else: | |
return intervalMembership(None, self.is_valid) | |
if isinstance(other, interval): | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
if self.end <= other.start: | |
return intervalMembership(True, valid) | |
if self.start > other.end: | |
return intervalMembership(False, valid) | |
return intervalMembership(None, valid) | |
else: | |
return NotImplemented | |
def __ge__(self, other): | |
if isinstance(other, (int, float)): | |
if self.start >= other: | |
return intervalMembership(True, self.is_valid) | |
elif self.end < other: | |
return intervalMembership(False, self.is_valid) | |
else: | |
return intervalMembership(None, self.is_valid) | |
elif isinstance(other, interval): | |
return other.__le__(self) | |
def __add__(self, other): | |
if isinstance(other, (int, float)): | |
if self.is_valid: | |
return interval(self.start + other, self.end + other) | |
else: | |
start = self.start + other | |
end = self.end + other | |
return interval(start, end, is_valid=self.is_valid) | |
elif isinstance(other, interval): | |
start = self.start + other.start | |
end = self.end + other.end | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
return interval(start, end, is_valid=valid) | |
else: | |
return NotImplemented | |
__radd__ = __add__ | |
def __sub__(self, other): | |
if isinstance(other, (int, float)): | |
start = self.start - other | |
end = self.end - other | |
return interval(start, end, is_valid=self.is_valid) | |
elif isinstance(other, interval): | |
start = self.start - other.end | |
end = self.end - other.start | |
valid = fuzzy_and([self.is_valid, other.is_valid]) | |
return interval(start, end, is_valid=valid) | |
else: | |
return NotImplemented | |
def __rsub__(self, other): | |
if isinstance(other, (int, float)): | |
start = other - self.end | |
end = other - self.start | |
return interval(start, end, is_valid=self.is_valid) | |
elif isinstance(other, interval): | |
return other.__sub__(self) | |
else: | |
return NotImplemented | |
def __neg__(self): | |
if self.is_valid: | |
return interval(-self.end, -self.start) | |
else: | |
return interval(-self.end, -self.start, is_valid=self.is_valid) | |
def __mul__(self, other): | |
if isinstance(other, interval): | |
if self.is_valid is False or other.is_valid is False: | |
return interval(-float('inf'), float('inf'), is_valid=False) | |
elif self.is_valid is None or other.is_valid is None: | |
return interval(-float('inf'), float('inf'), is_valid=None) | |
else: | |
inters = [] | |
inters.append(self.start * other.start) | |
inters.append(self.end * other.start) | |
inters.append(self.start * other.end) | |
inters.append(self.end * other.end) | |
start = min(inters) | |
end = max(inters) | |
return interval(start, end) | |
elif isinstance(other, (int, float)): | |
return interval(self.start*other, self.end*other, is_valid=self.is_valid) | |
else: | |
return NotImplemented | |
__rmul__ = __mul__ | |
def __contains__(self, other): | |
if isinstance(other, (int, float)): | |
return self.start <= other and self.end >= other | |
else: | |
return self.start <= other.start and other.end <= self.end | |
def __rtruediv__(self, other): | |
if isinstance(other, (int, float)): | |
other = interval(other) | |
return other.__truediv__(self) | |
elif isinstance(other, interval): | |
return other.__truediv__(self) | |
else: | |
return NotImplemented | |
def __truediv__(self, other): | |
# Both None and False are handled | |
if not self.is_valid: | |
# Don't divide as the value is not valid | |
return interval(-float('inf'), float('inf'), is_valid=self.is_valid) | |
if isinstance(other, (int, float)): | |
if other == 0: | |
# Divide by zero encountered. valid nowhere | |
return interval(-float('inf'), float('inf'), is_valid=False) | |
else: | |
return interval(self.start / other, self.end / other) | |
elif isinstance(other, interval): | |
if other.is_valid is False or self.is_valid is False: | |
return interval(-float('inf'), float('inf'), is_valid=False) | |
elif other.is_valid is None or self.is_valid is None: | |
return interval(-float('inf'), float('inf'), is_valid=None) | |
else: | |
# denominator contains both signs, i.e. being divided by zero | |
# return the whole real line with is_valid = None | |
if 0 in other: | |
return interval(-float('inf'), float('inf'), is_valid=None) | |
# denominator negative | |
this = self | |
if other.end < 0: | |
this = -this | |
other = -other | |
# denominator positive | |
inters = [] | |
inters.append(this.start / other.start) | |
inters.append(this.end / other.start) | |
inters.append(this.start / other.end) | |
inters.append(this.end / other.end) | |
start = max(inters) | |
end = min(inters) | |
return interval(start, end) | |
else: | |
return NotImplemented | |
def __pow__(self, other): | |
# Implements only power to an integer. | |
from .lib_interval import exp, log | |
if not self.is_valid: | |
return self | |
if isinstance(other, interval): | |
return exp(other * log(self)) | |
elif isinstance(other, (float, int)): | |
if other < 0: | |
return 1 / self.__pow__(abs(other)) | |
else: | |
if int_valued(other): | |
return _pow_int(self, other) | |
else: | |
return _pow_float(self, other) | |
else: | |
return NotImplemented | |
def __rpow__(self, other): | |
if isinstance(other, (float, int)): | |
if not self.is_valid: | |
#Don't do anything | |
return self | |
elif other < 0: | |
if self.width > 0: | |
return interval(-float('inf'), float('inf'), is_valid=False) | |
else: | |
power_rational = nsimplify(self.start) | |
num, denom = power_rational.as_numer_denom() | |
if denom % 2 == 0: | |
return interval(-float('inf'), float('inf'), | |
is_valid=False) | |
else: | |
start = -abs(other)**self.start | |
end = start | |
return interval(start, end) | |
else: | |
return interval(other**self.start, other**self.end) | |
elif isinstance(other, interval): | |
return other.__pow__(self) | |
else: | |
return NotImplemented | |
def __hash__(self): | |
return hash((self.is_valid, self.start, self.end)) | |
def _pow_float(inter, power): | |
"""Evaluates an interval raised to a floating point.""" | |
power_rational = nsimplify(power) | |
num, denom = power_rational.as_numer_denom() | |
if num % 2 == 0: | |
start = abs(inter.start)**power | |
end = abs(inter.end)**power | |
if start < 0: | |
ret = interval(0, max(start, end)) | |
else: | |
ret = interval(start, end) | |
return ret | |
elif denom % 2 == 0: | |
if inter.end < 0: | |
return interval(-float('inf'), float('inf'), is_valid=False) | |
elif inter.start < 0: | |
return interval(0, inter.end**power, is_valid=None) | |
else: | |
return interval(inter.start**power, inter.end**power) | |
else: | |
if inter.start < 0: | |
start = -abs(inter.start)**power | |
else: | |
start = inter.start**power | |
if inter.end < 0: | |
end = -abs(inter.end)**power | |
else: | |
end = inter.end**power | |
return interval(start, end, is_valid=inter.is_valid) | |
def _pow_int(inter, power): | |
"""Evaluates an interval raised to an integer power""" | |
power = int(power) | |
if power & 1: | |
return interval(inter.start**power, inter.end**power) | |
else: | |
if inter.start < 0 and inter.end > 0: | |
start = 0 | |
end = max(inter.start**power, inter.end**power) | |
return interval(start, end) | |
else: | |
return interval(inter.start**power, inter.end**power) | |