Skip to content

Commit d792429

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. Note that the tests are skipped on 32-bit systems (see #23440)
1 parent 7c5c81e commit d792429

File tree

4 files changed

+51
-0
lines changed

4 files changed

+51
-0
lines changed

doc/source/whatsnew/v1.5.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,8 @@ Indexing
786786
- Bug in :meth:`Series.asof` and :meth:`DataFrame.asof` incorrectly casting bool-dtype results to ``float64`` dtype (:issue:`16063`)
787787
- Bug in :meth:`NDFrame.xs`, :meth:`DataFrame.iterrows`, :meth:`DataFrame.loc` and :meth:`DataFrame.iloc` not always propagating metadata (:issue:`28283`)
788788
- Bug in :meth:`DataFrame.sum` min_count changes dtype if input contains NaNs (:issue:`46947`)
789+
- Bug in :class:`IntervalTree` that lead to an infinite recursion. (:issue:`46658`)
790+
-
789791

790792
Missing
791793
^^^^^^^

pandas/_libs/intervaltree.pxi.in

+7
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,13 @@ cdef class {{dtype_title}}Closed{{closed_title}}IntervalNode(IntervalNode):
342342
# calculate a pivot so we can create child nodes
343343
self.is_leaf_node = False
344344
self.pivot = np.median(left / 2 + right / 2)
345+
if np.isinf(self.pivot):
346+
self.pivot = cython.cast({{dtype}}_t, 0)
347+
if self.pivot > np.max(right):
348+
self.pivot = np.max(left)
349+
if self.pivot < np.min(left):
350+
self.pivot = np.min(right)
351+
345352
left_set, right_set, center_set = self.classify_intervals(
346353
left, right)
347354

pandas/tests/indexes/interval/test_interval_tree.py

+18
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,21 @@ def test_interval_tree_error_and_warning(self):
207207
):
208208
left, right = np.arange(10), [np.iinfo(np.int64).max] * 10
209209
IntervalTree(left, right, closed="both")
210+
211+
@pytest.mark.xfail(not IS64, reason="GH 23440")
212+
@pytest.mark.parametrize(
213+
"left, right, expected",
214+
[
215+
([-np.inf, 1.0], [1.0, 2.0], 0.0),
216+
([-np.inf, -2.0], [-2.0, -1.0], -2.0),
217+
([-2.0, -1.0], [-1.0, np.inf], 0.0),
218+
([1.0, 2.0], [2.0, np.inf], 2.0),
219+
],
220+
)
221+
def test_inf_bound_infinite_recursion(self, left, right, expected):
222+
# GH 46658
223+
224+
tree = IntervalTree(left * 101, right * 101)
225+
226+
result = tree.root.pivot
227+
assert result == expected

pandas/tests/indexing/interval/test_interval_new.py

+24
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import numpy as np
44
import pytest
55

6+
from pandas.compat import IS64
7+
68
from pandas import (
9+
Index,
710
Interval,
811
IntervalIndex,
912
Series,
@@ -217,3 +220,24 @@ def test_loc_getitem_missing_key_error_message(
217220
obj = frame_or_series(ser)
218221
with pytest.raises(KeyError, match=r"\[6\]"):
219222
obj.loc[[4, 5, 6]]
223+
224+
225+
@pytest.mark.xfail(not IS64, reason="GH 23440")
226+
@pytest.mark.parametrize(
227+
"intervals",
228+
[
229+
([Interval(-np.inf, 0.0), Interval(0.0, 1.0)]),
230+
([Interval(-np.inf, -2.0), Interval(-2.0, -1.0)]),
231+
([Interval(-1.0, 0.0), Interval(0.0, np.inf)]),
232+
([Interval(1.0, 2.0), Interval(2.0, np.inf)]),
233+
],
234+
)
235+
def test_repeating_interval_index_with_infs(intervals):
236+
# GH 46658
237+
238+
interval_index = Index(intervals * 51)
239+
240+
expected = np.arange(1, 102, 2, dtype=np.intp)
241+
result = interval_index.get_indexer_for([intervals[1]])
242+
243+
tm.assert_equal(result, expected)

0 commit comments

Comments
 (0)