""" 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)