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

898 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-22 18:25 -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") # type: ignore[arg-type] 

296 with pytest.raises(TypeError): 

297 "a" in Interval(1, 2) # pyright: ignore[reportUnusedExpression] 

298 

299 

300def test_interval_initialization_with_iterables(): 

301 # Test initialization with different iterable types 

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

303 

304 

305def test_clamp_with_infinite_values(): 

306 # Test clamping with infinite values 

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

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

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

310 

311 right_inf = Interval(0, math.inf) 

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

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

314 

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

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

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

318 

319 

320def test_interval_equality_with_different_types(): 

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

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

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

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

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

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

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

328 

329 

330def test_interval_containment_edge_cases(): 

331 i = Interval(0, 1) 

332 assert 0 + _EPSILON / 2 in i 

333 assert 1 - _EPSILON / 2 in i 

334 assert 0 not in i 

335 assert 1 not in i 

336 

337 ci = ClosedInterval(0, 1) 

338 assert 0 in ci 

339 assert 1 in ci 

340 assert 0 - _EPSILON / 2 not in ci 

341 assert 1 + _EPSILON / 2 not in ci 

342 

343 

344def test_clamp_with_custom_epsilon(): 

345 i = Interval(0, 1) 

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

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

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

349 

350 

351def test_interval_with_float_imprecision(): 

352 # Test handling of float imprecision 

353 i = Interval(0.1, 0.2) 

354 assert 0.15 in i 

355 assert 0.1 + 1e-15 in i 

356 assert 0.2 - 1e-15 in i 

357 

358 

359def test_interval_initialization_with_reversed_bounds(): 

360 with pytest.raises(ValueError): 

361 Interval(2, 1) 

362 with pytest.raises(ValueError): 

363 ClosedInterval([5, 3]) 

364 

365 

366def test_interval_initialization_with_equal_bounds(): 

367 i = Interval(1, 1) 

368 assert i == Interval.get_empty() 

369 assert i == Interval() 

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

371 assert 1 not in i 

372 

373 ci = ClosedInterval(1, 1) 

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

375 assert 1 in ci 

376 

377 

378def test_interval_with_only_one_infinite_bound(): 

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

380 assert -1e1000 in left_inf 

381 assert 5 not in left_inf 

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

383 

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

385 assert 1e1000 in right_inf 

386 assert 5 not in right_inf 

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

388 

389 

