1
+ import logging
1
2
import os
2
3
import numpy as np
3
4
from typing import Optional , Union
15
16
from autolens .lens .tracer import Tracer
16
17
from autolens .point .solver import PointSolver
17
18
19
+ logger = logging .getLogger (__name__ )
20
+
18
21
19
22
class Result (AgResultDataset ):
20
23
@property
@@ -101,8 +104,76 @@ def image_plane_multiple_image_positions(self) -> aa.Grid2DIrregular:
101
104
source_plane_coordinate = self .source_plane_centre .in_list [0 ],
102
105
)
103
106
107
+ if multiple_images .shape [0 ] == 1 :
108
+ return self .image_plane_multiple_image_positions_for_single_image_from ()
109
+
104
110
return aa .Grid2DIrregular (values = multiple_images )
105
111
112
+ def image_plane_multiple_image_positions_for_single_image_from (
113
+ self , increments : int = 20
114
+ ) -> aa .Grid2DIrregular :
115
+ """
116
+ If the standard point solver only locates one multiple image, finds one or more additional images, which are
117
+ not technically multiple image in the point source regime, but are close enough to it they can be used
118
+ in a position threshold likelihood.
119
+
120
+ This is performed by incrementally moving the source-plane centre's coordinates towards the centre of the
121
+ source-plane at (0.0", 0.0"). This ensures that the centre will eventually go inside the caustic, where
122
+ multiple images are formed.
123
+
124
+ To move the source-plane centre, the original source-plane centre is multiplied by a factor that decreases
125
+ from 1.0 to 0.0 in increments of 1/increments. For example, if the source-plane centre is (1.0", -0.5") and
126
+ the `factor` is 0.5, the input source-plane centre is (0.5", -0.25").
127
+
128
+ The multiple images are always computed for the same mass model, thus they will always be valid multiple images
129
+ for the model being fitted, but as the factor decrease the multiple images may move furhter from their observed
130
+ positions.
131
+
132
+ Parameters
133
+ ----------
134
+ increments
135
+ The number of increments the source-plane centre is moved to compute multiple images.
136
+ """
137
+
138
+ logger .info (
139
+ """
140
+ Could not find multiple images for maximum likelihood lens model.
141
+
142
+ Incrementally moving source centre inwards towards centre of source-plane until caustic crossing occurs
143
+ and multiple images are formed.
144
+ """
145
+ )
146
+
147
+ grid = self .analysis .dataset .mask .derive_grid .all_false
148
+
149
+ centre = self .source_plane_centre .in_list [0 ]
150
+
151
+ solver = PointSolver .for_grid (
152
+ grid = grid ,
153
+ pixel_scale_precision = 0.001 ,
154
+ )
155
+
156
+ for i in range (1 , increments ):
157
+ factor = 1.0 - (1.0 * (i / increments ))
158
+
159
+ multiple_images = solver .solve (
160
+ tracer = self .max_log_likelihood_tracer ,
161
+ source_plane_coordinate = (centre [0 ] * factor , centre [1 ] * factor ),
162
+ )
163
+
164
+ if multiple_images .shape [0 ] > 1 :
165
+ return aa .Grid2DIrregular (values = multiple_images )
166
+
167
+ logger .info (
168
+ """
169
+ Could not find multiple images for maximum likelihood lens model, even after incrementally moving the source
170
+ centre inwards to the centre of the source-plane.
171
+
172
+ Set the multiple image postiions to two images at (1.0", 1.0") so code continues to run.
173
+ """
174
+ )
175
+ return aa .Grid2DIrregular (values = [(1.0 , 1.0 ), (1.0 , 1.0 )])
176
+
106
177
def positions_threshold_from (
107
178
self ,
108
179
factor = 1.0 ,
@@ -168,7 +239,47 @@ def positions_likelihood_from(
168
239
minimum_threshold = None ,
169
240
use_resample = False ,
170
241
positions : Optional [aa .Grid2DIrregular ] = None ,
242
+ mass_centre_radial_distance_min : float = None ,
171
243
) -> Union [PositionsLHPenalty , PositionsLHResample ]:
244
+ """
245
+ Returns a `PositionsLH` object from the result of a lens model-fit, where the maximum log likelihood mass
246
+ and source models are used to determine the multiple image positions in the image-plane and source-plane
247
+ and ray-trace them to the source-plane to determine the threshold.
248
+
249
+ In chained fits, for example the SLaM pipelines, this means that a simple initial fit (e.g. SIE mass model,
250
+ parametric source) can be used to determine the multiple image positions and threshold for a more complex
251
+ subsequent fit (e.g. power-law mass model, pixelized source).
252
+
253
+ The mass model central image is removed from the solution, as this is rarely physically observed and therefore
254
+ should not be included in the likelihood penalty or resampling. It is removed by setting a positive
255
+ magnification threshold in the `PointSolver`. For strange lens models the central image may still be
256
+ solved for, in which case the `mass_centre_radial_distance_min` parameter can be used to remove it.
257
+
258
+ Parameters
259
+ ----------
260
+ factor
261
+ The value the computed threshold is multiplied by to make the position threshold larger or smaller than the
262
+ maximum log likelihood model's threshold.
263
+ minimum_threshold
264
+ The output threshold is rounded up to this value if it is below it, to avoid extremely small threshold
265
+ values.
266
+ use_resample
267
+ If `False` the `PositionsLH` object is created using the `PositionsLHPenalty` class, which uses the
268
+ threshold to apply a penalty term to the likelihood. If `True` the `PositionsLH` object is created using
269
+ the `PositionsLHResample` class, which resamples the positions to the threshold.
270
+ positions
271
+ If input, these positions are used instead of the computed multiple image positions from the lens mass
272
+ model.
273
+ mass_centre_radial_distance_min
274
+ The minimum radial distance from the mass model centre that a multiple image position must be to be
275
+ included in the likelihood penalty or resampling. If `None` all positions are used. This is an additional
276
+ method to remove central images that may make it through the point solver's magnification threshold.
277
+
278
+ Returns
279
+ -------
280
+ The `PositionsLH` object used to apply a likelihood penalty or resample the positions.
281
+ """
282
+
172
283
if os .environ .get ("PYAUTOFIT_TEST_MODE" ) == "1" :
173
284
return None
174
285
@@ -177,6 +288,18 @@ def positions_likelihood_from(
177
288
if positions is None
178
289
else positions
179
290
)
291
+
292
+ if mass_centre_radial_distance_min is not None :
293
+ mass_centre = self .max_log_likelihood_tracer .extract_attribute (
294
+ cls = ag .mp .MassProfile , attr_name = "centre"
295
+ )
296
+
297
+ distances = positions .distances_to_coordinate_from (
298
+ coordinate = mass_centre [0 ]
299
+ )
300
+
301
+ positions = positions [distances > mass_centre_radial_distance_min ]
302
+
180
303
threshold = self .positions_threshold_from (
181
304
factor = factor , minimum_threshold = minimum_threshold , positions = positions
182
305
)
0 commit comments