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

899 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-02-05 19:24 -0700

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

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 Interval(1, 2).intersection(5) # Invalid type for intersection 

725 

726 with pytest.raises(TypeError): 

727 Interval(1, 2).union("invalid") # Invalid type for union 

728 

729 

730@pytest.mark.parametrize( 

731 "invalid_string", 

732 [ 

733 "1, 5", # Missing brackets 

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

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

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

737 ], 

738) 

739def test_from_str_errors(invalid_string): 

740 with pytest.raises(ValueError): 

741 Interval.from_str(invalid_string) 

742 

743 

744def test_interval_repr(): 

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

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

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

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

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

750 

751 

752def test_interval_from_str_with_whitespace(): 

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

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

755 

756 

757def test_interval_from_str_with_scientific_notation(): 

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

759 

760 

761def test_interval_clamp_with_custom_epsilon(): 

762 i = Interval(0, 1) 

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

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

765 

766 

767def test_interval_size_with_small_values(): 

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

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

770 

771 

772def test_interval_intersection_edge_cases(): 

773 i1 = Interval(1, 2) 

774 i2 = Interval(2, 3) 

775 i3 = ClosedInterval(2, 3) 

776 

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

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

779 

780 

781def test_interval_union_edge_cases(): 

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

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

784 i3 = Interval(3, 4) 

785 

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

787 with pytest.raises(NotImplementedError): 

788 i1.union(i3) 

789 

790 

791def test_interval_contains_with_epsilon(): 

792 i = OpenInterval(0, 1) 

793 assert 0 + _EPSILON in i 

794 assert 1 - _EPSILON in i 

795 assert 0 not in i 

796 assert 1 not in i 

797 

798 

799def test_singleton_creation(): 

800 s = Interval.get_singleton(5) 

801 assert s.is_singleton 

802 assert s.singleton == 5 

803 assert len(s) == 1 

804 assert s.lower == s.upper == 5 

805 assert s.closed_L and s.closed_R 

806 

807 

808def test_empty_creation(): 

809 e = Interval.get_empty() 

810 assert e.is_empty 

811 assert len(e) == 0 

812 with pytest.raises(ValueError): 

813 _ = e.singleton 

814 

815 

816def test_singleton_properties(): 

817 s = Interval.get_singleton(3.14) 

818 assert s.is_singleton 

819 assert not s.is_empty 

820 assert s.is_finite 

821 assert s.is_closed 

822 assert not s.is_open 

823 assert not s.is_half_open 

824 

825 

826def test_empty_properties(): 

827 e = Interval.get_empty() 

828 assert e.is_empty 

829 assert not e.is_singleton 

830 assert e.is_finite 

831 assert e.is_closed 

832 assert e.is_open 

833 assert not e.is_half_open 

834 

835 

836def test_singleton_containment(): 

837 s = Interval.get_singleton(5) 

838 assert 5 in s 

839 assert 5.0 in s 

840 assert 4.999999999999999 not in s 

841 assert 5.000000000000001 not in s 

842 assert Interval.get_singleton(5) in s 

843 assert Interval(4, 6) not in s 

844 assert s in Interval(4, 6) 

845 

846 

847def test_empty_containment(): 

848 e = Interval.get_empty() 

849 assert 0 not in e 

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

851 assert Interval.get_empty() in e 

852 

853 

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

855def test_singleton_various_values(value): 

856 s = Interval.get_singleton(value) 

857 assert s.is_singleton 

858 assert s.singleton == value 

859 assert value in s 

860 

861 

862def test_singleton_nan(): 

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

864 

865 

866def test_singleton_operations(): 

867 s = Interval.get_singleton(5) 

868 assert s.size() == 0 

869 assert s.clamp(3) == 5 

870 assert s.clamp(7) == 5 

871 

872 

873def test_empty_operations(): 

874 e = Interval.get_empty() 

875 assert e.size() == 0 

876 with pytest.raises(ValueError): 

877 e.clamp(3) 

878 

879 

880def test_singleton_intersection(): 

881 s = Interval.get_singleton(5) 

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

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

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

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

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

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

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

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

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

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

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

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

894 

895 

896def test_empty_intersection(): 

897 e = Interval.get_empty() 

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

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

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

901 

902 

903def test_singleton_union(): 

904 s = Interval.get_singleton(5) 

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

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

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

908 with pytest.raises(NotImplementedError): 

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

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

911 with pytest.raises(NotImplementedError): 

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

913 

914 

915def test_empty_union(): 

916 e = Interval.get_empty() 

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

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

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

920 

921 

922def test_singleton_equality(): 

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

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

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

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

927 

928 

929def test_empty_equality(): 

930 assert Interval.get_empty() == Interval.get_empty() 

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

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

933 

934 

935def test_singleton_representation(): 

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

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

938 

939 

940def test_empty_representation(): 

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

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

943 

944 

945def test_singleton_from_str(): 

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

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

948 

949 

950def test_empty_from_str(): 

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

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

953 

954 

955def test_singleton_iteration(): 

956 s = Interval.get_singleton(5) 

957 assert list(s) == [5] 

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

959 

960 

961def test_empty_iteration(): 

962 e = Interval.get_empty() 

963 assert list(e) == [] 

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

965 

966 

967def test_singleton_indexing(): 

968 s = Interval.get_singleton(5) 

969 assert s[0] == 5 

970 with pytest.raises(IndexError): 

971 _ = s[1] 

972 

973 

974def test_empty_indexing(): 

975 e = Interval.get_empty() 

976 with pytest.raises(IndexError): 

977 _ = e[0] 

978 

979 

980def test_singleton_bool(): 

981 assert bool(Interval.get_singleton(5)) 

982 assert bool(Interval.get_singleton(0)) 

983 

984 

985def test_empty_bool(): 

986 assert not bool(Interval.get_empty()) 

987 

988 

989def test_singleton_infinity(): 

990 inf_singleton = Interval.get_singleton(math.inf) 

991 assert inf_singleton.is_singleton 

992 assert not inf_singleton.is_finite 

993 assert math.inf in inf_singleton 

994 assert math.inf - 1 in inf_singleton 

995 

996 

997def test_mixed_operations(): 

998 s = Interval.get_singleton(5) 

999 e = Interval.get_empty() 

1000 i = Interval(0, 10) 

1001 

1002 assert s.intersection(e) == e 

1003 assert e.intersection(s) == e 

1004 assert i.intersection(s) == s 

1005 assert i.intersection(e) == e 

1006 

1007 assert s.union(e) == s 

1008 assert e.union(s) == s 

1009 assert i.union(s) == i 

1010 assert i.union(e) == i 

1011 

1012 

1013def test_edge_case_conversions(): 

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

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

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

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

1018 

1019 

1020def test_nan_handling_is_empty(): 

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

1022 assert Interval(None, None).is_empty 

1023 assert Interval().is_empty 

1024 assert Interval.get_empty().is_empty 

1025 with pytest.raises(ValueError): 

1026 Interval(math.nan, 5) 

1027 with pytest.raises(ValueError): 

1028 assert Interval(5, math.nan) 

1029 

1030 

1031def test_infinity_edge_cases(): 

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

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

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

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

1036 

1037 

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

1039def test_potential_bug_infinity_singleton(): 

1040 inf_singleton = Interval.get_singleton(math.inf) 

1041 assert math.isinf(inf_singleton.singleton) 

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

1043 

1044 

1045# Potential bug: Epsilon handling with singletons 

1046def test_potential_bug_singleton_epsilon(): 

1047 s = Interval.get_singleton(5) 

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

1049 

1050 

1051# Potential bug: Empty set comparison 

1052def test_potential_bug_empty_comparison(): 

1053 e1 = Interval.get_empty() 

1054 e2 = OpenInterval(5, 5) 

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

1056 

1057 

1058# Potential bug: Singleton at zero 

1059def test_potential_bug_zero_singleton(): 

1060 zero_singleton = Interval.get_singleton(0) 

