Skip to content

Commit 4a11347

Browse files
BUG: Fix infinite recursion loop when pivot of IntervalTree is ±inf
Attempts to fix #46658. When the pivot of an IntervalTree becomes ±inf the construction of the IntervalTree comes to an infinite loop recursion. This patch tries to fix that by catching those cases and set the pivot to a reasonable value.
1 parent 8980af7 commit 4a11347

File tree

4 files changed

+46
-0
lines changed

4 files changed

+46
-0
lines changed

doc/source/whatsnew/v1.5.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ Indexing
542542
- Bug in :meth:`CategoricalIndex.get_indexer` when index contains ``NaN`` values, resulting in elements that are in target but not present in the index to be mapped to the index of the NaN element, instead of -1 (:issue:`45361`)
543543
- Bug in setting large integer values into :class:`Series` with ``float32`` or ``float16`` dtype incorrectly altering these values instead of coercing to ``float64`` dtype (:issue:`45844`)
544544
- Bug in :meth:`Series.asof` and :meth:`DataFrame.asof` incorrectly casting bool-dtype results to ``float64`` dtype (:issue:`16063`)
545+
- Bug in :class:`IntervalTree` that lead to an infinite recursion. (:issue:`46658`)
545546
-
546547

547548
Missing

pandas/_libs/intervaltree.pxi.in

+7
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,13 @@ cdef class {{dtype_title}}Closed{{closed_title}}IntervalNode(IntervalNode):
324324
# calculate a pivot so we can create child nodes
325325
self.is_leaf_node = False
326326
self.pivot = np.median(left / 2 + right / 2)
327+
if np.isinf(self.pivot):
328+
self.pivot = cython.cast({{dtype}}_t, 0)
329+
if self.pivot > np.max(right):
330+
self.pivot = np.max(left)
331+
if self.pivot < np.min(left):
332+
self.pivot = np.min(right)
333+
327334
left_set, right_set, center_set = self.classify_intervals(
328335
left, right)
329336

pandas/tests/indexes/interval/test_interval_tree.py

+17
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,20 @@ def test_construction_overflow(self):
189189
result = tree.root.pivot
190190
expected = (50 + np.iinfo(np.int64).max) / 2
191191
assert result == expected
192+
193+
@pytest.mark.parametrize(
194+
"left, right, expected",
195+
[
196+
([-np.inf, 1.0], [1.0, 2.0], 0.0),
197+
([-np.inf, -2.0], [-2.0, -1.0], -2.0),
198+
([-2.0, -1.0], [-1.0, np.inf], 0.0),
199+
([1.0, 2.0], [2.0, np.inf], 2.0),
200+
],
201+
)
202+
def test_inf_bound_infinite_recursion(self, left, right, expected):
203+
# GH 46658
204+
205+
tree = IntervalTree(left * 101, right * 101)
206+
207+
result = tree.root.pivot
208+
assert result == expected

pandas/tests/indexing/interval/test_interval_new.py

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from pandas import (
7+
Index,
78
Interval,
89
IntervalIndex,
910
Series,
@@ -207,3 +208,23 @@ def test_loc_getitem_missing_key_error_message(
207208
obj = frame_or_series(ser)
208209
with pytest.raises(KeyError, match=r"\[6\]"):
209210
obj.loc[[4, 5, 6]]
211+
212+
213+
@pytest.mark.parametrize(
214+
"intervals",
215+
[
216+
([Interval(-np.inf, 0.0), Interval(0.0, 1.0)]),
217+
([Interval(-np.inf, -2.0), Interval(-2.0, -1.0)]),
218+
([Interval(-1.0, 0.0), Interval(0.0, np.inf)]),
219+
([Interval(1.0, 2.0), Interval(2.0, np.inf)]),
220+
],
221+
)
222+
def test_repeating_interval_index_with_infs(intervals):
223+
# GH 46658
224+
225+
interval_index = Index(intervals * 51)
226+
227+
expected = np.arange(1, 102, 2, dtype=np.intp)
228+
result = interval_index.get_indexer_for([intervals[1]])
229+
230+
tm.assert_equal(result, expected)

0 commit comments

Comments
 (0)