lib_interval.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. """ The module contains implemented functions for interval arithmetic."""
  2. from functools import reduce
  3. from sympy.plotting.intervalmath import interval
  4. from sympy.external import import_module
  5. def Abs(x):
  6. if isinstance(x, (int, float)):
  7. return interval(abs(x))
  8. elif isinstance(x, interval):
  9. if x.start < 0 and x.end > 0:
  10. return interval(0, max(abs(x.start), abs(x.end)), is_valid=x.is_valid)
  11. else:
  12. return interval(abs(x.start), abs(x.end))
  13. else:
  14. raise NotImplementedError
  15. #Monotonic
  16. def exp(x):
  17. """evaluates the exponential of an interval"""
  18. np = import_module('numpy')
  19. if isinstance(x, (int, float)):
  20. return interval(np.exp(x), np.exp(x))
  21. elif isinstance(x, interval):
  22. return interval(np.exp(x.start), np.exp(x.end), is_valid=x.is_valid)
  23. else:
  24. raise NotImplementedError
  25. #Monotonic
  26. def log(x):
  27. """evaluates the natural logarithm of an interval"""
  28. np = import_module('numpy')
  29. if isinstance(x, (int, float)):
  30. if x <= 0:
  31. return interval(-np.inf, np.inf, is_valid=False)
  32. else:
  33. return interval(np.log(x))
  34. elif isinstance(x, interval):
  35. if not x.is_valid:
  36. return interval(-np.inf, np.inf, is_valid=x.is_valid)
  37. elif x.end <= 0:
  38. return interval(-np.inf, np.inf, is_valid=False)
  39. elif x.start <= 0:
  40. return interval(-np.inf, np.inf, is_valid=None)
  41. return interval(np.log(x.start), np.log(x.end))
  42. else:
  43. raise NotImplementedError
  44. #Monotonic
  45. def log10(x):
  46. """evaluates the logarithm to the base 10 of an interval"""
  47. np = import_module('numpy')
  48. if isinstance(x, (int, float)):
  49. if x <= 0:
  50. return interval(-np.inf, np.inf, is_valid=False)
  51. else:
  52. return interval(np.log10(x))
  53. elif isinstance(x, interval):
  54. if not x.is_valid:
  55. return interval(-np.inf, np.inf, is_valid=x.is_valid)
  56. elif x.end <= 0:
  57. return interval(-np.inf, np.inf, is_valid=False)
  58. elif x.start <= 0:
  59. return interval(-np.inf, np.inf, is_valid=None)
  60. return interval(np.log10(x.start), np.log10(x.end))
  61. else:
  62. raise NotImplementedError
  63. #Monotonic
  64. def atan(x):
  65. """evaluates the tan inverse of an interval"""
  66. np = import_module('numpy')
  67. if isinstance(x, (int, float)):
  68. return interval(np.arctan(x))
  69. elif isinstance(x, interval):
  70. start = np.arctan(x.start)
  71. end = np.arctan(x.end)
  72. return interval(start, end, is_valid=x.is_valid)
  73. else:
  74. raise NotImplementedError
  75. #periodic
  76. def sin(x):
  77. """evaluates the sine of an interval"""
  78. np = import_module('numpy')
  79. if isinstance(x, (int, float)):
  80. return interval(np.sin(x))
  81. elif isinstance(x, interval):
  82. if not x.is_valid:
  83. return interval(-1, 1, is_valid=x.is_valid)
  84. na, __ = divmod(x.start, np.pi / 2.0)
  85. nb, __ = divmod(x.end, np.pi / 2.0)
  86. start = min(np.sin(x.start), np.sin(x.end))
  87. end = max(np.sin(x.start), np.sin(x.end))
  88. if nb - na > 4:
  89. return interval(-1, 1, is_valid=x.is_valid)
  90. elif na == nb:
  91. return interval(start, end, is_valid=x.is_valid)
  92. else:
  93. if (na - 1) // 4 != (nb - 1) // 4:
  94. #sin has max
  95. end = 1
  96. if (na - 3) // 4 != (nb - 3) // 4:
  97. #sin has min
  98. start = -1
  99. return interval(start, end)
  100. else:
  101. raise NotImplementedError
  102. #periodic
  103. def cos(x):
  104. """Evaluates the cos of an interval"""
  105. np = import_module('numpy')
  106. if isinstance(x, (int, float)):
  107. return interval(np.sin(x))
  108. elif isinstance(x, interval):
  109. if not (np.isfinite(x.start) and np.isfinite(x.end)):
  110. return interval(-1, 1, is_valid=x.is_valid)
  111. na, __ = divmod(x.start, np.pi / 2.0)
  112. nb, __ = divmod(x.end, np.pi / 2.0)
  113. start = min(np.cos(x.start), np.cos(x.end))
  114. end = max(np.cos(x.start), np.cos(x.end))
  115. if nb - na > 4:
  116. #differ more than 2*pi
  117. return interval(-1, 1, is_valid=x.is_valid)
  118. elif na == nb:
  119. #in the same quadarant
  120. return interval(start, end, is_valid=x.is_valid)
  121. else:
  122. if (na) // 4 != (nb) // 4:
  123. #cos has max
  124. end = 1
  125. if (na - 2) // 4 != (nb - 2) // 4:
  126. #cos has min
  127. start = -1
  128. return interval(start, end, is_valid=x.is_valid)
  129. else:
  130. raise NotImplementedError
  131. def tan(x):
  132. """Evaluates the tan of an interval"""
  133. return sin(x) / cos(x)
  134. #Monotonic
  135. def sqrt(x):
  136. """Evaluates the square root of an interval"""
  137. np = import_module('numpy')
  138. if isinstance(x, (int, float)):
  139. if x > 0:
  140. return interval(np.sqrt(x))
  141. else:
  142. return interval(-np.inf, np.inf, is_valid=False)
  143. elif isinstance(x, interval):
  144. #Outside the domain
  145. if x.end < 0:
  146. return interval(-np.inf, np.inf, is_valid=False)
  147. #Partially outside the domain
  148. elif x.start < 0:
  149. return interval(-np.inf, np.inf, is_valid=None)
  150. else:
  151. return interval(np.sqrt(x.start), np.sqrt(x.end),
  152. is_valid=x.is_valid)
  153. else:
  154. raise NotImplementedError
  155. def imin(*args):
  156. """Evaluates the minimum of a list of intervals"""
  157. np = import_module('numpy')
  158. if not all(isinstance(arg, (int, float, interval)) for arg in args):
  159. return NotImplementedError
  160. else:
  161. new_args = [a for a in args if isinstance(a, (int, float))
  162. or a.is_valid]
  163. if len(new_args) == 0:
  164. if all(a.is_valid is False for a in args):
  165. return interval(-np.inf, np.inf, is_valid=False)
  166. else:
  167. return interval(-np.inf, np.inf, is_valid=None)
  168. start_array = [a if isinstance(a, (int, float)) else a.start
  169. for a in new_args]
  170. end_array = [a if isinstance(a, (int, float)) else a.end
  171. for a in new_args]
  172. return interval(min(start_array), min(end_array))
  173. def imax(*args):
  174. """Evaluates the maximum of a list of intervals"""
  175. np = import_module('numpy')
  176. if not all(isinstance(arg, (int, float, interval)) for arg in args):
  177. return NotImplementedError
  178. else:
  179. new_args = [a for a in args if isinstance(a, (int, float))
  180. or a.is_valid]
  181. if len(new_args) == 0:
  182. if all(a.is_valid is False for a in args):
  183. return interval(-np.inf, np.inf, is_valid=False)
  184. else:
  185. return interval(-np.inf, np.inf, is_valid=None)
  186. start_array = [a if isinstance(a, (int, float)) else a.start
  187. for a in new_args]
  188. end_array = [a if isinstance(a, (int, float)) else a.end
  189. for a in new_args]
  190. return interval(max(start_array), max(end_array))
  191. #Monotonic
  192. def sinh(x):
  193. """Evaluates the hyperbolic sine of an interval"""
  194. np = import_module('numpy')
  195. if isinstance(x, (int, float)):
  196. return interval(np.sinh(x), np.sinh(x))
  197. elif isinstance(x, interval):
  198. return interval(np.sinh(x.start), np.sinh(x.end), is_valid=x.is_valid)
  199. else:
  200. raise NotImplementedError
  201. def cosh(x):
  202. """Evaluates the hyperbolic cos of an interval"""
  203. np = import_module('numpy')
  204. if isinstance(x, (int, float)):
  205. return interval(np.cosh(x), np.cosh(x))
  206. elif isinstance(x, interval):
  207. #both signs
  208. if x.start < 0 and x.end > 0:
  209. end = max(np.cosh(x.start), np.cosh(x.end))
  210. return interval(1, end, is_valid=x.is_valid)
  211. else:
  212. #Monotonic
  213. start = np.cosh(x.start)
  214. end = np.cosh(x.end)
  215. return interval(start, end, is_valid=x.is_valid)
  216. else:
  217. raise NotImplementedError
  218. #Monotonic
  219. def tanh(x):
  220. """Evaluates the hyperbolic tan of an interval"""
  221. np = import_module('numpy')
  222. if isinstance(x, (int, float)):
  223. return interval(np.tanh(x), np.tanh(x))
  224. elif isinstance(x, interval):
  225. return interval(np.tanh(x.start), np.tanh(x.end), is_valid=x.is_valid)
  226. else:
  227. raise NotImplementedError
  228. def asin(x):
  229. """Evaluates the inverse sine of an interval"""
  230. np = import_module('numpy')
  231. if isinstance(x, (int, float)):
  232. #Outside the domain
  233. if abs(x) > 1:
  234. return interval(-np.inf, np.inf, is_valid=False)
  235. else:
  236. return interval(np.arcsin(x), np.arcsin(x))
  237. elif isinstance(x, interval):
  238. #Outside the domain
  239. if x.is_valid is False or x.start > 1 or x.end < -1:
  240. return interval(-np.inf, np.inf, is_valid=False)
  241. #Partially outside the domain
  242. elif x.start < -1 or x.end > 1:
  243. return interval(-np.inf, np.inf, is_valid=None)
  244. else:
  245. start = np.arcsin(x.start)
  246. end = np.arcsin(x.end)
  247. return interval(start, end, is_valid=x.is_valid)
  248. def acos(x):
  249. """Evaluates the inverse cos of an interval"""
  250. np = import_module('numpy')
  251. if isinstance(x, (int, float)):
  252. if abs(x) > 1:
  253. #Outside the domain
  254. return interval(-np.inf, np.inf, is_valid=False)
  255. else:
  256. return interval(np.arccos(x), np.arccos(x))
  257. elif isinstance(x, interval):
  258. #Outside the domain
  259. if x.is_valid is False or x.start > 1 or x.end < -1:
  260. return interval(-np.inf, np.inf, is_valid=False)
  261. #Partially outside the domain
  262. elif x.start < -1 or x.end > 1:
  263. return interval(-np.inf, np.inf, is_valid=None)
  264. else:
  265. start = np.arccos(x.start)
  266. end = np.arccos(x.end)
  267. return interval(start, end, is_valid=x.is_valid)
  268. def ceil(x):
  269. """Evaluates the ceiling of an interval"""
  270. np = import_module('numpy')
  271. if isinstance(x, (int, float)):
  272. return interval(np.ceil(x))
  273. elif isinstance(x, interval):
  274. if x.is_valid is False:
  275. return interval(-np.inf, np.inf, is_valid=False)
  276. else:
  277. start = np.ceil(x.start)
  278. end = np.ceil(x.end)
  279. #Continuous over the interval
  280. if start == end:
  281. return interval(start, end, is_valid=x.is_valid)
  282. else:
  283. #Not continuous over the interval
  284. return interval(start, end, is_valid=None)
  285. else:
  286. return NotImplementedError
  287. def floor(x):
  288. """Evaluates the floor of an interval"""
  289. np = import_module('numpy')
  290. if isinstance(x, (int, float)):
  291. return interval(np.floor(x))
  292. elif isinstance(x, interval):
  293. if x.is_valid is False:
  294. return interval(-np.inf, np.inf, is_valid=False)
  295. else:
  296. start = np.floor(x.start)
  297. end = np.floor(x.end)
  298. #continuous over the argument
  299. if start == end:
  300. return interval(start, end, is_valid=x.is_valid)
  301. else:
  302. #not continuous over the interval
  303. return interval(start, end, is_valid=None)
  304. else:
  305. return NotImplementedError
  306. def acosh(x):
  307. """Evaluates the inverse hyperbolic cosine of an interval"""
  308. np = import_module('numpy')
  309. if isinstance(x, (int, float)):
  310. #Outside the domain
  311. if x < 1:
  312. return interval(-np.inf, np.inf, is_valid=False)
  313. else:
  314. return interval(np.arccosh(x))
  315. elif isinstance(x, interval):
  316. #Outside the domain
  317. if x.end < 1:
  318. return interval(-np.inf, np.inf, is_valid=False)
  319. #Partly outside the domain
  320. elif x.start < 1:
  321. return interval(-np.inf, np.inf, is_valid=None)
  322. else:
  323. start = np.arccosh(x.start)
  324. end = np.arccosh(x.end)
  325. return interval(start, end, is_valid=x.is_valid)
  326. else:
  327. return NotImplementedError
  328. #Monotonic
  329. def asinh(x):
  330. """Evaluates the inverse hyperbolic sine of an interval"""
  331. np = import_module('numpy')
  332. if isinstance(x, (int, float)):
  333. return interval(np.arcsinh(x))
  334. elif isinstance(x, interval):
  335. start = np.arcsinh(x.start)
  336. end = np.arcsinh(x.end)
  337. return interval(start, end, is_valid=x.is_valid)
  338. else:
  339. return NotImplementedError
  340. def atanh(x):
  341. """Evaluates the inverse hyperbolic tangent of an interval"""
  342. np = import_module('numpy')
  343. if isinstance(x, (int, float)):
  344. #Outside the domain
  345. if abs(x) >= 1:
  346. return interval(-np.inf, np.inf, is_valid=False)
  347. else:
  348. return interval(np.arctanh(x))
  349. elif isinstance(x, interval):
  350. #outside the domain
  351. if x.is_valid is False or x.start >= 1 or x.end <= -1:
  352. return interval(-np.inf, np.inf, is_valid=False)
  353. #partly outside the domain
  354. elif x.start <= -1 or x.end >= 1:
  355. return interval(-np.inf, np.inf, is_valid=None)
  356. else:
  357. start = np.arctanh(x.start)
  358. end = np.arctanh(x.end)
  359. return interval(start, end, is_valid=x.is_valid)
  360. else:
  361. return NotImplementedError
  362. #Three valued logic for interval plotting.
  363. def And(*args):
  364. """Defines the three valued ``And`` behaviour for a 2-tuple of
  365. three valued logic values"""
  366. def reduce_and(cmp_intervala, cmp_intervalb):
  367. if cmp_intervala[0] is False or cmp_intervalb[0] is False:
  368. first = False
  369. elif cmp_intervala[0] is None or cmp_intervalb[0] is None:
  370. first = None
  371. else:
  372. first = True
  373. if cmp_intervala[1] is False or cmp_intervalb[1] is False:
  374. second = False
  375. elif cmp_intervala[1] is None or cmp_intervalb[1] is None:
  376. second = None
  377. else:
  378. second = True
  379. return (first, second)
  380. return reduce(reduce_and, args)
  381. def Or(*args):
  382. """Defines the three valued ``Or`` behaviour for a 2-tuple of
  383. three valued logic values"""
  384. def reduce_or(cmp_intervala, cmp_intervalb):
  385. if cmp_intervala[0] is True or cmp_intervalb[0] is True:
  386. first = True
  387. elif cmp_intervala[0] is None or cmp_intervalb[0] is None:
  388. first = None
  389. else:
  390. first = False
  391. if cmp_intervala[1] is True or cmp_intervalb[1] is True:
  392. second = True
  393. elif cmp_intervala[1] is None or cmp_intervalb[1] is None:
  394. second = None
  395. else:
  396. second = False
  397. return (first, second)
  398. return reduce(reduce_or, args)