@@ -130,6 +130,10 @@ GitHub workflow. There are a few reasons for this:
130
130
rather than breakages happening when it is least expected.
131
131
In simple terms, bugs in ``unumpy `` mean that ``numpy `` remains
132
132
unaffected.
133
+ * For ``numpy.fft ``, ``numpy.linalg `` and ``numpy.random ``, the functions in
134
+ the main namespace will mirror those in the ``numpy.overridable `` namespace.
135
+ The reason for this is that there may exist functions in the in these
136
+ submodules that need backends, even for ``numpy.ndarray `` inputs.
133
137
134
138
Advantanges of ``unumpy `` over other solutions
135
139
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -156,7 +160,12 @@ allows one to override a large part of the NumPy API by defining only a small
156
160
part of it. This is to ease the creation of new duck-arrays, by providing
157
161
default implementations of many functions that can be easily expressed in
158
162
terms of others, as well as a repository of utility functions that help in the
159
- implementation of duck-arrays that most duck-arrays would require.
163
+ implementation of duck-arrays that most duck-arrays would require. This would
164
+ allow us to avoid designing entire protocols, e.g., a protocol for stacking
165
+ and concatenating would be replaced by simply implementing ``stack `` and/or
166
+ ``concatenate `` and then providing default implementations for everything else
167
+ in that class. The same applies for transposing, and many other functions
168
+ which cannot even be concretely covered by protocols.
160
169
161
170
It also allows one to override functions in a manner which
162
171
``__array_function__ `` simply cannot, such as overriding ``np.einsum `` with the
@@ -211,6 +220,94 @@ If the user wishes to obtain a NumPy array, there are two ways of doing it:
211
220
2. Use ``numpy.overridable.asarray `` with the NumPy backend set and coercion
212
221
enabled
213
222
223
+ Aliases outside of the ``numpy.overridable `` namespace
224
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
225
+
226
+ All functionality in ``numpy.random ``, ``numpy.linalg `` and ``numpy.fft ``
227
+ will be aliased to their respective overridable versions inside
228
+ ``numpy.overridable ``. The reason for this is that there are alternative
229
+ implementations of RNGs (``mkl-random ``), linear algebra routines (``eigen ``,
230
+ ``blis ``) and FFT routines (``mkl-fft ``, ``pyFFTW ``) that need to operate on
231
+ ``numpy.ndarray `` inputs, but still need the ability to switch behaviour.
232
+
233
+ This is different from monkeypatching in a few different ways:
234
+
235
+ * The caller-facing signature of the function is always the same,
236
+ so there is at least the loose sense of an API contract. Monkeypatching
237
+ does not provide this ability.
238
+ * There is the ability of locally switching the backend.
239
+ * It has been `suggested <http://numpy-discussion.10968.n7.nabble.com/NEP-31-Context-local-and-global-overrides-of-the-NumPy-API-tp47452p47472.html >`_
240
+ that the reason that 1.17 hasn't landed in the Anaconda defaults channel is
241
+ due to the incompatibility between monkeypatching and `__array_function__ `.
242
+
243
+ All this isn't possible at all with ``__array_function__ `` or
244
+ ``__array_ufunc__ ``.
245
+
246
+ It has been formally realised (at least in part) that a backend system is
247
+ needed for this, in the `NumPy roadmap <https://numpy.org/neps/roadmap.html#other-functionality >`_.
248
+
249
+ For ``numpy.random ``, it's still necessary to make the C-API fit the one
250
+ proposed in `NEP-19 <https://numpy.org/neps/nep-0019-rng-policy.html >`_.
251
+ This is impossible for `mkl-random `, because then it would need to be
252
+ rewritten to fit that framework. The general guarantees on stream
253
+ compatibility will be the same as before: If there's a backend that affects
254
+ ``numpy.random `` set, we make no guarantees about stream compatibility, and it
255
+ is up to the backend author to provide their own guarantees.
256
+
257
+ Providing a way for implicit dispatch
258
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
259
+
260
+ It has been suggested that the ability to dispatch methods which do not take
261
+ a dispatchable is needed, while guessing that backend from another dispatchable.
262
+
263
+ As a concrete example, consider the following:
264
+
265
+ .. code :: python
266
+
267
+ with unumpy.determine_backend(array_like, np.ndarray):
268
+ unumpy.arange(len (array_like))
269
+
270
+ While this does not exist yet in ``uarray ``, it is trivial to add it. The
271
+ answer is to simply call ``__ua_convert__ `` on the passed-in array with
272
+ ``coerce=False `` for each backend, and comparing the result to
273
+ ``NotImplemented ``.
274
+
275
+ The need for an opt-in module
276
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
277
+
278
+ The need for an opt-in module is realised because of a few reasons:
279
+
280
+ * There are parts of the API (like `numpy.asarray `) that simply cannot be
281
+ overridden due to incompatibility concerns with C/Cython extensions, however,
282
+ one may want to coerce to a duck-array using ``asarray `` with a backend set.
283
+ * There are possible issues around an implicit option and monkeypatching.
284
+
285
+ NEP 18 notes that this may require maintenance of two separate APIs. However,
286
+ this burden may be lessened by, for example, parametrizing all tests over
287
+ ``numpy.overridable `` separately via a fixture. This also has the side-effect
288
+ of thoroughly testing it, unlike ``__array_function__ ``. We also feel that it
289
+ provides an oppurtunity to separate the NumPy API contract properly from the
290
+ implementation.
291
+
292
+ Benefits to end-users and mixing backends
293
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
294
+
295
+ Mixing backends is easy in ``uarray ``, one only has to do:
296
+
297
+ .. code :: python
298
+
299
+ # Explicitly say which backends you want to mix
300
+ ua.register_backend(backend1)
301
+ ua.register_backend(backend2)
302
+ ua.register_backend(backend3)
303
+
304
+ # Freely use code that mixes backends here.
305
+
306
+ The benefits to end-users extend beyond just writing new code. Old code
307
+ (usually in the form of scripts) can be easily ported to different backends
308
+ by a simple import switch and a line adding the preferred backend. This way,
309
+ users may find it easier to port existing code to GPU or distributed computing.
310
+
214
311
Related Work
215
312
------------
216
313
@@ -245,6 +342,14 @@ Existing alternate dtype implementations
245
342
* Datashape: https://datashape.readthedocs.io
246
343
* Plum: https://plum-py.readthedocs.io/
247
344
345
+ Alternate implementations of parts of the NumPy API
346
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
347
+
348
+ * ``mkl_random ``: https://github.com/IntelPython/mkl_random
349
+ * ``mkl_fft ``: https://github.com/IntelPython/mkl_fft
350
+ * ``bottleneck ``: https://github.com/pydata/bottleneck
351
+ * ``opt_einsum ``: https://github.com/dgasmith/opt_einsum
352
+
248
353
Implementation
249
354
--------------
250
355
@@ -420,6 +525,12 @@ also a possibility that can be considered by this NEP. However, the act of
420
525
doing an extra ``pip install `` or ``conda install `` may discourage some users
421
526
from adopting this method.
422
527
528
+ An alternative to requiring opt-in is mainly to *not * override ``np.asarray ``
529
+ and ``np.array ``, and making the rest of the NumPy API surface overridable,
530
+ instead providing ``np.duckarray `` and ``np.asduckarray ``
531
+ as duck-array friendly alternatives that used the respective overrides. However,
532
+ this has the downside of adding a minor overhead to NumPy calls.
533
+
423
534
Discussion
424
535
----------
425
536
0 commit comments