Coverage for tests/unit/test_interval.py: 99%

899 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-04-04 03:33 -0600

1import sys 

2import math 

3from typing import Optional, Union, Tuple 

4 

5import pytest 

6 

7from muutils.interval import Interval, ClosedInterval, OpenInterval, _EPSILON 

8 

9 

10@pytest.fixture 

11def sample_intervals(): 

12 return [ 

13 Interval(1, 5), 

14 Interval([1, 5]), 

15 Interval(1, 5, closed_L=True), 

16 ClosedInterval(1, 5), 

17 OpenInterval(1, 5), 

18 ] 

19 

20 

21def test_interval_initialization(): 

22 assert str(Interval(1, 5)) == "(1, 5)" 

23 assert str(Interval([1, 5])) == "[1, 5]" 

24 assert str(Interval(1, 5, closed_L=True)) == "[1, 5)" 

25 assert str(Interval(1, 5, closed_R=True)) == "(1, 5]" 

26 assert str(Interval(1, 5, is_closed=True)) == "[1, 5]" 

27 

28 

29def test_closed_interval_initialization(): 

30 assert str(ClosedInterval(1, 5)) == "[1, 5]" 

31 assert str(ClosedInterval([1, 5])) == "[1, 5]" 

32 

33 

34def test_open_interval_initialization(): 

35 assert str(OpenInterval(1, 5)) == "(1, 5)" 

36 assert str(OpenInterval([1, 5])) == "(1, 5)" 

37 

38 

39@pytest.mark.parametrize( 

40 "interval,point,expected", 

41 [ 

42 (Interval(1, 5), 3, True), 

43 (Interval(1, 5), 1, False), 

44 (Interval(1, 5), 5, False), 

45 (Interval([1, 5]), 1, True), 

46 (Interval([1, 5]), 5, True), 

47 (Interval(1, 5, closed_L=True), 1, True), 

48 (Interval(1, 5, closed_R=True), 5, True), 

49 (ClosedInterval(1, 5), 1, True), 

50 (ClosedInterval(1, 5), 5, True), 

51 (OpenInterval(1, 5), 1, False), 

52 (OpenInterval(1, 5), 5, False), 

53 ], 

54) 

55def test_containment_minimal( 

56 interval: Interval, point: Union[int, float], expected: bool 

57): 

58 assert (point in interval) == expected 

59 

60 

61def test_equality(): 

62 assert Interval(1, 5) == Interval(1, 5) 

63 assert Interval([1, 5]) == Interval(1, 5, is_closed=True) 

64 assert Interval(1, 5) != Interval(1, 5, closed_L=True) 

65 assert ClosedInterval(1, 5) == Interval([1, 5]) 

66 assert OpenInterval(1, 5) == Interval(1, 5) 

67 assert Interval(1, 5) != "not an interval" 

68 

69 

70@pytest.mark.parametrize( 

71 "args,kwargs", 

72 [ 

73 ((5, 1), {}), # Lower bound greater than upper bound 

74 ((1, 2, 3), {}), # Too many arguments 

75 (([1, 2, 3],), {}), # List with wrong number of elements 

76 ( 

77 (1, 5), 

78 {"is_closed": True, "closed_L": True}, 

79 ), # Conflicting closure specifications 

80 ], 

81) 

82def test_invalid_initialization(args: Tuple, kwargs: dict): 

83 with pytest.raises(ValueError): 

84 Interval(*args, **kwargs) 

85 

86 

87def test_closed_interval_invalid_initialization(): 

88 with pytest.raises(ValueError): 

89 ClosedInterval(1, 5, closed_L=True) 

90 

91 

92def test_open_interval_invalid_initialization(): 

93 with pytest.raises(ValueError): 

94 OpenInterval(1, 5, is_closed=True) 

95 

96 

97@pytest.mark.parametrize( 

98 "interval,point,expected", 

99 [ 

100 # Integer tests 

101 (Interval(1, 5), 3, True), 

102 (Interval(1, 5), 1, False), 

103 (Interval(1, 5), 5, False), 

104 (Interval([1, 5]), 1, True), 

105 (Interval([1, 5]), 5, True), 

106 (Interval(1, 5, closed_L=True), 1, True), 

107 (Interval(1, 5, closed_R=True), 5, True), 

108 (ClosedInterval(1, 5), 1, True), 

109 (ClosedInterval(1, 5), 5, True), 

110 (OpenInterval(1, 5), 1, False), 

111 (OpenInterval(1, 5), 5, False), 

112 # Float tests 

113 (Interval(1.5, 5.5), 3.14, True), 

114 (Interval(1.5, 5.5), 1.5, False), 

115 (Interval(1.5, 5.5), 5.5, False), 

116 (Interval([1.5, 5.5]), 1.5, True), 

117 (Interval([1.5, 5.5]), 5.5, True), 

118 # Mixed integer and float tests 

119 (Interval(1, 5.5), 5, True), 

120 (Interval(1.5, 5), 2, True), 

121 # Infinity tests 

122 (Interval(-math.inf, 0), -1000000, True), 

123 (Interval(-math.inf, 0), 0, False), 

124 (Interval(-math.inf, 0, closed_R=True), 0, True), 

125 (Interval(0, math.inf), 1000000, True), 

126 (Interval(0, math.inf), 0, False), 

127 (Interval(0, math.inf, closed_L=True), 0, True), 

128 (Interval(-math.inf, math.inf), 0, True), 

129 (Interval(-math.inf, math.inf), math.inf, False), 

130 (Interval(-math.inf, math.inf), -math.inf, False), 

131 (ClosedInterval(-math.inf, math.inf), math.inf, True), 

132 (ClosedInterval(-math.inf, math.inf), -math.inf, True), 

133 # Very large and very small number tests 

134 (Interval(1e-10, 1e10), 1, True), 

135 (Interval(1e-10, 1e10), 1e-11, False), 

136 (Interval(1e-10, 1e10), 1e11, False), 

137 ], 

138) 

139def test_containment(interval: Interval, point: Union[int, float], expected: bool): 

140 assert (point in interval) == expected 

141 

142 

143def test_nan_handling(): 

144 interval = Interval(0, 1) 

145 with pytest.raises(ValueError): 

146 _ = math.nan in interval 

147 

148 with pytest.raises(ValueError): 

149 Interval(0, math.nan) 

150 

151 with pytest.raises(ValueError): 

152 Interval(math.nan, 1) 

153 

154 

155def test_interval_tuple_behavior(): 

156 i = Interval(1, 5) 

157 assert len(i) == 2 

158 assert i[0] == 1 

159 assert i[1] == 5 

160 with pytest.raises(IndexError): 

161 _ = i[2] 

162 

163 lower, upper = i 

164 assert lower == 1 

165 assert upper == 5 

166 

167 assert tuple(i) == (1, 5) 

168 assert list(i) == [1, 5] 

169 

170 

171def test_min_max_intervals(): 

172 i1 = Interval(1, 5) 

173 i2 = Interval([1, 5]) 

174 i3 = Interval(1, 5, closed_L=True) 

175 i4 = Interval(2, 6) 

176 

177 assert min(i1) == 1 

178 assert max(i1) == 5 

179 assert min(i2) == 1 

180 assert max(i2) == 5 

181 assert min(i3) == 1 

182 assert max(i3) == 5 

183 assert min(i4) == 2 

184 assert max(i4) == 6 

185 

186 

187def test_min_max_with_numbers(): 

188 i1 = Interval(1, 5) 

189 i2 = ClosedInterval(2, 6) 

190 

191 assert min(i1) == 1 

192 assert max(i1) == 5 

193 assert min(i2) == 2 

194 assert max(i2) == 6 

195 

196 

197@pytest.mark.parametrize( 

198 "interval,value,expected", 

199 [ 

200 (Interval(1, 5), 3, 3), 

201 (Interval(1, 5), 0, 1 + _EPSILON), 

202 (Interval(1, 5), 6, 5 - _EPSILON), 

203 (Interval([1, 5]), 0, 1), 

204 (Interval([1, 5]), 6, 5), 

205 (Interval(1, 5, closed_L=True), 0, 1), 

206 (Interval(1, 5, closed_R=True), 6, 5), 

207 (ClosedInterval(1, 5), 0, 1), 

208 (ClosedInterval(1, 5), 6, 5), 

209 (OpenInterval(1, 5), 1, 1 + _EPSILON), 

210 (OpenInterval(1, 5), 5, 5 - _EPSILON), 

211 (Interval(-math.inf, math.inf), 0, 0), 

212 (Interval(-math.inf, 0), -1000000, -1000000), 

213 (Interval(0, math.inf), 1000000, 1000000), 

214 ], 

215) 

216def test_clamp(interval: Interval, value: Union[int, float], expected: float): 

217 assert math.isclose(interval.clamp(value), expected, rel_tol=1e-9, abs_tol=1e-15) 

218 

219 

220def test_clamp_nan(): 

221 interval = Interval(0, 1) 

222 with pytest.raises(ValueError): 

223 interval.clamp(math.nan) 

224 

225 

226def test_zero_width_interval(): 

227 # Test initialization and behavior of zero-width intervals 

228 zero_open = Interval(1, 1) 

229 zero_closed = ClosedInterval(1, 1) 

230 assert str(zero_open) == str(Interval()) 

231 assert str(zero_closed) == r"{1}" 

232 assert 1 not in zero_open 

233 assert 1 in zero_closed 

234 with pytest.raises(ValueError): 

235 assert zero_open.clamp(1) == 1 + _EPSILON 

236 assert zero_closed.clamp(1) == 1 

237 

238 

239def test_interval_containing_zero(): 

240 # Test intervals that contain zero, especially for multiplication operations 

241 i = Interval(-1, 1) 

242 assert 0 in i 

243 assert i.clamp(0) == 0 

244 

245 

246def test_very_large_intervals(): 

247 # Test intervals with very large bounds 

248 large = Interval(1e300, 1e301) 

249 assert 1e300 not in large 

250 assert 5e300 in large 

251 assert large.clamp(0) == 1e300 + _EPSILON 

252 assert large.clamp(2e301) == 1e301 - _EPSILON 

253 

254 

255def test_very_small_intervals(): 

256 # Test intervals with very small bounds 

257 small = Interval(1e-20, 1e-19) 

258 assert 1e-20 not in small 

259 assert 2e-20 in small 

260 

261 with pytest.raises(ValueError): 

262 small.clamp(-1) 

263 

264 assert small.clamp(2e-19, epsilon=1e-20) == 1e-19 - 1e-20 

265 

266 

267def test_intervals_with_epsilon_width(): 

268 # Test intervals with width close to or less than epsilon 

269 i = Interval(1, 1.5) 

270 assert 1 not in i 

271 assert 1.2 in i 

272 assert i.clamp(1) == 1 + _EPSILON 

273 assert i.clamp(2) == 1.5 - _EPSILON 

274 

275 

276def test_extreme_epsilon_values(): 

277 # Test clamp method with extreme epsilon values 

278 i = Interval(0, 1) 

279 assert i.clamp(2, epsilon=1e-100) == 1 - 1e-100 

280 assert i.clamp(-1, epsilon=0.1) == 0.1 

281 with pytest.raises(ValueError): 

282 i.clamp(0.5, epsilon=-1e-10) # Negative epsilon should raise an error 

283 

284 

285def test_interval_intersection(): 

286 # Test intersection of intervals 

287 assert Interval(1, 3) in Interval(0, 4) 

288 assert Interval(1, 3) not in Interval(2, 4) 

289 assert ClosedInterval(1, 2) in OpenInterval(0, 3) 

290 

291 

292def test_interval_with_non_numeric_types(): 

293 # Test behavior with non-numeric types 

294 with pytest.raises(TypeError): 

295 Interval("a", "b") # type: ignore[arg-type] 

296 with pytest.raises(TypeError): 

297 "a" in Interval(1, 2) 

298 

299 

300def test_interval_initialization_with_iterables(): 

301 # Test initialization with different iterable types 

302 assert str(Interval(range(1, 3))) == "(1, 2)" 

303 assert str(Interval((1, 2))) == "(1, 2)" 

304 

305 

306def test_clamp_with_infinite_values(): 

307 # Test clamping with infinite values 

308 inf_interval = Interval(-math.inf, math.inf) 

309 assert inf_interval.clamp(1e1000) == 1e1000 

310 assert inf_interval.clamp(-1e1000) == -1e1000 

311 

312 right_inf = Interval(0, math.inf) 

313 assert right_inf.clamp(1e1000) == 1e1000 

314 assert right_inf.clamp(-1) == 0 + _EPSILON 

315 

316 left_inf = Interval(-math.inf, 0) 

317 assert left_inf.clamp(-1e1000) == -1e1000 

318 assert left_inf.clamp(1) == 0 - _EPSILON 

319 

320 

321def test_interval_equality_with_different_types(): 

322 assert Interval(1, 2) == OpenInterval(1, 2) 

323 assert Interval(1, 2, is_closed=True) == ClosedInterval(1, 2) 

324 assert Interval(1, 2) != ClosedInterval(1, 2) 

325 assert Interval(1, 2, closed_L=True) != Interval(1, 2, closed_R=True) 

326 assert Interval(1, 2) != (1, 2) 

327 assert Interval(1, 2) != [1, 2] 

328 assert Interval(1, 2) != "Interval(1, 2)" 

329 

330 

331def test_interval_containment_edge_cases(): 

332 i = Interval(0, 1) 

333 assert 0 + _EPSILON / 2 in i 

334 assert 1 - _EPSILON / 2 in i 

335 assert 0 not in i 

336 assert 1 not in i 

337 

338 ci = ClosedInterval(0, 1) 

339 assert 0 in ci 

340 assert 1 in ci 

341 assert 0 - _EPSILON / 2 not in ci 

342 assert 1 + _EPSILON / 2 not in ci 

343 

344 

345def test_clamp_with_custom_epsilon(): 

346 i = Interval(0, 1) 

347 assert i.clamp(-1, epsilon=0.1) == 0.1 

348 assert i.clamp(2, epsilon=0.1) == 0.9 

349 assert i.clamp(0.5, epsilon=0.4) == 0.5 # epsilon doesn't affect internal points 

350 

351 

352def test_interval_with_float_imprecision(): 

353 # Test handling of float imprecision 

354 i = Interval(0.1, 0.2) 

355 assert 0.15 in i 

356 assert 0.1 + 1e-15 in i 

357 assert 0.2 - 1e-15 in i 

358 

359 

360def test_interval_initialization_with_reversed_bounds(): 

361 with pytest.raises(ValueError): 

362 Interval(2, 1) 

363 with pytest.raises(ValueError): 

364 ClosedInterval([5, 3]) 

365 

366 

367def test_interval_initialization_with_equal_bounds(): 

368 i = Interval(1, 1) 

369 assert i == Interval.get_empty() 

370 assert i == Interval() 

371 assert str(i) == str(Interval.get_empty()) 

372 assert 1 not in i 

373 

374 ci = ClosedInterval(1, 1) 

375 assert str(ci) == r"{1}" 

376 assert 1 in ci 

377 

378 

379def test_interval_with_only_one_infinite_bound(): 

380 left_inf = Interval(-math.inf, 5, closed_L=True) 

381 assert -1e1000 in left_inf 

382 assert 5 not in left_inf 

383 assert left_inf.clamp(6) == 5 - _EPSILON 

384 

385 right_inf = Interval(5, math.inf, closed_R=True) 

386 assert 1e1000 in right_inf 

387 assert 5 not in right_inf 

388 assert right_inf.clamp(4) == 5 + _EPSILON 

389 

390 

391@pytest.mark.parametrize( 

392 "container,contained,expected", 

393 [ 

394 (Interval(0, 6), Interval(1, 5), True), 

395 (Interval(2, 6), Interval(1, 5), False), 

396 (OpenInterval(0, 6), ClosedInterval(1, 5), True), 

397 (ClosedInterval(1, 5), OpenInterval(1, 5), True), 

398 (OpenInterval(1, 5), ClosedInterval(1, 5), False), 

399 (Interval(1, 5), Interval(1, 5), True), 

400 (Interval(1, 5), ClosedInterval(1, 5), False), 

401 (ClosedInterval(1, 5), Interval(1, 5), True), 

402 (Interval(1, 5), OpenInterval(1, 5), True), 

403 (Interval(1, 5), Interval(1, 5, closed_L=True), False), 

404 (Interval(1, 5), Interval(1, 5, closed_R=True), False), 

405 (ClosedInterval(1, 5), Interval(1, 5, closed_L=True), True), 

406 (ClosedInterval(1, 5), Interval(1, 5, closed_R=True), True), 

407 (Interval(1, 5, closed_R=True), ClosedInterval(1, 5), False), 

408 (Interval(1, 5, closed_L=True), ClosedInterval(1, 5), False), 

409 (Interval(1, 5, closed_L=True, closed_R=True), ClosedInterval(1, 5), True), 

410 (Interval(1, 5, is_closed=True), ClosedInterval(1, 5), True), 

411 (ClosedInterval(1, 5), Interval(1, 5, closed_L=True), True), 

412 (ClosedInterval(1, 5), Interval(1, 5, closed_R=True), True), 

413 (Interval(1, 5, closed_L=True, closed_R=True), Interval(1, 5), True), 

414 (Interval(-math.inf, math.inf), Interval(-math.inf, math.inf), True), 

415 (Interval(0, 1, closed_L=True), Interval(0, 1), True), 

416 ( 

417 Interval(1, 5), 

418 Interval(0, 6), 

419 False, 

420 ), # Contained interval extends beyond container 

421 (Interval(1, 5), Interval(2, 4), True), # Strictly contained interval 

422 (OpenInterval(1, 5), OpenInterval(1, 5), True), # Equal open intervals 

423 (ClosedInterval(1, 5), ClosedInterval(1, 5), True), # Equal closed intervals 

424 ( 

425 OpenInterval(1, 5), 

426 ClosedInterval(1, 5), 

427 False, 

428 ), # Open doesn't contain closed with same bounds 

429 ( 

430 ClosedInterval(1, 5), 

431 OpenInterval(1, 5), 

432 True, 

433 ), # Closed contains open with same bounds 

434 (Interval(1, 5, closed_L=True), Interval(1, 5), True), 

435 ( 

436 Interval(1, 5), 

437 Interval(1, 5, closed_L=True), 

438 False, 

439 ), # Open doesn't contain half-open 

440 (Interval(1, 5, closed_R=True), Interval(1, 5), True), 

441 ( 

442 Interval(1, 5), 

443 Interval(1, 5, closed_R=True), 

444 False, 

445 ), # Open doesn't contain half-open 

446 (Interval(1, 1), Interval(1, 1), True), # Point intervals 

447 (OpenInterval(1, 1), OpenInterval(1, 1), True), # Empty open intervals 

448 (ClosedInterval(1, 1), ClosedInterval(1, 1), True), # Point closed intervals 

449 ( 

450 OpenInterval(1, 1), 

451 ClosedInterval(1, 1), 

452 False, 

453 ), # Empty open doesn't contain point closed 

454 ( 

455 ClosedInterval(1, 1), 

456 OpenInterval(1, 1), 

457 True, 

458 ), # Point closed contains empty open 

459 (Interval(0, math.inf), Interval(1, math.inf), True), # Infinite upper bound 

460 (Interval(-math.inf, 0), Interval(-math.inf, -1), True), # Infinite lower bound 

461 ( 

462 Interval(-math.inf, math.inf), 

463 Interval(0, 0), 

464 True, 

465 ), # Real line contains any point 

466 ( 

467 Interval(0, 1), 

468 Interval(-math.inf, math.inf), 

469 False, 

470 ), # Finite doesn't contain infinite 

471 (Interval(1, 5), Interval(5, 10), False), # Adjacent intervals 

472 ( 

473 ClosedInterval(1, 5), 

474 ClosedInterval(5, 10), 

475 False, 

476 ), # Adjacent closed intervals 

477 (OpenInterval(1, 5), OpenInterval(5, 10), False), # Adjacent open intervals 

478 ( 

479 Interval(1, 5, closed_R=True), 

480 Interval(5, 10, closed_L=True), 

481 False, 

482 ), # Adjacent half-open intervals 

483 ], 

484) 

485def test_interval_containment(container, contained, expected): 

486 print(f"{container = }, {contained = }, {expected = }") 

487 assert (contained in container) == expected 

488 

489 

490@pytest.mark.parametrize( 

491 "interval_a,interval_b,expected", 

492 [ 

493 (ClosedInterval(1, 5), Interval(1, 5, is_closed=True), True), 

494 (Interval(1, 5), Interval(1, 5, closed_L=True), False), 

495 (Interval(1, 5), Interval(1, 5, closed_R=True), False), 

496 (Interval(1, 5), Interval(1, 5, closed_L=True, closed_R=True), False), 

497 ( 

498 Interval(1, 5, is_closed=True), 

499 Interval(1, 5, closed_L=True, closed_R=True), 

500 True, 

501 ), 

502 (ClosedInterval(1, 5), Interval(1, 5, closed_L=True, closed_R=True), True), 

503 (OpenInterval(1, 5), ClosedInterval(1, 5), False), 

504 (Interval(1, 5, closed_L=True), ClosedInterval(0, 6), False), 

505 (OpenInterval(1, 5), ClosedInterval(1, 5), False), 

506 ], 

507) 

508def test_mixed_interval_types(interval_a, interval_b, expected): 

509 assert (interval_a == interval_b) == expected 

510 

511 

512@pytest.mark.parametrize( 

513 "interval", 

514 [ 

515 Interval(), 

516 Interval(5), 

517 Interval.get_singleton(5), 

518 Interval(1, 5), 

519 ClosedInterval(1, 5), 

520 OpenInterval(1, 5), 

521 Interval(1, 5, closed_L=True), 

522 Interval(1, 5, closed_R=True), 

523 Interval(1, 5, is_closed=True), 

524 Interval(0, math.inf, closed_L=True), 

525 Interval(-math.inf, 0, closed_R=True), 

526 Interval(5.0), 

527 Interval.get_singleton(5.0), 

528 Interval(1.0, 5.0), 

529 ClosedInterval(1.0, 5.0), 

530 OpenInterval(1.0, 5.0), 

531 Interval(1.0, 5.0, closed_L=True), 

532 Interval(1.0, 5.0, closed_R=True), 

533 Interval(1.0, 5.0, is_closed=True), 

534 Interval(0.0, math.inf, closed_L=True), 

535 Interval(-math.inf, 0.0, closed_R=True), 

536 Interval(-math.inf, math.inf), 

537 ClosedInterval(-math.inf, math.inf), 

538 OpenInterval(-math.inf, math.inf), 

539 ], 

540) 

541def test_string_representation_round_trip(interval): 

542 assert Interval.from_str(str(interval)) == interval 

543 

544 

545@pytest.mark.parametrize( 

546 "string,expected", 

547 [ 

548 ("[1, 5]", ClosedInterval(1, 5)), 

549 ("(1, 5)", OpenInterval(1, 5)), 

550 ("[1, 5)", Interval(1, 5, closed_L=True)), 

551 ("(1, 5]", Interval(1, 5, closed_R=True)), 

552 ("[-inf, inf]", ClosedInterval(-math.inf, math.inf)), 

553 ("(0, inf)", OpenInterval(0, math.inf)), 

554 (" [ 1.5, 5.5 ) ", Interval(1.5, 5.5, closed_L=True)), 

555 ("(-1e3, 1e3]", Interval(-1000, 1000, closed_R=True)), 

556 ("[1K, 1M)", Interval(1000, 1000000, closed_L=True)), 

557 ("[-1K, 1M)", Interval(-1000, 1000000, closed_L=True)), 

558 ("(1/2, 3/2]", Interval(0.5, 1.5, closed_R=True)), 

559 ], 

560) 

561def test_parsing_from_strings(string, expected): 

562 assert Interval.from_str(string) == expected 

563 

564 

565@pytest.mark.parametrize( 

566 "interval,expected_size", 

567 [ 

568 (Interval(1, 5), 4), 

569 (ClosedInterval(1, 5), 4), 

570 (OpenInterval(1, 5), 4), 

571 (Interval(0, math.inf), math.inf), 

572 (Interval(-math.inf, math.inf), math.inf), 

573 (Interval(1, 1), 0), 

574 (ClosedInterval(1, 1), 0), 

575 (Interval(0.1, 0.2), 0.1), 

576 ], 

577) 

578def test_interval_size(interval, expected_size): 

579 assert interval.size() == expected_size 

580 

581 

582@pytest.mark.parametrize( 

583 "interval,value,expected", 

584 [ 

585 (ClosedInterval(1, 5), 0, 1), 

586 (OpenInterval(1, 5), 5, 5 - _EPSILON), 

587 ], 

588) 

589def test_clamp_mixed_types(interval, value, expected): 

590 assert math.isclose(interval.clamp(value), expected, rel_tol=1e-9, abs_tol=1e-15) 

591 

592 

593def test_interval_edge_cases(): 

594 # Test with very small intervals 

595 small = Interval(1, 1 + 1e-4) 

596 assert math.isclose(small.size(), 1e-4) 

597 assert 1 + 0.5e-4 in small 

598 assert math.isclose(small.clamp(1), 1 + _EPSILON, rel_tol=1e-9, abs_tol=1e-15) 

599 assert math.isclose( 

600 small.clamp(2), 1 + 1e-4 - _EPSILON, rel_tol=1e-9, abs_tol=1e-15 

601 ) 

602 

603 # Test with intervals smaller than epsilon 

604 tiny = Interval(1, 1 + _EPSILON / 2) 

605 assert math.isclose( 

606 tiny.size(), _EPSILON / 2, rel_tol=1e-9, abs_tol=1e-12 

607 ), f"Size: {tiny.size()}, Epsilon: {_EPSILON}, {tiny = }" 

608 with pytest.raises(ValueError): 

609 assert math.isclose( 

610 tiny.clamp(0), 1 + _EPSILON / 4, rel_tol=1e-9, abs_tol=1e-15 

611 ) 

612 

613 # Test with large intervals 

614 large = Interval(-1e100, 1e100) 

615 assert large.size() == 2e100 

616 assert 1e99 in large 

617 assert math.isclose( 

618 large.clamp(-2e100), -1e100 + _EPSILON, rel_tol=1e-9, abs_tol=1e-15 

619 ) 

620 

621 

622@pytest.mark.parametrize( 

623 "a,b,expected_intersection,expected_union", 

624 [ 

625 ( 

626 Interval(1, 3), 

627 Interval(2, 4), 

628 Interval(2, 3), 

629 Interval(1, 4), 

630 ), 

631 ( 

632 ClosedInterval(0, 2), 

633 OpenInterval(1, 3), 

634 Interval(1, 2, closed_R=True), 

635 Interval(0, 3, closed_L=True), 

636 ), 

637 ( 

638 OpenInterval(1, 5), 

639 ClosedInterval(2, 4), 

640 ClosedInterval(2, 4), 

641 OpenInterval(1, 5), 

642 ), 

643 # Non-overlapping intervals 

644 (Interval(1, 3), Interval(4, 6), Interval(), Interval()), 

645 # Touching intervals 

646 ( 

647 Interval(1, 3, closed_R=True), 

648 Interval(3, 5), 

649 Interval.get_singleton(3), 

650 Interval(1, 5), 

651 ), 

652 ( 

653 ClosedInterval(1, 3), 

654 ClosedInterval(3, 5), 

655 Interval.get_singleton(3), 

656 ClosedInterval(1, 5), 

657 ), 

658 # Fully contained interval 

659 ( 

660 Interval(1, 5), 

661 Interval(2, 3), 

662 Interval(2, 3), 

663 Interval(1, 5), 

664 ), 

665 # Infinite intervals 

666 ( 

667 Interval(-float("inf"), 1), 

668 Interval(0, float("inf")), 

669 Interval(0, 1), 

670 Interval(-float("inf"), float("inf")), 

671 ), 

672 ], 

673) 

