Repository navigation
Conversation
jorenham
left a comment
There was a problem hiding this comment.
sorry if premature, but I left some typing-related comments
|
|
||
| add_newdoc('numpy._core', 'ufunc', ('segmented_reduce', | ||
| """ | ||
| segmented_reduce($self, array, /, starts, stops=None, axis=0, dtype=None, out=None, **kwargs) |
There was a problem hiding this comment.
Can we make anything after stops=None keyword-only?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
mhvk
left a comment
There was a problem hiding this comment.
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]) |
There was a problem hiding this comment.
I'm not sure about the plural here, since a single value is in principle allowed.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 🤣
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
I'm with @jorenham in thinking we should make everything starting with axis keyword only.
There was a problem hiding this comment.
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.
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. |
|
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. |
|
This is honestly mostly ready from my side. It just needs code-deduplication with reduceat which is easy when I look at the diff between the two. However, this would conflict with the work done on multi-output reductions for |
|
I'm keeping this a bit frozen until multi-output reductions land in main for reduceat but I had a question in the meantime. Do you think segmented reduce should support multi-output reductions? Sure completeness is an argument but I thought maybe not yet to keep the code of segmented reduce simpler? I don't know. I doubt anyone eagerly wants a segmented minmax for example. |
|
@ikrommyd - I think deduplication with |
yeah part of why I keep this frozen for now is because reduceat is changing in #32212. So I want to restart with that PR (once merged) as a basis. |
… values Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
…uce` Signed-off-by: Iason Krommydas <[email protected]>
…educe Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
…mented_reduce` Signed-off-by: Iason Krommydas <[email protected]>
595511d to
b9418ab
Compare
|
@jorenham should I implement the typing here or want me to leave it for a future PR or for you? it's currently in the diff as copy-pasted from reduceat with the names changed. |
Copy-pasting is probably what I'd do myself, so I see no reason to defer that. I'll take a closer look at the diff once this is ready for review. |
|
Yeah I just did a massive rebase as most of the reduceat core has been changed to add the multi-output support. This is now in sync with main's reduceat implementation and also supports multi-output loops out of the box. I want to do a pass myself to simplify more if possible and an AI review but I guess the typing shouldn't change so if you're ever bored you can do a pass on that already I guess. |
…ented_reduce` Signed-off-by: Iason Krommydas <[email protected]>
…uceat_impl` Signed-off-by: Iason Krommydas <[email protected]>
Signed-off-by: Iason Krommydas <[email protected]>
| axis: SupportsIndex = 0, | ||
| dtype: DTypeLike | None = None, | ||
| out: EllipsisType | None = None, | ||
| initial: Incomplete = ..., |
There was a problem hiding this comment.
- Below in the
__init__.pyichanges forufuncyou added a comment saying thatinitialis keyword-only, but right now it can also be passed as positional arg here. - Could we narrow
Incompleteto something like_ScalarLike_coorArrayLike? Or, if it can accept anything, then we should useobjectinstead ofIncomplete, becauseIncompleteis basically intended as a# TODO: annotate me. - Is there a default value we could use here instead of the
= ...? Or is it more like anif "initial" in kwargs: ...kinda situation?
There was a problem hiding this comment.
IIRC you said that the segmented_reduce signature in these ufunc stubs is pretty much equivalent to that of reduceat (modulo the extra params that aren't tested here). Because if that's the case, we don't need to also copy-paste their type-tests here, and we could just write a simple comment like we do elsewhere, e.g.:
https://github.com/ikrommyd/numpy/blob/36312d6d1611ef6a3878d4bbf4e849f476b8d4b9/numpy/typing/tests/data/reveal/ufuncs.pyi#L3928
| assert_type(np.frompyfunc(func21, n2, n1).segmented_reduce(AR_f4_2d, AR_i8, AR_i8), npt.Array2D[np.object_[int]]) | ||
| assert_type(np.frompyfunc(func21, n2, n1).segmented_reduce([f8], AR_i8, AR_i8), npt.Array1D[np.object_[int]]) | ||
| assert_type(np.frompyfunc(func21, n2, n1).segmented_reduce([[f8]], AR_i8, AR_i8), npt.Array2D[np.object_[int]]) | ||
| assert_type(np.frompyfunc(func21, n2, n1).segmented_reduce(AR_f8, AR_i8, AR_i8, out=AR_f8), npt.NDArray[np.float64]) | ||
| assert_type(np.frompyfunc(func21, n2, n1).segmented_reduce([AR_f8], AR_i8, AR_i8), npt.NDArray[np.object_[int]]) |
There was a problem hiding this comment.
If this is also the same as reduceat here, then a comment would suffice
| starts: _ArrayLikeInt_co, | ||
| stops: _ArrayLikeInt_co, |
There was a problem hiding this comment.
_ArrayLikeInt_co also accepts boolean array-likes, which I'm guessing wouldn't work at runtime. So let's use _ArrayLikeInt here instead (without the _co), like you already did above in _core.umath. It's not yet imported though, so you'll have to import it from ._array_like.
| # `initial` is passed through `kwargs` (like it is for `reduce`) | ||
| **kwargs: Incomplete, |
There was a problem hiding this comment.
No need to repeat the mistakes of the past: it's easy enough to instead write something like
| # `initial` is passed through `kwargs` (like it is for `reduce`) | |
| **kwargs: Incomplete, | |
| *, | |
| initial: _ScalarLike_co, |
| starts: _ArrayLikeInt_co, | ||
| stops: _ArrayLikeInt_co, |
There was a problem hiding this comment.
| starts: _ArrayLikeInt_co, | |
| stops: _ArrayLikeInt_co, | |
| starts: _ArrayLikeInt, | |
| stops: _ArrayLikeInt, |
| segmented_reduce($self, array, /, starts, stops, axis=0, dtype=None, out=None, **kwargs) | ||
| -- | ||
|
|
||
| segmented_reduce(array, starts, stops, axis=0, dtype=None, out=None, initial=<no value>) |
There was a problem hiding this comment.
should we also mark the pos-only and kw-only params here?
PR summary
Taking over #25476.
Implements a new
segmented_reducemethod to ufuncs as it was discussed on that PR instead of enhancing thereduceatinterface. I tried to make the code changes as minimal as possible and take most of the implementation from reduceat from helpers to reduce duplication.AI Disclosure
I iterated with AI on most code outside the actual ufunc_object.c implementation like tests, typing, and boilerplate. The core implementation was handled more manually.