Repository navigation
feat: opt-in quadrature pooling of unequal real/imag sigma on the sparse path - #619
Merged
Merged
Conversation
…rse path Add pool_noise_map=False to sparse_terms_from_chunks, Interferometer.from_stream and Interferometer.apply_sparse_operator: when True, each noise map is pooled in quadrature, sigma^2 = (sigma_re^2 + sigma_im^2) / 2, before every sparse term, with one log line per call (info up to a 25 % median difference, warning above). apply_sparse_operator returns the dataset carrying the pooled noise_map; apply_sparse_operator_from_chunks refuses pool_noise_map=True. New helpers noise_map_pooled_from and noise_map_real_imag_asymmetry_from. Default behaviour unchanged. Docstrings state the equal-sigma assumption and the cos(a+b) caveat. Requested by @HRSAstro in https://github.com/orgs/PyAutoLabs/discussions/13. Closes #617. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
_streaming_inputs builds a TransformerNUFFT, so the no-JAX CI leg raised
ModuleNotFoundError. Guard with pytest.importorskip("nufftax") like the
sibling streaming tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
8 tasks done
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The sparse interferometer path refuses any noise map whose real and imaginary sigmas differ beyond
rtol=1e-5. Noise estimated by differencing adjacent visibilities typically shows 1-2 % real/imag scatter, so every such chunk raised. This adds an opt-inpool_noise_map=Truethat pools the two sigmas in quadrature,sigma^2 = (sigma_re^2 + sigma_im^2) / 2(which preserves the total variance), and logs how large the difference was. The default (False) is unchanged and still raises.Requested by @HRSAstro (pyuvimage) in https://github.com/orgs/PyAutoLabs/discussions/13 (item 2, "Unequal real and imaginary sigma"). Closes #617.
sparse_terms_from_chunks(..., pool_noise_map=True)pools each chunk's noise map before every term (W~, dirty image, dirty beam,sum_weights,data_term,noise_normalization). The asymmetry is accumulated over the whole stream and logged once per call: nothing when the sigmas are already equal (withinrtol=1e-5), INFO with the median and max difference when the median is at most 25 %, WARNING above 25 % (the difference may be real). The warning names the denseInversionInterferometerMappingpath as the exact one.Interferometer.from_stream(..., pool_noise_map=True)passes the option through.Interferometer.apply_sparse_operator(..., pool_noise_map=True)builds every term from the pooled noise map and returns the dataset carrying the poolednoise_map, so the dense residual/chi-squared maps agree with the cached sparsedata_term/noise_normalization.apply_sparse_operator_from_chunks(pool_noise_map=True)is refused with aDatasetException, asphase_centrealready is: it keeps this dataset's unpoolednoise_map. The message says to pool the dataset first withnoise_map_pooled_from, or to usefrom_stream(..., pool_noise_map=True).sum wbar cos(a-b) + dw cos(a+b), and W~ can only hold the Toeplitzcos(a-b)part;data_termis exactly phase-invariant;API Changes
Added only. Two new helpers and a new keyword argument
pool_noise_map=Falseon three existing entry points. A fourth entry point refuses that keyword when it isTrue. Default behaviour is unchanged bit for bit.See full details below.
Test Plan
sparse_terms_from_chunks(chunks, pool_noise_map=True)on chunks with 2 % asymmetry equals the default call on the same chunks pooled beforehand withnoise_map_pooled_from. The check is field by field and bit-identical. The default call on the unpooled chunks still raises. The test fails on unfixedmain(unknown kwarg).test_dataset.pytest__apply_sparse_operator__unequal_real_imag_noise__raises_exceptionandtest__apply_sparse_operator_from_chunks__unequal_real_imag_noise__raises; utiltest__check_noise_map_real_imag_equal__tiny_sigmas_use_a_relative_toleranceandtest__sparse_terms_from_chunks__unequal_real_imag_noise_in_a_later_chunk__raises.re^2 + im^2and has equal parts. Already-equal entries come back bit for bit, so pooling is idempotent. All three input forms are accepted. The asymmetry helper gives the expected values on a known 2 % map.apply_sparse_operator(pool_noise_map=True): the returnednoise_mapis pooled. The sparse log_evidence equals the mapping-path log_evidence on the pooled dataset torel=1e-8(measured ~1e-15). On equal sigmas the result is unchanged and nothing is logged.from_stream(pool_noise_map=True)equalsfrom_streamon pre-pooled chunks, bit for bit.InversionInterferometerMapping, and nothing at equality. Each case logs once per call across 3 chunks.apply_sparse_operator_from_chunks(pool_noise_map=True)raises.noise_normalizationshift is fixed and second order (8.2e-3 nats). The test asserts1e-3 < |Δ| < 1e-1.test_dataset.py+test_inversion_interferometer_util.py: 83 passed. Fulltest_autoarray: 1991 passed, 4 xfailed.Full API Changes (for automation & release notes)
Added
autoarray.inversion.inversion.interferometer.inversion_interferometer_util.noise_map_pooled_from(noise_map) -> np.ndarray: complexs + 1j*swiths = sqrt((re^2 + im^2) / 2). It accepts aVisibilitiesNoiseMap, a complex(K,)array or a real(K, 2)array, and returns exactly-equal entries unchanged.inversion_interferometer_util.noise_map_real_imag_asymmetry_from(noise_map) -> (median, max): the fractional|re - im| / max(re, im).inversion_interferometer_util.NOISE_MAP_POOLING_WARNING_FRACTION = 0.25.Changed Signature
inversion_interferometer_util.sparse_terms_from_chunks(..., pool_noise_map: bool = False)Interferometer.from_stream(..., pool_noise_map: bool = False)Interferometer.apply_sparse_operator(..., pool_noise_map: bool = False)Changed Behaviour
Interferometer.apply_sparse_operator_from_chunks(..., pool_noise_map=True)raisesDatasetException. Before this change it would have raisedTypeErrorfrom the accumulator.check_noise_map_real_imag_equalerror message now explains pooling (pool_noise_map=True,noise_map_pooled_from) and thecos(a+b)caveat. The raising condition is unchanged.Migration
None needed (opt-in). To use it:
aa.Interferometer.from_stream(chunks, mask)raises on 1-2 % sigma scatter.aa.Interferometer.from_stream(chunks, mask, pool_noise_map=True). Or pool upstream withnoise_map_pooled_from(noise_map).Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code