<div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Sat, Sep 7, 2019 at 2:18 PM sebastian <<a href="mailto:sebastian@sipsolutions.net">sebastian@sipsolutions.net</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">On 2019-09-07 15:33, Ralf Gommers wrote:<br>
> On Sat, Sep 7, 2019 at 1:07 PM Sebastian Berg<br>
> <<a href="mailto:sebastian@sipsolutions.net" target="_blank">sebastian@sipsolutions.net</a>> wrote:<br>
> <br>
>> On Fri, 2019-09-06 at 14:45 -0700, Ralf Gommers wrote:<br>
>>> <br>
>>> <br>
>> <snip><br>
>> <br>
>>>> That's part of it. The concrete problems it's solving are<br>
>>>> threefold:<br>
>>>> Array creation functions can be overridden.<br>
>>>> Array coercion is now covered.<br>
>>>> "Default implementations" will allow you to re-write your NumPy<br>
>>>> array more easily, when such efficient implementations exist in<br>
>>>> terms of other NumPy functions. That will also help achieve<br>
>> similar<br>
>>>> semantics, but as I said, they're just "default"...<br>
>>>> <br>
>>> <br>
>>> There may be another very concrete one (that's not yet in the<br>
>> NEP):<br>
>>> allowing other libraries that consume ndarrays to use overrides.<br>
>> An<br>
>>> example is numpy.fft: currently both mkl_fft and pyfftw<br>
>> monkeypatch<br>
>>> NumPy, something we don't like all that much (in particular for<br>
>>> mkl_fft, because it's the default in Anaconda).<br>
>> `__array_function__`<br>
>>> isn't able to help here, because it will always choose NumPy's own<br>
>>> implementation for ndarray input. With unumpy you can support<br>
>>> multiple libraries that consume ndarrays.<br>
>>> <br>
>>> Another example is einsum: if you want to use opt_einsum for all<br>
>>> inputs (including ndarrays), then you cannot use np.einsum. And<br>
>> yet<br>
>>> another is using bottleneck (<br>
>>> <a href="https://kwgoodman.github.io/bottleneck-doc/reference.html" rel="noreferrer" target="_blank">https://kwgoodman.github.io/bottleneck-doc/reference.html</a>) for<br>
>> nan-<br>
>>> functions and partition. There's likely more of these.<br>
>>> <br>
>>> The point is: sometimes the array protocols are preferred (e.g.<br>
>>> Dask/Xarray-style meta-arrays), sometimes unumpy-style dispatch<br>
>> works<br>
>>> better. It's also not necessarily an either or, they can be<br>
>>> complementary.<br>
>>> <br>
>> <br>
>> Let me try to move the discussion from the github issue here (this<br>
>> may<br>
>> not be the best place). (<a href="https://github.com/numpy/numpy/issues/14441" rel="noreferrer" target="_blank">https://github.com/numpy/numpy/issues/14441</a><br>
>> which asked for easier creation functions together with<br>
>> `__array_function__`).<br>
>> <br>
>> I think an important note mentioned here is how users interact with<br>
>> unumpy, vs. __array_function__. The former is an explicit opt-in,<br>
>> while<br>
>> the latter is implicit choice based on an `array-like` abstract base<br>
>> class and functional type based dispatching.<br>
>> <br>
>> To quote NEP 18 on this: "The downsides are that this would require<br>
>> an<br>
>> explicit opt-in from all existing code, e.g., import numpy.api as<br>
>> np,<br>
>> and in the long term would result in the maintenance of two separate<br>
>> NumPy APIs. Also, many functions from numpy itself are already<br>
>> overloaded (but inadequately), so confusion about high vs. low level<br>
>> APIs in NumPy would still persist."<br>
>> (I do think this is a point we should not just ignore, `uarray` is a<br>
>> thin layer, but it has a big surface area)<br>
>> <br>
>> Now there are things where explicit opt-in is obvious. And the FFT<br>
>> example is one of those, there is no way to implicitly choose<br>
>> another<br>
>> backend (except by just replacing it, i.e. monkeypatching) [1]. And<br>
>> right now I think these are _very_ different.<br>
>> <br>
>> Now for the end-users choosing one array-like over another, seems<br>
>> nicer<br>
>> as an implicit mechanism (why should I not mix sparse, dask and<br>
>> numpy<br>
>> arrays!?). This is the promise `__array_function__` tries to make.<br>
>> Unless convinced otherwise, my guess is that most library authors<br>
>> would<br>
>> strive for implicit support (i.e. sklearn, skimage, scipy).<br>
>> <br>
>> Circling back to creation and coercion. In a purely Object type<br>
>> system,<br>
>> these would be classmethods, I guess, but in NumPy and the libraries<br>
>> above, we are lost.<br>
>> <br>
>> Solution 1: Create explicit opt-in, e.g. through uarray. (NEP-31)<br>
>> * Required end-user opt-in.<br>
> <br>
>> * Seems cleaner in many ways<br>
>> * Requires a full copy of the API.<br>
> <br>
> bullet 1 and 3 are not required. if we decide to make it default, then<br>
> there's no separate namespace<br>
<br>
It does require explicit opt-in to have any benefits to the user.<br>
<br>
> <br>
>> Solution 2: Add some coercion "protocol" (NEP-30) and expose a way<br>
>> to<br>
>> create new arrays more conveniently. This would practically mean<br>
>> adding<br>
>> an `array_type=np.ndarray` argument.<br>
>> * _Not_ used by end-users! End users should use dask.linspace!<br>
>> * Adds "strange" API somewhere in numpy, and possible a new<br>
>> "protocol" (additionally to coercion).[2]<br>
>> <br>
>> I still feel these solve different issues. The second one is<br>
>> intended<br>
>> to make array likes work implicitly in libraries (without end users<br>
>> having to do anything). While the first seems to force the end user<br>
>> to<br>
>> opt in, sometimes unnecessarily:<br>
>> <br>
>> def my_library_func(array_like):<br>
>> exp = np.exp(array_like)<br>
>> idx = np.arange(len(exp))<br>
>> return idx, exp<br>
>> <br>
>> Would have all the information for implicit opt-in/Array-like<br>
>> support,<br>
>> but cannot do it right now.<br>
> <br>
> Can you explain this a bit more? `len(exp)` is a number, so<br>
> `np.arange(number)` doesn't really have any information here.<br>
> <br>
<br>
Right, but as a library author, I want a way a way to make it use the <br>
same type as `array_like` in this particular function, that is the <br>
point! The end-user already signaled they prefer say dask, due to the <br>
array that was actually passed in. (but this is just repeating what is <br>
below I think).<br></blockquote><div><br></div><div>Okay, you meant conceptually:) <br></div><div> <br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
>> This is what I have been wondering, if<br>
>> uarray/unumpy, can in some way help me make this work (even<br>
>> _without_<br>
>> the end user opting in).<br>
> <br>
> good question. if that needs to work in the absence of the user doing<br>
> anything, it should be something like<br>
> <br>
> with unumpy.determine_backend(exp):<br>
>    unumpy.arange(len(exp))   # or np.arange if we make unumpy default<br>
> <br>
> to get the equivalent to `np.arange_like(len(exp), array_type=exp)`.<br>
> <br>
> Note, that `determine_backend` thing doesn't exist today.<br>
> <br>
<br>
Exactly, that is what I have been wondering about, there may be more <br>
issues around that.<br>
If it existed, we may be able to solve the implicit library usage by <br>
making libraries use<br>
unumpy (or similar). Although, at that point we half replace <br>
`__array_function__` maybe.<br></blockquote><div><br></div><div>I don't really think so. Libraries can/will still use __array_function__ for most functionality, and just add a `with determine_backend` for the places where __array_function__ doesn't work.<br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
However, the main point is that without such a functionality, NEP 30 and <br>
NEP 31 seem to solve slightly<br>
different issues with respect to how they interact with the end-user <br>
(opt in)?<br></blockquote><div><br></div><div>Yes, I agree with that.</div><div><br></div><div>Cheers,<br></div><div>Ralf</div><div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex">
<br>
We may decide that we do not want to solve the library users issue of <br>
wanting to support implicit<br>
opt-in for array like inputs because it is a rabbit hole. But we may <br>
need to discuss/argue a bit<br>
more that it really is a deep enough rabbit hole that it is not worth <br>
the trouble.<br>
<br>
>> The reason is that simply, right now I am very<br>
>> clear on the need for this use case, but not sure about the need for<br>
>> end user opt in, since end users can just use dask.arange().<br>
> <br>
> I don't get the last part. The arange is inside a library function, so<br>
> a user can't just go in and change things there.<br>
<br>
A "user" here means "end user". An end user writes a script, and they <br>
can easily change<br>
`arr = np.linspace(10)` to `arr = dask.linspace(10)`, or more likely <br>
just use one within one<br>
script and the other within another script, while both use the same <br>
sklearn functions.<br>
(Although using a backend switching may be nicer in some contexts)<br>
<br>
A library provider (library user of unumpy/numpy) of course cannot just <br>
use dask conveniently,<br>
unless they write their own `guess_numpy_like_module()` function first.<br>
<br>
<br>
> Cheers,<br>
> <br>
> Ralf<br>
> <br>
>> Cheers,<br>
>> <br>
>> Sebastian<br>
>> <br>
>> [1] To be honest, I do think a lot of the "issues" around<br>
>> monkeypatching exists just as much with backend choosing, the main<br>
>> difference seems to me that a lot of that:<br>
>> 1. monkeypatching was not done explicit<br>
>> (import mkl_fft; mkl_fft.monkeypatch_numpy())?<br>
>> 2. A backend system allows libaries to prefer one locally?<br>
>> (which I think is a big advantage)<br>
>> <br>
>> [2] There are the options of adding `linspace_like` functions<br>
>> somewhere<br>
>> in a numpy submodule, or adding `linspace(...,<br>
>> array_type=np.ndarray)`,<br>
>> or simply inventing a new "protocl" (which is not really a<br>
>> protocol?),<br>
>> and make it `ndarray.__numpy_like_creation_functions__.arange()`.<br>
>> <br>
>>> Actually, after writing this I just realized something. With<br>
>> 1.17.x<br>
>>> we have:<br>
>>> <br>
>>> ```<br>
>>> In [1]: import dask.array as da<br>
>> <br>
>>> <br>
>>> <br>
>>> In [2]: d = da.from_array(np.linspace(0, 1))<br>
>> <br>
>>> <br>
>>> <br>
>>> In [3]: np.fft.fft(d)<br>
>> <br>
>>> <br>
>>> Out[3]: dask.array<fft, shape=(50,), dtype=complex128,<br>
>>> chunksize=(50,)><br>
>>> ```<br>
>>> <br>
>>> In Anaconda `np.fft.fft` *is* `mkl_fft._numpy_fft.fft`, so this<br>
>> won't<br>
>>> work. We have no bug report yet because 1.17.x hasn't landed in<br>
>> conda<br>
>>> defaults yet (perhaps this is a/the reason why?), but it will be a<br>
>>> problem.<br>
>>> <br>
>>>> The import numpy.overridable part is meant to help garner<br>
>> adoption,<br>
>>>> and to prefer the unumpy module if it is available (which will<br>
>>>> continue to be developed separately). That way it isn't so<br>
>> tightly<br>
>>>> coupled to the release cycle. One alternative Sebastian Berg<br>
>>>> mentioned (and I am on board with) is just moving unumpy into<br>
>> the<br>
>>>> NumPy organisation. What we fear keeping it separate is that the<br>
>>>> simple act of a pip install unumpy will keep people from using<br>
>> it<br>
>>>> or trying it out.<br>
>>>> <br>
>>> Note that this is not the most critical aspect. I pushed for<br>
>>> vendoring as numpy.overridable because I want to not derail the<br>
>>> comparison with NEP 30 et al. with a "should we add a dependency"<br>
>>> discussion. The interesting part to decide on first is: do we need<br>
>>> the unumpy override mechanism? Vendoring opt-in vs. making it<br>
>> default<br>
>>> vs. adding a dependency is of secondary interest right now.<br>
>>> <br>
>>> Cheers,<br>
>>> Ralf<br>
>>> <br>
>>> <br>
>>> <br>
>>> _______________________________________________<br>
>>> NumPy-Discussion mailing list<br>
>>> <a href="mailto:NumPy-Discussion@python.org" target="_blank">NumPy-Discussion@python.org</a><br>
>>> <a href="https://mail.python.org/mailman/listinfo/numpy-discussion" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/numpy-discussion</a><br>
>> _______________________________________________<br>
>> NumPy-Discussion mailing list<br>
>> <a href="mailto:NumPy-Discussion@python.org" target="_blank">NumPy-Discussion@python.org</a><br>
>> <a href="https://mail.python.org/mailman/listinfo/numpy-discussion" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/numpy-discussion</a><br>
> _______________________________________________<br>
> NumPy-Discussion mailing list<br>
> <a href="mailto:NumPy-Discussion@python.org" target="_blank">NumPy-Discussion@python.org</a><br>
> <a href="https://mail.python.org/mailman/listinfo/numpy-discussion" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/numpy-discussion</a><br>
_______________________________________________<br>
NumPy-Discussion mailing list<br>
<a href="mailto:NumPy-Discussion@python.org" target="_blank">NumPy-Discussion@python.org</a><br>
<a href="https://mail.python.org/mailman/listinfo/numpy-discussion" rel="noreferrer" target="_blank">https://mail.python.org/mailman/listinfo/numpy-discussion</a><br>
</blockquote></div></div>