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