Skip to content

pol gradient fixes to dev#306

Merged
achael merged 13 commits into
devfrom
fix/pol-grad-mask
Jun 16, 2026
Merged

pol gradient fixes to dev#306
achael merged 13 commits into
devfrom
fix/pol-grad-mask

Conversation

@achael

@achael achael commented Jun 16, 2026

Copy link
Copy Markdown
Owner

What & why

Stops overloading pol_solve, which conflated two distinct masks: (1) which
solver DOFs are optimized, and (2) which physical gradient slots (I, rho, phi,
psi) the chisq/reg kernels must fill. These differ wherever a value-transform's
Jacobian is non-diagonal (mcv, vcv): one solved pol DOF drives BOTH rho and psi.

Introduces physical_grad_slots(pol_solve, transforms) as the single source of
truth, wired into both the chisq and regularizer gradient dicts, and reverts the
kernels to clean diagonal gating.

Correctness fixes

  • mcv/vcv cross-coupling, centralized in physical_grad_slots. This fixes
    IP imaging when V!=0: mcv_grad's m' gradient needs the psi
    physical gradient, which the old mask zeroed.
  • reggrad_ptv first-row/col boundary masking + epsilon_tv in
    reg_ptv/reggrad_ptv and reg_vtv/reggrad_vtv (default 0, byte-identical).

