Skip to content

Tracking: remaining work for the array-API migration #971

Description

@mwcraig

This is the canonical checklist for finishing the array-API (array-library-agnostic) migration. It supersedes #909 as the place to track status; #909 and its children (#910#913) remain as the CuPy-specific findings.

State as of 2026-08-25 (post-#989/#990): the August batch (#972#980, plus #985 CI coverage), #987 (NaN-aware sum/mean/std/median fallbacks, closing #986), #988 (combiner internals: xp.any/xp.count_nonzero, namespace bool mask dtypes, nbytes-free memory sizing), #989 (array-API median fallback for subtract_overscan, wrapping nanmedian with numpy's NaN-propagating semantics) and #990 (test-only hygiene batch addressing #969/#970 and the test-body numpy-isms) are all merged. #992 (combine-method mask= bucket, 63 → 54) and #993 (gain_correct/flat_correct device=, 54 → 41) are merged too. #995 (namespace dtypes: ccd_process bool, transform_image mask, combine()/ImageFileCollection FITS byte order) and #994 (clip_extrema rank-comparison rewrite) are merged too (2026-08-25, 6724c8e): strict is now 31 on main, and every section-1 bucket that was on the strict failure list is closed. What is left on strict is 18 test-body fixes (section 2) and 13 upstream-blocked tests (section 3). CI exercises numpy, jax, dask (including dask+bottleneck and a dask escape-baseline gate); array-api-strict runs as a soft (continue-on-error) job. CuPy and torch are not in CI. User docs live in docs/array_api.rst. 36 backend_xfail markers remain in the test suite. Later on 2026-08-25, three PRs are open on top of 6724c8e: #996 (narrows the two stale test_cosmicray.py markers, 5 XPASS → 0), #997 (#982 and the correlated add/subtract leak in the wrapper) and #998 (the section-2 test bodies, strict 31 → 15). Merged together locally they give strict 15 failed, 496 passed, 36 skipped, 45 xfailed, 0 xpassed; numpy 557 passed / 35 skipped; jax and dask green apart from xfails. Of the 15, 11 are #929, one each is #936, #983 (test_rebin_ccddata[True-True]) and the two new ccdproc items below (combine(output_file=) writing a namespace array, combine(dtype=int)). All three merged 2026-08-25 (main = 4d7d21b); strict on main verified at exactly those 15. #999 then took the two ccdproc items plus the len(oscan) one-liner; merged 2026-08-26 (main = be80055). Strict on main verified at 13 failed / 503 passed / 36 skipped / 45 xfailed / 0 xpassed, and every remaining failure is upstream-blocked: 8 × #929 via the default sigma_func (median_absolute_deviation), 3 × #929 via Combiner.sigma_clipping (sigma_clip), #936, #983. Section 1 is complete. #946 and #943 are closed. 2026-08-26, later: the #929 half that is fixable on the ccdproc side is now in two PRs off be80055: #1000 (sigma_func → xp-native MAD when the namespace is not numpy; strict 13 → 5) and #1001 (Combiner.sigma_clipping → xp-native clip reproducing astropy.stats.sigma_clip; strict 13 → 10). Numpy keeps astropy in both. Merged together locally: strict 2 failed / 1129 passed / 0 xpassed (#936, #983 only), the two BOUNDARY lines leave the escape baseline, and the dask enforce run is clean.

Current strict result on main (1b9b621): 63 failed, 418 passed, 36 skipped, 46 xfailed, 5 xpassed — down from 85 with #989 + #990. With #993 (711bb26) the strict job was 41; #995 and #994 (6724c8e) take it to 31 failed, 469 passed, 36 skipped, 43 xfailed, 5 xpassed with no new failures; the 5 XPASSes are still the stale test_cosmicray.py markers. The job is continue-on-error, so read the pytest summary line in its log rather than the job conclusion. Every one of the 63 has been traced to a cause (2026-08-25):

# cause where section
12 average_combine/sum_combine/median_combine build the result with CCDData(..., mask=mask); astropy's NDData.mask setter forces numpy and cannot convert a device1 strict array combiner.py:726, :804, :616 1
9 gain_correct calls xp.asarray(gain_value) without device= ("two different devices") core.py:968 1
2 flat_correct calls xp.asarray(flat_mean) without device= (same class) core.py:1057 1
4 ccd_process bad-pixel mask uses builtin bool instead of xp.bool core.py:383 1
4 clip_extrema fancy-index assignment combiner.py:406 1
2 combine() passes a numpy dtype (ccd.data.dtype.type) to the requested namespace's asarray — strict warns, filterwarnings=error fails combiner.py:1082 1
2 uncertainty propagation for multiply/divide delegates to astropy's _propagate_multiply_divide, whose np.abs(...) (nduncertainty.py:832) densifies the strict array and the mixed expression then fails _ccddata_wrapper_for_array_api.py:305–330 1
1 transform_image applies the user transform to the bool mask; strict rejects 10 * bool_array (numpy silently promotes) core.py:1153 1
8 astropy.stats densification: default sigma_func (median_absolute_deviation, 5) and sigma_clipping (sigma_clip, 3) core.py:1291, combiner.py:491 3 (#929)
6 test bodies call astropy CCDData.multiply(...) on strict data to build scaled inputs; astropy's _prepare_then_do_arithmetic does np.result_type(ref, operand) on a strict dtype ("Could not convert Array … to a NumPy dtype") — this is the NDData-arithmetic upstream item already in #940, not a ccdproc code path test_combiner.py (test_combiner_with_scaling*, test_combiner_result_dtype, test_combine_overwrite_output) 2
5 combine() given FITS filenames / numpy CCDData with no array_package= returns numpy data, and the tests then compare it with strict arrays via xp.all(xpx.isclose(...)); array_package is opt-in by design, so the tests should pass it test_combine_average_fitsimages, test_combine_numpyndarray, test_combine_average_ccddata, test_combine_limitedmem_fitsimages, test_combine_limitedmem_scale_fitsimages 2
2 test bodies ccd.write() a device1 strict CCDData; astropy.io.fits cannot coerce it test_combiner_image_file_collection_input, test_combine_image_file_collection_input 2
2 test body xp.asarray(np_mgrid[...]) / 10.0 — integer strict array divided by a float test_ccdproc.py:304 (test_subtract_overscan_model) 2
1 test helper _make_mean_scaler uses .mean() / np.ma.averagethis is the "Combiner.scaling setter bug" from #990's deferred list; the setter is fine test_combiner.py:52 (test_3d_combiner_with_scaling) 2
1 astropy _arithmetic decorator leaks _config_ccd_requires_unit=False (astropy/astropy#20268) test_generator_ccds_without_unit 2/3
1 astropy NDData.mask setter (#983's blocker) test_rebin_ccddata[True-True] 2/3
1 Quantity machinery on a device1 strict array test_unit_mismatch_behaves_as_expected 3 (#936)

So: 36 are ccdproc bugs fixable now (section 1), 17 are test-body fixes (section 2), and 10 are upstream (#929 ×8, #936, astropy#20268) — plus the rebin/mask-setter one that is fixable with the _mask workaround if we want it. The 5 xpassed are all in test_cosmicray.py: the two test_cosmicray_median_masked_column[masked_array-*] cases (stale since #979) and the three gain_apply=False cases of test_cosmicray_gain_correct_uncertainty_namespace (the gain_apply=True cases still xfail, so that marker needs narrowing to the True parametrizations, not removing).

1. Fixable-now bugs in ccdproc (no upstream dependency)

2. Test-suite hygiene

3. Design issues / blocked on upstream (tracked in #940)

4. Tooling / infrastructure

5. Close out

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions