Skip to content

Commit 231a7bc

Browse files
authored
Merge pull request #626 from PyAutoLabs/feature/potential-correction-interferometer
fix: Marquardt-scaled LM damping + zero-fill correction profiles
2 parents 9b3683e + ad97e5f commit 231a7bc

4 files changed

Lines changed: 20 additions & 5 deletions

File tree

autolens/potential_correction/dense_util.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -376,14 +376,20 @@ def lm_hessian_and_gradient_from(
376376

377377
def solve_lm_step_from(H, minus_gradient, mu, constraint_matrix=None, x=None, xp=np):
378378
"""
379-
The damped LM step delta_x solving (H + mu I) dx = -g, or — when a
380-
``constraint_matrix`` C is given — the equality-constrained step via the
381-
KKT system enforcing C (x + dx) = 0.
379+
The damped LM step delta_x solving (H + mu D) dx = -g with Marquardt
380+
scaling D = diag(diag(H)) (clipped below at the mean diagonal times
381+
1e-12 so zero diagonal entries stay damped) — scale-invariant damping,
382+
required when H's magnitude varies over many orders between datasets
383+
(e.g. visibility-weighted interferometer curvatures ~1e11 vs imaging
384+
~1e4). When a ``constraint_matrix`` C is given, the equality-constrained
385+
step solves the KKT system enforcing C (x + dx) = 0.
382386
"""
383387
H_d = as_dense(H, xp=xp)
384388
g = xp.asarray(minus_gradient)
385389
n_x = H_d.shape[0]
386-
H_lm = H_d + mu * xp.eye(n_x, dtype=H_d.dtype)
390+
diag = xp.diag(H_d)
391+
diag = xp.clip(diag, 1e-12 * xp.mean(xp.abs(diag)), None)
392+
H_lm = H_d + mu * xp.diag(diag)
387393

388394
if constraint_matrix is None:
389395
return xp.linalg.solve(H_lm, g)

autolens/potential_correction/iterative.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,14 @@ def _updated_lens_galaxies_from_dpsi(self, dpsi_vec: np.ndarray):
212212
mask_dpsi_aa = self.pair_dpsi_data_obj.mask_dpsi_aa
213213
grid_dpsi = aa.Grid2D.from_mask(mask=mask_dpsi_aa)
214214

215+
# zero-fill extrapolation: the correction vanishes outside its mesh
216+
# (nearest extrapolation would smear constant deflections across the
217+
# full re-trace grid when the dpsi mesh is a sub-region of it)
215218
pix_mass_profile = InputPotential(
216219
lensing_potential=dpsi_vec,
217220
image_plane_grid=np.asarray(grid_dpsi),
218221
mask=mask_dpsi_aa,
222+
extrapolate="zero",
219223
)
220224

221225
lens_macro = self.lens_start

autolens/potential_correction/iterative_interferometer.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,10 +230,14 @@ def _updated_lens_galaxies_from_dpsi(self, dpsi_vec: np.ndarray):
230230
mask_dpsi_aa = self.pair_dpsi_data_obj.mask_dpsi_aa
231231
grid_dpsi = aa.Grid2D.from_mask(mask=mask_dpsi_aa)
232232

233+
# zero-fill extrapolation: the correction vanishes outside its mesh
234+
# (nearest extrapolation would smear constant deflections across the
235+
# full re-trace grid when the dpsi mesh is a sub-region of it)
233236
pix_mass_profile = InputPotential(
234237
lensing_potential=dpsi_vec,
235238
image_plane_grid=np.asarray(grid_dpsi),
236239
mask=mask_dpsi_aa,
240+
extrapolate="zero",
237241
)
238242

239243
lens_macro = self.lens_start

test_autolens/potential_correction/test_dense_util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ def test__solve_lm_step_from__unconstrained_and_constrained():
219219

220220
mu = 0.7
221221
step = dense_util.solve_lm_step_from(H, minus_gradient, mu)
222-
assert (H + mu * np.eye(H.shape[0])) @ step == pytest.approx(
222+
diag = np.clip(np.diag(H), 1e-12 * np.mean(np.abs(np.diag(H))), None)
223+
assert (H + mu * np.diag(diag)) @ step == pytest.approx(
223224
minus_gradient, rel=1.0e-8
224225
)
225226

0 commit comments

Comments
 (0)