Test coverage

  • physical_grad_slots unit tests; pol chisq FD all-slots (pvis/m/vvis x
    direct/nfft); pol-reg FD driven with all four slots; boundary FD regressions
    for ptv/vtv/tv; one parametrized objective-FD over {IP/mcv, IV/vcv,
    IQUV/polcv} x {direct,nfft} that samples the polarization DOF block (the
    existing global-sampling tests missed the m'-coupling bug).
  • FD fixtures switched to an asymmetric image (offset double-Gaussian) with
    spatially-varying pol (add_random_pol, ccorr>0) so EVPA/vfrac/rho/psi all
    vary -- surfaces boundary/axis bugs a centered constant-fraction Gaussian hides.

Cosmetic

  • Comment/docstring cleanup pass on imager_utils.py and pol_imager_utils.py
    (no behavior change), ruff-clean.

Supersedes part of #295

The reggrad_ptv boundary + epsilon_tv fixes and the reg-grad cross-coupling
land here; #295 should drop those and stay jax-agnostic plumbing (Option B).

Testing

Full suite: 1776 passed, 2 skipped, 1 xfailed.

achael added 12 commits June 15, 2026 20:16
Maps the Stokes DOF mask to the physical gradout slots the chisq/reg
kernels must fill, centralizing the mcv/vcv cross-coupling that mirrors
transform_gradients' Jacobian sparsity. Not yet wired in. + unit tests.
Feed the cross-coupling-aware mask to the pol gradient kernels in both
compute_chisqgrad_dict and compute_reggrad_dict. Behavior-identical for
now (kernels still carry the or-patches). Guard physical_grad_slots
against sub-4-wide single-pol masks (Stokes-I carries 'mcv' inertly).
+ regression test.
The mcv/vcv cross-coupling now lives in physical_grad_slots, so drop the
'or pol_solve[3]' patches (#296) in chisqgrad_vvis / chisqgrad_vvis_nfft;
each physical slot keys on its own bit again. Note in each pol chisqgrad
docstring that pol_solve flags required physical gradients, not DOFs.
Zero the back-neighbor (m2/m3) terms on the first row/column in
reggrad_ptv slots 0/1/3 (the back-neighbor is the zero pad), matching
reggrad_vtv/reggrad_tv. Pre-fix the whole first row+col of those slots
was wrong (corner ~4x off vs FD). Add epsilon_tv to reg_ptv/reggrad_ptv
denominators (default 0, byte-identical) for #295 parity. Note pol_solve
= physical-gradient slots in the 8 pol reggrad docstrings.

Add full-grid boundary FD regression tests for ptv, vtv, and Stokes-I tv.
polchisqgrad is a legacy shim (parity tests only); document that its
pol_solve is a physical-gradient mask, not a raw DOF mask.
_pol_solve_for now returns [1,1,1,1] so the previously-blind cross-
coupling slots are FD-checked: reggrad_ptv psi (3), reggrad_vflux/l1v/
l2v/vtv rho (1), and slot 0 for every pol reg. Proves the reg-grad
slots are individually correct against finite differences.
New pol coverage in its final-home file: TestPolChisqGradFD checks
chisqgrad_p/m/vvis against finite differences of the chisq value in all
four physical slots (pol_solve=[1,1,1,1]) for direct+nfft, asserting
vvis slot 2 (EVPA) is identically zero. TestPolChisq{,Grad}Consistency
check direct-vs-nfft agreement. Closes the m / p-slot-3 blind spots.
TestObjectiveGradPolarimetricFD checks objgrad vs FD for IP/IV/IQUV x
{direct,nfft}, with each case bundling its pol data terms + a pol reg, so
both the chisq and reg gradient paths through physical_grad_slots are
exercised. Samples the pol DOF block (past the Stokes-I block), where the
mcv/vcv cross-coupling lives -- the existing global-sampling FD tests
missed it (the dropped IP slot-3 term is ~4% off FD at V=0.02*I, ~430% at
V=0.2*I). Comments out the now-subsumed test_fd_matches_analytic_polarimetric
(backend) and test_iv_gradient_matches_finite_difference (e2e).
Add make_asym_image (broad offset/elongated/rotated double-Gaussian) and
switch the Stokes-I FD fixtures (chisq_setup, reg_setup, mfreg_setup,
grad_setup) to it. Breaking the reflection/rotation/x<->y symmetry of the
centered Gaussian surfaces boundary/axis-ordering bugs a symmetric image
hides. Blobs kept broad (grid-filling, no dead pixels) so the |.|-kink TV
gradients stay FD-well-conditioned at epsilon_tv=0; all tolerances unchanged.
chisq_setup_pol and a new asym_pol_setup build on make_asym_image and use
add_random_pol (ccorr>0) so EVPA, vfrac, rho, and psi all vary spatially
instead of a constant pol fraction. polreg_setup switches its Stokes I to
the asymmetric image (keeping the per-pixel pol jitter that keeps TV
denominators non-degenerate). TestObjectiveGradPolarimetricFD now uses the
structured-pol obs.

Widen the pol chisq FD check to a median+max split (median 1e-5, max 1e-3):
the structured-pol imcur has sharper local curvature, so 2nd-order FD
truncation pushes a few small-gradient pixels to ~2.6e-4 -- well below any
real pol-gradient bug (%-level), which the tight median still catches.
Manual review pass: per-slot dR/dX labels, docstrings on the reg kernels,
a module-level CONVENTIONS block (imarr = [I, rho, phi=2chi, psi]), and
removal of stale TODOs. Two behavior touches, both byte-identical at the
defaults:
- reg_vtv / reggrad_vtv now honor epsilon_tv (kwargs, default 0) like the
  ptv pair, instead of the value ignoring it while the grad pinned it to 0.
- reggrad_ptv masks the chi-slot back-neighbor terms (c2/c3) too, for
  uniformity (they already self-zero at the pad).
Plus an mcv_r exception-message fix and ruff-clean whitespace.
Manual review pass: docstrings on the Stokes-I reg kernels, per-block
comments, 'fourier/transform matrices' labels on the diag Amatrices
unpacking, and removal of dead commented-out systematic-noise code in the
bispectrum data functions (the intent is now documented in
apply_systematic_noise_snrcut). Purely cosmetic; ruff-clean.
@achael achael requested a review from rohandahale June 16, 2026 00:35
@achael achael marked this pull request as ready for review June 16, 2026 00:37
@codecov

codecov Bot commented Jun 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.90244% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 47.50%. Comparing base (db685e6) to head (7c06d88).

Files with missing lines Patch % Lines
ehtim/imaging/pol_imager_utils.py 92.72% 1 Missing and 3 partials ⚠️
ehtim/imaging/imager_backend.py 92.85% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##              dev     #306      +/-   ##
==========================================
+ Coverage   47.42%   47.50%   +0.07%     
==========================================
  Files          54       54              
  Lines       26899    26928      +29     
  Branches     4588     4591       +3     
==========================================
+ Hits        12758    12793      +35     
+ Misses      12664    12648      -16     
- Partials     1477     1487      +10     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@achael achael self-assigned this Jun 16, 2026
@achael achael added the bug label Jun 16, 2026
@achael achael changed the title Separate physical-gradient mask from solve mask (pol gradient correctness) + asymmetric FD coverage pol gradient fixes to dev Jun 16, 2026

@rohandahale rohandahale left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the logic and ran the gradient suites locally, looks great! Good to merge.

@achael achael merged commit 9d29103 into dev Jun 16, 2026
6 checks passed
This was referenced Jun 16, 2026
@achael achael deleted the fix/pol-grad-mask branch June 17, 2026 09:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants