diff --git a/autolens/__init__.py b/autolens/__init__.py index 144e771ff..6f3bdf94a 100644 --- a/autolens/__init__.py +++ b/autolens/__init__.py @@ -47,7 +47,8 @@ from autoarray.structures.visibilities import VisibilitiesNoiseMap from autogalaxy import cosmology as cosmo -from autogalaxy.analysis.adapt_images import AdaptImages +from autogalaxy.analysis.adapt_images.adapt_images import AdaptImages +from autogalaxy.analysis.adapt_images.adapt_image_maker import AdaptImageMaker from autogalaxy.gui.clicker import Clicker from autogalaxy.gui.scribbler import Scribbler from autogalaxy.galaxy.galaxy import Galaxy diff --git a/autolens/analysis/analysis.py b/autolens/analysis/analysis.py index e6cdfc0be..2d77a8208 100644 --- a/autolens/analysis/analysis.py +++ b/autolens/analysis/analysis.py @@ -1,11 +1,7 @@ -import dill import os -import json import logging import numpy as np -from os import path -from scipy.stats import norm -from typing import Dict, Optional, List, Union +from typing import Dict, Optional, Union from autoconf import conf from autoconf.dictable import to_dict, output_to_json @@ -14,14 +10,13 @@ import autoarray as aa import autogalaxy as ag -from autogalaxy.analysis.analysis import AnalysisDataset as AgAnalysisDataset +from autogalaxy.analysis.analysis.dataset import AnalysisDataset as AgAnalysisDataset from autolens.analysis.result import ResultDataset from autolens.analysis.maker import FitMaker from autolens.analysis.preloads import Preloads from autolens.analysis.positions import PositionsLHResample from autolens.analysis.positions import PositionsLHPenalty -from autolens.analysis.visualizer import Visualizer from autolens.lens.tracer import Tracer from autolens.lens import tracer_util @@ -33,7 +28,7 @@ logger.setLevel(level="INFO") -class AnalysisLensing: +class AnalysisLens: def __init__( self, positions_likelihood: Optional[ @@ -139,14 +134,14 @@ def log_likelihood_positions_overwrite_from( raise exc.FitException from e -class AnalysisDataset(AgAnalysisDataset, AnalysisLensing): +class AnalysisDataset(AgAnalysisDataset, AnalysisLens): def __init__( self, dataset, positions_likelihood: Optional[ Union[PositionsLHResample, PositionsLHPenalty] ] = None, - adapt_images: Optional[ag.AdaptImages] = None, + adapt_image_maker: Optional[ag.AdaptImageMaker] = None, cosmology: ag.cosmo.LensingCosmology = ag.cosmo.Planck15(), settings_inversion: aa.SettingsInversion = None, raise_inversion_positions_likelihood_exception: bool = True, @@ -185,12 +180,12 @@ def __init__( super().__init__( dataset=dataset, - adapt_images=adapt_images, + adapt_image_maker=adapt_image_maker, cosmology=cosmology, settings_inversion=settings_inversion, ) - AnalysisLensing.__init__( + AnalysisLens.__init__( self=self, positions_likelihood=positions_likelihood, cosmology=cosmology, diff --git a/autolens/analysis/analysis/__init__.py b/autolens/analysis/analysis/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/autolens/analysis/analysis/dataset.py b/autolens/analysis/analysis/dataset.py new file mode 100644 index 000000000..280ac2ec0 --- /dev/null +++ b/autolens/analysis/analysis/dataset.py @@ -0,0 +1,195 @@ +import os +import logging +from typing import Optional, Union + +from autoconf import conf +from autoconf.dictable import to_dict, output_to_json + +import autofit as af +import autoarray as aa +import autogalaxy as ag + +from autogalaxy.analysis.analysis.dataset import AnalysisDataset as AgAnalysisDataset + +from autolens.analysis.analysis.lens import AnalysisLens +from autolens.analysis.result import ResultDataset +from autolens.analysis.maker import FitMaker +from autolens.analysis.preloads import Preloads +from autolens.analysis.positions import PositionsLHResample +from autolens.analysis.positions import PositionsLHPenalty + +from autolens import exc + +logger = logging.getLogger(__name__) + +logger.setLevel(level="INFO") + +class AnalysisDataset(AgAnalysisDataset, AnalysisLens): + def __init__( + self, + dataset, + positions_likelihood: Optional[ + Union[PositionsLHResample, PositionsLHPenalty] + ] = None, + adapt_image_maker: Optional[ag.AdaptImageMaker] = None, + cosmology: ag.cosmo.LensingCosmology = ag.cosmo.Planck15(), + settings_inversion: aa.SettingsInversion = None, + raise_inversion_positions_likelihood_exception: bool = True, + ): + """ + Fits a lens model to a dataset via a non-linear search. + + This abstract Analysis class has attributes and methods for all model-fits which fit the model to a dataset + (e.g. imaging or interferometer data). + + This class stores the Cosmology used for the analysis and settings that control aspects of the calculation, + including how pixelizations, inversions and lensing calculations are performed. + + Parameters + ---------- + dataset + The imaging, interferometer or other dataset that the model if fitted too. + positions_likelihood + An object which alters the likelihood function to include a term which accounts for whether + image-pixel coordinates in arc-seconds corresponding to the multiple images of the lensed source galaxy + trace close to one another in the source-plane. + adapt_images + Contains the adapt-images which are used to make a pixelization's mesh and regularization adapt to the + reconstructed galaxy's morphology. + cosmology + The AstroPy Cosmology assumed for this analysis. + settings_inversion + Settings controlling how an inversion is fitted during the model-fit, for example which linear algebra + formalism is used. + raise_inversion_positions_likelihood_exception + If an inversion is used without the `positions_likelihood` it is likely a systematic solution will + be inferred, in which case an Exception is raised before the model-fit begins to inform the user + of this. This exception is not raised if this input is False, allowing the user to perform the model-fit + anyway. + """ + + super().__init__( + dataset=dataset, + adapt_image_maker=adapt_image_maker, + cosmology=cosmology, + settings_inversion=settings_inversion, + ) + + AnalysisLens.__init__( + self=self, + positions_likelihood=positions_likelihood, + cosmology=cosmology, + ) + + self.preloads = self.preloads_cls() + + self.raise_inversion_positions_likelihood_exception = ( + raise_inversion_positions_likelihood_exception + ) + + if os.environ.get("PYAUTOFIT_TEST_MODE") == "1": + self.raise_inversion_positions_likelihood_exception = False + + def modify_before_fit(self, paths: af.DirectoryPaths, model: af.Collection): + """ + This function is called immediately before the non-linear search begins and performs final tasks and checks + before it begins. + + This function: + + - Checks that the adapt-dataset is consistent with previous adapt-datasets if the model-fit is being + resumed from a previous run. + + - Checks the model and raises exceptions if certain critieria are not met. + + Once inherited from it also visualizes objects which do not change throughout the model fit like the dataset. + + 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. + model + The PyAutoFit model object, which includes model components representing the galaxies that are fitted to + the imaging data. + """ + + super().modify_before_fit(paths=paths, model=model) + + self.raise_exceptions(model=model) + + def raise_exceptions(self, model): + has_pix = model.has_model(cls=(aa.Pixelization,)) or model.has_instance( + cls=(aa.Pixelization,) + ) + + if has_pix: + if ( + self.positions_likelihood is None + and self.raise_inversion_positions_likelihood_exception + and not conf.instance["general"]["test"][ + "disable_positions_lh_inversion_check" + ] + ): + raise exc.AnalysisException( + """ + You have begun a model-fit which reconstructs the source using a pixelization. + However, you have not input a `positions_likelihood` object. + It is likely your model-fit will infer an inaccurate solution. + + Please read the following readthedocs page for a description of why this is, and how to set up + a positions likelihood object: + + https://pyautolens.readthedocs.io/en/latest/general/demagnified_solutions.html + """ + ) + + @property + def preloads_cls(self): + return Preloads + + @property + def fit_maker_cls(self): + return FitMaker + + def save_results(self, paths: af.DirectoryPaths, result: ResultDataset): + """ + At the end of a model-fit, this routine saves attributes of the `Analysis` object to the `files` + folder such that they can be loaded after the analysis using PyAutoFit's database and aggregator tools. + + For this analysis it outputs the following: + + - The maximum log likelihood tracer of the fit. + + 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. + result + The result of a model fit, including the non-linear search, samples and maximum likelihood tracer. + """ + try: + output_to_json( + obj=result.max_log_likelihood_tracer, + file_path=paths._files_path / "tracer.json", + ) + except AttributeError: + pass + + image_mesh_list = [] + + for galaxy in result.instance.galaxies: + pixelization_list = galaxy.cls_list_from(cls=aa.Pixelization) + + for pixelization in pixelization_list: + if pixelization is not None: + image_mesh_list.append(pixelization.image_mesh) + + if len(image_mesh_list) > 0: + paths.save_json( + name="preload_mesh_grids_of_planes", + object_dict=to_dict( + result.max_log_likelihood_fit.tracer_to_inversion.image_plane_mesh_grid_pg_list + ), + ) \ No newline at end of file diff --git a/autolens/analysis/analysis/lens.py b/autolens/analysis/analysis/lens.py new file mode 100644 index 000000000..b795b8db9 --- /dev/null +++ b/autolens/analysis/analysis/lens.py @@ -0,0 +1,125 @@ +import logging +import numpy as np +from typing import Dict, Optional, Union + +import autofit as af +import autoarray as aa +import autogalaxy as ag + +from autolens.analysis.positions import PositionsLHResample +from autolens.analysis.positions import PositionsLHPenalty +from autolens.lens.tracer import Tracer + +from autolens.lens import tracer_util + +from autolens import exc + +logger = logging.getLogger(__name__) + +logger.setLevel(level="INFO") + + +class AnalysisLens: + def __init__( + self, + positions_likelihood: Optional[ + Union[PositionsLHResample, PositionsLHPenalty] + ] = None, + cosmology: ag.cosmo.LensingCosmology = ag.cosmo.Planck15(), + ): + """ + Analysis classes are used by PyAutoFit to fit a model to a dataset via a non-linear search. + + This abstract Analysis class has attributes and methods for all model-fits which include lensing calculations, + but does not perform a model-fit by itself (and is therefore only inherited from). + + This class stores the Cosmology used for the analysis and settings that control specific aspects of the lensing + calculation, for example how close the brightest pixels in the lensed source have to trace within one another + in the source plane for the model to not be discarded. + + Parameters + ---------- + cosmology + The Cosmology assumed for this analysis. + """ + self.cosmology = cosmology + self.positions_likelihood = positions_likelihood + + def tracer_via_instance_from( + self, + instance: af.ModelInstance, + run_time_dict: Optional[Dict] = None, + ) -> Tracer: + """ + Create a `Tracer` from the galaxies contained in a model instance. + + If PyAutoFit's profiling tools are used with the analysis class, this function may receive a `run_time_dict` + which times how long each set of the model-fit takes to perform. + + Parameters + ---------- + instance + An instance of the model that is fitted to the data by this analysis (whose parameters may have been set + via a non-linear search). + + Returns + ------- + Tracer + An instance of the Tracer class that is used to then fit the dataset. + """ + if hasattr(instance, "perturb"): + instance.galaxies.subhalo = instance.perturb + + # TODO : Need to think about how we do this without building it into the model attribute names. + # TODO : A Subhalo class that extends the Galaxy class maybe? + + if hasattr(instance.galaxies, "subhalo"): + subhalo_centre = tracer_util.grid_2d_at_redshift_from( + galaxies=instance.galaxies, + redshift=instance.galaxies.subhalo.redshift, + grid=aa.Grid2DIrregular(values=[instance.galaxies.subhalo.mass.centre]), + cosmology=self.cosmology, + ) + + instance.galaxies.subhalo.mass.centre = tuple(subhalo_centre.in_list[0]) + + if hasattr(instance, "cosmology"): + cosmology = instance.cosmology + else: + cosmology = self.cosmology + + return Tracer( + galaxies=instance.galaxies, + cosmology=cosmology, + run_time_dict=run_time_dict, + ) + + def log_likelihood_positions_overwrite_from( + self, instance: af.ModelInstance + ) -> Optional[float]: + """ + Call the positions overwrite log likelihood function, which add a penalty term to the likelihood if the + positions of the multiple images of the lensed source do not trace close to one another in the + source plane. + + This function handles a number of exceptions which may occur when calling the overwrite function via the + `PositionsLikelihood` class, so that they do not need to be handled individually for each `Analysis` class. + + 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). + + Returns + ------- + The penalty value of the positions log likelihood, if the positions do not trace close in the source plane, + else a None is returned to indicate there is no penalty. + """ + if self.positions_likelihood is not None: + try: + return self.positions_likelihood.log_likelihood_function_positions_overwrite( + instance=instance, analysis=self + ) + except (ValueError, np.linalg.LinAlgError) as e: + raise exc.FitException from e diff --git a/autolens/analysis/positions.py b/autolens/analysis/positions.py index 2679599f7..873351702 100644 --- a/autolens/analysis/positions.py +++ b/autolens/analysis/positions.py @@ -10,7 +10,7 @@ import autogalaxy as ag -from autogalaxy.analysis.analysis import AnalysisDataset +from autogalaxy.analysis.analysis.dataset import AnalysisDataset from autolens.lens.tracer import Tracer from autolens.point.fit_point.max_separation import FitPositionsSourceMaxSeparation diff --git a/autolens/analysis/result.py b/autolens/analysis/result.py index bccf6098b..d2b187b67 100644 --- a/autolens/analysis/result.py +++ b/autolens/analysis/result.py @@ -143,8 +143,10 @@ def positions_threshold_from( else positions ) + tracer = Tracer(galaxies=self.max_log_likelihood_galaxies) + positions_fits = FitPositionsSourceMaxSeparation( - positions=positions, noise_map=None, tracer=self.max_log_likelihood_tracer + positions=positions, noise_map=None, tracer=tracer ) threshold = factor * np.max( diff --git a/autolens/fixtures.py b/autolens/fixtures.py index bf6b2ee3a..73770f501 100644 --- a/autolens/fixtures.py +++ b/autolens/fixtures.py @@ -150,18 +150,20 @@ def make_adapt_images_7x7(): def make_analysis_imaging_7x7(): - return al.AnalysisImaging( + analysis = al.AnalysisImaging( dataset=make_masked_imaging_7x7(), settings_inversion=aa.SettingsInversion(use_w_tilde=False), - adapt_images=make_adapt_images_7x7(), ) + analysis._adapt_images = make_adapt_images_7x7() + return analysis def make_analysis_interferometer_7(): - return al.AnalysisInterferometer( - dataset=make_interferometer_7(), adapt_images=make_adapt_images_7x7() + analysis = al.AnalysisInterferometer( + dataset=make_interferometer_7(), ) - + analysis._adapt_images = make_adapt_images_7x7() + return analysis def make_analysis_point_x2(): return al.AnalysisPoint( diff --git a/autolens/imaging/fit_imaging.py b/autolens/imaging/fit_imaging.py index b42edd13e..88a040169 100644 --- a/autolens/imaging/fit_imaging.py +++ b/autolens/imaging/fit_imaging.py @@ -373,17 +373,9 @@ def refit_with_new_preloads( return FitImaging( dataset=self.dataset, tracer=self.tracer, + sky=self.sky, adapt_images=self.adapt_images, settings_inversion=settings_inversion, preloads=preloads, run_time_dict=run_time_dict, - ) - - @property - def rff(self): - return np.divide( - self.residual_map, - self.data, - # out=np.zeros_like(self.residual_map.native), - # where=np.asarray(self.mask.native) == 0, - ) \ No newline at end of file + ) \ No newline at end of file diff --git a/autolens/imaging/model/analysis.py b/autolens/imaging/model/analysis.py index 631a98871..75746c30a 100644 --- a/autolens/imaging/model/analysis.py +++ b/autolens/imaging/model/analysis.py @@ -9,7 +9,7 @@ from autoarray.exc import PixelizationException -from autolens.analysis.analysis import AnalysisDataset +from autolens.analysis.analysis.dataset import AnalysisDataset from autolens.analysis.preloads import Preloads from autolens.imaging.model.result import ResultImaging from autolens.imaging.model.visualizer import VisualizerImaging @@ -283,8 +283,10 @@ def visualize( def make_result( self, - samples: af.SamplesPDF, - search_internal = None + samples_summary: af.SamplesSummary, + paths: af.AbstractPaths, + samples: Optional[af.SamplesPDF] = None, + search_internal: Optional[object] = None, ) -> ResultImaging: """ After the non-linear search is complete create its `Result`, which includes: @@ -311,7 +313,7 @@ def make_result( ResultImaging The result of fitting the model to the imaging dataset, via a non-linear search. """ - return ResultImaging(samples=samples, analysis=self, search_internal=search_internal) + return ResultImaging(samples_summary=samples_summary, paths=paths, samples=samples, search_internal=search_internal, analysis=self) def save_attributes(self, paths: af.DirectoryPaths): """ diff --git a/autolens/interferometer/model/analysis.py b/autolens/interferometer/model/analysis.py index 583382b8a..b7ed5e5fe 100644 --- a/autolens/interferometer/model/analysis.py +++ b/autolens/interferometer/model/analysis.py @@ -10,7 +10,7 @@ from autoarray.exc import PixelizationException -from autolens.analysis.analysis import AnalysisDataset +from autolens.analysis.analysis.dataset import AnalysisDataset from autolens.analysis.preloads import Preloads from autolens.analysis.positions import PositionsLHResample from autolens.analysis.positions import PositionsLHPenalty @@ -33,7 +33,7 @@ def __init__( positions_likelihood: Optional[ Union[PositionsLHResample, PositionsLHPenalty] ] = None, - adapt_images: Optional[ag.AdaptImages] = None, + adapt_image_maker: Optional[ag.AdaptImageMaker] = None, cosmology: ag.cosmo.LensingCosmology = ag.cosmo.Planck15(), settings_inversion: aa.SettingsInversion = None, raise_inversion_positions_likelihood_exception: bool = True, @@ -78,7 +78,7 @@ def __init__( super().__init__( dataset=dataset, positions_likelihood=positions_likelihood, - adapt_images=adapt_images, + adapt_image_maker=adapt_image_maker, cosmology=cosmology, settings_inversion=settings_inversion, raise_inversion_positions_likelihood_exception=raise_inversion_positions_likelihood_exception, @@ -328,7 +328,13 @@ def visualize(self, paths: af.DirectoryPaths, instance, during_analysis): except IndexError: pass - def make_result(self, samples: af.SamplesPDF, search_internal=None): + def make_result( + self, + samples_summary: af.SamplesSummary, + paths: af.AbstractPaths, + samples: Optional[af.SamplesPDF] = None, + search_internal: Optional[object] = None, + ): """ After the non-linear search is complete create its `Result`, which includes: @@ -355,7 +361,11 @@ def make_result(self, samples: af.SamplesPDF, search_internal=None): The result of fitting the model to the imaging dataset, via a non-linear search. """ return ResultInterferometer( - samples=samples, analysis=self, search_internal=search_internal + samples_summary=samples_summary, + paths=paths, + samples=samples, + analysis=self, + search_internal=search_internal ) def save_attributes(self, paths: af.DirectoryPaths): diff --git a/autolens/interferometer/model/result.py b/autolens/interferometer/model/result.py index 55426fea6..d02bf22fa 100644 --- a/autolens/interferometer/model/result.py +++ b/autolens/interferometer/model/result.py @@ -3,7 +3,7 @@ import autoarray as aa import autogalaxy as ag -from autogalaxy.analysis.adapt_images import AdaptImages +from autogalaxy.analysis.adapt_images.adapt_images import AdaptImages from autolens.lens.tracer import Tracer from autolens.interferometer.fit_interferometer import FitInterferometer from autolens.analysis.result import ResultDataset diff --git a/autolens/lens/to_inversion.py b/autolens/lens/to_inversion.py index e7068e538..0f4063836 100644 --- a/autolens/lens/to_inversion.py +++ b/autolens/lens/to_inversion.py @@ -70,7 +70,7 @@ def lp_linear_func_list_galaxy_dict( ) for plane_index, galaxies in enumerate(self.planes): - plane_to_inversion = ag.GalaxiesToInversion( + galaxies_to_inversion = ag.GalaxiesToInversion( galaxies=galaxies, sky=self.sky, dataset=self.dataset, @@ -82,7 +82,7 @@ def lp_linear_func_list_galaxy_dict( ) lp_linear_galaxy_dict_of_plane = ( - plane_to_inversion.lp_linear_func_list_galaxy_dict + galaxies_to_inversion.lp_linear_func_list_galaxy_dict ) lp_linear_galaxy_dict_list = { diff --git a/autolens/point/model/analysis.py b/autolens/point/model/analysis.py index 6ded76b84..6f4562e7d 100644 --- a/autolens/point/model/analysis.py +++ b/autolens/point/model/analysis.py @@ -3,9 +3,9 @@ import autofit as af import autogalaxy as ag -from autogalaxy.analysis.analysis import Analysis as AgAnalysis +from autogalaxy.analysis.analysis.analysis import Analysis as AgAnalysis -from autolens.analysis.analysis import AnalysisLensing +from autolens.analysis.analysis.lens import AnalysisLens from autolens.analysis.visualizer import Visualizer from autolens.point.point_dataset import PointDict from autolens.point.fit_point.point_dict import FitPointDict @@ -23,7 +23,7 @@ NumbaException = ValueError -class AnalysisPoint(AgAnalysis, AnalysisLensing): +class AnalysisPoint(AgAnalysis, AnalysisLens): def __init__( self, point_dict: PointDict, @@ -54,7 +54,7 @@ def __init__( super().__init__(cosmology=cosmology) - AnalysisLensing.__init__(self=self, cosmology=cosmology) + AnalysisLens.__init__(self=self, cosmology=cosmology) self.point_dict = point_dict @@ -100,11 +100,17 @@ def visualize(self, paths, instance, during_analysis): def make_result( self, - samples: af.SamplesPDF, - search_internal=None, + samples_summary: af.SamplesSummary, + paths: af.AbstractPaths, + samples: Optional[af.SamplesPDF] = None, + search_internal: Optional[object] = None, ): return ResultPoint( - samples=samples, analysis=self, search_internal=search_internal + samples_summary=samples_summary, + paths=paths, + samples=samples, + search_internal=search_internal, + analysis=self, ) def save_attributes(self, paths: af.DirectoryPaths): diff --git a/autolens/point/point_solver.py b/autolens/point/point_solver.py index f8b14fda7..11f3ee020 100644 --- a/autolens/point/point_solver.py +++ b/autolens/point/point_solver.py @@ -232,8 +232,8 @@ def grid_within_distance_of_source_plane_centre( ) grid_within_distance_of_centre = grid_within_distance( - distances_1d=source_plane_distances, - grid_slim=grid, + distances_1d=np.asarray(source_plane_distances), + grid_slim=np.asarray(grid), within_distance=distance, ) diff --git a/autolens/quantity/model/analysis.py b/autolens/quantity/model/analysis.py index 42b4a76b6..9eb89dbff 100644 --- a/autolens/quantity/model/analysis.py +++ b/autolens/quantity/model/analysis.py @@ -1,4 +1,5 @@ import os +from typing import Optional import autofit as af import autogalaxy as ag @@ -6,13 +7,13 @@ from autogalaxy.quantity.model.visualizer import VisualizerQuantity from autolens.analysis.visualizer import Visualizer -from autolens.analysis.analysis import AnalysisLensing +from autolens.analysis.analysis.lens import AnalysisLens from autogalaxy.quantity.plot.fit_quantity_plotters import FitQuantityPlotter from autolens.quantity.model.result import ResultQuantity from autolens.quantity.fit_quantity import FitQuantity -class AnalysisQuantity(ag.AnalysisQuantity, AnalysisLensing): +class AnalysisQuantity(ag.AnalysisQuantity, AnalysisLens): def __init__( self, dataset: ag.DatasetQuantity, @@ -51,7 +52,7 @@ def __init__( """ super().__init__(dataset=dataset, func_str=func_str, cosmology=cosmology) - AnalysisLensing.__init__(self=self, cosmology=cosmology) + AnalysisLens.__init__(self=self, cosmology=cosmology) def fit_quantity_for_instance(self, instance: af.ModelInstance) -> FitQuantity: """ @@ -126,8 +127,10 @@ def visualize( def make_result( self, - samples: af.SamplesPDF, - search_internal=None, + samples_summary: af.SamplesSummary, + paths: af.AbstractPaths, + samples: Optional[af.SamplesPDF] = None, + search_internal: Optional[object] = None, ) -> ResultQuantity: """ After the non-linear search is complete create its `ResultQuantity`, which includes: @@ -157,5 +160,9 @@ def make_result( The result of fitting the model to the imaging dataset, via a non-linear search. """ return ResultQuantity( - samples=samples, analysis=self, search_internal=search_internal + samples_summary=samples_summary, + paths=paths, + samples=samples, + search_internal=search_internal, + analysis=self ) diff --git a/docs/api/pixelization.rst b/docs/api/pixelization.rst index 95e65dfe8..022501f07 100644 --- a/docs/api/pixelization.rst +++ b/docs/api/pixelization.rst @@ -34,7 +34,6 @@ Image Mesh [ag.image_mesh] Overlay Hilbert - HilbertBalanced KMeans Mesh [ag.mesh] diff --git a/docs/api/plot.rst b/docs/api/plot.rst index c4c51417c..8ea9af044 100644 --- a/docs/api/plot.rst +++ b/docs/api/plot.rst @@ -56,11 +56,9 @@ by **PyAutoGalaxy**. :template: custom-class-template.rst :recursive: - DynestyPlotter - UltraNestPlotter - EmceePlotter - ZeusPlotter - PySwarmsPlotter + NestPlotter + MCMCPlotter + OptimizePlotter Plot Customization [aplt] ------------------------- diff --git a/docs/overview/overview_3_modeling.rst b/docs/overview/overview_3_modeling.rst index 766013021..42f627ed3 100644 --- a/docs/overview/overview_3_modeling.rst +++ b/docs/overview/overview_3_modeling.rst @@ -523,8 +523,8 @@ mass its name ``mass`` defined when making the ``Model`` above is used). .. code-block:: python - search_plotter = aplt.DynestyPlotter(samples=result.samples) - search_plotter.corner() + plotter = aplt.NestPlotter(samples=result.samples) + plotter.corner_cornerpy() Here is an example of how a PDF estimated for a lens model appears: diff --git a/test_autolens/analysis/analysis/__init__.py b/test_autolens/analysis/analysis/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test_autolens/analysis/analysis/test_analysis_dataset.py b/test_autolens/analysis/analysis/test_analysis_dataset.py new file mode 100644 index 000000000..4bea84f32 --- /dev/null +++ b/test_autolens/analysis/analysis/test_analysis_dataset.py @@ -0,0 +1,144 @@ +import numpy as np +from os import path +import os +import pytest + +from autoconf import conf +from autoconf.dictable import from_json + +import autofit as af +import autolens as al +from autolens import exc + +directory = path.dirname(path.realpath(__file__)) + + +def test__relocate_pix_border__determines_if_border_pixel_relocation_is_used( + masked_imaging_7x7, +): + pixelization = al.Pixelization( + mesh=al.mesh.Rectangular(shape=(3, 3)), + regularization=al.reg.Constant(coefficient=1.0), + ) + + model = af.Collection( + galaxies=af.Collection( + lens=al.Galaxy( + redshift=0.5, mass=al.mp.IsothermalSph(einstein_radius=100.0) + ), + source=al.Galaxy(redshift=1.0, pixelization=pixelization), + ) + ) + + masked_imaging_7x7 = masked_imaging_7x7.apply_settings( + settings=al.SettingsImaging(sub_size_pixelization=2) + ) + + analysis = al.AnalysisImaging( + dataset=masked_imaging_7x7, + settings_inversion=al.SettingsInversion(relocate_pix_border=True), + ) + + analysis.dataset.grid_pixelization[4] = np.array([[500.0, 0.0]]) + + instance = model.instance_from_unit_vector([]) + fit = analysis.fit_from(instance=instance) + + assert fit.inversion.linear_obj_list[0].source_plane_data_grid[4][ + 0 + ] == pytest.approx(97.19584, 1.0e-2) + assert fit.inversion.linear_obj_list[0].source_plane_data_grid[4][ + 1 + ] == pytest.approx(-3.699999, 1.0e-2) + + analysis = al.AnalysisImaging( + dataset=masked_imaging_7x7, + settings_inversion=al.SettingsInversion(relocate_pix_border=False), + ) + + analysis.dataset.grid_pixelization[4] = np.array([300.0, 0.0]) + + instance = model.instance_from_unit_vector([]) + fit = analysis.fit_from( + instance=instance, + ) + + assert fit.inversion.linear_obj_list[0].source_plane_data_grid[4][ + 0 + ] == pytest.approx(200.0, 1.0e-4) + + +def test__modify_before_fit__inversion_no_positions_likelihood__raises_exception( + masked_imaging_7x7, +): + lens = al.Galaxy(redshift=0.5, mass=al.mp.IsothermalSph()) + + pixelization = al.Pixelization( + mesh=al.mesh.Rectangular(), regularization=al.reg.Constant() + ) + + source = al.Galaxy(redshift=1.0, pixelization=pixelization) + + model = af.Collection(galaxies=af.Collection(lens=lens, source=source)) + + analysis = al.AnalysisImaging(dataset=masked_imaging_7x7) + + with pytest.raises(exc.AnalysisException): + analysis.modify_before_fit(paths=af.DirectoryPaths(), model=model) + + positions_likelihood = al.PositionsLHPenalty( + positions=al.Grid2DIrregular([(1.0, 100.0), (200.0, 2.0)]), threshold=0.01 + ) + + analysis = al.AnalysisImaging( + dataset=masked_imaging_7x7, positions_likelihood=positions_likelihood + ) + analysis.modify_before_fit(paths=af.DirectoryPaths(), model=model) + + +def test__check_preloads(masked_imaging_7x7): + conf.instance["general"]["test"]["check_preloads"] = True + + lens_galaxy = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1)) + + model = af.Collection(galaxies=af.Collection(lens=lens_galaxy)) + + analysis = al.AnalysisImaging(dataset=masked_imaging_7x7) + + instance = model.instance_from_unit_vector([]) + tracer = analysis.tracer_via_instance_from(instance=instance) + fit = al.FitImaging(dataset=masked_imaging_7x7, tracer=tracer) + + analysis.preloads.check_via_fit(fit=fit) + + analysis.preloads.blurred_image = fit.blurred_image + + analysis.preloads.check_via_fit(fit=fit) + + analysis.preloads.blurred_image = fit.blurred_image + 1.0 + + with pytest.raises(exc.PreloadsException): + analysis.preloads.check_via_fit(fit=fit) + + +def test__save_results__tracer_output_to_json(analysis_imaging_7x7): + lens = al.Galaxy(redshift=0.5) + source = al.Galaxy(redshift=1.0) + + model = af.Collection(galaxies=af.Collection(lens=lens, source=source)) + + tracer = al.Tracer(galaxies=[lens, source]) + + paths = af.DirectoryPaths() + + analysis_imaging_7x7.save_results( + paths=paths, + result=al.m.MockResult(max_log_likelihood_tracer=tracer, model=model), + ) + + tracer = from_json(file_path=paths._files_path / "tracer.json") + + assert tracer.galaxies[0].redshift == 0.5 + assert tracer.galaxies[1].redshift == 1.0 + + os.remove(paths._files_path / "tracer.json") diff --git a/test_autolens/analysis/analysis/test_analysis_lens.py b/test_autolens/analysis/analysis/test_analysis_lens.py new file mode 100644 index 000000000..c6af325db --- /dev/null +++ b/test_autolens/analysis/analysis/test_analysis_lens.py @@ -0,0 +1,72 @@ +from os import path +import pytest + +import autofit as af +import autolens as al + +directory = path.dirname(path.realpath(__file__)) + + +def test__tracer_for_instance(analysis_imaging_7x7): + model = af.Collection( + galaxies=af.Collection( + lens=al.Galaxy( + redshift=0.5, + light=al.lp.SersicSph(intensity=2.0), + mass=al.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=1.0), + ), + source=al.Galaxy(redshift=1.0), + ) + + af.Collection( + clump=al.Galaxy( + redshift=0.5, + light=al.lp.SersicSph(intensity=0.1), + mass=al.mp.IsothermalSph(einstein_radius=0.2), + ) + ), + ) + + instance = model.instance_from_unit_vector([]) + tracer = analysis_imaging_7x7.tracer_via_instance_from(instance=instance) + + assert tracer.galaxies[0].redshift == 0.5 + assert tracer.galaxies[0].light.intensity == 2.0 + assert tracer.galaxies[0].mass.centre == pytest.approx((0.0, 0.0), 1.0e-4) + assert tracer.galaxies[0].mass.einstein_radius == 1.0 + assert tracer.galaxies[2].redshift == 0.5 + assert tracer.galaxies[2].light.intensity == 0.1 + assert tracer.galaxies[2].mass.einstein_radius == 0.2 + + +def test__tracer_for_instance__subhalo_redshift_rescale_used(analysis_imaging_7x7): + model = af.Collection( + galaxies=af.Collection( + lens=al.Galaxy( + redshift=0.5, + mass=al.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=1.0), + ), + subhalo=al.Galaxy(redshift=0.25, mass=al.mp.NFWSph(centre=(0.1, 0.2))), + source=al.Galaxy(redshift=1.0), + ) + ) + + instance = model.instance_from_unit_vector([]) + tracer = analysis_imaging_7x7.tracer_via_instance_from(instance=instance) + + assert tracer.galaxies[1].mass.centre == pytest.approx((0.1, 0.2), 1.0e-4) + + model = af.Collection( + galaxies=af.Collection( + lens=al.Galaxy( + redshift=0.5, + mass=al.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=1.0), + ), + subhalo=al.Galaxy(redshift=0.75, mass=al.mp.NFWSph(centre=(0.1, 0.2))), + source=al.Galaxy(redshift=1.0), + ) + ) + + instance = model.instance_from_unit_vector([]) + tracer = analysis_imaging_7x7.tracer_via_instance_from(instance=instance) + + assert tracer.galaxies[1].mass.centre == pytest.approx((-0.19959, -0.39919), 1.0e-4) \ No newline at end of file diff --git a/test_autolens/config/general.yaml b/test_autolens/config/general.yaml index 824254fa0..05d689509 100644 --- a/test_autolens/config/general.yaml +++ b/test_autolens/config/general.yaml @@ -36,8 +36,7 @@ profiling: perform: true repeats: 1 test: - check_figure_of_merit_sanity: false - bypass_figure_of_merit_sanity: true + check_likelihood_function: true # if True, when a search is resumed the likelihood of a previous sample is recalculated to ensure it is consistent with the previous run. check_preloads: false exception_override: false 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.