Skip to content

PR: Implement support for *Python Array API Standard*. - #1406

Open
thomasmansencal wants to merge 1 commit into
developfrom
feature/array-api-support
Open

PR: Implement support for *Python Array API Standard*.#1406
thomasmansencal wants to merge 1 commit into
developfrom
feature/array-api-support

Conversation

@thomasmansencal

@thomasmansencal thomasmansencal commented Jun 2, 2026

Copy link
Copy Markdown
Member

Summary

This PR implements support for the Python Array API Standard, enabling computations to dispatch onto alternative array backends:

  • NumPy (default)
  • JAX
  • PyTorch (including Apple MPS)

Dispatch is currently opt-in and NumPy-only behaviour is unchanged by default. Once enabled, the backend is selected from the type of the input array. It can be enabled three ways:

1. Environment variable, set before importing Colour:

import os

os.environ["COLOUR_SCIENCE__ARRAY_API"] = "1"

import colour
import jax.numpy as jnp

colour.XYZ_to_sRGB(jnp.array([0.20654008, 0.12197225, 0.05136952]))
# Array([0.7057394 , 0.19248262, 0.2235417 ], dtype=float32)

2. Programmatically, toggle the global state at runtime:

import colour
import torch
from colour.utilities import set_array_api_enabled

set_array_api_enabled(True)

colour.XYZ_to_sRGB(torch.tensor([0.20654008, 0.12197225, 0.05136952]))
# tensor([0.7057, 0.1925, 0.2235], dtype=torch.float64)

3. Scoped context manager (also usable as a decorator), enable for a block only:

import colour
import torch
from colour.utilities import array_api_enable

with array_api_enable(True):
    colour.XYZ_to_sRGB(
        torch.tensor([0.20654008, 0.12197225, 0.05136952], device="mps")
    )
# tensor([0.7057, 0.1925, 0.2235], device='mps:0')

What's added

  • Namespace-aware boundary helpers + a full xp_* operation surface in colour.utilities:
    • Namespace resolution: array_namespace, is_numpy_namespace, is_non_ndarray, trace_array_namespace
    • Boundary conversion: as_ndarray, cast_non_ndarray, xp_as_array / xp_as_float_array / xp_as_int_array, xp_astype, xp_ascontiguousarray
    • Shape & manipulation: xp_reshape, xp_squeeze, xp_atleast_1d / xp_atleast_2d, xp_broadcast_to, xp_matrix_transpose, xp_resize, xp_pad, xp_insert
    • Reductions & statistics: xp_average, xp_median, xp_nanmean, xp_trapezoid, xp_gradient
    • Element-wise math: xp_degrees / xp_radians, xp_sinc, xp_round, xp_nan_to_num
    • Linear algebra: xp_lstsq, xp_eig / xp_eigh, xp_create_diagonal
    • Sampling, interpolation & set operations: xp_linspace, xp_interp, xp_select, xp_isin, xp_setxor1d, xp_unique
    • Comparison & testing: xp_isclose, xp_assert_close, xp_assert_equal
  • contextvars-backed global state (Array API enablement, domain-range scale, ndarray copy, caching) for thread/async safety.
  • SciPy-free, dispatchable kernels replacing solver/interpolator hotspots: correlated colour temperature Gauss-Newton (colour.temperature.common), Jakob and Hanika (2019) trilinear interpolation, etc.
  • Default complex precision: COLOUR_SCIENCE__DEFAULT_COMPLEX_DTYPE / set_default_complex_dtype.
  • New public API: CIE_illuminant_D_series, msds_CIE_illuminant_D_series, msds_blackbody, msds_rayleigh_jeans.
  • Cross-backend testing: an xp pytest fixture parametrising numpy/jax/torch/torch-mps, with mps_tolerance_absolute and mps_xfail markers for float32 precision, plus a cross-backend benchmark suite (utilities/benchmark.py).
  • Documentation: a dedicated Array API Support section in advanced.rst.

Performance

Per-suite speed-up vs NumPy (best-of-3, HD inputs): speed-up = NumPy ÷ backend over cases succeeding on both, so higher = faster (e.g. 3.0× = 3× faster than NumPy; < 1.0× = slower). numpy (ms) is the summed best-of-3 over the suite's cases.