674def test_interval_arithmetic( 

675 a: Interval, 

676 b: Interval, 

677 expected_intersection: Optional[Interval], 

678 expected_union: Optional[Interval], 

679): 

680 # Test intersection 

681 if expected_intersection == Interval(): 

682 assert a.intersection(b) == Interval() 

683 assert b.intersection(a) == Interval() 

684 else: 

685 assert a.intersection(b) == expected_intersection 

686 assert b.intersection(a) == expected_intersection # Commutativity 

687 

688 # Test union 

689 if expected_union == Interval(): 

690 with pytest.raises(Exception): 

691 a.union(b) 

692 with pytest.raises(Exception): 

693 b.union(a) 

694 else: 

695 assert a.union(b) == expected_union 

696 assert b.union(a) == expected_union # Commutativity 

697 

698 

699# Additional tests for edge cases 

700def test_interval_arithmetic_edge_cases(): 

701 # Self-intersection and self-union 

702 a = Interval(1, 3) 

703 assert a.intersection(a) == a 

704 assert a.union(a) == a 

705 

706 # Empty interval intersection 

707 empty = Interval(1, 1) 

708 assert empty.intersection(Interval(2, 3)) == Interval() 

709 assert Interval(2, 3).intersection(empty) == Interval() 

710 

711 # Intersection with universal set 

712 universal = Interval(-float("inf"), float("inf")) 

713 assert Interval(1, 2).intersection(universal) == Interval(1, 2) 

714 assert universal.intersection(Interval(1, 2)) == Interval(1, 2) 

715 

716 # Union with universal set 

717 assert Interval(1, 2).union(universal) == universal 

718 assert universal.union(Interval(1, 2)) == universal 

719 

720 

721# Test for invalid operations 

722def test_interval_arithmetic_invalid(): 

723 with pytest.raises(TypeError): 

724 # Invalid type for intersection 

725 Interval(1, 2).intersection(5) # type: ignore[arg-type] 

726 

727 with pytest.raises(TypeError): 

728 # Invalid type for union 

729 Interval(1, 2).union("invalid") # type: ignore[arg-type] 

730 

731 

732@pytest.mark.parametrize( 

733 "invalid_string", 

734 [ 

735 "1, 5", # Missing brackets 

736 "[1, 5, 7]", # Too many values 

737 "[5, 1]", # Lower > Upper 

738 "[a, b]", # Non-numeric values 

739 ], 

740) 

741def test_from_str_errors(invalid_string): 

742 with pytest.raises(ValueError): 

743 Interval.from_str(invalid_string) 

744 

745 

746def test_interval_repr(): 

747 assert repr(Interval(1, 5)) == "(1, 5)" 

748 assert repr(ClosedInterval(1, 5)) == "[1, 5]" 

749 assert repr(OpenInterval(1, 5)) == "(1, 5)" 

750 assert repr(Interval(1, 5, closed_L=True)) == "[1, 5)" 

751 assert repr(Interval(1, 5, closed_R=True)) == "(1, 5]" 

752 

753 

754def test_interval_from_str_with_whitespace(): 

755 assert Interval.from_str(" ( 1 , 5 ) ") == Interval(1, 5) 

756 assert Interval.from_str("[1.5 , 3.5]") == ClosedInterval(1.5, 3.5) 

757 

758 

759def test_interval_from_str_with_scientific_notation(): 

760 assert Interval.from_str("(1e-3, 1e3)") == Interval(0.001, 1000) 

761 

762 

763def test_interval_clamp_with_custom_epsilon(): 

764 i = Interval(0, 1) 

765 assert math.isclose(i.clamp(-0.5, epsilon=0.25), 0.25, rel_tol=1e-9) 

766 assert math.isclose(i.clamp(1.5, epsilon=0.25), 0.75, rel_tol=1e-9) 

767 

768 

769def test_interval_size_with_small_values(): 

770 i = Interval(1e-10, 2e-10) 

771 assert math.isclose(i.size(), 1e-10, rel_tol=1e-9) 

772 

773 

774def test_interval_intersection_edge_cases(): 

775 i1 = Interval(1, 2) 

776 i2 = Interval(2, 3) 

777 i3 = ClosedInterval(2, 3) 

778 

779 assert i1.intersection(i2) == Interval.get_empty() 

780 assert i1.intersection(i3) == Interval(2, 2, closed_L=True) 

781 

782 

783def test_interval_union_edge_cases(): 

784 i1 = Interval(1, 2, closed_R=True) 

785 i2 = Interval(2, 3, closed_L=True) 

786 i3 = Interval(3, 4) 

787 

788 assert i1.union(i2) == Interval(1, 3) 

789 with pytest.raises(NotImplementedError): 

790 i1.union(i3) 

791 

792 

793def test_interval_contains_with_epsilon(): 

794 i = OpenInterval(0, 1) 

795 assert 0 + _EPSILON in i 

796 assert 1 - _EPSILON in i 

797 assert 0 not in i 

798 assert 1 not in i 

799 

800 

801def test_singleton_creation(): 

802 s = Interval.get_singleton(5) 

803 assert s.is_singleton 

804 assert s.singleton == 5 

805 assert len(s) == 1 

806 assert s.lower == s.upper == 5 

807 assert s.closed_L and s.closed_R 

808 

809 

810def test_empty_creation(): 

811 e = Interval.get_empty() 

812 assert e.is_empty 

813 assert len(e) == 0 

814 with pytest.raises(ValueError): 

815 _ = e.singleton 

816 

817 

818def test_singleton_properties(): 

819 s = Interval.get_singleton(3.14) 

820 assert s.is_singleton 

821 assert not s.is_empty 

822 assert s.is_finite 

823 assert s.is_closed 

824 assert not s.is_open 

825 assert not s.is_half_open 

826 

827 

828def test_empty_properties(): 

829 e = Interval.get_empty() 

830 assert e.is_empty 

831 assert not e.is_singleton 

832 assert e.is_finite 

833 assert e.is_closed 

834 assert e.is_open 

835 assert not e.is_half_open 

836 

837 

838def test_singleton_containment(): 

839 s = Interval.get_singleton(5) 

840 assert 5 in s 

841 assert 5.0 in s 

842 assert 4.999999999999999 not in s 

843 assert 5.000000000000001 not in s 

844 assert Interval.get_singleton(5) in s 

845 assert Interval(4, 6) not in s 

846 assert s in Interval(4, 6) 

847 

848 

849def test_empty_containment(): 

850 e = Interval.get_empty() 

851 assert 0 not in e 

852 assert e in Interval(0, 1) # Empty set is a subset of all sets 

853 assert Interval.get_empty() in e 

854 

855 

856@pytest.mark.parametrize("value", [0, 1, -1, math.pi, math.e, math.inf, -math.inf]) 

857def test_singleton_various_values(value): 

858 s = Interval.get_singleton(value) 

859 assert s.is_singleton 

860 assert s.singleton == value 

861 assert value in s 

862 

863 

864def test_singleton_nan(): 

865 assert Interval.get_singleton(math.nan).is_empty 

866 

867 

868def test_singleton_operations(): 

869 s = Interval.get_singleton(5) 

870 assert s.size() == 0 

871 assert s.clamp(3) == 5 

872 assert s.clamp(7) == 5 

873 

874 

875def test_empty_operations(): 

876 e = Interval.get_empty() 

877 assert e.size() == 0 

878 with pytest.raises(ValueError): 

879 e.clamp(3) 

880 

881 

882def test_singleton_intersection(): 

883 s = Interval.get_singleton(5) 

884 assert s.intersection(Interval(0, 10)) == s 

885 assert s.intersection(Interval(5, 10)) == Interval.get_empty() 

886 assert s.intersection(ClosedInterval(5, 10)) == s 

887 assert s.intersection(Interval(5, 10, closed_R=True)) == Interval.get_empty() 

888 assert s.intersection(Interval(5, 10, closed_L=True)) == s 

889 assert s.intersection(Interval(5, 10, is_closed=True)) == s 

890 assert s.intersection(Interval(0, 6)) == s 

891 assert s.intersection(Interval(0, 5)) == Interval.get_empty() 

892 assert s.intersection(Interval(6, 10)) == Interval.get_empty() 

893 assert s.intersection(Interval(6, 10)) == Interval.get_empty() 

894 assert s.intersection(Interval.get_singleton(5)) == s 

895 assert s.intersection(Interval.get_singleton(6)) == Interval.get_empty() 

896 

897 

898def test_empty_intersection(): 

899 e = Interval.get_empty() 

900 assert e.intersection(Interval(0, 1)) == e 

901 assert e.intersection(Interval.get_singleton(0)) == e 

902 assert e.intersection(Interval.get_empty()) == e 

903 

904 

905def test_singleton_union(): 

906 s = Interval.get_singleton(5) 

907 assert s.union(Interval(0, 10)) == Interval(0, 10) 

908 assert s.union(Interval(5, 10)) == Interval(5, 10, closed_L=True) 

909 assert s.union(Interval(0, 5)) == Interval(0, 5, closed_R=True) 

910 with pytest.raises(NotImplementedError): 

911 s.union(Interval(6, 10)) 

912 assert s.union(Interval.get_singleton(5)) == s 

913 with pytest.raises(NotImplementedError): 

914 s.union(Interval.get_singleton(6)) 

915 

916 

917def test_empty_union(): 

918 e = Interval.get_empty() 

919 assert e.union(Interval(0, 1)) == Interval(0, 1) 

920 assert e.union(Interval.get_singleton(0)) == Interval.get_singleton(0) 

921 assert e.union(Interval.get_empty()) == Interval.get_empty() 

922 

923 

924def test_singleton_equality(): 

925 assert Interval.get_singleton(5) == Interval.get_singleton(5) 

926 assert Interval.get_singleton(5) != Interval.get_singleton(6) 

927 assert Interval.get_singleton(5) == ClosedInterval(5, 5) 

928 assert Interval.get_singleton(5) != OpenInterval(5, 5) 

929 

930 

931def test_empty_equality(): 

932 assert Interval.get_empty() == Interval.get_empty() 

933 assert Interval.get_empty() == OpenInterval(5, 5) 

934 assert Interval.get_empty() != ClosedInterval(5, 5) 

935 

936 

937def test_singleton_representation(): 

938 assert repr(Interval.get_singleton(5)) == "{5}" 

939 assert str(Interval.get_singleton(5)) == "{5}" 

940 

941 

942def test_empty_representation(): 

943 assert repr(Interval.get_empty()) == "∅" 

944 assert str(Interval.get_empty()) == "∅" 

945 

946 

947def test_singleton_from_str(): 

948 assert Interval.from_str("{5}") == Interval.get_singleton(5) 

949 assert Interval.from_str("{3.14}") == Interval.get_singleton(3.14) 

950 

951 

952def test_empty_from_str(): 

953 assert Interval.from_str("∅") == Interval.get_empty() 

954 assert Interval.from_str("{}") == Interval.get_empty() 

955 

956 

957def test_singleton_iteration(): 

958 s = Interval.get_singleton(5) 

959 assert list(s) == [5] 

960 assert [x for x in s] == [5] 

961 

962 

963def test_empty_iteration(): 

964 e = Interval.get_empty() 

965 assert list(e) == [] 

966 assert [x for x in e] == [] 

967 

968 

969def test_singleton_indexing(): 

970 s = Interval.get_singleton(5) 

971 assert s[0] == 5 

972 with pytest.raises(IndexError): 

973 _ = s[1] 

974 

975 

976def test_empty_indexing(): 

977 e = Interval.get_empty() 

978 with pytest.raises(IndexError): 

979 _ = e[0] 

980 

981 

982def test_singleton_bool(): 

983 assert bool(Interval.get_singleton(5)) 

984 assert bool(Interval.get_singleton(0)) 

985 

986 

987def test_empty_bool(): 

988 assert not bool(Interval.get_empty()) 

989 

990 

991def test_singleton_infinity(): 

992 inf_singleton = Interval.get_singleton(math.inf) 

993 assert inf_singleton.is_singleton 

994 assert not inf_singleton.is_finite 

995 assert math.inf in inf_singleton 

996 assert math.inf - 1 in inf_singleton 

997 

998 

999def test_mixed_operations(): 

1000 s = Interval.get_singleton(5) 

1001 e = Interval.get_empty() 

1002 i = Interval(0, 10) 

1003 

1004 assert s.intersection(e) == e 

1005 assert e.intersection(s) == e 

1006 assert i.intersection(s) == s 

1007 assert i.intersection(e) == e 

1008 

1009 assert s.union(e) == s 

1010 assert e.union(s) == s 

1011 assert i.union(s) == i 

1012 assert i.union(e) == i 

1013 

1014 

1015def test_edge_case_conversions(): 

1016 assert Interval(5, 5, closed_L=True, closed_R=True).is_singleton 

1017 assert Interval(5, 5, closed_L=False, closed_R=False).is_empty 

1018 assert not Interval(5, 5, closed_L=True, closed_R=False).is_empty 

1019 assert not Interval(5, 5, closed_L=False, closed_R=True).is_empty 

1020 

1021 

1022def test_nan_handling_is_empty(): 

1023 assert Interval(math.nan, math.nan).is_empty 

1024 assert Interval(None, None).is_empty # type: ignore[arg-type] 

1025 assert Interval().is_empty 

1026 assert Interval.get_empty().is_empty 

1027 with pytest.raises(ValueError): 

1028 Interval(math.nan, 5) 

1029 with pytest.raises(ValueError): 

1030 assert Interval(5, math.nan) 

1031 

1032 

1033def test_infinity_edge_cases(): 

1034 assert not Interval(-math.inf, math.inf).is_empty 

1035 assert not Interval(-math.inf, math.inf).is_singleton 

1036 assert Interval(math.inf, math.inf, closed_L=True, closed_R=True).is_singleton 

1037 assert Interval(-math.inf, -math.inf, closed_L=True, closed_R=True).is_singleton 

1038 

1039 

1040# Potential bug: What should happen in this case? 

1041def test_potential_bug_infinity_singleton(): 

1042 inf_singleton = Interval.get_singleton(math.inf) 

1043 assert math.isinf(inf_singleton.singleton) 

1044 assert inf_singleton == Interval(math.inf, math.inf, closed_L=True, closed_R=True) 

1045 

1046 

1047# Potential bug: Epsilon handling with singletons 

1048def test_potential_bug_singleton_epsilon(): 

1049 s = Interval.get_singleton(5) 

1050 assert 5 + 1e-15 not in s # This might fail if epsilon is not handled correctly 

1051 

1052 

1053# Potential bug: Empty set comparison 

1054def test_potential_bug_empty_comparison(): 

1055 e1 = Interval.get_empty() 

1056 e2 = OpenInterval(5, 5) 

1057 assert e1 == e2 # This might fail if empty sets are not compared correctly 

1058 

1059 

1060# Potential bug: Singleton at zero 

1061def test_potential_bug_zero_singleton(): 

1062 zero_singleton = Interval.get_singleton(0) 

1063 assert 0 in zero_singleton 

1064 assert -0.0 in zero_singleton # This might fail due to float representation 

1065 

1066 

1067# Potential bug: Empty set size 

1068def test_potential_bug_empty_set_size(): 

1069 e = Interval.get_empty() 

1070 assert ( 

1071 e.size() == 0 

1072 ) # This might fail if size is not correctly implemented for empty sets 

1073 

1074 

1075@pytest.mark.parametrize( 

1076 "interval", 

1077 [ 

1078 # Empty set 

1079 Interval.get_empty(), 

1080 # Singletons 

1081 Interval.get_singleton(0), 

1082 Interval.get_singleton(5), 

1083 Interval.get_singleton(-3), 

1084 Interval.get_singleton(3.14), 

1085 Interval.get_singleton(-2.718), 

1086 # Regular intervals 

1087 Interval(0, 1), 

1088 Interval(0, 1, closed_L=True), 

1089 Interval(0, 1, closed_R=True), 

1090 Interval(0, 1, closed_L=True, closed_R=True), 

1091 OpenInterval(-5, 5), 

1092 ClosedInterval(-5, 5), 

1093 # Intervals with infinities 

1094 Interval(-math.inf, math.inf), 

1095 Interval(-math.inf, 0), 

1096 Interval(0, math.inf), 

1097 Interval(-math.inf, 0, closed_R=True), 

1098 Interval(0, math.inf, closed_L=True), 

1099 ClosedInterval(-math.inf, math.inf), 

1100 # Mixed finite and infinite bounds 

1101 Interval(-math.inf, 5), 

1102 Interval(-5, math.inf), 

1103 Interval(-math.inf, 5, closed_R=True), 

1104 Interval(-5, math.inf, closed_L=True), 

1105 # Very large and very small numbers 

1106 Interval(1e-100, 1e100), 

1107 ClosedInterval(-1e50, 1e50), 

1108 # Intervals with non-integer bounds 

1109 Interval(math.e, math.pi), 

1110 ClosedInterval(math.sqrt(2), math.sqrt(3)), 

1111 ], 

1112) 

1113def test_interval_string_round_trip(interval): 

1114 # Convert interval to string 

1115 interval_str = str(interval) 

1116 

1117 # Parse string back to interval 

1118 parsed_interval = Interval.from_str(interval_str) 

1119 

1120 # Check if the parsed interval is equal to the original 

1121 assert ( 

1122 parsed_interval == interval 

1123 ), f"Round trip failed for {interval}. Got {parsed_interval}" 

1124 

1125 # Additional check for string representation consistency 

1126 assert ( 

1127 str(parsed_interval) == interval_str 

1128 ), f"String representation mismatch for {interval}. Expected {interval_str}, got {str(parsed_interval)}" 

1129 

1130 

1131def test_empty_set_string_representations(): 

1132 empty = Interval.get_empty() 

1133 assert str(empty) == "∅" 

1134 assert Interval.from_str("∅") == empty 

1135 assert Interval.from_str("{}") == empty 

1136 

1137 

1138@pytest.mark.parametrize("value", [0, 1, -1, 3.14, -2.718, math.pi, math.e]) 

1139def test_singleton_string_representations(value): 

1140 singleton = Interval.get_singleton(value) 

1141 assert str(singleton) == f"{ {value}} " 

1142 assert Interval.from_str(f"{ {value}} ") == singleton 

1143 

1144 

1145def test_infinity_string_representations(): 

1146 assert str(Interval(-math.inf, math.inf)) == "(-inf, inf)" 

1147 assert str(ClosedInterval(-math.inf, math.inf)) == "[-inf, inf]" 

1148 assert str(Interval(-math.inf, 0)) == "(-inf, 0)" 

1149 assert str(Interval(0, math.inf)) == "(0, inf)" 

1150 

1151 

1152def test_mixed_closure_string_representations(): 

1153 assert str(Interval(0, 1, closed_L=True)) == "[0, 1)" 

1154 assert str(Interval(0, 1, closed_R=True)) == "(0, 1]" 

1155 assert str(Interval(-math.inf, 0, closed_R=True)) == "(-inf, 0]" 

1156 assert str(Interval(0, math.inf, closed_L=True)) == "[0, inf)" 

1157 

1158 

1159@pytest.mark.parametrize( 

1160 "string_repr", 

1161 [ 

1162 "(0, 1)", 

1163 "[0, 1]", 

1164 "(0, 1]", 

1165 "[0, 1)", 

1166 "(-inf, inf)", 

1167 "[0, inf)", 

1168 "(-inf, 0]", 

1169 "{2.5}", 

1170 "∅", 

1171 ], 

1172) 

1173def test_string_parsing_consistency(string_repr): 

1174 parsed_interval = Interval.from_str(string_repr) 

1175 assert ( 

1176 str(parsed_interval) == string_repr 

1177 ), f"Parsing inconsistency for {string_repr}. Got {str(parsed_interval)}" 

1178 

1179 

1180def test_string_parsing_edge_cases(): 

1181 with pytest.raises(ValueError): 

1182 Interval.from_str("(1, 0)") # Lower bound greater than upper bound 

1183 

1184 with pytest.raises(ValueError): 

1185 Interval.from_str("[1, 2, 3]") # Too many values 

1186 

1187 with pytest.raises(ValueError): 

1188 Interval.from_str("(a, b)") # Non-numeric values 

1189 

1190 

1191def test_string_representation_precision(): 

1192 # Test that string representation maintains sufficient precision 

1193 small_interval = Interval(1e-10, 2e-10) 

1194 parsed_small_interval = Interval.from_str(str(small_interval)) 

1195 assert small_interval == parsed_small_interval 

1196 assert small_interval.lower == parsed_small_interval.lower 

1197 assert small_interval.upper == parsed_small_interval.upper 

1198 

1199 

1200def test_string_representation_with_scientific_notation(): 

1201 large_interval = Interval(1e100, 2e100) 

1202 large_interval_str = str(large_interval) 

1203 assert "e+" in large_interval_str # Ensure scientific notation is used 

1204 parsed_large_interval = Interval.from_str(large_interval_str) 

1205 assert large_interval == parsed_large_interval 

1206 

1207 

1208# Potential bug: Handling of -0.0 in string representation 

1209def test_potential_bug_negative_zero(): 

1210 zero_singleton = Interval.get_singleton(-0.0) 

1211 zero_singleton_str = str(zero_singleton) 

1212 assert Interval.from_str(zero_singleton_str) == zero_singleton 

1213 assert ( 

1214 Interval.from_str(zero_singleton_str).singleton == 0.0 

1215 ) # Should this be -0.0 or 0.0? 

1216 

1217 i = Interval(-1, 1) 

1218 assert 0.0 in i 

1219 assert -0.0 in i # This might fail if -0.0 is not handled correctly 

1220 

1221 

1222# Potential bug: Precision loss in string representation 

1223def test_potential_bug_precision_loss(): 

1224 precise_interval = Interval(1 / 3, 2 / 3) 

1225 precise_interval_str = str(precise_interval) 

1226 parsed_precise_interval = Interval.from_str(precise_interval_str) 

1227 assert precise_interval == parsed_precise_interval 

1228 assert precise_interval.lower == parsed_precise_interval.lower 

1229 assert precise_interval.upper == parsed_precise_interval.upper 

1230 

1231 

1232def test_interval_with_very_close_bounds(): 

1233 i = Interval(1, 1 + 2 * _EPSILON) 

1234 assert i.size() > 0 

1235 assert 1 + _EPSILON in i 

1236 assert i.clamp(1) == 1 + _EPSILON 

1237 assert i.clamp(2) == 1 + _EPSILON 

1238 

1239 

1240def test_interval_with_bounds_closer_than_epsilon(): 

1241 i = Interval(1, 1 + _EPSILON / 2) 

1242 assert i.size() > 0 

1243 with pytest.raises(ValueError): 

1244 assert math.isclose(i.clamp(0), 1 + _EPSILON / 4) 

1245 

1246 

1247def test_interval_with_extremely_large_bounds(): 

1248 i = Interval(1e300, 1e301) 

1249 assert 5e300 in i 

1250 assert i.clamp(0) == 1e300 + _EPSILON 

1251 assert i.clamp(2e301) == 1e301 - _EPSILON 

1252 

1253 

1254def test_interval_with_mixed_infinities(): 

1255 i = Interval(-math.inf, math.inf) 

1256 assert i.size() == math.inf 

1257 assert 0 in i 

1258 assert math.inf not in i 

1259 assert -math.inf not in i 

1260 assert i.clamp(math.inf) == math.inf - _EPSILON 

1261 assert i.clamp(-math.inf) == -math.inf + _EPSILON 

1262 

1263 

1264def test_interval_with_one_infinity(): 

1265 i1 = Interval(-math.inf, 0) 

1266 assert i1.size() == math.inf 

1267 assert -1e300 in i1 

1268 assert 0 not in i1 

1269 assert i1.clamp(1) == 0 - _EPSILON 

1270 

1271 i2 = Interval(0, math.inf) 

1272 assert i2.size() == math.inf 

1273 assert 1e300 in i2 

1274 assert 0 not in i2 

1275 assert i2.clamp(-1) == 0 + _EPSILON 

1276 

1277 

1278def test_interval_singleton_edge_cases(): 

1279 s = Interval.get_singleton(0) 

1280 assert 0 in s 

1281 assert -0.0 in s 

1282 assert 1e-16 not in s 

1283 assert -1e-16 not in s 

1284 

1285 

1286def test_interval_empty_edge_cases(): 

1287 e = Interval.get_empty() 

1288 assert e.size() == 0 

1289 assert e.is_open and e.is_closed 

1290 assert Interval.get_empty() in e 

1291 assert e in Interval(0, 1) 

1292 

1293 

1294def test_interval_from_str_edge_cases(): 

1295 assert Interval.from_str("(0, 0)") == Interval.get_empty() 

1296 assert Interval.from_str("[0, 0]") == Interval.get_singleton(0) 

1297 assert Interval.from_str("(0, 0]") == Interval.get_singleton(0) 

1298 assert Interval.from_str("[0, 0)") == Interval.get_singleton(0) 

1299 

1300 

1301def test_interval_arithmetic_with_empty_intervals(): 

1302 e = Interval.get_empty() 

1303 i = Interval(0, 1) 

1304 assert e.intersection(i) == e 

1305 assert i.intersection(e) == e 

1306 assert e.union(i) == i 

1307 assert i.union(e) == i 

1308 

1309 

1310def test_interval_arithmetic_with_singletons(): 

1311 s = Interval.get_singleton(1) 

1312 i = Interval(0, 2) 

1313 assert s.intersection(i) == s 

1314 assert i.intersection(s) == s 

1315 assert s.union(i) == i 

1316 assert i.union(s) == i 

1317 

1318 

1319def test_interval_precision_near_bounds(): 

1320 i = Interval(1, 2) 

1321 assert 1 + _EPSILON / 2 in i 

1322 assert 2 - _EPSILON / 2 in i 

1323 assert 1 - _EPSILON / 2 not in i 

1324 assert 2 + _EPSILON / 2 not in i 

1325 

1326 

1327def test_interval_serialization_precision(): 

1328 i = Interval(1 / 3, 2 / 3) 

1329 serialized = str(i) 

1330 deserialized = Interval.from_str(serialized) 

1331 assert math.isclose(i.lower, deserialized.lower, rel_tol=1e-15) 

1332 assert math.isclose(i.upper, deserialized.upper, rel_tol=1e-15) 

1333 

1334 

1335def test_interval_with_repeated_float_operations(): 

1336 i = Interval(0, 1) 

1337 for _ in range(1000): 

1338 i = Interval(i.lower + _EPSILON, i.upper - _EPSILON) 

1339 assert i.lower < i.upper 

1340 assert 0.5 in i 

1341 

1342 

1343def test_interval_near_float_precision_limit(): 

1344 small_interval = Interval(1, 1 + 1e-15) 

1345 assert small_interval.size() > 0 

1346 assert 1 + 5e-16 in small_interval 

1347 

1348 

1349def test_interval_with_irrational_bounds(): 

1350 i = Interval(math.e, math.pi) 

1351 print(f"{i.size() = }, {math.e - math.pi = }") 

1352 assert math.isclose(i.size(), math.pi - math.e) 

1353 assert (math.pi + math.e) / 2 in i 

1354 assert 3 in i 

1355 

1356 

1357def test_interval_commutativity_of_operations(): 

1358 i1 = Interval(1, 3) 

1359 i2 = Interval(2, 4) 

1360 assert i1.intersection(i2) == i2.intersection(i1) 

1361 assert i1.union(i2) == i2.union(i1) 

1362 

1363 

1364def test_interval_associativity_of_operations(): 

1365 i1 = Interval(1, 4) 

1366 i2 = Interval(2, 5) 

1367 i3 = Interval(3, 6) 

1368 assert (i1.intersection(i2)).intersection(i3) == i1.intersection( 

1369 i2.intersection(i3) 

1370 ) 

1371 assert (i1.union(i2)).union(i3) == i1.union(i2.union(i3)) 

1372 

1373 

1374def test_interval_distributivity_of_operations(): 

1375 i1 = Interval(1, 3) 

1376 i2 = Interval(2, 4) 

1377 i3 = Interval(3, 5) 

1378 assert i1.intersection(i2.union(i3)) == (i1.intersection(i2)).union( 

1379 i1.intersection(i3) 

1380 ) 

1381 

1382 

1383def test_interval_comparison(): 

1384 i1 = Interval(1, 3) 

1385 i2 = Interval(2, 4) 

1386 i3 = Interval(1, 3) 

1387 assert i1 != i2 

1388 assert i1 == i3 

1389 with pytest.raises(TypeError): 

1390 i1 < i2 # type: ignore[operator] 

1391 

1392 

1393def test_interval_copy(): 

1394 i = Interval(1, 2, closed_L=True) 

1395 i_copy = i.copy() 

1396 assert i == i_copy 

1397 assert i is not i_copy 

1398 

1399 

1400def test_interval_pickling(): 

1401 import pickle 

1402 

1403 i = Interval(1, 2, closed_L=True) 

1404 pickled = pickle.dumps(i) 

1405 unpickled = pickle.loads(pickled) 

1406 assert i == unpickled 

1407 

1408 

1409def test_interval_with_numpy_types(): 

1410 import numpy as np 

1411 

1412 i = Interval(np.float64(1), np.float64(2)) # type: ignore[arg-type] 

1413 assert np.float64(1.5) in i 

1414 assert isinstance(i.clamp(np.float64(0)), np.float64) # type: ignore[arg-type] 

1415 

1416 

1417def test_interval_with_decimal_types(): 

1418 from decimal import Decimal 

1419 

1420 i = Interval(Decimal("1"), Decimal("2")) # type: ignore[arg-type] 

1421 assert Decimal("1.5") in i 

1422 assert min(i) == Decimal("1") 

1423 assert max(i) == Decimal("2") 

1424 assert isinstance(i.clamp(Decimal("0")), Decimal) # type: ignore[arg-type] 

1425 

1426 

1427def test_interval_with_fractions(): 

1428 from fractions import Fraction 

1429 

1430 i = Interval(Fraction(1, 3), Fraction(2, 3)) # type: ignore[arg-type] 

1431 assert Fraction(1, 2) in i 

1432 assert isinstance(i.clamp(Fraction(0, 1)), Fraction) # type: ignore[arg-type] 

1433 

1434 

1435# Potential bug: Infinity comparisons 

1436def test_potential_bug_infinity_comparisons(): 

1437 i = Interval(-math.inf, math.inf) 

1438 assert ( 

1439 math.inf not in i 

1440 ) # This might fail if infinity comparisons are not handled correctly 

1441 

1442 

1443# Potential bug: NaN handling 

1444def test_potential_bug_nan_handling(): 

1445 with pytest.raises(ValueError): 

1446 Interval(0, float("nan")) 

1447 

1448 i = Interval(0, 1) 

1449 with pytest.raises(ValueError): 

1450 float("nan") in i 

1451 

1452 

1453# Potential bug: Intersection of adjacent intervals 

1454def test_potential_bug_adjacent_interval_intersection(): 

1455 i1 = Interval(0, 1, closed_R=True) 

1456 i2 = Interval(1, 2, closed_L=True) 

1457 intersection = i1.intersection(i2) 

1458 assert intersection == Interval.get_singleton( 

1459 1 

1460 ) # This might fail if adjacent intervals are not handled correctly 

1461 

1462 

1463# Potential bug: Union of overlapping intervals 

1464def test_potential_bug_overlapping_interval_union(): 

1465 i1 = Interval(0, 2) 

1466 i2 = Interval(1, 3) 

1467 union = i1.union(i2) 

1468 assert union == Interval( 

1469 0, 3 

1470 ) # This might fail if overlapping intervals are not handled correctly 

1471 

1472 

1473# Potential bug: Interval containing only infinity 

1474def test_potential_bug_infinity_only_interval(): 

1475 i = Interval(math.inf, math.inf) 

1476 assert ( 

1477 i.is_empty 

1478 ) # This might fail if infinity-only intervals are not handled correctly 

1479 assert ( 

1480 i.size() == 0 

1481 ) # This might fail if the size calculation doesn't account for this case 

1482 

1483 

1484# Potential bug: Interval containing only negative infinity 

1485def test_potential_bug_negative_infinity_only_interval(): 

1486 i = Interval(-math.inf, -math.inf) 

1487 assert ( 

1488 i.is_empty 

1489 ) # This might fail if negative infinity-only intervals are not handled correctly 

1490 assert ( 

1491 i.size() == 0 

1492 ) # This might fail if the size calculation doesn't account for this case 

1493 

1494 

1495# Potential bug: Clamp with infinity 

1496def test_potential_bug_clamp_with_infinity(): 

1497 i = Interval(0, 1) 

1498 assert math.isclose(i.clamp(math.inf), 1) 

1499 assert i.clamp(math.inf) < 1 

1500 print(i.clamp(-math.inf)) 

1501 assert math.isclose(i.clamp(-math.inf), 0, rel_tol=1e-6, abs_tol=1e-6) 

1502 assert i.clamp(-math.inf) > 0 

1503 

1504 

1505# Potential bug: Intersection of intervals with different types 

1506def test_potential_bug_intersection_different_types(): 

1507 i1 = Interval(0, 1) 

1508 i2 = ClosedInterval(0.5, 1.5) 

1509 intersection = i1.intersection(i2) 

1510 assert intersection == Interval( 

1511 0.5, 1, closed_L=True 

1512 ) # This might fail if mixing interval types is not handled correctly 

1513 

1514 

1515# Potential bug: Union of intervals with different types 

1516def test_potential_bug_union_different_types(): 

1517 i1 = Interval(0, 1) 

1518 i2 = ClosedInterval(0.5, 1.5) 

1519 union = i1.union(i2) 

1520 assert union == Interval( 

1521 0, 1.5, closed_R=True 

1522 ) # This might fail if mixing interval types is not handled correctly 

1523 

1524 

1525# Potential bug: Interval bounds very close to each other 

1526def test_potential_bug_very_close_bounds(): 

1527 i = Interval(1, 1 + sys.float_info.epsilon) 

1528 assert ( 

1529 not i.is_empty 

1530 ) # This might fail if very close bounds are not handled correctly 

1531 assert ( 

1532 i.size() > 0 

1533 ) # This might fail if size calculation doesn't account for very close bounds 

1534 

1535 

1536# Potential bug: Interval from string with excessive precision 

1537def test_potential_bug_excessive_precision_from_string(): 

1538 s = "(0.1234567890123456, 0.1234567890123457)" 

1539 i = Interval.from_str(s) 

1540 assert ( 

1541 str(i) == s 

1542 ) # This might fail if string conversion doesn't preserve precision 

1543 

1544 

1545# Potential bug: Interval with bounds that are not exactly representable in binary 

1546def test_potential_bug_non_binary_representable_bounds(): 

1547 i = Interval(0.1, 0.3) 

1548 assert 0.2 in i # This might fail due to binary representation issues 

1549 

1550 

1551# Potential bug: Clamp with value very close to bound 

1552def test_potential_bug_clamp_near_bound(): 

1553 i = Interval(0, 1) 

1554 result = i.clamp(1 - sys.float_info.epsilon) 

1555 assert ( 

1556 result < 1 

1557 ) # This might fail if clamping near bounds is not handled carefully 

1558 

1559 

1560# Potential bug: Empty interval intersection with itself 

1561def test_potential_bug_empty_self_intersection(): 

1562 e = Interval.get_empty() 

1563 assert e.intersection(e) == e # This should pass, but worth checking 

1564 

1565 

1566# Potential bug: Empty interval union with itself 

1567def test_potential_bug_empty_self_union(): 

1568 e = Interval.get_empty() 

1569 assert e.union(e) == e # This should pass, but worth checking 

1570 

1571 

1572# Potential bug: Interval containing only NaN 

1573def test_potential_bug_nan_interval(): 

1574 i = Interval(float("nan"), float("nan")) 

