Skip to content
This repository was archived by the owner on Nov 17, 2025. It is now read-only.

Commit 22f463c

Browse files
Add take_along_axis function
1 parent 69dc7d1 commit 22f463c

2 files changed

Lines changed: 90 additions & 0 deletions

File tree

aesara/tensor/basic.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from typing import Dict, Tuple, Union
1616

1717
import numpy as np
18+
from numpy.core.multiarray import normalize_axis_index
1819

1920
import aesara
2021
import aesara.scalar.sharedvar
@@ -4347,7 +4348,55 @@ def expand_dims(
43474348
return a.reshape(shape)
43484349

43494350

4351+
def _make_along_axis_idx(arr_shape, indices, axis):
4352+
"""Take from `numpy.lib.shape_base`."""
4353+
# compute dimensions to iterate over
4354+
if str(indices.dtype) not in int_dtypes:
4355+
raise IndexError("`indices` must be an integer array")
4356+
shape_ones = (1,) * indices.ndim
4357+
dest_dims = list(range(axis)) + [None] + list(range(axis + 1, indices.ndim))
4358+
4359+
# build a fancy index, consisting of orthogonal aranges, with the
4360+
# requested index inserted at the right location
4361+
fancy_index = []
4362+
for dim, n in zip(dest_dims, arr_shape):
4363+
if dim is None:
4364+
fancy_index.append(indices)
4365+
else:
4366+
ind_shape = shape_ones[:dim] + (-1,) + shape_ones[dim + 1 :]
4367+
fancy_index.append(arange(n).reshape(ind_shape))
4368+
4369+
return tuple(fancy_index)
4370+
4371+
4372+
def take_along_axis(arr, indices, axis=0):
4373+
"""Take values from the input array by matching 1d index and data slices.
4374+
4375+
This iterates over matching 1d slices oriented along the specified axis in
4376+
the index and data arrays, and uses the former to look up values in the
4377+
latter. These slices can be different lengths.
4378+
4379+
Functions returning an index along an axis, like `argsort` and
4380+
`argpartition`, produce suitable indices for this function.
4381+
"""
4382+
arr = as_tensor_variable(arr)
4383+
indices = as_tensor_variable(indices)
4384+
# normalize inputs
4385+
if axis is None:
4386+
arr = arr.flatten()
4387+
axis = 0
4388+
else:
4389+
axis = normalize_axis_index(axis, arr.ndim)
4390+
4391+
if arr.ndim != indices.ndim:
4392+
raise ValueError("`indices` and `arr` must have the same number of dimensions")
4393+
4394+
# use the fancy index
4395+
return arr[_make_along_axis_idx(arr.shape, indices, axis)]
4396+
4397+
43504398
__all__ = [
4399+
"take_along_axis",
43514400
"expand_dims",
43524401
"atleast_Nd",
43534402
"atleast_1d",

tests/tensor/test_basic.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4175,3 +4175,44 @@ def test_expand_dims():
41754175
exp_res = np.expand_dims(x_val, (2, 1))
41764176
res_val = aesara.function([x_at], res_at)(x_val)
41774177
assert np.array_equal(exp_res, res_val)
4178+
4179+
4180+
class TestTakeAlongAxis:
4181+
@pytest.mark.parametrize(
4182+
["shape", "axis", "samples"],
4183+
(
4184+
((1,), None, 1),
4185+
((1,), -1, 10),
4186+
((3, 2, 1), -1, 1),
4187+
((3, 2, 1), 0, 10),
4188+
((3, 2, 1), -1, 10),
4189+
),
4190+
ids=str,
4191+
)
4192+
def test_take_along_axis(self, shape, axis, samples):
4193+
rng = np.random.default_rng()
4194+
arr = rng.normal(size=shape).astype(config.floatX)
4195+
indices_size = list(shape)
4196+
indices_size[axis or 0] = samples
4197+
indices = rng.integers(low=0, high=shape[axis or 0], size=indices_size)
4198+
4199+
arr_in = aet.tensor(config.floatX, [s == 1 for s in arr.shape])
4200+
indices_in = aet.tensor(np.int64, [s == 1 for s in indices.shape])
4201+
4202+
out = aet.take_along_axis(arr_in, indices_in, axis)
4203+
4204+
func = aesara.function([arr_in, indices_in], out)
4205+
4206+
assert np.allclose(
4207+
np.take_along_axis(arr, indices, axis=axis), func(arr, indices)
4208+
)
4209+
4210+
def test_ndim_dtype_failures(self):
4211+
arr = aet.tensor(config.floatX, [False] * 2)
4212+
indices = aet.tensor(np.int64, [False] * 3)
4213+
with pytest.raises(ValueError):
4214+
aet.take_along_axis(arr, indices)
4215+
4216+
indices = aet.tensor(np.float64, [False] * 2)
4217+
with pytest.raises(IndexError):
4218+
aet.take_along_axis(arr, indices)

0 commit comments

Comments
 (0)