Add noise-aware ARTS2D objective - #148
Conversation
There was a problem hiding this comment.
Review summary
Changes requested: two correctness blockers and one identifiability issue.
- Non-finite detector samples are marked invalid but still poison JAX gradients because masking happens after NaN arithmetic.
- Clipping a smoothness-coupled unconstrained gain solution is not a valid bound-constrained profile.
- The default free
amp1plus 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) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
| ) | ||
| target = rhs + prior_precision * float(gain_options["prior_mean"]) | ||
| gains = jnp.linalg.solve(system, target) | ||
| gains = jnp.maximum(gains, float(gain_options["minimum"])) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| gain: | ||
| mode: per_row | ||
| smoothness: 0.01 | ||
| prior_strength: 0.0 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Closes #139
Summary
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 passedpython3 -m compileall -q tsadar tests/test_inverse/test_angular_objective.pygit diff --checkEnvironment note
The complete suite was not a clean local gate because this host lacks the optional HDF4
pyhdfdependency. The issue-specific and adjacent tests above pass.