Skip to content

Conversation

@Enderdead
Copy link
Contributor

This merge request introduces the kron delegate function (#100).

All supported backends already implement the kron function. However, I wasn’t able to add support for the sparse backend because it requires all input sparse matrices to have a fill_value equal to zero (not the case in the test) - likely to avoid generating a large dense matrix.

If you’d like to discuss potential support for kron in PyData Sparse, please let me know!

Feedback is welcome.

@lucascolley lucascolley self-requested a review November 9, 2025 16:07
@lucascolley lucascolley added this to the 0.9.1 milestone Nov 9, 2025
@Enderdead
Copy link
Contributor Author

The test fails with the tests-numpy1 environment. I’m not able to reproduce this behavior on my machine (weird...).
The issue occurs when an input shape contains a zero.

I can update my delegation strategy to avoid delegating when the product of the shape elements equals zero (when using NumPy).

What do you think about this?

@lucascolley lucascolley added the enhancement New feature or request label Nov 9, 2025
@lucascolley
Copy link
Member

pixi run -e tests-numpy1 tests does reproduce the failures on my machine

@Enderdead
Copy link
Contributor Author

@lucascolley I'm a bit confused, even with your command, I still can't reproduce the issue.

~/array-api-extra$ pixi run -e tests-numpy1 tests tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]
✨ Pixi task (tests in tests-numpy1): pytest -v tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]: (Run tests)                                     
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.10.19, pytest-7.4.4, pluggy-1.4.0 -- /home/francois/array-api-extra/.pixi/envs/tests-numpy1/bin/python3.10
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/francois/array-api-extra
configfile: pyproject.toml
plugins: typeguard-2.13.3, hypothesis-6.142.3, cov-7.0.0
collected 1 item                                                                                                                                                        

tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3] PASSED

@lucascolley
Copy link
Member

what is your pixi list? For both me locally and CI, I see pytest is version 8.4.2, but your output shows pytest-7.4.4.

Maybe pixi run --locked makes a difference?

@Enderdead
Copy link
Contributor Author

Here is the information you requested:

~/array-api-extra$ pixi reinstall -e tests-numpy1
✔ The tests-numpy1 environment has been re-installed.
~/array-api-extra$ pixi list
Package           Version     Build               Size       Kind   Source
_libgcc_mutex     0.1         conda_forge         2.5 KiB    conda  https://prefix.dev/conda-forge/
_openmp_mutex     4.5         2_gnu               23.1 KiB   conda  https://prefix.dev/conda-forge/
array-api-compat  1.12.0      pyhe01879c_0        44.7 KiB   conda  https://prefix.dev/conda-forge/
array_api_extra   0.9.1.dev0                                 pypi    (editable)
bzip2             1.0.8       hda65f42_8          254.2 KiB  conda  https://prefix.dev/conda-forge/
ca-certificates   2025.10.5   hbd8a1cb_0          152.3 KiB  conda  https://prefix.dev/conda-forge/
ld_impl_linux-64  2.44        ha97dd6f_2          729.6 KiB  conda  https://prefix.dev/conda-forge/
libexpat          2.7.1       hecca717_0          73.1 KiB   conda  https://prefix.dev/conda-forge/
libffi            3.5.2       h9ec8514_0          56.5 KiB   conda  https://prefix.dev/conda-forge/
libgcc            15.2.0      h767d61c_7          803.3 KiB  conda  https://prefix.dev/conda-forge/
libgomp           15.2.0      h767d61c_7          437.4 KiB  conda  https://prefix.dev/conda-forge/
liblzma           5.8.1       hb9d3cd8_2          110.2 KiB  conda  https://prefix.dev/conda-forge/
libmpdec          4.0.0       hb9d3cd8_0          89 KiB     conda  https://prefix.dev/conda-forge/
libsqlite         3.50.4      h0c1763c_0          910.7 KiB  conda  https://prefix.dev/conda-forge/
libuuid           2.41.2      he9a06e4_0          36.3 KiB   conda  https://prefix.dev/conda-forge/
libzlib           1.3.1       hb9d3cd8_2          59.5 KiB   conda  https://prefix.dev/conda-forge/
ncurses           6.5         h2d0b736_3          870.7 KiB  conda  https://prefix.dev/conda-forge/
openssl           3.5.4       h26f9b46_0          3 MiB      conda  https://prefix.dev/conda-forge/
python            3.13.9      hc97d973_101_cp313  35.5 MiB   conda  https://prefix.dev/conda-forge/
python_abi        3.13        8_cp313             6.8 KiB    conda  https://prefix.dev/conda-forge/
readline          8.2         h8c095d6_2          275.9 KiB  conda  https://prefix.dev/conda-forge/
tk                8.6.13      noxft_hd72426e_102  3.1 MiB    conda  https://prefix.dev/conda-forge/
tzdata            2025b       h78e105d_0          120.1 KiB  conda  https://prefix.dev/conda-forge/
~/array-api-extra$ pixi run --locked -e tests-numpy1 tests tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]
✨ Pixi task (tests in tests-numpy1): pytest -v tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]: (Run tests)                                     
========================================================================== test session starts ==========================================================================
platform linux -- Python 3.10.19, pytest-7.4.4, pluggy-1.4.0 -- /home/francois/array-api-extra/.pixi/envs/tests-numpy1/bin/python3.10
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/francois/array-api-extra
configfile: pyproject.toml
plugins: typeguard-2.13.3, hypothesis-6.142.3, cov-7.0.0
collected 1 item                                                                                                                                                        

tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3] PASSED  
~/array-api-extra$ git status
Sur la branche kron_delegate
Votre branche est à jour avec 'origin/kron_delegate'.

rien à valider, la copie de travail est propre

@lucascolley
Copy link
Member

ah sorry, I meant pixi list -e tests-numpy1

@Enderdead
Copy link
Contributor Author

This is the pixi list. I just want to mention that I have the same behaviour with two different computer using kubuntu.

pixi list -e tests-numpy1
Environment: tests-numpy1
Package            Version     Build                 Size       Kind   Source
_libgcc_mutex      0.1         conda_forge           2.5 KiB    conda  https://prefix.dev/conda-forge/
_openmp_mutex      4.5         2_gnu                 23.1 KiB   conda  https://prefix.dev/conda-forge/
array-api-compat   1.12.0      pyhe01879c_0          44.7 KiB   conda  https://prefix.dev/conda-forge/
array-api-strict   2.4.1       pyhe01879c_0          61.1 KiB   conda  https://prefix.dev/conda-forge/
array_api_extra    0.9.1.dev0                                   pypi    (editable)
attrs              25.4.0      pyh71513ae_0          58.7 KiB   conda  https://prefix.dev/conda-forge/
bzip2              1.0.8       hda65f42_8            254.2 KiB  conda  https://prefix.dev/conda-forge/
ca-certificates    2025.10.5   hbd8a1cb_0            152.3 KiB  conda  https://prefix.dev/conda-forge/
click              8.3.0       pyh707e725_0          89.5 KiB   conda  https://prefix.dev/conda-forge/
colorama           0.4.6       pyhd8ed1ab_1          26.4 KiB   conda  https://prefix.dev/conda-forge/
coverage           7.11.0      py310h3406613_0       298.7 KiB  conda  https://prefix.dev/conda-forge/
exceptiongroup     1.3.0       pyhd8ed1ab_0          20.8 KiB   conda  https://prefix.dev/conda-forge/
hypothesis         6.142.3     pyha770c72_0          370.7 KiB  conda  https://prefix.dev/conda-forge/
iniconfig          2.3.0       pyhd8ed1ab_0          13.1 KiB   conda  https://prefix.dev/conda-forge/
ld_impl_linux-64   2.44        ha97dd6f_2            729.6 KiB  conda  https://prefix.dev/conda-forge/
libblas            3.9.0       37_h4a7cf45_openblas  17.1 KiB   conda  https://prefix.dev/conda-forge/
libcblas           3.9.0       37_h0358290_openblas  17.1 KiB   conda  https://prefix.dev/conda-forge/
libexpat           2.7.1       hecca717_0            73.1 KiB   conda  https://prefix.dev/conda-forge/
libffi             3.5.2       h9ec8514_0            56.5 KiB   conda  https://prefix.dev/conda-forge/
libgcc             15.2.0      h767d61c_7            803.3 KiB  conda  https://prefix.dev/conda-forge/
libgcc-ng          15.2.0      h69a702a_7            28.6 KiB   conda  https://prefix.dev/conda-forge/
libgfortran        15.2.0      h69a702a_7            28.6 KiB   conda  https://prefix.dev/conda-forge/
libgfortran5       15.2.0      hcd61629_7            1.5 MiB    conda  https://prefix.dev/conda-forge/
libgomp            15.2.0      h767d61c_7            437.4 KiB  conda  https://prefix.dev/conda-forge/
liblapack          3.9.0       37_h47877c9_openblas  17.1 KiB   conda  https://prefix.dev/conda-forge/
liblzma            5.8.1       hb9d3cd8_2            110.2 KiB  conda  https://prefix.dev/conda-forge/
libnsl             2.0.1       hb9d3cd8_1            32.9 KiB   conda  https://prefix.dev/conda-forge/
libopenblas        0.3.30      pthreads_h94d23a6_2   5.7 MiB    conda  https://prefix.dev/conda-forge/
libsqlite          3.50.4      h0c1763c_0            910.7 KiB  conda  https://prefix.dev/conda-forge/
libstdcxx          15.2.0      h8f9b012_7            3.7 MiB    conda  https://prefix.dev/conda-forge/
libstdcxx-ng       15.2.0      h4852527_7            28.7 KiB   conda  https://prefix.dev/conda-forge/
libuuid            2.41.2      he9a06e4_0            36.3 KiB   conda  https://prefix.dev/conda-forge/
libxcrypt          4.4.36      hd590300_1            98 KiB     conda  https://prefix.dev/conda-forge/
libzlib            1.3.1       hb9d3cd8_2            59.5 KiB   conda  https://prefix.dev/conda-forge/
ncurses            6.5         h2d0b736_3            870.7 KiB  conda  https://prefix.dev/conda-forge/
numpy              1.22.0      py310h454958d_1       19.4 MiB   conda  https://prefix.dev/conda-forge/
openssl            3.5.4       h26f9b46_0            3 MiB      conda  https://prefix.dev/conda-forge/
packaging          25.0        pyh29332c3_1          61 KiB     conda  https://prefix.dev/conda-forge/
pluggy             1.6.0       pyhd8ed1ab_0          23.7 KiB   conda  https://prefix.dev/conda-forge/
pygments           2.19.2      pyhd8ed1ab_0          868.4 KiB  conda  https://prefix.dev/conda-forge/
pytest             8.4.2       pyhd8ed1ab_0          270.2 KiB  conda  https://prefix.dev/conda-forge/
pytest-cov         7.0.0       pyhcf101f3_1          28.3 KiB   conda  https://prefix.dev/conda-forge/
python             3.10.19     h3c07f61_2_cpython    24.1 MiB   conda  https://prefix.dev/conda-forge/
python_abi         3.10        8_cp310               6.8 KiB    conda  https://prefix.dev/conda-forge/
readline           8.2         h8c095d6_2            275.9 KiB  conda  https://prefix.dev/conda-forge/
setuptools         80.9.0      pyhff2d567_0          731.2 KiB  conda  https://prefix.dev/conda-forge/
sortedcontainers   2.4.0       pyhd8ed1ab_1          28 KiB     conda  https://prefix.dev/conda-forge/
tk                 8.6.13      noxft_hd72426e_102    3.1 MiB    conda  https://prefix.dev/conda-forge/
tomli              2.3.0       pyhcf101f3_0          20.5 KiB   conda  https://prefix.dev/conda-forge/
typing_extensions  4.15.0      pyhcf101f3_0          50.5 KiB   conda  https://prefix.dev/conda-forge/
tzdata             2025b       h78e105d_0            120.1 KiB  conda  https://prefix.dev/conda-forge/

@lucascolley
Copy link
Member

Do you have pytest installed globally? There is definitely a problem if pytest-7.4.4 is being used in your test runs, since your pixi list shows 8.4.2.

@lucascolley lucascolley removed their request for review November 16, 2025 14:14
@Enderdead
Copy link
Contributor Author

I managed to reinstall the environment using Pixi and I now have the correct pytest version, but I still can pass the test.
Could you at least run it in verbose mode and send me the logs? I’ll try to fix the issue based on your logs and the GitLab CI.

pixi run --locked -e tests-numpy1 tests tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]
✨ Pixi task (tests in tests-numpy1): pytest -v tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3]: (Run tests)                                                       
=================================================================================== test session starts ===================================================================================
platform linux -- Python 3.10.19, pytest-8.4.2, pluggy-1.6.0 -- /home/francois/Documents/array-api-extra/.pixi/envs/tests-numpy1/bin/python3.10
cachedir: .pytest_cache
hypothesis profile 'default'
rootdir: /home/francois/Documents/array-api-extra
configfile: pyproject.toml
plugins: typeguard-4.1.5, anyio-3.6.2, cov-7.0.0, hypothesis-6.142.3
collected 1 item                                                                                                                                                                          

tests/test_funcs.py::TestKron::test_kron_shape[numpy-shape_a3-shape_b3] PASSED                                                                                                      [100%]

