提交 4c8c8b6e authored 作者: Brendan Murphy's avatar Brendan Murphy 提交者: Ricardo Vieira

Preserve numpy < 2.0 Unique inverse output shape

In numpy 2.0, if axis=None, then np.unique does not flatten the inverse indices returned if return_inverse=True A helper function has been added to npy_2_compat.py to mimic the output of `np.unique` from version of numpy before 2.0
上级 4d74d13a
......@@ -63,6 +63,28 @@ else:
numpy_maxdims = 64 if using_numpy_2 else 32
# function that replicates np.unique from numpy < 2.0
def old_np_unique(
arr, return_index=False, return_inverse=False, return_counts=False, axis=None
):
"""Replicate np.unique from numpy versions < 2.0"""
if not return_inverse or not using_numpy_2:
return np.unique(arr, return_index, return_inverse, return_counts, axis)
outs = list(np.unique(arr, return_index, return_inverse, return_counts, axis))
inv_idx = 2 if return_index else 1
if axis is None:
outs[inv_idx] = np.ravel(outs[inv_idx])
else:
inv_shape = (arr.shape[axis],)
outs[inv_idx] = outs[inv_idx].reshape(inv_shape)
return tuple(outs)
# compatibility header for C code
def npy_2_compat_header() -> str:
"""Compatibility header that Numpy suggests is vendored with code that uses Numpy < 2.0 and Numpy 2.x"""
return dedent("""
......
......@@ -20,6 +20,7 @@ from pytensor.npy_2_compat import (
normalize_axis_index,
npy_2_compat_header,
numpy_axis_is_none_flag,
old_np_unique,
)
from pytensor.raise_op import Assert
from pytensor.scalar import int64 as int_t
......@@ -1226,6 +1227,9 @@ class Unique(Op):
"""
Wraps `numpy.unique`.
The indices returned when `return_inverse` is True are ravelled
to match the behavior of `numpy.unique` from before numpy version 2.0.
Examples
--------
>>> import numpy as np
......@@ -1271,17 +1275,21 @@ class Unique(Op):
outputs = [TensorType(dtype=x.dtype, shape=out_shape)()]
typ = TensorType(dtype="int64", shape=(None,))
if self.return_index:
outputs.append(typ())
if self.return_inverse:
outputs.append(typ())
if self.return_counts:
outputs.append(typ())
return Apply(self, [x], outputs)
def perform(self, node, inputs, output_storage):
[x] = inputs
outs = np.unique(
outs = old_np_unique(
x,
return_index=self.return_index,
return_inverse=self.return_inverse,
......@@ -1306,9 +1314,14 @@ class Unique(Op):
out_shapes[0] = tuple(shape)
if self.return_inverse:
shape = prod(x_shape) if self.axis is None else x_shape[axis]
return_index_out_idx = 2 if self.return_index else 1
out_shapes[return_index_out_idx] = (shape,)
if self.axis is not None:
shape = (x_shape[axis],)
else:
shape = (prod(x_shape),)
out_shapes[return_index_out_idx] = shape
return out_shapes
......
......@@ -9,6 +9,7 @@ from pytensor import tensor as pt
from pytensor.compile.mode import Mode
from pytensor.configdefaults import config
from pytensor.graph.basic import Constant, applys_between, equal_computations
from pytensor.npy_2_compat import old_np_unique
from pytensor.raise_op import Assert
from pytensor.tensor import alloc
from pytensor.tensor.elemwise import DimShuffle
......@@ -899,14 +900,14 @@ class TestUnique(utt.InferShapeTester):
)
def test_basic_vector(self, x, inp, axis):
list_outs_expected = [
np.unique(inp, axis=axis),
np.unique(inp, True, axis=axis),
np.unique(inp, False, True, axis=axis),
np.unique(inp, True, True, axis=axis),
np.unique(inp, False, False, True, axis=axis),
np.unique(inp, True, False, True, axis=axis),
np.unique(inp, False, True, True, axis=axis),
np.unique(inp, True, True, True, axis=axis),
old_np_unique(inp, axis=axis),
old_np_unique(inp, True, axis=axis),
old_np_unique(inp, False, True, axis=axis),
old_np_unique(inp, True, True, axis=axis),
old_np_unique(inp, False, False, True, axis=axis),
old_np_unique(inp, True, False, True, axis=axis),
old_np_unique(inp, False, True, True, axis=axis),
old_np_unique(inp, True, True, True, axis=axis),
]
for params, outs_expected in zip(
self.op_params, list_outs_expected, strict=True
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论