From 733c8ad045bb1f326a585219d75fa17fc96c8728 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:15:49 +0000 Subject: [PATCH 1/4] Initial plan From a466989bf290b84677564ca78c57adf075db540d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:24:48 +0000 Subject: [PATCH 2/4] Replace searchsorted with np.searchsorted and add deprecation warning Co-authored-by: oyamad <7353083+oyamad@users.noreply.github.com> --- quantecon/markov/core.py | 10 ++++---- quantecon/random/utilities.py | 8 +++--- quantecon/util/array.py | 41 +++++++++++++++++------------- quantecon/util/tests/test_array.py | 35 ++++++++++++++++--------- 4 files changed, 56 insertions(+), 38 deletions(-) diff --git a/quantecon/markov/core.py b/quantecon/markov/core.py index 9af63a610..a3cb10b70 100644 --- a/quantecon/markov/core.py +++ b/quantecon/markov/core.py @@ -87,7 +87,7 @@ from .gth_solve import gth_solve from .._graph_tools import DiGraph -from ..util import searchsorted, check_random_state, rng_integers +from ..util import check_random_state, rng_integers class MarkovChain: @@ -618,7 +618,7 @@ def _generate_sample_paths(P_cdfs, init_states, random_values, out): for i in range(num_reps): out[i, 0] = init_states[i] for t in range(ts_length-1): - out[i, t+1] = searchsorted(P_cdfs[out[i, t]], random_values[i, t]) + out[i, t+1] = np.searchsorted(P_cdfs[out[i, t]], random_values[i, t], side='right') @jit(nopython=True) @@ -662,8 +662,8 @@ def _generate_sample_paths_sparse(P_cdfs1d, indices, indptr, init_states, for i in range(num_reps): out[i, 0] = init_states[i] for t in range(ts_length-1): - k = searchsorted(P_cdfs1d[indptr[out[i, t]]:indptr[out[i, t]+1]], - random_values[i, t]) + k = np.searchsorted(P_cdfs1d[indptr[out[i, t]]:indptr[out[i, t]+1]], + random_values[i, t], side='right') out[i, t+1] = indices[indptr[out[i, t]]+k] @@ -719,7 +719,7 @@ def mc_sample_path(P, init=0, sample_size=1000, random_state=None): else: cdf0 = np.cumsum(init) u_0 = random_state.random() - X_0 = searchsorted(cdf0, u_0) + X_0 = np.searchsorted(cdf0, u_0, side='right') mc = MarkovChain(P) return mc.simulate(ts_length=sample_size, init=X_0, diff --git a/quantecon/random/utilities.py b/quantecon/random/utilities.py index 9838b2e1c..bd309b065 100644 --- a/quantecon/random/utilities.py +++ b/quantecon/random/utilities.py @@ -6,7 +6,7 @@ import numpy as np from numba import guvectorize, types from numba.extending import overload -from ..util import check_random_state, searchsorted +from ..util import check_random_state # Generating Arrays and Vectors # @@ -204,7 +204,7 @@ def draw(cdf, size=None): return out else: r = np.random.random() - return searchsorted(cdf, r) + return np.searchsorted(cdf, r, side='right') # Overload for the `draw` function @@ -215,10 +215,10 @@ def draw_impl(cdf, size=None): rs = np.random.random(size) out = np.empty(size, dtype=np.int_) for i in range(size): - out[i] = searchsorted(cdf, rs[i]) + out[i] = np.searchsorted(cdf, rs[i], side='right') return out else: def draw_impl(cdf, size=None): r = np.random.random() - return searchsorted(cdf, r) + return np.searchsorted(cdf, r, side='right') return draw_impl diff --git a/quantecon/util/array.py b/quantecon/util/array.py index 3821a06a9..2cf1ea0ab 100644 --- a/quantecon/util/array.py +++ b/quantecon/util/array.py @@ -4,22 +4,29 @@ Array ----- -searchsorted +searchsorted (deprecated - use np.searchsorted with side='right' instead) """ -from numba import jit +import warnings +import numpy as np # ----------------- # # -ARRAY UTILITIES- # # ----------------- # -@jit(nopython=True) def searchsorted(a, v): """ - Custom version of np.searchsorted. Return the largest index `i` such - that `a[i-1] <= v < a[i]` (for `i = 0`, `v < a[0]`); if `v[n-1] <= - v`, return `n`, where `n = len(a)`. + .. deprecated:: 0.10.2 + `searchsorted` is deprecated and will be removed in a future version. + Use `np.searchsorted(a, v, side='right')` instead. + + Return the largest index `i` such that `a[i-1] <= v < a[i]` (for + `i = 0`, `v < a[0]`); if `a[n-1] <= v`, return `n`, where `n = + len(a)`. + + This function is now a thin wrapper around `np.searchsorted(a, v, + side='right')` and emits a deprecation warning when called. Parameters ---------- @@ -37,8 +44,10 @@ def searchsorted(a, v): Notes ----- - This routine is jit-complied if the module Numba is vailable; if - not, it is an alias of np.searchsorted(a, v, side='right'). + This routine was originally jit-compiled when Numba did not support + the `side` keyword argument for `np.searchsorted`. Now that Numba + supports this feature, this function is deprecated in favor of using + `np.searchsorted(a, v, side='right')` directly. Examples -------- @@ -51,12 +60,10 @@ def searchsorted(a, v): 3 """ - lo = -1 - hi = len(a) - while(lo < hi-1): - m = (lo + hi) // 2 - if v < a[m]: - hi = m - else: - lo = m - return hi + warnings.warn( + "searchsorted is deprecated and will be removed in a future version. " + "Use np.searchsorted(a, v, side='right') instead.", + DeprecationWarning, + stacklevel=2 + ) + return np.searchsorted(a, v, side='right') diff --git a/quantecon/util/tests/test_array.py b/quantecon/util/tests/test_array.py index 8c0156343..52bef0437 100644 --- a/quantecon/util/tests/test_array.py +++ b/quantecon/util/tests/test_array.py @@ -7,24 +7,35 @@ """ import numpy as np +import warnings from numpy.testing import assert_ from quantecon.util import searchsorted def test_searchsorted(): a = np.array([0.2, 0.4, 1.0]) - assert_(searchsorted(a, 0.1) == 0) - assert_(searchsorted(a, 0.4) == 2) - assert_(searchsorted(a, 2) == 3) + + # Test with deprecation warning + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + assert_(searchsorted(a, 0.1) == 0) + assert len(w) == 1 + assert issubclass(w[-1].category, DeprecationWarning) + assert "searchsorted is deprecated" in str(w[-1].message) + + with warnings.catch_warnings(record=True): + warnings.simplefilter("always") + assert_(searchsorted(a, 0.4) == 2) + assert_(searchsorted(a, 2) == 3) - a = np.ones(0) - for (v, i) in zip([0, 1, 2], [0, 0, 0]): - assert_(searchsorted(a, v) == i) + a = np.ones(0) + for (v, i) in zip([0, 1, 2], [0, 0, 0]): + assert_(searchsorted(a, v) == i) - a = np.ones(1) - for (v, i) in zip([0, 1, 2], [0, 1, 1]): - assert_(searchsorted(a, v) == i) + a = np.ones(1) + for (v, i) in zip([0, 1, 2], [0, 1, 1]): + assert_(searchsorted(a, v) == i) - a = np.ones(2) - for (v, i) in zip([0, 1, 2], [0, 2, 2]): - assert_(searchsorted(a, v) == i) + a = np.ones(2) + for (v, i) in zip([0, 1, 2], [0, 2, 2]): + assert_(searchsorted(a, v) == i) From f4477eafcdfb9beaec6a5fd9a1b670929d0acfa3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 15 Nov 2025 09:43:30 +0000 Subject: [PATCH 3/4] Incorporate changes from deprecate_searchsorted branch and fix indentation - Reverted array.py and test_array.py to use the implementation from commit ab0fa74 - This version keeps the JIT-compiled searchsorted but emits deprecation warning - Fixed indentation in markov/core.py line 666 as suggested in code review - Removed unused numpy import from array.py Co-authored-by: oyamad <7353083+oyamad@users.noreply.github.com> --- quantecon/markov/core.py | 2 +- quantecon/util/array.py | 48 ++++++++++++++++------------- quantecon/util/tests/test_array.py | 49 ++++++++++++++---------------- 3 files changed, 51 insertions(+), 48 deletions(-) diff --git a/quantecon/markov/core.py b/quantecon/markov/core.py index a3cb10b70..63e374a72 100644 --- a/quantecon/markov/core.py +++ b/quantecon/markov/core.py @@ -663,7 +663,7 @@ def _generate_sample_paths_sparse(P_cdfs1d, indices, indptr, init_states, out[i, 0] = init_states[i] for t in range(ts_length-1): k = np.searchsorted(P_cdfs1d[indptr[out[i, t]]:indptr[out[i, t]+1]], - random_values[i, t], side='right') + random_values[i, t], side='right') out[i, t+1] = indices[indptr[out[i, t]]+k] diff --git a/quantecon/util/array.py b/quantecon/util/array.py index 2cf1ea0ab..4200948fd 100644 --- a/quantecon/util/array.py +++ b/quantecon/util/array.py @@ -4,29 +4,27 @@ Array ----- -searchsorted (deprecated - use np.searchsorted with side='right' instead) +searchsorted """ import warnings -import numpy as np +from numba import jit, objmode # ----------------- # # -ARRAY UTILITIES- # # ----------------- # +@jit(nopython=True) def searchsorted(a, v): """ - .. deprecated:: 0.10.2 - `searchsorted` is deprecated and will be removed in a future version. - Use `np.searchsorted(a, v, side='right')` instead. + Custom version of np.searchsorted. Return the largest index `i` such + that `a[i-1] <= v < a[i]` (for `i = 0`, `v < a[0]`); if `v[n-1] <= + v`, return `n`, where `n = len(a)`. - Return the largest index `i` such that `a[i-1] <= v < a[i]` (for - `i = 0`, `v < a[0]`); if `a[n-1] <= v`, return `n`, where `n = - len(a)`. + .. deprecated:: - This function is now a thin wrapper around `np.searchsorted(a, v, - side='right')` and emits a deprecation warning when called. + Deprecated, use `np.searchsorted(a, v, side='right')` instead. Parameters ---------- @@ -44,10 +42,8 @@ def searchsorted(a, v): Notes ----- - This routine was originally jit-compiled when Numba did not support - the `side` keyword argument for `np.searchsorted`. Now that Numba - supports this feature, this function is deprecated in favor of using - `np.searchsorted(a, v, side='right')` directly. + This routine is jit-complied if the module Numba is vailable; if + not, it is an alias of np.searchsorted(a, v, side='right'). Examples -------- @@ -60,10 +56,20 @@ def searchsorted(a, v): 3 """ - warnings.warn( - "searchsorted is deprecated and will be removed in a future version. " - "Use np.searchsorted(a, v, side='right') instead.", - DeprecationWarning, - stacklevel=2 - ) - return np.searchsorted(a, v, side='right') + with objmode(): + warnings.warn( + "`searchsorted(a, v)` is deprecated. " + "Use `np.searchsorted(a, v, side='right')` instead.", + DeprecationWarning, + stacklevel=2, + ) + + lo = -1 + hi = len(a) + while(lo < hi-1): + m = (lo + hi) // 2 + if v < a[m]: + hi = m + else: + lo = m + return hi diff --git a/quantecon/util/tests/test_array.py b/quantecon/util/tests/test_array.py index 52bef0437..807228d15 100644 --- a/quantecon/util/tests/test_array.py +++ b/quantecon/util/tests/test_array.py @@ -7,35 +7,32 @@ """ import numpy as np -import warnings from numpy.testing import assert_ +import pytest from quantecon.util import searchsorted +@pytest.mark.filterwarnings("ignore::DeprecationWarning") def test_searchsorted(): a = np.array([0.2, 0.4, 1.0]) - - # Test with deprecation warning - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter("always") - assert_(searchsorted(a, 0.1) == 0) - assert len(w) == 1 - assert issubclass(w[-1].category, DeprecationWarning) - assert "searchsorted is deprecated" in str(w[-1].message) - - with warnings.catch_warnings(record=True): - warnings.simplefilter("always") - assert_(searchsorted(a, 0.4) == 2) - assert_(searchsorted(a, 2) == 3) - - a = np.ones(0) - for (v, i) in zip([0, 1, 2], [0, 0, 0]): - assert_(searchsorted(a, v) == i) - - a = np.ones(1) - for (v, i) in zip([0, 1, 2], [0, 1, 1]): - assert_(searchsorted(a, v) == i) - - a = np.ones(2) - for (v, i) in zip([0, 1, 2], [0, 2, 2]): - assert_(searchsorted(a, v) == i) + assert_(searchsorted(a, 0.1) == 0) + assert_(searchsorted(a, 0.4) == 2) + assert_(searchsorted(a, 2) == 3) + + a = np.ones(0) + for (v, i) in zip([0, 1, 2], [0, 0, 0]): + assert_(searchsorted(a, v) == i) + + a = np.ones(1) + for (v, i) in zip([0, 1, 2], [0, 1, 1]): + assert_(searchsorted(a, v) == i) + + a = np.ones(2) + for (v, i) in zip([0, 1, 2], [0, 2, 2]): + assert_(searchsorted(a, v) == i) + + +def test_warns(): + a = np.array([0.2, 0.4, 1.0]) + with pytest.warns(DeprecationWarning): + searchsorted(a, 0.5) From ec62a7cf72a90d97040e5a1ebbc358509c93768b Mon Sep 17 00:00:00 2001 From: Daisuke Oyama Date: Sat, 15 Nov 2025 19:37:13 +0900 Subject: [PATCH 4/4] Add a test case with jitted function --- quantecon/util/tests/test_array.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quantecon/util/tests/test_array.py b/quantecon/util/tests/test_array.py index 807228d15..ad6e33689 100644 --- a/quantecon/util/tests/test_array.py +++ b/quantecon/util/tests/test_array.py @@ -8,6 +8,7 @@ """ import numpy as np from numpy.testing import assert_ +from numba import njit import pytest from quantecon.util import searchsorted @@ -32,7 +33,16 @@ def test_searchsorted(): assert_(searchsorted(a, v) == i) +@njit +def _jitted_function(): + a = np.array([0.2, 0.4, 1.0]) + return searchsorted(a, 0.5) + + def test_warns(): a = np.array([0.2, 0.4, 1.0]) with pytest.warns(DeprecationWarning): searchsorted(a, 0.5) + + with pytest.warns(DeprecationWarning): + _jitted_function()