==================================================================================== 1 passed in 0.25s ====================================================================================

@lucascolley lucascolley self-requested a review November 21, 2025 10:32
@lucascolley
Copy link
Member

============================================================ FAILURES ============================================================
_______________________________________ TestKron.test_kron_shape[numpy-shape_a3-shape_b3] ________________________________________

self = <tests.test_funcs.TestKron object at 0x12058f370>
xp = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
shape_a = (1, 0), shape_b = (1, 1)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(1, 0), dtype=float64)
b          = array([[1.]])
expected_shape = (1, 0)
normalised_shape_a = array([1, 0])
normalised_shape_b = array([1, 1])
self       = <tests.test_funcs.TestKron object at 0x12058f370>
shape_a    = (1, 0)
shape_b    = (1, 1)
xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(1, 0), dtype=float64)
        b          = array([[1.]])
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(1, 0), dtype=float64), array([[1.]]))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(1, 0), dtype=float64), array([[1.]]))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(1, 0), dtype=float64)
        as_        = (1, 0)
        axis       = 1
        b          = array([[1.]])
        bs         = (1, 1)
        nd         = 2
        nda        = 2
        ndb        = 2
        result     = array([], shape=(0, 1, 1), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 1, 1), dtype=float64),), kwargs = {'axis': 1}
relevant_args = array([], shape=(0, 1, 1), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 1, 1), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 1}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 1, 1), dtype=float64)

<__array_function__ internals>:180: ValueError
_______________________________________ TestKron.test_kron_shape[numpy-shape_a4-shape_b4] ________________________________________

self = <tests.test_funcs.TestKron object at 0x12058f430>
xp = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
shape_a = (2, 0, 2), shape_b = (2, 2)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(2, 0, 2), dtype=float64)
b          = array([[1., 1.],
       [1., 1.]])
expected_shape = (2, 0, 4)
normalised_shape_a = array([2, 0, 2])
normalised_shape_b = array([1, 2, 2])
self       = <tests.test_funcs.TestKron object at 0x12058f430>
shape_a    = (2, 0, 2)
shape_b    = (2, 2)
xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(2, 0, 2), dtype=float64)
        b          = array([[1., 1.],
       [1., 1.]])
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(2, 0, 2), dtype=float64), array([[1., 1.],
       [1., 1.]]))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(2, 0, 2), dtype=float64), array([[1., 1.],
       [1., 1.]]))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(2, 0, 2), dtype=float64)
        as_        = (2, 0, 2)
        axis       = 2
        b          = array([[1., 1.],
       [1., 1.]])
        bs         = (1, 2, 2)
        nd         = 3
        nda        = 3
        ndb        = 2
        result     = array([], shape=(0, 2, 2, 2, 2), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 2, 2, 2, 2), dtype=float64),), kwargs = {'axis': 2}
relevant_args = array([], shape=(0, 2, 2, 2, 2), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 2, 2, 2, 2), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 2}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 2, 2, 2, 2), dtype=float64)

