From 8d6e63ab297901f955c30167cdcb60fa0331cc05 Mon Sep 17 00:00:00 2001 From: Trent Nelson Date: Wed, 30 Sep 2026 23:12:24 -0700 Subject: [PATCH] Allow float64 roundoff in XRay detector parity checks Keep structural and status checks exact while bounding fitted-output rounding in the normalized detector fixtures. Document the fixture tolerances and retain the existing tighter FFT bound. Signed-off-by: Trent Nelson --- docs/xray/README.md | 10 +++++++--- tests/xray/test_detector_artifacts.py | 13 ++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/xray/README.md b/docs/xray/README.md index e6c4a25..62b4b16 100644 --- a/docs/xray/README.md +++ b/docs/xray/README.md @@ -161,9 +161,13 @@ For Python A/B checks, pass `batch_rows=False` to `cuphoton.xray.detector_artifacts.build_detector_artifacts_cupy` to force the serial row loop. The default, `True`, batches eligible rows. The manifest records this choice, and serial runs have distinct configuration and resume -identities. Frequency, amplitude, and FFT-frequency arrays match exactly in -the batch-versus-row regression cases; `fft_all` can differ by rounding from -the batched cuFFT plan (the normalized-trace tests use `atol=1e-15`). +identities. Batch-versus-row regression checks preserve array shapes, dtypes, +finite masks, zero padding, mode positions, fit status and FFT frequencies +exactly. Fitted frequencies, amplitudes and filtered amplitude sums can differ +by floating-point rounding, as can the batched cuFFT output. The normalized +trace fixtures use `rtol=0`, with `atol=5e-15` for fitted outputs and +`atol=1e-15` for `fft_all`; these bounds describe those fixtures rather than +arbitrary input scales. `--fit-diagnostics summary` writes one status-aware record per detector row within each processed tile, covering the tile's `tile_x_start` to diff --git a/tests/xray/test_detector_artifacts.py b/tests/xray/test_detector_artifacts.py index 2988a3f..45ff73f 100644 --- a/tests/xray/test_detector_artifacts.py +++ b/tests/xray/test_detector_artifacts.py @@ -1475,13 +1475,20 @@ def _assert_detector_outputs_match( expected: dict[str, np.ndarray], ) -> None: for name, values in expected.items(): - if name == "fft_all": - # The batched cuFFT plan rounds differently from per-row FFTs. + assert actual[name].shape == values.shape + assert actual[name].dtype == values.dtype + if name in {"freq_all", "amp_all", "amp_all_sum_filtered", "fft_all"}: + np.testing.assert_array_equal( + np.isfinite(actual[name]), np.isfinite(values) + ) + np.testing.assert_array_equal(actual[name] == 0, values == 0) + # Row and batched GPU arithmetic can round differently. Keep a + # fixed absolute error budget for these normalized fixtures. np.testing.assert_allclose( actual[name], values, rtol=0.0, - atol=1e-15, + atol=1e-15 if name == "fft_all" else 5e-15, ) else: np.testing.assert_array_equal(actual[name], values)