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

1""" The module contains implemented functions for interval arithmetic.""" 

2from functools import reduce 

3 

4from sympy.plotting.intervalmath import interval 

5from sympy.external import import_module 

6 

7 

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 

18 

19#Monotonic 

20 

21 

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 

31 

32 

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) 

49 

50 return interval(np.log(x.start), np.log(x.end)) 

51 else: 

52 raise NotImplementedError 

53 

54 

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 

74 

75 

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 

88 

89 

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 

117 

118 

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 

148 

149 

150def tan(x): 

151 """Evaluates the tan of an interval""" 

152 return sin(x) / cos(x) 

153 

154 

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 

176 

177 

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] 

193 

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

197 

198 

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] 

214 

215 end_array = [a if isinstance(a, (int, float)) else a.end 

216 for a in new_args] 

217 

218 return interval(max(start_array), max(end_array)) 

219 

220 

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 

231 

232 

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 

250 

251 

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 

262 

263 

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) 

284 

285 

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) 

306 

307 

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 

327 

328 

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 

348 

349 

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 

372 

373 

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 

386 

387 

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 

410 

411 

412#Three valued logic for interval plotting. 

413 

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) 

432 

433 

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 

444 

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)