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
Would be really handy to have a function that computes and returns both the minimum and maximum of an array. This should be substantially faster than calling min and then max on the same array.
FWIW here's an SO question from some time ago that has gotten a fair bit of attention.
Reacted by Morwenn, Warren Weckesser, Germán Ruelas, Daniel Hrisca, chocolateboy, Maximilian Peters, Zhanwen Chen, Virgil Tudor, Shoval Tirman, Silmathoron and 50 more
Reacted by Demetry Pasсal, rsha256, Daniel Giger and smuecke [ˈsmʏkʰə]
Sorry if I'm being a bit slow here, but wouldn't it be possible to just return an array containing two values? Admittedly I've not dug deep into ufuncs internal workings. So am probably missing something.
The issue is that max(a), being a shorthand for ufunc.reduce, is implemented as f(a[0], f(a[1], f(a[2], ...))), where f = np.maximum, and acts on scalars. That's not enough state to implement a minmax.
You could of course just implement this with NpyIter - I'm just saying this isn't well suited to a ufunc
This is one of quite a number of requests to combine ufuncs in various ways (here, running two in parallel; often, two in series); I still like the idea of making it possible to programmatically make such combined ufuncs. It should actually not be that hard -- for some thoughts, see #8889 (comment). Now if only I had time to work on it...
@jakirkham - rereading this, I think @eric-wieser is correct that it is in fact not easy to implement minmax. Just as an example, even the calculation of ptp is done by first calculating max and min and then subtracting them (in core/src/multiarray/calculation.c). My own sense would be that effort is better spent on making a general way to combine ufunc, as while it is inefficient to calculate min and max separately, my guess would be that in most cases this is not a very big part of a larger calculation (e.g., that involves any trig function or also needs a median).
Why is it not easy to implement minmax? Are you just meaning as a ufunc? If so, you may be right. However if you mean the algorithm itself, I have to disagree. In fact, gave it a try and wrapped up a little Cython package that does just that ( https://github.com/jakirkham/cyminmax ). Turns out there are well defined bounds on the Big-O of such an algorithm as well. Though would not be surprised if NumPy has all sorts of neat tricks for something like this that I have missed in my naive implementation.
As to the usefulness of minmax, as with all tools their usefulness is dependent on the problem being solved. For me, taking min and max to normalize images is a regular occurrence. Even if there might be a computation that takes somewhat longer, a significant number of calls are made to min and max. At this stage, I find most other computations I'm doing have been well optimized excepting min and max. Having minmax fixes that. Given the large amount of traffic on the SO question linked above, would think that other people agree that minmax is worth solving too.
Reacted by Morwenn, Siddharth Chauhan, PhD, Carsten Allefeld, Daniel Giger and PAUL MCQUESTEN
It's a little messy to implement minmax(1d_array) for all input dtypes in C. As you show, using cython makes it a lot easier, but right now there's not much precedent for using cython inside numpy.
It's harder to implement minmax(3d_array, axis=(0,2)), which you need to do for this to actually work as an underlying implementation for np.ptp. I suppose you could expose a public minmax python function that falls back on calling min and max separately if it's not a simple axis=None case.
I might prefer the name np.extents to np.minmax, were such a function to be created.
It's true C is pretty rough when it comes to parameterizing functions over types. My impression is that most C programs leverage m4 to solve this problem. Though that is probably not a reasonable requirement to add to building NumPy. Have long wondered if people might start using Jinja to do the same thing. It looks like the Chromium project explored this enough to write up a developer guide. Though this is a bit off topic.
Certainly Cython helps as well. Though there is still some boilerplate required to glue everything together. This is mainly caused by Cython not supporting arbitrary dimensions for a memory view (e.g. could be 1-D or 3-D).
One option might be to rip the C generated code out from Cython and use that as a base for NumPy. Would need some tidying though. While it is not common to use Cython in NumPy, some random number generation code does use it and it is always possible to ship the C. Perhaps the lower maintenance burden is already a reason for NumPy to start thinking about it. 😉
Have a WIP implementation of axes in PR ( jakirkham/cyminmax#31 ). Though have no idea how well it performs yet. The approach is at least similar to what things like apply_along_axis. Namely transposing the axes. Also supports out in the same PR. Have not tried keepdims yet. Though think this should be possible with some more changes.
So minmax is the name used by MATLAB and C++. Though C++ oddly calls the traversal function minmax_element. Julia also calls it minmax. Interestingly so does Ruby. I don't think R has something like this in its core library, but there appear to be other packages that provided it and call it minmax. Would think it best to stick with minmax for consistency with users from these other languages that are likely to crossover into Python for numerical use cases and thus use NumPy.
@jakirkham - my comment really wasn't that minmax is not useful, just that in my opinion one can get greater reward for effort spent in trying to make combining ufuncs generally easier. But obviously that is just my opinion, and where I will spend the time (if I have some).
In this respect, in your example of normalizing an image, one also needs to subtract min and divide by max-min, so there are four operations, and having a minmax would thus produce at best a 33% improvement (if one ignores that for large arrays subtract and divide are substantially slower than min and max, as many more writes to memory have to be done). Writing a way to combine ufuncs (subtract and divide) would give you a similar improvement.
Sure. Fusing operations together would be very powerful and widely useful. Though that feels like a different topic to me. Is there already an issue open on this topic? Would be very interested in that discussion. :)
Would add that the minmax algorithm is actually different than just doing min and max as it iterates over pairs of values and uses them to avoid roughly half of a comparison per value checked. Just adding this in case it is not obvious.
For what it's worth, I implemented a simple minmax as a gufunc in ufunclab. Currently, it doesn't handle datetime64 or timedelta64 (ran into the Cliffs of Casting on my journey up the NumPy C API learning curve. :)
Reacted by Juan Nunez-Iglesias
Reacted by jakirkham, Inessa Pawson, smuecke [ˈsmʏkʰə] and Scott Shambaugh
If minmax cannot be a reduction like min and max are, and at some point another function has to be implemented, the gain provided by this function would basically be due to the fact that the number of "iterative scanning" would be reduced to one.
I think that function should not be called minmax unless it was a reduction, and np.extents, as proposed earlier, seem fairly good. The bothering of implementing it and adding it as a new additional function might justify making it even more versatile, or generalized.
Therefore I think a more complete signature in the API could be like : (vmins, idmins), (vmaxs, idmaxs) = np.extents(A, nmin=1, nmax=1, ret_ids=True, ignore_nans=True, axis=1)
where a number of min and max values could be provided as an argument, and also an option for ignoring nans.
This function could reduce the cost (and the code length) of use cases where np.max, np.min, np.argmax, np.argmin are used sequentially, including their nan-ignoring equivalents.
Wonder if someone could do very a mild tweak to the array before passing it to a (private) ufunc leveraged for minmax by adding an extra dimension of length 2 with a view onto the original array like so to get the array to be more ufunc friendly
For the implementation, clearly there is already the one provided by @WarrenWeckesser (see #9836 (comment)), so one could start there. But the trick will be to actually get it to good performance with vectorization, etc., because this will not be able to use much of the machinery in numpy/_core/src/umath/fast_loop_macros.h yet one does not want to start from scratch.
I'd be happy to work on adding minmax to NumPy (assuming there was agreement that it should be added), but at the moment I have too many languishing work-in-progress PRs and projects, so I can't commit to something new (at least not for a few months).
Reacted by Marten van Kerkwijk, Juan Nunez-Iglesias, Loïc Huder, Nathaniel-Bubis, Mxwell and Saurabh Misra
@ikrommyd and I talked about this today as a nice smallish project that fits a real need and also allows Iason to learn more about ufunc internals. Is this still something people want and no one has done satisfactorily yet?
Reacted by Marten van Kerkwijk, q-wertz, jakirkham, Chris Meyer, yohplala, Genevieve Buckley, Juan Nunez-Iglesias, Kimoon Higashihira Han and Iason Krommydas
EDIT: I just saw the NEP 54 guidance about transitioning to Highway. In light of that, it makes more sense to first port the existing loops_minmax.dispatch.c.src functionality to Highway, so I am working on that.
I have a branch that implements minmax in a way which I believe addresses all the concerns brought up here and in the other issues where this has been discussed over the years. However, it needs significant cleanup before I want anyone else investing their time with it.
The performance looks good. In some cases it's actually slightly faster than calling either min or max in isolation on my MacBook. The tests pass and I'm pretty sure I'm not falling victim to any floating point foot-guns to get that performance.
Where should this function live? In #18483@seberg suggested that it was unlikely minmax would get merged to np directly, and that np.lib.performance might be the right place, but they also undercut their own suggestion. I think putting it in np directly makes more sense.
If I can close this I'd love to work on landing the other functions @WarrenWeckesser mentioned.
@csp256 head's up, I am currently planning to work on this issue with @ikrommyd as part of an internship this summer. I see you're keen to help out, but I'm purposely waiting for Iason's internship to start before working on this.
I would hold off on further work towards fixing this particular issue for now. I would also advise against taking on such a big project as your first NumPy contribution.
Please also keep in mind our AI policy. In the age of AI, I don't personally feel
comfortable merging very large changes from someone we don't yet have a track record with.
Reacted by Andras Deak, Kimoon Higashihira Han, Sebastian Berg and Hans Meine
Activity
rbkn99 commentedon Oct 19, 2017
Can I try to implement this function as my first contribution?
eric-wieser commentedon Oct 20, 2017
Tricky to make this a ufunc, because
maxis currentlynp.maximum.reduce, but a reduction can't compute two values at once (unless we implement #8773)jakirkham commentedon Oct 20, 2017
Sorry if I'm being a bit slow here, but wouldn't it be possible to just return an array containing two values? Admittedly I've not dug deep into ufuncs internal workings. So am probably missing something.
eric-wieser commentedon Oct 20, 2017
The issue is that
max(a), being a shorthand forufunc.reduce, is implemented asf(a[0], f(a[1], f(a[2], ...))), wheref = np.maximum, and acts on scalars. That's not enough state to implement aminmax.You could of course just implement this with
NpyIter- I'm just saying this isn't well suited to a ufuncrbkn99 commentedon Oct 20, 2017
So, did I understand right that
minmaxcan be implemented in another, not as a ufunc, way?eric-wieser commentedon Oct 20, 2017
Correct, but it would by far be preferable to adapt ufuncs to make it possible.
You could implement it as a
gufunc(likelinalg.det), but then you lose the axis argument, and it only works in 1D.mhvk commentedon Oct 21, 2017
This is one of quite a number of requests to combine ufuncs in various ways (here, running two in parallel; often, two in series); I still like the idea of making it possible to programmatically make such combined ufuncs. It should actually not be that hard -- for some thoughts, see #8889 (comment). Now if only I had time to work on it...
jakirkham commentedon Nov 5, 2017
Since this can be done without creating a new type of ufuncs, would a proposal that added
minmaxwithout a new class of ufuncs still be accepted?mhvk commentedon Nov 6, 2017
@jakirkham - rereading this, I think @eric-wieser is correct that it is in fact not easy to implement
minmax. Just as an example, even the calculation ofptpis done by first calculatingmaxandminand then subtracting them (incore/src/multiarray/calculation.c). My own sense would be that effort is better spent on making a general way to combineufunc, as while it is inefficient to calculateminandmaxseparately, my guess would be that in most cases this is not a very big part of a larger calculation (e.g., that involves any trig function or also needs a median).jakirkham commentedon Nov 10, 2017
Why is it not easy to implement
minmax? Are you just meaning as aufunc? If so, you may be right. However if you mean the algorithm itself, I have to disagree. In fact, gave it a try and wrapped up a little Cython package that does just that ( https://github.com/jakirkham/cyminmax ). Turns out there are well defined bounds on the Big-O of such an algorithm as well. Though would not be surprised if NumPy has all sorts of neat tricks for something like this that I have missed in my naive implementation.As to the usefulness of
minmax, as with all tools their usefulness is dependent on the problem being solved. For me, takingminandmaxto normalize images is a regular occurrence. Even if there might be a computation that takes somewhat longer, a significant number of calls are made tominandmax. At this stage, I find most other computations I'm doing have been well optimized exceptingminandmax. Havingminmaxfixes that. Given the large amount of traffic on the SO question linked above, would think that other people agree thatminmaxis worth solving too.eric-wieser commentedon Nov 10, 2017
It's a little messy to implement
minmax(1d_array)for all input dtypes in C. As you show, using cython makes it a lot easier, but right now there's not much precedent for using cython inside numpy.It's harder to implement
minmax(3d_array, axis=(0,2)), which you need to do for this to actually work as an underlying implementation fornp.ptp. I suppose you could expose a publicminmaxpython function that falls back on callingminandmaxseparately if it's not a simpleaxis=Nonecase.I might prefer the name
np.extentstonp.minmax, were such a function to be created.jakirkham commentedon Nov 10, 2017
It's true C is pretty rough when it comes to parameterizing functions over types. My impression is that most C programs leverage
m4to solve this problem. Though that is probably not a reasonable requirement to add to building NumPy. Have long wondered if people might start using Jinja to do the same thing. It looks like the Chromium project explored this enough to write up a developer guide. Though this is a bit off topic.Certainly Cython helps as well. Though there is still some boilerplate required to glue everything together. This is mainly caused by Cython not supporting arbitrary dimensions for a memory view (e.g. could be 1-D or 3-D).
One option might be to rip the C generated code out from Cython and use that as a base for NumPy. Would need some tidying though. While it is not common to use Cython in NumPy, some random number generation code does use it and it is always possible to ship the C. Perhaps the lower maintenance burden is already a reason for NumPy to start thinking about it. 😉
Have a WIP implementation of axes in PR ( jakirkham/cyminmax#31 ). Though have no idea how well it performs yet. The approach is at least similar to what things like
apply_along_axis. Namely transposing the axes. Also supportsoutin the same PR. Have not triedkeepdimsyet. Though think this should be possible with some more changes.So
minmaxis the name used by MATLAB and C++. Though C++ oddly calls the traversal functionminmax_element. Julia also calls itminmax. Interestingly so does Ruby. I don't think R has something like this in its core library, but there appear to be other packages that provided it and call itminmax. Would think it best to stick withminmaxfor consistency with users from these other languages that are likely to crossover into Python for numerical use cases and thus use NumPy.mhvk commentedon Nov 10, 2017
@jakirkham - my comment really wasn't that
minmaxis not useful, just that in my opinion one can get greater reward for effort spent in trying to make combining ufuncs generally easier. But obviously that is just my opinion, and where I will spend the time (if I have some).In this respect, in your example of normalizing an image, one also needs to subtract
minand divide bymax-min, so there are four operations, and having a minmax would thus produce at best a 33% improvement (if one ignores that for large arrays subtract and divide are substantially slower thanminandmax, as many more writes to memory have to be done). Writing a way to combine ufuncs (subtract and divide) would give you a similar improvement.jakirkham commentedon Nov 11, 2017
Sure. Fusing operations together would be very powerful and widely useful. Though that feels like a different topic to me. Is there already an issue open on this topic? Would be very interested in that discussion. :)
Would add that the
minmaxalgorithm is actually different than just doingminandmaxas it iterates over pairs of values and uses them to avoid roughly half of a comparison per value checked. Just adding this in case it is not obvious.eric-wieser commentedon Nov 11, 2017
It certainly wasn't to me. This makes a good case for it needing to be a
gufuncand not just a reductionufunc.Do you mean it does something like this?
10 remaining items
WarrenWeckesser commentedon Oct 1, 2019
For what it's worth, I implemented a simple
minmaxas a gufunc in ufunclab. Currently, it doesn't handledatetime64ortimedelta64(ran into the Cliffs of Casting on my journey up the NumPy C API learning curve. :)iver56 commentedon Jan 13, 2024
For what it's worth, I implemented a SIMD-optimized minmax function for numpy arrays here: https://github.com/nomonosound/numpy-minmax
It's mainly optimized for float32, but is also partially optimized for int16
hprodh commentedon Oct 28, 2024
If minmax cannot be a reduction like min and max are, and at some point another function has to be implemented, the gain provided by this function would basically be due to the fact that the number of "iterative scanning" would be reduced to one.
I think that function should not be called minmax unless it was a reduction, and
np.extents, as proposed earlier, seem fairly good. The bothering of implementing it and adding it as a new additional function might justify making it even more versatile, or generalized.Therefore I think a more complete signature in the API could be like :
(vmins, idmins), (vmaxs, idmaxs) = np.extents(A, nmin=1, nmax=1, ret_ids=True, ignore_nans=True, axis=1)where a number of min and max values could be provided as an argument, and also an option for ignoring nans.
This function could reduce the cost (and the code length) of use cases where np.max, np.min, np.argmax, np.argmin are used sequentially, including their nan-ignoring equivalents.
jakirkham commentedon Oct 29, 2024
Wonder if someone could do very a mild tweak to the array before passing it to a (private)
ufuncleveraged forminmaxby adding an extra dimension of length 2 with a view onto the original array like so to get the array to be moreufuncfriendlyjakirkham commentedon Nov 14, 2024
It is worth noting that
np.divmodreturns 2 values and is aufuncFWIW
np.divmodwas added in PR: #9063mhvk commentedon Nov 14, 2024
Indeed,
(g)ufunccan have multiple outputs. It really is a question of (i) deciding whether one should have aminmaxand (ii) implementing it...jni commentedon Nov 14, 2024
(i) I'm a definite yes
(ii) please. 😂
mhvk commentedon Nov 15, 2024
For the implementation, clearly there is already the one provided by @WarrenWeckesser (see #9836 (comment)), so one could start there. But the trick will be to actually get it to good performance with vectorization, etc., because this will not be able to use much of the machinery in
numpy/_core/src/umath/fast_loop_macros.hyet one does not want to start from scratch.WarrenWeckesser commentedon Nov 15, 2024
FYI & FWIW: The core calculations that I implemented are in https://github.com/WarrenWeckesser/ufunclab/blob/main/src/minmax/minmax_gufunc.h (which also includes calculations for
argmin,argmax,argminmax,min_argminandmax_argmax). All the boilerplate gufunc wrapping is handled by code generation scripts, based on the configuration file https://github.com/WarrenWeckesser/ufunclab/blob/main/src/minmax/define_cxx_gufunc_extmod.py. I implemented just the integer and floating point types, and the object type.My implementation is not optimized; it is not much more than the serial loop in C++ that you would expect. I have experimented with a SIMD implementation in C++ using xsimd in https://github.com/WarrenWeckesser/experiments/blob/main/c%2B%2B/xsimd/reductions/minmax.hpp (no NumPy, just C++). For NumPy, one would use the Google Highway library. (Adding SIMD implementations to ufunclab is on my aspirational to-do list, but not my immediate to-do list.)
I'd be happy to work on adding
minmaxto NumPy (assuming there was agreement that it should be added), but at the moment I have too many languishing work-in-progress PRs and projects, so I can't commit to something new (at least not for a few months).ENH: Add minmax function for single-pass min/max computation
ENH: add np.minmax() for combined min/max reduction
ngoldbaum commentedon Feb 23, 2026
@ikrommyd and I talked about this today as a nice smallish project that fits a real need and also allows Iason to learn more about ufunc internals. Is this still something people want and no one has done satisfactorily yet?
csp256 commentedon Apr 13, 2026
EDIT: I just saw the NEP 54 guidance about transitioning to Highway. In light of that, it makes more sense to first port the existing loops_minmax.dispatch.c.src functionality to Highway, so I am working on that.
I have a branch that implementsminmaxin a way which I believe addresses all the concerns brought up here and in the other issues where this has been discussed over the years. However, it needs significant cleanup before I want anyone else investing their time with it.The performance looks good. In some cases it's actually slightly faster than calling eitherminormaxin isolation on my MacBook. The tests pass and I'm pretty sure I'm not falling victim to any floating point foot-guns to get that performance.Where should this function live? In #18483 @seberg suggested that it was unlikely
minmaxwould get merged tonpdirectly, and thatnp.lib.performancemight be the right place, but they also undercut their own suggestion. I think putting it innpdirectly makes more sense.If I can close this I'd love to work on landing the other functions @WarrenWeckesser mentioned.
ngoldbaum commentedon Apr 13, 2026
@csp256 head's up, I am currently planning to work on this issue with @ikrommyd as part of an internship this summer. I see you're keen to help out, but I'm purposely waiting for Iason's internship to start before working on this.
I would hold off on further work towards fixing this particular issue for now. I would also advise against taking on such a big project as your first NumPy contribution.
Please also keep in mind our AI policy. In the age of AI, I don't personally feel
comfortable merging very large changes from someone we don't yet have a track record with.