Coverage for /usr/lib/python3/dist-packages/sympy/plotting/intervalmath/interval_arithmetic.py: 12%
265 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"""
2Interval Arithmetic for plotting.
3This module does not implement interval arithmetic accurately and
4hence cannot be used for purposes other than plotting. If you want
5to use interval arithmetic, use mpmath's interval arithmetic.
7The module implements interval arithmetic using numpy and
8python floating points. The rounding up and down is not handled
9and hence this is not an accurate implementation of interval
10arithmetic.
12The module uses numpy for speed which cannot be achieved with mpmath.
13"""
15# Q: Why use numpy? Why not simply use mpmath's interval arithmetic?
16# A: mpmath's interval arithmetic simulates a floating point unit
17# and hence is slow, while numpy evaluations are orders of magnitude
18# faster.
20# Q: Why create a separate class for intervals? Why not use SymPy's
21# Interval Sets?
22# A: The functionalities that will be required for plotting is quite
23# different from what Interval Sets implement.
25# Q: Why is rounding up and down according to IEEE754 not handled?
26# A: It is not possible to do it in both numpy and python. An external
27# library has to used, which defeats the whole purpose i.e., speed. Also
28# rounding is handled for very few functions in those libraries.
30# Q Will my plots be affected?
31# A It will not affect most of the plots. The interval arithmetic
32# module based suffers the same problems as that of floating point
33# arithmetic.
35from sympy.core.logic import fuzzy_and
36from sympy.simplify.simplify import nsimplify
38from .interval_membership import intervalMembership
41class interval:
42 """ Represents an interval containing floating points as start and
43 end of the interval
44 The is_valid variable tracks whether the interval obtained as the
45 result of the function is in the domain and is continuous.
46 - True: Represents the interval result of a function is continuous and
47 in the domain of the function.
48 - False: The interval argument of the function was not in the domain of
49 the function, hence the is_valid of the result interval is False
50 - None: The function was not continuous over the interval or
51 the function's argument interval is partly in the domain of the
52 function
54 A comparison between an interval and a real number, or a
55 comparison between two intervals may return ``intervalMembership``
56 of two 3-valued logic values.
57 """
59 def __init__(self, *args, is_valid=True, **kwargs):
60 self.is_valid = is_valid
61 if len(args) == 1:
62 if isinstance(args[0], interval):
63 self.start, self.end = args[0].start, args[0].end
64 else:
65 self.start = float(args[0])
66 self.end = float(args[0])
67 elif len(args) == 2:
68 if args[0] < args[1]:
69 self.start = float(args[0])
70 self.end = float(args[1])
71 else:
72 self.start = float(args[1])
73 self.end = float(args[0])
75 else:
76 raise ValueError("interval takes a maximum of two float values "
77 "as arguments")
79 @property
80 def mid(self):
81 return (self.start + self.end) / 2.0
83 @property
84 def width(self):
85 return self.end - self.start
87 def __repr__(self):
88 return "interval(%f, %f)" % (self.start, self.end)
90 def __str__(self):
91 return "[%f, %f]" % (self.start, self.end)
93 def __lt__(self, other):
94 if isinstance(other, (int, float)):
95 if self.end < other:
96 return intervalMembership(True, self.is_valid)
97 elif self.start > other:
98 return intervalMembership(False, self.is_valid)
99 else:
100 return intervalMembership(None, self.is_valid)
102 elif isinstance(other, interval):
103 valid = fuzzy_and([self.is_valid, other.is_valid])
104 if self.end < other. start:
105 return intervalMembership(True, valid)
106 if self.start > other.end:
107 return intervalMembership(False, valid)
108 return intervalMembership(None, valid)
109 else:
110 return NotImplemented
112 def __gt__(self, other):
113 if isinstance(other, (int, float)):
114 if self.start > other:
115 return intervalMembership(True, self.is_valid)
116 elif self.end < other:
117 return intervalMembership(False, self.is_valid)
118 else:
119 return intervalMembership(None, self.is_valid)
120 elif isinstance(other, interval):
121 return other.__lt__(self)
122 else:
123 return NotImplemented
125 def __eq__(self, other):
126 if isinstance(other, (int, float)):
127 if self.start == other and self.end == other:
128 return intervalMembership(True, self.is_valid)
129 if other in self:
130 return intervalMembership(None, self.is_valid)
131 else:
132 return intervalMembership(False, self.is_valid)
134 if isinstance(other, interval):
135 valid = fuzzy_and([self.is_valid, other.is_valid])
136 if self.start == other.start and self.end == other.end:
137 return intervalMembership(True, valid)
138 elif self.__lt__(other)[0] is not None:
139 return intervalMembership(False, valid)
140 else:
141 return intervalMembership(None, valid)
142 else:
143 return NotImplemented
145 def __ne__(self, other):
146 if isinstance(other, (int, float)):
147 if self.start == other and self.end == other:
148 return intervalMembership(False, self.is_valid)
149 if other in self:
150 return intervalMembership(None, self.is_valid)
151 else:
152 return intervalMembership(True, self.is_valid)
154 if isinstance(other, interval):
155 valid = fuzzy_and([self.is_valid, other.is_valid])
156 if self.start == other.start and self.end == other.end:
157 return intervalMembership(False, valid)
158 if not self.__lt__(other)[0] is None:
159 return intervalMembership(True, valid)
160 return intervalMembership(None, valid)
161 else:
162 return NotImplemented
164 def __le__(self, other):
165 if isinstance(other, (int, float)):
166 if self.end <= other:
167 return intervalMembership(True, self.is_valid)
168 if self.start > other:
169 return intervalMembership(False, self.is_valid)
170 else:
171 return intervalMembership(None, self.is_valid)
173 if isinstance(other, interval):
174 valid = fuzzy_and([self.is_valid, other.is_valid])
175 if self.end <= other.start:
176 return intervalMembership(True, valid)
177 if self.start > other.end:
178 return intervalMembership(False, valid)
179 return intervalMembership(None, valid)
180 else:
181 return NotImplemented
183 def __ge__(self, other):
184 if isinstance(other, (int, float)):
185 if self.start >= other:
186 return intervalMembership(True, self.is_valid)
187 elif self.end < other:
188 return intervalMembership(False, self.is_valid)
189 else:
190 return intervalMembership(None, self.is_valid)
191 elif isinstance(other, interval):
192 return other.__le__(self)
194 def __add__(self, other):
195 if isinstance(other, (int, float)):
196 if self.is_valid:
197 return interval(self.start + other, self.end + other)
198 else:
199 start = self.start + other
200 end = self.end + other
201 return interval(start, end, is_valid=self.is_valid)
203 elif isinstance(other, interval):
204 start = self.start + other.start
205 end = self.end + other.end
206 valid = fuzzy_and([self.is_valid, other.is_valid])
207 return interval(start, end, is_valid=valid)
208 else:
209 return NotImplemented
211 __radd__ = __add__
213 def __sub__(self, other):
214 if isinstance(other, (int, float)):
215 start = self.start - other
216 end = self.end - other
217 return interval(start, end, is_valid=self.is_valid)
219 elif isinstance(other, interval):
220 start = self.start - other.end
221 end = self.end - other.start
222 valid = fuzzy_and([self.is_valid, other.is_valid])
223 return interval(start, end, is_valid=valid)
224 else:
225 return NotImplemented
227 def __rsub__(self, other):
228 if isinstance(other, (int, float)):
229 start = other - self.end
230 end = other - self.start
231 return interval(start, end, is_valid=self.is_valid)
232 elif isinstance(other, interval):
233 return other.__sub__(self)
234 else:
235 return NotImplemented
237 def __neg__(self):
238 if self.is_valid:
239 return interval(-self.end, -self.start)
240 else:
241 return interval(-self.end, -self.start, is_valid=self.is_valid)
243 def __mul__(self, other):
244 if isinstance(other, interval):
245 if self.is_valid is False or other.is_valid is False:
246 return interval(-float('inf'), float('inf'), is_valid=False)
247 elif self.is_valid is None or other.is_valid is None:
248 return interval(-float('inf'), float('inf'), is_valid=None)
249 else:
250 inters = []
251 inters.append(self.start * other.start)
252 inters.append(self.end * other.start)
253 inters.append(self.start * other.end)
254 inters.append(self.end * other.end)
255 start = min(inters)
256 end = max(inters)
257 return interval(start, end)
258 elif isinstance(other, (int, float)):
259 return interval(self.start*other, self.end*other, is_valid=self.is_valid)
260 else:
261 return NotImplemented
263 __rmul__ = __mul__
265 def __contains__(self, other):
266 if isinstance(other, (int, float)):
267 return self.start <= other and self.end >= other
268 else:
269 return self.start <= other.start and other.end <= self.end
271 def __rtruediv__(self, other):
272 if isinstance(other, (int, float)):
273 other = interval(other)
274 return other.__truediv__(self)
275 elif isinstance(other, interval):
276 return other.__truediv__(self)
277 else:
278 return NotImplemented
280 def __truediv__(self, other):
281 # Both None and False are handled
282 if not self.is_valid:
283 # Don't divide as the value is not valid
284 return interval(-float('inf'), float('inf'), is_valid=self.is_valid)
285 if isinstance(other, (int, float)):
286 if other == 0:
287 # Divide by zero encountered. valid nowhere
288 return interval(-float('inf'), float('inf'), is_valid=False)
289 else:
290 return interval(self.start / other, self.end / other)
292 elif isinstance(other, interval):
293 if other.is_valid is False or self.is_valid is False:
294 return interval(-float('inf'), float('inf'), is_valid=False)
295 elif other.is_valid is None or self.is_valid is None:
296 return interval(-float('inf'), float('inf'), is_valid=None)
297 else:
298 # denominator contains both signs, i.e. being divided by zero
299 # return the whole real line with is_valid = None
300 if 0 in other:
301 return interval(-float('inf'), float('inf'), is_valid=None)
303 # denominator negative
304 this = self
305 if other.end < 0:
306 this = -this
307 other = -other
309 # denominator positive
310 inters = []
311 inters.append(this.start / other.start)
312 inters.append(this.end / other.start)
313 inters.append(this.start / other.end)
314 inters.append(this.end / other.end)
315 start = max(inters)
316 end = min(inters)
317 return interval(start, end)
318 else:
319 return NotImplemented
321 def __pow__(self, other):
322 # Implements only power to an integer.
323 from .lib_interval import exp, log
324 if not self.is_valid:
325 return self
326 if isinstance(other, interval):
327 return exp(other * log(self))
328 elif isinstance(other, (float, int)):
329 if other < 0:
330 return 1 / self.__pow__(abs(other))
331 else:
332 if int(other) == other:
333 return _pow_int(self, other)
334 else:
335 return _pow_float(self, other)
336 else:
337 return NotImplemented
339 def __rpow__(self, other):
340 if isinstance(other, (float, int)):
341 if not self.is_valid:
342 #Don't do anything
343 return self
344 elif other < 0:
345 if self.width > 0:
346 return interval(-float('inf'), float('inf'), is_valid=False)
347 else:
348 power_rational = nsimplify(self.start)
349 num, denom = power_rational.as_numer_denom()
350 if denom % 2 == 0:
351 return interval(-float('inf'), float('inf'),
352 is_valid=False)
353 else:
354 start = -abs(other)**self.start
355 end = start
356 return interval(start, end)
357 else:
358 return interval(other**self.start, other**self.end)
359 elif isinstance(other, interval):
360 return other.__pow__(self)
361 else:
362 return NotImplemented
364 def __hash__(self):
365 return hash((self.is_valid, self.start, self.end))
368def _pow_float(inter, power):
369 """Evaluates an interval raised to a floating point."""
370 power_rational = nsimplify(power)
371 num, denom = power_rational.as_numer_denom()
372 if num % 2 == 0:
373 start = abs(inter.start)**power
374 end = abs(inter.end)**power
375 if start < 0:
376 ret = interval(0, max(start, end))
377 else:
378 ret = interval(start, end)
379 return ret
380 elif denom % 2 == 0:
381 if inter.end < 0:
382 return interval(-float('inf'), float('inf'), is_valid=False)
383 elif inter.start < 0:
384 return interval(0, inter.end**power, is_valid=None)
385 else:
386 return interval(inter.start**power, inter.end**power)
387 else:
388 if inter.start < 0:
389 start = -abs(inter.start)**power
390 else:
391 start = inter.start**power
393 if inter.end < 0:
394 end = -abs(inter.end)**power
395 else:
396 end = inter.end**power
398 return interval(start, end, is_valid=inter.is_valid)
401def _pow_int(inter, power):
402 """Evaluates an interval raised to an integer power"""
403 power = int(power)
404 if power & 1:
405 return interval(inter.start**power, inter.end**power)
406 else:
407 if inter.start < 0 and inter.end > 0:
408 start = 0
409 end = max(inter.start**power, inter.end**power)
410 return interval(start, end)
411 else:
412 return interval(inter.start**power, inter.end**power)