Skip to content

Add noise-aware ARTS2D objective - #148

Merged
joglekara merged 2 commits into
mainfrom
codex/issue-139-arts2d-objective
Sep 1, 2026
Merged

Add noise-aware ARTS2D objective#148
joglekara merged 2 commits into
mainfrom
codex/issue-139-arts2d-objective

Conversation

@joglekara

Copy link
Copy Markdown
Member

Closes #139

Summary

  • replace data-dependent angular peak normalization with profiled global, per-row, or per-wing gains and calibration priors
  • whiten angular residuals using Poisson/read/background, measured-variance, or constant noise models, with masks and robust contamination options
  • regularize the physical EDF with radial/angular smoothness, KL, density, temperature, and momentum terms
  • persist objective-term and gain-uncertainty diagnostics, reject unsupported peak-normalized angular IRFs, and document the new configuration

Verification

  • pytest -q tests/test_inverse/test_angular_objective.py tests/test_inverse/test_loops.py tests/test_forward/test_irf_area.py tests/test_utils/test_manifest.py — 63 passed
  • angular forward/inverse selection — 1 passed, 4 skipped (GPU-only)
  • python3 -m compileall -q tsadar tests/test_inverse/test_angular_objective.py
  • configuration YAML parse and git diff --check

Environment note

The complete suite was not a clean local gate because this host lacks the optional HDF4 pyhdf dependency. The issue-specific and adjacent tests above pass.

@ergodic-ludwig ergodic-ludwig Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review summary

Changes requested: two correctness blockers and one identifiability issue.

  1. Non-finite detector samples are marked invalid but still poison JAX gradients because masking happens after NaN arithmetic.
  2. Clipping a smoothness-coupled unconstrained gain solution is not a valid bound-constrained profile.
  3. The default free amp1 plus unconstrained per-row gains creates a flat, unidentifiable scale direction.

The focused suite passes locally (63 passed), and CPU CI is green, but these edge cases are not covered by the current tests.


valid_count = jnp.sum(valid)
n_valid = jnp.maximum(valid_count, 1.0)
deviance = jnp.where(valid, self._robust_deviance(whitened), 0.0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blocking — masked non-finite samples still produce non-finite gradients. valid correctly marks a NaN datum false, but the code first computes variance, whitened, and deviance with that NaN and only masks afterward with jnp.where. JAX's reverse pass propagates the NaN from the inactive branch. I reproduced this with e_data=[[1, nan, 2, 3]], gain.mode=none, and poisson_read: the scalar loss is finite (0.4444) but jax.grad(loss)(theory) contains nan at the masked pixel. The optimizer then fails _require_finite(grad), contrary to the documented support for non-finite samples. Please sanitize masked/non-finite data, signal, and variance before divisions/deviance (and add a gradient regression test).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in bef3b30. Non-finite detector operands are now sanitized before gain products, division, and deviance evaluation. A JITed value-and-gradient regression with a NaN datum verifies a finite full gradient and a zero masked component.

Comment thread tsadar/inverse/loss_function.py Outdated
)
target = rhs + prior_precision * float(gain_options["prior_mean"])
gains = jnp.linalg.solve(system, target)
gains = jnp.maximum(gains, float(gain_options["minimum"]))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blocking — clipping the unconstrained solution does not profile the constrained gain objective when smoothness couples gains. With information=[1,1], rhs=[-3,1], smoothness=1, prior=0, and minimum=0, the linear solve gives negative coupled gains and this line returns [0,0]. But the feasible point [0,0.5] has a lower quadratic objective (-0.5 versus 0), so the returned gains are not the constrained minimizer. This can occur for background-subtracted noisy rows and makes both the objective and reported uncertainty inconsistent with the claimed profiled likelihood. Please use a bound-constrained quadratic solve/active-set treatment, or parameterize gains without claiming analytic profiling; add a coupled-row regression test.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in bef3b30. Post-solve clipping is replaced by a differentiable primal active-set solve for the lower-bounded quadratic. Conditional covariance uses the free block, and bound-active gains report zero two-sided standard error. The review counterexample now returns [0, 0.5], with regression coverage.

Comment thread configs/arts-2d/defaults.yaml Outdated
gain:
mode: per_row
smoothness: 0.01
prior_strength: 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Identifiability warning: these defaults profile an unconstrained per-row gain (prior_strength: 0) while parameters.general.amp1 remains active below. Multiplying the forward spectrum by amp1 and dividing every profiled gain by the same factor leaves the objective invariant (I verified gradients are zero across several scales). That creates a flat optimizer direction and means the configured active amplitude cannot be inferred. Please either disable amp1 for nuisance-gain modes, require a calibration prior/normalization constraint, or reject the unidentifiable combination during validation.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in bef3b30. The default gain mean prior is now positive (0.01) to anchor the scale. Validation rejects active fitted amp1/amp2 parameters when profiled gains have zero prior strength, and the constraint is documented and regression-tested.

@ergodic-ludwig ergodic-ludwig Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the complete diff at bef3b30. The updated implementation now sanitizes non-finite detector operands before differentiable arithmetic, profiles smoothness-coupled lower-bounded gains with an active-set solve, and anchors or rejects otherwise unidentifiable amplitude/gain configurations. The focused regression coverage matches these behaviors; I found no remaining material correctness or reproducibility defects.

@joglekara
joglekara merged commit f8682bf into main Sep 1, 2026
2 checks passed
@joglekara
joglekara deleted the codex/issue-139-arts2d-objective branch September 1, 2026 01:32
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.

ARTS2D: use a noise-aware, regularized objective with explicit gain nuisances

1 participant