From d91b32242efaec66a9b2fbc9bc7b4fb4aeb0fdfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Fri, 13 Mar 2026 14:39:04 +0100 Subject: [PATCH 1/9] Add migration guides and tutorial --- spec/2025.12/index.rst | 7 ++ spec/2025.12/migration_guide.md | 205 ++++++++++++++++++++++++++++++++ spec/2025.12/tutorial_basic.md | 112 +++++++++++++++++ 3 files changed, 324 insertions(+) create mode 100644 spec/2025.12/migration_guide.md create mode 100644 spec/2025.12/tutorial_basic.md diff --git a/spec/2025.12/index.rst b/spec/2025.12/index.rst index 9b1c1a0eb..5df9b31cb 100644 --- a/spec/2025.12/index.rst +++ b/spec/2025.12/index.rst @@ -30,6 +30,13 @@ Contents verification_test_suite benchmark_suite +.. toctree:: + :caption: Guides and Tutorials + :maxdepth: 1 + + migration_guide + tutorial_basic + .. toctree:: :caption: Other :maxdepth: 1 diff --git a/spec/2025.12/migration_guide.md b/spec/2025.12/migration_guide.md new file mode 100644 index 000000000..2a0c0ef9b --- /dev/null +++ b/spec/2025.12/migration_guide.md @@ -0,0 +1,205 @@ +(migration-guide)= + +# Migration Guide + +This page is meant to help migrate your codebase to an Array API compliant +implementation. The guide is divided into two parts and, depending on your +exact use-case, you should look thoroughly into at least one of them. + +The first part is dedicated for {ref}`array-producers`. If your library +mimics e.g. NumPy's or Dask's functionality, then you can find there an +additional instructions and guidance on how to ensure downstream users can +easily pick your solution as an array provider for their system/algorithm. + +The second part delves into details for Array API compatibility for +{ref}`array-consumers`. This pertains to any software that performs +multidimensional array manipulation in Python, such as: scikit-learn, SciPy, +or statsmodels. If your software relies on a certain array producing library, +such as NumPy or JAX, then here you can learn how to make it library agnostic +and interchange them with way less friction. + +## Ecosystem + +Apart from the documented standard, the Array API ecosystem also provides +a set of tools and packages to help you with the migration process: + + +(array-api-compat)= + +### Array API Compat + +GitHub: [array-api-compat](https://github.com/data-apis/array-api-compat) + +Although NumPy, Dask, CuPy, and PyTorch support the Array API Standard, there +are still some corner cases where their behavior diverges from the standard. +`array-api-compat` provides a compatibility layer to cover these cases as well. +This is also accompanied by a few utility functions for easier introspection +into array objects. + + +(array-api-strict)= + +### Array API Strict + +GitHub: [array-api-strict](https://github.com/data-apis/array-api-strict) + +`array-api-strict` is a library that provides a strict and minimal +implementation of the Array API Standard. It is designed to be used as +a reference implementation for testing and development purposes. By comparing +your API calls with `array-api-strict` counterparts, you can ensure that your +library is fully compliant with the standard and can serve as a reliable +reference for other developers in the ecosystem. + + +(array-api-tests)= + +### Array API Test + +GitHub: [array-api-tests](https://github.com/data-apis/array-api-tests) + +`array-api-tests` is a collection of tests that can be used to verify the +compliance of your library with the Array API Standard. It includes tests +for array producers, covering a wide range of functionalities and use cases. +By running these tests, you can ensure that your library adheres to the +standard and can be used with compatible array consumers libraries. + + +(array-api-extra)= + +### Array API Extra + +GitHub: [array-api-extra](https://github.com/data-apis/array-api-extra) + +`array-api-extra` is a collection of additional utilities and tools that are +missing from the Array API Standard but can be useful for compliant array +consumers. It includes additional array manipulation and statistical functions. +It is already used by SciPy and scikit-learn. + +The sections below mention when and how to use them. + + +(array-producers)= + +## Array Producers + +For array producers, the central task during the development/migration process +is adhering user-facing API to the Array API Standard. + +The complete API of the standard is documented on the +[API specification](https://data-apis.org/array-api/latest/API_specification/index.html) +page. + +There, each function, constant, and object is described with details +on parameters, return values, and special cases. + +### Testing against Array API + +There are two main ways to test your API for compliance: Either using +`array-api-tests` suite or testing your API manually against `array-api-strict` +reference implementation. + +#### Array API Test suite (Recommended) + +{ref}`array-api-tests` is a test suite which verifies that your API +for adhering to the standard. For each function or method it confirms +it's importable, verifies the signature, and generates multiple test +cases with hypothesis package and runs asserts for the outputs. + +The setup details are enclosed in the GitHub repository, so here we +cover only the minimal workflow: + +1. Install your package, for example in editable mode. +2. Clone `array-api-tests`, and set `ARRAY_API_TESTS_MODULE` environment + variable to your package import name. +3. Inside the `array-api-tests` directory run `pytest` command. There are + multiple useful options delivered by the test suite, a few worth mentioning: + - `--max-examples=2` - maximal number of test cases to generate by the + hypothesis. This allows you to balance between execution time of the test + suite and thoroughness of the testing. + - With `--xfails-file` option you can describe which tests are expected to + fail - it's impossible to get the whole API perfectly implemented on a + first try, so tracking what still fails gives you more control over the + state of your API. + - `-o xfail_strict=` is often used with the previous one. If a test + expected to fail actually passes (`XPASS`) then you can decide whether + to ignore that fact or raise it as an error. + - `--skips-file` for skipping files. At times some failing tests might stall + the execution time of the test suite - in that case the most convenient + option is to skip these for the time being. + +We strongly advise you to embed this setup in your CI as well. This will allow +you to monitor the coverage live, and make sure new changes don't break existing +API. For a reference here's a [NumPy Array API Tests CI setup](https://github.com/numpy/numpy/blob/581d10f43b539a189a2d37856e5130464de9e5f6/.github/workflows/linux.yml#L296). + + +#### Array API Strict + +A simpler, and more manual, way of testing the Array API coverage is to +run your API calls along with {ref}`array-api-strict` Python implementation. + +This way you can ensure the outputs coming from your API match the minimal +reference implementation, but bare in mind you need to write the tests cases +yourself, so you need to also take into account the edge cases as well. + + +(array-consumers)= + +## Array Consumers + +For array consumers the main premise is keep in mind that your **array +manipulation operations should not lock in for a particular array producing +library**. For instance, if you use NumPy for arrays, then your code could +contain: + +```python +import numpy as np + +# ... +b = np.full(shape, val, dtype=dtype) @ a +c = np.mean(a, axis=0) +return np.dot(c, b) +``` + +The first step should be as simple as assigning `np` namespace to a dedicated +namespace variable - the convention in the ecosystem is to name it `xp`. Then +Making sure that each method and function call is something that Array API +supports is vital (we will get to that soon): + +```python +import numpy as np + +xp = np + +# ... +b = xp.full(shape, val, dtype=dtype) @ a +c = xp.mean(a, axis=0) +return xp.tensordot(c, b, axes=1) +``` + +Then replacing one backend with another one should rely on providing a different +namespace, such as: `xp = torch`, e.g. via environment variable. This can be useful +if you're writing a script or in your custom software. The other alternatives are: + +- If you are building a library where the backend is determined by input arrays + passed by the end-user, then a recommended way is to ask your input arrays for a + namespace to use: `xp = arr.__array_namespace__()` +- Each function you implement can have a namespace `xp` as a parameter in the + signature. Then enforcing inputs to be of type by the provided backend can be + achieved with `arg1 = xp.asarray(arg1)` for each input array. + +If you're relying on NumPy, CuPy, PyTorch, Dask, or JAX then +{ref}`array-api-compat` can come in handy for the transition. The compat layer +allows you to still rely on your selection of array producing library, while +making sure you're already using standard compatible API. Additionally, it +offers a set of useful utility functions, such as: + +- [array_namespace()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.array_namespace) + for fetching the namespace based on input arrays. +- [is_array_api_obj()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.is_array_api_obj) + for the introspection whether a given object is Array API compatible. +- [device()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.device) + to get a device the array resides on. + +For now the migration from a specific library (e.g. NumPy) to a standard compatible +setup requires a manual intervention for each failing API call but in the future +we plan to provide some automation tools for it. diff --git a/spec/2025.12/tutorial_basic.md b/spec/2025.12/tutorial_basic.md new file mode 100644 index 000000000..b81a2132a --- /dev/null +++ b/spec/2025.12/tutorial_basic.md @@ -0,0 +1,112 @@ +(tutorial-basic)= + +# Array API Tutorial + +In this tutorial we're going to show the migration from the array consumer +point of view for a simple graph algorithm. + +The example presented here comes from [`graphblas-algorithms`](https://github.com/python-graphblas/graphblas-algorithms). +library. There we can find [the HITS algorithm](https://github.com/python-graphblas/graphblas-algorithms/blob/35dbc90e808c6bf51b63d51d8a63f59238c02975/graphblas_algorithms/algorithms/link_analysis/hits_alg.py#L9), +used for the link analysis for estimating prominence in sparse networks. + +The inlined and slightly simplified (without "authority" feature) +implementation looks like this: + +```python +def hits(G, max_iter=100, tol=1.0e-8, normalized=True): + N = len(G) + h = Vector(float, N, name="h") + a = Vector(float, N, name="a") + h << 1.0 / N + # Power iteration: make up to max_iter iterations + A = G._A + hprev = Vector(float, N, name="h_prev") + for _i in range(max_iter): + hprev, h = h, hprev + a << hprev @ A + h << A @ a + h *= 1.0 / h.reduce(monoid.max).get(0) + if is_converged(hprev, h, tol): + break + else: + raise ConvergenceFailure(max_iter) + if normalized: + h *= 1.0 / h.reduce().get(0) + a *= 1.0 / a.reduce().get(0) + return h, a + +def is_converged(xprev, x, tol): + xprev << binary.minus(xprev | x) + xprev << unary.abs(xprev) + return xprev.reduce().get(0) < xprev.size * tol +``` + +We can see that the API is specific to the GraphBLAS array object. +There is `Vector` constructor, overloaded `<<` for assigning new values, +and `reduce`/`get` for reductions. We need to replace them, and, by convention, +we will use `xp` namespace for calling respective functions. + +First we want to make sure we construct arrays in an agnostic way: + +```python +h = xp.full(N, 1.0 / N) +A = xp.asarray(G.A) +``` + +Then, instead of `reduce` calls we use appropriate reducing +functions from the Array API: + +```python +h = h / xp.max(h) +# ... +h = h / xp.sum(xp.abs(h)) +a = a / xp.sum(xp.abs(a)) +# ... +err = xp.sum(xp.abs(...)) +``` + +We replace custom binary operation with the Array API counterpart: + +```python +...(x - xprev) +``` + +And last but not least, let's ensure that the result of the convergence +condition is a scalar coming from our API: + +```python +err < xp.asarray(N * tol) +``` + +The rewrite is complete now, we can assemble all constituent parts into +a full implementation: + +```python +def hits(G, max_iter=100, tol=1.0e-8, normalized=True): + N = len(G) + h = xp.full(N, 1.0 / N) + A = xp.asarray(G.A) + # Power iteration: make up to max_iter iterations + for _i in range(max_iter): + hprev = h + a = hprev @ A + h = A @ a + h = h / xp.max(h) + if is_converged(hprev, h, N, tol): + break + else: + raise Exception("Didn't converge") + if normalized: + h = h / xp.sum(xp.abs(h)) + a = a / xp.sum(xp.abs(a)) + return h, a + +def is_converged(xprev, x, N, tol): + err = xp.sum(xp.abs(x - xprev)) + return err < xp.asarray(N * tol) +``` + +At this point the actual execution depends only on `xp` namespace, +and replacing that one variable allow us to switch from e.g. NumPy arrays +to a JAX execution on a GPU. This allows us to be more flexible, and, for +example use lazy evaluation and JIT compile a loop body with JAX's JIT compilation. From a4e274b9f2b0fcb685992c3015bf19f55f0cf5ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Tue, 17 Mar 2026 17:32:23 +0100 Subject: [PATCH 2/9] Apply review comments --- spec/2025.12/migration_guide.md | 63 ++++++++++++++++++++++----------- spec/2025.12/tutorial_basic.md | 50 ++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 22 deletions(-) diff --git a/spec/2025.12/migration_guide.md b/spec/2025.12/migration_guide.md index 2a0c0ef9b..a86e5ad7c 100644 --- a/spec/2025.12/migration_guide.md +++ b/spec/2025.12/migration_guide.md @@ -29,12 +29,14 @@ a set of tools and packages to help you with the migration process: ### Array API Compat GitHub: [array-api-compat](https://github.com/data-apis/array-api-compat) +User group: Array Consumers Although NumPy, Dask, CuPy, and PyTorch support the Array API Standard, there are still some corner cases where their behavior diverges from the standard. `array-api-compat` provides a compatibility layer to cover these cases as well. This is also accompanied by a few utility functions for easier introspection -into array objects. +into array objects. As an array consumer you can still rely on the original API +while having access to the standard compatible one. (array-api-strict)= @@ -42,13 +44,16 @@ into array objects. ### Array API Strict GitHub: [array-api-strict](https://github.com/data-apis/array-api-strict) +User group: Array Consumers, Array Producers (for testing) `array-api-strict` is a library that provides a strict and minimal -implementation of the Array API Standard. It is designed to be used as -a reference implementation for testing and development purposes. By comparing -your API calls with `array-api-strict` counterparts, you can ensure that your -library is fully compliant with the standard and can serve as a reliable -reference for other developers in the ecosystem. +implementation of the Array API Standard. For array producers it is designed +to be used as a reference implementation for testing and development purposes. +You can compare your API calls with `array-api-strict` counterparts, +and ensure that your library is fully compliant with the standard and can +serve as a reliable reference for other developers in the ecosystem. +For consumers you can use `array-api-strict` during the development as an +array provider to ensure your code uses API compliant with the standard. (array-api-tests)= @@ -56,6 +61,7 @@ reference for other developers in the ecosystem. ### Array API Test GitHub: [array-api-tests](https://github.com/data-apis/array-api-tests) +User group: Array Producers `array-api-tests` is a collection of tests that can be used to verify the compliance of your library with the Array API Standard. It includes tests @@ -69,6 +75,7 @@ standard and can be used with compatible array consumers libraries. ### Array API Extra GitHub: [array-api-extra](https://github.com/data-apis/array-api-extra) +User group: Array Consumers `array-api-extra` is a collection of additional utilities and tools that are missing from the Array API Standard but can be useful for compliant array @@ -103,7 +110,8 @@ reference implementation. {ref}`array-api-tests` is a test suite which verifies that your API for adhering to the standard. For each function or method it confirms it's importable, verifies the signature, and generates multiple test -cases with hypothesis package and runs asserts for the outputs. +cases with [hypothesis](https://hypothesis.readthedocs.io/en/latest/) +package and runs asserts for the outputs. The setup details are enclosed in the GitHub repository, so here we cover only the minimal workflow: @@ -113,9 +121,12 @@ cover only the minimal workflow: variable to your package import name. 3. Inside the `array-api-tests` directory run `pytest` command. There are multiple useful options delivered by the test suite, a few worth mentioning: - - `--max-examples=2` - maximal number of test cases to generate by the + - `--max-examples=1000` - maximal number of test cases to generate by the hypothesis. This allows you to balance between execution time of the test - suite and thoroughness of the testing. + suite and thoroughness of the testing. It's advised to use as many examples + as the time buget can fit. Each test case is a random combination of + possible inputs: the more cases, the higher chance of finding an unsupported + edge case. - With `--xfails-file` option you can describe which tests are expected to fail - it's impossible to get the whole API perfectly implemented on a first try, so tracking what still fails gives you more control over the @@ -123,7 +134,7 @@ cover only the minimal workflow: - `-o xfail_strict=` is often used with the previous one. If a test expected to fail actually passes (`XPASS`) then you can decide whether to ignore that fact or raise it as an error. - - `--skips-file` for skipping files. At times some failing tests might stall + - `--skips-file` for skipping tests. At times some failing tests might stall the execution time of the test suite - in that case the most convenient option is to skip these for the time being. @@ -138,7 +149,7 @@ A simpler, and more manual, way of testing the Array API coverage is to run your API calls along with {ref}`array-api-strict` Python implementation. This way you can ensure the outputs coming from your API match the minimal -reference implementation, but bare in mind you need to write the tests cases +reference implementation, but bear in mind you need to write the tests cases yourself, so you need to also take into account the edge cases as well. @@ -146,7 +157,7 @@ yourself, so you need to also take into account the edge cases as well. ## Array Consumers -For array consumers the main premise is keep in mind that your **array +For array consumers the main premise is to keep in mind that your **array manipulation operations should not lock in for a particular array producing library**. For instance, if you use NumPy for arrays, then your code could contain: @@ -162,8 +173,9 @@ return np.dot(c, b) The first step should be as simple as assigning `np` namespace to a dedicated namespace variable - the convention in the ecosystem is to name it `xp`. Then -Making sure that each method and function call is something that Array API -supports is vital (we will get to that soon): +making sure that each method and function call is something that Array API +supports is vital. `dot` is present in the NumPy's API but the standard doesn't +support it. Let's use `tensordot` instead - both NumPy and the standard define it: ```python import numpy as np @@ -180,12 +192,23 @@ Then replacing one backend with another one should rely on providing a different namespace, such as: `xp = torch`, e.g. via environment variable. This can be useful if you're writing a script or in your custom software. The other alternatives are: -- If you are building a library where the backend is determined by input arrays - passed by the end-user, then a recommended way is to ask your input arrays for a - namespace to use: `xp = arr.__array_namespace__()` -- Each function you implement can have a namespace `xp` as a parameter in the - signature. Then enforcing inputs to be of type by the provided backend can be - achieved with `arg1 = xp.asarray(arg1)` for each input array. +- If you are building a library where the backend is determined by input arrays, + and your function accepts array arguments, then a recommended way is to ask + your input arrays for a namespace to use: `xp = arr.__array_namespace__()`. + If the given library doesn't have it, then [`array_api_compat.array_namespace()`](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.array_namespace) + should be used instead: + ```python + def func(array1, scalar1, scalar2): + xp = array1.__array_namespace__() # or array_namespace(array1) + return xp.arange(scalar1, scalar2) @ array1 + ``` +- For a function that accepts scalars and returns arrays, use namespace `xp` as + a parameter in the signature. Then enforcing objects to be of type by the + provided backend can be achieved with `arg1 = xp.asarray(arg1)` for each input: + ```python + def func(s1, s2, xp): + return xp.arange(s1, s2) + ``` If you're relying on NumPy, CuPy, PyTorch, Dask, or JAX then {ref}`array-api-compat` can come in handy for the transition. The compat layer diff --git a/spec/2025.12/tutorial_basic.md b/spec/2025.12/tutorial_basic.md index b81a2132a..13cd98dc7 100644 --- a/spec/2025.12/tutorial_basic.md +++ b/spec/2025.12/tutorial_basic.md @@ -108,5 +108,51 @@ def is_converged(xprev, x, N, tol): At this point the actual execution depends only on `xp` namespace, and replacing that one variable allow us to switch from e.g. NumPy arrays -to a JAX execution on a GPU. This allows us to be more flexible, and, for -example use lazy evaluation and JIT compile a loop body with JAX's JIT compilation. +to a JAX execution on a GPU. This lets us be more flexible, and, for example, +use lazy evaluation and JIT compile a loop body with JAX's JIT compilation: + +```python +import jax +import jax.numpy as jnp + +xp = jnp + +def hits(G, max_iter=100, tol=1.0e-8, normalized=True): + N = len(G) + h = xp.full((N, 1), 1.0 / N) + A = xp.asarray(G.A) + # Power iteration: make up to max_iter iterations + for _i in range(max_iter): + h, a, conv = loop_body(h, A, N, tol) + if conv: + break + else: + raise Exception("Didn't converge") + if normalized: + h = h / xp.sum(xp.abs(h)) + a = a / xp.sum(xp.abs(a)) + return h, a + +@jax.jit +def loop_body(hprev, A, N, tol): + a = hprev.mT @ A + h = A @ a.mT + h = h / xp.max(h) + conv = is_converged(hprev, h, N, tol) + return h, a, conv + +def is_converged(xprev, x, N, tol): + err = xp.sum(xp.abs(x - xprev)) + return err < xp.asarray(N * tol) + +if __name__ == "__main__": + + class Graph(): + def __init__(self): + self.A = xp.ones((10, 10)) + def __len__(self): + return len(self.A) + + G = Graph() + h, a = hits(G) +``` From 52880767f8cc8b4399599cb5eda62ae6479267d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Wed, 18 Mar 2026 11:30:34 +0100 Subject: [PATCH 3/9] Review comments --- spec/2025.12/migration_guide.md | 86 +++++++++++++++++---------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/spec/2025.12/migration_guide.md b/spec/2025.12/migration_guide.md index a86e5ad7c..4ebe9ef54 100644 --- a/spec/2025.12/migration_guide.md +++ b/spec/2025.12/migration_guide.md @@ -6,17 +6,19 @@ This page is meant to help migrate your codebase to an Array API compliant implementation. The guide is divided into two parts and, depending on your exact use-case, you should look thoroughly into at least one of them. -The first part is dedicated for {ref}`array-producers`. If your library -mimics e.g. NumPy's or Dask's functionality, then you can find there an -additional instructions and guidance on how to ensure downstream users can -easily pick your solution as an array provider for their system/algorithm. +The first part is dedicated for {ref}`array-producers`. If your library +mimics, for example, NumPy's or Dask's functionality, then you can find in +the first part additional instructions and guidance on how to ensure +downstream users can easily pick your solution as an array provider for +their system/algorithm. The second part delves into details for Array API compatibility for {ref}`array-consumers`. This pertains to any software that performs -multidimensional array manipulation in Python, such as: scikit-learn, SciPy, -or statsmodels. If your software relies on a certain array producing library, -such as NumPy or JAX, then here you can learn how to make it library agnostic -and interchange them with way less friction. +multidimensional array manipulation in Python, such as may be found in +scikit-learn, SciPy, or statsmodels. If your software relies on a certain +array producing library, such as NumPy or JAX, then you can use the second +part to learn how to make it library agnostic and interchange array +namespaces with significantly less friction. ## Ecosystem @@ -33,10 +35,10 @@ User group: Array Consumers Although NumPy, Dask, CuPy, and PyTorch support the Array API Standard, there are still some corner cases where their behavior diverges from the standard. -`array-api-compat` provides a compatibility layer to cover these cases as well. +`array-api-compat` provides a compatibility layer to cover these cases. This is also accompanied by a few utility functions for easier introspection -into array objects. As an array consumer you can still rely on the original API -while having access to the standard compatible one. +into array objects. As an array consumer, you can still rely on the original +API while having access to the standard compatible one. (array-api-strict)= @@ -47,12 +49,12 @@ GitHub: [array-api-strict](https://github.com/data-apis/array-api-strict) User group: Array Consumers, Array Producers (for testing) `array-api-strict` is a library that provides a strict and minimal -implementation of the Array API Standard. For array producers it is designed +implementation of the Array API Standard. For array producers, it is designed to be used as a reference implementation for testing and development purposes. -You can compare your API calls with `array-api-strict` counterparts, -and ensure that your library is fully compliant with the standard and can +You can compare your API calls with `array-api-strict` counterparts and +ensure that your library is fully compliant with the standard and can serve as a reliable reference for other developers in the ecosystem. -For consumers you can use `array-api-strict` during the development as an +For consumers, you can use `array-api-strict` during the development as an array provider to ensure your code uses API compliant with the standard. @@ -90,28 +92,27 @@ The sections below mention when and how to use them. ## Array Producers For array producers, the central task during the development/migration process -is adhering user-facing API to the Array API Standard. +is ensuring that the user-facing API adheres to the Array API Standard. -The complete API of the standard is documented on the -[API specification](https://data-apis.org/array-api/latest/API_specification/index.html) -page. +The complete API of the standard is documented in the +[API specification](https://data-apis.org/array-api/latest/API_specification/index.html). There, each function, constant, and object is described with details on parameters, return values, and special cases. ### Testing against Array API -There are two main ways to test your API for compliance: Either using -`array-api-tests` suite or testing your API manually against `array-api-strict` -reference implementation. +There are two main ways to test your API for compliance: either using +`array-api-tests` suite or testing your API manually against the +`array-api-strict` reference implementation. #### Array API Test suite (Recommended) {ref}`array-api-tests` is a test suite which verifies that your API -for adhering to the standard. For each function or method it confirms -it's importable, verifies the signature, and generates multiple test +adheres to the standard. For each function or method, it confirms +it's importable, verifies the signature, generates multiple test cases with [hypothesis](https://hypothesis.readthedocs.io/en/latest/) -package and runs asserts for the outputs. +package, and runs assertions on the outputs. The setup details are enclosed in the GitHub repository, so here we cover only the minimal workflow: @@ -120,44 +121,45 @@ cover only the minimal workflow: 2. Clone `array-api-tests`, and set `ARRAY_API_TESTS_MODULE` environment variable to your package import name. 3. Inside the `array-api-tests` directory run `pytest` command. There are - multiple useful options delivered by the test suite, a few worth mentioning: + multiple useful options delivered by the test suite. A few worth mentioning: - `--max-examples=1000` - maximal number of test cases to generate by the hypothesis. This allows you to balance between execution time of the test suite and thoroughness of the testing. It's advised to use as many examples as the time buget can fit. Each test case is a random combination of - possible inputs: the more cases, the higher chance of finding an unsupported - edge case. - - With `--xfails-file` option you can describe which tests are expected to - fail - it's impossible to get the whole API perfectly implemented on a + possible inputs: the more cases, the higher chance of finding an + unsupported edge case. + - With the `--xfails-file` option, you can describe which tests are expected + to fail. It's impossible to get the whole API perfectly implemented on a first try, so tracking what still fails gives you more control over the state of your API. - `-o xfail_strict=` is often used with the previous one. If a test - expected to fail actually passes (`XPASS`) then you can decide whether + expected to fail actually passes (`XPASS`), then you can decide whether to ignore that fact or raise it as an error. - - `--skips-file` for skipping tests. At times some failing tests might stall - the execution time of the test suite - in that case the most convenient + - `--skips-file` for skipping tests. At times, some failing tests might stall + the execution time of the test suite. In that case, the most convenient option is to skip these for the time being. We strongly advise you to embed this setup in your CI as well. This will allow you to monitor the coverage live, and make sure new changes don't break existing -API. For a reference here's a [NumPy Array API Tests CI setup](https://github.com/numpy/numpy/blob/581d10f43b539a189a2d37856e5130464de9e5f6/.github/workflows/linux.yml#L296). +APIs. For a reference, here's a [NumPy Array API Tests CI setup](https://github.com/numpy/numpy/blob/581d10f43b539a189a2d37856e5130464de9e5f6/.github/workflows/linux.yml#L296). #### Array API Strict A simpler, and more manual, way of testing the Array API coverage is to -run your API calls along with {ref}`array-api-strict` Python implementation. +run your API calls along with the {ref}`array-api-strict` Python implementation. This way you can ensure the outputs coming from your API match the minimal -reference implementation, but bear in mind you need to write the tests cases -yourself, so you need to also take into account the edge cases as well. +reference implementation. Bear in mind, however, that you need to write +the tests cases yourself, so you need to also take into account the edge +cases as well. (array-consumers)= ## Array Consumers -For array consumers the main premise is to keep in mind that your **array +For array consumers, the main premise is to keep in mind that your **array manipulation operations should not lock in for a particular array producing library**. For instance, if you use NumPy for arrays, then your code could contain: @@ -172,7 +174,7 @@ return np.dot(c, b) ``` The first step should be as simple as assigning `np` namespace to a dedicated -namespace variable - the convention in the ecosystem is to name it `xp`. Then +namespace variable. The convention in the ecosystem is to name it `xp`. Then making sure that each method and function call is something that Array API supports is vital. `dot` is present in the NumPy's API but the standard doesn't support it. Let's use `tensordot` instead - both NumPy and the standard define it: @@ -223,6 +225,6 @@ offers a set of useful utility functions, such as: - [device()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.device) to get a device the array resides on. -For now the migration from a specific library (e.g. NumPy) to a standard compatible -setup requires a manual intervention for each failing API call but in the future -we plan to provide some automation tools for it. +For now, the migration from a specific library (e.g., NumPy) to a standard +compatible setup requires a manual intervention for each failing API call, +but, in the future, we plan to provide some automation tools for it. From 6136b9b1a10a0313050817358446bb2cefaaaa25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Wed, 18 Mar 2026 16:47:12 +0100 Subject: [PATCH 4/9] `tensordot` explanation line --- spec/2025.12/migration_guide.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/2025.12/migration_guide.md b/spec/2025.12/migration_guide.md index 4ebe9ef54..32f4d96ba 100644 --- a/spec/2025.12/migration_guide.md +++ b/spec/2025.12/migration_guide.md @@ -176,8 +176,10 @@ return np.dot(c, b) The first step should be as simple as assigning `np` namespace to a dedicated namespace variable. The convention in the ecosystem is to name it `xp`. Then making sure that each method and function call is something that Array API -supports is vital. `dot` is present in the NumPy's API but the standard doesn't -support it. Let's use `tensordot` instead - both NumPy and the standard define it: +supports is vital. `dot` is present in the NumPy's API but the standard +doesn't support it. For the sake of simplicity let's assume both `c` and `b` +are `ndim=2`, therefore we select `tensordot` instead - both NumPy and the +standard define it: ```python import numpy as np From 28321a5c4dc20a1d71157a1d846fcf51a9a2279c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Wed, 18 Mar 2026 19:33:14 +0100 Subject: [PATCH 5/9] Move contents to draft --- spec/2025.12/index.rst | 7 ------- spec/draft/index.rst | 7 +++++++ spec/{2025.12 => draft}/migration_guide.md | 0 spec/{2025.12 => draft}/tutorial_basic.md | 0 4 files changed, 7 insertions(+), 7 deletions(-) rename spec/{2025.12 => draft}/migration_guide.md (100%) rename spec/{2025.12 => draft}/tutorial_basic.md (100%) diff --git a/spec/2025.12/index.rst b/spec/2025.12/index.rst index 5df9b31cb..9b1c1a0eb 100644 --- a/spec/2025.12/index.rst +++ b/spec/2025.12/index.rst @@ -30,13 +30,6 @@ Contents verification_test_suite benchmark_suite -.. toctree:: - :caption: Guides and Tutorials - :maxdepth: 1 - - migration_guide - tutorial_basic - .. toctree:: :caption: Other :maxdepth: 1 diff --git a/spec/draft/index.rst b/spec/draft/index.rst index 9b1c1a0eb..5df9b31cb 100644 --- a/spec/draft/index.rst +++ b/spec/draft/index.rst @@ -30,6 +30,13 @@ Contents verification_test_suite benchmark_suite +.. toctree:: + :caption: Guides and Tutorials + :maxdepth: 1 + + migration_guide + tutorial_basic + .. toctree:: :caption: Other :maxdepth: 1 diff --git a/spec/2025.12/migration_guide.md b/spec/draft/migration_guide.md similarity index 100% rename from spec/2025.12/migration_guide.md rename to spec/draft/migration_guide.md diff --git a/spec/2025.12/tutorial_basic.md b/spec/draft/tutorial_basic.md similarity index 100% rename from spec/2025.12/tutorial_basic.md rename to spec/draft/tutorial_basic.md From e58b6897ab046662c5e74565d4c08f87e36e603e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Wed, 18 Mar 2026 19:40:00 +0100 Subject: [PATCH 6/9] Linting --- spec/draft/migration_guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/draft/migration_guide.md b/spec/draft/migration_guide.md index 32f4d96ba..54b2c9303 100644 --- a/spec/draft/migration_guide.md +++ b/spec/draft/migration_guide.md @@ -31,6 +31,7 @@ a set of tools and packages to help you with the migration process: ### Array API Compat GitHub: [array-api-compat](https://github.com/data-apis/array-api-compat) + User group: Array Consumers Although NumPy, Dask, CuPy, and PyTorch support the Array API Standard, there @@ -46,6 +47,7 @@ API while having access to the standard compatible one. ### Array API Strict GitHub: [array-api-strict](https://github.com/data-apis/array-api-strict) + User group: Array Consumers, Array Producers (for testing) `array-api-strict` is a library that provides a strict and minimal @@ -63,6 +65,7 @@ array provider to ensure your code uses API compliant with the standard. ### Array API Test GitHub: [array-api-tests](https://github.com/data-apis/array-api-tests) + User group: Array Producers `array-api-tests` is a collection of tests that can be used to verify the @@ -77,6 +80,7 @@ standard and can be used with compatible array consumers libraries. ### Array API Extra GitHub: [array-api-extra](https://github.com/data-apis/array-api-extra) + User group: Array Consumers `array-api-extra` is a collection of additional utilities and tools that are From feea3f9921d2d7c179578ade8baeb87bb450f518 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 19 Mar 2026 01:25:39 -0700 Subject: [PATCH 7/9] docs: update copy --- spec/draft/migration_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/draft/migration_guide.md b/spec/draft/migration_guide.md index 54b2c9303..143b5864d 100644 --- a/spec/draft/migration_guide.md +++ b/spec/draft/migration_guide.md @@ -57,7 +57,7 @@ You can compare your API calls with `array-api-strict` counterparts and ensure that your library is fully compliant with the standard and can serve as a reliable reference for other developers in the ecosystem. For consumers, you can use `array-api-strict` during the development as an -array provider to ensure your code uses API compliant with the standard. +array provider to ensure your code uses APIs compliant with the standard. (array-api-tests)= From e382094de19f24a65a20cb15cb16ee1678b92f06 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 19 Mar 2026 01:41:26 -0700 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/migration_guide.md | 54 +++++++++++++++++------------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/spec/draft/migration_guide.md b/spec/draft/migration_guide.md index 143b5864d..9babf2f35 100644 --- a/spec/draft/migration_guide.md +++ b/spec/draft/migration_guide.md @@ -72,7 +72,7 @@ User group: Array Producers compliance of your library with the Array API Standard. It includes tests for array producers, covering a wide range of functionalities and use cases. By running these tests, you can ensure that your library adheres to the -standard and can be used with compatible array consumers libraries. +standard and can be used with compatible array consumer libraries. (array-api-extra)= @@ -115,18 +115,18 @@ There are two main ways to test your API for compliance: either using {ref}`array-api-tests` is a test suite which verifies that your API adheres to the standard. For each function or method, it confirms it's importable, verifies the signature, generates multiple test -cases with [hypothesis](https://hypothesis.readthedocs.io/en/latest/) +cases with the [hypothesis](https://hypothesis.readthedocs.io/en/latest/) package, and runs assertions on the outputs. The setup details are enclosed in the GitHub repository, so here we cover only the minimal workflow: -1. Install your package, for example in editable mode. -2. Clone `array-api-tests`, and set `ARRAY_API_TESTS_MODULE` environment +1. Install your package (e.g., in editable mode). +2. Clone `array-api-tests`, and set the `ARRAY_API_TESTS_MODULE` environment variable to your package import name. -3. Inside the `array-api-tests` directory run `pytest` command. There are +3. Inside the `array-api-tests` directory run the command for running pytest: `pytest`. There are multiple useful options delivered by the test suite. A few worth mentioning: - - `--max-examples=1000` - maximal number of test cases to generate by the + - `--max-examples=1000` - maximal number of test cases to generate when using hypothesis. This allows you to balance between execution time of the test suite and thoroughness of the testing. It's advised to use as many examples as the time buget can fit. Each test case is a random combination of @@ -136,7 +136,7 @@ cover only the minimal workflow: to fail. It's impossible to get the whole API perfectly implemented on a first try, so tracking what still fails gives you more control over the state of your API. - - `-o xfail_strict=` is often used with the previous one. If a test + - `-o xfail_strict=` is often used with the previous option. If a test expected to fail actually passes (`XPASS`), then you can decide whether to ignore that fact or raise it as an error. - `--skips-file` for skipping tests. At times, some failing tests might stall @@ -144,19 +144,19 @@ cover only the minimal workflow: option is to skip these for the time being. We strongly advise you to embed this setup in your CI as well. This will allow -you to monitor the coverage live, and make sure new changes don't break existing -APIs. For a reference, here's a [NumPy Array API Tests CI setup](https://github.com/numpy/numpy/blob/581d10f43b539a189a2d37856e5130464de9e5f6/.github/workflows/linux.yml#L296). +you to continuously monitor Array API coverage, and make sure new changes don't break existing +APIs. As a reference, see [NumPy's Array API Tests CI setup](https://github.com/numpy/numpy/blob/581d10f43b539a189a2d37856e5130464de9e5f6/.github/workflows/linux.yml#L296). #### Array API Strict -A simpler, and more manual, way of testing the Array API coverage is to +A simpler, and more manual, way of testing Array API coverage is to run your API calls along with the {ref}`array-api-strict` Python implementation. -This way you can ensure the outputs coming from your API match the minimal +This way, you can ensure that the outputs coming from your API match the minimal reference implementation. Bear in mind, however, that you need to write -the tests cases yourself, so you need to also take into account the edge -cases as well. +the tests cases yourself, so you need to also take into account any applicable edge +cases. (array-consumers)= @@ -177,12 +177,12 @@ c = np.mean(a, axis=0) return np.dot(c, b) ``` -The first step should be as simple as assigning `np` namespace to a dedicated -namespace variable. The convention in the ecosystem is to name it `xp`. Then -making sure that each method and function call is something that Array API -supports is vital. `dot` is present in the NumPy's API but the standard -doesn't support it. For the sake of simplicity let's assume both `c` and `b` -are `ndim=2`, therefore we select `tensordot` instead - both NumPy and the +The first step should be as simple as assigning the `np` namespace to a dedicated +namespace variable. The convention used in the ecosystem is to name it `xp`. Then, +it is vital to ensure that each method and function call is something that the Array API +supports. For example, `dot` is present in the NumPy's API, but the standard +doesn't support it. For the sake of simplicity, let's assume both `c` and `b` +are `ndim=2`; therefore, we select `tensordot` instead, as both NumPy and the standard define it: ```python @@ -196,8 +196,8 @@ c = xp.mean(a, axis=0) return xp.tensordot(c, b, axes=1) ``` -Then replacing one backend with another one should rely on providing a different -namespace, such as: `xp = torch`, e.g. via environment variable. This can be useful +At this point, replacing one backend with another one should only require providing a different +namespace, such as `xp = torch` (e.g., via an environment variable). This can be useful if you're writing a script or in your custom software. The other alternatives are: - If you are building a library where the backend is determined by input arrays, @@ -211,8 +211,8 @@ if you're writing a script or in your custom software. The other alternatives ar return xp.arange(scalar1, scalar2) @ array1 ``` - For a function that accepts scalars and returns arrays, use namespace `xp` as - a parameter in the signature. Then enforcing objects to be of type by the - provided backend can be achieved with `arg1 = xp.asarray(arg1)` for each input: + a parameter in the signature. Enforcing objects to have the same type as the + provided backend can then be achieved with `arg1 = xp.asarray(arg1)` for each input: ```python def func(s1, s2, xp): return xp.arange(s1, s2) @@ -220,17 +220,17 @@ if you're writing a script or in your custom software. The other alternatives ar If you're relying on NumPy, CuPy, PyTorch, Dask, or JAX then {ref}`array-api-compat` can come in handy for the transition. The compat layer -allows you to still rely on your selection of array producing library, while +allows you to still rely on your preferred array producing library, while making sure you're already using standard compatible API. Additionally, it offers a set of useful utility functions, such as: - [array_namespace()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.array_namespace) for fetching the namespace based on input arrays. - [is_array_api_obj()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.is_array_api_obj) - for the introspection whether a given object is Array API compatible. + for inspecting whether a given object is Array API compatible. - [device()](https://data-apis.org/array-api-compat/helper-functions.html#array_api_compat.device) - to get a device the array resides on. + for retrieving the device on which an array resides. For now, the migration from a specific library (e.g., NumPy) to a standard compatible setup requires a manual intervention for each failing API call, -but, in the future, we plan to provide some automation tools for it. +but, in the future, we're hoping to provide tools for automating the migration process. From 05a3aa09c56c3b75fa05db69639eef47c2156333 Mon Sep 17 00:00:00 2001 From: Athan Date: Thu, 19 Mar 2026 01:48:23 -0700 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Athan --- spec/draft/tutorial_basic.md | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/spec/draft/tutorial_basic.md b/spec/draft/tutorial_basic.md index 13cd98dc7..ea8a3b453 100644 --- a/spec/draft/tutorial_basic.md +++ b/spec/draft/tutorial_basic.md @@ -2,15 +2,15 @@ # Array API Tutorial -In this tutorial we're going to show the migration from the array consumer +In this tutorial, we're going to demonstrate how to migrate to the Array API from the array consumer's point of view for a simple graph algorithm. -The example presented here comes from [`graphblas-algorithms`](https://github.com/python-graphblas/graphblas-algorithms). -library. There we can find [the HITS algorithm](https://github.com/python-graphblas/graphblas-algorithms/blob/35dbc90e808c6bf51b63d51d8a63f59238c02975/graphblas_algorithms/algorithms/link_analysis/hits_alg.py#L9), -used for the link analysis for estimating prominence in sparse networks. +The example presented here comes from the [`graphblas-algorithms`](https://github.com/python-graphblas/graphblas-algorithms). +library. In particular, we'll be migrating [the HITS algorithm](https://github.com/python-graphblas/graphblas-algorithms/blob/35dbc90e808c6bf51b63d51d8a63f59238c02975/graphblas_algorithms/algorithms/link_analysis/hits_alg.py#L9), which is +used for the link analysis for estimating prominence in sparse networks, to be Array API compliant. The inlined and slightly simplified (without "authority" feature) -implementation looks like this: +implementation looks similar to the following: ```python def hits(G, max_iter=100, tol=1.0e-8, normalized=True): @@ -42,18 +42,18 @@ def is_converged(xprev, x, tol): ``` We can see that the API is specific to the GraphBLAS array object. -There is `Vector` constructor, overloaded `<<` for assigning new values, +There is a `Vector` constructor, overloaded `<<` for assigning new values, and `reduce`/`get` for reductions. We need to replace them, and, by convention, we will use `xp` namespace for calling respective functions. -First we want to make sure we construct arrays in an agnostic way: +First, we want to make sure we construct arrays in an agnostic way: ```python h = xp.full(N, 1.0 / N) A = xp.asarray(G.A) ``` -Then, instead of `reduce` calls we use appropriate reducing +Then, instead of `reduce` calls, we will use appropriate reduction functions from the Array API: ```python @@ -65,13 +65,13 @@ a = a / xp.sum(xp.abs(a)) err = xp.sum(xp.abs(...)) ``` -We replace custom binary operation with the Array API counterpart: +We replace the custom binary operation with the Array API counterpart: ```python ...(x - xprev) ``` -And last but not least, let's ensure that the result of the convergence +And finally, let's ensure that the result of the convergence condition is a scalar coming from our API: ```python @@ -106,9 +106,9 @@ def is_converged(xprev, x, N, tol): return err < xp.asarray(N * tol) ``` -At this point the actual execution depends only on `xp` namespace, -and replacing that one variable allow us to switch from e.g. NumPy arrays -to a JAX execution on a GPU. This lets us be more flexible, and, for example, +At this point, the actual execution depends only on `xp` namespace, +and replacing that one variable will allow us to switch from, e.g., NumPy arrays on the CPU +to JAX arrays for running on a GPU. This lets us be more flexible, and, for example, use lazy evaluation and JIT compile a loop body with JAX's JIT compilation: ```python