|
15 | 15 |
|
16 | 16 | These results feed directly into downstream pipeline stages and post-processing scripts. |
17 | 17 | """ |
| 18 | +import json |
18 | 19 | import logging |
19 | 20 | import os |
20 | 21 | import numpy as np |
@@ -267,6 +268,59 @@ def positions_threshold_from( |
267 | 268 |
|
268 | 269 | return threshold |
269 | 270 |
|
| 271 | + def _cached_multiple_image_positions_from( |
| 272 | + self, plane_redshift: Optional[float] = None |
| 273 | + ) -> aa.Grid2DIrregular: |
| 274 | + """ |
| 275 | + The multiple image positions of the maximum log likelihood lens model, loaded |
| 276 | + from this result's own ``files/`` folder when previously solved, else solved |
| 277 | + via the point solver and persisted there. |
| 278 | +
|
| 279 | + Solving the multiple image positions runs a point-solver grid search over the |
| 280 | + maximum log likelihood tracer, which on a resumed pipeline (e.g. SLaM) pays a |
| 281 | + fresh JIT compile and dominates resume overhead (autolens_profiling#70) — the |
| 282 | + solved positions themselves are a pure product of the completed fit, so they |
| 283 | + are cached as ``files/multiple_image_positions[_plane_<z>].json``. Staleness |
| 284 | + is structurally guarded: a changed model or search produces a new search |
| 285 | + identifier and a fresh output directory with no cache file. Results with no |
| 286 | + on-disk output (e.g. ``NullPaths``) always solve. |
| 287 | + """ |
| 288 | + from pathlib import Path |
| 289 | + |
| 290 | + name = "multiple_image_positions" |
| 291 | + if plane_redshift is not None: |
| 292 | + name += f"_plane_{str(plane_redshift).replace('.', '_')}" |
| 293 | + |
| 294 | + files_path = getattr(getattr(self, "paths", None), "_files_path", None) |
| 295 | + cache_path = ( |
| 296 | + Path(files_path) / f"{name}.json" |
| 297 | + if files_path is not None and Path(files_path).is_dir() |
| 298 | + else None |
| 299 | + ) |
| 300 | + |
| 301 | + if cache_path is not None and cache_path.exists(): |
| 302 | + with open(cache_path) as f: |
| 303 | + return aa.Grid2DIrregular(values=[tuple(p) for p in json.load(f)]) |
| 304 | + |
| 305 | + positions = self.image_plane_multiple_image_positions( |
| 306 | + plane_redshift=plane_redshift |
| 307 | + ) |
| 308 | + |
| 309 | + if cache_path is not None: |
| 310 | + with open(cache_path, "w") as f: |
| 311 | + json.dump(np.asarray(positions.array).tolist(), f) |
| 312 | + |
| 313 | + # Preserve the cache in the search's zip — a resumed search's |
| 314 | + # paths.restore() wipes the output dir and re-extracts the zip, |
| 315 | + # destroying any file written only to files/ after completion. |
| 316 | + from autogalaxy.analysis.adapt_images.adapt_images import ( |
| 317 | + _append_to_search_zip, |
| 318 | + ) |
| 319 | + |
| 320 | + _append_to_search_zip(self.paths, cache_path) |
| 321 | + |
| 322 | + return positions |
| 323 | + |
270 | 324 | def positions_likelihood_from( |
271 | 325 | self, |
272 | 326 | factor=1.0, |
@@ -355,11 +409,10 @@ def positions_likelihood_from( |
355 | 409 | ) |
356 | 410 | return |
357 | 411 |
|
358 | | - positions = ( |
359 | | - self.image_plane_multiple_image_positions(plane_redshift=plane_redshift) |
360 | | - if positions is None |
361 | | - else positions |
362 | | - ) |
| 412 | + if positions is None: |
| 413 | + positions = self._cached_multiple_image_positions_from( |
| 414 | + plane_redshift=plane_redshift |
| 415 | + ) |
363 | 416 |
|
364 | 417 | if mass_centre_radial_distance_min is not None: |
365 | 418 | mass_centre = self.max_log_likelihood_tracer.extract_attribute( |
|
0 commit comments