1061 assert 0 in zero_singleton 

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

1063 

1064 

1065# Potential bug: Empty set size 

1066def test_potential_bug_empty_set_size(): 

1067 e = Interval.get_empty() 

1068 assert ( 

1069 e.size() == 0 

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

1071 

1072 

1073@pytest.mark.parametrize( 

1074 "interval", 

1075 [ 

1076 # Empty set 

1077 Interval.get_empty(), 

1078 # Singletons 

1079 Interval.get_singleton(0), 

1080 Interval.get_singleton(5), 

1081 Interval.get_singleton(-3), 

1082 Interval.get_singleton(3.14), 

1083 Interval.get_singleton(-2.718), 

1084 # Regular intervals 

1085 Interval(0, 1), 

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

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

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

1089 OpenInterval(-5, 5), 

1090 ClosedInterval(-5, 5), 

1091 # Intervals with infinities 

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

1093 Interval(-math.inf, 0), 

1094 Interval(0, math.inf), 

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

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

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

1098 # Mixed finite and infinite bounds 

1099 Interval(-math.inf, 5), 

1100 Interval(-5, math.inf), 

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

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

1103 # Very large and very small numbers 

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

1105 ClosedInterval(-1e50, 1e50), 

1106 # Intervals with non-integer bounds 

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

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

1109 ], 

1110) 

1111def test_interval_string_round_trip(interval): 

1112 # Convert interval to string 

1113 interval_str = str(interval) 

1114 

1115 # Parse string back to interval 

1116 parsed_interval = Interval.from_str(interval_str) 

1117 

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

1119 assert ( 

1120 parsed_interval == interval 

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

1122 

1123 # Additional check for string representation consistency 

1124 assert ( 

1125 str(parsed_interval) == interval_str 

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

1127 

1128 

1129def test_empty_set_string_representations(): 

1130 empty = Interval.get_empty() 

1131 assert str(empty) == "∅" 

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

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

1134 

1135 

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

1137def test_singleton_string_representations(value): 

1138 singleton = Interval.get_singleton(value) 

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

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

1141 

1142 

1143def test_infinity_string_representations(): 

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

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

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

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

1148 

1149 

1150def test_mixed_closure_string_representations(): 

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

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

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

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

1155 

1156 

1157@pytest.mark.parametrize( 

1158 "string_repr", 

1159 [ 

1160 "(0, 1)", 

1161 "[0, 1]", 

1162 "(0, 1]", 

1163 "[0, 1)", 

1164 "(-inf, inf)", 

1165 "[0, inf)", 

1166 "(-inf, 0]", 

1167 "{2.5}", 

1168 "∅", 

1169 ], 

1170) 

1171def test_string_parsing_consistency(string_repr): 

1172 parsed_interval = Interval.from_str(string_repr) 

1173 assert ( 

1174 str(parsed_interval) == string_repr 

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

1176 

1177 

1178def test_string_parsing_edge_cases(): 

1179 with pytest.raises(ValueError): 

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

1181 

1182 with pytest.raises(ValueError): 

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

1184 

1185 with pytest.raises(ValueError): 

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

1187 

1188 

1189def test_string_representation_precision(): 

1190 # Test that string representation maintains sufficient precision 

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

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

1193 assert small_interval == parsed_small_interval 

1194 assert small_interval.lower == parsed_small_interval.lower 

1195 assert small_interval.upper == parsed_small_interval.upper 

1196 

1197 

1198def test_string_representation_with_scientific_notation(): 

1199 large_interval = Interval(1e100, 2e100) 

1200 large_interval_str = str(large_interval) 

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

1202 parsed_large_interval = Interval.from_str(large_interval_str) 

1203 assert large_interval == parsed_large_interval 

1204 

1205 

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

1207def test_potential_bug_negative_zero(): 

1208 zero_singleton = Interval.get_singleton(-0.0) 

1209 zero_singleton_str = str(zero_singleton) 

1210 assert Interval.from_str(zero_singleton_str) == zero_singleton 

1211 assert ( 

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

1213 ) # Should this be -0.0 or 0.0? 

1214 

1215 i = Interval(-1, 1) 

1216 assert 0.0 in i 

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

1218 

1219 

1220# Potential bug: Precision loss in string representation 

1221def test_potential_bug_precision_loss(): 

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

1223 precise_interval_str = str(precise_interval) 

1224 parsed_precise_interval = Interval.from_str(precise_interval_str) 

1225 assert precise_interval == parsed_precise_interval 

1226 assert precise_interval.lower == parsed_precise_interval.lower 

1227 assert precise_interval.upper == parsed_precise_interval.upper 

1228 

1229 

1230def test_interval_with_very_close_bounds(): 

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

1232 assert i.size() > 0 

1233 assert 1 + _EPSILON in i 

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

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

1236 

1237 

1238def test_interval_with_bounds_closer_than_epsilon(): 

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

1240 assert i.size() > 0 

1241 with pytest.raises(ValueError): 

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

1243 

1244 

1245def test_interval_with_extremely_large_bounds(): 

1246 i = Interval(1e300, 1e301) 

1247 assert 5e300 in i 

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

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

1250 

1251 

1252def test_interval_with_mixed_infinities(): 

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

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

1255 assert 0 in i 

1256 assert math.inf not in i 

1257 assert -math.inf not in i 

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

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

1260 

1261 

1262def test_interval_with_one_infinity(): 

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

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

1265 assert -1e300 in i1 

1266 assert 0 not in i1 

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

1268 

1269 i2 = Interval(0, math.inf) 

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

1271 assert 1e300 in i2 

1272 assert 0 not in i2 

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

1274 

1275 

1276def test_interval_singleton_edge_cases(): 

1277 s = Interval.get_singleton(0) 

1278 assert 0 in s 

1279 assert -0.0 in s 

1280 assert 1e-16 not in s 

1281 assert -1e-16 not in s 

1282 

1283 

1284def test_interval_empty_edge_cases(): 

1285 e = Interval.get_empty() 

1286 assert e.size() == 0 

1287 assert e.is_open and e.is_closed 

1288 assert Interval.get_empty() in e 

1289 assert e in Interval(0, 1) 

1290 

1291 

1292def test_interval_from_str_edge_cases(): 

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

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

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

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

1297 

1298 

1299def test_interval_arithmetic_with_empty_intervals(): 

1300 e = Interval.get_empty() 

1301 i = Interval(0, 1) 

1302 assert e.intersection(i) == e 

1303 assert i.intersection(e) == e 

1304 assert e.union(i) == i 

1305 assert i.union(e) == i 

1306 

1307 

1308def test_interval_arithmetic_with_singletons(): 

1309 s = Interval.get_singleton(1) 

1310 i = Interval(0, 2) 

1311 assert s.intersection(i) == s 

1312 assert i.intersection(s) == s 

1313 assert s.union(i) == i 

1314 assert i.union(s) == i 

1315 

1316 

1317def test_interval_precision_near_bounds(): 

1318 i = Interval(1, 2) 

1319 assert 1 + _EPSILON / 2 in i 

1320 assert 2 - _EPSILON / 2 in i 

1321 assert 1 - _EPSILON / 2 not in i 

1322 assert 2 + _EPSILON / 2 not in i 

1323 

1324 

1325def test_interval_serialization_precision(): 

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

1327 serialized = str(i) 

1328 deserialized = Interval.from_str(serialized) 

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

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

1331 

1332 

1333def test_interval_with_repeated_float_operations(): 

1334 i = Interval(0, 1) 

1335 for _ in range(1000): 

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

1337 assert i.lower < i.upper 

1338 assert 0.5 in i 

1339 

1340 

1341def test_interval_near_float_precision_limit(): 

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

1343 assert small_interval.size() > 0 

1344 assert 1 + 5e-16 in small_interval 

1345 

1346 

1347def test_interval_with_irrational_bounds(): 

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

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

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

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

1352 assert 3 in i 

1353 

1354 

1355def test_interval_commutativity_of_operations(): 

1356 i1 = Interval(1, 3) 

1357 i2 = Interval(2, 4) 

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

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

1360 

1361 

1362def test_interval_associativity_of_operations(): 

1363 i1 = Interval(1, 4) 

1364 i2 = Interval(2, 5) 

1365 i3 = Interval(3, 6) 

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

1367 i2.intersection(i3) 

1368 ) 

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

1370 

1371 

1372def test_interval_distributivity_of_operations(): 

1373 i1 = Interval(1, 3) 

1374 i2 = Interval(2, 4) 

1375 i3 = Interval(3, 5) 

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

1377 i1.intersection(i3) 

1378 ) 

1379 

1380 

1381def test_interval_comparison(): 

1382 i1 = Interval(1, 3) 

1383 i2 = Interval(2, 4) 

1384 i3 = Interval(1, 3) 

1385 assert i1 != i2 

1386 assert i1 == i3 

1387 with pytest.raises(TypeError): 

1388 i1 < i2 

1389 

1390 

1391def test_interval_copy(): 

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

1393 i_copy = i.copy() 

1394 assert i == i_copy 

1395 assert i is not i_copy 

1396 

1397 

1398def test_interval_pickling(): 

1399 import pickle 

1400 

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

1402 pickled = pickle.dumps(i) 

1403 unpickled = pickle.loads(pickled) 

1404 assert i == unpickled 

1405 

1406 

1407def test_interval_with_numpy_types(): 

1408 import numpy as np 

1409 

1410 i = Interval(np.float64(1), np.float64(2)) 

1411 assert np.float64(1.5) in i 

1412 assert isinstance(i.clamp(np.float64(0)), np.float64) 

1413 

1414 

1415def test_interval_with_decimal_types(): 

1416 from decimal import Decimal 

1417 

1418 i = Interval(Decimal("1"), Decimal("2")) 

1419 assert Decimal("1.5") in i 

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

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

1422 assert isinstance(i.clamp(Decimal("0")), Decimal) 

1423 

1424 

1425def test_interval_with_fractions(): 

1426 from fractions import Fraction 

1427 

1428 i = Interval(Fraction(1, 3), Fraction(2, 3)) 

1429 assert Fraction(1, 2) in i 

1430 assert isinstance(i.clamp(Fraction(0, 1)), Fraction) 

1431 

1432 

1433# Potential bug: Infinity comparisons 

1434def test_potential_bug_infinity_comparisons(): 

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

1436 assert ( 

1437 math.inf not in i 

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

1439 

1440 

1441# Potential bug: NaN handling 

1442def test_potential_bug_nan_handling(): 

1443 with pytest.raises(ValueError): 

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

1445 

1446 i = Interval(0, 1) 

1447 with pytest.raises(ValueError): 

1448 float("nan") in i 

1449 

1450 

1451# Potential bug: Intersection of adjacent intervals 

1452def test_potential_bug_adjacent_interval_intersection(): 

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

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

1455 intersection = i1.intersection(i2) 

1456 assert intersection == Interval.get_singleton( 

1457 1 

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

1459 

1460 

1461# Potential bug: Union of overlapping intervals 

1462def test_potential_bug_overlapping_interval_union(): 

1463 i1 = Interval(0, 2) 

1464 i2 = Interval(1, 3) 

1465 union = i1.union(i2) 

1466 assert union == Interval( 

1467 0, 3 

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

1469 

1470 

1471# Potential bug: Interval containing only infinity 

1472def test_potential_bug_infinity_only_interval(): 

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

1474 assert ( 

1475 i.is_empty 

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

1477 assert ( 

1478 i.size() == 0 

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

1480 

1481 

1482# Potential bug: Interval containing only negative infinity 

1483def test_potential_bug_negative_infinity_only_interval(): 

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

1485 assert ( 

1486 i.is_empty 

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

1488 assert ( 

1489 i.size() == 0 

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

1491 

1492 

1493# Potential bug: Clamp with infinity 

1494def test_potential_bug_clamp_with_infinity(): 

1495 i = Interval(0, 1) 

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

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

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

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

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

1501 

1502 

1503# Potential bug: Intersection of intervals with different types 

1504def test_potential_bug_intersection_different_types(): 

1505 i1 = Interval(0, 1) 

1506 i2 = ClosedInterval(0.5, 1.5) 

1507 intersection = i1.intersection(i2) 

1508 assert intersection == Interval( 

1509 0.5, 1, closed_L=True 

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

1511 

1512 

1513# Potential bug: Union of intervals with different types 

1514def test_potential_bug_union_different_types(): 

1515 i1 = Interval(0, 1) 

1516 i2 = ClosedInterval(0.5, 1.5) 

1517 union = i1.union(i2) 

1518 assert union == Interval( 

1519 0, 1.5, closed_R=True 

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

1521 

1522 

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

1524def test_potential_bug_very_close_bounds(): 

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

1526 assert ( 

1527 not i.is_empty 

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

1529 assert ( 

1530 i.size() > 0 

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

1532 

1533 

1534# Potential bug: Interval from string with excessive precision 

1535def test_potential_bug_excessive_precision_from_string(): 

1536 s = "(0.1234567890123456, 0.1234567890123457)" 

1537 i = Interval.from_str(s) 

1538 assert ( 

1539 str(i) == s 

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

1541 

1542 

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

1544def test_potential_bug_non_binary_representable_bounds(): 

1545 i = Interval(0.1, 0.3) 

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

1547 

1548 

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

1550def test_potential_bug_clamp_near_bound(): 

1551 i = Interval(0, 1) 

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

1553 assert ( 

1554 result < 1 

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

1556 

1557 

1558# Potential bug: Empty interval intersection with itself 

1559def test_potential_bug_empty_self_intersection(): 

1560 e = Interval.get_empty() 

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

1562 

1563 

1564# Potential bug: Empty interval union with itself 

1565def test_potential_bug_empty_self_union(): 

1566 e = Interval.get_empty() 

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

1568 

1569 

1570# Potential bug: Interval containing only NaN 

1571def test_potential_bug_nan_interval(): 

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

1573 assert i.is_empty 

1574 assert 0 not in i 

1575 

1576 

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

1578def test_potential_bug_reversed_bounds_after_operation(): 

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

1580 new_lower = i.lower + 1e-16 

1581 new_upper = i.upper - 1e-16 

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

1583 

1584 

1585# Potential bug: Interval size calculation with small numbers 

1586def test_potential_bug_small_number_size(): 

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

1588 assert math.isclose( 

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

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

1591 

1592 

1593# Potential bug: Clamp with large epsilon 

1594def test_potential_bug_clamp_large_epsilon(): 

1595 i = Interval(0, 1) 

1596 with pytest.raises(ValueError): 

1597 i.clamp(2, epsilon=10) 

1598 

1599 

1600# Potential bug: Intersection of intervals with shared bound 

1601def test_potential_bug_intersection_shared_bound(): 

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

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

1604 intersection = i1.intersection(i2) 

1605 assert intersection == Interval.get_singleton( 

1606 1 

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

1608 

1609 

1610# Potential bug: Union of intervals with shared bound 

1611def test_potential_bug_union_shared_bound(): 

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

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

1614 union = i1.union(i2) 

1615 assert union == Interval( 

1616 0, 2 

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

1618 

1619 

1620# Potential bug: Interval containing only zero 

1621def test_potential_bug_zero_only_interval(): 

1622 i = Interval(0, 0) 

1623 assert ( 

1624 i.is_empty 

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

1626 assert ( 

1627 i.size() == 0 

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

1629 

1630 

1631# Potential bug: Interval with custom numeric types 

1632def test_potential_bug_custom_numeric_types(): 

1633 from decimal import Decimal 

1634 

1635 i = Interval(Decimal("0.1"), Decimal("0.3")) 

1636 assert ( 

1637 Decimal("0.2") in i 

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

1639 

1640 

1641# Potential bug: Clamp with value equal to bound 

1642def test_potential_bug_clamp_equal_to_bound(): 

1643 i = Interval(0, 1) 

1644 assert i.clamp(0) > 0 

1645 assert i.clamp(1) < 1 

1646 

1647 

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

1649def test_potential_bug_closed_infinity_interval(): 

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

1651 assert ( 

1652 i.is_singleton 

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

1654 assert ( 

1655 math.inf in i 

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

1657 

1658 

1659# Potential bug: Interval spanning entire float range 

1660def test_potential_bug_full_float_range(): 

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

1662 assert sys.float_info.max in i 

1663 assert sys.float_info.min in i 

1664 assert 0.0 in i 

1665 assert -0.0 in i 

1666 

1667 

1668# Potential bug: Interval comparison with different types 

1669def test_potential_bug_comparison_different_types(): 

1670 i1 = Interval(0, 1) 

1671 i2 = ClosedInterval(0, 1) 

1672 assert ( 

1673 i1 != i2 

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

1675 

1676 

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

1678def test_potential_bug_zero_sign_difference(): 

1679 i1 = Interval(0.0, 1.0) 

1680 i2 = Interval(-0.0, 1.0) 

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

1682 

1683 

1684# Potential bug: Clamp with NaN epsilon 

1685def test_potential_bug_clamp_nan_epsilon(): 

1686 i = Interval(0, 1) 

1687 with pytest.raises(ValueError): 

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

1689 

1690 

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

1692def test_potential_bug_closed_inf(): 

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

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

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

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

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

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

1699 

1700 assert i1.is_singleton 

1701 assert i2.is_singleton 

1702 assert not i3.is_singleton 

1703 assert not i4.is_singleton 

1704 assert not i3.is_empty 

1705 assert not i4.is_empty 

1706 assert i5.is_singleton 

1707 assert i6.is_empty 

1708 

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

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

1711 assert i3.clamp(1) == 1 

1712 assert i4.clamp(1) == 1 

1713 

1714 

1715# Potential bug: Intersection of universal set with itself 

1716def test_potential_bug_universal_set_self_intersection(): 

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

1718 assert u.intersection(u) == u 

1719 

1720 

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

1722def test_potential_bug_universal_set_union(): 

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

1724 i = Interval(0, 1) 

1725 assert u.union(i) == u 

1726 

1727 

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

1729def test_potential_bug_clamp_integer_bounds_float_value(): 

1730 i = Interval(0, 1) 

1731 result = i.clamp(0.5) 

1732 assert isinstance( 

1733 result, float 

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

1735 

1736 

1737# Potential bug: Interval from string with scientific notation 

1738def test_potential_bug_interval_from_scientific_notation(): 

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

1740 assert i.lower == 1e-10 

1741 assert i.upper == 1e10 

1742 

1743 

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

1745def test_potential_bug_repeated_float_operations(): 

1746 i = Interval(0, 1) 

1747 for _ in range(1000): 

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

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

1750 

1751 

1752# Potential bug: Interval size with subnormal numbers 

1753def test_potential_bug_subnormal_size(): 

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

1755 i = Interval(tiny, tiny * 2) 

1756 assert ( 

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

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

1759 

1760 

1761# Potential bug: Clamp with subnormal epsilon 

1762def test_potential_bug_clamp_subnormal_epsilon(): 

1763 i = Interval(0, 1) 

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

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

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

1767 

1768 

1769# Potential bug: Interval containing maximum and minimum floats 

1770def test_potential_bug_max_min_float_interval(): 

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

1772 assert 1.0 in i 

1773 assert -1.0 not in i 

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

1775 

1776 

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

1778def test_potential_bug_near_zero_bounds(): 

1779 epsilon = sys.float_info.epsilon 

1780 i = Interval(-epsilon, epsilon) 

1781 assert 0 in i 

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

1783 

1784 

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

1786def test_potential_bug_clamp_near_infinity(): 

1787 i = ClosedInterval(0, 1) 

1788 very_large = sys.float_info.max * 0.99 

1789 result = i.clamp(very_large) 

1790 assert ( 

1791 result == 1 

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