Skip to content

Commit 2a7b40a

Browse files
Jammy2211Jammy2211
authored andcommitted
Merge branch 'main' into feature/jax_wrapper
2 parents 7265c68 + 49c189b commit 2a7b40a

20 files changed

Lines changed: 462 additions & 106 deletions

File tree

autolens/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@
101101
from .point.dataset import PointDataset
102102
from .point.fit.dataset import FitPointDataset
103103
from .point.fit.fluxes import FitFluxes
104+
from .point.fit.times_delays import FitTimeDelays
104105
from .point.fit.positions.image.abstract import AbstractFitPositionsImagePair
105106
from .point.fit.positions.image.pair import FitPositionsImagePair
106107
from .point.fit.positions.image.pair_all import FitPositionsImagePairAll

autolens/aggregator/subplot.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ class SubplotTracer(Enum):
5050
image = (0, 0)
5151
source_image = (1, 0)
5252
source_plane_image = (2, 0)
53-
lens_light_image = (1, 0)
53+
lens_light_image = (0, 1)
5454
convergence = (1, 1)
55-
potential = (1, 2)
55+
potential = (2, 1)
5656
magnification = (0, 2)
5757
deflections_y = (1, 2)
5858
deflections_x = (2, 2)
@@ -69,14 +69,14 @@ class SubplotFit(Enum):
6969
data_source_scale = (1, 0)
7070
signal_to_noise_map = (2, 0)
7171
model_data = (3, 0)
72-
lens_light_model = (1, 0)
72+
lens_light_model = (0, 1)
7373
lens_light_subtracted_image = (1, 1)
74-
source_model_image = (1, 2)
75-
source_plane_image_zoom = (1, 3)
76-
normalized_residual_map = (2, 0)
77-
normalized_residual_map_one_sigma = (2, 1)
74+
source_model_image = (2, 1)
75+
source_plane_image_zoom = (3, 1)
76+
normalized_residual_map = (0, 2)
77+
normalized_residual_map_one_sigma = (1, 2)
7878
chi_squared_map = (2, 2)
79-
source_plane_image = (2, 3)
79+
source_plane_image = (3, 2)
8080

8181

8282
class SubplotFitLog10(Enum):
@@ -90,11 +90,11 @@ class SubplotFitLog10(Enum):
9090
data_source_scale = (1, 0)
9191
signal_to_noise_map = (2, 0)
9292
model_data = (3, 0)
93-
lens_light_model = (1, 0)
93+
lens_light_model = (0, 1)
9494
lens_light_subtracted_image = (1, 1)
95-
source_model_image = (1, 2)
96-
source_plane_image_zoom = (1, 3)
97-
normalized_residual_map = (2, 0)
98-
normalized_residual_map_one_sigma = (2, 1)
95+
source_model_image = (2, 1)
96+
source_plane_image_zoom = (3, 1)
97+
normalized_residual_map = (0, 2)
98+
normalized_residual_map_one_sigma = (1, 2)
9999
chi_squared_map = (2, 2)
100-
source_plane_image = (2, 3)
100+
source_plane_image = (3, 2)

autolens/analysis/analysis/lens.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,20 @@ def log_likelihood_positions_overwrite_from(
121121
else a None is returned to indicate there is no penalty.
122122
"""
123123
if self.positions_likelihood_list is not None:
124+
125+
log_likelihood_overwrite = None
126+
124127
try:
125-
log_likelihood = 0.0
126128
for positions_likelihood in self.positions_likelihood_list:
127-
log_likelihood += positions_likelihood.log_likelihood_function_positions_overwrite(
129+
log_likelihood_with_penalty = positions_likelihood.log_likelihood_function_positions_overwrite(
128130
instance=instance, analysis=self
129131
)
130-
return log_likelihood
132+
if log_likelihood_with_penalty is not None:
133+
try:
134+
log_likelihood_overwrite += log_likelihood_with_penalty
135+
except TypeError:
136+
log_likelihood_overwrite = log_likelihood_with_penalty
137+
138+
return log_likelihood_overwrite
131139
except (ValueError, np.linalg.LinAlgError) as e:
132140
raise exc.FitException from e

autolens/analysis/positions.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ def __init__(
8484

8585
self.log_likelihood_penalty_factor = log_likelihood_penalty_factor
8686

87-
def output_positions_info(self, output_path: str, tracer: Tracer):
87+
def output_positions_info(
88+
self, output_path: str, tracer: Tracer, overwrite_file: bool = True
89+
):
8890
"""
8991
Outputs a `positions.info` file which summarises the positions penalty term for a model fit, including:
9092
@@ -102,7 +104,10 @@ def output_positions_info(self, output_path: str, tracer: Tracer):
102104
-------
103105
104106
"""
105-
with open_(path.join(output_path, "positions.info"), "a+") as f:
107+
108+
flag = "w+" if overwrite_file else "a+"
109+
110+
with open_(path.join(output_path, "positions.info"), flag) as f:
106111

107112
positions_fit = SourceMaxSeparation(
108113
data=self.positions,
@@ -202,8 +207,6 @@ def log_likelihood_penalty_from(self, tracer: Tracer) -> Optional[float]:
202207
if not tracer.has(cls=ag.mp.MassProfile) or len(tracer.planes) == 1:
203208
return
204209

205-
log_likelihood_penalty = 0.0
206-
207210
positions_fit = SourceMaxSeparation(
208211
data=self.positions,
209212
noise_map=None,
@@ -213,12 +216,10 @@ def log_likelihood_penalty_from(self, tracer: Tracer) -> Optional[float]:
213216

214217
if not positions_fit.max_separation_within_threshold(self.threshold):
215218

216-
log_likelihood_penalty += self.log_likelihood_penalty_factor * (
219+
return self.log_likelihood_penalty_factor * (
217220
positions_fit.max_separation_of_plane_positions - self.threshold
218221
)
219222

220-
return log_likelihood_penalty
221-
222223
def log_likelihood_function_positions_overwrite(
223224
self, instance: af.ModelInstance, analysis: AnalysisDataset
224225
) -> Optional[float]:

autolens/imaging/model/visualizer.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,16 @@ def visualize(
9393

9494
if analysis.positions_likelihood_list is not None:
9595

96+
overwrite_file = True
97+
9698
for positions_likelihood in analysis.positions_likelihood_list:
9799

98100
positions_likelihood.output_positions_info(
99-
output_path=paths.output_path, tracer=fit.tracer
101+
output_path=paths.output_path, tracer=fit.tracer, overwrite_file=overwrite_file
100102
)
101103

104+
overwrite_file = False
105+
102106
if fit.inversion is not None:
103107
try:
104108
fit.inversion.reconstruction

autolens/interferometer/model/visualizer.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,18 @@ def visualize(
9191

9292
if analysis.positions_likelihood_list is not None:
9393

94+
overwrite_file = True
95+
9496
for positions_likelihood in analysis.positions_likelihood_list:
9597

9698
positions_likelihood.output_positions_info(
97-
output_path=paths.output_path, tracer=fit.tracer
99+
output_path=paths.output_path,
100+
tracer=fit.tracer,
101+
overwrite_file=overwrite_file,
98102
)
99103

104+
overwrite_file = False
105+
100106
if fit.inversion is not None:
101107
try:
102108
fit.inversion.reconstruction

autolens/lens/mock/mock_tracer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def __init__(
2222
magnification=None,
2323
einstein_radius=None,
2424
einstein_mass=None,
25+
time_delays=None,
2526
):
2627
super().__init__(image_plane_mesh_grid_pg_list=image_plane_mesh_grid_pg_list)
2728

@@ -33,6 +34,7 @@ def __init__(
3334
self.magnification = magnification
3435
self.einstein_radius = einstein_radius
3536
self.einstein_mass = einstein_mass
37+
self.time_delays = time_delays
3638

3739
@property
3840
def planes(self):
@@ -58,3 +60,6 @@ def einstein_radius_from(self, grid):
5860

5961
def einstein_mass_angular_from(self, grid):
6062
return self.einstein_mass
63+
64+
def time_delays_from(self, grid):
65+
return self.time_delays

autolens/lens/tracer.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,29 @@ def potential_2d_from(self, grid: aa.type.Grid2DLike) -> aa.Array2D:
759759
"""
760760
return sum([galaxy.potential_2d_from(grid=grid) for galaxy in self.galaxies])
761761

762+
@aa.grid_dec.to_array
763+
def time_delays_from(self, grid: aa.type.Grid2DLike) -> aa.Array2D:
764+
"""
765+
Returns the gravitational lensing time delay in days, for a grid of 2D (y, x) coordinates.
766+
767+
This function calculates the time delay at each image-plane position due to both geometric and gravitational
768+
(Shapiro) effects, as described by the Fermat potential, which are computed using the deflection angles of the
769+
galaxies in the lens system.
770+
771+
Time dleays are computed from a reference point, which is the delay one would compute without a mass model.
772+
This means a time delay could be negative, which means the light travel time is faster when lensing is
773+
accounted for. When performing fitting, all time-delays are subtracted by the time-delay of the
774+
shortest time-delay, such that its value is zero and all other time-delays are positive.
775+
776+
A full description of the calculation is given in the `autolens.lens.tracer.tracer_util.time_delays_from`
777+
function, which performs the calculation and has full latex documentation of the equations used.
778+
"""
779+
return tracer_util.time_delays_from(
780+
galaxies=ag.Galaxies(self.galaxies_ascending_redshift),
781+
grid=grid,
782+
cosmology=self.cosmology,
783+
)
784+
762785
def has(self, cls: Type) -> bool:
763786
"""
764787
Returns a bool specifying whether this tracer has a galaxy with a certain class type.

autolens/lens/tracer_util.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import autoarray as aa
55
import autogalaxy as ag
66

7+
from autolens import exc
8+
79

810
def plane_redshifts_from(galaxies: List[ag.Galaxy]) -> List[float]:
911
"""
@@ -249,6 +251,92 @@ def grid_2d_at_redshift_from(
249251
return traced_grid_list[plane_index_insert]
250252

251253

254+
def time_delays_from(
255+
galaxies: List[ag.Galaxy],
256+
grid: aa.type.Grid2DLike,
257+
cosmology: ag.cosmo.LensingCosmology = ag.cosmo.Planck15(),
258+
) -> aa.type.Grid2DLike:
259+
"""
260+
Returns the gravitational lensing time delay in days for a grid of 2D (y, x) coordinates.
261+
262+
This function calculates the time delay at each image-plane position due to both geometric and gravitational
263+
(Shapiro) effects, as described by the Fermat potential, which are computed using the deflection angles of the
264+
galaxies in the lens system.
265+
266+
It requires a two-plane system (lens and source), and does not currently support multi-plane time delay
267+
calculations involving more than two planes, but it could be extended to do so in the future.
268+
269+
The time delay is computed as:
270+
271+
.. math::
272+
\Delta t(\boldsymbol{\theta}) = \frac{D_{\Delta t}}{c} \, \phi(\boldsymbol{\theta})
273+
274+
where:
275+
276+
- \( \boldsymbol{\theta} \): image-plane coordinate
277+
- \( \phi(\boldsymbol{\theta}) \): Fermat potential at each coordinate
278+
- \( c \): speed of light
279+
- \( D_{\Delta t} \): time-delay distance
280+
281+
The time-delay distance is given by:
282+
283+
.. math::
284+
D_{\Delta t} = (1 + z_l) \frac{D_d D_s}{D_{ds}}
285+
286+
with \( D_d, D_s, D_{ds} \) the angular diameter distances to the lens, to the source, and from lens to source.
287+
288+
The time delay is computed using the Fermat potential,
289+
290+
An input `AstroPy` cosmology object can change the cosmological model, which is used to compute the scaling
291+
factors between planes (which are derived from their redshifts and angular diameter distances). It is these
292+
scaling factors that account for multi-plane ray tracing effects.
293+
294+
Parameters
295+
----------
296+
galaxies
297+
List of galaxies whose mass profiles define the lens and source planes. Must contain exactly two redshifts.
298+
grid
299+
The 2D (y, x) image-plane coordinates where the time delay is computed.
300+
cosmology
301+
The cosmological model used to calculate angular diameter distances. Defaults to Planck15.
302+
303+
Returns
304+
-------
305+
The time delay at each (y, x) coordinate in the input grid, in units of days.
306+
"""
307+
plane_redshifts = plane_redshifts_from(galaxies=galaxies)
308+
309+
if len(plane_redshifts) != 2:
310+
raise exc.RayTracingException(
311+
"The time delay calculation requires exactly two planes, but the input galaxies have "
312+
f"{len(plane_redshifts)} planes with redshifts {plane_redshifts}."
313+
)
314+
315+
# Constants
316+
mpc_in_m = 3.08567758e22 # Mpc in meters
317+
arcsec_to_rad = np.deg2rad(1.0 / 3600.0) # arcsec to radians
318+
seconds_per_day = 86400
319+
c = 299792458 # speed of light in m/s
320+
321+
factor = arcsec_to_rad**2 / seconds_per_day
322+
323+
# Angular diameter distances
324+
Dd = cosmology.angular_diameter_distance(plane_redshifts[0]).value # [Mpc]
325+
Ds = cosmology.angular_diameter_distance(plane_redshifts[1]).value # [Mpc]
326+
Dds = cosmology.angular_diameter_distance_z1z2(
327+
z1=plane_redshifts[0], z2=plane_redshifts[1]
328+
).value # [Mpc]
329+
330+
# Time-delay distance in meters
331+
D_dt = (1 + plane_redshifts[0]) * Dd * Ds / Dds * mpc_in_m
332+
333+
# Fermat potential
334+
fermat_potential = galaxies.fermat_potential_from(grid=grid)
335+
336+
# Final time delay in days
337+
return D_dt / c * fermat_potential * factor
338+
339+
252340
def ordered_plane_redshifts_with_slicing_from(
253341
lens_redshifts, planes_between_lenses, source_plane_redshift
254342
):

0 commit comments

Comments
 (0)