add read me
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
from .interval_arithmetic import interval
|
||||
from .lib_interval import (Abs, exp, log, log10, sin, cos, tan, sqrt,
|
||||
imin, imax, sinh, cosh, tanh, acosh, asinh, atanh,
|
||||
asin, acos, atan, ceil, floor, And, Or)
|
||||
|
||||
__all__ = [
|
||||
'interval',
|
||||
|
||||
'Abs', 'exp', 'log', 'log10', 'sin', 'cos', 'tan', 'sqrt', 'imin', 'imax',
|
||||
'sinh', 'cosh', 'tanh', 'acosh', 'asinh', 'atanh', 'asin', 'acos', 'atan',
|
||||
'ceil', 'floor', 'And', 'Or',
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,413 @@
|
||||
"""
|
||||
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")
|
||||
|
||||
@property
|
||||
def mid(self):
|
||||
return (self.start + self.end) / 2.0
|
||||
|
||||
@property
|
||||
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)
|
||||
@@ -0,0 +1,78 @@
|
||||
from sympy.core.logic import fuzzy_and, fuzzy_or, fuzzy_not, fuzzy_xor
|
||||
|
||||
|
||||
class intervalMembership:
|
||||
"""Represents a boolean expression returned by the comparison of
|
||||
the interval object.
|
||||
|
||||
Parameters
|
||||
==========
|
||||
|
||||
(a, b) : (bool, bool)
|
||||
The first value determines the comparison as follows:
|
||||
- True: If the comparison is True throughout the intervals.
|
||||
- False: If the comparison is False throughout the intervals.
|
||||
- None: If the comparison is True for some part of the intervals.
|
||||
|
||||
The second value is determined as follows:
|
||||
- True: If both the intervals in comparison are valid.
|
||||
- False: If at least one of the intervals is False, else
|
||||
- None
|
||||
"""
|
||||
def __init__(self, a, b):
|
||||
self._wrapped = (a, b)
|
||||
|
||||
def __getitem__(self, i):
|
||||
try:
|
||||
return self._wrapped[i]
|
||||
except IndexError:
|
||||
raise IndexError(
|
||||
"{} must be a valid indexing for the 2-tuple."
|
||||
.format(i))
|
||||
|
||||
def __len__(self):
|
||||
return 2
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._wrapped)
|
||||
|
||||
def __str__(self):
|
||||
return "intervalMembership({}, {})".format(*self)
|
||||
__repr__ = __str__
|
||||
|
||||
def __and__(self, other):
|
||||
if not isinstance(other, intervalMembership):
|
||||
raise ValueError(
|
||||
"The comparison is not supported for {}.".format(other))
|
||||
|
||||
a1, b1 = self
|
||||
a2, b2 = other
|
||||
return intervalMembership(fuzzy_and([a1, a2]), fuzzy_and([b1, b2]))
|
||||
|
||||
def __or__(self, other):
|
||||
if not isinstance(other, intervalMembership):
|
||||
raise ValueError(
|
||||
"The comparison is not supported for {}.".format(other))
|
||||
|
||||
a1, b1 = self
|
||||
a2, b2 = other
|
||||
return intervalMembership(fuzzy_or([a1, a2]), fuzzy_and([b1, b2]))
|
||||
|
||||
def __invert__(self):
|
||||
a, b = self
|
||||
return intervalMembership(fuzzy_not(a), b)
|
||||
|
||||
def __xor__(self, other):
|
||||
if not isinstance(other, intervalMembership):
|
||||
raise ValueError(
|
||||
"The comparison is not supported for {}.".format(other))
|
||||
|
||||
a1, b1 = self
|
||||
a2, b2 = other
|
||||
return intervalMembership(fuzzy_xor([a1, a2]), fuzzy_and([b1, b2]))
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._wrapped == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return self._wrapped != other
|
||||
@@ -0,0 +1,452 @@
|
||||
""" The module contains implemented functions for interval arithmetic."""
|
||||
from functools import reduce
|
||||
|
||||
from sympy.plotting.intervalmath import interval
|
||||
from sympy.external import import_module
|
||||
|
||||
|
||||
def Abs(x):
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(abs(x))
|
||||
elif isinstance(x, interval):
|
||||
if x.start < 0 and x.end > 0:
|
||||
return interval(0, max(abs(x.start), abs(x.end)), is_valid=x.is_valid)
|
||||
else:
|
||||
return interval(abs(x.start), abs(x.end))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
#Monotonic
|
||||
|
||||
|
||||
def exp(x):
|
||||
"""evaluates the exponential of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.exp(x), np.exp(x))
|
||||
elif isinstance(x, interval):
|
||||
return interval(np.exp(x.start), np.exp(x.end), is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#Monotonic
|
||||
def log(x):
|
||||
"""evaluates the natural logarithm of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
if x <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.log(x))
|
||||
elif isinstance(x, interval):
|
||||
if not x.is_valid:
|
||||
return interval(-np.inf, np.inf, is_valid=x.is_valid)
|
||||
elif x.end <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
elif x.start <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
|
||||
return interval(np.log(x.start), np.log(x.end))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#Monotonic
|
||||
def log10(x):
|
||||
"""evaluates the logarithm to the base 10 of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
if x <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.log10(x))
|
||||
elif isinstance(x, interval):
|
||||
if not x.is_valid:
|
||||
return interval(-np.inf, np.inf, is_valid=x.is_valid)
|
||||
elif x.end <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
elif x.start <= 0:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
return interval(np.log10(x.start), np.log10(x.end))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#Monotonic
|
||||
def atan(x):
|
||||
"""evaluates the tan inverse of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.arctan(x))
|
||||
elif isinstance(x, interval):
|
||||
start = np.arctan(x.start)
|
||||
end = np.arctan(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#periodic
|
||||
def sin(x):
|
||||
"""evaluates the sine of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.sin(x))
|
||||
elif isinstance(x, interval):
|
||||
if not x.is_valid:
|
||||
return interval(-1, 1, is_valid=x.is_valid)
|
||||
na, __ = divmod(x.start, np.pi / 2.0)
|
||||
nb, __ = divmod(x.end, np.pi / 2.0)
|
||||
start = min(np.sin(x.start), np.sin(x.end))
|
||||
end = max(np.sin(x.start), np.sin(x.end))
|
||||
if nb - na > 4:
|
||||
return interval(-1, 1, is_valid=x.is_valid)
|
||||
elif na == nb:
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
if (na - 1) // 4 != (nb - 1) // 4:
|
||||
#sin has max
|
||||
end = 1
|
||||
if (na - 3) // 4 != (nb - 3) // 4:
|
||||
#sin has min
|
||||
start = -1
|
||||
return interval(start, end)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#periodic
|
||||
def cos(x):
|
||||
"""Evaluates the cos of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.sin(x))
|
||||
elif isinstance(x, interval):
|
||||
if not (np.isfinite(x.start) and np.isfinite(x.end)):
|
||||
return interval(-1, 1, is_valid=x.is_valid)
|
||||
na, __ = divmod(x.start, np.pi / 2.0)
|
||||
nb, __ = divmod(x.end, np.pi / 2.0)
|
||||
start = min(np.cos(x.start), np.cos(x.end))
|
||||
end = max(np.cos(x.start), np.cos(x.end))
|
||||
if nb - na > 4:
|
||||
#differ more than 2*pi
|
||||
return interval(-1, 1, is_valid=x.is_valid)
|
||||
elif na == nb:
|
||||
#in the same quadarant
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
if (na) // 4 != (nb) // 4:
|
||||
#cos has max
|
||||
end = 1
|
||||
if (na - 2) // 4 != (nb - 2) // 4:
|
||||
#cos has min
|
||||
start = -1
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def tan(x):
|
||||
"""Evaluates the tan of an interval"""
|
||||
return sin(x) / cos(x)
|
||||
|
||||
|
||||
#Monotonic
|
||||
def sqrt(x):
|
||||
"""Evaluates the square root of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
if x > 0:
|
||||
return interval(np.sqrt(x))
|
||||
else:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
elif isinstance(x, interval):
|
||||
#Outside the domain
|
||||
if x.end < 0:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
#Partially outside the domain
|
||||
elif x.start < 0:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
else:
|
||||
return interval(np.sqrt(x.start), np.sqrt(x.end),
|
||||
is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def imin(*args):
|
||||
"""Evaluates the minimum of a list of intervals"""
|
||||
np = import_module('numpy')
|
||||
if not all(isinstance(arg, (int, float, interval)) for arg in args):
|
||||
return NotImplementedError
|
||||
else:
|
||||
new_args = [a for a in args if isinstance(a, (int, float))
|
||||
or a.is_valid]
|
||||
if len(new_args) == 0:
|
||||
if all(a.is_valid is False for a in args):
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
start_array = [a if isinstance(a, (int, float)) else a.start
|
||||
for a in new_args]
|
||||
|
||||
end_array = [a if isinstance(a, (int, float)) else a.end
|
||||
for a in new_args]
|
||||
return interval(min(start_array), min(end_array))
|
||||
|
||||
|
||||
def imax(*args):
|
||||
"""Evaluates the maximum of a list of intervals"""
|
||||
np = import_module('numpy')
|
||||
if not all(isinstance(arg, (int, float, interval)) for arg in args):
|
||||
return NotImplementedError
|
||||
else:
|
||||
new_args = [a for a in args if isinstance(a, (int, float))
|
||||
or a.is_valid]
|
||||
if len(new_args) == 0:
|
||||
if all(a.is_valid is False for a in args):
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
start_array = [a if isinstance(a, (int, float)) else a.start
|
||||
for a in new_args]
|
||||
|
||||
end_array = [a if isinstance(a, (int, float)) else a.end
|
||||
for a in new_args]
|
||||
|
||||
return interval(max(start_array), max(end_array))
|
||||
|
||||
|
||||
#Monotonic
|
||||
def sinh(x):
|
||||
"""Evaluates the hyperbolic sine of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.sinh(x), np.sinh(x))
|
||||
elif isinstance(x, interval):
|
||||
return interval(np.sinh(x.start), np.sinh(x.end), is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def cosh(x):
|
||||
"""Evaluates the hyperbolic cos of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.cosh(x), np.cosh(x))
|
||||
elif isinstance(x, interval):
|
||||
#both signs
|
||||
if x.start < 0 and x.end > 0:
|
||||
end = max(np.cosh(x.start), np.cosh(x.end))
|
||||
return interval(1, end, is_valid=x.is_valid)
|
||||
else:
|
||||
#Monotonic
|
||||
start = np.cosh(x.start)
|
||||
end = np.cosh(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
#Monotonic
|
||||
def tanh(x):
|
||||
"""Evaluates the hyperbolic tan of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.tanh(x), np.tanh(x))
|
||||
elif isinstance(x, interval):
|
||||
return interval(np.tanh(x.start), np.tanh(x.end), is_valid=x.is_valid)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def asin(x):
|
||||
"""Evaluates the inverse sine of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
#Outside the domain
|
||||
if abs(x) > 1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.arcsin(x), np.arcsin(x))
|
||||
elif isinstance(x, interval):
|
||||
#Outside the domain
|
||||
if x.is_valid is False or x.start > 1 or x.end < -1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
#Partially outside the domain
|
||||
elif x.start < -1 or x.end > 1:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
else:
|
||||
start = np.arcsin(x.start)
|
||||
end = np.arcsin(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
|
||||
|
||||
def acos(x):
|
||||
"""Evaluates the inverse cos of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
if abs(x) > 1:
|
||||
#Outside the domain
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.arccos(x), np.arccos(x))
|
||||
elif isinstance(x, interval):
|
||||
#Outside the domain
|
||||
if x.is_valid is False or x.start > 1 or x.end < -1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
#Partially outside the domain
|
||||
elif x.start < -1 or x.end > 1:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
else:
|
||||
start = np.arccos(x.start)
|
||||
end = np.arccos(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
|
||||
|
||||
def ceil(x):
|
||||
"""Evaluates the ceiling of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.ceil(x))
|
||||
elif isinstance(x, interval):
|
||||
if x.is_valid is False:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
start = np.ceil(x.start)
|
||||
end = np.ceil(x.end)
|
||||
#Continuous over the interval
|
||||
if start == end:
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
#Not continuous over the interval
|
||||
return interval(start, end, is_valid=None)
|
||||
else:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
def floor(x):
|
||||
"""Evaluates the floor of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.floor(x))
|
||||
elif isinstance(x, interval):
|
||||
if x.is_valid is False:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
start = np.floor(x.start)
|
||||
end = np.floor(x.end)
|
||||
#continuous over the argument
|
||||
if start == end:
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
#not continuous over the interval
|
||||
return interval(start, end, is_valid=None)
|
||||
else:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
def acosh(x):
|
||||
"""Evaluates the inverse hyperbolic cosine of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
#Outside the domain
|
||||
if x < 1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.arccosh(x))
|
||||
elif isinstance(x, interval):
|
||||
#Outside the domain
|
||||
if x.end < 1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
#Partly outside the domain
|
||||
elif x.start < 1:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
else:
|
||||
start = np.arccosh(x.start)
|
||||
end = np.arccosh(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
#Monotonic
|
||||
def asinh(x):
|
||||
"""Evaluates the inverse hyperbolic sine of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
return interval(np.arcsinh(x))
|
||||
elif isinstance(x, interval):
|
||||
start = np.arcsinh(x.start)
|
||||
end = np.arcsinh(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
def atanh(x):
|
||||
"""Evaluates the inverse hyperbolic tangent of an interval"""
|
||||
np = import_module('numpy')
|
||||
if isinstance(x, (int, float)):
|
||||
#Outside the domain
|
||||
if abs(x) >= 1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
else:
|
||||
return interval(np.arctanh(x))
|
||||
elif isinstance(x, interval):
|
||||
#outside the domain
|
||||
if x.is_valid is False or x.start >= 1 or x.end <= -1:
|
||||
return interval(-np.inf, np.inf, is_valid=False)
|
||||
#partly outside the domain
|
||||
elif x.start <= -1 or x.end >= 1:
|
||||
return interval(-np.inf, np.inf, is_valid=None)
|
||||
else:
|
||||
start = np.arctanh(x.start)
|
||||
end = np.arctanh(x.end)
|
||||
return interval(start, end, is_valid=x.is_valid)
|
||||
else:
|
||||
return NotImplementedError
|
||||
|
||||
|
||||
#Three valued logic for interval plotting.
|
||||
|
||||
def And(*args):
|
||||
"""Defines the three valued ``And`` behaviour for a 2-tuple of
|
||||
three valued logic values"""
|
||||
def reduce_and(cmp_intervala, cmp_intervalb):
|
||||
if cmp_intervala[0] is False or cmp_intervalb[0] is False:
|
||||
first = False
|
||||
elif cmp_intervala[0] is None or cmp_intervalb[0] is None:
|
||||
first = None
|
||||
else:
|
||||
first = True
|
||||
if cmp_intervala[1] is False or cmp_intervalb[1] is False:
|
||||
second = False
|
||||
elif cmp_intervala[1] is None or cmp_intervalb[1] is None:
|
||||
second = None
|
||||
else:
|
||||
second = True
|
||||
return (first, second)
|
||||
return reduce(reduce_and, args)
|
||||
|
||||
|
||||
def Or(*args):
|
||||
"""Defines the three valued ``Or`` behaviour for a 2-tuple of
|
||||
three valued logic values"""
|
||||
def reduce_or(cmp_intervala, cmp_intervalb):
|
||||
if cmp_intervala[0] is True or cmp_intervalb[0] is True:
|
||||
first = True
|
||||
elif cmp_intervala[0] is None or cmp_intervalb[0] is None:
|
||||
first = None
|
||||
else:
|
||||
first = False
|
||||
|
||||
if cmp_intervala[1] is True or cmp_intervalb[1] is True:
|
||||
second = True
|
||||
elif cmp_intervala[1] is None or cmp_intervalb[1] is None:
|
||||
second = None
|
||||
else:
|
||||
second = False
|
||||
return (first, second)
|
||||
return reduce(reduce_or, args)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,415 @@
|
||||
from sympy.external import import_module
|
||||
from sympy.plotting.intervalmath import (
|
||||
Abs, acos, acosh, And, asin, asinh, atan, atanh, ceil, cos, cosh,
|
||||
exp, floor, imax, imin, interval, log, log10, Or, sin, sinh, sqrt,
|
||||
tan, tanh,
|
||||
)
|
||||
|
||||
np = import_module('numpy')
|
||||
if not np:
|
||||
disabled = True
|
||||
|
||||
|
||||
#requires Numpy. Hence included in interval_functions
|
||||
|
||||
|
||||
def test_interval_pow():
|
||||
a = 2**interval(1, 2) == interval(2, 4)
|
||||
assert a == (True, True)
|
||||
a = interval(1, 2)**interval(1, 2) == interval(1, 4)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 1)**interval(0.5, 2)
|
||||
assert a.is_valid is None
|
||||
a = interval(-2, -1) ** interval(1, 2)
|
||||
assert a.is_valid is False
|
||||
a = interval(-2, -1) ** (1.0 / 2)
|
||||
assert a.is_valid is False
|
||||
a = interval(-1, 1)**(1.0 / 2)
|
||||
assert a.is_valid is None
|
||||
a = interval(-1, 1)**(1.0 / 3) == interval(-1, 1)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 1)**2 == interval(0, 1)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 1) ** (1.0 / 29) == interval(-1, 1)
|
||||
assert a == (True, True)
|
||||
a = -2**interval(1, 1) == interval(-2, -2)
|
||||
assert a == (True, True)
|
||||
|
||||
a = interval(1, 2, is_valid=False)**2
|
||||
assert a.is_valid is False
|
||||
|
||||
a = (-3)**interval(1, 2)
|
||||
assert a.is_valid is False
|
||||
a = (-4)**interval(0.5, 0.5)
|
||||
assert a.is_valid is False
|
||||
assert ((-3)**interval(1, 1) == interval(-3, -3)) == (True, True)
|
||||
|
||||
a = interval(8, 64)**(2.0 / 3)
|
||||
assert abs(a.start - 4) < 1e-10 # eps
|
||||
assert abs(a.end - 16) < 1e-10
|
||||
a = interval(-8, 64)**(2.0 / 3)
|
||||
assert abs(a.start - 4) < 1e-10 # eps
|
||||
assert abs(a.end - 16) < 1e-10
|
||||
|
||||
|
||||
def test_exp():
|
||||
a = exp(interval(-np.inf, 0))
|
||||
assert a.start == np.exp(-np.inf)
|
||||
assert a.end == np.exp(0)
|
||||
a = exp(interval(1, 2))
|
||||
assert a.start == np.exp(1)
|
||||
assert a.end == np.exp(2)
|
||||
a = exp(1)
|
||||
assert a.start == np.exp(1)
|
||||
assert a.end == np.exp(1)
|
||||
|
||||
|
||||
def test_log():
|
||||
a = log(interval(1, 2))
|
||||
assert a.start == 0
|
||||
assert a.end == np.log(2)
|
||||
a = log(interval(-1, 1))
|
||||
assert a.is_valid is None
|
||||
a = log(interval(-3, -1))
|
||||
assert a.is_valid is False
|
||||
a = log(-3)
|
||||
assert a.is_valid is False
|
||||
a = log(2)
|
||||
assert a.start == np.log(2)
|
||||
assert a.end == np.log(2)
|
||||
|
||||
|
||||
def test_log10():
|
||||
a = log10(interval(1, 2))
|
||||
assert a.start == 0
|
||||
assert a.end == np.log10(2)
|
||||
a = log10(interval(-1, 1))
|
||||
assert a.is_valid is None
|
||||
a = log10(interval(-3, -1))
|
||||
assert a.is_valid is False
|
||||
a = log10(-3)
|
||||
assert a.is_valid is False
|
||||
a = log10(2)
|
||||
assert a.start == np.log10(2)
|
||||
assert a.end == np.log10(2)
|
||||
|
||||
|
||||
def test_atan():
|
||||
a = atan(interval(0, 1))
|
||||
assert a.start == np.arctan(0)
|
||||
assert a.end == np.arctan(1)
|
||||
a = atan(1)
|
||||
assert a.start == np.arctan(1)
|
||||
assert a.end == np.arctan(1)
|
||||
|
||||
|
||||
def test_sin():
|
||||
a = sin(interval(0, np.pi / 4))
|
||||
assert a.start == np.sin(0)
|
||||
assert a.end == np.sin(np.pi / 4)
|
||||
|
||||
a = sin(interval(-np.pi / 4, np.pi / 4))
|
||||
assert a.start == np.sin(-np.pi / 4)
|
||||
assert a.end == np.sin(np.pi / 4)
|
||||
|
||||
a = sin(interval(np.pi / 4, 3 * np.pi / 4))
|
||||
assert a.start == np.sin(np.pi / 4)
|
||||
assert a.end == 1
|
||||
|
||||
a = sin(interval(7 * np.pi / 6, 7 * np.pi / 4))
|
||||
assert a.start == -1
|
||||
assert a.end == np.sin(7 * np.pi / 6)
|
||||
|
||||
a = sin(interval(0, 3 * np.pi))
|
||||
assert a.start == -1
|
||||
assert a.end == 1
|
||||
|
||||
a = sin(interval(np.pi / 3, 7 * np.pi / 4))
|
||||
assert a.start == -1
|
||||
assert a.end == 1
|
||||
|
||||
a = sin(np.pi / 4)
|
||||
assert a.start == np.sin(np.pi / 4)
|
||||
assert a.end == np.sin(np.pi / 4)
|
||||
|
||||
a = sin(interval(1, 2, is_valid=False))
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_cos():
|
||||
a = cos(interval(0, np.pi / 4))
|
||||
assert a.start == np.cos(np.pi / 4)
|
||||
assert a.end == 1
|
||||
|
||||
a = cos(interval(-np.pi / 4, np.pi / 4))
|
||||
assert a.start == np.cos(-np.pi / 4)
|
||||
assert a.end == 1
|
||||
|
||||
a = cos(interval(np.pi / 4, 3 * np.pi / 4))
|
||||
assert a.start == np.cos(3 * np.pi / 4)
|
||||
assert a.end == np.cos(np.pi / 4)
|
||||
|
||||
a = cos(interval(3 * np.pi / 4, 5 * np.pi / 4))
|
||||
assert a.start == -1
|
||||
assert a.end == np.cos(3 * np.pi / 4)
|
||||
|
||||
a = cos(interval(0, 3 * np.pi))
|
||||
assert a.start == -1
|
||||
assert a.end == 1
|
||||
|
||||
a = cos(interval(- np.pi / 3, 5 * np.pi / 4))
|
||||
assert a.start == -1
|
||||
assert a.end == 1
|
||||
|
||||
a = cos(interval(1, 2, is_valid=False))
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_tan():
|
||||
a = tan(interval(0, np.pi / 4))
|
||||
assert a.start == 0
|
||||
# must match lib_interval definition of tan:
|
||||
assert a.end == np.sin(np.pi / 4)/np.cos(np.pi / 4)
|
||||
|
||||
a = tan(interval(np.pi / 4, 3 * np.pi / 4))
|
||||
#discontinuity
|
||||
assert a.is_valid is None
|
||||
|
||||
|
||||
def test_sqrt():
|
||||
a = sqrt(interval(1, 4))
|
||||
assert a.start == 1
|
||||
assert a.end == 2
|
||||
|
||||
a = sqrt(interval(0.01, 1))
|
||||
assert a.start == np.sqrt(0.01)
|
||||
assert a.end == 1
|
||||
|
||||
a = sqrt(interval(-1, 1))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = sqrt(interval(-3, -1))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = sqrt(4)
|
||||
assert (a == interval(2, 2)) == (True, True)
|
||||
|
||||
a = sqrt(-3)
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_imin():
|
||||
a = imin(interval(1, 3), interval(2, 5), interval(-1, 3))
|
||||
assert a.start == -1
|
||||
assert a.end == 3
|
||||
|
||||
a = imin(-2, interval(1, 4))
|
||||
assert a.start == -2
|
||||
assert a.end == -2
|
||||
|
||||
a = imin(5, interval(3, 4), interval(-2, 2, is_valid=False))
|
||||
assert a.start == 3
|
||||
assert a.end == 4
|
||||
|
||||
|
||||
def test_imax():
|
||||
a = imax(interval(-2, 2), interval(2, 7), interval(-3, 9))
|
||||
assert a.start == 2
|
||||
assert a.end == 9
|
||||
|
||||
a = imax(8, interval(1, 4))
|
||||
assert a.start == 8
|
||||
assert a.end == 8
|
||||
|
||||
a = imax(interval(1, 2), interval(3, 4), interval(-2, 2, is_valid=False))
|
||||
assert a.start == 3
|
||||
assert a.end == 4
|
||||
|
||||
|
||||
def test_sinh():
|
||||
a = sinh(interval(-1, 1))
|
||||
assert a.start == np.sinh(-1)
|
||||
assert a.end == np.sinh(1)
|
||||
|
||||
a = sinh(1)
|
||||
assert a.start == np.sinh(1)
|
||||
assert a.end == np.sinh(1)
|
||||
|
||||
|
||||
def test_cosh():
|
||||
a = cosh(interval(1, 2))
|
||||
assert a.start == np.cosh(1)
|
||||
assert a.end == np.cosh(2)
|
||||
a = cosh(interval(-2, -1))
|
||||
assert a.start == np.cosh(-1)
|
||||
assert a.end == np.cosh(-2)
|
||||
|
||||
a = cosh(interval(-2, 1))
|
||||
assert a.start == 1
|
||||
assert a.end == np.cosh(-2)
|
||||
|
||||
a = cosh(1)
|
||||
assert a.start == np.cosh(1)
|
||||
assert a.end == np.cosh(1)
|
||||
|
||||
|
||||
def test_tanh():
|
||||
a = tanh(interval(-3, 3))
|
||||
assert a.start == np.tanh(-3)
|
||||
assert a.end == np.tanh(3)
|
||||
|
||||
a = tanh(3)
|
||||
assert a.start == np.tanh(3)
|
||||
assert a.end == np.tanh(3)
|
||||
|
||||
|
||||
def test_asin():
|
||||
a = asin(interval(-0.5, 0.5))
|
||||
assert a.start == np.arcsin(-0.5)
|
||||
assert a.end == np.arcsin(0.5)
|
||||
|
||||
a = asin(interval(-1.5, 1.5))
|
||||
assert a.is_valid is None
|
||||
a = asin(interval(-2, -1.5))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = asin(interval(0, 2))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = asin(interval(2, 5))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = asin(0.5)
|
||||
assert a.start == np.arcsin(0.5)
|
||||
assert a.end == np.arcsin(0.5)
|
||||
|
||||
a = asin(1.5)
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_acos():
|
||||
a = acos(interval(-0.5, 0.5))
|
||||
assert a.start == np.arccos(0.5)
|
||||
assert a.end == np.arccos(-0.5)
|
||||
|
||||
a = acos(interval(-1.5, 1.5))
|
||||
assert a.is_valid is None
|
||||
a = acos(interval(-2, -1.5))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = acos(interval(0, 2))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = acos(interval(2, 5))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = acos(0.5)
|
||||
assert a.start == np.arccos(0.5)
|
||||
assert a.end == np.arccos(0.5)
|
||||
|
||||
a = acos(1.5)
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_ceil():
|
||||
a = ceil(interval(0.2, 0.5))
|
||||
assert a.start == 1
|
||||
assert a.end == 1
|
||||
|
||||
a = ceil(interval(0.5, 1.5))
|
||||
assert a.start == 1
|
||||
assert a.end == 2
|
||||
assert a.is_valid is None
|
||||
|
||||
a = ceil(interval(-5, 5))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = ceil(5.4)
|
||||
assert a.start == 6
|
||||
assert a.end == 6
|
||||
|
||||
|
||||
def test_floor():
|
||||
a = floor(interval(0.2, 0.5))
|
||||
assert a.start == 0
|
||||
assert a.end == 0
|
||||
|
||||
a = floor(interval(0.5, 1.5))
|
||||
assert a.start == 0
|
||||
assert a.end == 1
|
||||
assert a.is_valid is None
|
||||
|
||||
a = floor(interval(-5, 5))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = floor(5.4)
|
||||
assert a.start == 5
|
||||
assert a.end == 5
|
||||
|
||||
|
||||
def test_asinh():
|
||||
a = asinh(interval(1, 2))
|
||||
assert a.start == np.arcsinh(1)
|
||||
assert a.end == np.arcsinh(2)
|
||||
|
||||
a = asinh(0.5)
|
||||
assert a.start == np.arcsinh(0.5)
|
||||
assert a.end == np.arcsinh(0.5)
|
||||
|
||||
|
||||
def test_acosh():
|
||||
a = acosh(interval(3, 5))
|
||||
assert a.start == np.arccosh(3)
|
||||
assert a.end == np.arccosh(5)
|
||||
|
||||
a = acosh(interval(0, 3))
|
||||
assert a.is_valid is None
|
||||
a = acosh(interval(-3, 0.5))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = acosh(0.5)
|
||||
assert a.is_valid is False
|
||||
|
||||
a = acosh(2)
|
||||
assert a.start == np.arccosh(2)
|
||||
assert a.end == np.arccosh(2)
|
||||
|
||||
|
||||
def test_atanh():
|
||||
a = atanh(interval(-0.5, 0.5))
|
||||
assert a.start == np.arctanh(-0.5)
|
||||
assert a.end == np.arctanh(0.5)
|
||||
|
||||
a = atanh(interval(0, 3))
|
||||
assert a.is_valid is None
|
||||
|
||||
a = atanh(interval(-3, -2))
|
||||
assert a.is_valid is False
|
||||
|
||||
a = atanh(0.5)
|
||||
assert a.start == np.arctanh(0.5)
|
||||
assert a.end == np.arctanh(0.5)
|
||||
|
||||
a = atanh(1.5)
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_Abs():
|
||||
assert (Abs(interval(-0.5, 0.5)) == interval(0, 0.5)) == (True, True)
|
||||
assert (Abs(interval(-3, -2)) == interval(2, 3)) == (True, True)
|
||||
assert (Abs(-3) == interval(3, 3)) == (True, True)
|
||||
|
||||
|
||||
def test_And():
|
||||
args = [(True, True), (True, False), (True, None)]
|
||||
assert And(*args) == (True, False)
|
||||
|
||||
args = [(False, True), (None, None), (True, True)]
|
||||
assert And(*args) == (False, None)
|
||||
|
||||
|
||||
def test_Or():
|
||||
args = [(True, True), (True, False), (False, None)]
|
||||
assert Or(*args) == (True, True)
|
||||
args = [(None, None), (False, None), (False, False)]
|
||||
assert Or(*args) == (None, None)
|
||||
@@ -0,0 +1,150 @@
|
||||
from sympy.core.symbol import Symbol
|
||||
from sympy.plotting.intervalmath import interval
|
||||
from sympy.plotting.intervalmath.interval_membership import intervalMembership
|
||||
from sympy.plotting.experimental_lambdify import experimental_lambdify
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_creation():
|
||||
assert intervalMembership(True, True)
|
||||
raises(TypeError, lambda: intervalMembership(True))
|
||||
raises(TypeError, lambda: intervalMembership(True, True, True))
|
||||
|
||||
|
||||
def test_getitem():
|
||||
a = intervalMembership(True, False)
|
||||
assert a[0] is True
|
||||
assert a[1] is False
|
||||
raises(IndexError, lambda: a[2])
|
||||
|
||||
|
||||
def test_str():
|
||||
a = intervalMembership(True, False)
|
||||
assert str(a) == 'intervalMembership(True, False)'
|
||||
assert repr(a) == 'intervalMembership(True, False)'
|
||||
|
||||
|
||||
def test_equivalence():
|
||||
a = intervalMembership(True, True)
|
||||
b = intervalMembership(True, False)
|
||||
assert (a == b) is False
|
||||
assert (a != b) is True
|
||||
|
||||
a = intervalMembership(True, False)
|
||||
b = intervalMembership(True, False)
|
||||
assert (a == b) is True
|
||||
assert (a != b) is False
|
||||
|
||||
|
||||
def test_not():
|
||||
x = Symbol('x')
|
||||
|
||||
r1 = x > -1
|
||||
r2 = x <= -1
|
||||
|
||||
i = interval
|
||||
|
||||
f1 = experimental_lambdify((x,), r1)
|
||||
f2 = experimental_lambdify((x,), r2)
|
||||
|
||||
tt = i(-0.1, 0.1, is_valid=True)
|
||||
tn = i(-0.1, 0.1, is_valid=None)
|
||||
tf = i(-0.1, 0.1, is_valid=False)
|
||||
|
||||
assert f1(tt) == ~f2(tt)
|
||||
assert f1(tn) == ~f2(tn)
|
||||
assert f1(tf) == ~f2(tf)
|
||||
|
||||
nt = i(0.9, 1.1, is_valid=True)
|
||||
nn = i(0.9, 1.1, is_valid=None)
|
||||
nf = i(0.9, 1.1, is_valid=False)
|
||||
|
||||
assert f1(nt) == ~f2(nt)
|
||||
assert f1(nn) == ~f2(nn)
|
||||
assert f1(nf) == ~f2(nf)
|
||||
|
||||
ft = i(1.9, 2.1, is_valid=True)
|
||||
fn = i(1.9, 2.1, is_valid=None)
|
||||
ff = i(1.9, 2.1, is_valid=False)
|
||||
|
||||
assert f1(ft) == ~f2(ft)
|
||||
assert f1(fn) == ~f2(fn)
|
||||
assert f1(ff) == ~f2(ff)
|
||||
|
||||
|
||||
def test_boolean():
|
||||
# There can be 9*9 test cases in full mapping of the cartesian product.
|
||||
# But we only consider 3*3 cases for simplicity.
|
||||
s = [
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(True, True)
|
||||
]
|
||||
|
||||
# Reduced tests for 'And'
|
||||
a1 = [
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(True, True)
|
||||
]
|
||||
a1_iter = iter(a1)
|
||||
for i in range(len(s)):
|
||||
for j in range(len(s)):
|
||||
assert s[i] & s[j] == next(a1_iter)
|
||||
|
||||
# Reduced tests for 'Or'
|
||||
a1 = [
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(None, False),
|
||||
intervalMembership(True, False),
|
||||
intervalMembership(None, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(True, None),
|
||||
intervalMembership(True, False),
|
||||
intervalMembership(True, None),
|
||||
intervalMembership(True, True)
|
||||
]
|
||||
a1_iter = iter(a1)
|
||||
for i in range(len(s)):
|
||||
for j in range(len(s)):
|
||||
assert s[i] | s[j] == next(a1_iter)
|
||||
|
||||
# Reduced tests for 'Xor'
|
||||
a1 = [
|
||||
intervalMembership(False, False),
|
||||
intervalMembership(None, False),
|
||||
intervalMembership(True, False),
|
||||
intervalMembership(None, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(True, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(False, True)
|
||||
]
|
||||
a1_iter = iter(a1)
|
||||
for i in range(len(s)):
|
||||
for j in range(len(s)):
|
||||
assert s[i] ^ s[j] == next(a1_iter)
|
||||
|
||||
# Reduced tests for 'Not'
|
||||
a1 = [
|
||||
intervalMembership(True, False),
|
||||
intervalMembership(None, None),
|
||||
intervalMembership(False, True)
|
||||
]
|
||||
a1_iter = iter(a1)
|
||||
for i in range(len(s)):
|
||||
assert ~s[i] == next(a1_iter)
|
||||
|
||||
|
||||
def test_boolean_errors():
|
||||
a = intervalMembership(True, True)
|
||||
raises(ValueError, lambda: a & 1)
|
||||
raises(ValueError, lambda: a | 1)
|
||||
raises(ValueError, lambda: a ^ 1)
|
||||
@@ -0,0 +1,213 @@
|
||||
from sympy.plotting.intervalmath import interval
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_interval():
|
||||
assert (interval(1, 1) == interval(1, 1, is_valid=True)) == (True, True)
|
||||
assert (interval(1, 1) == interval(1, 1, is_valid=False)) == (True, False)
|
||||
assert (interval(1, 1) == interval(1, 1, is_valid=None)) == (True, None)
|
||||
assert (interval(1, 1.5) == interval(1, 2)) == (None, True)
|
||||
assert (interval(0, 1) == interval(2, 3)) == (False, True)
|
||||
assert (interval(0, 1) == interval(1, 2)) == (None, True)
|
||||
assert (interval(1, 2) != interval(1, 2)) == (False, True)
|
||||
assert (interval(1, 3) != interval(2, 3)) == (None, True)
|
||||
assert (interval(1, 3) != interval(-5, -3)) == (True, True)
|
||||
assert (
|
||||
interval(1, 3, is_valid=False) != interval(-5, -3)) == (True, False)
|
||||
assert (interval(1, 3, is_valid=None) != interval(-5, 3)) == (None, None)
|
||||
assert (interval(4, 4) != 4) == (False, True)
|
||||
assert (interval(1, 1) == 1) == (True, True)
|
||||
assert (interval(1, 3, is_valid=False) == interval(1, 3)) == (True, False)
|
||||
assert (interval(1, 3, is_valid=None) == interval(1, 3)) == (True, None)
|
||||
inter = interval(-5, 5)
|
||||
assert (interval(inter) == interval(-5, 5)) == (True, True)
|
||||
assert inter.width == 10
|
||||
assert 0 in inter
|
||||
assert -5 in inter
|
||||
assert 5 in inter
|
||||
assert interval(0, 3) in inter
|
||||
assert interval(-6, 2) not in inter
|
||||
assert -5.05 not in inter
|
||||
assert 5.3 not in inter
|
||||
interb = interval(-float('inf'), float('inf'))
|
||||
assert 0 in inter
|
||||
assert inter in interb
|
||||
assert interval(0, float('inf')) in interb
|
||||
assert interval(-float('inf'), 5) in interb
|
||||
assert interval(-1e50, 1e50) in interb
|
||||
assert (
|
||||
-interval(-1, -2, is_valid=False) == interval(1, 2)) == (True, False)
|
||||
raises(ValueError, lambda: interval(1, 2, 3))
|
||||
|
||||
|
||||
def test_interval_add():
|
||||
assert (interval(1, 2) + interval(2, 3) == interval(3, 5)) == (True, True)
|
||||
assert (1 + interval(1, 2) == interval(2, 3)) == (True, True)
|
||||
assert (interval(1, 2) + 1 == interval(2, 3)) == (True, True)
|
||||
compare = (1 + interval(0, float('inf')) == interval(1, float('inf')))
|
||||
assert compare == (True, True)
|
||||
a = 1 + interval(2, 5, is_valid=False)
|
||||
assert a.is_valid is False
|
||||
a = 1 + interval(2, 5, is_valid=None)
|
||||
assert a.is_valid is None
|
||||
a = interval(2, 5, is_valid=False) + interval(3, 5, is_valid=None)
|
||||
assert a.is_valid is False
|
||||
a = interval(3, 5) + interval(-1, 1, is_valid=None)
|
||||
assert a.is_valid is None
|
||||
a = interval(2, 5, is_valid=False) + 1
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_interval_sub():
|
||||
assert (interval(1, 2) - interval(1, 5) == interval(-4, 1)) == (True, True)
|
||||
assert (interval(1, 2) - 1 == interval(0, 1)) == (True, True)
|
||||
assert (1 - interval(1, 2) == interval(-1, 0)) == (True, True)
|
||||
a = 1 - interval(1, 2, is_valid=False)
|
||||
assert a.is_valid is False
|
||||
a = interval(1, 4, is_valid=None) - 1
|
||||
assert a.is_valid is None
|
||||
a = interval(1, 3, is_valid=False) - interval(1, 3)
|
||||
assert a.is_valid is False
|
||||
a = interval(1, 3, is_valid=None) - interval(1, 3)
|
||||
assert a.is_valid is None
|
||||
|
||||
|
||||
def test_interval_inequality():
|
||||
assert (interval(1, 2) < interval(3, 4)) == (True, True)
|
||||
assert (interval(1, 2) < interval(2, 4)) == (None, True)
|
||||
assert (interval(1, 2) < interval(-2, 0)) == (False, True)
|
||||
assert (interval(1, 2) <= interval(2, 4)) == (True, True)
|
||||
assert (interval(1, 2) <= interval(1.5, 6)) == (None, True)
|
||||
assert (interval(2, 3) <= interval(1, 2)) == (None, True)
|
||||
assert (interval(2, 3) <= interval(1, 1.5)) == (False, True)
|
||||
assert (
|
||||
interval(1, 2, is_valid=False) <= interval(-2, 0)) == (False, False)
|
||||
assert (interval(1, 2, is_valid=None) <= interval(-2, 0)) == (False, None)
|
||||
assert (interval(1, 2) <= 1.5) == (None, True)
|
||||
assert (interval(1, 2) <= 3) == (True, True)
|
||||
assert (interval(1, 2) <= 0) == (False, True)
|
||||
assert (interval(5, 8) > interval(2, 3)) == (True, True)
|
||||
assert (interval(2, 5) > interval(1, 3)) == (None, True)
|
||||
assert (interval(2, 3) > interval(3.1, 5)) == (False, True)
|
||||
|
||||
assert (interval(-1, 1) == 0) == (None, True)
|
||||
assert (interval(-1, 1) == 2) == (False, True)
|
||||
assert (interval(-1, 1) != 0) == (None, True)
|
||||
assert (interval(-1, 1) != 2) == (True, True)
|
||||
|
||||
assert (interval(3, 5) > 2) == (True, True)
|
||||
assert (interval(3, 5) < 2) == (False, True)
|
||||
assert (interval(1, 5) < 2) == (None, True)
|
||||
assert (interval(1, 5) > 2) == (None, True)
|
||||
assert (interval(0, 1) > 2) == (False, True)
|
||||
assert (interval(1, 2) >= interval(0, 1)) == (True, True)
|
||||
assert (interval(1, 2) >= interval(0, 1.5)) == (None, True)
|
||||
assert (interval(1, 2) >= interval(3, 4)) == (False, True)
|
||||
assert (interval(1, 2) >= 0) == (True, True)
|
||||
assert (interval(1, 2) >= 1.2) == (None, True)
|
||||
assert (interval(1, 2) >= 3) == (False, True)
|
||||
assert (2 > interval(0, 1)) == (True, True)
|
||||
a = interval(-1, 1, is_valid=False) < interval(2, 5, is_valid=None)
|
||||
assert a == (True, False)
|
||||
a = interval(-1, 1, is_valid=None) < interval(2, 5, is_valid=False)
|
||||
assert a == (True, False)
|
||||
a = interval(-1, 1, is_valid=None) < interval(2, 5, is_valid=None)
|
||||
assert a == (True, None)
|
||||
a = interval(-1, 1, is_valid=False) > interval(-5, -2, is_valid=None)
|
||||
assert a == (True, False)
|
||||
a = interval(-1, 1, is_valid=None) > interval(-5, -2, is_valid=False)
|
||||
assert a == (True, False)
|
||||
a = interval(-1, 1, is_valid=None) > interval(-5, -2, is_valid=None)
|
||||
assert a == (True, None)
|
||||
|
||||
|
||||
def test_interval_mul():
|
||||
assert (
|
||||
interval(1, 5) * interval(2, 10) == interval(2, 50)) == (True, True)
|
||||
a = interval(-1, 1) * interval(2, 10) == interval(-10, 10)
|
||||
assert a == (True, True)
|
||||
|
||||
a = interval(-1, 1) * interval(-5, 3) == interval(-5, 5)
|
||||
assert a == (True, True)
|
||||
|
||||
assert (interval(1, 3) * 2 == interval(2, 6)) == (True, True)
|
||||
assert (3 * interval(-1, 2) == interval(-3, 6)) == (True, True)
|
||||
|
||||
a = 3 * interval(1, 2, is_valid=False)
|
||||
assert a.is_valid is False
|
||||
|
||||
a = 3 * interval(1, 2, is_valid=None)
|
||||
assert a.is_valid is None
|
||||
|
||||
a = interval(1, 5, is_valid=False) * interval(1, 2, is_valid=None)
|
||||
assert a.is_valid is False
|
||||
|
||||
|
||||
def test_interval_div():
|
||||
div = interval(1, 2, is_valid=False) / 3
|
||||
assert div == interval(-float('inf'), float('inf'), is_valid=False)
|
||||
|
||||
div = interval(1, 2, is_valid=None) / 3
|
||||
assert div == interval(-float('inf'), float('inf'), is_valid=None)
|
||||
|
||||
div = 3 / interval(1, 2, is_valid=None)
|
||||
assert div == interval(-float('inf'), float('inf'), is_valid=None)
|
||||
a = interval(1, 2) / 0
|
||||
assert a.is_valid is False
|
||||
a = interval(0.5, 1) / interval(-1, 0)
|
||||
assert a.is_valid is None
|
||||
a = interval(0, 1) / interval(0, 1)
|
||||
assert a.is_valid is None
|
||||
|
||||
a = interval(-1, 1) / interval(-1, 1)
|
||||
assert a.is_valid is None
|
||||
|
||||
a = interval(-1, 2) / interval(0.5, 1) == interval(-2.0, 4.0)
|
||||
assert a == (True, True)
|
||||
a = interval(0, 1) / interval(0.5, 1) == interval(0.0, 2.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 0) / interval(0.5, 1) == interval(-2.0, 0.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-0.5, -0.25) / interval(0.5, 1) == interval(-1.0, -0.25)
|
||||
assert a == (True, True)
|
||||
a = interval(0.5, 1) / interval(0.5, 1) == interval(0.5, 2.0)
|
||||
assert a == (True, True)
|
||||
a = interval(0.5, 4) / interval(0.5, 1) == interval(0.5, 8.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, -0.5) / interval(0.5, 1) == interval(-2.0, -0.5)
|
||||
assert a == (True, True)
|
||||
a = interval(-4, -0.5) / interval(0.5, 1) == interval(-8.0, -0.5)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 2) / interval(-2, -0.5) == interval(-4.0, 2.0)
|
||||
assert a == (True, True)
|
||||
a = interval(0, 1) / interval(-2, -0.5) == interval(-2.0, 0.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, 0) / interval(-2, -0.5) == interval(0.0, 2.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-0.5, -0.25) / interval(-2, -0.5) == interval(0.125, 1.0)
|
||||
assert a == (True, True)
|
||||
a = interval(0.5, 1) / interval(-2, -0.5) == interval(-2.0, -0.25)
|
||||
assert a == (True, True)
|
||||
a = interval(0.5, 4) / interval(-2, -0.5) == interval(-8.0, -0.25)
|
||||
assert a == (True, True)
|
||||
a = interval(-1, -0.5) / interval(-2, -0.5) == interval(0.25, 2.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-4, -0.5) / interval(-2, -0.5) == interval(0.25, 8.0)
|
||||
assert a == (True, True)
|
||||
a = interval(-5, 5, is_valid=False) / 2
|
||||
assert a.is_valid is False
|
||||
|
||||
def test_hashable():
|
||||
'''
|
||||
test that interval objects are hashable.
|
||||
this is required in order to be able to put them into the cache, which
|
||||
appears to be necessary for plotting in py3k. For details, see:
|
||||
|
||||
https://github.com/sympy/sympy/pull/2101
|
||||
https://github.com/sympy/sympy/issues/6533
|
||||
'''
|
||||
hash(interval(1, 1))
|
||||
hash(interval(1, 1, is_valid=True))
|
||||
hash(interval(-4, -0.5))
|
||||
hash(interval(-2, -0.5))
|
||||
hash(interval(0.25, 8.0))
|
||||
Reference in New Issue
Block a user