You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
BUG: numpy.einsum with ellipses has different behavior when optimize=True or False. #30349
The following einsum works as expected when optimize=True but throws a ValueError when optimize=False. I believe the correct behavior is the one from optimize=True.
Reproduce the code example:
importnumpyasnpx=np.array([[[1,2,3], [1,2,3], [1,2,3]], [[1,2,3], [1,2,3], [1,2,3]], [[1,2,3], [1,2,3], [1,2,3]]])
np.einsum('i...->i', x, optimize=True)
# array([18, 18, 18])np.einsum('i...->i', x)
# ValueError: output has more dimensions than subscripts given in einstein sum, but no '...' ellipsis provided to broadcast the extra dimensions
Error message:
Traceback (most recent call last):
File "<python-input-0>", line 5, in<module>
np.einsum('i...->i', x)
~~~~~~~~~^^^^^^^^^^^^^^
File "[...]/venv/lib/python3.13/site-packages/numpy/_core/einsumfunc.py", line 1423, in einsum
return c_einsum(*operands, **kwargs)
ValueError: output has more dimensions than subscripts given in einstein sum, but no '...' ellipsis provided to broadcast the extra dimensions.
Python and NumPy Versions:
2.3.3
3.13.7 (main, Aug 15 2025, 12:34:02) [GCC 15.2.1 20250813]
This was one of the first things I tried after learning about einsum. It seemed like a simpler alternative to summing over every axis except the first in a tensor whose number of dimensions is not known in advance.
This needs new logic I would imagine. My understanding is that ... is implemented as indicated broadcasting dimensions and while this overloads it as an arbitrary number of reduce dimensions with the name ....
It gets a bit more unclear to me that this works:
In [17]: np.einsum("i...,i...->i", x, x[0], optimize=True)
Out[17]: array([42, 42, 42])
where I guess the ... of the second operand are actually broadcast to fit the first (not sure, it might be weirder). It isn't clear to me that the intention is clear there.
I do agree that this intention seems like a sensible generalization, but it is a bit unclear that broadcasting is actually sensible for reduced dimensions with multiple operands.
In [17]: np.einsum("i...,i...->i", x, x[0], optimize=True)
Out[17]: array([42, 42, 42])
Yes this I agree is not well defined and should probably error, in my opinion.
I always assumed the behavior of ellipses was "replace ... everywhere with a matching sequence of indices, inferred from the inputs". That includes broadcasting, but also cases where the ellipses don't appear in the output or some inputs, but not the above case.
Yeah, I guess it was written as indicating outer dimensions. Which may be slightly odd, because it effectively means we constantly write ellipsis everywhere since most functionality should allow an outer iteration.
I am slightly surprised about the use-case of i...->i for truly arbitrary number of dimensions, but it isn't like we support the gufunc notion of a ? dimensions here to write ijk?->i (supporting not arbitrary dimensions, but a specific variable number).
Apologies for potentially taking over, I hope this isn't poor practice (I am new to contributing to open source projects).
I have opened a pull request that solves this specific issue as described above; relating to the rest of the discussion, in particular the fact that np.einsum("i...,i...->i", x, x[0], optimize=True) should error, I do agree that it makes sense and that the logic of the handling of the ellipses should probably be changed.
However, I also noticed that the milestone for this specific bugfix was set to 2.4.1; considering that making that syntax not work anymore would, I suppose, be a breaking change, I felt that it might be better to do that in a separate pull request.
The one I made as far as I'm aware is not breaking.
Activity
mattip commentedon Dec 1, 2025
@jcmgray could I bother you to take a look?
jcmgray commentedon Dec 1, 2025
Seems to be a genuine bug (or just unsupported case) in the equation parsing of
c_einsum, triggering this here -numpy/numpy/_core/src/multiarray/einsum.cpp
Lines 231 to 238 in fb0578d
mattip commentedon Dec 1, 2025
OK, thanks for peeking
seberg commentedon Dec 2, 2025
This needs new logic I would imagine. My understanding is that
...is implemented as indicated broadcasting dimensions and while this overloads it as an arbitrary number of reduce dimensions with the name....It gets a bit more unclear to me that this works:
where I guess the
...of the second operand are actually broadcast to fit the first (not sure, it might be weirder). It isn't clear to me that the intention is clear there.I do agree that this intention seems like a sensible generalization, but it is a bit unclear that broadcasting is actually sensible for reduced dimensions with multiple operands.
jcmgray commentedon Dec 2, 2025
Yes this I agree is not well defined and should probably error, in my opinion.
I always assumed the behavior of ellipses was "replace
...everywhere with a matching sequence of indices, inferred from the inputs". That includes broadcasting, but also cases where the ellipses don't appear in the output or some inputs, but not the above case.seberg commentedon Dec 3, 2025
Yeah, I guess it was written as indicating outer dimensions. Which may be slightly odd, because it effectively means we constantly write ellipsis everywhere since most functionality should allow an outer iteration.
I am slightly surprised about the use-case of
i...->ifor truly arbitrary number of dimensions, but it isn't like we support the gufunc notion of a?dimensions here to writeijk?->i(supporting not arbitrary dimensions, but a specific variable number).TomPOGET commentedon Jan 4, 2026
Apologies for potentially taking over, I hope this isn't poor practice (I am new to contributing to open source projects).
I have opened a pull request that solves this specific issue as described above; relating to the rest of the discussion, in particular the fact that np.einsum("i...,i...->i", x, x[0], optimize=True) should error, I do agree that it makes sense and that the logic of the handling of the ellipses should probably be changed.
However, I also noticed that the milestone for this specific bugfix was set to 2.4.1; considering that making that syntax not work anymore would, I suppose, be a breaking change, I felt that it might be better to do that in a separate pull request.
The one I made as far as I'm aware is not breaking.
Improved test_einsum numpy#30349
Improved test_einsum numpy#30349
seberg commentedon Jan 5, 2026
I have removed the milestone, I don't think we should consider this a bug, rather a reasonable enhancement.