diff --git a/autogalaxy/__init__.py b/autogalaxy/__init__.py
index 55e3039af..780a30861 100644
--- a/autogalaxy/__init__.py
+++ b/autogalaxy/__init__.py
@@ -109,4 +109,4 @@
conf.instance.register(__file__)
-__version__ = "2023.3.27.1"
+__version__ = "2023.7.7.2"
diff --git a/autogalaxy/analysis/analysis.py b/autogalaxy/analysis/analysis.py
index 2ef1966fe..42c35dbae 100644
--- a/autogalaxy/analysis/analysis.py
+++ b/autogalaxy/analysis/analysis.py
@@ -1,9 +1,10 @@
import json
import logging
import numpy as np
-from typing import Optional, Union
+from typing import Callable, Dict, Optional, Tuple, Union
from os import path
import os
+import time
from autoconf import conf
import autofit as af
@@ -42,7 +43,9 @@ def __init__(self, cosmology: LensingCosmology = Planck15):
"""
self.cosmology = cosmology
- def plane_via_instance_from(self, instance: af.ModelInstance) -> Plane:
+ def plane_via_instance_from(
+ self, instance: af.ModelInstance, run_time_dict: Optional[Dict] = None
+ ) -> Plane:
"""
Create a `Plane` from the galaxies contained in a model instance.
@@ -57,8 +60,134 @@ def plane_via_instance_from(self, instance: af.ModelInstance) -> Plane:
An instance of the Plane class that is used to then fit the dataset.
"""
if hasattr(instance, "clumps"):
- return Plane(galaxies=instance.galaxies + instance.clumps)
- return Plane(galaxies=instance.galaxies)
+ return Plane(
+ galaxies=instance.galaxies + instance.clumps,
+ run_time_dict=run_time_dict,
+ )
+ return Plane(galaxies=instance.galaxies, run_time_dict=run_time_dict)
+
+ @property
+ def fit_func(self) -> Callable:
+ raise NotImplementedError
+
+ def profile_log_likelihood_function(
+ self, instance: af.ModelInstance, paths: Optional[af.DirectoryPaths] = None
+ ) -> Tuple[Dict, Dict]:
+ """
+ This function is optionally called throughout a model-fit to profile the log likelihood function.
+
+ All function calls inside the `log_likelihood_function` that are decorated with the `profile_func` are timed
+ with their times stored in a dictionary called the `run_time_dict`.
+
+ An `info_dict` is also created which stores information on aspects of the model and dataset that dictate
+ run times, so the profiled times can be interpreted with this context.
+
+ The results of this profiling are then output to hard-disk in the `preloads` folder of the model-fit results,
+ which they can be inspected to ensure run-times are as expected.
+
+ Parameters
+ ----------
+ instance
+ An instance of the model that is being fitted to the data by this analysis (whose parameters have been set
+ via a non-linear search).
+ paths
+ The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
+ visualization and the pickled objects used by the aggregator output by this function.
+
+ Returns
+ -------
+ Two dictionaries, the profiling dictionary and info dictionary, which contain the profiling times of the
+ `log_likelihood_function` and information on the model and dataset used to perform the profiling.
+ """
+ run_time_dict = {}
+ info_dict = {}
+
+ repeats = conf.instance["general"]["profiling"]["repeats"]
+ info_dict["repeats"] = repeats
+
+ fit = self.fit_func(instance=instance)
+ fit.figure_of_merit
+
+ start = time.time()
+
+ for i in range(repeats):
+ try:
+ fit = self.fit_func(instance=instance)
+ fit.figure_of_merit
+ except Exception:
+ logger.info(
+ "Profiling failed. Returning without outputting information."
+ )
+ return
+
+ fit_time = (time.time() - start) / repeats
+
+ run_time_dict["fit_time"] = fit_time
+
+ fit = self.fit_func(instance=instance, run_time_dict=run_time_dict)
+ fit.figure_of_merit
+
+ try:
+ info_dict["image_pixels"] = self.dataset.grid.sub_shape_slim
+ info_dict["sub_size_light_profiles"] = self.dataset.grid.sub_size
+ except AttributeError:
+ pass
+
+ if fit.model_obj.has(cls=aa.Pixelization):
+ info_dict["use_w_tilde"] = fit.inversion.settings.use_w_tilde
+ info_dict["sub_size_pixelization"] = self.dataset.grid_pixelization.sub_size
+ info_dict[
+ "use_positive_only_solver"
+ ] = fit.inversion.settings.use_positive_only_solver
+ info_dict[
+ "force_edge_pixels_to_zeros"
+ ] = fit.inversion.settings.force_edge_pixels_to_zeros
+ info_dict["use_w_tilde_numpy"] = fit.inversion.settings.use_w_tilde_numpy
+ info_dict["source_pixels"] = len(fit.inversion.reconstruction)
+
+ if hasattr(fit.inversion, "w_tilde"):
+ info_dict[
+ "w_tilde_curvature_preload_size"
+ ] = fit.inversion.w_tilde.curvature_preload.shape[0]
+
+ self.output_profiling_info(
+ paths=paths, run_time_dict=run_time_dict, info_dict=info_dict
+ )
+
+ return run_time_dict, info_dict
+
+ def output_profiling_info(
+ self, paths: Optional[af.DirectoryPaths], run_time_dict: Dict, info_dict: Dict
+ ):
+ """
+ Output the log likelihood function profiling information to hard-disk as a json file.
+
+ This function is separate from the `profile_log_likelihood_function` function above such that it can be
+ called by children `Analysis` classes that profile additional aspects of the model-fit and therefore add
+ extra information to the `run_time_dict` and `info_dict`.
+
+ Parameters
+ ----------
+ paths
+ The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
+ visualization and the pickled objects used by the aggregator output by this function.
+ run_time_dict
+ A dictionary containing the profiling times of the functions called by the `log_likelihood_function`.
+ info_dict
+ A dictionary containing information on the model and dataset used to perform the profiling, where these
+ settings typically control the overall run-time.
+ """
+
+ if paths is None:
+ return
+
+ os.makedirs(paths.profile_path, exist_ok=True)
+
+ with open(path.join(paths.profile_path, "run_time_dict.json"), "w+") as f:
+ json.dump(run_time_dict, f, indent=4)
+
+ with open(path.join(paths.profile_path, "info_dict.json"), "w+") as f:
+ json.dump(info_dict, f, indent=4)
class AnalysisDataset(Analysis):
@@ -419,7 +548,3 @@ def output_or_check_figure_of_merit_sanity(
f"Old Figure of Merit = {figure_of_merit_sanity}\n"
f"New Figure of Merit = {figure_of_merit}"
)
-
- @property
- def fit_func(self):
- raise NotImplementedError
diff --git a/autogalaxy/config/general.yaml b/autogalaxy/config/general.yaml
index 2060a19cc..ee88a811c 100644
--- a/autogalaxy/config/general.yaml
+++ b/autogalaxy/config/general.yaml
@@ -11,4 +11,4 @@ test:
bypass_figure_of_merit_sanity: false
check_preloads: false
exception_override: false
- preloads_check_threshold: 0.1 # If the figure of merit of a fit with and without preloads is greater than this threshold, the check preload test fails and an exception raised for a model-fit.
+ preloads_check_threshold: 1.0 # If the figure of merit of a fit with and without preloads is greater than this threshold, the check preload test fails and an exception raised for a model-fit.
diff --git a/autogalaxy/imaging/fit_imaging.py b/autogalaxy/imaging/fit_imaging.py
index ba77abefe..c7fa27755 100644
--- a/autogalaxy/imaging/fit_imaging.py
+++ b/autogalaxy/imaging/fit_imaging.py
@@ -25,7 +25,7 @@ def __init__(
settings_pixelization: aa.SettingsPixelization = aa.SettingsPixelization(),
settings_inversion: aa.SettingsInversion = aa.SettingsInversion(),
preloads: aa.Preloads = Preloads(),
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
"""
Fits an imaging dataset using a `Plane` object.
@@ -65,7 +65,7 @@ def __init__(
preloads
Contains preloaded calculations (e.g. linear algebra matrices) which can skip certain calculations in
the fit.
- profiling_dict
+ run_time_dict
A dictionary which if passed to the fit records how long fucntion calls which have the `profile_func`
decorator take to run.
"""
@@ -75,7 +75,7 @@ def __init__(
super().__init__(
dataset=dataset,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
AbstractFitInversion.__init__(
self=self, model_obj=plane, settings_inversion=settings_inversion
@@ -145,6 +145,7 @@ def plane_to_inversion(self) -> PlaneToInversion:
settings_pixelization=self.settings_pixelization,
settings_inversion=self.settings_inversion,
preloads=self.preloads,
+ run_time_dict=self.run_time_dict,
)
@cached_property
@@ -304,7 +305,7 @@ def refit_with_new_preloads(
-------
A new fit which has used new preloads input into this function but the same dataset, plane and other settings.
"""
- profiling_dict = {} if self.profiling_dict is not None else None
+ run_time_dict = {} if self.run_time_dict is not None else None
settings_inversion = (
self.settings_inversion
@@ -318,5 +319,5 @@ def refit_with_new_preloads(
settings_pixelization=self.settings_pixelization,
settings_inversion=settings_inversion,
preloads=preloads,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
diff --git a/autogalaxy/imaging/model/analysis.py b/autogalaxy/imaging/model/analysis.py
index a61d40d80..14f42267d 100644
--- a/autogalaxy/imaging/model/analysis.py
+++ b/autogalaxy/imaging/model/analysis.py
@@ -1,6 +1,6 @@
import numpy as np
-import os
-from typing import Dict, Optional
+
+from typing import Dict, Optional, Tuple
import autofit as af
import autoarray as aa
@@ -151,7 +151,7 @@ def fit_imaging_via_instance_from(
self,
instance: af.ModelInstance,
preload_overwrite: Optional[Preloads] = None,
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
) -> FitImaging:
"""
Given a model instance create a `FitImaging` object.
@@ -166,7 +166,7 @@ def fit_imaging_via_instance_from(
via a non-linear search).
preload_overwrite
If a `Preload` object is input this is used instead of the preloads stored as an attribute in the analysis.
- profiling_dict
+ run_time_dict
A dictionary which times functions called to fit the model to data, for profiling.
Returns
@@ -176,19 +176,21 @@ def fit_imaging_via_instance_from(
"""
instance = self.instance_with_associated_adapt_images_from(instance=instance)
- plane = self.plane_via_instance_from(instance=instance)
+ plane = self.plane_via_instance_from(
+ instance=instance, run_time_dict=run_time_dict
+ )
return self.fit_imaging_via_plane_from(
plane=plane,
preload_overwrite=preload_overwrite,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
def fit_imaging_via_plane_from(
self,
plane: Plane,
preload_overwrite: Optional[Preloads] = None,
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
) -> FitImaging:
"""
Given a `Plane`, which the analysis constructs from a model instance, create a `FitImaging` object.
@@ -202,7 +204,7 @@ def fit_imaging_via_plane_from(
The plane of galaxies whose model images are used to fit the imaging data.
preload_overwrite
If a `Preload` object is input this is used instead of the preloads stored as an attribute in the analysis.
- profiling_dict
+ run_time_dict
A dictionary which times functions called to fit the model to data, for profiling.
Returns
@@ -219,7 +221,7 @@ def fit_imaging_via_plane_from(
settings_pixelization=self.settings_pixelization,
settings_inversion=self.settings_inversion,
preloads=preloads,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
@property
@@ -400,3 +402,44 @@ def save_attributes_for_aggregator(self, paths: af.DirectoryPaths):
paths.save_object("psf", self.dataset.psf)
paths.save_object("mask", self.dataset.mask)
+
+ def profile_log_likelihood_function(
+ self, instance: af.ModelInstance, paths: Optional[af.DirectoryPaths] = None
+ ) -> Tuple[Dict, Dict]:
+ """
+ This function is optionally called throughout a model-fit to profile the log likelihood function.
+
+ All function calls inside the `log_likelihood_function` that are decorated with the `profile_func` are timed
+ with their times stored in a dictionary called the `run_time_dict`.
+
+ An `info_dict` is also created which stores information on aspects of the model and dataset that dictate
+ run times, so the profiled times can be interpreted with this context.
+
+ The results of this profiling are then output to hard-disk in the `preloads` folder of the model-fit results,
+ which they can be inspected to ensure run-times are as expected.
+
+ Parameters
+ ----------
+ instance
+ An instance of the model that is being fitted to the data by this analysis (whose parameters have been set
+ via a non-linear search).
+ paths
+ The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
+ visualization and the pickled objects used by the aggregator output by this function.
+
+ Returns
+ -------
+ Two dictionaries, the profiling dictionary and info dictionary, which contain the profiling times of the
+ `log_likelihood_function` and information on the model and dataset used to perform the profiling.
+ """
+ run_time_dict, info_dict = super().profile_log_likelihood_function(
+ instance=instance,
+ )
+
+ info_dict["psf_shape_2d"] = self.dataset.psf.shape_native
+
+ self.output_profiling_info(
+ paths=paths, run_time_dict=run_time_dict, info_dict=info_dict
+ )
+
+ return run_time_dict, info_dict
diff --git a/autogalaxy/interferometer/fit_interferometer.py b/autogalaxy/interferometer/fit_interferometer.py
index dfbb2747a..c1f42f688 100644
--- a/autogalaxy/interferometer/fit_interferometer.py
+++ b/autogalaxy/interferometer/fit_interferometer.py
@@ -20,7 +20,7 @@ def __init__(
settings_pixelization: aa.SettingsPixelization = aa.SettingsPixelization(),
settings_inversion: aa.SettingsInversion = aa.SettingsInversion(),
preloads: aa.Preloads = Preloads(),
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
"""
Fits an interferometer dataset using a `Plane` object.
@@ -61,7 +61,7 @@ def __init__(
preloads
Contains preloaded calculations (e.g. linear algebra matrices) which can skip certain calculations in
the fit.
- profiling_dict
+ run_time_dict
A dictionary which if passed to the fit records how long fucntion calls which have the `profile_func`
decorator take to run.
"""
@@ -72,7 +72,7 @@ def __init__(
settings_inversion.use_w_tilde = False
super().__init__(
- dataset=dataset, use_mask_in_fit=False, profiling_dict=profiling_dict
+ dataset=dataset, use_mask_in_fit=False, run_time_dict=run_time_dict
)
AbstractFitInversion.__init__(
self=self, model_obj=plane, settings_inversion=settings_inversion
@@ -241,10 +241,10 @@ def refit_with_new_preloads(
-------
A new fit which has used new preloads input into this function but the same dataset, plane and other settings.
"""
- if self.profiling_dict is not None:
- profiling_dict = {}
+ if self.run_time_dict is not None:
+ run_time_dict = {}
else:
- profiling_dict = None
+ run_time_dict = None
if settings_inversion is None:
settings_inversion = self.settings_inversion
@@ -255,5 +255,5 @@ def refit_with_new_preloads(
settings_pixelization=self.settings_pixelization,
settings_inversion=settings_inversion,
preloads=preloads,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
diff --git a/autogalaxy/interferometer/model/analysis.py b/autogalaxy/interferometer/model/analysis.py
index d2f07a1cb..30868ce57 100644
--- a/autogalaxy/interferometer/model/analysis.py
+++ b/autogalaxy/interferometer/model/analysis.py
@@ -1,7 +1,6 @@
import logging
import numpy as np
-import os
-from typing import Optional
+from typing import Dict, Optional, Tuple
import autofit as af
import autoarray as aa
@@ -15,7 +14,6 @@
from autogalaxy.interferometer.model.result import ResultInterferometer
from autogalaxy.interferometer.model.visualizer import VisualizerInterferometer
from autogalaxy.interferometer.fit_interferometer import FitInterferometer
-from autogalaxy.galaxy.galaxy import Galaxy
from autogalaxy.plane.plane import Plane
from autogalaxy import exc
@@ -161,7 +159,7 @@ def log_likelihood_function(self, instance: af.ModelInstance) -> float:
def fit_interferometer_via_instance_from(
self,
instance: af.ModelInstance,
- preload_overwrite: Optional[Preloads] = None,
+ run_time_dict: Optional[Dict] = None,
) -> FitInterferometer:
"""
Given a model instance create a `FitInterferometer` object.
@@ -176,7 +174,7 @@ def fit_interferometer_via_instance_from(
via a non-linear search).
preload_overwrite
If a `Preload` object is input this is used instead of the preloads stored as an attribute in the analysis.
- profiling_dict
+ run_time_dict
A dictionary which times functions called to fit the model to data, for profiling.
Returns
@@ -186,16 +184,19 @@ def fit_interferometer_via_instance_from(
"""
instance = self.instance_with_associated_adapt_images_from(instance=instance)
- plane = self.plane_via_instance_from(instance=instance)
+ plane = self.plane_via_instance_from(
+ instance=instance, run_time_dict=run_time_dict
+ )
return self.fit_interferometer_via_plane_from(
- plane=plane,
+ plane=plane, run_time_dict=run_time_dict
)
def fit_interferometer_via_plane_from(
self,
plane: Plane,
preload_overwrite: Optional[Preloads] = None,
+ run_time_dict: Optional[Dict] = None,
) -> FitInterferometer:
"""
Given a `Plane`, which the analysis constructs from a model instance, create a `FitInterferometer` object.
@@ -222,6 +223,7 @@ def fit_interferometer_via_plane_from(
settings_pixelization=self.settings_pixelization,
settings_inversion=self.settings_inversion,
preloads=preloads,
+ run_time_dict=run_time_dict,
)
@property
@@ -407,3 +409,45 @@ def save_attributes_for_aggregator(self, paths: af.DirectoryPaths):
paths.save_object("uv_wavelengths", self.dataset.uv_wavelengths)
paths.save_object("real_space_mask", self.dataset.real_space_mask)
+
+ def profile_log_likelihood_function(
+ self, instance: af.ModelInstance, paths: Optional[af.DirectoryPaths] = None
+ ) -> Tuple[Dict, Dict]:
+ """
+ This function is optionally called throughout a model-fit to profile the log likelihood function.
+
+ All function calls inside the `log_likelihood_function` that are decorated with the `profile_func` are timed
+ with their times stored in a dictionary called the `run_time_dict`.
+
+ An `info_dict` is also created which stores information on aspects of the model and dataset that dictate
+ run times, so the profiled times can be interpreted with this context.
+
+ The results of this profiling are then output to hard-disk in the `preloads` folder of the model-fit results,
+ which they can be inspected to ensure run-times are as expected.
+
+ Parameters
+ ----------
+ instance
+ An instance of the model that is being fitted to the data by this analysis (whose parameters have been set
+ via a non-linear search).
+ paths
+ The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
+ visualization and the pickled objects used by the aggregator output by this function.
+
+ Returns
+ -------
+ Two dictionaries, the profiling dictionary and info dictionary, which contain the profiling times of the
+ `log_likelihood_function` and information on the model and dataset used to perform the profiling.
+ """
+ run_time_dict, info_dict = super().profile_log_likelihood_function(
+ instance=instance,
+ )
+
+ info_dict["number_of_visibilities"] = self.dataset.visibilities.shape[0]
+ info_dict["transformer_cls"] = self.dataset.transformer.__class__.__name__
+
+ self.output_profiling_info(
+ paths=paths, run_time_dict=run_time_dict, info_dict=info_dict
+ )
+
+ return run_time_dict, info_dict
diff --git a/autogalaxy/interferometer/plot/fit_interferometer_plotters.py b/autogalaxy/interferometer/plot/fit_interferometer_plotters.py
index d3d6811f3..f9d4562bd 100644
--- a/autogalaxy/interferometer/plot/fit_interferometer_plotters.py
+++ b/autogalaxy/interferometer/plot/fit_interferometer_plotters.py
@@ -1,3 +1,4 @@
+import autoarray as aa
import autoarray.plot as aplt
from autoarray.fit.plot.fit_interferometer_plotters import FitInterferometerPlotterMeta
@@ -184,14 +185,14 @@ def subplot_fit_real_space(self):
Depending on whether `LightProfile`'s or an `Inversion` are used to represent galaxies in the `Plane`, different
methods are called to create these real-space images.
"""
- if self.fit.inversion is None:
+ if not self.plane.has(cls=aa.Pixelization):
plane_plotter = self.plane_plotter_from(plane=self.plane)
plane_plotter.subplot(
image=True, plane_image=True, auto_filename="subplot_fit_real_space"
)
- elif self.fit.inversion is not None:
+ elif self.plane.has(cls=aa.Pixelization):
self.open_subplot_figure(number_subplots=6)
mapper_index = 0
diff --git a/autogalaxy/operate/image.py b/autogalaxy/operate/image.py
index ee01df826..1d89bd125 100644
--- a/autogalaxy/operate/image.py
+++ b/autogalaxy/operate/image.py
@@ -29,6 +29,7 @@ def image_2d_from(
def has(self, cls) -> bool:
raise NotImplementedError
+ @aa.profile_func
def _blurred_image_2d_from(
self,
image_2d: aa.Array2D,
@@ -168,6 +169,7 @@ def unmasked_blurred_image_2d_from(self, grid, psf):
return padded_image_2d + padded_image_2d_operated.binned
+ @aa.profile_func
def visibilities_from(
self, grid: aa.Grid2D, transformer: aa.type.Transformer
) -> aa.Visibilities:
diff --git a/autogalaxy/plane/plane.py b/autogalaxy/plane/plane.py
index 594b4925b..5e618913d 100644
--- a/autogalaxy/plane/plane.py
+++ b/autogalaxy/plane/plane.py
@@ -1,6 +1,6 @@
import json
import numpy as np
-from typing import Dict, List, Optional, Tuple, Type
+from typing import Dict, List, Optional, Tuple, Type, Union
import autoarray as aa
@@ -23,7 +23,7 @@ def __init__(
self,
galaxies,
redshift: Optional[float] = None,
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
"""
A plane of galaxies where all galaxies are at the same redshift.
@@ -52,7 +52,7 @@ def __init__(
self.redshift = redshift
self.galaxies = galaxies
- self.profiling_dict = profiling_dict
+ self.run_time_dict = run_time_dict
def dict(self) -> Dict:
plane_dict = super().dict()
@@ -67,7 +67,7 @@ def output_to_json(self, file_path: str):
def galaxy_redshifts(self) -> List[float]:
return [galaxy.redshift for galaxy in self.galaxies]
- def has(self, cls: Tuple[Type]) -> bool:
+ def has(self, cls: Union[Type, Tuple[Type]]) -> bool:
if self.galaxies is not None:
return any(list(map(lambda galaxy: galaxy.has(cls=cls), self.galaxies)))
return False
diff --git a/autogalaxy/plane/plane_util.py b/autogalaxy/plane/plane_util.py
index 9c5403176..f7482302e 100644
--- a/autogalaxy/plane/plane_util.py
+++ b/autogalaxy/plane/plane_util.py
@@ -209,7 +209,7 @@ def galaxies_in_redshift_ordered_planes_from(galaxies, plane_redshifts):
return galaxies_in_redshift_ordered_planes
-def planes_via_galaxies_from(galaxies, profiling_dict=None, plane_cls=Plane):
+def planes_via_galaxies_from(galaxies, run_time_dict=None, plane_cls=Plane):
plane_redshifts = ordered_plane_redshifts_from(galaxies=galaxies)
galaxies_in_planes = galaxies_in_redshift_ordered_planes_from(
@@ -221,7 +221,7 @@ def planes_via_galaxies_from(galaxies, profiling_dict=None, plane_cls=Plane):
for plane_index in range(0, len(plane_redshifts)):
planes.append(
plane_cls(
- galaxies=galaxies_in_planes[plane_index], profiling_dict=profiling_dict
+ galaxies=galaxies_in_planes[plane_index], run_time_dict=run_time_dict
)
)
diff --git a/autogalaxy/plane/plot/plane_plotters.py b/autogalaxy/plane/plot/plane_plotters.py
index 37c3d62df..ee4b6ad41 100644
--- a/autogalaxy/plane/plot/plane_plotters.py
+++ b/autogalaxy/plane/plot/plane_plotters.py
@@ -133,6 +133,7 @@ def figures_2d(
zoom_to_brightest: bool = True,
title_suffix: str = "",
filename_suffix: str = "",
+ source_plane_title: bool = False,
):
"""
Plots the individual attributes of the plotter's `Plane` object in 2D, which are computed via the plotter's 2D
@@ -166,6 +167,8 @@ def figures_2d(
Add a suffix to the end of the matplotlib title label.
filename_suffix
Add a suffix to the end of the filename the plot is saved to hard-disk using.
+ source_plane_title
+ If `True`, the title of the plot is overwritten to read "source-plane image".
"""
if image:
self.mat_plot_2d.plot_array(
@@ -177,18 +180,10 @@ def figures_2d(
)
if plane_image:
- import numpy as np
-
- # print(self.plane.galaxies[0].bulge)
- # print(self.plane.galaxies[0].bulge.intensity)
- #
- # print(self.plane.plane_image_2d_from(
- # grid=self.grid, zoom_to_brightest=zoom_to_brightest
- # ))
- # print(np.max(self.plane.plane_image_2d_from(
- # grid=self.grid, zoom_to_brightest=zoom_to_brightest
- # )))
- # www
+ if source_plane_title:
+ title = "Source Plane Image"
+ else:
+ title = f"Plane Image{title_suffix}"
self.mat_plot_2d.plot_array(
array=self.plane.plane_image_2d_from(
@@ -196,17 +191,22 @@ def figures_2d(
),
visuals_2d=self.get_visuals_2d(),
auto_labels=aplt.AutoLabels(
- title=f"Plane Image{title_suffix}",
+ title=title,
filename=f"plane_image{filename_suffix}",
),
)
if plane_grid:
+ if source_plane_title:
+ title = "Source Plane Grid"
+ else:
+ title = f"Plane Grid{title_suffix}"
+
self.mat_plot_2d.plot_grid(
grid=self.grid,
visuals_2d=self.get_visuals_2d(),
auto_labels=aplt.AutoLabels(
- title=f"Plane Grid2D{title_suffix}",
+ title=title,
filename=f"plane_grid{filename_suffix}",
),
)
diff --git a/autogalaxy/plane/to_inversion.py b/autogalaxy/plane/to_inversion.py
index ada28336b..9f350cd5d 100644
--- a/autogalaxy/plane/to_inversion.py
+++ b/autogalaxy/plane/to_inversion.py
@@ -29,7 +29,7 @@ def __init__(
settings_pixelization=aa.SettingsPixelization(),
settings_inversion: aa.SettingsInversion = aa.SettingsInversion(),
preloads=Preloads(),
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
if dataset is not None:
if dataset.noise_covariance_matrix is not None:
@@ -52,7 +52,7 @@ def __init__(
self.settings_inversion = settings_inversion
self.preloads = preloads
- self.profiling_dict = profiling_dict
+ self.run_time_dict = run_time_dict
def cls_light_profile_func_list_galaxy_dict_from(
self, cls: Type
@@ -102,7 +102,7 @@ def __init__(
settings_pixelization=aa.SettingsPixelization(),
settings_inversion: aa.SettingsInversion = aa.SettingsInversion(),
preloads=aa.Preloads(),
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
self.plane = plane
@@ -114,7 +114,7 @@ def __init__(
settings_pixelization=settings_pixelization,
settings_inversion=settings_inversion,
preloads=preloads,
- profiling_dict=profiling_dict,
+ run_time_dict=run_time_dict,
)
if grid is not None:
@@ -223,7 +223,7 @@ def mapper_from(
adapt_data=adapt_galaxy_image,
settings=self.settings_pixelization,
preloads=self.preloads,
- profiling_dict=self.plane.profiling_dict,
+ run_time_dict=self.plane.run_time_dict,
)
return mapper_from(mapper_grids=mapper_grids, regularization=regularization)
@@ -268,7 +268,7 @@ def inversion(self) -> aa.AbstractInversion:
linear_obj_list=self.linear_obj_list,
settings=self.settings_inversion,
preloads=self.preloads,
- profiling_dict=self.plane.profiling_dict,
+ run_time_dict=self.plane.run_time_dict,
)
inversion.linear_obj_galaxy_dict = self.linear_obj_galaxy_dict
diff --git a/autogalaxy/plot/mass_plotter.py b/autogalaxy/plot/mass_plotter.py
index e6c841810..2e9359ae3 100644
--- a/autogalaxy/plot/mass_plotter.py
+++ b/autogalaxy/plot/mass_plotter.py
@@ -108,7 +108,7 @@ def figures_2d(
array=deflections_x,
visuals_2d=self.get_visuals_2d(),
auto_labels=aplt.AutoLabels(
- title=f"deflections X{title_suffix}",
+ title=f"Deflections X{title_suffix}",
filename=f"deflections_x_2d{filename_suffix}",
),
)
diff --git a/autogalaxy/profiles/light/linear/abstract.py b/autogalaxy/profiles/light/linear/abstract.py
index 34f8a95ca..84d8cd962 100644
--- a/autogalaxy/profiles/light/linear/abstract.py
+++ b/autogalaxy/profiles/light/linear/abstract.py
@@ -107,7 +107,7 @@ def __init__(
convolver: Optional[aa.Convolver],
light_profile_list: List[LightProfileLinear],
regularization=aa.reg.Regularization,
- profiling_dict: Optional[Dict] = None,
+ run_time_dict: Optional[Dict] = None,
):
for light_profile in light_profile_list:
if not isinstance(light_profile, LightProfileLinear):
@@ -121,7 +121,7 @@ def __init__(
)
super().__init__(
- grid=grid, regularization=regularization, profiling_dict=profiling_dict
+ grid=grid, regularization=regularization, run_time_dict=run_time_dict
)
self.blurring_grid = blurring_grid
diff --git a/docs/index.rst b/docs/index.rst
index 06291eeaf..7b0c1f6d6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,7 +6,7 @@ irregulars. Today, by analysing millions of galaxies with advanced image process
expanded on this picture and revealed the rich diversity of galaxy morphology both in the nearby and distant
Universe.
-``PyAutoGalaxy`` is an open-source Python 3.8+ package for analysing the morphologies and structures of large
+``PyAutoGalaxy`` is an open-source Python 3.8 - 3.10 package for analysing the morphologies and structures of large
multi-wavelength galaxy samples. **PyAutoGalaxy** makes it simple to model galaxies, for example this Hubble Space
Telescope imaging of a spiral galaxy:
diff --git a/docs/installation/conda.rst b/docs/installation/conda.rst
index e567f28b6..b119e7599 100644
--- a/docs/installation/conda.rst
+++ b/docs/installation/conda.rst
@@ -47,7 +47,7 @@ You may get warnings which state something like:
.. code-block:: bash
- ERROR: autoarray 2022.2.14.1 has requirement numpy<=1.22.1, but you'll have numpy 1.22.2 which is incompatible.
+ ERROR: autoarray 2023.7.7.2 has requirement numpy<=1.22.1, but you'll have numpy 1.22.2 which is incompatible.
ERROR: numba 0.53.1 has requirement llvmlite<0.37,>=0.36.0rc1, but you'll have llvmlite 0.38.0 which is incompatible.
If you see these messages, they do not mean that the installation has failed and the instructions below will
diff --git a/docs/installation/overview.rst b/docs/installation/overview.rst
index eaa2848b3..6630edaf8 100644
--- a/docs/installation/overview.rst
+++ b/docs/installation/overview.rst
@@ -3,7 +3,7 @@
Overview
========
-**PyAutoGalaxy** requires Python 3.8+ and support the Linux, MacOS and Windows operating systems.
+**PyAutoGalaxy** requires Python 3.8 - 3.10 and support the Linux, MacOS and Windows operating systems.
**PyAutoGalaxy** can be installed via the Python distribution `Anaconda `_ or using
`PyPI `_ to ``pip install`` **PyAutoGalaxy** into your Python distribution.
diff --git a/docs/installation/pip.rst b/docs/installation/pip.rst
index e252bc83e..ca89c6ec5 100644
--- a/docs/installation/pip.rst
+++ b/docs/installation/pip.rst
@@ -27,7 +27,7 @@ You may get warnings which state something like:
.. code-block:: bash
- ERROR: autoarray 2022.2.14.1 has requirement numpy<=1.22.1, but you'll have numpy 1.22.2 which is incompatible.
+ ERROR: autoarray 2023.7.7.2 has requirement numpy<=1.22.1, but you'll have numpy 1.22.2 which is incompatible.
ERROR: numba 0.53.1 has requirement llvmlite<0.37,>=0.36.0rc1, but you'll have llvmlite 0.38.0 which is incompatible.
If you see these messages, they do not mean that the installation has failed and the instructions below will
diff --git a/docs/installation/troubleshooting.rst b/docs/installation/troubleshooting.rst
index 400b12edc..b8724e3ae 100644
--- a/docs/installation/troubleshooting.rst
+++ b/docs/installation/troubleshooting.rst
@@ -14,42 +14,6 @@ the latest version of pip.
pip install --upgrade pip
pip3 install --upgrade pip
-NumPy / numba
--------------
-
-The libraries ``numpy`` and ``numba`` can be installed with incompatible versions.
-
-An error message like the one below occurs when importing **PyAutoGalaxy**:
-
-.. code-block:: bash
-
- Traceback (most recent call last):
- File "", line 1, in
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autolens/__init__.py", line 1, in
- from autoarray import preprocess
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autoarray/__init__.py", line 2, in
- from . import type
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autoarray/type.py", line 7, in
- from autoarray.mask.mask_1d import Mask1D
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autoarray/mask/mask_1d.py", line 8, in
- from autoarray.structures.arrays import array_1d_util
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autoarray/structures/arrays/array_1d_util.py", line 5, in
- from autoarray import numba_util
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/autoarray/numba_util.py", line 2, in
- import numba
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/numba/__init__.py", line 200, in
- _ensure_critical_deps()
- File "/home/jammy/venvs/PyAutoMay2/lib/python3.8/site-packages/numba/__init__.py", line 140, in _ensure_critical_deps
- raise ImportError("Numba needs NumPy 1.21 or less")
- ImportError: Numba needs NumPy 1.21 or less
-
-This can be fixed by reinstalling numpy with the version requested by the error message, in the example
-numpy 1.21 (you should replace the ``==1.21.0`` with a different version if requested).
-
-.. code-block:: bash
-
- pip install numpy==1.21.0
-
Pip / Conda
-----------
@@ -91,15 +55,15 @@ Matplotlib uses the default backend on your computer, as set in the config file:
.. code-block:: bash
- autogalaxy_workspace/config/visualize/generaltrue
+ autogalaxy_workspace/config/visualize/general.yaml
If unchanged, the backend is set to 'default', meaning it will use the backend automatically set up for Python on
your system.
.. code-block:: bash
- [general]
- backend = default
+ general:
+ backend: default
There have been reports that using the default backend causes crashes when running the test script below (either the
code crashes without a error or your computer restarts). If this happens, change the config's backend until the test
@@ -107,5 +71,5 @@ works (TKAgg has worked on Linux machines, Qt5Agg has worked on new MACs). For e
.. code-block:: bash
- [general]
- backend = TKAgg
+ general:
+ backend: TKAgg
diff --git a/docs/overview/overview_2_fitting.rst b/docs/overview/overview_2_fitting.rst
index e759c06fd..881ef6f20 100644
--- a/docs/overview/overview_2_fitting.rst
+++ b/docs/overview/overview_2_fitting.rst
@@ -26,7 +26,7 @@ demonstrate fitting.
)
dataset_plotter = aplt.ImagingPlotter(dataset=dataset)
- dataset_plotter.figures_2d(image=True, noise_map=True, psf=True)
+ dataset_plotter.figures_2d(data=True, noise_map=True, psf=True)
Here's what our ``image``, ``noise_map`` and ``psf`` (point-spread function) look like:
@@ -58,7 +58,7 @@ To do this we can use a ``Mask2D`` object, which for this example we'll create a
dataset = dataset.apply_mask(mask=mask)
dataset_plotter = aplt.ImagingPlotter(dataset=dataset)
- dataset_plotter.figures_2d(image=True)
+ dataset_plotter.figures_2d(data=True)
Here is what our image looks like with the mask applied, where **PyAutoGalaxy** has automatically zoomed around the
``Mask2D`` to make the lensed source appear bigger:
diff --git a/docs/overview/overview_3_modeling.rst b/docs/overview/overview_3_modeling.rst
index 5ec1ce4c7..ef29396b1 100644
--- a/docs/overview/overview_3_modeling.rst
+++ b/docs/overview/overview_3_modeling.rst
@@ -114,12 +114,73 @@ Analysis
--------
We next create an ``AnalysisImaging`` object, which contains the ``log likelihood function`` that the non-linear
-search calls to fit the lens model to the data.
+search calls to fit the model to the data.
.. code-block:: python
analysis = ag.AnalysisImaging(dataset=dataset)
+
+Run Times
+---------
+
+modeling can be a computationally expensive process. When fitting complex models to high resolution datasets
+run times can be of order hours, days, weeks or even months.
+
+Run times are dictated by two factors:
+
+ - The log likelihood evaluation time: the time it takes for a single ``instance`` of the model to be fitted to
+ the dataset such that a log likelihood is returned.
+
+ - The number of iterations (e.g. log likelihood evaluations) performed by the non-linear search: more complex lens
+ models require more iterations to converge to a solution.
+
+The log likelihood evaluation time can be estimated before a fit using the ``profile_log_likelihood_function`` method,
+which returns two dictionaries containing the run-times and information about the fit.
+
+.. code-block:: python
+
+ run_time_dict, info_dict = analysis.profile_log_likelihood_function(
+ instance=model.random_instance()
+ )
+
+The overall log likelihood evaluation time is given by the ``fit_time`` key.
+
+For this example, it is ~0.01 seconds, which is extremely fast for modeling. More advanced lens
+modeling features (e.g. shapelets, multi Gaussian expansions, pixelizations) have slower log likelihood evaluation
+times (1-3 seconds), and you should be wary of this when using these features.
+
+The ``run_time_dict`` has a break-down of the run-time of every individual function call in the log likelihood
+function, whereas the ``info_dict`` stores information about the data which drives the run-time (e.g. number of
+image-pixels in the mask, the shape of the PSF, etc.).
+
+.. code-block:: python
+
+ print(f"Log Likelihood Evaluation Time (second) = {run_time_dict['fit_time']}")
+
+This gives an output of ~0.01 seconds.
+
+To estimate the expected overall run time of the model-fit we multiply the log likelihood evaluation time by an
+estimate of the number of iterations the non-linear search will perform.
+
+Estimating this quantity is more tricky, as it varies depending on the model complexity (e.g. number of parameters)
+and the properties of the dataset and model being fitted.
+
+For this example, we conservatively estimate that the non-linear search will perform ~10000 iterations per free
+parameter in the model. This is an upper limit, with models typically converging in far fewer iterations.
+
+If you perform the fit over multiple CPUs, you can divide the run time by the number of cores to get an estimate of
+the time it will take to fit the model. However, above ~6 cores the speed-up from parallelization is less efficient and
+does not scale linearly with the number of cores.
+
+.. code-block:: python
+
+ print(
+ "Estimated Run Time Upper Limit (seconds) = ",
+ (run_time_dict["fit_time"] * model.total_free_parameters * 10000)
+ / search.number_of_cores,
+ )
+
Model-Fit
---------
@@ -133,7 +194,7 @@ dynesty samples, model parameters, visualization) to hard-disk.
The non-linear search fits the model by guessing many models over and over iteratively, using the models which
give a good fit to the data to guide it where to guess subsequent model.
-An animation of a non-linear search is shown below, although this is for a strong gravitational lens using
+An animation of a non-linear search is shown below, although this is for a strong gravitational using
**PyAutoGalaxy**'s child project **PyAutoLens**. Updating the animation for a galaxy is on the **PyAutoGalaxy**
to-do list!
@@ -256,7 +317,7 @@ This gives the following output:
This result contains the full posterior information of our non-linear search, including all
-parameter samples, log likelihood values and tools to compute the errors on the lens model.
+parameter samples, log likelihood values and tools to compute the errors on the model.
This is contained in the ``Samples`` object. Below, we show how to print the median PDF parameter estimates, but
many different results are available and illustrated in the `results package of the workspace `_.
diff --git a/paper/paper.md b/paper/paper.md
index 38bd3931f..5439f26b8 100644
--- a/paper/paper.md
+++ b/paper/paper.md
@@ -66,7 +66,7 @@ bibliography: paper.bib
Nearly a century ago, Edwin Hubble famously classified galaxies into three distinct groups: ellipticals, spirals and
irregulars [@Hubble1926]. Today, by analysing millions of galaxies with advanced image processing techniques Astronomers have
expanded on this picture and revealed the rich diversity of galaxy morphology in both the nearby and distant
-Universe [@Kormendy2015a; @Vulcani2014; @VanDerWel2012]. `PyAutoGalaxy` is an open-source Python 3.8+ package
+Universe [@Kormendy2015a; @Vulcani2014; @VanDerWel2012]. `PyAutoGalaxy` is an open-source Python 3.8 - 3.10 package
for analysing the morphologies and structures of large multiwavelength galaxy samples, with core features including
fully automated Bayesian model-fitting of galaxy two-dimensional surface brightness profiles, support for dataset and
interferometer datasets and comprehensive tools for simulating galaxy images. The software places a focus
@@ -161,7 +161,7 @@ taken without a local `PyAutoGalaxy` installation.
# Software Citations
-`PyAutoGalaxy` is written in Python 3.8+ and uses the following software packages:
+`PyAutoGalaxy` is written in Python 3.8 - 3.10 and uses the following software packages:
- `Astropy` [@astropy1; @astropy2]
- `COLOSSUS` [@colossus]
diff --git a/test_autogalaxy/config/general.yaml b/test_autogalaxy/config/general.yaml
index 4b7f0e2dc..8e7f12e42 100644
--- a/test_autogalaxy/config/general.yaml
+++ b/test_autogalaxy/config/general.yaml
@@ -42,4 +42,4 @@ test:
bypass_figure_of_merit_sanity: false
check_preloads: false
exception_override: false
- preloads_check_threshold: 0.1 # If the figure of merit of a fit with and without preloads is greater than this threshold, the check preload test fails and an exception raised for a model-fit.
+ preloads_check_threshold: 1.0 # If the figure of merit of a fit with and without preloads is greater than this threshold, the check preload test fails and an exception raised for a model-fit.
diff --git a/test_autogalaxy/imaging/model/test_analysis_imaging.py b/test_autogalaxy/imaging/model/test_analysis_imaging.py
index 273f2437e..49f86611e 100644
--- a/test_autogalaxy/imaging/model/test_analysis_imaging.py
+++ b/test_autogalaxy/imaging/model/test_analysis_imaging.py
@@ -37,3 +37,25 @@ def test__figure_of_merit__matches_correct_fit_given_galaxy_profiles(
fit = ag.FitImaging(dataset=masked_imaging_7x7, plane=plane)
assert fit.log_likelihood == fit_figure_of_merit
+
+
+def test__profile_log_likelihood_function(masked_imaging_7x7):
+ pixelization = ag.Pixelization(
+ mesh=ag.mesh.Rectangular(shape=(3, 3)),
+ regularization=ag.reg.Constant(coefficient=1.0),
+ )
+
+ galaxy = ag.Galaxy(redshift=0.5, pixelization=pixelization)
+
+ model = af.Collection(galaxies=af.Collection(galaxy=galaxy))
+
+ instance = model.instance_from_unit_vector([])
+
+ analysis = ag.AnalysisImaging(dataset=masked_imaging_7x7)
+
+ run_time_dict, info_dict = analysis.profile_log_likelihood_function(
+ instance=instance
+ )
+
+ assert "regularization_term_0" in run_time_dict
+ assert "log_det_regularization_matrix_term_0" in run_time_dict
diff --git a/test_autogalaxy/interferometer/model/test_analysis_interferometer.py b/test_autogalaxy/interferometer/model/test_analysis_interferometer.py
index 508bc1a74..47f65f9b4 100644
--- a/test_autogalaxy/interferometer/model/test_analysis_interferometer.py
+++ b/test_autogalaxy/interferometer/model/test_analysis_interferometer.py
@@ -38,3 +38,25 @@ def test__fit_figure_of_merit__matches_correct_fit_given_galaxy_profiles(
fit = ag.FitInterferometer(dataset=interferometer_7, plane=plane)
assert fit.log_likelihood == fit_figure_of_merit
+
+
+def test__profile_log_likelihood_function(interferometer_7):
+ pixelization = ag.Pixelization(
+ mesh=ag.mesh.Rectangular(shape=(3, 3)),
+ regularization=ag.reg.Constant(coefficient=1.0),
+ )
+
+ galaxy = ag.Galaxy(redshift=0.5, pixelization=pixelization)
+
+ model = af.Collection(galaxies=af.Collection(galaxy=galaxy))
+
+ instance = model.instance_from_unit_vector([])
+
+ analysis = ag.AnalysisInterferometer(dataset=interferometer_7)
+
+ run_time_dict, info_dict = analysis.profile_log_likelihood_function(
+ instance=instance
+ )
+
+ assert "regularization_term_0" in run_time_dict
+ assert "log_det_regularization_matrix_term_0" in run_time_dict
diff --git a/zenodo.json b/zenodo.json
index 03b18040a..60fb4a06e 100644
--- a/zenodo.json
+++ b/zenodo.json
@@ -2,7 +2,7 @@
"description": "Release which is tied to the publication of PyAutoGalaxy in the Journal of Open Source software (JOSS).",
"license": "other-open",
"title": "PyAutoGalaxy: Open-Source Multiwavelength Galaxy Structure & Morphology",
- "version": "2023.3.27.1",
+ "version": "2023.7.7.2",
"upload_type": "software",
"publication_date": "2023-01-19",
"creators": [
@@ -63,7 +63,7 @@
"related_identifiers": [
{
"scheme": "url",
- "identifier": "https://github.com/Jammy2211/PyAutoGalaxy/tree/2023.3.27.1",
+ "identifier": "https://github.com/Jammy2211/PyAutoGalaxy/tree/2023.7.7.2",
"relation": "isSupplementTo"
},
{