Skip to content

Commit 7a6cc27

Browse files
committed
Merge branch 'main' into feat/searchsorted
2 parents 0a47a67 + 5555d41 commit 7a6cc27

12 files changed

+166
-9
lines changed

spec/draft/API_specification/array_object.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Array object
77

88
A conforming implementation of the array API standard must provide and support an array object having the following attributes and methods.
99

10-
Furthermore, a conforming implementation of the array API standard must support array objects of arbitrary rank ``N`` (i.e., number of dimensions), where ``N`` is greater than or equal to zero.
10+
Furthermore, a conforming implementation of the array API standard must support, at minimum, array objects of rank (i.e., number of dimensions) ``0``, ``1``, ``2``, ``3``, and ``4`` and must explicitly document their maximum supported rank ``N``.
1111

1212
.. note::
1313
Conforming implementations must support zero-dimensional arrays.

spec/draft/API_specification/elementwise_functions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Objects in API
3434
bitwise_xor
3535
ceil
3636
conj
37+
copysign
3738
cos
3839
cosh
3940
divide

spec/draft/API_specification/indexing.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,9 @@ Multi-dimensional arrays must extend the concept of single-axis indexing to mult
156156
.. note::
157157
Expanding dimensions can be equivalently achieved via repeated invocation of :func:`~array_api.expand_dims`.
158158

159+
.. note::
160+
The constant ``newaxis`` is an alias of ``None`` and can thus be used in a similar manner as ``None``.
161+
159162
- Except in the case of providing a single ellipsis (e.g., ``A[2:10, ...]`` or ``A[1:, ..., 2:5]``), the number of provided single-axis indexing expressions (excluding ``None``) should equal ``N``. For example, if ``A`` has rank ``2``, a single-axis indexing expression should be explicitly provided for both axes (e.g., ``A[2:10, :]``). An ``IndexError`` exception should be raised if the number of provided single-axis indexing expressions (excluding ``None``) is less than ``N``.
160163

161164
.. note::

spec/draft/API_specification/manipulation_functions.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,5 @@ Objects in API
2929
roll
3030
squeeze
3131
stack
32+
tile
3233
unstack

spec/draft/design_topics/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Design topics & constraints
77

88
copies_views_and_mutation
99
data_dependent_output_shapes
10+
lazy_eager
1011
data_interchange
1112
device_support
1213
static_typing
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.. _lazy-eager:
2+
3+
Lazy vs. eager execution
4+
========================
5+
6+
While the execution model for implementations is out of scope of this standard,
7+
there are a few aspects of lazy (or graph-based) execution as contrasted to
8+
eager execution that may have an impact on the prescribed semantics of
9+
individual APIs, and will therefore show up in the API specification.
10+
11+
One important difference is data-dependent or value-dependent behavior, as
12+
described in :ref:`data-dependent-output-shapes`. Because such behavior is hard
13+
to implement, implementers may choose to omit such APIs from their library.
14+
15+
Another difference is when the Python language itself prescribes that a
16+
specific type *must* be returned. For those cases, it is not possible to return
17+
a lazy/delayed kind of object to avoid computing a value. This is the case for
18+
five dunder methods: `__bool__`, `__int__`, `__float__`, `__complex__` and
19+
`__index__`. Each implementation has only two choices when one of these methods
20+
is called:
21+
22+
1. Compute a value of the required type (a Python scalar of type `bool`, `int`,
23+
`float` or `complex`), or
24+
2. Raise an exception.
25+
26+
When an implementation is 100% lazy, for example when it serializes a
27+
computation graph, computing the value is not possible and hence such an
28+
implementation has no choice but to raise an exception. For a "mostly lazy"
29+
implementation, it may make sense to trigger execution instead - but it is not
30+
required to, both choices are valid.
31+
32+
A common code construct where this happens is conditional logic, e.g.::
33+
34+
vals = compute_something()
35+
if all(vals):
36+
# The if-statement will make Python call the __bool__ method
37+
# on the result of `all(vals)`.
38+
do_something_else()
39+
40+
Note that the API does not contain control flow constructs, as of now, that
41+
would allow avoiding the implicit `__bool__` call in the example above. The
42+
only control flow-like function is `where`, but there's no function like `cond`
43+
to replace an `if`-statement.

src/array_api_stubs/_2021_12/array_object.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,12 @@ def __ge__(self: array, other: Union[int, float, array], /) -> array:
453453
def __getitem__(
454454
self: array,
455455
key: Union[
456-
int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array
456+
int,
457+
slice,
458+
ellipsis,
459+
None,
460+
Tuple[Union[int, slice, ellipsis, None], ...],
461+
array,
457462
],
458463
/,
459464
) -> array:
@@ -464,7 +469,7 @@ def __getitem__(
464469
----------
465470
self: array
466471
array instance.
467-
key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array]
472+
key: Union[int, slice, ellipsis, None, Tuple[Union[int, slice, ellipsis, None], ...], array]
468473
index key.
469474
470475
Returns

src/array_api_stubs/_2022_12/array_object.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,12 @@ def __ge__(self: array, other: Union[int, float, array], /) -> array:
477477
def __getitem__(
478478
self: array,
479479
key: Union[
480-
int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array
480+
int,
481+
slice,
482+
ellipsis,
483+
None,
484+
Tuple[Union[int, slice, ellipsis, None], ...],
485+
array,
481486
],
482487
/,
483488
) -> array:
@@ -488,7 +493,7 @@ def __getitem__(
488493
----------
489494
self: array
490495
array instance.
491-
key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis], ...], array]
496+
key: Union[int, slice, ellipsis, None, Tuple[Union[int, slice, ellipsis, None], ...], array]
492497
index key.
493498
494499
Returns

src/array_api_stubs/_2022_12/elementwise_functions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def conj(x: array, /) -> array:
727727
Parameters
728728
----------
729729
x: array
730-
input array. Should have a complex-floating point data type.
730+
input array. Should have a complex floating-point data type.
731731
732732
Returns
733733
-------

src/array_api_stubs/_draft/array_object.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ def __bool__(self: array, /) -> bool:
241241
242242
For complex floating-point operands, special cases must be handled as if the operation is implemented as the logical AND of ``bool(real(self))`` and ``bool(imag(self))``.
243243
244+
**Lazy implementations**
245+
246+
The Python language requires the return value to be of type ``bool``. Lazy implementations are therefore not able to return any kind of lazy/delayed object here and should raise a ``ValueError`` instead.
247+
244248
.. versionchanged:: 2022.12
245249
Added boolean and complex data type support.
246250
"""
@@ -276,6 +280,10 @@ def __complex__(self: array, /) -> complex:
276280
- If ``self`` is ``-infinity``, the result is ``-infinity + 0j``.
277281
- If ``self`` is a finite number, the result is ``self + 0j``.
278282
283+
**Lazy implementations**
284+
285+
The Python language requires the return value to be of type ``complex``. Lazy implementations are therefore not able to return any kind of lazy/delayed object here and should raise a ``ValueError`` instead.
286+
279287
.. versionadded:: 2022.12
280288
"""
281289

@@ -424,6 +432,10 @@ def __float__(self: array, /) -> float:
424432
- If ``self`` is ``True``, the result is ``1``.
425433
- If ``self`` is ``False``, the result is ``0``.
426434
435+
**Lazy implementations**
436+
437+
The Python language requires the return value to be of type ``float``. Lazy implementations are therefore not able to return any kind of lazy/delayed object here and should raise a ``ValueError`` instead.
438+
427439
.. versionchanged:: 2022.12
428440
Added boolean and complex data type support.
429441
"""
@@ -479,7 +491,12 @@ def __ge__(self: array, other: Union[int, float, array], /) -> array:
479491
def __getitem__(
480492
self: array,
481493
key: Union[
482-
int, slice, ellipsis, Tuple[Union[int, slice, ellipsis, None], ...], array
494+
int,
495+
slice,
496+
ellipsis,
497+
None,
498+
Tuple[Union[int, slice, ellipsis, None], ...],
499+
array,
483500
],
484501
/,
485502
) -> array:
@@ -490,7 +507,7 @@ def __getitem__(
490507
----------
491508
self: array
492509
array instance.
493-
key: Union[int, slice, ellipsis, Tuple[Union[int, slice, ellipsis, None], ...], array]
510+
key: Union[int, slice, ellipsis, None, Tuple[Union[int, slice, ellipsis, None], ...], array]
494511
index key.
495512
496513
Returns
@@ -539,6 +556,13 @@ def __index__(self: array, /) -> int:
539556
-------
540557
out: int
541558
a Python ``int`` object representing the single element of the array instance.
559+
560+
Notes
561+
-----
562+
563+
**Lazy implementations**
564+
565+
The Python language requires the return value to be of type ``int``. Lazy implementations are therefore not able to return any kind of lazy/delayed object here and should raise a ``ValueError`` instead.
542566
"""
543567

544568
def __int__(self: array, /) -> int:
@@ -577,6 +601,13 @@ def __int__(self: array, /) -> int:
577601
- If ``self`` is either ``+infinity`` or ``-infinity``, raise ``OverflowError``.
578602
- If ``self`` is ``NaN``, raise ``ValueError``.
579603
604+
Notes
605+
-----
606+
607+
**Lazy implementations**
608+
609+
The Python language requires the return value to be of type ``int``. Lazy implementations are therefore not able to return any kind of lazy/delayed object here and should raise a ``ValueError`` instead.
610+
580611
.. versionchanged:: 2022.12
581612
Added boolean and complex data type support.
582613
"""

src/array_api_stubs/_draft/elementwise_functions.py

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"bitwise_xor",
1717
"ceil",
1818
"conj",
19+
"copysign",
1920
"cos",
2021
"cosh",
2122
"divide",
@@ -790,7 +791,7 @@ def conj(x: array, /) -> array:
790791
Parameters
791792
----------
792793
x: array
793-
input array. Should have a complex-floating point data type.
794+
input array. Should have a complex floating-point data type.
794795
795796
Returns
796797
-------
@@ -804,6 +805,47 @@ def conj(x: array, /) -> array:
804805
"""
805806