390@pytest.mark.parametrize( 

391 "container,contained,expected", 

392 [ 

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

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

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

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

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

398 (Interval(1, 5), Interval(1, 5), True), 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

415 ( 

416 Interval(1, 5), 

417 Interval(0, 6), 

418 False, 

419 ), # Contained interval extends beyond container 

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

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

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

423 ( 

424 OpenInterval(1, 5), 

425 ClosedInterval(1, 5), 

426 False, 

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

428 ( 

429 ClosedInterval(1, 5), 

430 OpenInterval(1, 5), 

431 True, 

432 ), # Closed contains open with same bounds 

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

434 ( 

435 Interval(1, 5), 

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

437 False, 

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

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

440 ( 

441 Interval(1, 5), 

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

443 False, 

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

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

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

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

448 ( 

449 OpenInterval(1, 1), 

450 ClosedInterval(1, 1), 

451 False, 

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

453 ( 

454 ClosedInterval(1, 1), 

455 OpenInterval(1, 1), 

456 True, 

457 ), # Point closed contains empty open 

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

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

460 ( 

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

462 Interval(0, 0), 

463 True, 

464 ), # Real line contains any point 

465 ( 

466 Interval(0, 1), 

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

468 False, 

469 ), # Finite doesn't contain infinite 

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

471 ( 

472 ClosedInterval(1, 5), 

473 ClosedInterval(5, 10), 

474 False, 

475 ), # Adjacent closed intervals 

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

477 ( 

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

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

480 False, 

481 ), # Adjacent half-open intervals 

482 ], 

483) 

484def test_interval_containment(container, contained, expected): 

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

486 assert (contained in container) == expected 

487 

488 

489@pytest.mark.parametrize( 

490 "interval_a,interval_b,expected", 

491 [ 

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

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

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

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

496 ( 

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

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

499 True, 

500 ), 

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

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

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

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

505 ], 

506) 

507def test_mixed_interval_types(interval_a, interval_b, expected): 

508 assert (interval_a == interval_b) == expected 

509 

510 

511@pytest.mark.parametrize( 

512 "interval", 

513 [ 

514 Interval(), 

515 Interval(5), 

516 Interval.get_singleton(5), 

517 Interval(1, 5), 

518 ClosedInterval(1, 5), 

519 OpenInterval(1, 5), 

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

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

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

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

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

525 Interval(5.0), 

526 Interval.get_singleton(5.0), 

527 Interval(1.0, 5.0), 

528 ClosedInterval(1.0, 5.0), 

529 OpenInterval(1.0, 5.0), 

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

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

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

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

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

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

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

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

538 ], 

539) 

540def test_string_representation_round_trip(interval): 

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

542 

543 

544@pytest.mark.parametrize( 

545 "string,expected", 

546 [ 

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

548 ("(1, 5)", OpenInterval(1, 5)), 

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

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

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

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

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

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

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

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

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

558 ], 

559) 

560def test_parsing_from_strings(string, expected): 

561 assert Interval.from_str(string) == expected 

562 

563 

564@pytest.mark.parametrize( 

565 "interval,expected_size", 

566 [ 

567 (Interval(1, 5), 4), 

568 (ClosedInterval(1, 5), 4), 

569 (OpenInterval(1, 5), 4), 

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

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

572 (Interval(1, 1), 0), 

573 (ClosedInterval(1, 1), 0), 

574 (Interval(0.1, 0.2), 0.1), 

575 ], 

576) 

577def test_interval_size(interval, expected_size): 

578 assert interval.size() == expected_size 

579 

580 

581@pytest.mark.parametrize( 

582 "interval,value,expected", 

583 [ 

584 (ClosedInterval(1, 5), 0, 1), 

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

586 ], 

587) 

588def test_clamp_mixed_types(interval, value, expected): 

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

590 

591 

592def test_interval_edge_cases(): 

593 # Test with very small intervals 

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

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

596 assert 1 + 0.5e-4 in small 

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

598 assert math.isclose( 

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

600 ) 

601 

602 # Test with intervals smaller than epsilon 

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

604 assert math.isclose(tiny.size(), _EPSILON / 2, rel_tol=1e-9, abs_tol=1e-12), ( 

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

606 ) 

607 with pytest.raises(ValueError): 

608 assert math.isclose( 

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

610 ) 

611 

612 # Test with large intervals 

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

614 assert large.size() == 2e100 

615 assert 1e99 in large 

616 assert math.isclose( 

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

618 ) 

619 

620 

621@pytest.mark.parametrize( 

622 "a,b,expected_intersection,expected_union", 

623 [ 

624 ( 

625 Interval(1, 3), 

626 Interval(2, 4), 

627 Interval(2, 3), 

628 Interval(1, 4), 

629 ), 

630 ( 

631 ClosedInterval(0, 2), 

632 OpenInterval(1, 3), 

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

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

635 ), 

636 ( 

637 OpenInterval(1, 5), 

638 ClosedInterval(2, 4), 

639 ClosedInterval(2, 4), 

640 OpenInterval(1, 5), 

641 ), 

642 # Non-overlapping intervals 

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

644 # Touching intervals 

645 ( 

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

647 Interval(3, 5), 

648 Interval.get_singleton(3), 

649 Interval(1, 5), 

650 ), 

651 ( 

652 ClosedInterval(1, 3), 

653 ClosedInterval(3, 5), 

654 Interval.get_singleton(3), 

655 ClosedInterval(1, 5), 

656 ), 

657 # Fully contained interval 

658 ( 

659 Interval(1, 5), 

660 Interval(2, 3), 

661 Interval(2, 3), 

662 Interval(1, 5), 

663 ), 

664 # Infinite intervals 

665 ( 

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

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

668 Interval(0, 1), 

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

670 ), 

671 ], 

672) 

673def test_interval_arithmetic( 

674 a: Interval, 

675 b: Interval, 

676 expected_intersection: Optional[Interval], 

677 expected_union: Optional[Interval], 

678): 

679 # Test intersection 

680 if expected_intersection == Interval(): 

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

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

683 else: 

684 assert a.intersection(b) == expected_intersection 

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

686 

687 # Test union 

688 if expected_union == Interval(): 

689 with pytest.raises(Exception): 

690 a.union(b) 

691 with pytest.raises(Exception): 

692 b.union(a) 

693 else: 

694 assert a.union(b) == expected_union 

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

696 

697 

698# Additional tests for edge cases 

699def test_interval_arithmetic_edge_cases(): 

700 # Self-intersection and self-union 

701 a = Interval(1, 3) 

702 assert a.intersection(a) == a 

703 assert a.union(a) == a 

704 

705 # Empty interval intersection 

706 empty = Interval(1, 1) 

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

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

709 

710 # Intersection with universal set 

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

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

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

714 

715 # Union with universal set 

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

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

718 

719 

720# Test for invalid operations 

721def test_interval_arithmetic_invalid(): 

722 with pytest.raises(TypeError): 

723 # Invalid type for intersection 

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

725 

726 with pytest.raises(TypeError): 

727 # Invalid type for union 

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

729 

730 

731@pytest.mark.parametrize( 

732 "invalid_string", 

733 [ 

734 "1, 5", # Missing brackets 

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

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

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

738 ], 

739) 

740def test_from_str_errors(invalid_string): 

741 with pytest.raises(ValueError): 

742 Interval.from_str(invalid_string) 

743 

744 

745def test_interval_repr(): 

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

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

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

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

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

751 

752 

753def test_interval_from_str_with_whitespace(): 

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

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

756 

757 

758def test_interval_from_str_with_scientific_notation(): 

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

760 

761 

762def test_interval_clamp_with_custom_epsilon(): 

763 i = Interval(0, 1) 

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

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

766 

767 

768def test_interval_size_with_small_values(): 

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

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

771 

772 

773def test_interval_intersection_edge_cases(): 

774 i1 = Interval(1, 2) 

775 i2 = Interval(2, 3) 

776 i3 = ClosedInterval(2, 3) 

777 

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

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

780 

781 

782def test_interval_union_edge_cases(): 

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

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

785 i3 = Interval(3, 4) 

786 

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

788 with pytest.raises(NotImplementedError): 

789 i1.union(i3) 

790 

791 

792def test_interval_contains_with_epsilon(): 

793 i = OpenInterval(0, 1) 

794 assert 0 + _EPSILON in i 

795 assert 1 - _EPSILON in i 

796 assert 0 not in i 

797 assert 1 not in i 

798 

799 

800def test_singleton_creation(): 

801 s = Interval.get_singleton(5) 

802 assert s.is_singleton 

803 assert s.singleton == 5 

804 assert len(s) == 1 

805 assert s.lower == s.upper == 5 

806 assert s.closed_L and s.closed_R 

807 

808 

809def test_empty_creation(): 

810 e = Interval.get_empty() 

811 assert e.is_empty 

812 assert len(e) == 0 

813 with pytest.raises(ValueError): 

814 _ = e.singleton 

815 

816 

817def test_singleton_properties(): 

818 s = Interval.get_singleton(3.14) 

819 assert s.is_singleton 

820 assert not s.is_empty 

821 assert s.is_finite 

822 assert s.is_closed 

823 assert not s.is_open 

824 assert not s.is_half_open 

825 

826 

827def test_empty_properties(): 

828 e = Interval.get_empty() 

829 assert e.is_empty 

830 assert not e.is_singleton 

831 assert e.is_finite 

832 assert e.is_closed 

833 assert e.is_open 

834 assert not e.is_half_open 

835 

836 

837def test_singleton_containment(): 

838 s = Interval.get_singleton(5) 

839 assert 5 in s 

840 assert 5.0 in s 

841 assert 4.999999999999999 not in s 

842 assert 5.000000000000001 not in s 

843 assert Interval.get_singleton(5) in s 

844 assert Interval(4, 6) not in s 

845 assert s in Interval(4, 6) 

846 

847 

848def test_empty_containment(): 

849 e = Interval.get_empty() 

850 assert 0 not in e 

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

852 assert Interval.get_empty() in e 

853 

854 

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

856def test_singleton_various_values(value): 

857 s = Interval.get_singleton(value) 

858 assert s.is_singleton 

859 assert s.singleton == value 

860 assert value in s 

861 

862 

863def test_singleton_nan(): 

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

865 

866 

867def test_singleton_operations(): 

868 s = Interval.get_singleton(5) 

869 assert s.size() == 0 

870 assert s.clamp(3) == 5 

871 assert s.clamp(7) == 5 

872 

873 

874def test_empty_operations(): 

875 e = Interval.get_empty() 

876 assert e.size() == 0 

877 with pytest.raises(ValueError): 

878 e.clamp(3) 

879 

880 

881def test_singleton_intersection(): 

882 s = Interval.get_singleton(5) 

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

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

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

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

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

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

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

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

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

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

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

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

895 

896 

897def test_empty_intersection(): 

898 e = Interval.get_empty() 

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

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

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

902 

903 

904def test_singleton_union(): 

905 s = Interval.get_singleton(5) 

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

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

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

909 with pytest.raises(NotImplementedError): 

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

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

912 with pytest.raises(NotImplementedError): 

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

914 

915 

916def test_empty_union(): 

917 e = Interval.get_empty() 

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

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

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

921 

922 

923def test_singleton_equality(): 

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

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

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

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

928 

929 

930def test_empty_equality(): 

931 assert Interval.get_empty() == Interval.get_empty() 

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

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

934 

935 

936def test_singleton_representation(): 

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

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

939 

940 

941def test_empty_representation(): 

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

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

944 

945 

946def test_singleton_from_str(): 

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

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

949 

950 

951def test_empty_from_str(): 

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

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

954 

955 

956def test_singleton_iteration(): 

957 s = Interval.get_singleton(5) 

958 assert list(s) == [5] 

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

960 

961 

962def test_empty_iteration(): 

963 e = Interval.get_empty() 

964 assert list(e) == [] 

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

966 

967 

968def test_singleton_indexing(): 

969 s = Interval.get_singleton(5) 

970 assert s[0] == 5 

971 with pytest.raises(IndexError): 

972 _ = s[1] 

973 

974 

975def test_empty_indexing(): 

976 e = Interval.get_empty() 

977 with pytest.raises(IndexError): 

978 _ = e[0] 

979 

980 

981def test_singleton_bool(): 

982 assert bool(Interval.get_singleton(5)) 

983 assert bool(Interval.get_singleton(0)) 

984 

985 

986def test_empty_bool(): 

987 assert not bool(Interval.get_empty()) 

988 

989 

990def test_singleton_infinity(): 

991 inf_singleton = Interval.get_singleton(math.inf) 

992 assert inf_singleton.is_singleton 

993 assert not inf_singleton.is_finite 

994 assert math.inf in inf_singleton 

995 assert math.inf - 1 in inf_singleton 

996 

997 

998def test_mixed_operations(): 

999 s = Interval.get_singleton(5) 

1000 e = Interval.get_empty() 

1001 i = Interval(0, 10) 

1002 

1003 assert s.intersection(e) == e 

1004 assert e.intersection(s) == e 

1005 assert i.intersection(s) == s 

1006 assert i.intersection(e) == e 

1007 

1008 assert s.union(e) == s 

1009 assert e.union(s) == s 

1010 assert i.union(s) == i 

1011 assert i.union(e) == i 

1012 

1013 

1014def test_edge_case_conversions(): 

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

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

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

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

1019 

1020 

1021def test_nan_handling_is_empty(): 

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

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

1024 assert Interval().is_empty 

1025 assert Interval.get_empty().is_empty 

1026 with pytest.raises(ValueError): 

1027 Interval(math.nan, 5) 

1028 with pytest.raises(ValueError): 

1029 assert Interval(5, math.nan) 

1030 

1031 

1032def test_infinity_edge_cases(): 

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

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

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

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

1037 

1038 

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

1040def test_potential_bug_infinity_singleton(): 

1041 inf_singleton = Interval.get_singleton(math.inf) 

1042 assert math.isinf(inf_singleton.singleton) 

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

1044 

1045 

1046# Potential bug: Epsilon handling with singletons 

1047def test_potential_bug_singleton_epsilon(): 

1048 s = Interval.get_singleton(5) 

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

1050 

1051 

1052# Potential bug: Empty set comparison 

1053def test_potential_bug_empty_comparison(): 

1054 e1 = Interval.get_empty() 

1055 e2 = OpenInterval(5, 5) 

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

1057 

1058 

1059# Potential bug: Singleton at zero 

1060def test_potential_bug_zero_singleton(): 

1061 zero_singleton = Interval.get_singleton(0) 

1062 assert 0 in zero_singleton 

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

1064 

1065 

1066# Potential bug: Empty set size 

1067def test_potential_bug_empty_set_size(): 

1068 e = Interval.get_empty() 

1069 assert ( 

1070 e.size() == 0 

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

1072 

1073 

1074@pytest.mark.parametrize( 

1075 "interval", 

1076 [ 

1077 # Empty set 

1078 Interval.get_empty(), 

1079 # Singletons 

1080 Interval.get_singleton(0), 

1081 Interval.get_singleton(5), 

1082 Interval.get_singleton(-3), 

1083 Interval.get_singleton(3.14), 

1084 Interval.get_singleton(-2.718), 

1085 # Regular intervals 

1086 Interval(0, 1), 

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

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

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

1090 OpenInterval(-5, 5), 

1091 ClosedInterval(-5, 5), 

1092 # Intervals with infinities 

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

1094 Interval(-math.inf, 0), 

1095 Interval(0, math.inf), 

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

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

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

1099 # Mixed finite and infinite bounds 

1100 Interval(-math.inf, 5), 

1101 Interval(-5, math.inf), 

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

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

1104 # Very large and very small numbers 

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

1106 ClosedInterval(-1e50, 1e50), 

1107 # Intervals with non-integer bounds 

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

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

1110 ], 

1111) 

1112def test_interval_string_round_trip(interval): 

1113 # Convert interval to string 

1114 interval_str = str(interval) 

1115 

1116 # Parse string back to interval 

1117 parsed_interval = Interval.from_str(interval_str) 

1118 

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

1120 assert parsed_interval == interval, ( 

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

1122 ) 

1123 

1124 # Additional check for string representation consistency 

1125 assert str(parsed_interval) == interval_str, ( 

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

1127 ) 

1128 

1129 

1130def test_empty_set_string_representations(): 

1131 empty = Interval.get_empty() 

1132 assert str(empty) == "∅" 

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

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

1135 

1136 

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

1138def test_singleton_string_representations(value): 

1139 singleton = Interval.get_singleton(value) 

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

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

1142 

1143 

1144def test_infinity_string_representations(): 

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

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

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

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

1149 

1150 

1151def test_mixed_closure_string_representations(): 

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

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

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

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

1156 

1157 

1158@pytest.mark.parametrize( 

1159 "string_repr", 

1160 [ 

1161 "(0, 1)", 

1162 "[0, 1]", 

1163 "(0, 1]", 

1164 "[0, 1)", 

1165 "(-inf, inf)", 

1166 "[0, inf)", 

1167 "(-inf, 0]", 

1168 "{2.5}", 

1169 "∅", 

1170 ], 

1171) 

1172def test_string_parsing_consistency(string_repr): 

1173 parsed_interval = Interval.from_str(string_repr) 

1174 assert str(parsed_interval) == string_repr, ( 

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

1176 ) 

1177 

1178 

1179def test_string_parsing_edge_cases(): 

1180 with pytest.raises(ValueError): 

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

1182 

1183 with pytest.raises(ValueError): 

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

1185 

1186 with pytest.raises(ValueError): 

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

1188 

1189 

1190def test_string_representation_precision(): 

1191 # Test that string representation maintains sufficient precision 

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

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

1194 assert small_interval == parsed_small_interval 

1195 assert small_interval.lower == parsed_small_interval.lower 

1196 assert small_interval.upper == parsed_small_interval.upper 

1197 

1198 

1199def test_string_representation_with_scientific_notation(): 

1200 large_interval = Interval(1e100, 2e100) 

1201 large_interval_str = str(large_interval) 

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

1203 parsed_large_interval = Interval.from_str(large_interval_str) 

1204 assert large_interval == parsed_large_interval 

1205 

1206 

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

1208def test_potential_bug_negative_zero(): 

1209 zero_singleton = Interval.get_singleton(-0.0) 

1210 zero_singleton_str = str(zero_singleton) 

1211 assert Interval.from_str(zero_singleton_str) == zero_singleton 

1212 assert ( 

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

1214 ) # Should this be -0.0 or 0.0? 

1215 

1216 i = Interval(-1, 1) 

1217 assert 0.0 in i 

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

1219 

1220 

1221# Potential bug: Precision loss in string representation 

1222def test_potential_bug_precision_loss(): 

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

1224 precise_interval_str = str(precise_interval) 

1225 parsed_precise_interval = Interval.from_str(precise_interval_str) 

1226 assert precise_interval == parsed_precise_interval 

1227 assert precise_interval.lower == parsed_precise_interval.lower 

1228 assert precise_interval.upper == parsed_precise_interval.upper 

1229 

1230 

1231def test_interval_with_very_close_bounds(): 

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

1233 assert i.size() > 0 

1234 assert 1 + _EPSILON in i 

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

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

1237 

1238 

1239def test_interval_with_bounds_closer_than_epsilon(): 

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

1241 assert i.size() > 0 

1242 with pytest.raises(ValueError): 

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

1244 

1245 

1246def test_interval_with_extremely_large_bounds(): 

1247 i = Interval(1e300, 1e301) 

1248 assert 5e300 in i 

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

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

1251 

1252 

1253def test_interval_with_mixed_infinities(): 

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

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

1256 assert 0 in i 

1257 assert math.inf not in i 

1258 assert -math.inf not in i 

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

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

1261 

1262 

1263def test_interval_with_one_infinity(): 

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

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

1266 assert -1e300 in i1 

1267 assert 0 not in i1 

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

1269 

1270 i2 = Interval(0, math.inf) 

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

1272 assert 1e300 in i2 

1273 assert 0 not in i2 

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

1275 

1276 

1277def test_interval_singleton_edge_cases(): 

1278 s = Interval.get_singleton(0) 

1279 assert 0 in s 

1280 assert -0.0 in s 

1281 assert 1e-16 not in s 

1282 assert -1e-16 not in s 

1283 

1284 

1285def test_interval_empty_edge_cases(): 

1286 e = Interval.get_empty() 

1287 assert e.size() == 0 

1288 assert e.is_open and e.is_closed 

1289 assert Interval.get_empty() in e 

1290 assert e in Interval(0, 1) 

1291 

1292 

1293def test_interval_from_str_edge_cases(): 

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

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

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

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

1298 

1299 

1300def test_interval_arithmetic_with_empty_intervals(): 

1301 e = Interval.get_empty() 

1302 i = Interval(0, 1) 

1303 assert e.intersection(i) == e 

1304 assert i.intersection(e) == e 

1305 assert e.union(i) == i 

1306 assert i.union(e) == i 

1307 

1308 

1309def test_interval_arithmetic_with_singletons(): 

1310 s = Interval.get_singleton(1) 

1311 i = Interval(0, 2) 

1312 assert s.intersection(i) == s 

1313 assert i.intersection(s) == s 

1314 assert s.union(i) == i 

1315 assert i.union(s) == i 

1316 

1317 

1318def test_interval_precision_near_bounds(): 

1319 i = Interval(1, 2) 

1320 assert 1 + _EPSILON / 2 in i 

1321 assert 2 - _EPSILON / 2 in i 

1322 assert 1 - _EPSILON / 2 not in i 

1323 assert 2 + _EPSILON / 2 not in i 

1324 

1325 

1326def test_interval_serialization_precision(): 

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

1328 serialized = str(i) 

1329 deserialized = Interval.from_str(serialized) 

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

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

1332 

1333 

1334def test_interval_with_repeated_float_operations(): 

1335 i = Interval(0, 1) 

1336 for _ in range(1000): 

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

1338 assert i.lower < i.upper 

1339 assert 0.5 in i 

1340 

1341 

1342def test_interval_near_float_precision_limit(): 

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

1344 assert small_interval.size() > 0 

1345 assert 1 + 5e-16 in small_interval 

1346 

1347 

1348def test_interval_with_irrational_bounds(): 

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

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

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

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

1353 assert 3 in i 

1354 

1355 

1356def test_interval_commutativity_of_operations(): 

1357 i1 = Interval(1, 3) 

1358 i2 = Interval(2, 4) 

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

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

1361 

1362 

1363def test_interval_associativity_of_operations(): 

1364 i1 = Interval(1, 4) 

1365 i2 = Interval(2, 5) 

1366 i3 = Interval(3, 6) 

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

1368 i2.intersection(i3) 

1369 ) 

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

1371 

1372 

1373def test_interval_distributivity_of_operations(): 

1374 i1 = Interval(1, 3) 

1375 i2 = Interval(2, 4) 

1376 i3 = Interval(3, 5) 

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

1378 i1.intersection(i3) 

1379 ) 

1380 

1381 

1382def test_interval_comparison(): 

1383 i1 = Interval(1, 3) 

1384 i2 = Interval(2, 4) 

1385 i3 = Interval(1, 3) 

1386 assert i1 != i2 

1387 assert i1 == i3 

1388 with pytest.raises(TypeError): 

1389 i1 < i2 # type: ignore[operator] 

1390 

1391 

1392def test_interval_copy(): 

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

1394 i_copy = i.copy() 

1395 assert i == i_copy 

1396 assert i is not i_copy 

1397 

1398 

1399def test_interval_pickling(): 

1400 import pickle 

1401 

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

1403 pickled = pickle.dumps(i) 

1404 unpickled = pickle.loads(pickled) 

1405 assert i == unpickled 

1406 

1407 

1408def test_interval_with_numpy_types(): 

1409 import numpy as np 

1410 

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

1412 assert np.float64(1.5) in i 

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

1414 

1415 

1416def test_interval_with_decimal_types(): 

1417 from decimal import Decimal 

1418 

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

1420 assert Decimal("1.5") in i 

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

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

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

1424 

1425 

1426def test_interval_with_fractions(): 

1427 from fractions import Fraction 

1428 

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

1430 assert Fraction(1, 2) in i 

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

1432 

1433 

1434# Potential bug: Infinity comparisons 

1435def test_potential_bug_infinity_comparisons(): 

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

1437 assert ( 

1438 math.inf not in i 

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

1440 

1441 

1442# Potential bug: NaN handling 

1443def test_potential_bug_nan_handling(): 

1444 with pytest.raises(ValueError): 

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

1446 

1447 i = Interval(0, 1) 

1448 with pytest.raises(ValueError): 

1449 float("nan") in i # pyright: ignore[reportUnusedExpression] 

1450 

1451 

1452# Potential bug: Intersection of adjacent intervals 

1453def test_potential_bug_adjacent_interval_intersection(): 

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

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

1456 intersection = i1.intersection(i2) 

1457 assert intersection == Interval.get_singleton( 

1458 1 

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

1460 

1461 

1462# Potential bug: Union of overlapping intervals 

1463def test_potential_bug_overlapping_interval_union(): 

1464 i1 = Interval(0, 2) 

1465 i2 = Interval(1, 3) 

1466 union = i1.union(i2) 

1467 assert union == Interval( 

1468 0, 3 

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

1470 

1471 

1472# Potential bug: Interval containing only infinity 

1473def test_potential_bug_infinity_only_interval(): 

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

1475 assert ( 

1476 i.is_empty 

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

1478 assert ( 

1479 i.size() == 0 

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

1481 

1482 

1483# Potential bug: Interval containing only negative infinity 

1484def test_potential_bug_negative_infinity_only_interval(): 

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

1486 assert ( 

1487 i.is_empty 

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

1489 assert ( 

1490 i.size() == 0 

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

1492 

1493 

1494# Potential bug: Clamp with infinity 

1495def test_potential_bug_clamp_with_infinity(): 

1496 i = Interval(0, 1) 

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

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

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

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

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

1502 

1503 

1504# Potential bug: Intersection of intervals with different types 

1505def test_potential_bug_intersection_different_types(): 

1506 i1 = Interval(0, 1) 

1507 i2 = ClosedInterval(0.5, 1.5) 

1508 intersection = i1.intersection(i2) 

1509 assert intersection == Interval( 

1510 0.5, 1, closed_L=True 

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

1512 

1513 

1514# Potential bug: Union of intervals with different types 

1515def test_potential_bug_union_different_types(): 

1516 i1 = Interval(0, 1) 

1517 i2 = ClosedInterval(0.5, 1.5) 

1518 union = i1.union(i2) 

1519 assert union == Interval( 

1520 0, 1.5, closed_R=True 

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

1522 

1523 

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

1525def test_potential_bug_very_close_bounds(): 

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

1527 assert ( 

1528 not i.is_empty 

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

1530 assert ( 

1531 i.size() > 0 

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

1533 

1534 

1535# Potential bug: Interval from string with excessive precision 

1536def test_potential_bug_excessive_precision_from_string(): 

1537 s = "(0.1234567890123456, 0.1234567890123457)" 

1538 i = Interval.from_str(s) 

1539 assert ( 

1540 str(i) == s 

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

1542 

1543 

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

1545def test_potential_bug_non_binary_representable_bounds(): 

1546 i = Interval(0.1, 0.3) 

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

1548 

1549 

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

1551def test_potential_bug_clamp_near_bound(): 

1552 i = Interval(0, 1) 

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

1554 assert ( 

1555 result < 1 

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

1557 

1558 

1559# Potential bug: Empty interval intersection with itself 

1560def test_potential_bug_empty_self_intersection(): 

1561 e = Interval.get_empty() 

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

1563 

1564 

1565# Potential bug: Empty interval union with itself 

1566def test_potential_bug_empty_self_union(): 

1567 e = Interval.get_empty() 

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

1569 

1570 

1571# Potential bug: Interval containing only NaN 

1572def test_potential_bug_nan_interval(): 

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

1574 assert i.is_empty 

1575 assert 0 not in i 

1576 

1577 

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

1579def test_potential_bug_reversed_bounds_after_operation(): 

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

1581 new_lower = i.lower + 1e-16 

1582 new_upper = i.upper - 1e-16 

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

1584 

1585 

1586# Potential bug: Interval size calculation with small numbers 

1587def test_potential_bug_small_number_size(): 

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

1589 assert math.isclose( 

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

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

1592 

1593 

1594# Potential bug: Clamp with large epsilon 

1595def test_potential_bug_clamp_large_epsilon(): 

1596 i = Interval(0, 1) 

1597 with pytest.raises(ValueError): 

1598 i.clamp(2, epsilon=10) 

1599 

1600 

1601# Potential bug: Intersection of intervals with shared bound 

1602def test_potential_bug_intersection_shared_bound(): 

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

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

1605 intersection = i1.intersection(i2) 

1606 assert intersection == Interval.get_singleton( 

1607 1 

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

1609 

1610 

1611# Potential bug: Union of intervals with shared bound 

1612def test_potential_bug_union_shared_bound(): 

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

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

1615 union = i1.union(i2) 

1616 assert union == Interval( 

1617 0, 2 

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

1619 

1620 

1621# Potential bug: Interval containing only zero 

1622def test_potential_bug_zero_only_interval(): 

1623 i = Interval(0, 0) 

1624 assert ( 

1625 i.is_empty 

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

1627 assert ( 

1628 i.size() == 0 

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

1630 

1631 

1632# Potential bug: Interval with custom numeric types 

1633def test_potential_bug_custom_numeric_types(): 

1634 from decimal import Decimal 

1635 

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

1637 assert ( 

1638 Decimal("0.2") in i 

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

1640 

1641 

1642# Potential bug: Clamp with value equal to bound 

1643def test_potential_bug_clamp_equal_to_bound(): 

1644 i = Interval(0, 1) 

1645 assert i.clamp(0) > 0 

1646 assert i.clamp(1) < 1 

1647 

1648 

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

1650def test_potential_bug_closed_infinity_interval(): 

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

1652 assert ( 

1653 i.is_singleton 

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

1655 assert ( 

1656 math.inf in i 

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

1658 

1659 

1660# Potential bug: Interval spanning entire float range 

1661def test_potential_bug_full_float_range(): 

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

1663 assert sys.float_info.max in i 

1664 assert sys.float_info.min in i 

1665 assert 0.0 in i 

1666 assert -0.0 in i 

1667 

1668 

1669# Potential bug: Interval comparison with different types 

1670def test_potential_bug_comparison_different_types(): 

1671 i1 = Interval(0, 1) 

1672 i2 = ClosedInterval(0, 1) 

1673 assert ( 

1674 i1 != i2 

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

1676 

1677 

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

1679def test_potential_bug_zero_sign_difference(): 

1680 i1 = Interval(0.0, 1.0) 

1681 i2 = Interval(-0.0, 1.0) 

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

1683 

1684 

1685# Potential bug: Clamp with NaN epsilon 

1686def test_potential_bug_clamp_nan_epsilon(): 

1687 i = Interval(0, 1) 

1688 with pytest.raises(ValueError): 

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

1690 

1691 

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

1693def test_potential_bug_closed_inf(): 

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

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

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

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

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

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

1700 

1701 assert i1.is_singleton 

1702 assert i2.is_singleton 

1703 assert not i3.is_singleton 

1704 assert not i4.is_singleton 

1705 assert not i3.is_empty 

1706 assert not i4.is_empty 

1707 assert i5.is_singleton 

1708 assert i6.is_empty 

1709 

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

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

1712 assert i3.clamp(1) == 1 

1713 assert i4.clamp(1) == 1 

1714 

1715 

1716# Potential bug: Intersection of universal set with itself 

1717def test_potential_bug_universal_set_self_intersection(): 

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

1719 assert u.intersection(u) == u 

1720 

1721 

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

1723def test_potential_bug_universal_set_union(): 

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

1725 i = Interval(0, 1) 

1726 assert u.union(i) == u 

1727 

1728 

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

1730def test_potential_bug_clamp_integer_bounds_float_value(): 

1731 i = Interval(0, 1) 

1732 result = i.clamp(0.5) 

1733 assert isinstance( 

1734 result, float 

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

1736 

1737 

1738# Potential bug: Interval from string with scientific notation 

1739def test_potential_bug_interval_from_scientific_notation(): 

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

1741 assert i.lower == 1e-10 

1742 assert i.upper == 1e10 

1743 

1744 

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

1746def test_potential_bug_repeated_float_operations(): 

1747 i = Interval(0, 1) 

1748 for _ in range(1000): 

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

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

1751 

1752 

1753# Potential bug: Interval size with subnormal numbers 

1754def test_potential_bug_subnormal_size(): 

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

1756 i = Interval(tiny, tiny * 2) 

1757 assert ( 

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

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

1760 

1761 

1762# Potential bug: Clamp with subnormal epsilon 

1763def test_potential_bug_clamp_subnormal_epsilon(): 

1764 i = Interval(0, 1) 

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

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

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

1768 

1769 

1770# Potential bug: Interval containing maximum and minimum floats 

1771def test_potential_bug_max_min_float_interval(): 

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

1773 assert 1.0 in i 

1774 assert -1.0 not in i 

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

1776 

1777 

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

1779def test_potential_bug_near_zero_bounds(): 

1780 epsilon = sys.float_info.epsilon 

1781 i = Interval(-epsilon, epsilon) 

1782 assert 0 in i 

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

1784 

1785 

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

1787def test_potential_bug_clamp_near_infinity(): 

1788 i = ClosedInterval(0, 1) 

1789 very_large = sys.float_info.max * 0.99 

1790 result = i.clamp(very_large) 

1791 assert ( 

1792 result == 1 

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