Skip to content

ENH: add a segmented_reduce method to ufuncs - #32243

Draft
ikrommyd wants to merge 3 commits into
numpy:mainfrom
ikrommyd:segmented-reduce
Draft

ENH: add a segmented_reduce method to ufuncs#32243
ikrommyd wants to merge 3 commits into
numpy:mainfrom
ikrommyd:segmented-reduce

Conversation

@ikrommyd

@ikrommyd ikrommyd commented Aug 10, 2026

Copy link
Copy Markdown
Member

PR summary

Taking over #25476.
Implements a new segmented_reduce method to ufuncs as it was discussed on that PR instead of enhancing the reduceat interface.
In its current state, this was mostly developed within the weekend by starting over by copying the whole reduceat code into a new segmented_reduce method, adding all the boilerplate for it and iterating with AI on making the necessary changes to it to support starts and stops offsets while considering the changes from Marten's PR. This is obviously not in a ready state and I have only skimmed through the code while iterating with AI. I just wanted a proof of concept over the weekend. I'm opening it as draft for visibility and because I wanted a CI run. I will be iterating over it in the next few days to get it in a ready to review state.

First time committer introduction

N/A

AI Disclosure

See above for the AI usage in the current state. Will update later.

cc @mhvk since the previous PR was yours

Signed-off-by: Iason Krommydas <iason.krom@gmail.com>
@ikrommyd ikrommyd added this to the 2.6.0 Release milestone Aug 10, 2026

@jorenham jorenham left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry if premature, but I left some typing-related comments

Comment thread numpy/_core/umath.pyi Outdated
Comment thread numpy/_core/umath.pyi
Comment thread numpy/_core/_add_newdocs.py Outdated

add_newdoc('numpy._core', 'ufunc', ('segmented_reduce',
"""
segmented_reduce($self, array, /, starts, stops=None, axis=0, dtype=None, out=None, **kwargs)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make anything after stops=None keyword-only?

@ikrommyd ikrommyd Aug 10, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will actually make stops required btw. I don't like the None case. Regarding keyword-only, there's no precedent for that in ufunc methods. No other ufunc methods have keyword-only args as far as I can see. We have to follow what reduce, reduceat and accumulate do here regarding their signature.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what the stubs enforce though, because I checked, and axis is rarely ever used as positional argument in practice for e.g. reduce.

there's no precedent for that in ufunc methods

Personally I don't think that consistency is worth repeating the mistakes of the past (even though I think consistency is pretty important).

Signed-off-by: Iason Krommydas <iason.krom@gmail.com>
@github-actions

This comment has been minimized.

Signed-off-by: Iason Krommydas <iason.krom@gmail.com>
@github-actions

Copy link
Copy Markdown

Diff from mypy_primer, showing the effect of this PR on type check results on a corpus of open source code:

optuna (https://github.com/optuna/optuna)
  ./optuna/study/_multi_objective.py:182: error: INTERNAL ERROR -- Please try using mypy master on GitHub:

@mhvk mhvk left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not looked in any detail yet, but think it is important that we end up in a state where there is only one function that deals with the internals of both reduceat and segmented_reduce, i.e., we should factor out the innards. Otherwise we're nearly guaranteed that the code will diverge.

Otherwise, only bikeshedding about names and keywords so far...

over segments (slices) of an axis given by their start and stop offsets::

>>> a = np.arange(8)
>>> np.add.segmented_reduce(a, starts=[0, 5], stops=[3, 8])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the plural here, since a single value is in principle allowed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea is that it comes from "start offsets" and "stop offsets" and offsets is always plural so I called them starts and stops. CCCL calls them "start_offsets_inandend_offsets_in`. I don't care much but I'm personally a fan of plural tbh.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My analogy is with slice, which has start and stop -- at some point I had a branch where for reduceat I let the user give a slice object with start and stop arrays. Also, things like np.linspace have start and stop which can be arrays.

But of course, for linspace, the expectation is that they are scalars, while here it is that you input multiple items with a single one an unusual possibility. Given that, I don't mind so much any more.

p.s. Actually, I think a single item should behave differently than a list of length 1: it should remove axis unless keepdims=True. If we do that, we can pass ufunc.reduce trivially through your new code... Relatedly, if the start and stop are >=2D arrays, one might want to insert multiple axes... For this PR, I suggest we insist on 1D arrays so that we do not prevent exploring use cases for different dimensions...

@ikrommyd ikrommyd Aug 11, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was planning on doing ONLY 1D starts/stops anyways here. If somebody wants to expand that sure but indices in reduceat is also 1D only. I had only thought of segmentes along axis and nothing more than that. I'm haven't even thought about more than 1D 🤣

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent. The main thing is not to get starts and stops with something like the C-side equivalent of np.asanyarray(starts, ndmin=1), but rather explicitly check that they are 1D.


add_newdoc('numpy._core', 'ufunc', ('segmented_reduce',
"""
segmented_reduce($self, array, /, starts, stops, axis=0, dtype=None, out=None, **kwargs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm with @jorenham in thinking we should make everything starting with axis keyword only.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally I agree. My disagreement here is that this this feels like an improved version of reduceat to me and it feels nicer to maintain the way arguments are parsed consistent between them. Happy to do what the maintainers think of course. If I was designing the API from scratch I'd go keyword only for sure.

@ikrommyd

Copy link
Copy Markdown
Member Author

I have not looked in any detail yet, but think it is important that we end up in a state where there is only one function that deals with the internals of both reduceat and segmented_reduce, i.e., we should factor out the innards. Otherwise we're nearly guaranteed that the code will diverge.

Otherwise, only bikeshedding about names and keywords so far...

Yes I started this by copying the reduceat code exactly as is. There's definitely a ton of duplicated code here overall. Reduceat also duplicates a lot of code from accumulate as well and there's a TODO in the code to fix that. I'd be willing to tackle this as well but I want to do this after the upgrade to handle reduction loops comes in.

@mhvk

mhvk commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Yes, good to get the reduction, etc., loops in first -- they're a very nice improvement!

@ikrommyd

Copy link
Copy Markdown
Member Author

Yes, good to get the reduction, etc., loops in first -- they're a very nice improvement!

Yeah treat this PR as "ready for design comments" basically 🤣. The generalization of the reduction loops to reduceat and accumulate (two PRs are open for this) will definitely come in first and this will be heavily rebased afterwards.

@Shreyaparab28

Copy link
Copy Markdown

Great work

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants