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

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. 

6 

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. 

11 

12The module uses numpy for speed which cannot be achieved with mpmath. 

13""" 

14 

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. 

19 

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. 

24 

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. 

29 

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. 

34 

35from sympy.core.logic import fuzzy_and 

36from sympy.simplify.simplify import nsimplify 

37 

38from .interval_membership import intervalMembership 

39 

40 

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 

53 

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

58 

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

74 

75 else: 

76 raise ValueError("interval takes a maximum of two float values " 

77 "as arguments") 

78 

79 @property 

80 def mid(self): 

81 return (self.start + self.end) / 2.0 

82 

83 @property 

84 def width(self): 

85 return self.end - self.start 

86 

87 def __repr__(self): 

88 return "interval(%f, %f)" % (self.start, self.end) 

89 

90 def __str__(self): 

91 return "[%f, %f]" % (self.start, self.end) 

92 

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) 

101 

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 

111 

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 

124 

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) 

133 

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 

144 

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) 

153 

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 

163 

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) 

172 

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 

182 

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) 

193 

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) 

202 

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 

210 

211 __radd__ = __add__ 

212 

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) 

218 

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 

226 

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 

236 

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) 

242 

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 

262 

263 __rmul__ = __mul__ 

264 

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 

270 

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 

279 

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) 

291 

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) 

302 

303 # denominator negative 

304 this = self 

305 if other.end < 0: 

306 this = -this 

307 other = -other 

308 

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 

320 

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 

338 

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 

363 

364 def __hash__(self): 

365 return hash((self.is_valid, self.start, self.end)) 

366 

367 

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 

392 

393 if inter.end < 0: 

394 end = -abs(inter.end)**power 

395 else: 

396 end = inter.end**power 

397 

398 return interval(start, end, is_valid=inter.is_valid) 

399 

400 

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)