Suite cases numpy (ms) jax torch-cpu torch-mps
conversion_graph 207 32973.5 3.1× 3.4× 18×
conversion_graph_iterative 4 38181.9 3.3× 1.8× 12×
difference 17 2230.7 2.4× 2.8× 30×
integration_array 2 211.3 2.6× 1.02× 15×
integration_object 6 3.8 1.3× 0.91× 0.90×
transfer_function 114 3892.1 3.0× 2.2× 18×
adaptation 7 1095.8 3.6× 4.3× 6.0×
characterisation 3 262.2 2.7× 3.7× 9.5×
recovery_array 4 1938.3 2.3× 2.7× 13×
recovery_object 3 508.8 0.60× 0.59× 0.50×
quality_array 4 538.3 2.3× 2.8× 3.3×
quality_object 5 8.1 0.13× 0.56× 0.07×
volume 2 37.5 0.92× 0.93× 1.1×
volume_iterative 2 1957.4 1.03× 1.03× 16×
phenomena 5 176.6 2.2× 3.7× 25×
temperature_array 4 339.3 3.0× 2.5× 23×
temperature_iterative 4 1177.8 0.84× 1.6× 0.74×
blindness 3 166.0 5.4× 16× 7.7×
contrast 1 82.2 2.8× 3.4× 24×
generators_array 3 61.0 2.1× 1.9× 3.3×
generators_object 6 11.5 0.75× 0.67× 0.56×
photometry 3 0.1 0.11× 0.22× 0.03×
overall 409 85854.3 2.8× 2.2× 11×

NumPy is the baseline (1.00×). JAX dispatches asynchronously, so every timed operation is synchronised with jax.block_until_ready before the clock stops: the jax column measures completed computation, not enqueue latency.

Measured on an Apple M1 Max (10-core, 32 GB), macOS 15.7, Python 3.13, NumPy 2.3, PyTorch 2.9, JAX 0.8; 409 cases across 22 suites.

Preflight

Code Style and Quality

  • Unit tests have been implemented and passed.
  • Pyright static checking has been run and passed.
  • Pre-commit hooks have been run and passed.
  • [N/A] New transformations have been added to the Automatic Colour Conversion Graph.
  • New transformations have been exported to the relevant namespaces, e.g. colour, colour.models.

Documentation

  • New features are documented along with examples if relevant.
  • The documentation is Sphinx and numpydoc compliant.

@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 4 times, most recently from bc223a6 to 3163508 Compare June 4, 2026 11:24
@thomasmansencal thomasmansencal changed the title Implement support for *Python Array API Standard*. PR: Implement support for *Python Array API Standard*. Jun 6, 2026
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 22 times, most recently from d825253 to dcdf17c Compare June 13, 2026 21:14
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 4 times, most recently from 71d9f23 to 0a99593 Compare July 8, 2026 23:13
@coveralls

coveralls commented Jul 22, 2026

Copy link
Copy Markdown

Coverage Status

Coverage is 98.33%feature/array-api-support into develop. No base build found for develop.

@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 7 times, most recently from f876179 to 1ea7990 Compare July 29, 2026 09:33
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 5 times, most recently from 0f6eb2d to 533292f Compare August 7, 2026 20:46
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 4 times, most recently from ac82265 to e01afc0 Compare August 16, 2026 00:05
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 2 times, most recently from 015ab6f to d894c8f Compare August 16, 2026 00:52
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch 3 times, most recently from 0f3166e to d62e565 Compare August 16, 2026 07:46
*Colour* now dispatches array operations to the caller's backend (*NumPy*,
*JAX*, *PyTorch*) through the array-namespace machinery in
`colour.utilities.array`. Beyond the mechanical *NumPy* to namespace
conversion, this commit bundles the behaviour and public API changes
documented below so that they remain discoverable under `git blame` and
`git bisect`.

- Support for the *Python Array API Standard* was implemented: array
  operations dispatch to the input backend (*NumPy*, *JAX*, *PyTorch*)
  through the new `colour.utilities.array_namespace` and `xp_*` utilities,
  toggled with `colour.utilities.is_array_api_enabled` and
  `colour.utilities.set_array_api_enabled`.
- `colour.utilities.is_array_api_compat_installed` and
  `colour.utilities.is_array_api_extra_installed` were added.
- `colour.colorimetry.interpolate_signal`,
  `colour.colorimetry.extrapolate_signal` and
  `colour.colorimetry.trim_signal` were added, sharing the spectral
  distribution and multi-spectral distributions resampling implementation.
- `colour.colorimetry.msds_blackbody`,
  `colour.colorimetry.msds_rayleigh_jeans`,
  `colour.colorimetry.CIE_illuminant_D_series`,
  `colour.colorimetry.msds_CIE_illuminant_D_series` and
  `colour.colorimetry.msds_to_XYZ_tristimulus_weighting_factors_ASTME308`
  were added.
- `colour.appearance.eccentricity_factor_Hellwig2022` and
  `colour.appearance.hue_angle_dependency_Hellwig2022` were added.
- `colour.appearance.XYZ_to_Nayatani95` now computes the hue quadrature
  `H` correlate, previously left unset.

- The multi-spectral distributions paths of `colour.colour_fidelity_index`,
  `colour.colour_quality_scale` and `colour.colour_rendering_index` were
  vectorised.
- The `colour.temperature` correlated colour temperature solvers were
  vectorised, replacing the *SciPy* `minimize` calls with closed-form
  Gauss-Newton iterations.

- `colour.colour_rendering_index`: the *"CIE 2024"* `Q_a` general index now
  averages test colour samples 1 to 8, it was averaging all 15.
- `colour.adaptation.chromatic_adaptation_Li2025` now applies domain and
  range scaling.
- The `COLOUR_SCIENCE__FILTER_COLOUR_WARNINGS` environment variable is now
  honoured correctly.

- `colour.utilities.set_caching_enable`,
  `colour.utilities.set_ndarray_copy_enable` and
  `colour.algebra.set_spow_enable` were renamed to `set_caching_enabled`,
  `set_ndarray_copy_enabled` and `set_spow_enabled` respectively, without
  aliases.
- *Multiprocessing* support was removed: `disable_multiprocessing`,
  `multiprocessing_pool` and `ParallelForMultiprocess`.
- Around 80 internal appearance helpers were removed from the
  `colour.appearance` modules `__all__` (`ciecam02`, `ciecam16`,
  `hellwig2022`, `hunt`, `nayatani95`, `llab`, `atd95`).
- `colour.quality.cfi2017.sd_reference_illuminant` and
  `colour.quality.cfi2017.CCT_reference_illuminant` were removed, orphaned
  by the vectorised reference illuminant path.
- The appearance models `compute_H` argument now defaults to `False`.
- `colour.continuous.Signal` and `colour.continuous.MultiSignals` now
  default to `colour.algebra.LinearInterpolator` instead of
  `colour.algebra.KernelInterpolator`: the default *Lanczos* kernel assumes
  uniformly-spaced data, returns incorrect values on non-uniformly-spaced
  domains and overshoots the input range, e.g. by 11% on a step, which are
  surprising properties for the generic continuous signal containers.
  `colour.colorimetry.SpectralDistribution` and
  `colour.colorimetry.MultiSpectralDistributions` are unaffected: they
  select `colour.algebra.SpragueInterpolator` or
  `colour.algebra.CubicSplineInterpolator` according to the domain
  uniformity, as recommended for spectral data.
- The `*_to_msds` definitions now return a `MultiSpectralDistributions`
  instance by default instead of a `numpy.ndarray`.
- `colour.algebra.least_square_mapping_MoorePenrose` now uses batched,
  greater than 2-D, matrix multiplication semantics.
- The *Jiang et al. (2013)* principal component analysis dropped its
  covariance-matrix path; its reference basis functions were regenerated.
- The `colour.temperature` solvers reference values were regenerated to
  match the new Gauss-Newton implementation.
- *Filmic Pro*: the look-up table domain start was changed from `0` to
  `EPSILON` and a `left=0` clamp was added.
- The `_SPOW_ENABLED` and `_SDIV_MODE` module states were migrated to
  `contextvars.ContextVar` for thread and async-task safety.
- The minimum *NumPy* version was raised from 2.0 to 2.1: the array
  operations dispatch through `numpy.cumulative_sum` and the keyword form
  of `numpy.clip`, both introduced in *NumPy* 2.1.
@thomasmansencal
thomasmansencal force-pushed the feature/array-api-support branch from d62e565 to 3d257b3 Compare August 16, 2026 09:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants