diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eccabac..dceadc1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: - id: python-use-type-annotations - id: text-unicode-replacement-char - repo: https://github.com/pycqa/isort - rev: 8.0.1 + rev: 9.0.0b1 hooks: - id: isort name: isort @@ -55,13 +55,13 @@ repos: # args: # - --py37-plus - repo: https://github.com/psf/black-pre-commit-mirror - rev: 26.1.0 + rev: 26.5.1 hooks: - id: black language_version: python3.12 exclude: tests/utils/fast_upper_envelope_org.py - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.4 + rev: v0.16.1 hooks: - id: ruff # exclude: | @@ -70,7 +70,7 @@ repos: # docs/source/conf.py| # )$ - repo: https://github.com/PyCQA/docformatter - rev: v1.7.7 + rev: v1.7.8 hooks: - id: docformatter args: diff --git a/docs/time_period2_ops.py b/docs/time_period2_ops.py index 4242d57..45ff026 100644 --- a/docs/time_period2_ops.py +++ b/docs/time_period2_ops.py @@ -24,7 +24,7 @@ def _parse_args() -> argparse.Namespace: def utility_crra_jax( - consumption: jnp.ndarray, choice: int, params: Dict[str, float] + consumption: jnp.ndarray, choice: int, params: dict[str, float] ) -> jnp.ndarray: utility_consumption = (consumption ** (1 - params["rho"]) - 1) / (1 - params["rho"]) utility = utility_consumption - (1 - choice) * params["delta"] @@ -50,7 +50,7 @@ def value_func_numba( test_resources / f"pol{period}.csv", delimiter=",", dtype=float ) -params: Dict[str, float] = {"beta": 0.95, "rho": 1.95, "delta": 0.35} +params: dict[str, float] = {"beta": 0.95, "rho": 1.95, "delta": 0.35} state_choice = {"lagged_choice": 0, "choice": 0} diff --git a/docs/tutorials/getting_started.ipynb b/docs/tutorials/getting_started.ipynb index f343212..cf0e55e 100644 --- a/docs/tutorials/getting_started.ipynb +++ b/docs/tutorials/getting_started.ipynb @@ -2,25 +2,35 @@ "cells": [ { "cell_type": "code", - "outputs": [], - "source": [ - "import numpy as np\n", - "from upper_envelope.fues_numba.fues_numba import fast_upper_envelope_wrapper\n", - "import numba as nb\n", - "from collections import namedtuple\n" - ], + "execution_count": 1, + "id": "c71bb4b54fd1da58", "metadata": { - "collapsed": false, "ExecuteTime": { "end_time": "2024-05-24T06:42:04.589484405Z", "start_time": "2024-05-24T06:42:04.167227740Z" - } + }, + "collapsed": false }, - "id": "c71bb4b54fd1da58", - "execution_count": 1 + "outputs": [], + "source": [ + "from collections import namedtuple\n", + "\n", + "import numba as nb\n", + "import numpy as np\n", + "from upper_envelope.fues_numba.fues_numba import fast_upper_envelope_wrapper\n" + ] }, { "cell_type": "code", + "execution_count": 2, + "id": "ab4b0dcb970dac41", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T06:42:04.611236211Z", + "start_time": "2024-05-24T06:42:04.609614665Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "@nb.njit\n", @@ -50,27 +60,18 @@ " utility = utility_consumption - (1 - params_ntuple.choice) * params_ntuple.delta\n", "\n", " return utility" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-05-24T06:42:04.611236211Z", - "start_time": "2024-05-24T06:42:04.609614665Z" - } - }, - "id": "ab4b0dcb970dac41", - "execution_count": 2 + ] }, { "cell_type": "code", "execution_count": 3, "id": "initial_id", "metadata": { - "collapsed": true, "ExecuteTime": { "end_time": "2024-05-24T06:42:04.613370376Z", "start_time": "2024-05-24T06:42:04.609954700Z" - } + }, + "collapsed": true }, "outputs": [], "source": [ @@ -89,6 +90,15 @@ }, { "cell_type": "code", + "execution_count": 4, + "id": "61b7b971c6f81237", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T06:42:04.614201145Z", + "start_time": "2024-05-24T06:42:04.610074325Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "resource_dir = \"../../tests/resources/\"\n", @@ -108,19 +118,19 @@ " \"n_constrained_points_to_add\": n_constrained_points_to_add,\n", " \"n_points_to_scan\": 10,\n", "}" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-05-24T06:42:04.614201145Z", - "start_time": "2024-05-24T06:42:04.610074325Z" - } - }, - "id": "61b7b971c6f81237", - "execution_count": 4 + ] }, { "cell_type": "code", + "execution_count": 5, + "id": "415a872b98d65631", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T06:42:04.614607376Z", + "start_time": "2024-05-24T06:42:04.610206856Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "\n", @@ -130,19 +140,19 @@ "tuning_params_tuple = namedtuple(\"tunings\", tuning_params.keys())(\n", " *tuning_params.values()\n", ")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-05-24T06:42:04.614607376Z", - "start_time": "2024-05-24T06:42:04.610206856Z" - } - }, - "id": "415a872b98d65631", - "execution_count": 5 + ] }, { "cell_type": "code", + "execution_count": 6, + "id": "df76ba014c57934d", + "metadata": { + "ExecuteTime": { + "end_time": "2024-05-24T06:42:14.783923576Z", + "start_time": "2024-05-24T06:42:04.611563430Z" + }, + "collapsed": false + }, "outputs": [], "source": [ "endog_grid_refined, policy_refined, value_refined = fast_upper_envelope_wrapper(\n", @@ -156,16 +166,7 @@ " discount_factor=beta,\n", " tuning_params=tuning_params_tuple,\n", ")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-05-24T06:42:14.783923576Z", - "start_time": "2024-05-24T06:42:04.611563430Z" - } - }, - "id": "df76ba014c57934d", - "execution_count": 6 + ] } ], "metadata": { diff --git a/docs/tutorials/ue_drued_jorg.ipynb b/docs/tutorials/ue_drued_jorg.ipynb index ec162bd..fa21e89 100644 --- a/docs/tutorials/ue_drued_jorg.ipynb +++ b/docs/tutorials/ue_drued_jorg.ipynb @@ -26,10 +26,10 @@ "metadata": {}, "outputs": [], "source": [ - "import numpy as np\n", "import jax\n", "import jax.numpy as jnp\n", "import matplotlib.pyplot as plt\n", + "import numpy as np\n", "\n", "import upper_envelope.jax as upenv\n", "\n", diff --git a/src/upper_envelope/jax/drued_jorg_jax.py b/src/upper_envelope/jax/drued_jorg_jax.py index 6b682c6..f32d703 100644 --- a/src/upper_envelope/jax/drued_jorg_jax.py +++ b/src/upper_envelope/jax/drued_jorg_jax.py @@ -1,7 +1,8 @@ from __future__ import annotations +from collections.abc import Callable from functools import partial -from typing import Callable, Dict, Optional +from typing import Dict, Optional import jax import jax.numpy as jnp @@ -16,7 +17,7 @@ def drued_jorg_jax( expected_value_zero_savings: jnp.ndarray | float, value_function: Callable, value_function_args=(), - value_function_kwargs: Optional[Dict] = None, + value_function_kwargs: dict | None = None, ): """Compute a simple 1D upper envelope on a given common grid. diff --git a/src/upper_envelope/jax/fues_jax/check_and_scan_funcs.py b/src/upper_envelope/jax/fues_jax/check_and_scan_funcs.py index 77fc653..20e8367 100644 --- a/src/upper_envelope/jax/fues_jax/check_and_scan_funcs.py +++ b/src/upper_envelope/jax/fues_jax/check_and_scan_funcs.py @@ -256,7 +256,7 @@ def forward_scan( n_points_to_scan: int, is_scan_needed, jump_thresh: float, -) -> Tuple[float, int]: +) -> tuple[float, int]: """Find next point on same value function as most recent point on upper envelope. We use the forward scan to find the next point that lies on the same value @@ -322,7 +322,7 @@ def backward_scan( n_points_to_scan: int, is_scan_needed, jump_thresh: float, -) -> Tuple[float, int]: +) -> tuple[float, int]: """Find previous point on same value function as idx_to_scan_from. We use the backward scan to find the preceding point that lies on the same value diff --git a/src/upper_envelope/jax/fues_jax/fues_jax.py b/src/upper_envelope/jax/fues_jax/fues_jax.py index 822464b..51ab049 100644 --- a/src/upper_envelope/jax/fues_jax/fues_jax.py +++ b/src/upper_envelope/jax/fues_jax/fues_jax.py @@ -6,8 +6,9 @@ """ +from collections.abc import Callable from functools import partial -from typing import Callable, Dict, Optional, Tuple +from typing import Dict, Optional, Tuple import jax import jax.numpy as jnp @@ -35,13 +36,13 @@ def fues_jax( value: jnp.ndarray, expected_value_zero_savings: jnp.ndarray | float, value_function: Callable, - value_function_args: Optional[Tuple] = (), - value_function_kwargs: Optional[Dict] = None, + value_function_args: tuple | None = (), + value_function_kwargs: dict | None = None, n_constrained_points_to_add=None, n_final_wealth_grid=None, jump_thresh=2, n_points_to_scan=10, -) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: +) -> tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Drop suboptimal points and refines the endogenous grid, policy, and value. Computes the upper envelope over the overlapping segments of the @@ -185,7 +186,7 @@ def fues_jax_unconstrained( n_final_wealth_grid=None, jump_thresh=2, n_points_to_scan=10, -) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: +) -> tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Remove suboptimal points from the endogenous grid, policy, and value function. Args: @@ -249,8 +250,8 @@ def scan_value_function( expected_value_zero_savings, n_final_wealth_grid: int, jump_thresh: float, - n_points_to_scan: Optional[int] = 0, -) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: + n_points_to_scan: int | None = 0, +) -> tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Scan the value function to remove suboptimal points and add intersection points. Args: diff --git a/src/upper_envelope/math_funcs.py b/src/upper_envelope/math_funcs.py index e13dc6c..9beb680 100644 --- a/src/upper_envelope/math_funcs.py +++ b/src/upper_envelope/math_funcs.py @@ -158,7 +158,7 @@ def _find_linear_intersection( y3: float | jnp.ndarray, x4: float | jnp.ndarray, y4: float | jnp.ndarray, -) -> Tuple[float | jnp.ndarray, float | jnp.ndarray]: +) -> tuple[float | jnp.ndarray, float | jnp.ndarray]: """Find the linear intersection of two lines. Args: @@ -176,7 +176,6 @@ def _find_linear_intersection( tuple: x and y coordinates of the intersection point. """ - slope1 = calc_gradient(x1, y1, x2, y2) slope2 = calc_gradient(x3, y3, x4, y4) diff --git a/src/upper_envelope/numba/drued_jorg_numba.py b/src/upper_envelope/numba/drued_jorg_numba.py index 172d3f9..ffa5903 100644 --- a/src/upper_envelope/numba/drued_jorg_numba.py +++ b/src/upper_envelope/numba/drued_jorg_numba.py @@ -1,4 +1,4 @@ -from typing import Callable +from collections.abc import Callable import numpy as np from numba import njit diff --git a/src/upper_envelope/numba/fues_numba/fues_numba.py b/src/upper_envelope/numba/fues_numba/fues_numba.py index 7a0ed91..49c7c71 100644 --- a/src/upper_envelope/numba/fues_numba/fues_numba.py +++ b/src/upper_envelope/numba/fues_numba/fues_numba.py @@ -6,7 +6,8 @@ """ -from typing import Callable, Optional, Tuple +from collections.abc import Callable +from typing import Optional, Tuple import numpy as np from numba import njit @@ -19,12 +20,12 @@ def fues_numba( value: np.ndarray, expected_value_zero_savings: np.ndarray | float, value_function: Callable, - value_function_args: Tuple, + value_function_args: tuple, n_constrained_points_to_add=None, n_final_wealth_grid=None, jump_thresh=2, n_points_to_scan=10, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Drop suboptimal points and refine the endogenous grid, policy, and value. Computes the upper envelope over the overlapping segments of the @@ -146,7 +147,7 @@ def fues_numba_unconstrained( policy: np.ndarray, jump_thresh=2, n_points_to_scan=10, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Remove suboptimal points from the endogenous grid, policy, and value function. Args: @@ -170,7 +171,6 @@ def fues_numba_unconstrained( containing refined value function. """ - endog_grid = endog_grid[np.where(~np.isnan(value))[0]] policy = policy[np.where(~np.isnan(value))] value = value[np.where(~np.isnan(value))] @@ -209,9 +209,9 @@ def scan_value_function( value: np.ndarray, policy: np.ndarray, exog_grid: np.ndarray, - jump_thresh: Optional[float] = 2, - n_points_to_scan: Optional[int] = 0, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + jump_thresh: float | None = 2, + n_points_to_scan: int | None = 0, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Scan the value function to remove suboptimal points and add intersection points. Args: @@ -234,7 +234,6 @@ def scan_value_function( the optimal points are kept. """ - value_refined, policy_refined, endog_grid_refined = _initialize_refined_arrays( value, policy, endog_grid ) @@ -524,7 +523,7 @@ def _forward_scan( idx_current: int, idx_next: int, n_points_to_scan: int, -) -> Tuple[float, int, int]: +) -> tuple[float, int, int]: """Scan forward to check whether next point is optimal. Args: @@ -550,7 +549,6 @@ def _forward_scan( the same value function. """ - is_next_on_same_value = 0 idx_on_same_value = 0 grad_next_on_same_value = 0 @@ -599,7 +597,7 @@ def _backward_scan( jump_thresh: float, idx_current: int, idx_next: int, -) -> Tuple[float, int]: +) -> tuple[float, int]: """Scan backward to check whether current point is optimal. Args: @@ -623,7 +621,6 @@ def _backward_scan( previous point on the same value function. """ - is_before_on_same_value = 0 sub_idx_point_before_on_same_value = 0 grad_before_on_same_value = 0 @@ -695,7 +692,7 @@ def _linear_intersection( y3: float | np.ndarray, x4: float | np.ndarray, y4: float | np.ndarray, -) -> Tuple[float, float]: +) -> tuple[float, float]: """Find the intersection of two lines. Args: @@ -713,7 +710,6 @@ def _linear_intersection( tuple: x and y coordinates of the intersection point. """ - slope1 = (y2 - y1) / (x2 - x1) slope2 = (y4 - y3) / (x4 - x3) @@ -741,8 +737,8 @@ def _augment_grids( min_wealth_grid: float, n_constrained_points_to_add: int, value_function: Callable, - value_function_args: Tuple, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + value_function_args: tuple, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Extends the endogenous wealth grid, value, and policy functions to the left. Args: @@ -796,7 +792,7 @@ def _augment_grids( @njit def _initialize_refined_arrays( value: np.ndarray, policy: np.ndarray, endog_grid: np.ndarray -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: value_refined = np.empty_like(value) policy_refined = np.empty_like(policy) endog_grid_refined = np.empty_like(endog_grid) diff --git a/tests/conftest.py b/tests/conftest.py index b258f2d..bea943f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,15 +16,15 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "utils")) -def pytest_sessionstart(session): # noqa: ARG001 +def pytest_sessionstart(session): jax.config.update("jax_enable_x64", val=True) -def pytest_configure(config): # noqa: ARG001 +def pytest_configure(config): """Called after command line options have been parsed.""" os.environ["NUMBA_DISABLE_JIT"] = "1" -def pytest_unconfigure(config): # noqa: ARG001 +def pytest_unconfigure(config): """Called before test process is exited.""" os.environ.pop("NUMBA_DISABLE_JIT", None) diff --git a/tests/test_drued_jorg_numba.py b/tests/test_drued_jorg_numba.py index 4b061e9..d56cc55 100644 --- a/tests/test_drued_jorg_numba.py +++ b/tests/test_drued_jorg_numba.py @@ -1,10 +1,9 @@ """Tests for `drued_jorg_numba`. -This test mirrors `tests/test_jorg_drued_jax.py` but exercises the numba -implementation. +This test mirrors `tests/test_jorg_drued_jax.py` but exercises the numba implementation. -We compare against `upenv.fues_jax`, but only on evaluation points that lie on -reference line segments that are not affected by explicit intersection handling. +We compare against `upenv.fues_jax`, but only on evaluation points that lie on reference +line segments that are not affected by explicit intersection handling. """ @@ -26,7 +25,7 @@ def utility_crra( - consumption: jnp.ndarray, choice: int, params: Dict[str, float] + consumption: jnp.ndarray, choice: int, params: dict[str, float] ) -> jnp.ndarray: utility_consumption = (consumption ** (1 - params["rho"]) - 1) / (1 - params["rho"]) utility = utility_consumption - (1 - choice) * params["delta"] diff --git a/tests/test_fues_jax.py b/tests/test_fues_jax.py index 03f9108..f84575e 100644 --- a/tests/test_fues_jax.py +++ b/tests/test_fues_jax.py @@ -33,7 +33,7 @@ def utility_crra( consumption: jnp.array, choice: int, - params: Dict[str, float], + params: dict[str, float], ) -> jnp.array: """Computes the agent's current utility based on a CRRA utility function. diff --git a/tests/test_jorg_drued_jax.py b/tests/test_jorg_drued_jax.py index 0f88c60..801e21e 100644 --- a/tests/test_jorg_drued_jax.py +++ b/tests/test_jorg_drued_jax.py @@ -33,7 +33,7 @@ def utility_crra( - consumption: jnp.ndarray, choice: int, params: Dict[str, float] + consumption: jnp.ndarray, choice: int, params: dict[str, float] ) -> jnp.ndarray: utility_consumption = (consumption ** (1 - params["rho"]) - 1) / (1 - params["rho"]) utility = utility_consumption - (1 - choice) * params["delta"] diff --git a/tests/utils/fast_upper_envelope_org.py b/tests/utils/fast_upper_envelope_org.py index 6db12ad..1da4149 100644 --- a/tests/utils/fast_upper_envelope_org.py +++ b/tests/utils/fast_upper_envelope_org.py @@ -5,7 +5,8 @@ https://dx.doi.org/10.2139/ssrn.4181302 """ -from typing import Callable, Optional, Tuple +from collections.abc import Callable +from typing import Optional, Tuple import numpy as np @@ -17,7 +18,7 @@ def fast_upper_envelope_wrapper_org( exog_grid: np.ndarray, choice: int, # noqa: U100 compute_utility: Callable, # noqa: U100 -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Drop suboptimal points and refine the endogenous grid, policy, and value. Computes the upper envelope over the overlapping segments of the @@ -95,9 +96,9 @@ def fast_upper_envelope( value: np.ndarray, policy: np.ndarray, exog_grid: np.ndarray, - jump_thresh: Optional[float] = 2, - b: Optional[float] = 1e-10, -) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + jump_thresh: float | None = 2, + b: float | None = 1e-10, +) -> tuple[np.ndarray, np.ndarray, np.ndarray]: """Remove suboptimal points from the endogenous grid, policy, and value function. Args: @@ -289,7 +290,7 @@ def _scan_org(e_grid, vf, c, a_prime, m_bar, lb, fwd_scan_do=True): # noqa: U10 j = np.copy(np.array([i]))[0] + 1 else: - previous_opt_is_intersect = False # noqa: F841 + previous_opt_is_intersect = False if g_1 > g_j_minus_1: previous_opt_is_intersect = True # noqa: F841 diff --git a/tests/utils/interpolation.py b/tests/utils/interpolation.py index 4cfdbcb..c7c03c8 100644 --- a/tests/utils/interpolation.py +++ b/tests/utils/interpolation.py @@ -141,7 +141,7 @@ def linear_interpolation_formula( def get_index_high_and_low( x: np.ndarray, x_new: np.ndarray | float -) -> Tuple[np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: """Get index of the highest value in x that is smaller than x_new. Args: diff --git a/tests/utils/upper_envelope_fedor.py b/tests/utils/upper_envelope_fedor.py index 9895e76..e78532f 100644 --- a/tests/utils/upper_envelope_fedor.py +++ b/tests/utils/upper_envelope_fedor.py @@ -5,7 +5,8 @@ """ -from typing import Callable, Dict, List, Tuple +from collections.abc import Callable +from typing import Dict, List, Tuple import numpy as np from scipy.optimize import brenth as root @@ -17,10 +18,10 @@ def upper_envelope( policy: np.ndarray, value: np.ndarray, exog_grid: np.ndarray, - state_choice_vec: Dict, - params: Dict[str, float], + state_choice_vec: dict, + params: dict[str, float], compute_utility: Callable, -) -> Tuple[np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: """Runs the Upper Envelope algorithm and drops sub-optimal points. Calculates the upper envelope over the overlapping segments of the decision-specific value functions, which in fact are value "correspondences" @@ -133,7 +134,7 @@ def upper_envelope( def locate_non_concave_regions( value: np.ndarray, -) -> List[np.ndarray]: +) -> list[np.ndarray]: """Locates non-concave regions. Find non-monotonicity in the endogenous wealth grid where a grid point to the right is smaller than its preceding point. Put differently, the @@ -200,8 +201,8 @@ def locate_non_concave_regions( def compute_upper_envelope( - segments: List[np.ndarray], -) -> Tuple[np.ndarray, np.ndarray]: + segments: list[np.ndarray], +) -> tuple[np.ndarray, np.ndarray]: """Compute upper envelope and refines value function correspondence. The upper envelope algorithm detects suboptimal points in the value function correspondence. Consequently, (i) the suboptimal points are removed and the @@ -531,7 +532,7 @@ def _augment_grid( n_grid_wealth: int, params, compute_utility: Callable, -) -> Tuple[np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: """Extends the endogenous wealth grid, value, and policy function to the left. Args: @@ -563,7 +564,6 @@ def _augment_grid( Shape (2, *n_grid_augmented*). """ - grid_points_to_add = np.linspace(min_wealth_grid, value[0, 1], n_grid_wealth // 10)[ :-1 ] @@ -593,7 +593,7 @@ def _augment_grid( def _partition_grid( value_correspondence: np.ndarray, j: int -) -> Tuple[np.ndarray, np.ndarray]: +) -> tuple[np.ndarray, np.ndarray]: """Splits the grid into two parts, 1,..., j and j, j+1,..., J. Note that the index ``j``, after which the separation occurs,