<__array_function__ internals>:180: ValueError
_______________________________________ TestKron.test_kron_shape[numpy-shape_a5-shape_b5] ________________________________________

self = <tests.test_funcs.TestKron object at 0x12058f4f0>
xp = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
shape_a = (2, 0, 0, 2), shape_b = (2, 0, 2)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(2, 0, 0, 2), dtype=float64)
b          = array([], shape=(2, 0, 2), dtype=float64)
expected_shape = (2, 0, 0, 4)
normalised_shape_a = array([2, 0, 0, 2])
normalised_shape_b = array([1, 2, 0, 2])
self       = <tests.test_funcs.TestKron object at 0x12058f4f0>
shape_a    = (2, 0, 0, 2)
shape_b    = (2, 0, 2)
xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(2, 0, 0, 2), dtype=float64)
        b          = array([], shape=(2, 0, 2), dtype=float64)
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(2, 0, 0, 2), dtype=float64), array([], shape=(2, 0, 2), dtype=float64))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(2, 0, 0, 2), dtype=float64), array([], shape=(2, 0, 2), dtype=float64))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(2, 0, 0, 2), dtype=float64)
        as_        = (2, 0, 0, 2)
        axis       = 3
        b          = array([], shape=(2, 0, 2), dtype=float64)
        bs         = (1, 2, 0, 2)
        nd         = 4
        nda        = 4
        ndb        = 3
        result     = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64),), kwargs = {'axis': 3}
relevant_args = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 3}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)

<__array_function__ internals>:180: ValueError
___________________________________ TestKron.test_kron_shape[numpy:readonly-shape_a3-shape_b3] ___________________________________

self = <tests.test_funcs.TestKron object at 0x12058f7f0>, xp = <tests.conftest.NumPyReadOnly object at 0x120564670>
shape_a = (1, 0), shape_b = (1, 1)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(1, 0), dtype=float64)
b          = array([[1.]])
expected_shape = (1, 0)
normalised_shape_a = array([1, 0])
normalised_shape_b = array([1, 1])
self       = <tests.test_funcs.TestKron object at 0x12058f7f0>
shape_a    = (1, 0)
shape_b    = (1, 1)
xp         = <tests.conftest.NumPyReadOnly object at 0x120564670>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(1, 0), dtype=float64)
        b          = array([[1.]])
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(1, 0), dtype=float64), array([[1.]]))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(1, 0), dtype=float64), array([[1.]]))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(1, 0), dtype=float64)
        as_        = (1, 0)
        axis       = 1
        b          = array([[1.]])
        bs         = (1, 1)
        nd         = 2
        nda        = 2
        ndb        = 2
        result     = array([], shape=(0, 1, 1), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 1, 1), dtype=float64),), kwargs = {'axis': 1}
relevant_args = array([], shape=(0, 1, 1), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 1, 1), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 1}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 1, 1), dtype=float64)

<__array_function__ internals>:180: ValueError
___________________________________ TestKron.test_kron_shape[numpy:readonly-shape_a4-shape_b4] ___________________________________

self = <tests.test_funcs.TestKron object at 0x12058f8b0>, xp = <tests.conftest.NumPyReadOnly object at 0x1204414e0>
shape_a = (2, 0, 2), shape_b = (2, 2)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(2, 0, 2), dtype=float64)
b          = array([[1., 1.],
       [1., 1.]])
expected_shape = (2, 0, 4)
normalised_shape_a = array([2, 0, 2])
normalised_shape_b = array([1, 2, 2])
self       = <tests.test_funcs.TestKron object at 0x12058f8b0>
shape_a    = (2, 0, 2)
shape_b    = (2, 2)
xp         = <tests.conftest.NumPyReadOnly object at 0x1204414e0>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(2, 0, 2), dtype=float64)
        b          = array([[1., 1.],
       [1., 1.]])
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(2, 0, 2), dtype=float64), array([[1., 1.],
       [1., 1.]]))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(2, 0, 2), dtype=float64), array([[1., 1.],
       [1., 1.]]))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(2, 0, 2), dtype=float64)
        as_        = (2, 0, 2)
        axis       = 2
        b          = array([[1., 1.],
       [1., 1.]])
        bs         = (1, 2, 2)
        nd         = 3
        nda        = 3
        ndb        = 2
        result     = array([], shape=(0, 2, 2, 2, 2), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 2, 2, 2, 2), dtype=float64),), kwargs = {'axis': 2}
relevant_args = array([], shape=(0, 2, 2, 2, 2), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 2, 2, 2, 2), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 2}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 2, 2, 2, 2), dtype=float64)

<__array_function__ internals>:180: ValueError
___________________________________ TestKron.test_kron_shape[numpy:readonly-shape_a5-shape_b5] ___________________________________

self = <tests.test_funcs.TestKron object at 0x12058f970>, xp = <tests.conftest.NumPyReadOnly object at 0x1204c09d0>
shape_a = (2, 0, 0, 2), shape_b = (2, 0, 2)

    @pytest.mark.parametrize(
        ("shape_a", "shape_b"),
        [
            ((1, 1), (1, 1)),
            ((1, 2, 3), (4, 5, 6)),
            ((2, 2), (2, 2, 2)),
            ((1, 0), (1, 1)),
            ((2, 0, 2), (2, 2)),
            ((2, 0, 0, 2), (2, 0, 2)),
        ],
    )
    def test_kron_shape(
        self, xp: ModuleType, shape_a: tuple[int, ...], shape_b: tuple[int, ...]
    ):
        a = xp.ones(shape_a)
        b = xp.ones(shape_b)
        normalised_shape_a = xp.asarray(
            (1,) * max(0, len(shape_b) - len(shape_a)) + shape_a
        )
        normalised_shape_b = xp.asarray(
            (1,) * max(0, len(shape_a) - len(shape_b)) + shape_b
        )
        expected_shape = tuple(
            int(dim) for dim in xp.multiply(normalised_shape_a, normalised_shape_b)
        )

>       k = kron(a, b)

a          = array([], shape=(2, 0, 0, 2), dtype=float64)
b          = array([], shape=(2, 0, 2), dtype=float64)
expected_shape = (2, 0, 0, 4)
normalised_shape_a = array([2, 0, 0, 2])
normalised_shape_b = array([1, 2, 0, 2])
self       = <tests.test_funcs.TestKron object at 0x12058f970>
shape_a    = (2, 0, 0, 2)
shape_b    = (2, 0, 2)
xp         = <tests.conftest.NumPyReadOnly object at 0x1204c09d0>

tests/test_funcs.py:971:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
src/array_api_extra/_delegation.py:510: in kron
    return xp.kron(a, b)
        a          = array([], shape=(2, 0, 0, 2), dtype=float64)
        b          = array([], shape=(2, 0, 2), dtype=float64)
        xp         = <module 'array_api_compat.numpy' from '/Users/lucascolley/ghq/github.com/data-apis/array-api-extra/.pixi/envs/tests-numpy1/lib/python3.10/site-packages/array_api_compat/numpy/__init__.py'>
<__array_function__ internals>:180: in kron
    ???
        args       = (array([], shape=(2, 0, 0, 2), dtype=float64), array([], shape=(2, 0, 2), dtype=float64))
        dispatcher = <function _kron_dispatcher at 0x10419d6c0>
        implementation = <function kron at 0x10419d7e0>
        kwargs     = {}
        public_api = <function kron at 0x10419d870>
        relevant_args = (array([], shape=(2, 0, 0, 2), dtype=float64), array([], shape=(2, 0, 2), dtype=float64))
.pixi/envs/tests-numpy1/lib/python3.10/site-packages/numpy/lib/shape_base.py:1157: in kron
    result = concatenate(result, axis=axis)
        _          = 1
        a          = array([], shape=(2, 0, 0, 2), dtype=float64)
        as_        = (2, 0, 0, 2)
        axis       = 3
        b          = array([], shape=(2, 0, 2), dtype=float64)
        bs         = (1, 2, 0, 2)
        nd         = 4
        nda        = 4
        ndb        = 3
        result     = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = (array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64),), kwargs = {'axis': 3}
relevant_args = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)

>   ???
E   ValueError: need at least one array to concatenate

args       = (array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64),)
dispatcher = <function concatenate at 0x10382e830>
implementation = <built-in function concatenate>
kwargs     = {'axis': 3}
public_api = <function concatenate at 0x10382e9e0>
relevant_args = array([], shape=(0, 0, 2, 2, 2, 0, 2), dtype=float64)

<__array_function__ internals>:180: ValueError

@lucascolley lucascolley removed their request for review November 21, 2025 21:51
@lucascolley lucascolley modified the milestones: 0.9.1, 0.9.2 Nov 21, 2025
@Enderdead
Copy link
Contributor Author

After some investigation, I found the root cause of the failing tests: the issue occurs when the left input array has at least one dimension of size zero.

This appears to be a bug related to an older implementation of the kron operator. I tested several NumPy versions, and the code works correctly starting from NumPy 1.23.0. It seems that the performance improvements to kron introduced in numpy/numpy#21354 resolve this issue.

How would you like to proceed?
Do we already have a strategy in place for handling differences across backend versions (If we want to delegate as much as possible) ?

@lucascolley
Copy link
Member

How would you like to proceed?

I'm fine with just skipping the tests when is_numpy(xp) and np.__version__ <= .... WDYT?

@Enderdead
Copy link
Contributor Author

This change is a bit risky because the code will fail for users running NumPy < 1.23.
It would be safer to either skip delegation when NumPy < 2.0, or more specifically when NumPy < 1.23.

If feasible, we should add a runtime check to conditionally disable delegation for unsupported NumPy versions.

@lucascolley
Copy link
Member

It would be safer to either skip delegation when NumPy < 2.0, or more specifically when NumPy < 1.23.

ah yes, that sounds good to me too

@Enderdead
Copy link
Contributor Author

Another question I have is that I wasn’t able to reproduce the test failure on my side.
What happens when you run the following commands? Are you using NumPy 1.22 or 1.23?

(array-api-extra:tests-numpy1) francois@ordinateur:~/array-api-extra$ python3
Python 3.10.19 | packaged by conda-forge | (main, Oct 22 2025, 22:29:10) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
Ctrl click to launch VS Code Native REPL
>>> import numpy as np
>>> np.__version__
'1.23.5'

@lucascolley
Copy link
Member

hmmm, apparently neither!

array-api-extra on 🎋 main is 📦 v0.9.2.dev0 via 🐍 v3.10.19 via 🧚 v0.60.0 (tests-numpy1)python3
Python 3.10.19 | packaged by conda-forge | (main, Oct 22 2025, 22:29:10) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> np.__version__
'1.25.0.dev0+1465.g126b46c7a'

@lucascolley
Copy link
Member

so it is finding the wrong numpy for some reason:

array-api-extra on 🎋 main is 📦 v0.9.2.dev0 via 🐍 v3.10.19 via 🧚 v0.60.0 (tests-numpy1)python
Python 3.10.19 | packaged by conda-forge | (main, Oct 22 2025, 22:29:10) [GCC 14.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy as np
>>> np.__file__
'/home/lucas/.local/lib/python3.10/site-packages/numpy/__init__.py'

it should be picking up 1.22 on both linux and osx, though:

array-api-extra on 🎋 main is 📦 v0.9.2.dev0 via 🐍 via 🧚 v0.60.0pixi ls -e tests-numpy1 numpy --platform=linux-64
Environment: tests-numpy1
Package  Version  Build            Size      Kind   Source
numpy    1.22.0   py310h454958d_1  19.4 MiB  conda  https://prefix.dev/conda-forge/

array-api-extra on 🎋 main is 📦 v0.9.2.dev0 via 🐍 via 🧚 v0.60.0pixi ls -e tests-numpy1 numpy --platform=osx-arm64
Environment: tests-numpy1
Package  Version  Build            Size   Kind   Source
numpy    1.22.0   py310h567df17_1  6 MiB  conda  https://prefix.dev/conda-forge/

@Enderdead
Copy link
Contributor Author

Should we open a dedicated issue on the Pixi repository to investigate this further?

@lucascolley
Copy link
Member

a more minimal reproducer would be helpful first, I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

delegation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants