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