1575 assert i.is_empty 

1576 assert 0 not in i 

1577 

1578 

1579# Potential bug: Interval with reversed bounds after float operation 

1580def test_potential_bug_reversed_bounds_after_operation(): 

1581 i = Interval(1, 1 + 1e-15) 

1582 new_lower = i.lower + 1e-16 

1583 new_upper = i.upper - 1e-16 

1584 assert new_lower <= new_upper # This might fail due to float imprecision 

1585 

1586 

1587# Potential bug: Interval size calculation with small numbers 

1588def test_potential_bug_small_number_size(): 

1589 i = Interval(1e-300, 1e-300 + 1e-310) 

1590 assert math.isclose( 

1591 i.size(), 1e-310, rel_tol=1e-6 

1592 ) # This might fail due to float imprecision with small numbers 

1593 

1594 

1595# Potential bug: Clamp with large epsilon 

1596def test_potential_bug_clamp_large_epsilon(): 

1597 i = Interval(0, 1) 

1598 with pytest.raises(ValueError): 

1599 i.clamp(2, epsilon=10) 

1600 

1601 

1602# Potential bug: Intersection of intervals with shared bound 

1603def test_potential_bug_intersection_shared_bound(): 

1604 i1 = Interval(0, 1, closed_R=True) 

1605 i2 = Interval(1, 2, closed_L=True) 

1606 intersection = i1.intersection(i2) 

1607 assert intersection == Interval.get_singleton( 

1608 1 

1609 ) # This might fail if shared bounds are not handled correctly 

1610 

1611 

1612# Potential bug: Union of intervals with shared bound 

1613def test_potential_bug_union_shared_bound(): 

1614 i1 = Interval(0, 1, closed_R=True) 

1615 i2 = Interval(1, 2, closed_L=True) 

1616 union = i1.union(i2) 

1617 assert union == Interval( 

1618 0, 2 

1619 ) # This might fail if shared bounds are not handled correctly 

1620 

1621 

1622# Potential bug: Interval containing only zero 

1623def test_potential_bug_zero_only_interval(): 

1624 i = Interval(0, 0) 

1625 assert ( 

1626 i.is_empty 

1627 ) # This might fail if zero-only intervals are not handled correctly 

1628 assert ( 

1629 i.size() == 0 

1630 ) # This might fail if the size calculation doesn't account for this case 

1631 

1632 

1633# Potential bug: Interval with custom numeric types 

1634def test_potential_bug_custom_numeric_types(): 

1635 from decimal import Decimal 

1636 

1637 i = Interval(Decimal("0.1"), Decimal("0.3")) # type: ignore[arg-type] 

1638 assert ( 

1639 Decimal("0.2") in i 

1640 ) # This might fail if custom numeric types are not handled correctly 

1641 

1642 

1643# Potential bug: Clamp with value equal to bound 

1644def test_potential_bug_clamp_equal_to_bound(): 

1645 i = Interval(0, 1) 

1646 assert i.clamp(0) > 0 

1647 assert i.clamp(1) < 1 

1648 

1649 

1650# Potential bug: Interval containing only infinity with closed bounds 

1651def test_potential_bug_closed_infinity_interval(): 

1652 i = ClosedInterval(math.inf, math.inf) 

1653 assert ( 

1654 i.is_singleton 

1655 ) # This might fail if closed infinity-only intervals are not handled correctly 

1656 assert ( 

1657 math.inf in i 

1658 ) # This might fail if membership of infinity in closed intervals is not handled correctly 

1659 

1660 

1661# Potential bug: Interval spanning entire float range 

1662def test_potential_bug_full_float_range(): 

1663 i = Interval(float("-inf"), float("inf")) 

1664 assert sys.float_info.max in i 

1665 assert sys.float_info.min in i 

1666 assert 0.0 in i 

1667 assert -0.0 in i 

1668 

1669 

1670# Potential bug: Interval comparison with different types 

1671def test_potential_bug_comparison_different_types(): 

1672 i1 = Interval(0, 1) 

1673 i2 = ClosedInterval(0, 1) 

1674 assert ( 

1675 i1 != i2 

1676 ) # This might fail if comparison between different interval types is not handled correctly 

1677 

1678 

1679# Potential bug: Interval with bounds differing only in sign (0.0 vs -0.0) 

1680def test_potential_bug_zero_sign_difference(): 

1681 i1 = Interval(0.0, 1.0) 

1682 i2 = Interval(-0.0, 1.0) 

1683 assert i1 == i2 # This might fail if signed zero is not handled correctly 

1684 

1685 

1686# Potential bug: Clamp with NaN epsilon 

1687def test_potential_bug_clamp_nan_epsilon(): 

1688 i = Interval(0, 1) 

1689 with pytest.raises(ValueError): 

1690 i.clamp(0.5, epsilon=float("nan")) 

1691 

1692 

1693# Potential bug: Interval containing only NaN with closed bounds 

1694def test_potential_bug_closed_inf(): 

1695 i1 = ClosedInterval.get_singleton(float("inf")) 

1696 i2 = Interval.get_singleton(float("inf")) 

1697 i3 = ClosedInterval(-float("inf"), float("inf")) 

1698 i4 = Interval(-float("inf"), float("inf")) 

1699 i5 = ClosedInterval(float("inf"), float("inf")) 

1700 i6 = Interval(float("inf"), float("inf")) 

1701 

1702 assert i1.is_singleton 

1703 assert i2.is_singleton 

1704 assert not i3.is_singleton 

1705 assert not i4.is_singleton 

1706 assert not i3.is_empty 

1707 assert not i4.is_empty 

1708 assert i5.is_singleton 

1709 assert i6.is_empty 

1710 

1711 assert i1.clamp(1) == float("inf") 

1712 assert i2.clamp(1) == float("inf") 

1713 assert i3.clamp(1) == 1 

1714 assert i4.clamp(1) == 1 

1715 

1716 

1717# Potential bug: Intersection of universal set with itself 

1718def test_potential_bug_universal_set_self_intersection(): 

1719 u = Interval(float("-inf"), float("inf")) 

1720 assert u.intersection(u) == u 

1721 

1722 

1723# Potential bug: Union of universal set with any other set 

1724def test_potential_bug_universal_set_union(): 

1725 u = Interval(float("-inf"), float("inf")) 

1726 i = Interval(0, 1) 

1727 assert u.union(i) == u 

1728 

1729 

1730# Potential bug: Clamp with integer bounds and float value 

1731def test_potential_bug_clamp_integer_bounds_float_value(): 

1732 i = Interval(0, 1) 

1733 result = i.clamp(0.5) 

1734 assert isinstance( 

1735 result, float 

1736 ) # This might fail if type consistency is not maintained 

1737 

1738 

1739# Potential bug: Interval from string with scientific notation 

1740def test_potential_bug_interval_from_scientific_notation(): 

1741 i = Interval.from_str("[1e-10, 1e10]") 

1742 assert i.lower == 1e-10 

1743 assert i.upper == 1e10 

1744 

1745 

1746# Potential bug: Interval with repeated float operations leading to unexpected results 

1747def test_potential_bug_repeated_float_operations(): 

1748 i = Interval(0, 1) 

1749 for _ in range(1000): 

1750 i = Interval(i.lower + sys.float_info.epsilon, i.upper - sys.float_info.epsilon) 

1751 assert 0.5 in i # This might fail due to accumulated float imprecision 

1752 

1753 

1754# Potential bug: Interval size with subnormal numbers 

1755def test_potential_bug_subnormal_size(): 

1756 tiny = sys.float_info.min * sys.float_info.epsilon 

1757 i = Interval(tiny, tiny * 2) 

1758 assert ( 

1759 0 < i.size() < sys.float_info.min 

1760 ) # This might fail if subnormal numbers are not handled correctly in size calculation 

1761 

1762 

1763# Potential bug: Clamp with subnormal epsilon 

1764def test_potential_bug_clamp_subnormal_epsilon(): 

1765 i = Interval(0, 1) 

1766 tiny_epsilon = sys.float_info.min * sys.float_info.epsilon 

1767 result = i.clamp(-1, epsilon=tiny_epsilon) 

1768 assert result > 0 # This might fail if subnormal epsilons are not handled correctly 

1769 

1770 

1771# Potential bug: Interval containing maximum and minimum floats 

1772def test_potential_bug_max_min_float_interval(): 

1773 i = Interval(sys.float_info.min, sys.float_info.max) 

1774 assert 1.0 in i 

1775 assert -1.0 not in i 

1776 assert i.size() == sys.float_info.max - sys.float_info.min 

1777 

1778 

1779# Potential bug: Interval with bounds very close to zero 

1780def test_potential_bug_near_zero_bounds(): 

1781 epsilon = sys.float_info.epsilon 

1782 i = Interval(-epsilon, epsilon) 

1783 assert 0 in i 

1784 assert i.size() == 2 * epsilon 

1785 

1786 

1787# Potential bug: Clamp with value very close to infinity 

1788def test_potential_bug_clamp_near_infinity(): 

1789 i = ClosedInterval(0, 1) 

1790 very_large = sys.float_info.max * 0.99 

1791 result = i.clamp(very_large) 

1792 assert ( 

1793 result == 1 

1794 ) # This might fail if values near infinity are not handled correctly in clamp