806807

808+
def copysign(x1: array, x2: array, /) -> array:
809+
r"""
810+
Composes a floating-point value with the magnitude of ``x1_i`` and the sign of ``x2_i`` for each element of the input array ``x1``.
811+
812+
Parameters
813+
----------
814+
x1: array
815+
input array containing magnitudes. Should have a real-valued floating-point data type.
816+
x2: array
817+
input array whose sign bits are applied to the magnitudes of ``x1``. Must be compatible with ``x1`` (see :ref:`broadcasting`). Should have a real-valued floating-point data type.
818+
819+
Returns
820+
-------
821+
out: array
822+
an array containing the element-wise results. The returned array must have a floating-point data type determined by :ref:`type-promotion`.
823+
824+
Notes
825+
-----
826+
827+
**Special cases**
828+
829+
For real-valued floating-point operands, let ``|x|`` be the absolute value, and if ``x1_i`` is not ``NaN``,
830+
831+
- If ``x2_i`` is less than ``0``, the result is ``-|x1_i|``.
832+
- If ``x2_i`` is ``-0``, the result is ``-|x1_i|``.
833+
- If ``x2_i`` is ``+0``, the result is ``|x1_i|``.
834+
- If ``x2_i`` is greater than ``0``, the result is ``|x1_i|``.
835+
- If ``x2_i`` is ``NaN`` and the sign bit of ``x2_i`` is ``1``, the result is ``-|x1_i|``.
836+
- If ``x2_i`` is ``NaN`` and the sign bit of ``x2_i`` is ``0``, the result is ``|x1_i|``.
837+
838+
If ``x1_i`` is ``NaN``,
839+
840+
- If ``x2_i`` is less than ``0``, the result is ``NaN`` with a sign bit of ``1``.
841+
- If ``x2_i`` is ``-0``, the result is ``NaN`` with a sign bit of ``1``.
842+
- If ``x2_i`` is ``+0``, the result is ``NaN`` with a sign bit of ``0``.
843+
- If ``x2_i`` is greater than ``0``, the result is ``NaN`` with a sign bit of ``0``.
844+
- If ``x2_i`` is ``NaN`` and the sign bit of ``x2_i`` is ``1``, the result is ``NaN`` with a sign bit of ``1``.
845+
- If ``x2_i`` is ``NaN`` and the sign bit of ``x2_i`` is ``0``, the result is ``NaN`` with a sign bit of ``0``.
846+
"""
847+
848+
807849
def cos(x: array, /) -> array:
808850
r"""
809851
Calculates an implementation-dependent approximation to the cosine for each element ``x_i`` of the input array ``x``.

src/array_api_stubs/_draft/manipulation_functions.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"roll",
1111
"squeeze",
1212
"stack",
13+
"tile",
1314
"unstack",
1415
]
1516

@@ -257,6 +258,30 @@ def stack(arrays: Union[Tuple[array, ...], List[array]], /, *, axis: int = 0) ->
257258
"""
258259

259260

261+
def tile(x: array, repetitions: Tuple[int, ...], /):
262+
"""
263+
Constructs an array by tiling an input array.
264+
265+
Parameters
266+
----------
267+
x: array
268+
input array.
269+
repetitions: Tuple[int, ...]
270+
number of repetitions along each axis (dimension).
271+
272+
Let ``N = len(x.shape)`` and ``M = len(repetitions)``.
273+
274+
If ``N > M``, the function must prepend ones until all axes (dimensions) are specified (e.g., if ``x`` has shape ``(8,6,4,2)`` and ``repetitions`` is the tuple ``(3,3)``, then ``repetitions`` must be treated as ``(1,1,3,3)``).
275+
276+
If ``N < M``, the function must prepend singleton axes (dimensions) to ``x`` until ``x`` has as many axes (dimensions) as ``repetitions`` specifies (e.g., if ``x`` has shape ``(4,2)`` and ``repetitions`` is the tuple ``(3,3,3,3)``, then ``x`` must be treated as if it has shape ``(1,1,4,2)``).
277+
278+
Returns
279+
-------
280+
out: array
281+
a tiled output array. The returned array must have the same data type as ``x`` and must have a rank (i.e., number of dimensions) equal to ``max(N, M)``. If ``S`` is the shape of the tiled array after prepending singleton dimensions (if necessary) and ``r`` is the tuple of repetitions after prepending ones (if necessary), then the number of elements along each axis (dimension) must satisfy ``S[i]*r[i]``, where ``i`` refers to the ``i`` th axis (dimension).
282+
"""
283+
284+
260285
def unstack(x: array, /, *, axis: int = 0) -> Tuple[array, ...]:
261286
"""
262287
Splits an array in a sequence of arrays along the given axis.

0 commit comments

Comments
 (0)