diff --git a/.gitignore b/.gitignore index 01554c321..3525e9b70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,11 @@ data_temp/ test_autogalaxy/quantity/model/files/ +test_autogalaxy/analysis/files/ +test_autogalaxy/galaxy/files/galaxy.json +test_autogalaxy/imaging/plot/files/ +test_autogalaxy/interferometer/model/files/ +test_autogalaxy/quantity/plot/files/ +test_autogalaxy/galaxy/plot/files/ *.log test_autogalaxy/unit/pipeline/files/plot/ test_autogalaxy/imaging/model/files/ diff --git a/autogalaxy/analysis/plotter_interface.py b/autogalaxy/analysis/plotter_interface.py index f825b2091..96f4ef52d 100644 --- a/autogalaxy/analysis/plotter_interface.py +++ b/autogalaxy/analysis/plotter_interface.py @@ -1,327 +1,188 @@ -from __future__ import annotations -import csv -import numpy as np -import os -from typing import List, Union, TYPE_CHECKING - -if TYPE_CHECKING: - from pathlib import Path - -from autoconf import conf -from autoconf.fitsable import hdu_list_for_output_from - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.analysis.adapt_images.adapt_images import AdaptImages -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.galaxy.galaxies import Galaxies -from autogalaxy.galaxy.plot.galaxies_plotters import GalaxiesPlotter -from autogalaxy.galaxy.plot.adapt_plotters import AdaptPlotter - -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D - - -def setting(section: Union[List[str], str], name: str): - if isinstance(section, str): - return conf.instance["visualize"]["plots"][section][name] - - for sect in reversed(section): - try: - return conf.instance["visualize"]["plots"][sect][name] - except KeyError: - continue - - return conf.instance["visualize"]["plots"][section[0]][name] - - -def plot_setting(section: Union[List[str], str], name: str) -> bool: - return setting(section, name) - - -class PlotterInterface: - def __init__(self, image_path: Union[Path, str], title_prefix: str = None): - """ - Provides an interface between an output path and all plotter objects. - - This is used to visualize the results of a model-fit, where the `image_path` points to the - folder where the results of the model-fit are stored on your hard-disk, which is typically the `image` folder - of a non-linear search. - - The `PlotterInterface` is typically used in the `Analysis` class of a non-linear search to visualize the maximum - log likelihood model of the model-fit so far. - - The methods of the `PlotterInterface` are called throughout a non-linear search using the `Analysis.Visualizer` - classes `visualize` method. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml`. - - Parameters - ---------- - image_path - The path on the hard-disk to the `image` folder of the non-linear searches results. - title_prefix - A string that is added before the title of all figures output by visualization, for example to - put the name of the dataset and galaxy in the title. - """ - from pathlib import Path - - self.image_path = Path(image_path) - self.title_prefix = title_prefix - - os.makedirs(image_path, exist_ok=True) - - @property - def fmt(self) -> List[str]: - return conf.instance["visualize"]["plots"]["subplot_format"] - - def mat_plot_1d_from(self) -> MatPlot1D: - """ - Returns a 1D matplotlib plotting object whose `Output` class uses the `image_path`, such that it outputs - images to the `image` folder of the non-linear search. - - Returns - ------- - MatPlot1D - The 1D matplotlib plotter object. - """ - return MatPlot1D( - title=aplt.Title(prefix=self.title_prefix), - output=aplt.Output(path=self.image_path, format=self.fmt), - ) - - def mat_plot_2d_from(self, quick_update: bool = False) -> MatPlot2D: - """ - Returns a 2D matplotlib plotting object whose `Output` class uses the `image_path`, such that it outputs - images to the `image` folder of the non-linear search. - - Returns - ------- - MatPlot2D - The 2D matplotlib plotter object. - """ - return MatPlot2D( - title=aplt.Title(prefix=self.title_prefix), - output=aplt.Output(path=self.image_path, format=self.fmt), - quick_update=quick_update, - ) - - def galaxies( - self, - galaxies: List[Galaxy], - grid: aa.type.Grid2DLike, - ): - """ - Visualizes a list of galaxies. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the - `image_path` points to the search's results folder and this function visualizes the maximum log likelihood - galaxies inferred by the search so far. - - Visualization includes subplots of the individual images of attributes of the galaxies (e.g. its image, - convergence, deflection angles) and .fits files containing these attributes. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `galaxies` header. - - Parameters - ---------- - galaxies - The maximum log likelihood galaxies of the non-linear search. - grid - A 2D grid of (y,x) arc-second coordinates used to perform ray-tracing, which is the masked grid tied to - the dataset. - """ - - galaxies = Galaxies(galaxies=galaxies) - - def should_plot(name): - return plot_setting(section="galaxies", name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - plotter = GalaxiesPlotter( - galaxies=galaxies, - grid=grid, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_galaxy_images"): - plotter.subplot_galaxy_images() - - mat_plot_2d = self.mat_plot_2d_from() - - plotter = GalaxiesPlotter( - galaxies=galaxies, - grid=grid, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_galaxies"): - plotter.subplot() - - mat_plot_1d = self.mat_plot_1d_from() - - galaxies_plotter = GalaxiesPlotter( - galaxies=galaxies, - grid=grid, - mat_plot_1d=mat_plot_1d, - ) - - if should_plot("fits_galaxy_images"): - - image_list = [ - galaxy.image_2d_from(grid=grid).native_for_fits for galaxy in galaxies - ] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=["mask"] + [f"galaxy_{i}" for i in range(len(galaxies))], - header_dict=grid.mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "galaxy_images.fits", overwrite=True) - - def inversion(self, inversion: aa.Inversion): - """ - Visualizes an `Inversion` object. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - points to the search's results folder and this function visualizes the maximum log likelihood `Inversion` - inferred by the search so far. - - Visualization includes subplots of individual images of attributes of the dataset (e.g. the reconstructed image, - the reconstruction) and .fits file of attributes. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `inversion` header. - - Parameters - ---------- - inversion - The inversion used to fit the dataset whose attributes are visualized. - """ - - def should_plot(name): - return plot_setting(section="inversion", name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - inversion_plotter = aplt.InversionPlotter( - inversion=inversion, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_inversion"): - mapper_list = inversion.cls_list_from(cls=aa.Mapper) - - for i in range(len(mapper_list)): - suffix = "" if len(mapper_list) == 1 else f"_{i}" - - inversion_plotter.subplot_of_mapper( - mapper_index=i, auto_filename=f"subplot_inversion{suffix}" - ) - - if should_plot("csv_reconstruction"): - mapper_list = inversion.cls_list_from(cls=aa.Mapper) - - for i, mapper in enumerate(mapper_list): - y = mapper.source_plane_mesh_grid[:, 0] - x = mapper.source_plane_mesh_grid[:, 1] - reconstruction = inversion.reconstruction_dict[mapper] - noise_map = inversion.reconstruction_noise_map_dict[mapper] - - with open( - self.image_path / f"source_plane_reconstruction_{i}.csv", - mode="w", - newline="", - ) as file: - writer = csv.writer(file) - writer.writerow(["y", "x", "reconstruction", "noise_map"]) # header - - for i in range(len(x)): - writer.writerow( - [ - float(y[i]), - float(x[i]), - float(reconstruction[i]), - float(noise_map[i]), - ] - ) - - def adapt_images( - self, - adapt_images: AdaptImages, - ): - """ - Visualizes the adapt images used by a model-fit for adaptive pixelization mesh's and regularization. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a subplot image of all galaxy images on the same figure. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `adapt` header. - - Parameters - ---------- - adapt_images - The adapt images (e.g. overall model image, individual galaxy images). - """ - - def should_plot(name): - return plot_setting(section="adapt", name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - adapt_plotter = AdaptPlotter( - mat_plot_2d=mat_plot_2d, - ) - - if adapt_images.galaxy_name_image_dict is not None: - - if should_plot("subplot_adapt_images"): - adapt_plotter.subplot_adapt_images( - adapt_galaxy_name_image_dict=adapt_images.galaxy_name_image_dict - ) - - if should_plot("fits_adapt_images"): - - if adapt_images.galaxy_name_image_dict is not None: - - image_list = [ - adapt_images.galaxy_name_image_dict[name].native_for_fits - for name in adapt_images.galaxy_name_image_dict.keys() - ] - - hdu_list = hdu_list_for_output_from( - values_list=[ - image_list[0].mask.astype("float"), - ] - + image_list, - ext_name_list=["mask"] - + list(adapt_images.galaxy_name_image_dict.keys()), - header_dict=adapt_images.mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "adapt_images.fits", overwrite=True) - - if adapt_images.galaxy_name_image_plane_mesh_grid_dict is not None: - - image_plane_mesh_grid_list = [ - adapt_images.galaxy_name_image_plane_mesh_grid_dict[name].native - for name in adapt_images.galaxy_name_image_plane_mesh_grid_dict.keys() - ] - - hdu_list = hdu_list_for_output_from( - values_list=[np.array([1])] + image_plane_mesh_grid_list, - ext_name_list=[""] - + list(adapt_images.galaxy_name_image_plane_mesh_grid_dict.keys()), - ) - - hdu_list.writeto( - self.image_path / "adapt_image_plane_mesh_grids.fits", - overwrite=True, - ) +from __future__ import annotations +import csv +import matplotlib.pyplot as plt +import numpy as np +import os +from typing import List, Union, TYPE_CHECKING + +if TYPE_CHECKING: + from pathlib import Path + +from autoconf import conf +from autoconf.fitsable import hdu_list_for_output_from + +import autoarray as aa +import autoarray.plot as aplt + +from autogalaxy.analysis.adapt_images.adapt_images import AdaptImages +from autogalaxy.galaxy.galaxy import Galaxy +from autogalaxy.galaxy.galaxies import Galaxies +from autogalaxy.galaxy.plot import galaxies_plots +from autogalaxy.galaxy.plot import adapt_plots + + +def setting(section: Union[List[str], str], name: str): + if isinstance(section, str): + return conf.instance["visualize"]["plots"][section][name] + + for sect in reversed(section): + try: + return conf.instance["visualize"]["plots"][sect][name] + except KeyError: + continue + + return conf.instance["visualize"]["plots"][section[0]][name] + + +def plot_setting(section: Union[List[str], str], name: str) -> bool: + return setting(section, name) + + +class PlotterInterface: + def __init__(self, image_path: Union[Path, str], title_prefix: str = None): + from pathlib import Path + + self.image_path = Path(image_path) + self.title_prefix = title_prefix + + os.makedirs(image_path, exist_ok=True) + + @property + def fmt(self) -> List[str]: + return conf.instance["visualize"]["plots"]["subplot_format"] + + def output_from(self) -> aplt.Output: + return aplt.Output(path=self.image_path, format=self.fmt) + + def galaxies( + self, + galaxies: List[Galaxy], + grid: aa.type.Grid2DLike, + ): + galaxies = Galaxies(galaxies=galaxies) + + def should_plot(name): + return plot_setting(section="galaxies", name=name) + + if should_plot("subplot_galaxy_images"): + galaxies_plots.subplot_galaxy_images( + galaxies=galaxies, + grid=grid, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("subplot_galaxies"): + galaxies_plots.subplot_galaxies( + galaxies=galaxies, + grid=grid, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("fits_galaxy_images"): + image_list = [ + galaxy.image_2d_from(grid=grid).native_for_fits for galaxy in galaxies + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + [f"galaxy_{i}" for i in range(len(galaxies))], + header_dict=grid.mask.header_dict, + ) + + hdu_list.writeto(self.image_path / "galaxy_images.fits", overwrite=True) + + def inversion(self, inversion: aa.Inversion): + def should_plot(name): + return plot_setting(section="inversion", name=name) + + output = self.output_from() + + if should_plot("subplot_inversion"): + from autogalaxy.plot.plot_utils import _save_subplot + + mapper_list = inversion.cls_list_from(cls=aa.Mapper) + + for i, mapper in enumerate(mapper_list): + suffix = f"_{i}" + reconstruction = inversion.reconstruction_dict[mapper] + grid = np.array(mapper.source_plane_mesh_grid) + + fig, ax = plt.subplots(1, 1, figsize=(7, 7)) + sc = ax.scatter(grid[:, 1], grid[:, 0], c=reconstruction, s=10) + plt.colorbar(sc, ax=ax) + ax.set_title("Reconstruction") + ax.set_aspect("equal") + _save_subplot(fig, output.path, f"subplot_inversion{suffix}", output.format_list[0] if output.format_list else "png") + + if should_plot("csv_reconstruction"): + mapper_list = inversion.cls_list_from(cls=aa.Mapper) + + for i, mapper in enumerate(mapper_list): + y = mapper.source_plane_mesh_grid[:, 0] + x = mapper.source_plane_mesh_grid[:, 1] + reconstruction = inversion.reconstruction_dict[mapper] + noise_map = inversion.reconstruction_noise_map_dict[mapper] + + with open( + self.image_path / f"source_plane_reconstruction_{i}.csv", + mode="w", + newline="", + ) as file: + writer = csv.writer(file) + writer.writerow(["y", "x", "reconstruction", "noise_map"]) + + for i in range(len(x)): + writer.writerow( + [ + float(y[i]), + float(x[i]), + float(reconstruction[i]), + float(noise_map[i]), + ] + ) + + def adapt_images(self, adapt_images: AdaptImages): + def should_plot(name): + return plot_setting(section="adapt", name=name) + + if adapt_images.galaxy_name_image_dict is not None: + if should_plot("subplot_adapt_images"): + adapt_plots.subplot_adapt_images( + adapt_galaxy_name_image_dict=adapt_images.galaxy_name_image_dict, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("fits_adapt_images"): + if adapt_images.galaxy_name_image_dict is not None: + image_list = [ + adapt_images.galaxy_name_image_dict[name].native_for_fits + for name in adapt_images.galaxy_name_image_dict.keys() + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + list(adapt_images.galaxy_name_image_dict.keys()), + header_dict=adapt_images.mask.header_dict, + ) + + hdu_list.writeto(self.image_path / "adapt_images.fits", overwrite=True) + + if adapt_images.galaxy_name_image_plane_mesh_grid_dict is not None: + image_plane_mesh_grid_list = [ + adapt_images.galaxy_name_image_plane_mesh_grid_dict[name].native + for name in adapt_images.galaxy_name_image_plane_mesh_grid_dict.keys() + ] + + hdu_list = hdu_list_for_output_from( + values_list=[np.array([1])] + image_plane_mesh_grid_list, + ext_name_list=[""] + + list(adapt_images.galaxy_name_image_plane_mesh_grid_dict.keys()), + ) + + hdu_list.writeto( + self.image_path / "adapt_image_plane_mesh_grids.fits", + overwrite=True, + ) diff --git a/autogalaxy/config/visualize/mat_wrap_1d.yaml b/autogalaxy/config/visualize/mat_wrap_1d.yaml deleted file mode 100644 index 590cf0b5b..000000000 --- a/autogalaxy/config/visualize/mat_wrap_1d.yaml +++ /dev/null @@ -1,19 +0,0 @@ -EinsteinRadiusAXVLine: - figure: - c: b - linestyle: -- - subplot: - c: b - linestyle: -- -HalfLightRadiusAXVLine: - figure: - c: g - linestyle: -. - subplot: - c: kg - linestyle: -. -ModelFluxesYXScatter: - figure: - c: c - subplot: - c: c diff --git a/autogalaxy/config/visualize/mat_wrap_2d.yaml b/autogalaxy/config/visualize/mat_wrap_2d.yaml deleted file mode 100644 index 3d8be5b01..000000000 --- a/autogalaxy/config/visualize/mat_wrap_2d.yaml +++ /dev/null @@ -1,63 +0,0 @@ -TangentialCriticalCurvesPlot: - figure: - c: w - linestyle: '-' - linewidth: 2 - subplot: - c: w - linestyle: '-' - linewidth: 2 -TangentialCausticsPlot: - figure: - c: w - linestyle: '-' - linewidth: 2 - subplot: - c: w - linestyle: '-' - linewidth: 2 -RadialCriticalCurvesPlot: - figure: - c: y - linestyle: '-' - linewidth: 2 - subplot: - c: y - linestyle: '-' - linewidth: 2 -RadialCausticsPlot: - figure: - c: y - linestyle: '-' - linewidth: 2 - subplot: - c: y - linestyle: '-' - linewidth: 2 -LightProfileCentresScatter: - figure: - c: k,r,g,b,m,y - marker: + - s: 20 - subplot: - c: r,g,b,m,y,k - marker: + - s: 26 -MassProfileCentresScatter: - figure: - c: k,r,g,b,m,y - marker: x - s: 20 - subplot: - c: r,g,b,m,y,k - marker: x - s: 26 -MultipleImagesScatter: - figure: - c: k,r,g,b,m,y - marker: o - s: 16 - subplot: - c: r,g,b,m,y,k - marker: o - s: 16 diff --git a/autogalaxy/ellipse/model/plotter_interface.py b/autogalaxy/ellipse/model/plotter_interface.py index ce40a2b21..cba51b6be 100644 --- a/autogalaxy/ellipse/model/plotter_interface.py +++ b/autogalaxy/ellipse/model/plotter_interface.py @@ -1,121 +1,101 @@ -from typing import List - -from autoconf.fitsable import hdu_list_for_output_from - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.ellipse.fit_ellipse import FitEllipse -from autogalaxy.ellipse.plot.fit_ellipse_plotters import FitEllipsePlotter -from autogalaxy.analysis.plotter_interface import PlotterInterface - -from autogalaxy.analysis.plotter_interface import plot_setting - - -class PlotterInterfaceEllipse(PlotterInterface): - def imaging(self, dataset: aa.Imaging): - """ - Visualizes an `Imaging` dataset object. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes individual images of attributes of the dataset (e.g. the image, noise map, PSF) and a - subplot of all these attributes on the same figure. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under the - [dataset] header. - - Parameters - ---------- - dataset - The imaging dataset whose attributes are visualized. - """ - - def should_plot(name): - return plot_setting(section=["dataset", "imaging"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - dataset_plotter = aplt.ImagingPlotter( - dataset=dataset, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_dataset"): - dataset_plotter.subplot_dataset() - - image_list = [ - dataset.data.native, - dataset.noise_map.native, - ] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=[ - "mask", - "data", - "noise_map", - ], - header_dict=dataset.mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) - - def fit_ellipse( - self, - fit_list: List[FitEllipse], - ): - """ - Visualizes a `FitEllipse` object, which fits an imaging dataset. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - points to the search's results folder and this function visualizes the maximum log likelihood `FitEllipse` - inferred by the search so far. - - Visualization includes a subplot of individual images of attributes of the `FitEllipse` (e.g. the model data, - residual map). - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `fit` and `fit_ellipse` headers. - - Parameters - ---------- - fit - The maximum log likelihood `FitEllipse` of the non-linear search which is used to plot the fit. - """ - - def should_plot(name): - return plot_setting(section=["fit", "fit_ellipse"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - fit_plotter = FitEllipsePlotter( - fit_list=fit_list, - mat_plot_2d=mat_plot_2d, - ) - - fit_plotter.figures_2d( - data=should_plot("data"), - ellipse_residuals=should_plot("ellipse_residuals"), - ) - - if should_plot("data_no_ellipse"): - fit_plotter.figures_2d( - data=True, - disable_data_contours=True, - ) - - if should_plot("subplot_fit_ellipse"): - - fit_plotter.subplot_fit_ellipse() - - fit_plotter.mat_plot_2d.use_log10 = True - - fit_plotter.figures_2d(data=should_plot("data")) - - if should_plot("data_no_ellipse"): - fit_plotter.figures_2d( - data=True, - disable_data_contours=True, - ) +import matplotlib.pyplot as plt +from typing import List + +from autoconf.fitsable import hdu_list_for_output_from + +import autoarray as aa +import autoarray.plot as aplt + +from autogalaxy.ellipse.fit_ellipse import FitEllipse +from autogalaxy.ellipse.plot import fit_ellipse_plots +from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +class PlotterInterfaceEllipse(PlotterInterface): + def imaging(self, dataset: aa.Imaging): + def should_plot(name): + return plot_setting(section=["dataset", "imaging"], name=name) + + if should_plot("subplot_dataset"): + panels = [ + (dataset.data, "Data"), + (dataset.noise_map, "Noise Map"), + (dataset.signal_to_noise_map, "Signal-To-Noise Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) if n > 1 else [axes] + for i, (array, title) in enumerate(panels): + plot_array(array, title, ax=axes_flat[i]) + plt.tight_layout() + _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + + image_list = [ + dataset.data.native, + dataset.noise_map.native, + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=[ + "mask", + "data", + "noise_map", + ], + header_dict=dataset.mask.header_dict, + ) + + hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) + + def fit_ellipse( + self, + fit_list: List[FitEllipse], + ): + def should_plot(name): + return plot_setting(section=["fit", "fit_ellipse"], name=name) + + if should_plot("data"): + fit_ellipse_plots._plot_data( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("ellipse_residuals"): + fit_ellipse_plots._plot_ellipse_residuals( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("data_no_ellipse"): + fit_ellipse_plots._plot_data( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + disable_data_contours=True, + ) + + if should_plot("subplot_fit_ellipse"): + fit_ellipse_plots.subplot_fit_ellipse( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + ) + + fit_ellipse_plots._plot_data( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + use_log10=True, + ) + + if should_plot("data_no_ellipse"): + fit_ellipse_plots._plot_data( + fit_list=fit_list, + output_path=self.image_path, + output_format=self.fmt, + use_log10=True, + disable_data_contours=True, + ) diff --git a/autogalaxy/ellipse/plot/fit_ellipse_plot_util.py b/autogalaxy/ellipse/plot/fit_ellipse_plot_util.py index cea3c97ab..68bf89825 100644 --- a/autogalaxy/ellipse/plot/fit_ellipse_plot_util.py +++ b/autogalaxy/ellipse/plot/fit_ellipse_plot_util.py @@ -4,7 +4,36 @@ def plot_ellipse_residuals(array, fit_list, colors, output, for_subplot: bool = False): - + """Plot the 1-D ellipse residuals as a function of position angle. + + For each :class:`~autogalaxy.ellipse.fit_ellipse.FitEllipse` in + *fit_list*, the interpolated data values are scatter-plotted against + the position angle (in degrees) together with error bars from the noise + map and a horizontal line at the mean intensity. The y-axis uses a + log₁₀ scale so that faint residuals remain visible. + + The plot can be used either as a standalone figure (``for_subplot=False``) + — in which case it is saved via the *output* object and the figure is + closed — or as the right-hand panel of a larger subplot + (``for_subplot=True``), in which case it draws onto the current figure + and the caller is responsible for saving. + + Parameters + ---------- + array + The native 2-D data array; used only to extract the pixel scale for + computing position angles. + fit_list : list of FitEllipse + The ellipse fits to summarise. One series is drawn per fit. + colors : str or sequence + A color spec or iterable of color specs cycled across the fits. + output + An ``autoarray`` ``Output`` object used to save the figure when + ``for_subplot=False``. + for_subplot : bool + If ``True``, draw onto subplot position ``(1, 2, 2)`` of the current + figure instead of creating a new figure. + """ from astropy import units color = itertools.cycle(colors) diff --git a/autogalaxy/ellipse/plot/fit_ellipse_plots.py b/autogalaxy/ellipse/plot/fit_ellipse_plots.py new file mode 100644 index 000000000..071c20970 --- /dev/null +++ b/autogalaxy/ellipse/plot/fit_ellipse_plots.py @@ -0,0 +1,245 @@ +import numpy as np +import math +import matplotlib.pyplot as plt +from typing import List, Optional + +import autoarray as aa +from autoarray import plot as aplt + +from autogalaxy.ellipse.plot import fit_ellipse_plot_util +from autogalaxy.ellipse.fit_ellipse import FitEllipse +from autogalaxy.plot.plot_utils import plot_array, _save_subplot +from autogalaxy.util import error_util + + +def _plot_data( + fit_list: List[FitEllipse], + output_path=None, + output_filename="ellipse_fit", + output_format="png", + colormap="default", + use_log10=False, + disable_data_contours: bool = False, + suffix: str = "", + ax=None, +): + """Plot the 2-D image data with fitted ellipse contours overlaid. + + For each :class:`~autogalaxy.ellipse.fit_ellipse.FitEllipse` in + *fit_list* the major-axis sampling points are extracted and converted to + ``Grid2DIrregular`` objects, which are then passed as *lines* and + *positions* overlays to the underlying array-plot routine. + + Parameters + ---------- + fit_list : list of FitEllipse + The ellipse fits whose contours are to be overlaid. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_filename : str + Stem of the output file name. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + disable_data_contours : bool + Reserved for future contour-suppression support (currently unused). + suffix : str + Optional suffix appended to *output_filename* before the extension. + ax : matplotlib.axes.Axes or None + Existing ``Axes`` to draw into; the caller is responsible for saving + when this is provided. + """ + ellipse_list = [] + for fit in fit_list: + points = fit.points_from_major_axis_from() + x = points[:, 1] + y = points[:, 0] * -1.0 + ellipse_list.append(aa.Grid2DIrregular.from_yx_1d(y=y, x=x)) + + lines = [np.array(e.array) for e in ellipse_list if e is not None] + positions = lines + + plot_array( + array=fit_list[0].data, + title="Ellipse Fit", + output_path=output_path, + output_filename=f"{output_filename}{suffix}", + output_format=output_format, + colormap=colormap, + use_log10=use_log10, + lines=lines or None, + positions=positions or None, + ax=ax, + ) + + +def _plot_ellipse_residuals( + fit_list: List[FitEllipse], + output_path=None, + output_format="png", + for_subplot: bool = False, + suffix: str = "", + ax=None, +): + """Plot the 1-D ellipse residuals via the low-level utility function. + + Constructs the ``autoarray`` ``Output`` object required by + :func:`~autogalaxy.ellipse.plot.fit_ellipse_plot_util.plot_ellipse_residuals` + and then delegates to it. + + Parameters + ---------- + fit_list : list of FitEllipse + The ellipse fits to summarise. + output_path : str or None + Directory in which to save the figure. ``None`` → an ``Output`` + with no path is created, falling back to ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + for_subplot : bool + If ``True``, draw into subplot position ``(1, 2, 2)`` of the current + figure. + suffix : str + Reserved for future filename-suffix support (currently unused). + ax : matplotlib.axes.Axes or None + Reserved for future direct-axes support (currently unused). + """ + output = aplt.Output(path=output_path, format=output_format) if output_path else aplt.Output() + + fit_ellipse_plot_util.plot_ellipse_residuals( + array=fit_list[0].dataset.data.native, + fit_list=fit_list, + colors="k", + output=output, + for_subplot=for_subplot, + ) + + +def subplot_fit_ellipse( + fit_list: List[FitEllipse], + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + disable_data_contours: bool = False, +): + """Create a two-panel subplot summarising a list of ellipse fits. + + The left panel shows the 2-D image with fitted ellipse contours overlaid + (via :func:`_plot_data`); the right panel shows the 1-D residuals as a + function of position angle (via :func:`_plot_ellipse_residuals`). + + Parameters + ---------- + fit_list : list of FitEllipse + The ellipse fits to visualise. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values in the left panel. + disable_data_contours : bool + If ``True``, suppress ellipse contour overlays on the image panel. + """ + fig, axes = plt.subplots(1, 2, figsize=(14, 7)) + + _plot_data( + fit_list=fit_list, + colormap=colormap, + use_log10=use_log10, + disable_data_contours=disable_data_contours, + ax=axes[0], + ) + _plot_ellipse_residuals(fit_list=fit_list, for_subplot=True, ax=axes[1]) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_fit_ellipse", output_format) + + +def subplot_ellipse_errors( + fit_pdf_list: List[List[FitEllipse]], + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + sigma: Optional[float] = 3.0, +): + """Create a subplot showing the median ellipse and its uncertainty region from a PDF sample. + + *fit_pdf_list* is a list of fit-lists — each inner list represents one + posterior sample and contains one :class:`~autogalaxy.ellipse.fit_ellipse.FitEllipse` + per ellipse. For each ellipse position the median contour and the + ``sigma``-level confidence interval are computed in polar coordinates + (via :func:`~autogalaxy.util.error_util.ellipse_median_and_error_region_in_polar`) + and overlaid on the 2-D image. + + One panel is produced per ellipse. + + Parameters + ---------- + fit_pdf_list : list of list of FitEllipse + Outer list: posterior samples. Inner list: per-ellipse fits for that + sample. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + sigma : float or None + Number of standard deviations defining the confidence interval + (default ``3.0``). + """ + low_limit = (1 - math.erf(sigma / math.sqrt(2))) / 2 + + ellipse_centre_list = [] + fit_ellipse_list = [[] for _ in range(len(fit_pdf_list[0]))] + + for fit_list in fit_pdf_list: + ellipse_centre_list.append(fit_list[0].ellipse.centre) + for i, fit in enumerate(fit_list): + points = fit.points_from_major_axis_from() + x = points[:, 1] + y = points[:, 0] * -1.0 + fit_ellipse_list[i].append(aa.Grid2DIrregular.from_yx_1d(y=y, x=x)) + + n = len(fit_ellipse_list) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = [axes] if n == 1 else list(axes.flatten()) + + for i in range(n): + median_ellipse, [lower_ellipse, upper_ellipse] = ( + error_util.ellipse_median_and_error_region_in_polar( + fit_ellipse_list[i], + low_limit=low_limit, + center=ellipse_centre_list[i], + ) + ) + + try: + median_arr = np.array( + median_ellipse.array if hasattr(median_ellipse, "array") else median_ellipse + ) + lines = [median_arr] if median_arr.ndim == 2 else None + except Exception: + lines = None + + plot_array( + array=fit_pdf_list[0][0].data, + title="Ellipse Fit", + colormap=colormap, + use_log10=use_log10, + lines=lines, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_ellipse_errors", output_format) diff --git a/autogalaxy/ellipse/plot/fit_ellipse_plotters.py b/autogalaxy/ellipse/plot/fit_ellipse_plotters.py deleted file mode 100644 index 0bb67a21b..000000000 --- a/autogalaxy/ellipse/plot/fit_ellipse_plotters.py +++ /dev/null @@ -1,234 +0,0 @@ -import numpy as np -from typing import List - -import math -from typing import List, Optional - -import autoarray as aa -from autoarray import plot as aplt - -from autogalaxy.ellipse.plot import fit_ellipse_plot_util - -from autogalaxy.ellipse.fit_ellipse import FitEllipse -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.util import error_util - - -class FitEllipsePlotter(Plotter): - def __init__( - self, - fit_list: List[FitEllipse], - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - super().__init__( - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - ) - - self.fit_list = fit_list - - def figures_2d( - self, - data: bool = False, - disable_data_contours: bool = False, - ellipse_residuals: bool = False, - for_subplot: bool = False, - suffix: str = "", - ): - """ - Plots the individual attributes of the plotter's `FitEllipse` object in 1D. - - The API is such that every plottable attribute of the `FitEllipse` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - data - Whether to make a 1D plot (via `imshow`) of the image data. - disable_data_contours - If `True`, the data is plotted without the black data contours over the top (but the white contours - showing the ellipses are still plotted). - """ - - filename_tag = "" - - if data: - - self.mat_plot_2d.contour = aplt.Contour( - manual_levels=np.sort( - [float(np.mean(fit.data_interp)) for fit in self.fit_list] - ) - ) - - if disable_data_contours: - contour_original = self.mat_plot_2d.contour - self.mat_plot_2d.contour = False - filename_tag = "_no_data_contours" - - ellipse_list = [] - - for fit in self.fit_list: - points = fit.points_from_major_axis_from() - - x = points[:, 1] - y = points[:, 0] * -1.0 # flip for plot - - ellipse_list.append(aa.Grid2DIrregular.from_yx_1d(y=y, x=x)) - - visuals_2d = self.visuals_2d + Visuals2D( - positions=ellipse_list, lines=ellipse_list - ) - - self.mat_plot_2d.plot_array( - array=self.fit_list[0].data, - visuals_2d=visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Ellipse Fit", - filename=f"ellipse_fit{filename_tag}", - ), - ) - - if disable_data_contours: - self.mat_plot_2d.contour = contour_original - - if ellipse_residuals: - - try: - colors = self.mat_plot_2d.grid_plot.config_dict["c"] - except KeyError: - colors = "k" - - fit_ellipse_plot_util.plot_ellipse_residuals( - array=self.fit_list[0].dataset.data.native, - fit_list=self.fit_list, - colors=colors, - output=self.mat_plot_2d.output, - for_subplot=for_subplot, - ) - - def subplot_fit_ellipse(self, disable_data_contours: bool = False): - """ - Standard subplot of the attributes of the plotter's `FitEllipse` object. - """ - - self.open_subplot_figure(number_subplots=2) - - self.mat_plot_2d.use_log10 = True - self.figures_2d(data=True, disable_data_contours=disable_data_contours) - self.figures_2d(ellipse_residuals=True, for_subplot=True) - - self.mat_plot_2d.output.subplot_to_figure(auto_filename="subplot_fit_ellipse") - self.close_subplot_figure() - - -class FitEllipsePDFPlotter(Plotter): - def __init__( - self, - fit_pdf_list: List[FitEllipse], - mat_plot_1d: MatPlot1D = MatPlot1D(), - visuals_1d: Visuals1D = Visuals1D(), - mat_plot_2d: MatPlot2D = MatPlot2D(), - visuals_2d: Visuals2D = Visuals2D(), - sigma: Optional[float] = 3.0, - ): - super().__init__( - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - ) - - self.fit_pdf_list = fit_pdf_list - self.sigma = sigma - self.low_limit = (1 - math.erf(sigma / math.sqrt(2))) / 2 - - def get_visuals_1d(self) -> Visuals1D: - return self.visuals_1d - - def get_visuals_2d(self): - return self.get_2d.via_mask_from(mask=self.fit_pdf_list[0][0].dataset.mask) - - def subplot_ellipse_errors(self): - """ - Plots the individual attributes of the plotter's `FitEllipse` object in 1D. - - The API is such that every plottable attribute of the `FitEllipse` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - data - Whether to make a 1D plot (via `imshow`) of the image data. - disable_data_contours - If `True`, the data is plotted without the black data contours over the top (but the white contours - showing the ellipses are still plotted). - """ - - contour_original = self.mat_plot_2d.contour - self.mat_plot_2d.contour = False - - ellipse_centre_list = [] - fit_ellipse_list = [[] for _ in range(len(self.fit_pdf_list[0]))] - - for fit_list in self.fit_pdf_list: - - ellipse_centre_list.append(fit_list[0].ellipse.centre) - - for i, fit in enumerate(fit_list): - - points = fit.points_from_major_axis_from() - - x = points[:, 1] - y = points[:, 0] * -1.0 # flip for plot - - fit_ellipse_list[i].append(aa.Grid2DIrregular.from_yx_1d(y=y, x=x)) - - self.open_subplot_figure(number_subplots=len(fit_ellipse_list)) - - for i in range(len(fit_ellipse_list)): - - median_ellipse, [lower_ellipse, upper_ellipse] = ( - error_util.ellipse_median_and_error_region_in_polar( - fit_ellipse_list[i], - low_limit=self.low_limit, - center=ellipse_centre_list[i], - ) - ) - - # Unpack points - y_lower, x_lower = lower_ellipse[:, 0], lower_ellipse[:, 1] - y_upper, x_upper = upper_ellipse[:, 0], upper_ellipse[:, 1] - - # Close the contours - x_fill = np.concatenate([x_lower, x_upper[::-1]]) - y_fill = np.concatenate([y_lower, y_upper[::-1]]) - - visuals_2d = self.get_visuals_2d() + Visuals2D( - lines=median_ellipse, fill_region=[y_fill, x_fill] - ) - - self.mat_plot_2d.plot_array( - array=self.fit_pdf_list[0][0].data, - visuals_2d=visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Ellipse Fit", - filename=f"subplot_ellipse_errors", - ), - ) - - self.mat_plot_2d.output.subplot_to_figure( - auto_filename="subplot_ellipse_errors" - ) - self.close_subplot_figure() - - self.mat_plot_2d.contour = contour_original diff --git a/autogalaxy/galaxy/plot/adapt_plots.py b/autogalaxy/galaxy/plot/adapt_plots.py new file mode 100644 index 000000000..4a672fa3b --- /dev/null +++ b/autogalaxy/galaxy/plot/adapt_plots.py @@ -0,0 +1,61 @@ +import matplotlib.pyplot as plt +import numpy as np +from typing import Dict + +import autoarray as aa + +from autogalaxy.galaxy.galaxy import Galaxy +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +def subplot_adapt_images( + adapt_galaxy_name_image_dict: Dict[Galaxy, aa.Array2D], + output_path=None, + output_format="png", + colormap="default", + use_log10=False, +): + """Create a subplot showing the adapt (model) image for each galaxy. + + Adapt images are per-galaxy model images produced during a previous + non-linear search. They are used to drive adaptive mesh and + regularisation schemes in subsequent searches. This function lays out + one panel per entry in *adapt_galaxy_name_image_dict*, arranged in rows + of up to three columns. + + If *adapt_galaxy_name_image_dict* is ``None`` the function returns + immediately without producing any output. + + Parameters + ---------- + adapt_galaxy_name_image_dict : dict[Galaxy, aa.Array2D] or None + Mapping from galaxy (used as a label) to its adapt image array. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + """ + if adapt_galaxy_name_image_dict is None: + return + + n = len(adapt_galaxy_name_image_dict) + cols = min(n, 3) + rows = (n + cols - 1) // cols + fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 7 * rows)) + axes_list = [axes] if n == 1 else list(np.array(axes).flatten()) + + for i, (_, galaxy_image) in enumerate(adapt_galaxy_name_image_dict.items()): + plot_array( + array=galaxy_image, + title="Galaxy Image", + colormap=colormap, + use_log10=use_log10, + ax=axes_list[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_adapt_images", output_format) diff --git a/autogalaxy/galaxy/plot/adapt_plotters.py b/autogalaxy/galaxy/plot/adapt_plotters.py deleted file mode 100644 index a2b07e2ef..000000000 --- a/autogalaxy/galaxy/plot/adapt_plotters.py +++ /dev/null @@ -1,79 +0,0 @@ -from typing import Dict, List - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.two_d import Visuals2D - - -class AdaptPlotter(Plotter): - def __init__( - self, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - super().__init__(mat_plot_2d=mat_plot_2d, visuals_2d=visuals_2d) - - def figure_model_image(self, model_image: aa.Array2D): - """ - Plots the adapt model image (e.g. sum of all individual galaxy model images). - - Parameters - ---------- - model_image - The adapt model image that is plotted. - """ - - self.mat_plot_2d.plot_array( - array=model_image, - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title="adapt image", filename="adapt_model_image" - ), - ) - - def figure_galaxy_image(self, galaxy_image: aa.Array2D): - """ - Plot the galaxy image of a galaxy. - - Parameters - ---------- - galaxy_image - The galaxy image that is plotted. - """ - self.mat_plot_2d.plot_array( - array=galaxy_image, - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title="galaxy Image", filename="adapt_galaxy_image" - ), - ) - - def subplot_adapt_images( - self, adapt_galaxy_name_image_dict: Dict[Galaxy, aa.Array2D] - ): - """ - Plots a subplot of the galaxy image of all galaxies. - - This uses the `adapt_galaxy_name_image_dict` which is a dictionary mapping each galaxy to its corresponding - to galaxy image. - - Parameters - ---------- - adapt_galaxy_name_image_dict - A dictionary mapping each galaxy to its corresponding to galaxy image. - """ - if adapt_galaxy_name_image_dict is None: - return - - self.open_subplot_figure(number_subplots=len(adapt_galaxy_name_image_dict)) - - for path, galaxy_image in adapt_galaxy_name_image_dict.items(): - self.figure_galaxy_image(galaxy_image=galaxy_image) - - self.mat_plot_2d.output.subplot_to_figure(auto_filename="subplot_adapt_images") - - self.close_subplot_figure() diff --git a/autogalaxy/galaxy/plot/galaxies_plots.py b/autogalaxy/galaxy/plot/galaxies_plots.py new file mode 100644 index 000000000..f25400136 --- /dev/null +++ b/autogalaxy/galaxy/plot/galaxies_plots.py @@ -0,0 +1,215 @@ +import matplotlib.pyplot as plt +import numpy as np + +import autoarray as aa + +from autogalaxy.galaxy.galaxies import Galaxies +from autogalaxy.plot.plot_utils import _to_lines, _to_positions, plot_array, plot_grid, _save_subplot, _critical_curves_from +from autogalaxy import exc + + +def _check_no_linear(galaxies): + """Raise if any galaxy in *galaxies* contains a linear light profile. + + Plotting functions cannot render + :class:`~autogalaxy.profiles.light.linear.LightProfileLinear` profiles + directly because their intensity is not a fixed parameter — it is solved + via a linear inversion. This guard ensures a clear error message is + produced rather than a silent wrong result. + + Parameters + ---------- + galaxies : sequence of Galaxy + The galaxies to inspect. + + Raises + ------ + exc.LinearLightProfileInPlotError + If at least one linear light profile is found. + """ + from autogalaxy.profiles.light.linear import LightProfileLinear + + if Galaxies(galaxies=galaxies).has(cls=LightProfileLinear): + raise exc.raise_linear_light_profile_in_plot(plotter_type="galaxies plot") + + +def _galaxies_critical_curves(galaxies, grid, tc=None, rc=None): + """Return the tangential and radial critical curves for a set of galaxies. + + Thin wrapper around :func:`~autogalaxy.plot.plot_utils._critical_curves_from` + that keeps the galaxies-plot API decoupled from the utility layer. + + Parameters + ---------- + galaxies : Galaxies + The galaxies acting as a combined lens. + grid : aa.type.Grid2DLike + The grid on which to evaluate the critical curves. + tc : list or None + Pre-computed tangential critical curves; ``None`` to trigger + computation. + rc : list or None + Pre-computed radial critical curves; ``None`` to trigger computation. + + Returns + ------- + tuple[list, list or None] + ``(tangential_critical_curves, radial_critical_curves)``. + """ + return _critical_curves_from(galaxies, grid, tc=tc, rc=rc) + + +def subplot_galaxies( + galaxies, + grid: aa.type.Grid1D2DLike, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, + light_profile_centres=None, + mass_profile_centres=None, + multiple_images=None, + tangential_critical_curves=None, + radial_critical_curves=None, + auto_filename="subplot_galaxies", +): + """Create a standard five-panel summary subplot for a collection of galaxies. + + The subplot shows: image, convergence, potential, deflections-y, and + deflections-x. Critical curves and various point overlays can be added + to all lensing panels automatically. + + Parameters + ---------- + galaxies : sequence of Galaxy + The galaxies to plot. Must not contain any + :class:`~autogalaxy.profiles.light.linear.LightProfileLinear` profiles. + grid : aa.type.Grid1D2DLike + The grid on which all quantities are evaluated. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + positions : array-like or None + Arbitrary point positions to overlay on all panels. + light_profile_centres : array-like or None + Light-profile centre coordinates to overlay. + mass_profile_centres : array-like or None + Mass-profile centre coordinates to overlay. + multiple_images : array-like or None + Multiple-image positions to overlay on all panels. + tangential_critical_curves : list or None + Pre-computed tangential critical curves. ``None`` triggers + automatic computation. + radial_critical_curves : list or None + Pre-computed radial critical curves. ``None`` triggers automatic + computation. + auto_filename : str + Output filename stem (default ``"subplot_galaxies"``). + """ + _check_no_linear(galaxies) + gs = Galaxies(galaxies=galaxies) + tc, rc = _galaxies_critical_curves( + gs, grid, tc=tangential_critical_curves, rc=radial_critical_curves + ) + lines = _to_lines(tc, rc) + pos = _to_positions(positions, light_profile_centres, mass_profile_centres, multiple_images) + pos_no_cc = _to_positions(positions, light_profile_centres, mass_profile_centres) + + def _defl_y(): + d = gs.deflections_yx_2d_from(grid=grid) + return aa.Array2D(values=d.slim[:, 0], mask=grid.mask) + + def _defl_x(): + d = gs.deflections_yx_2d_from(grid=grid) + return aa.Array2D(values=d.slim[:, 1], mask=grid.mask) + + panels = [ + ("image", gs.image_2d_from(grid=grid), "Image", pos_no_cc, None), + ("convergence", gs.convergence_2d_from(grid=grid), "Convergence", pos, lines), + ("potential", gs.potential_2d_from(grid=grid), "Potential", pos, lines), + ("deflections_y", _defl_y(), "Deflections Y", pos, lines), + ("deflections_x", _defl_x(), "Deflections X", pos, lines), + ] + + n = len(panels) + cols = min(n, 3) + rows = (n + cols - 1) // cols + fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 7 * rows)) + axes_flat = [axes] if n == 1 else list(np.array(axes).flatten()) + + for i, (_, array, title, p, l) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + positions=p, + lines=l, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, auto_filename, output_format) + + +def subplot_galaxy_images( + galaxies, + grid: aa.type.Grid1D2DLike, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + tangential_critical_curves=None, + radial_critical_curves=None, +): + """Create a subplot showing the individual image of each galaxy. + + One panel is drawn per galaxy. This is useful for inspecting which + galaxy contributes how much light when multiple galaxies are present in + a scene. + + Parameters + ---------- + galaxies : sequence of Galaxy + The galaxies whose images are to be plotted. Must not contain any + :class:`~autogalaxy.profiles.light.linear.LightProfileLinear` + profiles. + grid : aa.type.Grid1D2DLike + The grid on which each galaxy image is evaluated. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + tangential_critical_curves : list or None + Reserved for future overlay support (currently unused). + radial_critical_curves : list or None + Reserved for future overlay support (currently unused). + """ + _check_no_linear(galaxies) + gs = Galaxies(galaxies=galaxies) + + n = len(gs) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = [axes] if n == 1 else list(axes.flatten()) + + for i in range(n): + plot_array( + array=gs[i].image_2d_from(grid=grid), + title=f"Image Of Galaxies {i}", + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_galaxy_images", output_format) diff --git a/autogalaxy/galaxy/plot/galaxies_plotters.py b/autogalaxy/galaxy/plot/galaxies_plotters.py deleted file mode 100644 index e49a8e1a7..000000000 --- a/autogalaxy/galaxy/plot/galaxies_plotters.py +++ /dev/null @@ -1,321 +0,0 @@ -from typing import List, Optional - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D -from autogalaxy.plot.mass_plotter import MassPlotter -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.galaxy.galaxies import Galaxies -from autogalaxy.galaxy.plot.galaxy_plotters import GalaxyPlotter - -from autogalaxy import exc - - -class GalaxiesPlotter(Plotter): - def __init__( - self, - galaxies: List[Galaxy], - grid: aa.type.Grid1D2DLike, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of a list of galaxies using the matplotlib methods `plot()` and `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2D` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `MassProfile` and plotted via the visuals object. - - Parameters - ---------- - galaxies - The galaxies the plotter plots. - grid - The 2D (y,x) grid of coordinates used to evaluate the galaxies light and mass quantities that are plotted. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - - self.galaxies = Galaxies(galaxies=galaxies) - - from autogalaxy.profiles.light.linear import ( - LightProfileLinear, - ) - - if self.galaxies.has(cls=LightProfileLinear): - raise exc.raise_linear_light_profile_in_plot( - plotter_type=self.__class__.__name__, - ) - - super().__init__( - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - ) - - self.grid = grid - - self._mass_plotter = MassPlotter( - mass_obj=self.galaxies, - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - def galaxy_plotter_from(self, galaxy_index: int) -> GalaxyPlotter: - """ - Returns an `GalaxyPlotter` corresponding to a `Galaxy` in the `Tracer`. - - Returns - ------- - galaxy_index - The index of the galaxy in the `Tracer` used to make the `GalaxyPlotter`. - """ - - return GalaxyPlotter( - galaxy=self.galaxies[galaxy_index], - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self._mass_plotter.visuals_2d_with_critical_curves, - ) - - def figures_2d( - self, - image: bool = False, - convergence: bool = False, - potential: bool = False, - deflections_y: bool = False, - deflections_x: bool = False, - magnification: bool = False, - plane_image: bool = False, - plane_grid: bool = False, - zoom_to_brightest: bool = True, - title_suffix: str = "", - filename_suffix: str = "", - source_plane_title: bool = False, - ): - """ - Plots the individual attributes of the plotter's `Galaxies` object in 2D, which are computed via the plotter's 2D - grid object. - - The API is such that every plottable attribute of the `Galaxies` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image of the galaxies. - convergence - Whether to make a 2D plot (via `imshow`) of the convergence. - potential - Whether to make a 2D plot (via `imshow`) of the potential. - deflections_y - Whether to make a 2D plot (via `imshow`) of the y component of the deflection angles. - deflections_x - Whether to make a 2D plot (via `imshow`) of the x component of the deflection angles. - magnification - Whether to make a 2D plot (via `imshow`) of the magnification. - plane_image - Whether to make a 2D plot (via `imshow`) of the image of the plane in the soure-plane (e.g. its - unlensed light). - zoom_to_brightest - Whether to automatically zoom the plot to the brightest regions of the galaxies being plotted as - opposed to the full extent of the grid. - title_suffix - 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. - """ - if image: - self.mat_plot_2d.plot_array( - array=self.galaxies.image_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Image{title_suffix}", filename=f"image_2d{filename_suffix}" - ), - ) - - if plane_image: - if source_plane_title: - title = "Source Plane Image" - else: - title = f"Plane Image{title_suffix}" - - self.mat_plot_2d.plot_array( - array=self.galaxies.plane_image_2d_from( - grid=self.grid, zoom_to_brightest=zoom_to_brightest - ), - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - 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.visuals_2d, - auto_labels=aplt.AutoLabels( - title=title, - filename=f"plane_grid{filename_suffix}", - ), - ) - - self._mass_plotter.figures_2d( - convergence=convergence, - potential=potential, - deflections_y=deflections_y, - deflections_x=deflections_x, - magnification=magnification, - ) - - def galaxy_indexes_from(self, galaxy_index: Optional[int]) -> List[int]: - """ - Returns a list of all indexes of the galaxys in the fit, which is iterated over in figures that plot - individual figures of each galaxy. - - Parameters - ---------- - galaxy_index - A specific galaxy index which when input means that only a single galaxy index is returned. - - Returns - ------- - list - A list of galaxy indexes corresponding to galaxys in the galaxy. - """ - if galaxy_index is None: - return list(range(len(self.galaxies))) - return [galaxy_index] - - def figures_2d_of_galaxies( - self, image: bool = False, galaxy_index: Optional[int] = None - ): - """ - Plots galaxy images for each individual `Galaxy` in the plotter's `Galaxies` in 2D, which are computed via the - plotter's 2D grid object. - - The API is such that every plottable attribute of the `galaxy` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image of the galaxy in the soure-galaxy (e.g. its - unlensed light). - galaxy_index - If input, plots for only a single galaxy based on its index are created. - """ - galaxy_indexes = self.galaxy_indexes_from(galaxy_index=galaxy_index) - - for galaxy_index in galaxy_indexes: - galaxy_plotter = self.galaxy_plotter_from(galaxy_index=galaxy_index) - - if image: - galaxy_plotter.figures_2d( - image=True, - title_suffix=f" Of Galaxy {galaxy_index}", - filename_suffix=f"_of_galaxy_{galaxy_index}", - ) - - def subplot( - self, - image: bool = False, - convergence: bool = False, - potential: bool = False, - deflections_y: bool = False, - deflections_x: bool = False, - magnification: bool = False, - auto_filename: str = "subplot_galaxies", - ): - """ - Plots the individual attributes of the plotter's `Galaxies` object in 2D on a subplot, which are computed via the - plotter's 2D grid object. - - The API is such that every plottable attribute of the `Galaxies` object is an input parameter of type bool of - the function, which if switched to `True` means that it is included on the subplot. - - Parameters - ---------- - image - Whether or not to include a 2D plot (via `imshow`) of the image. - convergence - Whether or not to include a 2D plot (via `imshow`) of the convergence. - potential - Whether or not to include a 2D plot (via `imshow`) of the potential. - deflections_y - Whether or not to include a 2D plot (via `imshow`) of the y component of the deflection angles. - deflections_x - Whether or not to include a 2D plot (via `imshow`) of the x component of the deflection angles. - magnification - Whether or not to include a 2D plot (via `imshow`) of the magnification. - auto_filename - The default filename of the output subplot if written to hard-disk. - """ - self._subplot_custom_plot( - image=image, - convergence=convergence, - potential=potential, - deflections_y=deflections_y, - deflections_x=deflections_x, - magnification=magnification, - auto_labels=aplt.AutoLabels(filename=auto_filename), - ) - - def subplot_galaxies(self): - """ - Standard subplot of the attributes of the plotter's `Galaxies` object. - """ - return self.subplot( - image=True, - convergence=True, - potential=True, - deflections_y=True, - deflections_x=True, - ) - - def subplot_galaxy_images(self): - """ - Subplot of the image of every galaxy. - - For example, for a 2 galaxy `Galaxies`, this creates a subplot with 2 panels, one for each galaxy. - """ - number_subplots = len(self.galaxies) - - self.open_subplot_figure(number_subplots=number_subplots) - - for galaxy_index in range(0, len(self.galaxies)): - galaxy_plotter = self.galaxy_plotter_from(galaxy_index=galaxy_index) - galaxy_plotter.figures_2d( - image=True, title_suffix=f" Of Galaxies {galaxy_index}" - ) - - self.mat_plot_2d.output.subplot_to_figure( - auto_filename=f"subplot_galaxy_images" - ) - self.close_subplot_figure() diff --git a/autogalaxy/galaxy/plot/galaxy_plots.py b/autogalaxy/galaxy/plot/galaxy_plots.py new file mode 100644 index 000000000..d96248966 --- /dev/null +++ b/autogalaxy/galaxy/plot/galaxy_plots.py @@ -0,0 +1,145 @@ +from __future__ import annotations +import matplotlib.pyplot as plt +from typing import TYPE_CHECKING + +import autoarray as aa + +from autogalaxy.galaxy.galaxy import Galaxy +from autogalaxy.profiles.light.abstract import LightProfile +from autogalaxy.profiles.mass.abstract.abstract import MassProfile +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +def subplot_of_light_profiles( + galaxy: Galaxy, + grid: aa.type.Grid1D2DLike, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, +): + """Create a subplot showing the image of every light profile in a galaxy. + + One panel is drawn per :class:`~autogalaxy.profiles.light.abstract.LightProfile` + attached to *galaxy*. If the galaxy has no light profiles the function + returns immediately without producing any output. + + Parameters + ---------- + galaxy : Galaxy + The galaxy whose light profiles are to be plotted. + grid : aa.type.Grid1D2DLike + The grid on which each light profile image is evaluated. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + positions : array-like or None + Point positions to scatter-plot over each panel. + """ + light_profiles = galaxy.cls_list_from(cls=LightProfile) + if not light_profiles: + return + + n = len(light_profiles) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = [axes] if n == 1 else list(axes.flatten()) + + for i, lp in enumerate(light_profiles): + plot_array( + array=lp.image_2d_from(grid=grid), + title="Image", + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_image", output_format) + + +def subplot_of_mass_profiles( + galaxy: Galaxy, + grid: aa.type.Grid2DLike, + convergence: bool = False, + potential: bool = False, + deflections_y: bool = False, + deflections_x: bool = False, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, +): + """Create subplots showing lensing quantities for every mass profile in a galaxy. + + One figure is produced *per requested quantity*, each containing one panel + per :class:`~autogalaxy.profiles.mass.abstract.abstract.MassProfile` + attached to *galaxy*. Only the quantities whose corresponding flag is + ``True`` are plotted; if none are requested, or if the galaxy has no mass + profiles, the function returns without producing output. + + Parameters + ---------- + galaxy : Galaxy + The galaxy whose mass profiles are to be plotted. + grid : aa.type.Grid2DLike + The grid on which lensing quantities are evaluated. + convergence : bool + Plot the convergence map of each mass profile. + potential : bool + Plot the gravitational potential map of each mass profile. + deflections_y : bool + Plot the y-component of the deflection-angle map. + deflections_x : bool + Plot the x-component of the deflection-angle map. + output_path : str or None + Directory in which to save figures. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + """ + mass_profiles = galaxy.cls_list_from(cls=MassProfile) + if not mass_profiles: + return + + n = len(mass_profiles) + + def _deflections_y(mp): + deflections = mp.deflections_yx_2d_from(grid=grid) + return aa.Array2D(values=deflections.slim[:, 0], mask=grid.mask) + + def _deflections_x(mp): + deflections = mp.deflections_yx_2d_from(grid=grid) + return aa.Array2D(values=deflections.slim[:, 1], mask=grid.mask) + + for name, flag, array_fn, title in [ + ("convergence", convergence, lambda mp: mp.convergence_2d_from(grid=grid), "Convergence"), + ("potential", potential, lambda mp: mp.potential_2d_from(grid=grid), "Potential"), + ("deflections_y", deflections_y, _deflections_y, "Deflections Y"), + ("deflections_x", deflections_x, _deflections_x, "Deflections X"), + ]: + if not flag: + continue + + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = [axes] if n == 1 else list(axes.flatten()) + + for i, mp in enumerate(mass_profiles): + plot_array( + array=array_fn(mp), + title=title, + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, f"subplot_{name}", output_format) diff --git a/autogalaxy/galaxy/plot/galaxy_plotters.py b/autogalaxy/galaxy/plot/galaxy_plotters.py deleted file mode 100644 index d1a372831..000000000 --- a/autogalaxy/galaxy/plot/galaxy_plotters.py +++ /dev/null @@ -1,295 +0,0 @@ -from __future__ import annotations -import math -from typing import TYPE_CHECKING, List, Optional - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D -from autogalaxy.plot.mass_plotter import MassPlotter - -from autogalaxy.profiles.light.abstract import LightProfile -from autogalaxy.profiles.mass.abstract.abstract import MassProfile -from autogalaxy.galaxy.galaxy import Galaxy - -if TYPE_CHECKING: - from autogalaxy.profiles.plot.light_profile_plotters import LightProfilePlotter -from autogalaxy.profiles.plot.mass_profile_plotters import MassProfilePlotter - -from autogalaxy import exc - - -class GalaxyPlotter(Plotter): - def __init__( - self, - galaxy: Galaxy, - grid: aa.type.Grid1D2DLike, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of `Galaxy` objects using the matplotlib methods `plot()` and `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2D` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `MassProfile` and plotted via the visuals object. - - Parameters - ---------- - galaxy - The galaxy the plotter plots. - grid - The 2D (y,x) grid of coordinates used to evaluate the galaxy's light and mass quantities that are plotted. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - - from autogalaxy.profiles.light.linear import ( - LightProfileLinear, - ) - - if galaxy is not None: - if galaxy.has(cls=LightProfileLinear): - raise exc.raise_linear_light_profile_in_plot( - plotter_type=self.__class__.__name__, - ) - - super().__init__( - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - ) - - self.galaxy = galaxy - self.grid = grid - - self._mass_plotter = MassPlotter( - mass_obj=self.galaxy, - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - def light_profile_plotter_from( - self, light_profile: LightProfile, one_d_only: bool = False - ) -> LightProfilePlotter: - """ - Returns a `LightProfilePlotter` given an input light profile, which is typically used for plotting the - individual light profiles of the plotter's `Galaxy` (e.g. in the function `figures_1d_decomposed`). - - Parameters - ---------- - light_profile - The light profile which is used to create the `LightProfilePlotter`. - - Returns - ------- - LightProfilePlotter - An object that plots the light profiles, often used for plotting attributes of the galaxy. - """ - - from autogalaxy.profiles.plot.light_profile_plotters import LightProfilePlotter - - if not one_d_only: - - return LightProfilePlotter( - light_profile=light_profile, - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.visuals_1d - + Visuals1D().add_half_light_radius(light_obj=light_profile), - ) - - return LightProfilePlotter( - light_profile=light_profile, - grid=self.grid, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.visuals_1d - + Visuals1D().add_half_light_radius(light_obj=light_profile), - ) - - def mass_profile_plotter_from( - self, mass_profile: MassProfile, one_d_only: bool = False - ) -> MassProfilePlotter: - """ - Returns a `MassProfilePlotter` given an input mass profile, which is typically used for plotting the individual - mass profiles of the plotter's `Galaxy` (e.g. in the function `figures_1d_decomposed`). - - Parameters - ---------- - mass_profile - The mass profile which is used to create the `MassProfilePlotter`. - - Returns - ------- - MassProfilePlotter - An object that plots the mass profiles, often used for plotting attributes of the galaxy. - """ - - if not one_d_only: - return MassProfilePlotter( - mass_profile=mass_profile, - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self._mass_plotter.visuals_2d_with_critical_curves, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.visuals_1d - + Visuals1D().add_einstein_radius( - mass_obj=mass_profile, grid=self.grid - ), - ) - - return MassProfilePlotter( - mass_profile=mass_profile, - grid=self.grid, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.visuals_1d - + Visuals1D().add_einstein_radius(mass_obj=mass_profile, grid=self.grid), - ) - - def figures_2d( - self, - image: bool = False, - convergence: bool = False, - potential: bool = False, - deflections_y: bool = False, - deflections_x: bool = False, - magnification: bool = False, - title_suffix: str = "", - filename_suffix: str = "", - ): - """ - Plots the individual attributes of the plotter's `Galaxy` object in 2D, which are computed via the plotter's 2D - grid object. - - The API is such that every plottable attribute of the `Galaxy` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image. - convergence - Whether to make a 2D plot (via `imshow`) of the convergence. - potential - Whether to make a 2D plot (via `imshow`) of the potential. - deflections_y - Whether to make a 2D plot (via `imshow`) of the y component of the deflection angles. - deflections_x - Whether to make a 2D plot (via `imshow`) of the x component of the deflection angles. - magnification - Whether to make a 2D plot (via `imshow`) of the magnification. - """ - if image: - self.mat_plot_2d.plot_array( - array=self.galaxy.image_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Image{title_suffix}", filename=f"image_2d{filename_suffix}" - ), - ) - - self._mass_plotter.figures_2d( - convergence=convergence, - potential=potential, - deflections_y=deflections_y, - deflections_x=deflections_x, - magnification=magnification, - title_suffix=title_suffix, - filename_suffix=filename_suffix, - ) - - def subplot_of_light_profiles(self, image: bool = False): - """ - Output a subplot of attributes of every individual light profile in 1D of the `Galaxy` object. - - For example, a 1D plot showing how the image (e.g. luminosity) of each component varies radially outwards. - - If the plotter has a 1D grid object this is used to evaluate each quantity. If it has a 2D grid, a 1D grid is - computed from the light profile. This is performed by aligning a 1D grid with the major-axis of the light - profile in projection, uniformly computing 1D values based on the 2D grid's size and pixel-scale. - - Parameters - ---------- - image - Whether to make a 1D subplot (via `plot`) of the image. - """ - light_profile_plotters = [ - self.light_profile_plotter_from(light_profile) - for light_profile in self.galaxy.cls_list_from(cls=LightProfile) - ] - - if image: - self.subplot_of_plotters_figure( - plotter_list=light_profile_plotters, name="image" - ) - - def subplot_of_mass_profiles( - self, - convergence: bool = False, - potential: bool = False, - deflections_y: bool = False, - deflections_x: bool = False, - ): - """ - Output a subplot of attributes of every individual mass profile in 1D of the `Galaxy` object. - - For example, a 1D plot showing how the convergence of each component varies radially outwards. - - If the plotter has a 1D grid object this is used to evaluate each quantity. If it has a 2D grid, a 1D grid is - computed from the light profile. This is performed by aligning a 1D grid with the major-axis of the light - profile in projection, uniformly computing 1D values based on the 2D grid's size and pixel-scale. - - Parameters - ---------- - image - Whether to make a 1D subplot (via `plot`) of the image. - convergence - Whether to make a 1D plot (via `plot`) of the convergence. - potential - Whether to make a 1D plot (via `plot`) of the potential. - """ - mass_profile_plotters = [ - self.mass_profile_plotter_from(mass_profile) - for mass_profile in self.galaxy.cls_list_from(cls=MassProfile) - ] - - if convergence: - self.subplot_of_plotters_figure( - plotter_list=mass_profile_plotters, name="convergence" - ) - - if potential: - self.subplot_of_plotters_figure( - plotter_list=mass_profile_plotters, name="potential" - ) - - if deflections_y: - self.subplot_of_plotters_figure( - plotter_list=mass_profile_plotters, name="deflections_y" - ) - - if deflections_x: - self.subplot_of_plotters_figure( - plotter_list=mass_profile_plotters, name="deflections_x" - ) diff --git a/autogalaxy/gui/scribbler.py b/autogalaxy/gui/scribbler.py index 1d6cff883..06756a284 100644 --- a/autogalaxy/gui/scribbler.py +++ b/autogalaxy/gui/scribbler.py @@ -76,7 +76,8 @@ def __init__( plt.imshow(image, interpolation="none") else: norm = cmap.norm_from(array=image) - plt.imshow(image, cmap=cmap.config_dict["cmap"], norm=norm) + cmap_name = getattr(cmap, "cmap_name", None) or cmap.config_dict.get("cmap", "viridis") + plt.imshow(image, cmap=cmap_name, norm=norm) if mask_overlay is not None: grid = mask_overlay.derive_grid.edge diff --git a/autogalaxy/imaging/model/plotter_interface.py b/autogalaxy/imaging/model/plotter_interface.py index 4c38fea61..e2604a333 100644 --- a/autogalaxy/imaging/model/plotter_interface.py +++ b/autogalaxy/imaging/model/plotter_interface.py @@ -1,336 +1,163 @@ -from pathlib import Path -from typing import List - -from autoconf.fitsable import hdu_list_for_output_from - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.imaging.fit_imaging import FitImaging -from autogalaxy.imaging.plot.fit_imaging_plotters import FitImagingPlotter -from autogalaxy.analysis.plotter_interface import PlotterInterface - -from autogalaxy.analysis.plotter_interface import plot_setting - - -def fits_to_fits( - should_plot: bool, - image_path: Path, - fit: FitImaging, -): - """ - Output attributes of a `FitImaging` to .fits format. - - This function is separated on its own so that it can be called by `PyAutoLens` and therefore avoid repeating - large amounts of code for visualization. - - Parameters - ---------- - should_plot - The function which inspects the configuration files to determine if a .fits file should be output. - image_path - The path the .fits files are output and the name of the .fits files. - fit - The fit to output to a .fits file. - """ - - if should_plot("fits_fit"): - - image_list = [ - fit.model_data.native_for_fits, - fit.residual_map.native_for_fits, - fit.normalized_residual_map.native_for_fits, - fit.chi_squared_map.native_for_fits, - ] - - hdu_list = hdu_list_for_output_from( - values_list=[ - image_list[0].mask.astype("float"), - ] - + image_list, - ext_name_list=[ - "mask", - "model_data", - "residual_map", - "normalized_residual_map", - "chi_squared_map", - ], - header_dict=fit.mask.header_dict, - ) - - hdu_list.writeto(image_path / "fit.fits", overwrite=True) - - if should_plot("fits_galaxy_images"): - number_plots = len(fit.galaxy_image_dict.keys()) + 1 - - image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=[ - "mask", - ] - + [f"galaxy_{i}" for i in range(number_plots)], - header_dict=fit.mask.header_dict, - ) - - hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True) - - if should_plot("fits_model_galaxy_images"): - number_plots = len(fit.galaxy_model_image_dict.keys()) + 1 - - image_list = [ - image.native_for_fits for image in fit.galaxy_model_image_dict.values() - ] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=[ - "mask", - ] - + [f"galaxy_{i}" for i in range(number_plots)], - header_dict=fit.mask.header_dict, - ) - - hdu_list.writeto(image_path / "model_galaxy_images.fits", overwrite=True) - - -class PlotterInterfaceImaging(PlotterInterface): - def imaging(self, dataset: aa.Imaging): - """ - Output visualization of an `Imaging` dataset, typically before a model-fit is performed. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a subplot of the individual images of attributes of the dataset (e.g. the image, - noise map, PSF). - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `dataset` and `imaging` headers. - - Parameters - ---------- - dataset - The imaging dataset which is visualized. - """ - - def should_plot(name): - return plot_setting(section=["dataset", "imaging"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - dataset_plotter = aplt.ImagingPlotter( - dataset=dataset, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_dataset"): - dataset_plotter.subplot_dataset() - - if should_plot("fits_dataset"): - image_list = [ - dataset.data.native_for_fits, - dataset.noise_map.native_for_fits, - dataset.psf.kernel.native_for_fits, - dataset.grids.lp.over_sample_size.native_for_fits.astype("float"), - dataset.grids.pixelization.over_sample_size.native_for_fits.astype( - "float" - ), - ] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=[ - "mask", - "data", - "noise_map", - "psf", - "over_sample_size_lp", - "over_sample_size_pixelization", - ], - header_dict=dataset.mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) - - def fit_imaging(self, fit: FitImaging, quick_update: bool = False): - """ - Visualizes a `FitImaging` object, which fits an imaging dataset. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - points to the search's results folder and this function visualizes the maximum log likelihood `FitImaging` - inferred by the search so far. - - Visualization includes a subplot of individual images of attributes of the `FitImaging` (e.g. the model data, - residual map) and .fits files containing its attributes grouped together. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `fit` and `fit_imaging` headers. - - Parameters - ---------- - fit - The maximum log likelihood `FitImaging` of the non-linear search which is used to plot the fit. - """ - - def should_plot(name): - return plot_setting(section=["fit", "fit_imaging"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - fit_plotter = FitImagingPlotter( - fit=fit, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_fit") or quick_update: - fit_plotter.subplot_fit() - - if quick_update: - return - - if should_plot("subplot_of_galaxies"): - fit_plotter.subplot_of_galaxies() - - fits_to_fits( - should_plot=should_plot, - image_path=self.image_path, - fit=fit, - ) - - def imaging_combined(self, dataset_list: List[aa.Imaging]): - """ - Output visualization of all `Imaging` datasets in a summed combined analysis, typically before a model-fit - is performed. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a single subplot of individual images of attributes of each dataset (e.g. the image, - noise map, PSF), such that the full suite of multiple datasets can be viewed on the same figure. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `dataset` header. - - Parameters - ---------- - dataset - The list of imaging datasets which are visualized. - """ - - def should_plot(name): - return plot_setting(section=["dataset", "imaging"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - dataset_plotter_list = [ - aplt.ImagingPlotter( - dataset=dataset, - mat_plot_2d=mat_plot_2d, - ) - for dataset in dataset_list - ] - - subplot_shape = (len(dataset_list), 4) - - multi_plotter = aplt.MultiFigurePlotter( - plotter_list=dataset_plotter_list, subplot_shape=subplot_shape - ) - - if should_plot("subplot_dataset"): - multi_plotter.subplot_of_figures_multi( - func_name_list=["figures_2d"] * 4, - figure_name_list=["data", "noise_map", "signal_to_noise_map", "psf"], - filename_suffix="dataset_combined", - ) - - for plotter in multi_plotter.plotter_list: - plotter.mat_plot_2d.use_log10 = True - - multi_plotter.subplot_of_figures_multi( - func_name_list=["figures_2d"] * 4, - figure_name_list=["data", "noise_map", "signal_to_noise_map", "psf"], - filename_suffix="dataset_combined_log10", - ) - - def fit_imaging_combined(self, fit_list: List[FitImaging]): - """ - Output visualization of all `FitImaging` objects in a summed combined analysis, typically during or after a - model-fit is performed. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a single subplot of individual images of attributes of each fit (e.g. data, - normalized residual-map), such that the full suite of multiple datasets can be viewed on the same figure. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `fit` header. - - Parameters - ---------- - fit - The list of imaging fits which are visualized. - """ - - def should_plot(name): - return plot_setting(section=["fit", "fit_imaging"], name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - fit_plotter_list = [ - FitImagingPlotter( - fit=fit, - mat_plot_2d=mat_plot_2d, - ) - for fit in fit_list - ] - - subplot_shape = (len(fit_list), 5) - - multi_plotter = aplt.MultiFigurePlotter( - plotter_list=fit_plotter_list, subplot_shape=subplot_shape - ) - - if should_plot("subplot_fit"): - - def make_subplot_fit(filename_suffix): - multi_plotter.subplot_of_figures_multi( - func_name_list=["figures_2d"] * 4, - figure_name_list=[ - "data", - "signal_to_noise_map", - "model_image", - "normalized_residual_map", - ], - filename_suffix=filename_suffix, - number_subplots=len(fit_list) * 5, - close_subplot=False, - ) - - for plotter in multi_plotter.plotter_list: - plotter.mat_plot_2d.cmap.kwargs["vmin"] = -1.0 - plotter.mat_plot_2d.cmap.kwargs["vmax"] = 1.0 - - multi_plotter.subplot_of_figures_multi( - func_name_list=["figures_2d"], - figure_name_list=[ - "normalized_residual_map", - ], - filename_suffix=filename_suffix, - number_subplots=len(fit_list) * 5, - subplot_index_offset=4, - open_subplot=False, - ) - - for plotter in multi_plotter.plotter_list: - plotter.mat_plot_2d.cmap.kwargs["vmin"] = None - plotter.mat_plot_2d.cmap.kwargs["vmax"] = None - - make_subplot_fit(filename_suffix="fit_combined") - - for plotter in multi_plotter.plotter_list: - plotter.mat_plot_2d.use_log10 = True - - make_subplot_fit(filename_suffix="fit_combined_log10") +import matplotlib.pyplot as plt +from pathlib import Path +from typing import List + +from autoconf.fitsable import hdu_list_for_output_from + +import autoarray as aa +import autoarray.plot as aplt + +from autogalaxy.imaging.fit_imaging import FitImaging +from autogalaxy.imaging.plot import fit_imaging_plots +from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting +from autogalaxy.plot.plot_utils import _save_subplot, plot_array + + +def fits_to_fits(should_plot, image_path: Path, fit: FitImaging): + if should_plot("fits_fit"): + image_list = [ + fit.model_data.native_for_fits, + fit.residual_map.native_for_fits, + fit.normalized_residual_map.native_for_fits, + fit.chi_squared_map.native_for_fits, + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=[ + "mask", + "model_data", + "residual_map", + "normalized_residual_map", + "chi_squared_map", + ], + header_dict=fit.mask.header_dict, + ) + hdu_list.writeto(image_path / "fit.fits", overwrite=True) + + if should_plot("fits_galaxy_images"): + number_plots = len(fit.galaxy_image_dict.keys()) + 1 + image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()] + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + [f"galaxy_{i}" for i in range(number_plots)], + header_dict=fit.mask.header_dict, + ) + hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True) + + if should_plot("fits_model_galaxy_images"): + number_plots = len(fit.galaxy_model_image_dict.keys()) + 1 + image_list = [image.native_for_fits for image in fit.galaxy_model_image_dict.values()] + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + [f"galaxy_{i}" for i in range(number_plots)], + header_dict=fit.mask.header_dict, + ) + hdu_list.writeto(image_path / "model_galaxy_images.fits", overwrite=True) + + +class PlotterInterfaceImaging(PlotterInterface): + def imaging(self, dataset: aa.Imaging): + def should_plot(name): + return plot_setting(section=["dataset", "imaging"], name=name) + + if should_plot("subplot_dataset"): + panels = [ + (dataset.data, "Data"), + (dataset.noise_map, "Noise Map"), + (dataset.signal_to_noise_map, "Signal-To-Noise Map"), + ] + try: + panels.append((dataset.psf.kernel, "PSF")) + except Exception: + pass + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) if n > 1 else [axes] + for i, (array, title) in enumerate(panels): + plot_array(array, title, ax=axes_flat[i]) + plt.tight_layout() + _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + + if should_plot("fits_dataset"): + image_list = [ + dataset.data.native_for_fits, + dataset.noise_map.native_for_fits, + dataset.psf.kernel.native_for_fits, + dataset.grids.lp.over_sample_size.native_for_fits.astype("float"), + dataset.grids.pixelization.over_sample_size.native_for_fits.astype("float"), + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask", "data", "noise_map", "psf", + "over_sample_size_lp", "over_sample_size_pixelization"], + header_dict=dataset.mask.header_dict, + ) + hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) + + def fit_imaging(self, fit: FitImaging, quick_update: bool = False): + def should_plot(name): + return plot_setting(section=["fit", "fit_imaging"], name=name) + + if should_plot("subplot_fit") or quick_update: + fit_imaging_plots.subplot_fit( + fit=fit, + output_path=self.image_path, + output_format=self.fmt, + ) + + if quick_update: + return + + if should_plot("subplot_of_galaxies"): + galaxy_indices = list(range(len(fit.galaxies))) + for galaxy_index in galaxy_indices: + fit_imaging_plots.subplot_of_galaxy( + fit=fit, + galaxy_index=galaxy_index, + output_path=self.image_path, + output_format=self.fmt, + ) + + fits_to_fits(should_plot=should_plot, image_path=self.image_path, fit=fit) + + def imaging_combined(self, dataset_list: List[aa.Imaging]): + def should_plot(name): + return plot_setting(section=["dataset", "imaging"], name=name) + + if should_plot("subplot_dataset"): + n = len(dataset_list) + fig, axes = plt.subplots(n, 4, figsize=(28, 7 * n)) + if n == 1: + axes = [axes] + + for i, dataset in enumerate(dataset_list): + plot_array(dataset.data, "Data", ax=axes[i][0]) + plot_array(dataset.noise_map, "Noise Map", ax=axes[i][1]) + plot_array(dataset.signal_to_noise_map, "Signal-To-Noise Map", ax=axes[i][2]) + + plt.tight_layout() + _save_subplot(fig, self.image_path, "subplot_dataset_combined", self.fmt) + + def fit_imaging_combined(self, fit_list: List[FitImaging]): + def should_plot(name): + return plot_setting(section=["fit", "fit_imaging"], name=name) + + output = self.output_from() + + if should_plot("subplot_fit"): + n = len(fit_list) + fig, axes = plt.subplots(n, 5, figsize=(35, 7 * n)) + if n == 1: + axes = [axes] + + for i, fit in enumerate(fit_list): + plot_array(fit.data, "Data", ax=axes[i][0]) + plot_array(fit.signal_to_noise_map, "Signal-To-Noise Map", ax=axes[i][1]) + plot_array(fit.model_data, "Model Image", ax=axes[i][2]) + plot_array(fit.normalized_residual_map, "Normalized Residual Map", ax=axes[i][3]) + plot_array(fit.chi_squared_map, "Chi-Squared Map", ax=axes[i][4]) + + plt.tight_layout() + _save_subplot(fig, self.image_path, "subplot_fit_combined", self.fmt) diff --git a/autogalaxy/imaging/plot/fit_imaging_plots.py b/autogalaxy/imaging/plot/fit_imaging_plots.py new file mode 100644 index 000000000..cfa223663 --- /dev/null +++ b/autogalaxy/imaging/plot/fit_imaging_plots.py @@ -0,0 +1,128 @@ +import matplotlib.pyplot as plt + +import autoarray as aa + +from autogalaxy.imaging.fit_imaging import FitImaging +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +def subplot_fit( + fit: FitImaging, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, + residuals_symmetric_cmap: bool = True, +): + """Create a six-panel subplot summarising a :class:`~autogalaxy.imaging.fit_imaging.FitImaging`. + + The panels show, in order: data, signal-to-noise map, model image, + residual map, normalised residual map, and chi-squared map. + + Parameters + ---------- + fit : FitImaging + The completed imaging fit to visualise. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + positions : array-like or None + Point positions to scatter-plot over each panel. + residuals_symmetric_cmap : bool + Reserved for future symmetric-colormap support on residual panels + (currently unused). + """ + panels = [ + (fit.data, "Data"), + (fit.signal_to_noise_map, "Signal-To-Noise Map"), + (fit.model_data, "Model Image"), + (fit.residual_map, "Residual Map"), + (fit.normalized_residual_map, "Normalized Residual Map"), + (fit.chi_squared_map, "Chi-Squared Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + + for i, (array, title) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + positions=positions, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_fit", output_format) + + +def subplot_of_galaxy( + fit: FitImaging, + galaxy_index: int, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, + residuals_symmetric_cmap: bool = True, +): + """Create a three-panel subplot focused on a single galaxy contribution. + + Shows the observed data alongside the subtracted image and model image + for the galaxy at *galaxy_index* in the fitted galaxy list. This is + useful for inspecting the contribution of individual galaxies when + multiple galaxies are being fitted simultaneously. + + Parameters + ---------- + fit : FitImaging + The completed imaging fit to visualise. + galaxy_index : int + Index into ``fit.galaxies`` selecting which galaxy to highlight. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + positions : array-like or None + Point positions to scatter-plot over each panel. + residuals_symmetric_cmap : bool + Reserved for future symmetric-colormap support (currently unused). + """ + panels = [ + (fit.data, "Data"), + ( + fit.subtracted_images_of_galaxies_list[galaxy_index], + f"Subtracted Image of Galaxy {galaxy_index}", + ), + ( + fit.model_images_of_galaxies_list[galaxy_index], + f"Model Image of Galaxy {galaxy_index}", + ), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + + for i, (array, title) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, f"subplot_of_galaxy_{galaxy_index}", output_format) diff --git a/autogalaxy/imaging/plot/fit_imaging_plotters.py b/autogalaxy/imaging/plot/fit_imaging_plotters.py deleted file mode 100644 index 3f21b5a2f..000000000 --- a/autogalaxy/imaging/plot/fit_imaging_plotters.py +++ /dev/null @@ -1,227 +0,0 @@ -import numpy as np -from typing import List, Optional - -import autoarray as aa -import autoarray.plot as aplt - -from autoarray.fit.plot.fit_imaging_plotters import FitImagingPlotterMeta - -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.imaging.fit_imaging import FitImaging -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.two_d import Visuals2D - - -class FitImagingPlotter(Plotter): - def __init__( - self, - fit: FitImaging, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - residuals_symmetric_cmap: bool = True, - ): - """ - Plots the attributes of `FitImaging` objects using the matplotlib method `imshow()` and many other matplotlib - functions which customize the plot's appearance. - - The `mat_plot_2d` attribute wraps matplotlib function calls to make the figure. By default, the settings - passed to every matplotlib function called are those specified in the `config/visualize/mat_wrap/*.ini` files, - but a user can manually input values into `MatPlot2d` to customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals2D` object. Attributes may be extracted from - the `FitImaging` and plotted via the visuals object. - - Parameters - ---------- - fit - The fit to an imaging dataset the plotter plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make the plot. - visuals_2d - Contains visuals that can be overlaid on the plot. - residuals_symmetric_cmap - If true, the `residual_map` and `normalized_residual_map` are plotted with a symmetric color map such - that `abs(vmin) = abs(vmax)`. - """ - super().__init__(mat_plot_2d=mat_plot_2d, visuals_2d=visuals_2d) - - self.fit = fit - - self._fit_imaging_meta_plotter = FitImagingPlotterMeta( - fit=self.fit, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - residuals_symmetric_cmap=residuals_symmetric_cmap, - ) - - self.figures_2d = self._fit_imaging_meta_plotter.figures_2d - self.subplot = self._fit_imaging_meta_plotter.subplot - - @property - def inversion_plotter(self) -> aplt.InversionPlotter: - """ - Returns an `InversionPlotter` corresponding to the `Inversion` of the fit. - - Returns - ------- - InversionPlotter - An object that plots inversions which is used for plotting attributes of the inversion. - """ - return aplt.InversionPlotter( - inversion=self.fit.inversion, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - @property - def galaxies(self) -> List[Galaxy]: - return self.fit.galaxies_linear_light_profiles_to_light_profiles - - @property - def galaxy_indices(self) -> List[int]: - """ - Returns a list of all indexes of the galaxies in the fit, which is iterated over in figures that plot - individual figures of each galaxy. - - Parameters - ---------- - galaxy_index - A specific galaxy index such that only a single galaxy index is returned. - - Returns - ------- - list - A list of galaxy indexes corresponding to the galaxies. - """ - return list(range(len(self.fit.galaxies))) - - def figures_2d_of_galaxies( - self, - galaxy_index: Optional[int] = None, - subtracted_image: bool = False, - model_image: bool = False, - ): - """ - Plots images representing each individual `Galaxy` in the plotter's list of galaxies in 2D, which are - computed via the plotter's 2D grid object. - - These images subtract or omit the contribution of other galaxies, such that plots showing - each individual galaxy are made. - - The API is such that every plottable attribute of the `Galaxy` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - subtracted_image - Whether to make a 2D plot (via `imshow`) of the subtracted image of a galaxy, where this image is - the fit's `data` minus the model images of all other galaxies, thereby showing an individual galaxy in the - data. - model_image - Whether to make a 2D plot (via `imshow`) of the model image of a galaxy, where this image is the - model image of one galaxy, thereby showing how much it contributes to the overall model image. - galaxy_index - If input, plots for only a single galaxy based on its index are created. - """ - if galaxy_index is None: - galaxy_indices = self.galaxy_indices - else: - galaxy_indices = [galaxy_index] - - for galaxy_index in galaxy_indices: - if subtracted_image: - self.mat_plot_2d.cmap.kwargs["vmin"] = np.max( - self.fit.model_images_of_galaxies_list[galaxy_index] - ) - self.mat_plot_2d.cmap.kwargs["vmin"] = np.min( - self.fit.model_images_of_galaxies_list[galaxy_index] - ) - - self.mat_plot_2d.plot_array( - array=self.fit.subtracted_images_of_galaxies_list[galaxy_index], - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Subtracted Image of Galaxy {galaxy_index}", - filename=f"subtracted_image_of_galaxy_{galaxy_index}", - ), - ) - - if model_image: - self.mat_plot_2d.plot_array( - array=self.fit.model_images_of_galaxies_list[galaxy_index], - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels( - title=f"Model Image of Galaxy {galaxy_index}", - filename=f"model_image_of_galaxy_{galaxy_index}", - ), - ) - - def subplot_fit(self): - """ - Standard subplot of the attributes of the plotter's `FitImaging` object. - """ - - self.open_subplot_figure(number_subplots=6) - - self.figures_2d(data=True) - - self.figures_2d(signal_to_noise_map=True) - self.figures_2d(model_image=True) - self.figures_2d(normalized_residual_map=True) - - self.mat_plot_2d.cmap.kwargs["vmin"] = -1.0 - self.mat_plot_2d.cmap.kwargs["vmax"] = 1.0 - - self.set_title(label=r"Normalized Residual Map $1\sigma$") - self.figures_2d(normalized_residual_map=True) - self.set_title(label=None) - - self.mat_plot_2d.cmap.kwargs.pop("vmin") - self.mat_plot_2d.cmap.kwargs.pop("vmax") - - self.figures_2d(chi_squared_map=True) - - self.mat_plot_2d.output.subplot_to_figure(auto_filename="subplot_fit") - self.close_subplot_figure() - - def subplot_of_galaxies(self, galaxy_index: Optional[int] = None): - """ - Plots images representing each individual `Galaxy` in the plotter's list of galaxies in 2D on a subplot, - which are computed via the plotter's 2D grid object. - - These images subtract or omit the contribution of other galaxies, such that plots showing each individual - galaxy are made. - - The subplot plots the subtracted image and model image of each galaxy, where are described in the - `figures_2d_of_galaxies` function. - - Parameters - ---------- - galaxy_index - If input, plots for only a single galaxy based on its index are created. - """ - - if galaxy_index is None: - galaxy_indices = self.galaxy_indices - else: - galaxy_indices = [galaxy_index] - - for galaxy_index in galaxy_indices: - self.open_subplot_figure(number_subplots=4) - - self.figures_2d(data=True) - self.figures_2d_of_galaxies( - galaxy_index=galaxy_index, subtracted_image=True - ) - self.figures_2d_of_galaxies(galaxy_index=galaxy_index, model_image=True) - - if self.galaxies.has(cls=aa.Pixelization): - self.inversion_plotter.figures_2d_of_pixelization( - pixelization_index=0, reconstruction=True - ) - - self.mat_plot_2d.output.subplot_to_figure( - auto_filename=f"subplot_of_galaxy_{galaxy_index}" - ) - self.close_subplot_figure() diff --git a/autogalaxy/interferometer/model/plotter_interface.py b/autogalaxy/interferometer/model/plotter_interface.py index afefab74e..f62a2414d 100644 --- a/autogalaxy/interferometer/model/plotter_interface.py +++ b/autogalaxy/interferometer/model/plotter_interface.py @@ -1,181 +1,132 @@ -from pathlib import Path - -from autoconf.fitsable import hdu_list_for_output_from - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.interferometer.fit_interferometer import FitInterferometer -from autogalaxy.interferometer.plot.fit_interferometer_plotters import ( - FitInterferometerPlotter, -) -from autogalaxy.analysis.plotter_interface import PlotterInterface - -from autogalaxy.analysis.plotter_interface import plot_setting - - -def fits_to_fits( - should_plot: bool, - image_path: Path, - fit: FitInterferometer, -): - """ - Output attributes of a `FitInterferometer` to .fits format. - - This function is separated on its own so that it can be called by `PyAutoLens` and therefore avoid repeating - large amounts of code for visualization. - - Parameters - ---------- - should_plot - The function which inspects the configuration files to determine if a .fits file should be output. - image_path - The path the .fits files are output and the name of the .fits files. - fit - The fit to output to a .fits file. - """ - - if should_plot("fits_galaxy_images"): - - image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=["mask"] - + [f"galaxy_{i}" for i in range(len(fit.galaxy_image_dict.values()))], - header_dict=fit.dataset.real_space_mask.header_dict, - ) - - hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True) - - if should_plot("fits_dirty_images"): - - image_list = [ - fit.dirty_image.native_for_fits, - fit.dirty_noise_map.native_for_fits, - fit.dirty_model_image.native_for_fits, - fit.dirty_residual_map.native_for_fits, - fit.dirty_normalized_residual_map.native_for_fits, - fit.dirty_chi_squared_map.native_for_fits, - ] - - hdu_list = hdu_list_for_output_from( - values_list=[image_list[0].mask.astype("float")] + image_list, - ext_name_list=["mask"] - + [ - "dirty_image", - "dirty_noise_map", - "dirty_model_image", - "dirty_residual_map", - "dirty_normalized_residual_map", - "dirty_chi_squared_map", - ], - header_dict=fit.dataset.real_space_mask.header_dict, - ) - - hdu_list.writeto(image_path / "fit_dirty_images.fits", overwrite=True) - - -class PlotterInterfaceInterferometer(PlotterInterface): - def interferometer(self, dataset: aa.Interferometer): - """ - Visualizes an `Interferometer` dataset object. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a subplot of individual images of attributes of the dataset (e.g. the visibilities, - noise map, uv-wavelengths). - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `dataset` and `interferometer` headers. - - Parameters - ---------- - dataset - The interferometer dataset whose attributes are visualized. - """ - - def should_plot(name): - return plot_setting(section=["dataset", "interferometer"], name=name) - - mat_plot_1d = self.mat_plot_1d_from() - mat_plot_2d = self.mat_plot_2d_from() - - dataset_plotter = aplt.InterferometerPlotter( - dataset=dataset, - mat_plot_1d=mat_plot_1d, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_dataset"): - dataset_plotter.subplot_dataset() - - if should_plot("fits_dataset"): - - hdu_list = hdu_list_for_output_from( - values_list=[ - dataset.real_space_mask.astype("float"), - dataset.data.in_array, - dataset.noise_map.in_array, - dataset.uv_wavelengths, - ], - ext_name_list=["mask", "data", "noise_map", "uv_wavelengths"], - header_dict=dataset.real_space_mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) - - def fit_interferometer( - self, - fit: FitInterferometer, - quick_update: bool = False, - ): - """ - Visualizes a `FitInterferometer` object, which fits an interferometer dataset. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - points to the search's results folder and this function visualizes the maximum log likelihood `FitInterferometer` - inferred by the search so far. - - Visualization includes a subplot of individual images of attributes of the `FitInterferometer` (e.g. the model - data, residual map) and .fits files containing its attributes grouped together. - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `fit` and `fit_interferometer` headers. - - Parameters - ---------- - fit - The maximum log likelihood `FitInterferometer` of the non-linear search which is used to plot the fit. - """ - - def should_plot(name): - return plot_setting(section=["fit", "fit_interferometer"], name=name) - - mat_plot_1d = self.mat_plot_1d_from() - mat_plot_2d = self.mat_plot_2d_from() - - fit_plotter = FitInterferometerPlotter( - fit=fit, - mat_plot_1d=mat_plot_1d, - mat_plot_2d=mat_plot_2d, - ) - - if should_plot("subplot_fit") or quick_update: - fit_plotter.subplot_fit() - - if should_plot("subplot_fit_dirty_images") or quick_update: - fit_plotter.subplot_fit_dirty_images() - - if quick_update: - return - - if should_plot("subplot_fit_real_space"): - fit_plotter.subplot_fit_real_space() - - fits_to_fits( - should_plot=should_plot, - image_path=self.image_path, - fit=fit, - ) +from pathlib import Path + +from autoconf.fitsable import hdu_list_for_output_from + +import autoarray as aa +import autoarray.plot as aplt + +from autogalaxy.interferometer.fit_interferometer import FitInterferometer +from autogalaxy.interferometer.plot import fit_interferometer_plots +from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting + + +def fits_to_fits( + should_plot: bool, + image_path: Path, + fit: FitInterferometer, +): + if should_plot("fits_galaxy_images"): + + image_list = [image.native_for_fits for image in fit.galaxy_image_dict.values()] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + + [f"galaxy_{i}" for i in range(len(fit.galaxy_image_dict.values()))], + header_dict=fit.dataset.real_space_mask.header_dict, + ) + + hdu_list.writeto(image_path / "galaxy_images.fits", overwrite=True) + + if should_plot("fits_dirty_images"): + + image_list = [ + fit.dirty_image.native_for_fits, + fit.dirty_noise_map.native_for_fits, + fit.dirty_model_image.native_for_fits, + fit.dirty_residual_map.native_for_fits, + fit.dirty_normalized_residual_map.native_for_fits, + fit.dirty_chi_squared_map.native_for_fits, + ] + + hdu_list = hdu_list_for_output_from( + values_list=[image_list[0].mask.astype("float")] + image_list, + ext_name_list=["mask"] + + [ + "dirty_image", + "dirty_noise_map", + "dirty_model_image", + "dirty_residual_map", + "dirty_normalized_residual_map", + "dirty_chi_squared_map", + ], + header_dict=fit.dataset.real_space_mask.header_dict, + ) + + hdu_list.writeto(image_path / "fit_dirty_images.fits", overwrite=True) + + +class PlotterInterfaceInterferometer(PlotterInterface): + def interferometer(self, dataset: aa.Interferometer): + def should_plot(name): + return plot_setting(section=["dataset", "interferometer"], name=name) + + if should_plot("subplot_dataset"): + from autogalaxy.plot.plot_utils import plot_array, _save_subplot + import matplotlib.pyplot as plt + + panels = [ + (dataset.dirty_image, "Dirty Image"), + (dataset.dirty_noise_map, "Dirty Noise Map"), + (dataset.dirty_signal_to_noise_map, "Dirty Signal-To-Noise Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + for i, (array, title) in enumerate(panels): + plot_array(array, title, ax=axes_flat[i]) + plt.tight_layout() + _save_subplot(fig, self.image_path, "subplot_dataset", self.fmt) + + if should_plot("fits_dataset"): + + hdu_list = hdu_list_for_output_from( + values_list=[ + dataset.real_space_mask.astype("float"), + dataset.data.in_array, + dataset.noise_map.in_array, + dataset.uv_wavelengths, + ], + ext_name_list=["mask", "data", "noise_map", "uv_wavelengths"], + header_dict=dataset.real_space_mask.header_dict, + ) + + hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) + + def fit_interferometer( + self, + fit: FitInterferometer, + quick_update: bool = False, + ): + def should_plot(name): + return plot_setting(section=["fit", "fit_interferometer"], name=name) + + if should_plot("subplot_fit") or quick_update: + fit_interferometer_plots.subplot_fit( + fit=fit, + output_path=self.image_path, + output_format=self.fmt, + ) + + if should_plot("subplot_fit_dirty_images") or quick_update: + fit_interferometer_plots.subplot_fit_dirty_images( + fit=fit, + output_path=self.image_path, + output_format=self.fmt, + ) + + if quick_update: + return + + if should_plot("subplot_fit_real_space"): + fit_interferometer_plots.subplot_fit_real_space( + fit=fit, + output_path=self.image_path, + output_format=self.fmt, + ) + + fits_to_fits( + should_plot=should_plot, + image_path=self.image_path, + fit=fit, + ) diff --git a/autogalaxy/interferometer/plot/fit_interferometer_plots.py b/autogalaxy/interferometer/plot/fit_interferometer_plots.py new file mode 100644 index 000000000..c85c9e3aa --- /dev/null +++ b/autogalaxy/interferometer/plot/fit_interferometer_plots.py @@ -0,0 +1,175 @@ +import matplotlib.pyplot as plt +import numpy as np + +import autoarray as aa +from autoarray.plot import plot_visibilities_1d + +from autogalaxy.interferometer.fit_interferometer import FitInterferometer +from autogalaxy.galaxy.plot import galaxies_plots +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +def subplot_fit( + fit: FitInterferometer, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + residuals_symmetric_cmap: bool = True, +): + """Create a three-panel subplot summarising a :class:`~autogalaxy.interferometer.fit_interferometer.FitInterferometer`. + + The panels show the visibility-space residual map, normalised residual + map, and chi-squared map, each rendered as 1-D visibility plots via + ``autoarray.plot.plot_visibilities_1d``. + + Parameters + ---------- + fit : FitInterferometer + The completed interferometer fit to visualise. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"`` (passed through but not + used by the 1-D visibility renderer). + use_log10 : bool + Reserved for future log-stretch support (currently unused). + residuals_symmetric_cmap : bool + Reserved for future symmetric-colormap support (currently unused). + """ + panels = [ + (fit.residual_map, "Residual Map"), + (fit.normalized_residual_map, "Normalized Residual Map"), + (fit.chi_squared_map, "Chi-Squared Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + + for i, (vis, title) in enumerate(panels): + plot_visibilities_1d(vis, axes_flat[i], title) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_fit", output_format) + + +def subplot_fit_dirty_images( + fit: FitInterferometer, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + residuals_symmetric_cmap: bool = True, +): + """Create a six-panel subplot of dirty-image diagnostics for an interferometer fit. + + Dirty images are the real-space counterparts of the visibility-space data, + obtained via an inverse Fourier transform. The panels show: dirty image, + dirty signal-to-noise map, dirty model image, dirty residual map, dirty + normalised residual map, and dirty chi-squared map. + + Parameters + ---------- + fit : FitInterferometer + The completed interferometer fit to visualise. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + residuals_symmetric_cmap : bool + Reserved for future symmetric-colormap support (currently unused). + """ + panels = [ + (fit.dirty_image, "Dirty Image"), + (fit.dirty_signal_to_noise_map, "Dirty Signal-To-Noise Map"), + (fit.dirty_model_image, "Dirty Model Image"), + (fit.dirty_residual_map, "Dirty Residual Map"), + (fit.dirty_normalized_residual_map, "Dirty Normalized Residual Map"), + (fit.dirty_chi_squared_map, "Dirty Chi-Squared Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + + for i, (array, title) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_fit_dirty_images", output_format) + + +def subplot_fit_real_space( + fit: FitInterferometer, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, +): + """Create a real-space summary subplot for an interferometer fit. + + The exact panels depend on whether the fit includes a pixelization: + + - **No pixelization**: delegates to + :func:`~autogalaxy.galaxy.plot.galaxies_plots.subplot_galaxies` which + shows image, convergence, potential, and deflections on the light-profile + grid. + - **With pixelization**: shows three dirty-image panels (dirty image, dirty + model image, dirty residual map), which are the best real-space + representation available when a pixelized source is used. + + Parameters + ---------- + fit : FitInterferometer + The completed interferometer fit to visualise. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + """ + galaxy_list = fit.galaxies_linear_light_profiles_to_light_profiles + + if not galaxy_list.has(cls=aa.Pixelization): + galaxies_plots.subplot_galaxies( + galaxies=galaxy_list, + grid=fit.grids.lp, + output_path=output_path, + output_format=output_format, + colormap=colormap, + use_log10=use_log10, + auto_filename="subplot_fit_real_space", + ) + else: + panels = [ + (fit.dirty_image, "Dirty Image"), + (fit.dirty_model_image, "Dirty Model Image"), + (fit.dirty_residual_map, "Dirty Residual Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + for i, (array, title) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + ax=axes_flat[i], + ) + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_fit_real_space", output_format) diff --git a/autogalaxy/interferometer/plot/fit_interferometer_plotters.py b/autogalaxy/interferometer/plot/fit_interferometer_plotters.py deleted file mode 100644 index de764522b..000000000 --- a/autogalaxy/interferometer/plot/fit_interferometer_plotters.py +++ /dev/null @@ -1,192 +0,0 @@ -from typing import List - -import autoarray as aa -import autoarray.plot as aplt - -from autoarray.fit.plot.fit_interferometer_plotters import FitInterferometerPlotterMeta - -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.interferometer.fit_interferometer import FitInterferometer -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.galaxy.plot.galaxies_plotters import GalaxiesPlotter - - -class FitInterferometerPlotter(Plotter): - def __init__( - self, - fit: FitInterferometer, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - residuals_symmetric_cmap: bool = True, - ): - """ - Plots the attributes of `FitInterferometer` objects using the matplotlib method `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2d` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `FitInterferometer` and plotted via the visuals object. - - Parameters - ---------- - fit - The fit to an interferometer dataset the plotter plots. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - residuals_symmetric_cmap - If true, the `residual_map` and `normalized_residual_map` are plotted with a symmetric color map such - that `abs(vmin) = abs(vmax)`. - """ - super().__init__( - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - ) - - self.fit = fit - - self._fit_interferometer_meta_plotter = FitInterferometerPlotterMeta( - fit=self.fit, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.visuals_1d, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - residuals_symmetric_cmap=residuals_symmetric_cmap, - ) - - self.figures_2d = self._fit_interferometer_meta_plotter.figures_2d - self.subplot = self._fit_interferometer_meta_plotter.subplot - # self.subplot_fit = self._fit_interferometer_meta_plotter.subplot_fit - self.subplot_fit_dirty_images = ( - self._fit_interferometer_meta_plotter.subplot_fit_dirty_images - ) - - @property - def galaxies(self) -> List[Galaxy]: - return self.fit.galaxies_linear_light_profiles_to_light_profiles - - def galaxies_plotter_from(self, galaxies: List[Galaxy]) -> GalaxiesPlotter: - """ - Returns a `GalaxiesPlotter` corresponding to an input galaxies list. - - Returns - ------- - galaxies - The galaxies used to make the `GalaxiesPlotter`. - """ - return GalaxiesPlotter( - galaxies=galaxies, - grid=self.fit.grids.lp, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - @property - def inversion_plotter(self) -> aplt.InversionPlotter: - """ - Returns an `InversionPlotter` corresponding to the `Inversion` of the fit. - - Returns - ------- - InversionPlotter - An object that plots inversions which is used for plotting attributes of the inversion. - """ - return aplt.InversionPlotter( - inversion=self.fit.inversion, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - def subplot_fit(self): - """ - Standard subplot of the attributes of the plotter's `FitImaging` object. - """ - - self.open_subplot_figure(number_subplots=9) - - self.figures_2d(amplitudes_vs_uv_distances=True) - - self.mat_plot_1d.subplot_index = 2 - self.mat_plot_2d.subplot_index = 2 - - self.figures_2d(dirty_image=True) - self.figures_2d(dirty_signal_to_noise_map=True) - - self.mat_plot_1d.subplot_index = 4 - self.mat_plot_2d.subplot_index = 4 - - self.figures_2d(dirty_model_image=True) - - self.mat_plot_1d.subplot_index = 5 - self.mat_plot_2d.subplot_index = 5 - - self.figures_2d(normalized_residual_map_real=True) - self.figures_2d(normalized_residual_map_imag=True) - - self.mat_plot_1d.subplot_index = 7 - self.mat_plot_2d.subplot_index = 7 - - self.figures_2d(dirty_normalized_residual_map=True) - - self.mat_plot_2d.cmap.kwargs["vmin"] = -1.0 - self.mat_plot_2d.cmap.kwargs["vmax"] = 1.0 - - self.set_title(label=r"Normalized Residual Map $1\sigma$") - self.figures_2d(dirty_normalized_residual_map=True) - self.set_title(label=None) - - self.mat_plot_2d.cmap.kwargs.pop("vmin") - self.mat_plot_2d.cmap.kwargs.pop("vmax") - - self.figures_2d(dirty_chi_squared_map=True) - - self.mat_plot_2d.output.subplot_to_figure(auto_filename="subplot_fit") - self.close_subplot_figure() - - def subplot_fit_real_space(self): - """ - Standard subplot of the real-space attributes of the plotter's `FitInterferometer` object. - - Depending on whether `LightProfile`'s or an `Inversion` are used to represent galaxies, different - methods are called to create these real-space images. - """ - if not self.galaxies.has(cls=aa.Pixelization): - galaxies_plotter = self.galaxies_plotter_from(galaxies=self.galaxies) - - galaxies_plotter.subplot(image=True, auto_filename="subplot_fit_real_space") - - elif self.galaxies.has(cls=aa.Pixelization): - self.open_subplot_figure(number_subplots=6) - - mapper_index = 0 - - self.inversion_plotter.figures_2d_of_pixelization( - pixelization_index=mapper_index, reconstructed_operated_data=True - ) - self.inversion_plotter.figures_2d_of_pixelization( - pixelization_index=mapper_index, reconstruction=True - ) - - self.mat_plot_2d.output.subplot_to_figure( - auto_filename=f"subplot_fit_real_space" - ) - - self.close_subplot_figure() diff --git a/autogalaxy/plot/__init__.py b/autogalaxy/plot/__init__.py index e7d1197b3..7d94d7c62 100644 --- a/autogalaxy/plot/__init__.py +++ b/autogalaxy/plot/__init__.py @@ -1,89 +1,37 @@ -from autofit.non_linear.plot.nest_plotters import NestPlotter -from autofit.non_linear.plot.mcmc_plotters import MCMCPlotter -from autofit.non_linear.plot.mle_plotters import MLEPlotter - -from autoarray.plot.wrap.base import ( - Units, - Figure, - Axis, - Cmap, - Colorbar, - ColorbarTickParams, - TickParams, - YTicks, - XTicks, - Title, - YLabel, - XLabel, - Text, - Annotate, - Legend, - Output, -) -from autoarray.plot.wrap.one_d import YXPlot, FillBetween -from autoarray.plot.wrap.two_d import ( - ArrayOverlay, - Contour, - GridScatter, - GridPlot, - VectorYXQuiver, - PatchOverlay, - DelaunayDrawer, - OriginScatter, - MaskScatter, - BorderScatter, - PositionsScatter, - IndexScatter, - MeshGridScatter, - ParallelOverscanPlot, - SerialPrescanPlot, - SerialOverscanPlot, -) - -from autoarray.structures.plot.structure_plotters import Array2DPlotter -from autoarray.structures.plot.structure_plotters import Grid2DPlotter -from autoarray.structures.plot.structure_plotters import YX1DPlotter -from autoarray.structures.plot.structure_plotters import YX1DPlotter as Array1DPlotter -from autoarray.inversion.plot.mapper_plotters import MapperPlotter -from autoarray.inversion.plot.inversion_plotters import InversionPlotter -from autoarray.dataset.plot.imaging_plotters import ImagingPlotter -from autoarray.dataset.plot.interferometer_plotters import InterferometerPlotter - -from autoarray.plot.multi_plotters import MultiFigurePlotter -from autoarray.plot.multi_plotters import MultiYX1DPlotter - -from autoarray.plot.auto_labels import AutoLabels - -from autogalaxy.plot.wrap import ( - HalfLightRadiusAXVLine, - EinsteinRadiusAXVLine, - ModelFluxesYXScatter, - LightProfileCentresScatter, - MassProfileCentresScatter, - TangentialCriticalCurvesPlot, - RadialCriticalCurvesPlot, - TangentialCausticsPlot, - RadialCausticsPlot, - MultipleImagesScatter, -) - - -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.profiles.plot.light_profile_plotters import LightProfilePlotter -from autogalaxy.profiles.plot.basis_plotters import BasisPlotter -from autogalaxy.profiles.plot.mass_profile_plotters import MassProfilePlotter -from autogalaxy.galaxy.plot.galaxy_plotters import GalaxyPlotter -from autogalaxy.galaxy.plot.galaxies_plotters import GalaxiesPlotter -from autogalaxy.quantity.plot.fit_quantity_plotters import FitQuantityPlotter -from autogalaxy.imaging.plot.fit_imaging_plotters import FitImagingPlotter -from autogalaxy.interferometer.plot.fit_interferometer_plotters import ( - FitInterferometerPlotter, -) -from autogalaxy.galaxy.plot.galaxies_plotters import GalaxiesPlotter -from autogalaxy.galaxy.plot.adapt_plotters import AdaptPlotter -from autogalaxy.ellipse.plot.fit_ellipse_plotters import FitEllipsePlotter -from autogalaxy.ellipse.plot.fit_ellipse_plotters import FitEllipsePDFPlotter +from autogalaxy.plot.plot_utils import plot_array, plot_grid + +from autogalaxy.profiles.plot.basis_plots import subplot_image as subplot_basis_image + +from autogalaxy.galaxy.plot.galaxy_plots import ( + subplot_of_light_profiles as subplot_galaxy_light_profiles, + subplot_of_mass_profiles as subplot_galaxy_mass_profiles, +) + +from autogalaxy.galaxy.plot.galaxies_plots import ( + subplot_galaxies, + subplot_galaxy_images, +) + +from autogalaxy.galaxy.plot.adapt_plots import ( + subplot_adapt_images, +) + +from autogalaxy.imaging.plot.fit_imaging_plots import ( + subplot_fit as subplot_fit_imaging, + subplot_of_galaxy as subplot_fit_imaging_of_galaxy, +) + +from autogalaxy.interferometer.plot.fit_interferometer_plots import ( + subplot_fit as subplot_fit_interferometer, + subplot_fit_dirty_images, + subplot_fit_real_space, +) + +from autogalaxy.quantity.plot.fit_quantity_plots import ( + subplot_fit as subplot_fit_quantity, +) + +from autogalaxy.ellipse.plot.fit_ellipse_plots import ( + subplot_fit_ellipse, + subplot_ellipse_errors, +) diff --git a/autogalaxy/plot/abstract_plotters.py b/autogalaxy/plot/abstract_plotters.py deleted file mode 100644 index 3acd36679..000000000 --- a/autogalaxy/plot/abstract_plotters.py +++ /dev/null @@ -1,34 +0,0 @@ -from autoarray.plot.wrap.base.abstract import set_backend - -set_backend() - -from autoarray.plot.abstract_plotters import AbstractPlotter - -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - - -class Plotter(AbstractPlotter): - - def __init__( - self, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - - super().__init__( - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - ) - - self.visuals_1d = visuals_1d or Visuals1D() - self.mat_plot_1d = mat_plot_1d or MatPlot1D() - - self.visuals_2d = visuals_2d or Visuals2D() - self.mat_plot_2d = mat_plot_2d or MatPlot2D() diff --git a/autogalaxy/plot/mass_plotter.py b/autogalaxy/plot/mass_plotter.py deleted file mode 100644 index 16167ad59..000000000 --- a/autogalaxy/plot/mass_plotter.py +++ /dev/null @@ -1,133 +0,0 @@ -from autoconf import cached_property - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.plot.abstract_plotters import Plotter - - -class MassPlotter(Plotter): - def __init__( - self, - mass_obj, - grid: aa.type.Grid2DLike, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - super().__init__(mat_plot_2d=mat_plot_2d, visuals_2d=visuals_2d) - - self.mass_obj = mass_obj - self.grid = grid - - @cached_property - def visuals_2d_with_critical_curves(self) -> aplt.Visuals2D: - """ - Returns the `Visuals2D` of the plotter with critical curves and caustics added, which are used to plot - the critical curves and caustics of the `Tracer` object. - """ - return self.visuals_2d.add_critical_curves_or_caustics( - mass_obj=self.mass_obj, grid=self.grid, plane_index=0 - ) - - def figures_2d( - self, - convergence: bool = False, - potential: bool = False, - deflections_y: bool = False, - deflections_x: bool = False, - magnification: bool = False, - title_suffix: str = "", - filename_suffix: str = "", - ): - """ - Plots the individual attributes of the plotter's mass object in 2D, which are computed via the plotter's 2D - grid object. - - The API is such that every plottable attribute of the `Imaging` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - convergence - Whether to make a 2D plot (via `imshow`) of the convergence. - potential - Whether to make a 2D plot (via `imshow`) of the potential. - deflections_y - Whether to make a 2D plot (via `imshow`) of the y component of the deflection angles. - deflections_x - Whether to make a 2D plot (via `imshow`) of the x component of the deflection angles. - magnification - Whether to make a 2D plot (via `imshow`) of the magnification. - """ - - if convergence: - self.mat_plot_2d.plot_array( - array=self.mass_obj.convergence_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d_with_critical_curves, - auto_labels=aplt.AutoLabels( - title=f"Convergence{title_suffix}", - filename=f"convergence_2d{filename_suffix}", - cb_unit="", - ), - ) - - if potential: - self.mat_plot_2d.plot_array( - array=self.mass_obj.potential_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d_with_critical_curves, - auto_labels=aplt.AutoLabels( - title=f"Potential{title_suffix}", - filename=f"potential_2d{filename_suffix}", - cb_unit="", - ), - ) - - if deflections_y: - deflections = self.mass_obj.deflections_yx_2d_from(grid=self.grid) - deflections_y = aa.Array2D( - values=deflections.slim[:, 0], mask=self.grid.mask - ) - - self.mat_plot_2d.plot_array( - array=deflections_y, - visuals_2d=self.visuals_2d_with_critical_curves, - auto_labels=aplt.AutoLabels( - title=f"Deflections Y{title_suffix}", - filename=f"deflections_y_2d{filename_suffix}", - cb_unit="", - ), - ) - - if deflections_x: - deflections = self.mass_obj.deflections_yx_2d_from(grid=self.grid) - deflections_x = aa.Array2D( - values=deflections.slim[:, 1], mask=self.grid.mask - ) - - self.mat_plot_2d.plot_array( - array=deflections_x, - visuals_2d=self.visuals_2d_with_critical_curves, - auto_labels=aplt.AutoLabels( - title=f"Deflections X{title_suffix}", - filename=f"deflections_x_2d{filename_suffix}", - cb_unit="", - ), - ) - - if magnification: - from autogalaxy.operate.lens_calc import LensCalc - - self.mat_plot_2d.plot_array( - array=LensCalc.from_mass_obj( - self.mass_obj - ).magnification_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d_with_critical_curves, - auto_labels=aplt.AutoLabels( - title=f"Magnification{title_suffix}", - filename=f"magnification_2d{filename_suffix}", - cb_unit="", - ), - ) diff --git a/autogalaxy/plot/mat_plot/__init__.py b/autogalaxy/plot/mat_plot/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/autogalaxy/plot/mat_plot/one_d.py b/autogalaxy/plot/mat_plot/one_d.py deleted file mode 100644 index d150eb646..000000000 --- a/autogalaxy/plot/mat_plot/one_d.py +++ /dev/null @@ -1,140 +0,0 @@ -from typing import Optional - -import autoarray.plot as aplt -from autogalaxy.plot import wrap as w - - -class MatPlot1D(aplt.MatPlot1D): - def __init__( - self, - units: Optional[aplt.Units] = None, - figure: Optional[aplt.Figure] = None, - axis: Optional[aplt.Axis] = None, - cmap: Optional[aplt.Cmap] = None, - colorbar: Optional[aplt.Colorbar] = None, - colorbar_tickparams: Optional[aplt.ColorbarTickParams] = None, - tickparams: Optional[aplt.TickParams] = None, - yticks: Optional[aplt.YTicks] = None, - xticks: Optional[aplt.XTicks] = None, - title: Optional[aplt.Title] = None, - ylabel: Optional[aplt.YLabel] = None, - xlabel: Optional[aplt.XLabel] = None, - text: Optional[aplt.Text] = None, - legend: Optional[aplt.Legend] = None, - output: Optional[aplt.Output] = None, - yx_plot: Optional[aplt.YXPlot] = None, - fill_between: Optional[aplt.FillBetween] = None, - half_light_radius_axvline: Optional[w.HalfLightRadiusAXVLine] = None, - einstein_radius_axvline: Optional[w.EinsteinRadiusAXVLine] = None, - model_fluxes_yx_scatter: Optional[w.ModelFluxesYXScatter] = None, - ): - """ - Visualizes 1D data structures as a y versus x plot using Matplotlib. - - The `Plotter` is passed objects from the `wrap_base` package which wrap matplotlib plot functions and customize - the appearance of the plots of the data structure. If the values of these matplotlib wrapper objects are not - manually specified, they assume the default values provided in the `config.visualize.mat_*` `.ini` config files. - - Parameters - ---------- - units - The units of the figure used to plot the data structure which sets the y and x ticks and labels. - figure - Opens the matplotlib figure before plotting via `plt.figure` and closes it once plotting is complete - via `plt.close`. - axis - Sets the extent of the figure axis via `plt.axis` and allows for a manual axis range. - cmap - Customizes the colormap of the plot and its normalization via matplotlib `colors` objects such - as `colors.Normalize` and `colors.LogNorm`. - colorbar - Plots the colorbar of the plot via `plt.colorbar` and customizes its tick labels and values using method - like `cb.set_yticklabels`. - colorbar_tickparams - Customizes the yticks of the colorbar plotted via `plt.colorbar`. - tickparams - Customizes the appearances of the y and x ticks on the plot, (e.g. the fontsize), using `plt.tick_params`. - yticks - Sets the yticks of the plot, including scaling them to new units depending on the `Units` object, via - `plt.yticks`. - xticks - Sets the xticks of the plot, including scaling them to new units depending on the `Units` object, via - `plt.xticks`. - title - Sets the figure title and customizes its appearance using `plt.title`. - ylabel - Sets the figure ylabel and customizes its appearance using `plt.ylabel`. - xlabel - Sets the figure xlabel and customizes its appearance using `plt.xlabel`. - legend - Sets whether the plot inclues a legend and customizes its appearance and labels using `plt.legend`. - output - Sets if the figure is displayed on the user's screen or output to `.png` using `plt.show` and `plt.savefig` - yx_plot - Sets how the y versus x plot appears, for example if it each axis is linear or log, using `plt.plot`. - half_light_radius_axvline - Sets how a vertical line representing the half light radius of a `LightProfile` is plotted on the figure - using the `plt.axvline` method. - half_light_radius_axvline - Sets how a vertical line representing the Einstein radius of a `LensingObj` (e.g. a `MassProfile`) is - plotted on the figure using the `plt.axvline` method. - """ - - super().__init__( - units=units, - figure=figure, - axis=axis, - cmap=cmap, - colorbar=colorbar, - colorbar_tickparams=colorbar_tickparams, - tickparams=tickparams, - yticks=yticks, - xticks=xticks, - title=title, - ylabel=ylabel, - xlabel=xlabel, - text=text, - legend=legend, - output=output, - yx_plot=yx_plot, - fill_between=fill_between, - ) - - self.half_light_radius_axvline = ( - half_light_radius_axvline or w.HalfLightRadiusAXVLine(is_default=True) - ) - self.einstein_radius_axvline = ( - einstein_radius_axvline or w.EinsteinRadiusAXVLine(is_default=True) - ) - self.model_fluxes_yx_scatter = ( - model_fluxes_yx_scatter or w.ModelFluxesYXScatter(is_default=True) - ) - - def set_for_multi_plot( - self, is_for_multi_plot: bool, color: str, xticks=None, yticks=None - ): - """ - Sets the `is_for_subplot` attribute for every `MatWrap` object in this `MatPlot` object by updating - the `is_for_subplot`. By changing this tag: - - - The subplot: section of the config file of every `MatWrap` object is used instead of figure:. - - Calls which output or close the matplotlib figure are over-ridden so that the subplot is not removed. - - Parameters - ---------- - is_for_subplot - The entry the `is_for_subplot` attribute of every `MatWrap` object is set too. - """ - - super().set_for_multi_plot( - is_for_multi_plot=is_for_multi_plot, - color=color, - xticks=xticks, - yticks=yticks, - ) - - self.half_light_radius_axvline.kwargs["c"] = color - self.einstein_radius_axvline.kwargs["c"] = color - - self.half_light_radius_axvline.no_label = True - self.einstein_radius_axvline.no_label = True diff --git a/autogalaxy/plot/mat_plot/two_d.py b/autogalaxy/plot/mat_plot/two_d.py deleted file mode 100644 index 2c4924163..000000000 --- a/autogalaxy/plot/mat_plot/two_d.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import List, Optional, Union - -import autoarray.plot as aplt -from autogalaxy.plot import wrap as w - - -class MatPlot2D(aplt.MatPlot2D): - def __init__( - self, - units: Optional[aplt.Units] = None, - figure: Optional[aplt.Figure] = None, - axis: Optional[aplt.Axis] = None, - cmap: Optional[aplt.Cmap] = None, - colorbar: Optional[aplt.Colorbar] = None, - colorbar_tickparams: Optional[aplt.ColorbarTickParams] = None, - tickparams: Optional[aplt.TickParams] = None, - yticks: Optional[aplt.YTicks] = None, - xticks: Optional[aplt.XTicks] = None, - title: Optional[aplt.Title] = None, - ylabel: Optional[aplt.YLabel] = None, - xlabel: Optional[aplt.XLabel] = None, - text: Optional[Union[aplt.Text, List[aplt.Text]]] = None, - annotate: Optional[Union[aplt.Annotate, List[aplt.Annotate]]] = None, - legend: Optional[aplt.Legend] = None, - output: Optional[aplt.Output] = None, - array_overlay: Optional[aplt.ArrayOverlay] = None, - contour: Optional[aplt.Contour] = None, - grid_scatter: Optional[aplt.GridScatter] = None, - grid_plot: Optional[aplt.GridPlot] = None, - vector_yx_quiver: Optional[aplt.VectorYXQuiver] = None, - patch_overlay: Optional[aplt.PatchOverlay] = None, - delaunay_drawer: Optional[aplt.DelaunayDrawer] = None, - origin_scatter: Optional[aplt.OriginScatter] = None, - mask_scatter: Optional[aplt.MaskScatter] = None, - border_scatter: Optional[aplt.BorderScatter] = None, - positions_scatter: Optional[aplt.PositionsScatter] = None, - index_scatter: Optional[aplt.IndexScatter] = None, - index_plot: Optional[aplt.IndexPlot] = None, - mesh_grid_scatter: Optional[aplt.MeshGridScatter] = None, - light_profile_centres_scatter: Optional[w.LightProfileCentresScatter] = None, - mass_profile_centres_scatter: Optional[w.MassProfileCentresScatter] = None, - multiple_images_scatter: Optional[w.MultipleImagesScatter] = None, - tangential_critical_curves_plot: Optional[ - w.TangentialCriticalCurvesPlot - ] = None, - radial_critical_curves_plot: Optional[w.RadialCriticalCurvesPlot] = None, - tangential_caustics_plot: Optional[w.TangentialCausticsPlot] = None, - radial_caustics_plot: Optional[w.RadialCausticsPlot] = None, - use_log10: bool = False, - quick_update: bool = False, - ): - """ - Visualizes data structures (e.g an `Array2D`, `Grid2D`, `VectorField`, etc.) using Matplotlib. - - The `Plotter` is passed objects from the `mat_wrap` package which wrap matplotlib plot functions and - customize the appearance of the plots of the data structure. If the values of these matplotlib wrapper - objects are not manually specified, they assume the default values provided in - the `config.visualize.mat_*` `.ini` config files. - - The following data structures can be plotted using the following matplotlib functions: - - - `Array2D`:, using `plt.imshow`. - - `Grid2D`: using `plt.scatter`. - - `Line`: using `plt.plot`, `plt.semilogy`, `plt.loglog` or `plt.scatter`. - - `VectorField`: using `plt.quiver`. - - `RectangularMapper`: using `plt.imshow`. - - `MapperVoronoiNoInterp`: using `plt.fill`. - - Parameters - ---------- - units - The units of the figure used to plot the data structure which sets the y and x ticks and labels. - figure - Opens the matplotlib figure before plotting via `plt.figure` and closes it once plotting is complete - via `plt.close`. - axis - Sets the extent of the figure axis via `plt.axis` and allows for a manual axis range. - cmap - Customizes the colormap of the plot and its normalization via matplotlib `colors` objects such - as `colors.Normalize` and `colors.LogNorm`. - colorbar - Plots the colorbar of the plot via `plt.colorbar` and customizes its tick labels and values using method - like `cb.set_yticklabels`. - colorbar_tickparams - Customizes the yticks of the colorbar plotted via `plt.colorbar`. - tickparams - Customizes the appearances of the y and x ticks on the plot, (e.g. the fontsize), using `plt.tick_params`. - yticks - Sets the yticks of the plot, including scaling them to new units depending on the `Units` object, via - `plt.yticks`. - xticks - Sets the xticks of the plot, including scaling them to new units depending on the `Units` object, via - `plt.xticks`. - title - Sets the figure title and customizes its appearance using `plt.title`. - ylabel - Sets the figure ylabel and customizes its appearance using `plt.ylabel`. - xlabel - Sets the figure xlabel and customizes its appearance using `plt.xlabel`. - text - Sets any text on the figure and customizes its appearance using `plt.text`. - annotate - Sets any annotations on the figure and customizes its appearance using `plt.annotate`. - legend - Sets whether the plot inclues a legend and customizes its appearance and labels using `plt.legend`. - fill - Sets the fill of the figure using `plt.fill` and customizes its appearance, such as the color and alpha. - output - Sets if the figure is displayed on the user's screen or output to `.png` using `plt.show` and `plt.savefig` - array_overlay - Overlays an input `Array2D` over the figure using `plt.imshow`. - contour - Overlays contours of an input `Array2D` over the figure using `plt.contour`. - fill - Sets the fill of the figure using `plt.fill` and customizes its appearance, such as the color and alpha. - grid_scatter - Scatters a `Grid2D` of (y,x) coordinates over the figure using `plt.scatter`. - grid_plot - Plots lines of data (e.g. a y versus x plot via `plt.plot`, vertical lines via `plt.avxline`, etc.) - vector_yx_quiver - Plots a `VectorField` object using the matplotlib function `plt.quiver`. - patch_overlay - Overlays matplotlib `patches.Patch` objects over the figure, such as an `Ellipse`. - delaunay_drawer - Draws a colored Delaunay mesh of pixels using `plt.tripcolor`. - voronoi_drawer - Draws a colored Voronoi mesh of pixels using `plt.fill`. - origin_scatter - Scatters the (y,x) origin of the data structure on the figure. - mask_scatter - Scatters an input `Mask2d` over the plotted data structure's figure. - border_scatter - Scatters the border of an input `Mask2d` over the plotted data structure's figure. - positions_scatter - Scatters specific (y,x) coordinates input as a `Grid2DIrregular` object over the figure. - index_scatter - Scatters specific coordinates of an input `Grid2D` based on input values of the `Grid2D`'s 1D or 2D indexes. - mesh_grid_scatter - Scatters the `PixelizationGrid` of a `Pixelization` object. - light_profile_centres_scatter - Scatters the (y,x) centres of all `LightProfile`'s in the plotted object (e.g. a `Tracer`). - mass_profile_centres_scatter - Scatters the (y,x) centres of all `MassProfile`'s in the plotted object (e.g. a `Tracer`). - light_profile_centres_scatter - Scatters the (y,x) coordinates of the multiple image locations of the lens mass model. - tangential_critical_curves_plot - Plots the tangential critical curves of the lens mass model as colored lines. - radial_critical_curves_plot - Plots the radial critical curves of the lens mass model as colored lines. - tangential_caustics_plot - Plots the tangential caustics of the lens mass model as colored lines. - radial_caustics_plot - Plots the radial caustics of the lens mass model as colored lines. - """ - - self.light_profile_centres_scatter = ( - light_profile_centres_scatter - or w.LightProfileCentresScatter(is_default=True) - ) - self.mass_profile_centres_scatter = ( - mass_profile_centres_scatter or w.MassProfileCentresScatter(is_default=True) - ) - self.multiple_images_scatter = ( - multiple_images_scatter or w.MultipleImagesScatter(is_default=True) - ) - self.tangential_critical_curves_plot = ( - tangential_critical_curves_plot - or w.TangentialCriticalCurvesPlot(is_default=True) - ) - self.radial_critical_curves_plot = ( - radial_critical_curves_plot or w.RadialCriticalCurvesPlot() - ) - self.tangential_caustics_plot = ( - tangential_caustics_plot or w.TangentialCausticsPlot(is_default=True) - ) - self.radial_caustics_plot = radial_caustics_plot or w.RadialCausticsPlot() - - super().__init__( - units=units, - figure=figure, - axis=axis, - cmap=cmap, - colorbar=colorbar, - colorbar_tickparams=colorbar_tickparams, - legend=legend, - title=title, - tickparams=tickparams, - yticks=yticks, - xticks=xticks, - ylabel=ylabel, - xlabel=xlabel, - text=text, - annotate=annotate, - output=output, - origin_scatter=origin_scatter, - mask_scatter=mask_scatter, - border_scatter=border_scatter, - grid_scatter=grid_scatter, - positions_scatter=positions_scatter, - index_scatter=index_scatter, - index_plot=index_plot, - mesh_grid_scatter=mesh_grid_scatter, - vector_yx_quiver=vector_yx_quiver, - patch_overlay=patch_overlay, - array_overlay=array_overlay, - contour=contour, - grid_plot=grid_plot, - delaunay_drawer=delaunay_drawer, - use_log10=use_log10, - quick_update=quick_update, - ) diff --git a/autogalaxy/plot/plot_utils.py b/autogalaxy/plot/plot_utils.py new file mode 100644 index 000000000..bf28d8875 --- /dev/null +++ b/autogalaxy/plot/plot_utils.py @@ -0,0 +1,308 @@ +import logging +import os +import numpy as np +import matplotlib.pyplot as plt + +logger = logging.getLogger(__name__) + + +def _to_lines(*items): + """Convert multiple line sources into a flat list of (N,2) numpy arrays. + + Each item may be ``None`` (skipped), a list of line-like objects, or a + single line-like object. A line-like object is anything that either has + an ``.array`` attribute or can be coerced to a 2-D numpy array with shape + ``(N, 2)``. Items that cannot be converted, or that are empty, are + silently dropped. + + Parameters + ---------- + *items + Any number of line sources to merge. + + Returns + ------- + list of np.ndarray or None + A flat list of ``(N, 2)`` arrays, or ``None`` if nothing valid was + found. + """ + result = [] + for item in items: + if item is None: + continue + if isinstance(item, list): + for sub in item: + try: + arr = np.array(sub.array if hasattr(sub, "array") else sub) + if arr.ndim == 2 and arr.shape[1] == 2 and len(arr) > 0: + result.append(arr) + except Exception: + pass + else: + try: + arr = np.array(item.array if hasattr(item, "array") else item) + if arr.ndim == 2 and arr.shape[1] == 2 and len(arr) > 0: + result.append(arr) + except Exception: + pass + return result or None + + +def _to_positions(*items): + """Convert multiple position sources into a flat list of (N,2) numpy arrays. + + Thin wrapper around :func:`_to_lines` — positions and lines share the same + underlying representation (lists of ``(N, 2)`` coordinate arrays). + + Parameters + ---------- + *items + Any number of position sources to merge. + + Returns + ------- + list of np.ndarray or None + A flat list of ``(N, 2)`` arrays, or ``None`` if nothing valid was + found. + """ + return _to_lines(*items) + + +def _save_subplot(fig, output_path, output_filename, output_format="png", + dpi=300, structure=None): + """Save a subplot figure to disk (or show it if output_path is falsy). + + Mirrors the interface of ``autoarray.plot.plots.utils.save_figure``. + When ``output_format`` is ``"fits"`` the *structure* argument is used to + write a FITS file via its ``output_to_fits`` method. + """ + fmt = output_format[0] if isinstance(output_format, (list, tuple)) else (output_format or "png") + if output_path: + os.makedirs(str(output_path), exist_ok=True) + fpath = os.path.join(str(output_path), f"{output_filename}.{fmt}") + if fmt == "fits": + if structure is not None and hasattr(structure, "output_to_fits"): + structure.output_to_fits(file_path=fpath, overwrite=True) + else: + logger.warning( + f"_save_subplot: fits format requested for {output_filename} " + "but no compatible structure was provided; skipping." + ) + else: + fig.savefig(fpath, dpi=dpi, bbox_inches="tight", pad_inches=0.1) + else: + plt.show() + plt.close(fig) + + +def _resolve_colormap(colormap): + """Resolve 'default' to the autoarray default colormap.""" + if colormap == "default": + return "jet" + return colormap + + +def _resolve_format(output_format): + """Normalise output_format: accept a list/tuple or a plain string.""" + if isinstance(output_format, (list, tuple)): + return output_format[0] + return output_format or "png" + + +def _numpy_grid(grid): + """Convert a grid-like object to a numpy array, or return None.""" + if grid is None: + return None + try: + return np.array(grid.array if hasattr(grid, "array") else grid) + except Exception: + return None + + +def plot_array( + array, + title, + output_path=None, + output_filename="array", + output_format="png", + colormap="default", + use_log10=False, + vmin=None, + vmax=None, + symmetric=False, + positions=None, + lines=None, + grid=None, + ax=None, +): + """Plot an autoarray ``Array2D`` to file or onto an existing ``Axes``. + + All array preprocessing (zoom, mask-edge extraction, native/extent + unpacking) is handled internally so callers never need to duplicate it. + The actual rendering is delegated to ``autoarray.plot.plot_array``. + + Parameters + ---------- + array + The ``Array2D`` (or array-like) to plot. + title : str + Title displayed above the panel. + output_path : str or None + Directory in which to save the figure. ``None`` → call + ``plt.show()`` instead. + output_filename : str + Stem of the output file name (extension is added from + *output_format*). + output_format : str + File format, e.g. ``"png"`` or ``"pdf"``. + colormap : str + Matplotlib colormap name, or ``"default"`` to use the autoarray + default (``"jet"``). + use_log10 : bool + If ``True`` apply a log₁₀ stretch to the array values. + vmin, vmax : float or None + Explicit colour-bar limits. Ignored when *symmetric* is ``True``. + symmetric : bool + If ``True`` set ``vmin = -vmax`` so that zero maps to the middle of + the colormap. + positions : list or array-like or None + Point positions to scatter-plot over the image. + lines : list or array-like or None + Line coordinates to overlay on the image. + grid : array-like or None + An additional grid of points to overlay. + ax : matplotlib.axes.Axes or None + Existing ``Axes`` to draw into. When provided the figure is *not* + saved — the caller is responsible for saving. + """ + from autoarray.plot import plot_array as _aa_plot_array + from autoarray.plot import zoom_array, auto_mask_edge + + colormap = _resolve_colormap(colormap) + output_format = _resolve_format(output_format) + array = zoom_array(array) + + try: + arr = array.native.array + extent = array.geometry.extent + except AttributeError: + arr = np.asarray(array) + extent = None + + mask = auto_mask_edge(array) if hasattr(array, "mask") else None + + if symmetric: + finite = arr[np.isfinite(arr)] + abs_max = float(np.max(np.abs(finite))) if len(finite) > 0 else 1.0 + vmin, vmax = -abs_max, abs_max + + _positions_list = positions if isinstance(positions, list) else _to_positions(positions) + _lines_list = lines if isinstance(lines, list) else _to_lines(lines) + + _output_path = None if ax is not None else output_path + + _aa_plot_array( + array=arr, + ax=ax, + extent=extent, + mask=mask, + grid=_numpy_grid(grid), + positions=_positions_list, + lines=_lines_list, + title=title or "", + colormap=colormap, + use_log10=use_log10, + vmin=vmin, + vmax=vmax, + output_path=_output_path, + output_filename=output_filename, + output_format=output_format, + structure=array, + ) + + +def plot_grid( + grid, + title, + output_path=None, + output_filename="grid", + output_format="png", + lines=None, + ax=None, +): + """Plot an autoarray ``Grid2D`` as a scatter plot. + + Delegates to ``autoarray.plot.plot_grid`` after converting the grid to a + plain numpy array. + + Parameters + ---------- + grid + The ``Grid2D`` (or grid-like) to plot. + title : str + Title displayed above the panel. + output_path : str or None + Directory in which to save the figure. ``None`` → call + ``plt.show()`` instead. + output_filename : str + Stem of the output file name. + output_format : str + File format, e.g. ``"png"``. + lines : list or None + Line coordinates to overlay on the grid plot. + ax : matplotlib.axes.Axes or None + Existing ``Axes`` to draw into. + """ + from autoarray.plot import plot_grid as _aa_plot_grid + + output_format = _resolve_format(output_format) + _output_path = None if ax is not None else output_path + + _aa_plot_grid( + grid=np.array(grid.array), + ax=ax, + title=title or "", + output_path=_output_path, + output_filename=output_filename, + output_format=output_format, + ) + + +def _critical_curves_from(mass_obj, grid, tc=None, rc=None): + """Compute tangential and radial critical curves for a mass object. + + If *tc* is already provided it is returned unchanged (along with *rc*), + allowing callers to cache the curves across multiple plot calls. When + *tc* is ``None`` the curves are computed via :class:`LensCalc`. Radial + critical curves are only computed when at least one radial critical-curve + area exceeds the grid pixel scale, avoiding spurious empty curves. + + Parameters + ---------- + mass_obj + Any object understood by ``LensCalc.from_mass_obj`` (e.g. a + :class:`~autogalaxy.galaxy.galaxies.Galaxies` instance). + grid : aa.type.Grid2DLike + The grid on which to evaluate the critical curves. + tc : list or None + Pre-computed tangential critical curves. Pass ``None`` to trigger + computation. + rc : list or None + Pre-computed radial critical curves. Pass ``None`` to trigger + computation. + + Returns + ------- + tuple[list, list or None] + ``(tangential_critical_curves, radial_critical_curves)``. + """ + from autogalaxy.operate.lens_calc import LensCalc + + if tc is None: + od = LensCalc.from_mass_obj(mass_obj) + tc = od.tangential_critical_curve_list_from(grid=grid) + rc_area = od.radial_critical_curve_area_list_from(grid=grid) + if any(area > grid.pixel_scale for area in rc_area): + rc = od.radial_critical_curve_list_from(grid=grid) + + return tc, rc diff --git a/autogalaxy/plot/visuals/__init__.py b/autogalaxy/plot/visuals/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/autogalaxy/plot/visuals/one_d.py b/autogalaxy/plot/visuals/one_d.py deleted file mode 100644 index f6ea4af8f..000000000 --- a/autogalaxy/plot/visuals/one_d.py +++ /dev/null @@ -1,243 +0,0 @@ -from __future__ import annotations - -import numpy as np -from typing import List, Union, Optional, TYPE_CHECKING - -import autoarray as aa -import autoarray.plot as aplt - -if TYPE_CHECKING: - - from autogalaxy.galaxy.galaxy import Galaxy - from autogalaxy.profiles.light.abstract import LightProfile - from autogalaxy.profiles.mass.abstract.abstract import MassProfile - -from autogalaxy.util import error_util - - -class Visuals1D(aplt.Visuals1D): - def __init__( - self, - origin: Optional[aa.Grid1D] = None, - mask: Optional[aa.Mask1D] = None, - points: Optional[aa.Grid1D] = None, - vertical_line: Optional[float] = None, - shaded_region: Optional[List[Union[List, aa.Array1D, np.ndarray]]] = None, - half_light_radius: Optional[float] = None, - half_light_radius_errors: Optional[List[float]] = None, - einstein_radius: Optional[float] = None, - einstein_radius_errors: Optional[List[float]] = None, - model_fluxes: Optional[aa.Grid1D] = None, - ): - super().__init__( - origin=origin, - mask=mask, - points=points, - vertical_line=vertical_line, - shaded_region=shaded_region, - ) - - self.half_light_radius = half_light_radius - self.half_light_radius_errors = half_light_radius_errors - self.einstein_radius = einstein_radius - self.einstein_radius_errors = einstein_radius_errors - self.model_fluxes = model_fluxes - - def plot_via_plotter(self, plotter, grid_indexes=None, mapper=None): - super().plot_via_plotter(plotter=plotter) - - if self.half_light_radius is not None: - plotter.half_light_radius_axvline.axvline_vertical_line( - vertical_line=self.half_light_radius, - vertical_errors=self.half_light_radius_errors, - label="Half-light Radius", - ) - - if self.einstein_radius is not None: - plotter.einstein_radius_axvline.axvline_vertical_line( - vertical_line=self.einstein_radius, - vertical_errors=self.einstein_radius_errors, - label="Einstein Radius", - ) - - if self.model_fluxes is not None: - plotter.model_fluxes_yx_scatter.scatter_yx( - y=self.model_fluxes, x=np.arange(len(self.model_fluxes)) - ) - - def add_half_light_radius( - self, light_obj: Union[LightProfile, Galaxy] - ) -> "Visuals1D": - """ - From an object with light profiles (e.g. a `LightProfile`, `Galaxy`) get its attributes that can be plotted - and return them in a `Visuals1D` object. - - Only attributes not already in `self` are extracted for plotting. - - From a light object the following 1D attributes can be extracted for plotting: - - - half_light_radius: the radius containing 50% of the light objects total integrated luminosity. - - Parameters - ---------- - light_obj - The light object (e.g. a `LightProfile`, `Galaxy`) whose attributes are extracted for plotting. - - Returns - ------- - Visuals1D - The collection of attributes that can be plotted by a `Plotter` object. - """ - return self + self.__class__(half_light_radius=light_obj.half_light_radius) - - def add_half_light_radius_errors( - self, light_obj_list: Union[List[LightProfile], List[Galaxy]], low_limit: float - ) -> "Visuals1D": - """ - From a list of objects with light profiles (e.g. a `LightProfile`, `Galaxy`) get its attributes that can be - plotted and return them in a `Visuals1D` object. - - Only attributes not already in `self` are extracted for plotting. - - This function iterates over all light objects in the list and averages over each attribute's values to estimate - the mean value of the attribute and its error, both of which can then be plotted. This is typically used - to plot 1D errors on a quantity that are estimated via a Probability Density Function. - - From a light object lust the following 1D attributes can be extracted for plotting: - - - half_light_radius: the radius containing 50% of the light objects total integrated luminosity. - - Parameters - ---------- - light_obj_list - The list of light objects (e.g. a `LightProfile`, `Galaxy`) whose mean attributes and error estimates are - extracted for plotting. - low_limit - The value of sigma to which errors are estimated (e.g. 1.0 will estimate errors at the ~0.32 and ~0.68 - intervals of the probability distribution. - - Returns - ------- - Visuals1D - The mean value and errors of each attribute that are plotted in 1D by a `Plotter` object. - """ - - half_light_radius_list = [ - light_profile.half_light_radius for light_profile in light_obj_list - ] - - if None in half_light_radius_list: - half_light_radius = None - half_light_radius_errors = None - - else: - ( - half_light_radius, - half_light_radius_errors, - ) = error_util.value_median_and_error_region_via_quantile( - value_list=half_light_radius_list, low_limit=low_limit - ) - - return self + self.__class__( - half_light_radius=half_light_radius, - half_light_radius_errors=half_light_radius_errors, - ) - - def add_einstein_radius( - self, mass_obj: Union[MassProfile, Galaxy], grid: aa.type.Grid2DLike - ) -> "Visuals1D": - """ - From an object with mass profiles (e.g. a `MassProfile`, `Galaxy`) get its attributes that can be plotted - and return them in a `Visuals1D` object. - - Only attributes not already in `self` are extracted for plotting. - - From a mass object the following 1D attributes can be extracted for plotting: - - - einstein_radius: the einstein radius (e.g. area within critical curve) of the mass object. - - Mass profiles can be too shallow to do lensing and therefore an Einstein radius cannot be computed. This - raises a TypeError which is accounted for below. - - Parameters - ---------- - mass_obj - The mass object (e.g. a `MassProfile`, `Galaxy`) whose attributes are extracted for plotting. - - Returns - ------- - Visuals1D - The collection of attributes that can be plotted by a `Plotter` object. - """ - - from autogalaxy.operate.lens_calc import LensCalc - - einstein_radius = None - - try: - od = LensCalc.from_mass_obj(mass_obj) - einstein_radius = od.einstein_radius_from(grid=grid) - except (TypeError, AttributeError): - pass - - return self + self.__class__(einstein_radius=einstein_radius) - - def add_einstein_radius_errors( - self, - mass_obj_list: Union[List[MassProfile], List[Galaxy]], - grid: aa.type.Grid2DLike, - low_limit: float, - ) -> "Visuals1D": - """ - From a list of objects with mass profiles (e.g. a `MassProfile`, `Galaxy`) get its attributes that can be - plotted and return them in a `Visuals1D` object. - - Only attributes not already in `self` are extracted for plotting. - - This function iterates over all mass objects in the list and averages over each attribute's values to estimate - the mean value of the attribute and its error, both of which can then be plotted. This is typically used - to plot 1D errors on a quantity that are estimated via a Probability Density Function. - - From a mass object lust the following 1D attributes can be extracted for plotting: - - - half_mass_radius: the radius containing 50% of the mass objects total integrated luminosity. - - Parameters - ---------- - mass_obj_list - The list of mass objects (e.g. a `MassProfile`, `Galaxy`) whose mean attributes and error estimates are - extracted for plotting. - low_limit - The value of sigma to which errors are estimated (e.g. 1.0 will estimate errors at the ~0.32 and ~0.68 - intervals of the probability distribution. - - Returns - ------- - Visuals1D - The mean value and errors of each attribute that are plotted in 1D by a `Plotter` object. - """ - - from autogalaxy.operate.lens_calc import LensCalc - - einstein_radius_list = [] - - for mass_obj in mass_obj_list: - try: - od = LensCalc.from_mass_obj(mass_obj) - einstein_radius_list.append(od.einstein_radius_from(grid=grid)) - except TypeError: - einstein_radius_list.append(None) - - einstein_radius_list = list(filter(None, einstein_radius_list)) - - ( - einstein_radius, - einstein_radius_errors, - ) = error_util.value_median_and_error_region_via_quantile( - value_list=einstein_radius_list, low_limit=low_limit - ) - - return self + self.__class__( - einstein_radius=einstein_radius, - einstein_radius_errors=einstein_radius_errors, - ) diff --git a/autogalaxy/plot/visuals/two_d.py b/autogalaxy/plot/visuals/two_d.py deleted file mode 100644 index 9a177463d..000000000 --- a/autogalaxy/plot/visuals/two_d.py +++ /dev/null @@ -1,240 +0,0 @@ -from typing import List, Union, Optional - -import autoarray as aa -import autoarray.plot as aplt - - -class Visuals2D(aplt.Visuals2D): - def __init__( - self, - origin: aa.Grid2D = None, - border: aa.Grid2D = None, - mask: aa.Mask2D = None, - lines: Optional[Union[List[aa.Array1D], aa.Grid2DIrregular]] = None, - positions: Optional[Union[aa.Grid2DIrregular, List[aa.Grid2DIrregular]]] = None, - grid: Union[aa.Grid2D] = None, - mesh_grid: aa.Grid2D = None, - vectors: aa.VectorYX2DIrregular = None, - patches: "Union[ptch.Patch]" = None, - fill_region: Optional[List] = None, - array_overlay: aa.Array2D = None, - light_profile_centres: aa.Grid2DIrregular = None, - mass_profile_centres: aa.Grid2DIrregular = None, - multiple_images: aa.Grid2DIrregular = None, - tangential_critical_curves: Optional[ - Union[aa.Grid2DIrregular, List[aa.Grid2DIrregular]] - ] = None, - radial_critical_curves: Optional[ - Union[aa.Grid2DIrregular, List[aa.Grid2DIrregular]] - ] = None, - tangential_caustics: Optional[ - Union[aa.Grid2DIrregular, List[aa.Grid2DIrregular]] - ] = None, - radial_caustics: Optional[ - Union[aa.Grid2DIrregular, List[aa.Grid2DIrregular]] - ] = None, - parallel_overscan=None, - serial_prescan=None, - serial_overscan=None, - indexes: Union[List[int], List[List[int]]] = None, - ): - super().__init__( - mask=mask, - positions=positions, - grid=grid, - lines=lines, - mesh_grid=mesh_grid, - vectors=vectors, - patches=patches, - fill_region=fill_region, - array_overlay=array_overlay, - origin=origin, - border=border, - parallel_overscan=parallel_overscan, - serial_prescan=serial_prescan, - serial_overscan=serial_overscan, - indexes=indexes, - ) - - self.light_profile_centres = light_profile_centres - self.mass_profile_centres = mass_profile_centres - self.multiple_images = multiple_images - self.tangential_critical_curves = tangential_critical_curves - self.radial_critical_curves = radial_critical_curves - self.tangential_caustics = tangential_caustics - self.radial_caustics = radial_caustics - - def plot_via_plotter(self, plotter, grid_indexes=None): - super().plot_via_plotter( - plotter=plotter, - grid_indexes=grid_indexes, - ) - - if self.light_profile_centres is not None: - plotter.light_profile_centres_scatter.scatter_grid( - grid=self.light_profile_centres - ) - - if self.mass_profile_centres is not None: - plotter.mass_profile_centres_scatter.scatter_grid( - grid=self.mass_profile_centres - ) - - if self.multiple_images is not None: - try: - plotter.multiple_images_scatter.scatter_grid( - grid=self.multiple_images.array - ) - except (AttributeError, ValueError): - plotter.multiple_images_scatter.scatter_grid(grid=self.multiple_images) - - if self.tangential_critical_curves is not None: - try: - plotter.tangential_critical_curves_plot.plot_grid( - grid=self.tangential_critical_curves - ) - except (AttributeError, TypeError): - pass - - if self.radial_critical_curves is not None: - try: - plotter.radial_critical_curves_plot.plot_grid( - grid=self.radial_critical_curves - ) - except (AttributeError, TypeError): - pass - - if self.tangential_caustics is not None: - try: - try: - plotter.tangential_caustics_plot.plot_grid( - grid=self.tangential_caustics - ) - except (AttributeError, ValueError): - try: - plotter.tangential_caustics_plot.plot_grid( - grid=self.tangential_caustics.array - ) - except (AttributeError, TypeError): - pass - except (AttributeError, TypeError): - pass - - if self.radial_caustics is not None: - try: - plotter.radial_caustics_plot.plot_grid(grid=self.radial_caustics) - except (AttributeError, TypeError): - pass - - def add_critical_curves_or_caustics( - self, mass_obj, grid: aa.type.Grid2DLike, plane_index: int - ): - """ - From a object with mass profiles (e.g. mass profile, galaxy) extract the critical curves or caustics and - returns them in a `Visuals2D` object. - - This includes support for a `plane_index`, which specifies the index of the plane in the tracer, which is - an object used in PyAutoLens to represent a lensing system with multiple planes (e.g. an image plane and a - source plane). The `plane_index` allows for the extraction of quantities from a specific plane in the tracer. - - When plotting a `Tracer` it is common for plots to only display quantities corresponding to one plane at a time - (e.g. the convergence in the image plane, the source in the source plane). Therefore, quantities are only - extracted from one plane, specified by the input `plane_index`. - - Parameters - ---------- - mass_obj - The mass object (e.g. mass profile, galaxy, tracer) object which has attributes extracted for plotting. - grid - The 2D grid of (y,x) coordinates used to plot the tracer's quantities in 2D. - plane_index - The index of the plane in the tracer which is used to extract quantities, as only one plane is plotted - at a time. - - Returns - ------- - vis.Visuals2D - A collection of attributes that can be plotted by a `Plotter` object. - """ - if plane_index == 0: - return self.add_critical_curves(mass_obj=mass_obj, grid=grid) - return self.add_caustics(mass_obj=mass_obj, grid=grid) - - def add_critical_curves(self, mass_obj, grid: aa.type.Grid2DLike): - """ - From a object with mass profiles (e.g. mass profile, galaxy) extract the critical curves and - returns them in a `Visuals2D` object. - - When plotting a `Tracer` it is common for plots to only display quantities corresponding to one plane at a time - (e.g. the convergence in the image plane, the source in the source plane). Therefore, quantities are only - extracted from one plane, specified by the input `plane_index`. - - Parameters - ---------- - mass_obj - The mass object (e.g. mass profile, galaxy, tracer) object which has attributes extracted for plotting. - grid - The 2D grid of (y,x) coordinates used to plot the tracer's quantities in 2D. - plane_index - The index of the plane in the tracer which is used to extract quantities, as only one plane is plotted - at a time. - - Returns - ------- - vis.Visuals2D - A collection of attributes that can be plotted by a `Plotter` object. - """ - from autogalaxy.operate.lens_calc import LensCalc - - od = LensCalc.from_mass_obj(mass_obj) - - tangential_critical_curves = od.tangential_critical_curve_list_from(grid=grid) - - radial_critical_curves = None - radial_critical_curve_area_list = od.radial_critical_curve_area_list_from( - grid=grid - ) - - if any([area > grid.pixel_scale for area in radial_critical_curve_area_list]): - radial_critical_curves = od.radial_critical_curve_list_from(grid=grid) - - return self + self.__class__( - tangential_critical_curves=tangential_critical_curves, - radial_critical_curves=radial_critical_curves, - ) - - def add_caustics(self, mass_obj, grid: aa.type.Grid2DLike): - """ - From a object with mass profiles (e.g. mass profile, galaxy) extract the caustics and - returns them in a `Visuals2D` object. - - When plotting a `Tracer` it is common for plots to only display quantities corresponding to one plane at a time - (e.g. the convergence in the image plane, the source in the source plane). Therefore, quantities are only - extracted from one plane, specified by the input `plane_index`. - - Parameters - ---------- - mass_obj - The mass object (e.g. mass profile, galaxy, tracer) object which has attributes extracted for plotting. - grid - The 2D grid of (y,x) coordinates used to plot the tracer's quantities in 2D. - plane_index - The index of the plane in the tracer which is used to extract quantities, as only one plane is plotted - at a time. - - Returns - ------- - vis.Visuals2D - A collection of attributes that can be plotted by a `Plotter` object. - """ - from autogalaxy.operate.lens_calc import LensCalc - - od = LensCalc.from_mass_obj(mass_obj) - - tangential_caustics = od.tangential_caustic_list_from(grid=grid) - radial_caustics = od.radial_caustic_list_from(grid=grid) - - return self + self.__class__( - tangential_caustics=tangential_caustics, - radial_caustics=radial_caustics, - ) diff --git a/autogalaxy/plot/wrap.py b/autogalaxy/plot/wrap.py deleted file mode 100644 index 190e8c7e4..000000000 --- a/autogalaxy/plot/wrap.py +++ /dev/null @@ -1,41 +0,0 @@ -import autoarray.plot as aplt - - -class HalfLightRadiusAXVLine(aplt.AXVLine): - pass - - -class EinsteinRadiusAXVLine(aplt.AXVLine): - pass - - -class ModelFluxesYXScatter(aplt.YXScatter): - pass - - -class LightProfileCentresScatter(aplt.GridScatter): - pass - - -class MassProfileCentresScatter(aplt.GridScatter): - pass - - -class MultipleImagesScatter(aplt.GridScatter): - pass - - -class TangentialCriticalCurvesPlot(aplt.GridPlot): - pass - - -class RadialCriticalCurvesPlot(aplt.GridPlot): - pass - - -class TangentialCausticsPlot(aplt.GridPlot): - pass - - -class RadialCausticsPlot(aplt.GridPlot): - pass diff --git a/autogalaxy/profiles/plot/basis_plots.py b/autogalaxy/profiles/plot/basis_plots.py new file mode 100644 index 000000000..d8e1b6b6b --- /dev/null +++ b/autogalaxy/profiles/plot/basis_plots.py @@ -0,0 +1,79 @@ +import matplotlib.pyplot as plt +import numpy as np + +import autoarray as aa + +from autogalaxy.profiles.basis import Basis +from autogalaxy.plot.plot_utils import _to_positions, plot_array, _save_subplot +from autogalaxy import exc + + +def subplot_image( + basis: Basis, + grid: aa.type.Grid1D2DLike, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, + lines=None, +): + """Create a subplot showing the image of every light profile in a basis. + + Produces one panel per profile in + :attr:`~autogalaxy.profiles.basis.Basis.light_profile_list`, arranged in + rows of up to four columns. Each panel title is taken from the profile's + ``coefficient_tag`` attribute. + + Linear light profiles cannot be plotted directly (their intensity is + solved via inversion), so an error is raised if any are present. + + Parameters + ---------- + basis : Basis + The basis (e.g. MGE or shapelet set) whose component images are to be + plotted. + grid : aa.type.Grid1D2DLike + The grid on which each light profile image is evaluated. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the image values. + positions : array-like or None + Point positions to scatter-plot over each panel. + lines : list or None + Line coordinates to overlay on each panel. + """ + from autogalaxy.profiles.light.linear import LightProfileLinear + + for light_profile in basis.light_profile_list: + if isinstance(light_profile, LightProfileLinear): + raise exc.raise_linear_light_profile_in_plot( + plotter_type="subplot_image", + ) + + n = len(basis.light_profile_list) + cols = min(n, 4) + rows = (n + cols - 1) // cols + fig, axes = plt.subplots(rows, cols, figsize=(7 * cols, 7 * rows)) + axes_flat = [axes] if n == 1 else list(np.array(axes).flatten()) + + _positions = _to_positions(positions) + + for i, light_profile in enumerate(basis.light_profile_list): + plot_array( + array=light_profile.image_2d_from(grid=grid), + title=light_profile.coefficient_tag, + colormap=colormap, + use_log10=use_log10, + positions=_positions, + lines=lines, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, "subplot_basis_image", output_format) diff --git a/autogalaxy/profiles/plot/basis_plotters.py b/autogalaxy/profiles/plot/basis_plotters.py deleted file mode 100644 index 93d4bbc6b..000000000 --- a/autogalaxy/profiles/plot/basis_plotters.py +++ /dev/null @@ -1,126 +0,0 @@ -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.profiles.light.abstract import LightProfile -from autogalaxy.profiles.basis import Basis -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.profiles.plot.light_profile_plotters import LightProfilePlotter - -from autogalaxy import exc - - -class BasisPlotter(Plotter): - def __init__( - self, - basis: Basis, - grid: aa.type.Grid1D2DLike, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of `Basis` objects using the matplotlib methods `plot()` and `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2D` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `LightProfile` and plotted via the visuals object. - - Parameters - ---------- - basis - The basis the plotter plots. - grid - The 2D (y,x) grid of coordinates used to evaluate the light profile quantities that are plotted. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - - from autogalaxy.profiles.light.linear import ( - LightProfileLinear, - ) - - for light_profile in basis.light_profile_list: - if isinstance(light_profile, LightProfileLinear): - raise exc.raise_linear_light_profile_in_plot( - plotter_type=self.__class__.__name__, - ) - - self.basis = basis - self.grid = grid - - super().__init__( - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - ) - - def light_profile_plotter_from( - self, - light_profile: LightProfile, - ) -> LightProfilePlotter: - """ - Returns a `LightProfilePlotter` given an input light profile, which is typically used for plotting the - individual light profiles of the plotter's `Galaxy` (e.g. in the function `figures_1d_decomposed`). - - Parameters - ---------- - light_profile - The light profile which is used to create the `LightProfilePlotter`. - - Returns - ------- - LightProfilePlotter - An object that plots the light profiles, often used for plotting attributes of the galaxy. - """ - - return LightProfilePlotter( - light_profile=light_profile, - grid=self.grid, - mat_plot_1d=self.mat_plot_1d, - visuals_1d=self.get_1d.via_light_obj_from(light_obj=light_profile), - ) - - def subplot_image(self): - """ - Plots the individual attributes of the plotter's `LightProfile` object in 2D, which are computed via the - plotter's 2D grid object. - - The API is such that every plottable attribute of the `LightProfile` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image. - """ - - self.open_subplot_figure(number_subplots=len(self.basis.light_profile_list)) - - for light_profile in self.basis.light_profile_list: - self.mat_plot_2d.plot_array( - array=light_profile.image_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels(title=light_profile.coefficient_tag), - ) - - self.mat_plot_2d.output.subplot_to_figure(auto_filename=f"subplot_basis_image") - - self.close_subplot_figure() diff --git a/autogalaxy/profiles/plot/light_profile_plotters.py b/autogalaxy/profiles/plot/light_profile_plotters.py deleted file mode 100644 index 7292f5eb0..000000000 --- a/autogalaxy/profiles/plot/light_profile_plotters.py +++ /dev/null @@ -1,96 +0,0 @@ -import autoarray as aa -import autoarray.plot as aplt - - -from autogalaxy.profiles.light.abstract import LightProfile -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy import exc - - -class LightProfilePlotter(Plotter): - def __init__( - self, - light_profile: LightProfile, - grid: aa.type.Grid1D2DLike, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of `LightProfile` objects using the matplotlib methods `plot()` and `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2D` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `LightProfile` and plotted via the visuals object. - - Parameters - ---------- - light_profile - The light profile the plotter plots. - grid - The 2D (y,x) grid of coordinates used to evaluate the light profile quantities that are plotted. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - - from autogalaxy.profiles.light.linear import ( - LightProfileLinear, - ) - - if isinstance(light_profile, LightProfileLinear): - raise exc.raise_linear_light_profile_in_plot( - plotter_type=self.__class__.__name__, - ) - - self.light_profile = light_profile - self.grid = grid - - super().__init__( - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - ) - - @property - def grid_2d_projected(self): - return self.grid.grid_2d_radial_projected_from( - centre=self.light_profile.centre, angle=self.light_profile.angle() - ) - - def figures_2d(self, image: bool = False): - """ - Plots the individual attributes of the plotter's `LightProfile` object in 2D, which are computed via the - plotter's 2D grid object. - - The API is such that every plottable attribute of the `LightProfile` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image. - """ - if image: - self.mat_plot_2d.plot_array( - array=self.light_profile.image_2d_from(grid=self.grid), - visuals_2d=self.visuals_2d, - auto_labels=aplt.AutoLabels(title="Image", filename="image_2d"), - ) diff --git a/autogalaxy/profiles/plot/mass_profile_plotters.py b/autogalaxy/profiles/plot/mass_profile_plotters.py deleted file mode 100644 index 62eaf8b88..000000000 --- a/autogalaxy/profiles/plot/mass_profile_plotters.py +++ /dev/null @@ -1,78 +0,0 @@ -import math -from typing import List, Optional - -import autoarray as aa -import autoarray.plot as aplt - -from autogalaxy.plot.mass_plotter import MassPlotter -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.profiles.mass.abstract.abstract import MassProfile -from autogalaxy.plot.mat_plot.one_d import MatPlot1D -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.one_d import Visuals1D -from autogalaxy.plot.visuals.two_d import Visuals2D - -from autogalaxy.util import error_util - - -class MassProfilePlotter(Plotter): - def __init__( - self, - mass_profile: MassProfile, - grid: aa.type.Grid2DLike, - mat_plot_1d: MatPlot1D = None, - visuals_1d: Visuals1D = None, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of `MassProfile` objects using the matplotlib methods `plot()` and `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2D` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `MassProfile` and plotted via the visuals object. - - Parameters - ---------- - mass_profile - The mass profile the plotter plots. - grid - The 2D (y,x) grid of coordinates used to evaluate the mass profile quantities that are plotted. - mat_plot_1d - Contains objects which wrap the matplotlib function calls that make 1D plots. - visuals_1d - Contains 1D visuals that can be overlaid on 1D plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - super().__init__( - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - mat_plot_1d=mat_plot_1d, - visuals_1d=visuals_1d, - ) - - self.mass_profile = mass_profile - self.grid = grid - - self._mass_plotter = MassPlotter( - mass_obj=self.mass_profile, - grid=self.grid, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - self.figures_2d = self._mass_plotter.figures_2d - - @property - def grid_2d_projected(self): - return self.grid.grid_2d_radial_projected_from( - centre=self.mass_profile.centre, angle=self.mass_profile.angle() - ) diff --git a/autogalaxy/quantity/model/plotter_interface.py b/autogalaxy/quantity/model/plotter_interface.py index 0a573b74e..1039c16df 100644 --- a/autogalaxy/quantity/model/plotter_interface.py +++ b/autogalaxy/quantity/model/plotter_interface.py @@ -1,92 +1,46 @@ -from autoconf.fitsable import hdu_list_for_output_from - -from autogalaxy.quantity.dataset_quantity import DatasetQuantity -from autogalaxy.quantity.fit_quantity import FitQuantity -from autogalaxy.quantity.plot.fit_quantity_plotters import FitQuantityPlotter -from autogalaxy.analysis.plotter_interface import PlotterInterface -from autogalaxy.analysis.plotter_interface import plot_setting -from autogalaxy.plot.visuals.two_d import Visuals2D - - -class PlotterInterfaceQuantity(PlotterInterface): - def dataset_quantity(self, dataset: DatasetQuantity): - """ - Output visualization of an `Imaging` dataset, typically before a model-fit is performed. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - is the output folder of the non-linear search. - - Visualization includes a subplot of the individual images of attributes of the dataset (e.g. the image, - noise map, PSF). - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `dataset` and `imaging` headers. - - Parameters - ---------- - dataset - The imaging dataset which is visualized. - """ - - image_list = [ - dataset.data.native_for_fits, - dataset.noise_map.native_for_fits, - ] - - hdu_list = hdu_list_for_output_from( - values_list=[ - image_list[0].mask.astype("float"), - ] - + image_list, - ext_name_list=[ - "mask", - "data", - "noise_map", - ], - header_dict=dataset.mask.header_dict, - ) - - hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) - - def fit_quantity( - self, - fit: FitQuantity, - visuals_2d: Visuals2D = None, - fit_quanaity_plotter_cls=FitQuantityPlotter, - ): - """ - Visualizes a `FitQuantity` object, which fits a quantity of a light or mass profile (e.g. an image, potential) - to the same quantity of another light or mass profile. - - Images are output to the `image` folder of the `image_path`. When used with a non-linear search the `image_path` - points to the search's results folder and this function visualizes the maximum log likelihood `FitQuantity` - inferred by the search so far. - - Visualization includes a subplot of individual images of attributes of the `FitQuantity` (e.g. the model data, - residual map). - - The images output by the `PlotterInterface` are customized using the file `config/visualize/plots.yaml` under - the `fit_quantity` header. - - Parameters - ---------- - fit - The maximum log likelihood `FitQuantity` of the non-linear search which is used to plot the fit. - visuals_2d - An object containing attributes which may be plotted over the figure (e.g. the centres of mass and light - profiles). - """ - - def should_plot(name): - return plot_setting(section="fit_quantity", name=name) - - mat_plot_2d = self.mat_plot_2d_from() - - fit_quantity_plotter = fit_quanaity_plotter_cls( - fit=fit, - mat_plot_2d=mat_plot_2d, - visuals_2d=visuals_2d, - ) - - if should_plot("subplot_fit"): - fit_quantity_plotter.subplot_fit() +from autoconf.fitsable import hdu_list_for_output_from + +from autogalaxy.quantity.dataset_quantity import DatasetQuantity +from autogalaxy.quantity.fit_quantity import FitQuantity +from autogalaxy.quantity.plot import fit_quantity_plots +from autogalaxy.analysis.plotter_interface import PlotterInterface, plot_setting + + +class PlotterInterfaceQuantity(PlotterInterface): + def dataset_quantity(self, dataset: DatasetQuantity): + image_list = [ + dataset.data.native_for_fits, + dataset.noise_map.native_for_fits, + ] + + hdu_list = hdu_list_for_output_from( + values_list=[ + image_list[0].mask.astype("float"), + ] + + image_list, + ext_name_list=[ + "mask", + "data", + "noise_map", + ], + header_dict=dataset.mask.header_dict, + ) + + hdu_list.writeto(self.image_path / "dataset.fits", overwrite=True) + + def fit_quantity( + self, + fit: FitQuantity, + fit_quantity_plots_module=None, + ): + def should_plot(name): + return plot_setting(section="fit_quantity", name=name) + + plots_module = fit_quantity_plots_module or fit_quantity_plots + + if should_plot("subplot_fit"): + plots_module.subplot_fit( + fit=fit, + output_path=self.image_path, + output_format=self.fmt, + ) diff --git a/autogalaxy/quantity/plot/fit_quantity_plots.py b/autogalaxy/quantity/plot/fit_quantity_plots.py new file mode 100644 index 000000000..5b16dfd51 --- /dev/null +++ b/autogalaxy/quantity/plot/fit_quantity_plots.py @@ -0,0 +1,103 @@ +import matplotlib.pyplot as plt + +import autoarray as aa + +from autogalaxy.quantity.fit_quantity import FitQuantity +from autogalaxy.plot.plot_utils import plot_array, _save_subplot + + +def _subplot_fit_array(fit, output_path, output_format, colormap, use_log10, positions, filename="subplot_fit"): + """Render a six-panel fit summary subplot for a single array-valued quantity fit. + + The panels show: data, signal-to-noise map, model image, residual map, + normalised residual map, and chi-squared map. This internal helper is + shared by both the scalar-array and vector-component paths in + :func:`subplot_fit`. + + Parameters + ---------- + fit + A fit object exposing ``.data``, ``.signal_to_noise_map``, + ``.model_data``, ``.residual_map``, ``.normalized_residual_map``, + and ``.chi_squared_map`` as ``Array2D``-like objects. + output_path : str or None + Directory in which to save the figure. ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + positions : array-like or None + Point positions to scatter-plot over each panel. + filename : str + Output filename stem (default ``"subplot_fit"``). + """ + panels = [ + (fit.data, "Data"), + (fit.signal_to_noise_map, "Signal-To-Noise Map"), + (fit.model_data, "Model Image"), + (fit.residual_map, "Residual Map"), + (fit.normalized_residual_map, "Normalized Residual Map"), + (fit.chi_squared_map, "Chi-Squared Map"), + ] + n = len(panels) + fig, axes = plt.subplots(1, n, figsize=(7 * n, 7)) + axes_flat = list(axes.flatten()) + + for i, (array, title) in enumerate(panels): + plot_array( + array=array, + title=title, + colormap=colormap, + use_log10=use_log10, + positions=positions, + ax=axes_flat[i], + ) + + plt.tight_layout() + _save_subplot(fig, output_path, filename, output_format) + + +def subplot_fit( + fit: FitQuantity, + output_path=None, + output_format="png", + colormap="default", + use_log10=False, + positions=None, +): + """Create a summary subplot for a :class:`~autogalaxy.quantity.fit_quantity.FitQuantity`. + + The output depends on the type of the dataset's data: + + - **Scalar** (``aa.Array2D``): produces a single six-panel subplot saved + as ``subplot_fit``. + - **Vector** (anything else, e.g. a deflection-angle grid): produces two + six-panel subplots, one for the y-component (``subplot_fit_y``) and one + for the x-component (``subplot_fit_x``). + + Parameters + ---------- + fit : FitQuantity + The completed quantity fit to visualise. + output_path : str or None + Directory in which to save the figure(s). ``None`` → ``plt.show()``. + output_format : str + File format, e.g. ``"png"``. + colormap : str + Matplotlib colormap name, or ``"default"``. + use_log10 : bool + Apply a log₁₀ stretch to the plotted values. + positions : array-like or None + Point positions to scatter-plot over each panel. + """ + if isinstance(fit.dataset.data, aa.Array2D): + _subplot_fit_array(fit, output_path, output_format, colormap, use_log10, positions) + else: + _subplot_fit_array( + fit.y, output_path, output_format, colormap, use_log10, positions, filename="subplot_fit_y" + ) + _subplot_fit_array( + fit.x, output_path, output_format, colormap, use_log10, positions, filename="subplot_fit_x" + ) diff --git a/autogalaxy/quantity/plot/fit_quantity_plotters.py b/autogalaxy/quantity/plot/fit_quantity_plotters.py deleted file mode 100644 index d0371ab3f..000000000 --- a/autogalaxy/quantity/plot/fit_quantity_plotters.py +++ /dev/null @@ -1,186 +0,0 @@ -import autoarray as aa - -from autoarray.fit.plot.fit_imaging_plotters import FitImagingPlotterMeta - -from autogalaxy.quantity.fit_quantity import FitQuantity - -from autogalaxy.plot.abstract_plotters import Plotter -from autogalaxy.plot.mat_plot.two_d import MatPlot2D -from autogalaxy.plot.visuals.two_d import Visuals2D - - -# TODO : Ew, this is a mass, but it works. Clean up one day! - - -class FitQuantityPlotter(Plotter): - def __init__( - self, - fit: FitQuantity, - mat_plot_2d: MatPlot2D = None, - visuals_2d: Visuals2D = None, - ): - """ - Plots the attributes of `FitQuantity` objects using the matplotlib method `imshow()` and many - other matplotlib functions which customize the plot's appearance. - - The `mat_plot_1d` and `mat_plot_2d` attributes wrap matplotlib function calls to make the figure. By default, - the settings passed to every matplotlib function called are those specified in - the `config/visualize/mat_wrap/*.ini` files, but a user can manually input values into `MatPlot2d` to - customize the figure's appearance. - - Overlaid on the figure are visuals, contained in the `Visuals1D` and `Visuals2D` objects. Attributes may be - extracted from the `FitQuantity` and plotted via the visuals object. - - Parameters - ---------- - fit - The fit to an interferometer dataset the plotter plots. - mat_plot_2d - Contains objects which wrap the matplotlib function calls that make 2D plots. - visuals_2d - Contains 2D visuals that can be overlaid on 2D plots. - """ - super().__init__(mat_plot_2d=mat_plot_2d, visuals_2d=visuals_2d) - - self.fit = fit - - def figures_2d( - self, - image: bool = False, - noise_map: bool = False, - signal_to_noise_map: bool = False, - model_image: bool = False, - residual_map: bool = False, - normalized_residual_map: bool = False, - chi_squared_map: bool = False, - ): - """ - Plots the individual attributes of the plotter's `FitImaging` object in 2D. - - The API is such that every plottable attribute of the `FitImaging` object is an input parameter of type bool of - the function, which if switched to `True` means that it is plotted. - - Parameters - ---------- - image - Whether to make a 2D plot (via `imshow`) of the image data. - noise_map - Whether to make a 2D plot (via `imshow`) of the noise map. - signal_to_noise_map - Whether to make a 2D plot (via `imshow`) of the signal-to-noise map. - model_image - Whether to make a 2D plot (via `imshow`) of the model image. - residual_map - Whether to make a 2D plot (via `imshow`) of the residual map. - normalized_residual_map - Whether to make a 2D plot (via `imshow`) of the normalized residual map. - chi_squared_map - Whether to make a 2D plot (via `imshow`) of the chi-squared map. - """ - - if isinstance(self.fit.dataset.data, aa.Array2D): - fit_plotter = FitImagingPlotterMeta( - fit=self.fit, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter.figures_2d( - data=image, - noise_map=noise_map, - signal_to_noise_map=signal_to_noise_map, - model_image=model_image, - residual_map=residual_map, - normalized_residual_map=normalized_residual_map, - chi_squared_map=chi_squared_map, - ) - - else: - fit_plotter_y = FitImagingPlotterMeta( - fit=self.fit.y, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter_y.figures_2d( - data=image, - noise_map=noise_map, - signal_to_noise_map=signal_to_noise_map, - model_image=model_image, - residual_map=residual_map, - normalized_residual_map=normalized_residual_map, - chi_squared_map=chi_squared_map, - suffix="_y", - ) - - fit_plotter_x = FitImagingPlotterMeta( - fit=self.fit.y, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter_x.figures_2d( - data=image, - noise_map=noise_map, - signal_to_noise_map=signal_to_noise_map, - model_image=model_image, - residual_map=residual_map, - normalized_residual_map=normalized_residual_map, - chi_squared_map=chi_squared_map, - suffix="_x", - ) - - def subplot_fit(self): - """ - Standard subplot of the attributes of the plotter's `FitQuantity` object. - """ - - if isinstance(self.fit.dataset.data, aa.Array2D): - fit_plotter = FitImagingPlotterMeta( - fit=self.fit, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter.subplot( - data=True, - signal_to_noise_map=True, - model_image=True, - residual_map=True, - normalized_residual_map=True, - chi_squared_map=True, - auto_filename="subplot_fit", - ) - - else: - fit_plotter_y = FitImagingPlotterMeta( - fit=self.fit.y, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter_y.subplot( - data=True, - signal_to_noise_map=True, - model_image=True, - residual_map=True, - normalized_residual_map=True, - chi_squared_map=True, - auto_filename="subplot_fit_y", - ) - - fit_plotter_x = FitImagingPlotterMeta( - fit=self.fit.x, - mat_plot_2d=self.mat_plot_2d, - visuals_2d=self.visuals_2d, - ) - - fit_plotter_x.subplot( - data=True, - signal_to_noise_map=True, - model_image=True, - residual_map=True, - normalized_residual_map=True, - chi_squared_map=True, - auto_filename="subplot_fit_x", - ) diff --git a/test_autogalaxy/config/visualize.yaml b/test_autogalaxy/config/visualize.yaml index bc57d7878..0bd92eac6 100644 --- a/test_autogalaxy/config/visualize.yaml +++ b/test_autogalaxy/config/visualize.yaml @@ -4,11 +4,6 @@ general: imshow_origin: upper zoom_around_mask: true mat_wrap: - Axis: - figure: - emit: true - subplot: - emit: false Cmap: figure: cmap: default @@ -31,305 +26,6 @@ mat_wrap: subplot: fraction: 0.1 pad: 0.2 - ColorbarTickParams: - figure: - labelsize: 1 - subplot: - labelsize: 1 - Figure: - figure: - aspect: square - figsize: (7,7) - subplot: - aspect: square - figsize: auto - Legend: - figure: - fontsize: 12 - include: true - subplot: - fontsize: 13 - include: false - Text: - figure: - fontsize: 16 - subplot: - fontsize: 10 - TickParams: - figure: - labelsize: 16 - subplot: - labelsize: 10 - Title: - figure: - fontsize: 11 - subplot: - fontsize: 15 - XLabel: - figure: - fontsize: 3 - subplot: - fontsize: 4 - XTicks: - figure: - fontsize: 17 - subplot: - fontsize: 11 - YLabel: - figure: - fontsize: 1 - subplot: - fontsize: 2 - YTicks: - figure: - fontsize: 16 - subplot: - fontsize: 10 -mat_wrap_1d: - AXVLine: - figure: - c: k - ymin: 0.5 - subplot: - c: k - ymin: 0.6 - FillBetween: - figure: - alpha: 0.6 - color: k - subplot: - alpha: 0.5 - color: k - YXPlot: - figure: - c: k - linestyle: '-' - linewidth: 3 - subplot: - c: k - linestyle: '-' - linewidth: 1 - YXScatter: - figure: - c: k - marker: . - subplot: - c: k - marker: x - EinsteinRadiusAXVLine: - figure: {} - subplot: {} - HalfLightRadiusAXVLine: - figure: {} - subplot: {} -mat_wrap_2d: - ArrayOverlay: - figure: - alpha: 0.5 - subplot: - alpha: 0.7 - BorderScatter: - figure: - c: c - marker: + - s: 13 - subplot: - c: k - marker: . - s: 7 - GridErrorbar: - figure: - c: k - marker: o - subplot: - c: b - marker: . - GridPlot: - figure: - c: k - linestyle: '-' - linewidth: 3 - subplot: - c: k - linestyle: '-' - linewidth: 1 - GridScatter: - figure: - c: y - marker: x - s: 14 - subplot: - c: r - marker: . - s: 6 - IndexScatter: - figure: - c: r,g,b,y,k,w - marker: . - s: 20 - subplot: - c: r,g,b,y,w,k - marker: + - s: 21 - MaskScatter: - figure: - c: g - marker: . - s: 12 - subplot: - c: w - marker: . - s: 8 - MeshGridScatter: - figure: - c: r - marker: . - s: 5 - subplot: - c: g - marker: o - s: 6 - OriginScatter: - figure: - c: k - marker: x - s: 80 - subplot: - c: r - marker: . - s: 81 - ParallelOverscanPlot: - figure: - c: k - linestyle: '-' - linewidth: 1 - subplot: - c: k - linestyle: '-' - linewidth: 1 - PatchOverlay: - figure: - edgecolor: c - facecolor: null - subplot: - edgecolor: y - facecolor: null - PositionsScatter: - figure: - c: r,g,b - marker: o - s: 15 - subplot: - c: c,g,b - marker: . - s: 5 - SerialOverscanPlot: - figure: - c: k - linestyle: '-' - linewidth: 2 - subplot: - c: k - linestyle: '-' - linewidth: 1 - SerialPrescanPlot: - figure: - c: k - linestyle: '-' - linewidth: 3 - subplot: - c: k - linestyle: '-' - linewidth: 1 - VectorYXQuiver: - figure: - alpha: 1.0 - angles: xy - headlength: 0 - headwidth: 1 - linewidth: 5 - pivot: middle - units: xy - subplot: - alpha: 1.1 - angles: xy1 - headlength: 0.1 - headwidth: 11 - linewidth: 51 - pivot: middle1 - units: xy1 - VoronoiDrawer: - figure: - alpha: 0.7 - edgecolor: k - linewidth: 0.3 - subplot: - alpha: 0.5 - edgecolor: r - linewidth: 1.0 - TangentialCausticsPlot: - figure: - c: w,g - linestyle: -- - linewidth: 5 - subplot: - c: g - linestyle: -- - linewidth: 7 - TangentialCriticalCurvesPlot: - figure: - c: w,k - linestyle: '-' - linewidth: 4 - subplot: - c: b - linestyle: '-' - linewidth: 6 - RadialCausticsPlot: - figure: - c: w,g - linestyle: -- - linewidth: 5 - subplot: - c: g - linestyle: -- - linewidth: 7 - RadialCriticalCurvesPlot: - figure: - c: w,k - linestyle: '-' - linewidth: 4 - subplot: - c: b - linestyle: '-' - linewidth: 6 - LightProfileCentresScatter: - figure: - c: k,r - marker: + - s: 1 - subplot: - c: b - marker: . - s: 15 - MassProfileCentresScatter: - figure: - c: r,k - marker: x - s: 2 - subplot: - c: k - marker: o - s: 16 - MultipleImagesScatter: - figure: - c: k,w - marker: o - s: 3 - subplot: - c: g - marker: . - s: 17 plots: fits_are_zoomed: true dataset: @@ -337,22 +33,22 @@ plots: fit: subplot_fit: true subplot_of_galaxies: false - fits_fit: true # Output a .fits file containing the fit model data, residual map, normalized residual map and chi-squared? - fits_galaxy_images : true # Output a .fits file containing the images (e.g. without PSF convolution) of every galaxy? - fits_model_galaxy_images : true # Output a .fits file containing the model images (e.g. with PSF convolution) of every galaxy? + fits_fit: true + fits_galaxy_images : true + fits_model_galaxy_images : true fit_imaging: {} fit_interferometer: - fits_dirty_images: true # output dirty_images.fits showing the dirty image, noise-map, model-data, resiual-map, normalized residual map and chi-squared map? + fits_dirty_images: true fit_quantity: subplot_fit: false adapt: subplot_adapt_images: true inversion: subplot_inversion: true - csv_reconstruction: true # output reconstruction_mesh.fits containing the reconstructed pixelization and noise map on the source-plane mesh? + csv_reconstruction: true galaxies: subplot_galaxies: true subplot_galaxy_images: true - fits_galaxy_images: true # Output a .fits file containing images of every galaxy? + fits_galaxy_images: true positions: image_with_positions: true diff --git a/test_autogalaxy/conftest.py b/test_autogalaxy/conftest.py index 9c8027d9c..45ca6de37 100644 --- a/test_autogalaxy/conftest.py +++ b/test_autogalaxy/conftest.py @@ -29,8 +29,11 @@ def __call__(self, path, *args, **kwargs): @pytest.fixture(name="plot_patch") def make_plot_patch(monkeypatch): + import matplotlib.figure + plot_patch = PlotPatch() monkeypatch.setattr(pyplot, "savefig", plot_patch) + monkeypatch.setattr(matplotlib.figure.Figure, "savefig", plot_patch) return plot_patch diff --git a/test_autogalaxy/galaxy/plot/test_adapt_plotters.py b/test_autogalaxy/galaxy/plot/test_adapt_plotters.py index 5b3a2fa68..2b9c7a8fe 100644 --- a/test_autogalaxy/galaxy/plot/test_adapt_plotters.py +++ b/test_autogalaxy/galaxy/plot/test_adapt_plotters.py @@ -1,27 +1,25 @@ -from os import path - -import autogalaxy.plot as aplt -import pytest - - -@pytest.fixture(name="plot_path") -def make_adapt_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), - "files", - "plots", - "adapt", - ) - - -def test__plot_adapt_adapt_images( - adapt_galaxy_name_image_dict_7x7, mask_2d_7x7, plot_path, plot_patch -): - adapt_plotter = aplt.AdaptPlotter( - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - adapt_plotter.subplot_adapt_images( - adapt_galaxy_name_image_dict=adapt_galaxy_name_image_dict_7x7 - ) - assert path.join(plot_path, "subplot_adapt_images.png") in plot_patch.paths +from os import path + +import autogalaxy.plot as aplt +import pytest + + +@pytest.fixture(name="plot_path") +def make_adapt_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), + "files", + "plots", + "adapt", + ) + + +def test__plot_adapt_adapt_images( + adapt_galaxy_name_image_dict_7x7, mask_2d_7x7, plot_path, plot_patch +): + aplt.subplot_adapt_images( + adapt_galaxy_name_image_dict=adapt_galaxy_name_image_dict_7x7, + output_path=plot_path, + output_format="png", + ) + assert path.join(plot_path, "subplot_adapt_images.png") in plot_patch.paths diff --git a/test_autogalaxy/galaxy/plot/test_galaxies_plotter.py b/test_autogalaxy/galaxy/plot/test_galaxies_plotter.py index af14dc927..19ee06d6a 100644 --- a/test_autogalaxy/galaxy/plot/test_galaxies_plotter.py +++ b/test_autogalaxy/galaxy/plot/test_galaxies_plotter.py @@ -1,75 +1,31 @@ -from os import path - -import autogalaxy as ag -import autogalaxy.plot as aplt -import pytest - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "galaxies" - ) - - -def test__all_individual_plotter__output_file_with_default_name( - galaxies_7x7, - grid_2d_7x7, - mask_2d_7x7, - grid_2d_irregular_7x7_list, - plot_path, - plot_patch, -): - plotter = aplt.GalaxiesPlotter( - galaxies=galaxies_7x7, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - plotter.figures_2d(image=True, convergence=True) - - assert path.join(plot_path, "image_2d.png") in plot_patch.paths - assert path.join(plot_path, "convergence_2d.png") in plot_patch.paths - - -def test__figures_of_galaxies( - galaxies_x2_7x7, - grid_2d_7x7, - mask_2d_7x7, - plot_path, - plot_patch, -): - plotter = aplt.GalaxiesPlotter( - galaxies=galaxies_x2_7x7, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(path=plot_path, format="png")), - ) - - plotter.figures_2d_of_galaxies(image=True) - - assert path.join(plot_path, "image_2d_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "image_2d_of_galaxy_1.png") in plot_patch.paths - - plot_patch.paths = [] - - plotter.figures_2d_of_galaxies(image=True, galaxy_index=0) - - assert path.join(plot_path, "image_2d_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "image_2d_of_galaxy_1.png") not in plot_patch.paths - - -def test__galaxies_sub_plot_output(galaxies_x2_7x7, grid_2d_7x7, plot_path, plot_patch): - plotter = aplt.GalaxiesPlotter( - galaxies=galaxies_x2_7x7, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - mat_plot_1d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - plotter.subplot_galaxies() - assert path.join(plot_path, "subplot_galaxies.png") in plot_patch.paths - - plotter.subplot_galaxy_images() - assert path.join(plot_path, "subplot_galaxy_images.png") in plot_patch.paths +from os import path + +import autogalaxy.plot as aplt +import pytest + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "galaxies" + ) + + +def test__galaxies_sub_plot_output(galaxies_x2_7x7, grid_2d_7x7, plot_path, plot_patch): + aplt.subplot_galaxies( + galaxies=galaxies_x2_7x7, + grid=grid_2d_7x7, + output_path=plot_path, + output_format="png", + ) + assert path.join(plot_path, "subplot_galaxies.png") in plot_patch.paths + + aplt.subplot_galaxy_images( + galaxies=galaxies_x2_7x7, + grid=grid_2d_7x7, + output_path=plot_path, + output_format="png", + ) + assert path.join(plot_path, "subplot_galaxy_images.png") in plot_patch.paths diff --git a/test_autogalaxy/galaxy/plot/test_galaxy_plotters.py b/test_autogalaxy/galaxy/plot/test_galaxy_plotters.py index e14916496..e7f60020b 100644 --- a/test_autogalaxy/galaxy/plot/test_galaxy_plotters.py +++ b/test_autogalaxy/galaxy/plot/test_galaxy_plotters.py @@ -1,59 +1,45 @@ -from os import path - -import autogalaxy as ag -import autogalaxy.plot as aplt -import pytest - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_galaxy_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "galaxy" - ) - - -def test__figures_2d__all_are_output( - gal_x1_lp_x1_mp, - grid_2d_7x7, - mask_2d_7x7, - grid_2d_irregular_7x7_list, - plot_path, - plot_patch, -): - galaxy_plotter = aplt.GalaxyPlotter( - galaxy=gal_x1_lp_x1_mp, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - galaxy_plotter.figures_2d(image=True, convergence=True) - - assert path.join(plot_path, "image_2d.png") in plot_patch.paths - assert path.join(plot_path, "convergence_2d.png") in plot_patch.paths - - -def test__subplots_galaxy_quantities__all_are_output( - gal_x1_lp_x1_mp, - grid_2d_7x7, - grid_2d_irregular_7x7_list, - plot_path, - plot_patch, -): - galaxy_plotter = aplt.GalaxyPlotter( - galaxy=gal_x1_lp_x1_mp, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - galaxy_plotter.subplot_of_light_profiles(image=True) - - assert path.join(plot_path, "subplot_image.png") in plot_patch.paths - - galaxy_plotter.subplot_of_mass_profiles( - convergence=True, potential=True, deflections_y=True, deflections_x=True - ) - - assert path.join(plot_path, "subplot_convergence.png") in plot_patch.paths - assert path.join(plot_path, "subplot_potential.png") in plot_patch.paths - assert path.join(plot_path, "subplot_deflections_y.png") in plot_patch.paths - assert path.join(plot_path, "subplot_deflections_x.png") in plot_patch.paths +from os import path + +import autogalaxy.plot as aplt +import pytest + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_galaxy_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "galaxy" + ) + + +def test__subplots_galaxy_quantities__all_are_output( + gal_x1_lp_x1_mp, + grid_2d_7x7, + plot_path, + plot_patch, +): + aplt.subplot_galaxy_light_profiles( + galaxy=gal_x1_lp_x1_mp, + grid=grid_2d_7x7, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_image.png") in plot_patch.paths + + aplt.subplot_galaxy_mass_profiles( + galaxy=gal_x1_lp_x1_mp, + grid=grid_2d_7x7, + convergence=True, + potential=True, + deflections_y=True, + deflections_x=True, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_convergence.png") in plot_patch.paths + assert path.join(plot_path, "subplot_potential.png") in plot_patch.paths + assert path.join(plot_path, "subplot_deflections_y.png") in plot_patch.paths + assert path.join(plot_path, "subplot_deflections_x.png") in plot_patch.paths diff --git a/test_autogalaxy/imaging/plot/test_fit_imaging_plotters.py b/test_autogalaxy/imaging/plot/test_fit_imaging_plotters.py index 6ed807b02..ce07d8f0a 100644 --- a/test_autogalaxy/imaging/plot/test_fit_imaging_plotters.py +++ b/test_autogalaxy/imaging/plot/test_fit_imaging_plotters.py @@ -1,87 +1,43 @@ -from os import path - -import pytest - -import autogalaxy.plot as aplt - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_fit_imaging_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "fit" - ) - - -def test__fit_individuals__source_and_galaxy__dependent_on_input( - fit_imaging_x2_galaxy_7x7, plot_path, plot_patch -): - fit_plotter = aplt.FitImagingPlotter( - fit=fit_imaging_x2_galaxy_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_plotter.figures_2d( - data=True, - noise_map=False, - signal_to_noise_map=False, - model_image=True, - chi_squared_map=True, - ) - - assert path.join(plot_path, "data.png") in plot_patch.paths - assert path.join(plot_path, "noise_map.png") not in plot_patch.paths - assert path.join(plot_path, "signal_to_noise_map.png") not in plot_patch.paths - assert path.join(plot_path, "model_image.png") in plot_patch.paths - assert path.join(plot_path, "residual_map.png") not in plot_patch.paths - assert path.join(plot_path, "normalized_residual_map.png") not in plot_patch.paths - assert path.join(plot_path, "chi_squared_map.png") in plot_patch.paths - - -def test__figures_of_galaxies(fit_imaging_x2_galaxy_7x7, plot_path, plot_patch): - fit_plotter = aplt.FitImagingPlotter( - fit=fit_imaging_x2_galaxy_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_plotter.figures_2d_of_galaxies(subtracted_image=True) - - assert path.join(plot_path, "subtracted_image_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "subtracted_image_of_galaxy_1.png") in plot_patch.paths - - fit_plotter.figures_2d_of_galaxies(model_image=True) - - assert path.join(plot_path, "model_image_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "model_image_of_galaxy_1.png") in plot_patch.paths - - plot_patch.paths = [] - - fit_plotter.figures_2d_of_galaxies(galaxy_index=0, subtracted_image=True) - - assert path.join(plot_path, "subtracted_image_of_galaxy_0.png") in plot_patch.paths - assert ( - path.join(plot_path, "subtracted_image_of_galaxy_1.png") not in plot_patch.paths - ) - - fit_plotter.figures_2d_of_galaxies(galaxy_index=1, model_image=True) - - assert path.join(plot_path, "model_image_of_galaxy_0.png") not in plot_patch.paths - assert path.join(plot_path, "model_image_of_galaxy_1.png") in plot_patch.paths - - -def test__subplot_of_galaxy(fit_imaging_x2_galaxy_7x7, plot_path, plot_patch): - fit_plotter = aplt.FitImagingPlotter( - fit=fit_imaging_x2_galaxy_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - fit_plotter.subplot_of_galaxies() - assert path.join(plot_path, "subplot_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "subplot_of_galaxy_1.png") in plot_patch.paths - - plot_patch.paths = [] - - fit_plotter.subplot_of_galaxies(galaxy_index=0) - - assert path.join(plot_path, "subplot_of_galaxy_0.png") in plot_patch.paths - assert path.join(plot_path, "subplot_of_galaxy_1.png") not in plot_patch.paths +from os import path + +import pytest + +import autogalaxy.plot as aplt + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_fit_imaging_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "fit" + ) + + +def test__subplot_of_galaxy(fit_imaging_x2_galaxy_7x7, plot_path, plot_patch): + aplt.subplot_fit_imaging_of_galaxy( + fit=fit_imaging_x2_galaxy_7x7, + galaxy_index=0, + output_path=plot_path, + output_format="png", + ) + aplt.subplot_fit_imaging_of_galaxy( + fit=fit_imaging_x2_galaxy_7x7, + galaxy_index=1, + output_path=plot_path, + output_format="png", + ) + assert path.join(plot_path, "subplot_of_galaxy_0.png") in plot_patch.paths + assert path.join(plot_path, "subplot_of_galaxy_1.png") in plot_patch.paths + + plot_patch.paths = [] + + aplt.subplot_fit_imaging_of_galaxy( + fit=fit_imaging_x2_galaxy_7x7, + galaxy_index=0, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_of_galaxy_0.png") in plot_patch.paths + assert path.join(plot_path, "subplot_of_galaxy_1.png") not in plot_patch.paths diff --git a/test_autogalaxy/interferometer/plot/test_fit_interferometer_plotters.py b/test_autogalaxy/interferometer/plot/test_fit_interferometer_plotters.py index 174351865..09b3e0ef0 100644 --- a/test_autogalaxy/interferometer/plot/test_fit_interferometer_plotters.py +++ b/test_autogalaxy/interferometer/plot/test_fit_interferometer_plotters.py @@ -1,38 +1,36 @@ -from os import path - -import autogalaxy.plot as aplt -import pytest - - -@pytest.fixture(name="plot_path") -def make_fit_dataset_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "fit" - ) - - -def test__fit_sub_plot_real_space( - fit_interferometer_7x7, - fit_interferometer_x2_galaxy_inversion_7x7, - plot_path, - plot_patch, -): - fit_plotter = aplt.FitInterferometerPlotter( - fit=fit_interferometer_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_plotter.subplot_fit_real_space() - - assert path.join(plot_path, "subplot_fit_real_space.png") in plot_patch.paths - - plot_patch.paths = [] - - fit_plotter = aplt.FitInterferometerPlotter( - fit=fit_interferometer_x2_galaxy_inversion_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_plotter.subplot_fit_real_space() - - assert path.join(plot_path, "subplot_fit_real_space.png") in plot_patch.paths +from os import path + +import autogalaxy.plot as aplt +import pytest + + +@pytest.fixture(name="plot_path") +def make_fit_dataset_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "fit" + ) + + +def test__fit_sub_plot_real_space( + fit_interferometer_7x7, + fit_interferometer_x2_galaxy_inversion_7x7, + plot_path, + plot_patch, +): + aplt.subplot_fit_real_space( + fit=fit_interferometer_7x7, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_fit_real_space.png") in plot_patch.paths + + plot_patch.paths = [] + + aplt.subplot_fit_real_space( + fit=fit_interferometer_x2_galaxy_inversion_7x7, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_fit_real_space.png") in plot_patch.paths diff --git a/test_autogalaxy/operate/test_deflections.py b/test_autogalaxy/operate/test_deflections.py index ee4617979..cfa933b43 100644 --- a/test_autogalaxy/operate/test_deflections.py +++ b/test_autogalaxy/operate/test_deflections.py @@ -92,7 +92,7 @@ def test__fermat_potential_from(): ) -def test__hessian_from(): +def test__hessian_from__diagonal_grid__correct_values(): grid = ag.Grid2DIrregular(values=[(0.5, 0.5), (1.0, 1.0)]) mp = ag.mp.Isothermal( @@ -107,8 +107,15 @@ def test__hessian_from(): assert hessian_yx == pytest.approx(np.array([-1.388165, -0.694099]), 1.0e-4) assert hessian_xx == pytest.approx(np.array([1.3883824, 0.694127]), 1.0e-4) + +def test__hessian_from__axis_aligned_grid__correct_values(): grid = ag.Grid2DIrregular(values=[(1.0, 0.0), (0.0, 1.0)]) + mp = ag.mp.Isothermal( + centre=(0.0, 0.0), ell_comps=(0.0, -0.111111), einstein_radius=2.0 + ) + + od = LensCalc.from_mass_obj(mp) hessian_yy, hessian_xy, hessian_yx, hessian_xx = od.hessian_from(grid=grid) assert hessian_yy == pytest.approx(np.array([0.0, 1.777699]), 1.0e-4) @@ -149,7 +156,7 @@ def test__magnification_2d_via_hessian_from(): assert magnification.in_list[1] == pytest.approx(-2.57591, 1.0e-4) -def test__tangential_critical_curve_list_from(): +def test__tangential_critical_curve_list_from__radius_matches_einstein_radius(): grid = ag.Grid2D.uniform(shape_native=(15, 15), pixel_scales=0.3) mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) @@ -166,6 +173,8 @@ def test__tangential_critical_curve_list_from(): x_critical_tangential**2 + y_critical_tangential**2 ) == pytest.approx(mp.einstein_radius**2, 5e-1) + +def test__tangential_critical_curve_list_from__centre_at_origin__curve_centred_on_origin(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) @@ -179,6 +188,10 @@ def test__tangential_critical_curve_list_from(): assert -0.03 < y_centre < 0.03 assert -0.03 < x_centre < 0.03 + +def test__tangential_critical_curve_list_from__offset_centre__curve_centred_on_offset(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.IsothermalSph(centre=(0.5, 1.0), einstein_radius=2.0) od = LensCalc.from_mass_obj(mp) @@ -225,7 +238,7 @@ def test__tangential_critical_curve_list_from(): # ) -def test__radial_critical_curve_list_from(): +def test__radial_critical_curve_list_from__centre_at_origin__curve_centred_on_origin(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=2.0, slope=1.5) @@ -239,6 +252,10 @@ def test__radial_critical_curve_list_from(): assert -0.05 < y_centre < 0.05 assert -0.05 < x_centre < 0.05 + +def test__radial_critical_curve_list_from__offset_centre__curve_centred_on_offset(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.PowerLawSph(centre=(0.5, 1.0), einstein_radius=2.0, slope=1.5) od = LensCalc.from_mass_obj(mp) @@ -271,7 +288,7 @@ def test__radial_critical_curve_list_from__compare_via_magnification(): ) -def test__tangential_caustic_list_from(): +def test__tangential_caustic_list_from__centre_at_origin__caustic_centred_on_origin(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) @@ -285,6 +302,10 @@ def test__tangential_caustic_list_from(): assert -0.03 < y_centre < 0.03 assert -0.03 < x_centre < 0.03 + +def test__tangential_caustic_list_from__offset_centre__caustic_centred_on_offset(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.IsothermalSph(centre=(0.5, 1.0), einstein_radius=2.0) od = LensCalc.from_mass_obj(mp) @@ -319,7 +340,7 @@ def test__tangential_caustic_list_from(): # ) -def test__radial_caustic_list_from(): +def test__radial_caustic_list_from__radius_check__correct_mean_radius(): grid = ag.Grid2D.uniform(shape_native=(20, 20), pixel_scales=0.2) mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=2.0, slope=1.5) @@ -336,6 +357,8 @@ def test__radial_caustic_list_from(): 0.25, 5e-1 ) + +def test__radial_caustic_list_from__centre_at_origin__caustic_centred_on_origin(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=2.0, slope=1.5) @@ -349,6 +372,10 @@ def test__radial_caustic_list_from(): assert -0.2 < y_centre < 0.2 assert -0.35 < x_centre < 0.35 + +def test__radial_caustic_list_from__offset_centre__caustic_centred_near_offset(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.PowerLawSph(centre=(0.5, 1.0), einstein_radius=2.0, slope=1.5) od = LensCalc.from_mass_obj(mp) @@ -410,7 +437,7 @@ def test__tangential_critical_curve_area_list_from(): ) -def test__einstein_radius_list_from(): +def test__einstein_radius_list_from__isothermal_sph__correct_einstein_radius(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) @@ -420,6 +447,10 @@ def test__einstein_radius_list_from(): assert einstein_radius_list[0] == pytest.approx(2.0, 1e-1) + +def test__einstein_radius_list_from__isothermal_elliptical__correct_einstein_radius(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.Isothermal( centre=(0.0, 0.0), einstein_radius=2.0, ell_comps=(0.0, -0.25) ) @@ -430,7 +461,7 @@ def test__einstein_radius_list_from(): assert einstein_radius_list[0] == pytest.approx(1.9360, 1e-1) -def test__einstein_radius_from(): +def test__einstein_radius_from__isothermal_sph__correct_einstein_radius(): grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) @@ -440,6 +471,10 @@ def test__einstein_radius_from(): assert einstein_radius == pytest.approx(2.0, 1e-1) + +def test__einstein_radius_from__isothermal_elliptical__correct_einstein_radius(): + grid = ag.Grid2D.uniform(shape_native=(50, 50), pixel_scales=0.2) + mp = ag.mp.Isothermal( centre=(0.0, 0.0), einstein_radius=2.0, ell_comps=(0.0, -0.25) ) diff --git a/test_autogalaxy/plot/mat_wrap/test_mat_obj.py b/test_autogalaxy/plot/mat_wrap/test_mat_obj.py deleted file mode 100644 index 53e4f154e..000000000 --- a/test_autogalaxy/plot/mat_wrap/test_mat_obj.py +++ /dev/null @@ -1,31 +0,0 @@ -import autogalaxy.plot as aplt - - -def test__mat_obj__all_load_from_config_correctly(): - light_profile_centres_scatter = aplt.LightProfileCentresScatter() - - assert light_profile_centres_scatter.config_dict["s"] == 1 - - mass_profile_centres_scatter = aplt.MassProfileCentresScatter() - - assert mass_profile_centres_scatter.config_dict["s"] == 2 - - multiple_images_scatter = aplt.MultipleImagesScatter() - - assert multiple_images_scatter.config_dict["s"] == 3 - - tangential_critical_curves_plot = aplt.TangentialCriticalCurvesPlot() - - assert tangential_critical_curves_plot.config_dict["linewidth"] == 4 - - tangential_caustics_plot = aplt.TangentialCausticsPlot() - - assert tangential_caustics_plot.config_dict["linewidth"] == 5 - - radial_critical_curves_plot = aplt.RadialCriticalCurvesPlot() - - assert radial_critical_curves_plot.config_dict["linewidth"] == 4 - - radial_caustics_plot = aplt.RadialCausticsPlot() - - assert radial_caustics_plot.config_dict["linewidth"] == 5 diff --git a/test_autogalaxy/plot/mat_wrap/test_visuals.py b/test_autogalaxy/plot/mat_wrap/test_visuals.py index 8051898ce..f1f529228 100644 --- a/test_autogalaxy/plot/mat_wrap/test_visuals.py +++ b/test_autogalaxy/plot/mat_wrap/test_visuals.py @@ -1,85 +1,59 @@ -from os import path -import pytest - -import autogalaxy.plot as aplt -from autogalaxy.operate.lens_calc import LensCalc - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_profile_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" - ) - - -def test__1d__add_half_light_radius(lp_0): - - visuals_1d_via = aplt.Visuals1D().add_half_light_radius(light_obj=lp_0) - - assert visuals_1d_via.half_light_radius == lp_0.half_light_radius - - -def test__1d__add_half_light_radius_errors(lp_0): - - visuals_1d_via = aplt.Visuals1D().add_half_light_radius_errors( - light_obj_list=[lp_0, lp_0], low_limit=1.0 - ) - - assert visuals_1d_via.half_light_radius == lp_0.half_light_radius - assert visuals_1d_via.half_light_radius_errors[0][0] == lp_0.half_light_radius - - -def test__1d__add_einstein_radius(mp_0, grid_2d_7x7): - - visuals_1d_via = aplt.Visuals1D().add_einstein_radius( - mass_obj=mp_0, grid=grid_2d_7x7 - ) - - assert visuals_1d_via.einstein_radius == LensCalc.from_mass_obj( - mp_0 - ).einstein_radius_from(grid=grid_2d_7x7) - - -def test__1d__add_einstein_radius_errors(mp_0, grid_2d_7x7): - - visuals_1d_via = aplt.Visuals1D().add_einstein_radius_errors( - mass_obj_list=[mp_0, mp_0], grid=grid_2d_7x7, low_limit=1.0 - ) - - od = LensCalc.from_mass_obj(mp_0) - assert visuals_1d_via.einstein_radius == od.einstein_radius_from(grid=grid_2d_7x7) - assert visuals_1d_via.einstein_radius_errors[0][0] == od.einstein_radius_from( - grid=grid_2d_7x7 - ) - - -def test__2d__add_critical_curve(gal_x1_mp, grid_2d_7x7): - - visuals_2d_via = aplt.Visuals2D().add_critical_curves_or_caustics( - mass_obj=gal_x1_mp, grid=grid_2d_7x7, plane_index=0 - ) - - od = LensCalc.from_mass_obj(gal_x1_mp) - assert ( - visuals_2d_via.tangential_critical_curves[0] - == od.tangential_critical_curve_list_from(grid=grid_2d_7x7)[0] - ).all() - - -def test__2d__add_caustic(gal_x1_mp, grid_2d_7x7): - - visuals_2d_via = aplt.Visuals2D().add_critical_curves_or_caustics( - mass_obj=gal_x1_mp, grid=grid_2d_7x7, plane_index=1 - ) - - od = LensCalc.from_mass_obj(gal_x1_mp) - assert ( - visuals_2d_via.tangential_caustics[0] - == od.tangential_caustic_list_from(grid=grid_2d_7x7)[0] - ).all() - assert ( - visuals_2d_via.radial_caustics[0] - == od.radial_caustic_list_from(grid=grid_2d_7x7)[0] - ).all() +from os import path +import pytest + +from autogalaxy.operate.lens_calc import LensCalc + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_profile_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" + ) + + +def test__1d__half_light_radius_from_light_profile(lp_0): + assert lp_0.half_light_radius is not None + + +def test__1d__einstein_radius_from_mass_profile(mp_0, grid_2d_7x7): + od = LensCalc.from_mass_obj(mp_0) + einstein_radius = od.einstein_radius_from(grid=grid_2d_7x7) + assert einstein_radius is not None + + +def test__2d__critical_curves_from_mass_obj(gal_x1_mp, grid_2d_7x7): + od = LensCalc.from_mass_obj(gal_x1_mp) + tc = od.tangential_critical_curve_list_from(grid=grid_2d_7x7) + assert tc is not None + assert len(tc) > 0 + + +def test__2d__caustics_from_mass_obj(gal_x1_mp, grid_2d_7x7): + od = LensCalc.from_mass_obj(gal_x1_mp) + tc = od.tangential_caustic_list_from(grid=grid_2d_7x7) + rc = od.radial_caustic_list_from(grid=grid_2d_7x7) + assert tc is not None + assert rc is not None + + +def test__mass_plotter__tangential_critical_curves(gal_x1_mp, grid_2d_7x7): + from autogalaxy.plot.plot_utils import _critical_curves_from + + tc, rc = _critical_curves_from(gal_x1_mp, grid_2d_7x7) + + od = LensCalc.from_mass_obj(gal_x1_mp) + expected_tc = od.tangential_critical_curve_list_from(grid=grid_2d_7x7) + + assert (tc[0] == expected_tc[0]).all() + + +def test__mass_plotter__caustics_via_lens_calc(gal_x1_mp, grid_2d_7x7): + od = LensCalc.from_mass_obj(gal_x1_mp) + tc = od.tangential_caustic_list_from(grid=grid_2d_7x7) + rc = od.radial_caustic_list_from(grid=grid_2d_7x7) + + assert tc is not None + assert len(tc) > 0 + assert rc is not None diff --git a/test_autogalaxy/profiles/mass/abstract/test_abstract.py b/test_autogalaxy/profiles/mass/abstract/test_abstract.py index 7532e48ea..51bd775dd 100644 --- a/test_autogalaxy/profiles/mass/abstract/test_abstract.py +++ b/test_autogalaxy/profiles/mass/abstract/test_abstract.py @@ -26,13 +26,12 @@ def mass_within_radius_of_profile_from_grid_calculation(radius, profile): return mass_total -def test__deflections_2d_via_potential_2d_from(): +def test__deflections_2d_via_potential_2d_from__isothermal_sph(): mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) grid = ag.Grid2D.uniform(shape_native=(10, 10), pixel_scales=0.05) deflections_via_calculation = mp.deflections_yx_2d_from(grid=grid) - deflections_via_potential = mp.deflections_2d_via_potential_2d_from(grid=grid) mean_error = np.mean( @@ -41,6 +40,8 @@ def test__deflections_2d_via_potential_2d_from(): assert mean_error < 1e-4 + +def test__deflections_2d_via_potential_2d_from__isothermal_ell_comps_1(): sie = ag.mp.Isothermal( centre=(0.0, 0.0), ell_comps=(0.111111, 0.0), einstein_radius=2.0 ) @@ -48,7 +49,6 @@ def test__deflections_2d_via_potential_2d_from(): grid = ag.Grid2D.uniform(shape_native=(10, 10), pixel_scales=0.05) deflections_via_calculation = sie.deflections_yx_2d_from(grid=grid) - deflections_via_potential = sie.deflections_2d_via_potential_2d_from(grid=grid) mean_error = np.mean( @@ -57,6 +57,8 @@ def test__deflections_2d_via_potential_2d_from(): assert mean_error < 1e-4 + +def test__deflections_2d_via_potential_2d_from__isothermal_ell_comps_2(): sie = ag.mp.Isothermal( centre=(0.0, 0.0), ell_comps=(0.0, -0.111111), einstein_radius=2.0 ) @@ -64,7 +66,6 @@ def test__deflections_2d_via_potential_2d_from(): grid = ag.Grid2D.uniform(shape_native=(10, 10), pixel_scales=0.05) deflections_via_calculation = sie.deflections_yx_2d_from(grid=grid) - deflections_via_potential = sie.deflections_2d_via_potential_2d_from(grid=grid) mean_error = np.mean( @@ -74,17 +75,21 @@ def test__deflections_2d_via_potential_2d_from(): assert mean_error < 1e-4 -def test__mass_angular_within_circle_from(): +def test__mass_angular_within_circle_from__sph_einstein_radius_2(): mp = ag.mp.IsothermalSph(einstein_radius=2.0) mass = mp.mass_angular_within_circle_from(radius=2.0) assert math.pi * mp.einstein_radius * 2.0 == pytest.approx(mass, 1e-3) + +def test__mass_angular_within_circle_from__sph_einstein_radius_4(): mp = ag.mp.IsothermalSph(einstein_radius=4.0) mass = mp.mass_angular_within_circle_from(radius=4.0) assert math.pi * mp.einstein_radius * 4.0 == pytest.approx(mass, 1e-3) + +def test__mass_angular_within_circle_from__sph_grid_integration(): mp = ag.mp.IsothermalSph(einstein_radius=2.0) mass_grid = mass_within_radius_of_profile_from_grid_calculation( @@ -96,23 +101,29 @@ def test__mass_angular_within_circle_from(): assert mass_grid == pytest.approx(mass, 0.02) -def test__average_convergence_of_1_radius(): +def test__average_convergence_of_1_radius__isothermal_sph(): mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) assert mp.average_convergence_of_1_radius == pytest.approx(2.0, 1e-4) + +def test__average_convergence_of_1_radius__isothermal_low_ellipticity(): sie = ag.mp.Isothermal( centre=(0.0, 0.0), einstein_radius=1.0, ell_comps=(0.0, 0.111111) ) assert sie.average_convergence_of_1_radius == pytest.approx(1.0, 1e-4) + +def test__average_convergence_of_1_radius__isothermal_medium_ellipticity(): sie = ag.mp.Isothermal( centre=(0.0, 0.0), einstein_radius=3.0, ell_comps=(0.0, 0.333333) ) assert sie.average_convergence_of_1_radius == pytest.approx(3.0, 1e-4) + +def test__average_convergence_of_1_radius__isothermal_high_ellipticity(): sie = ag.mp.Isothermal( centre=(0.0, 0.0), einstein_radius=8.0, ell_comps=(0.0, 0.666666) ) @@ -163,7 +174,7 @@ def test__extract_attribute(): mp.extract_attribute(cls=ag.LightProfile, attr_name="einstein_radius") -def test__regression__centre_of_profile_in_right_place(): +def test__regression__centre_of_profile_in_right_place__isothermal(): grid = ag.Grid2D.uniform(shape_native=(7, 7), pixel_scales=1.0) mass_profile = ag.mp.Isothermal(centre=(1.999999, 0.9999999), einstein_radius=1.0) @@ -184,6 +195,10 @@ def test__regression__centre_of_profile_in_right_place(): assert deflections.native[1, 4, 1] > 0 assert deflections.native[1, 3, 1] < 0 + +def test__regression__centre_of_profile_in_right_place__isothermal_sph(): + grid = ag.Grid2D.uniform(shape_native=(7, 7), pixel_scales=1.0) + mass_profile = ag.mp.IsothermalSph(centre=(2.0, 1.0), einstein_radius=1.0) convergence = mass_profile.convergence_2d_from(grid=grid) max_indexes = np.unravel_index( diff --git a/test_autogalaxy/profiles/mass/abstract/test_mge.py b/test_autogalaxy/profiles/mass/abstract/test_mge.py index 4babc8137..e40aad764 100644 --- a/test_autogalaxy/profiles/mass/abstract/test_mge.py +++ b/test_autogalaxy/profiles/mass/abstract/test_mge.py @@ -6,7 +6,7 @@ import autogalaxy as ag -def test__gnfw_deflections_yx_2d_via_mge(): +def test__gnfw_deflections_yx_2d_via_mge__config_1__inner_slope_05(): nfw = ag.mp.gNFW( centre=(0.0, 0.0), kappa_s=1.0, @@ -36,6 +36,8 @@ def test__gnfw_deflections_yx_2d_via_mge(): assert deflections_via_integral == pytest.approx(deflections_via_mge, 1.0e-3) + +def test__gnfw_deflections_yx_2d_via_mge__config_2__inner_slope_15(): nfw = ag.mp.gNFW( centre=(0.3, 0.2), kappa_s=2.5, @@ -65,7 +67,7 @@ def test__gnfw_deflections_yx_2d_via_mge(): assert deflections_via_integral == pytest.approx(deflections_via_mge, 1.0e-3) -def test__sersic_deflections_yx_2d_via_mge(): +def test__sersic_deflections_yx_2d_via_mge__sersic_index_2(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -95,6 +97,8 @@ def test__sersic_deflections_yx_2d_via_mge(): assert deflections_via_integral == pytest.approx(deflections_via_mge, 1.0e-3) + +def test__sersic_deflections_yx_2d_via_mge__sersic_index_3(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -104,6 +108,11 @@ def test__sersic_deflections_yx_2d_via_mge(): mass_to_light_ratio=1.0, ) + radii_min = mp.effective_radius / 100.0 + radii_max = mp.effective_radius * 20.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + deflections_via_integral = mp.deflections_2d_via_integral_from( grid=ag.Grid2DIrregular([[0.1625, 0.1625]]) ) @@ -205,7 +214,7 @@ def test__chameleon_deflections_yx_2d_via_mge(): assert deflections_analytic == pytest.approx(deflections_via_mge, 1.0e-3) -def test__DevVaucouleurs_convergence_2d_via_mge_from(): +def test__DevVaucouleurs_convergence_2d_via_mge_from__config_1(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, 0.333333), intensity=3.0, @@ -229,6 +238,8 @@ def test__DevVaucouleurs_convergence_2d_via_mge_from(): assert convergence == pytest.approx(5.6697, 1e-3) + +def test__DevVaucouleurs_convergence_2d_via_mge_from__config_2(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -252,6 +263,8 @@ def test__DevVaucouleurs_convergence_2d_via_mge_from(): assert convergence == pytest.approx(7.4455, 1e-3) + +def test__DevVaucouleurs_convergence_2d_via_mge_from__intensity_4(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=4.0, @@ -275,6 +288,8 @@ def test__DevVaucouleurs_convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 7.4455, 1e-3) + +def test__DevVaucouleurs_convergence_2d_via_mge_from__mass_to_light_2(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -298,6 +313,8 @@ def test__DevVaucouleurs_convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 7.4455, 1e-3) + +def test__DevVaucouleurs_convergence_2d_via_mge_from__small_effective_radius(): mp = ag.mp.DevVaucouleurs( centre=(0.0, 0.0), intensity=1.0, @@ -322,7 +339,7 @@ def test__DevVaucouleurs_convergence_2d_via_mge_from(): assert convergence == pytest.approx(0.351797, 1e-3) -def test__convergence_2d_via_mge_from(): +def test__exponential_convergence_2d_via_mge_from__config_1(): mp = ag.mp.Exponential( ell_comps=(0.0, 0.333333), intensity=3.0, @@ -346,6 +363,8 @@ def test__convergence_2d_via_mge_from(): assert convergence == pytest.approx(4.9047, 1e-3) + +def test__exponential_convergence_2d_via_mge_from__config_2(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -369,12 +388,15 @@ def test__convergence_2d_via_mge_from(): assert convergence == pytest.approx(4.8566, 1e-3) + +def test__exponential_convergence_2d_via_mge_from__intensity_4(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=4.0, effective_radius=3.0, mass_to_light_ratio=1.0, ) + radii_min = mp.effective_radius / 100.0 radii_max = mp.effective_radius * 20.0 log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) @@ -391,6 +413,8 @@ def test__convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 4.8566, 1e-3) + +def test__exponential_convergence_2d_via_mge_from__mass_to_light_2(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -414,6 +438,8 @@ def test__convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 4.8566, 1e-3) + +def test__exponential_convergence_2d_via_mge_from__mass_to_light_1(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -438,11 +464,7 @@ def test__convergence_2d_via_mge_from(): assert convergence == pytest.approx(4.8566, 1e-3) -def test__nfw_convergence_2d_via_mge_from(): - # r = 2.0 (> 1.0) - # F(r) = (1/(sqrt(3))*atan(sqrt(3)) = 0.60459978807 - # kappa(r) = 2 * kappa_s * (1 - 0.60459978807) / (4-1) = 0.263600141 - +def test__nfw_convergence_2d_via_mge_from__grid_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) radii_min = nfw.scale_radius / 2000.0 @@ -461,6 +483,17 @@ def test__nfw_convergence_2d_via_mge_from(): assert convergence == pytest.approx(0.263600141, 1e-2) + +def test__nfw_convergence_2d_via_mge_from__grid_05(): + nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) + + radii_min = nfw.scale_radius / 2000.0 + radii_max = nfw.scale_radius * 30.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + + mge_decomp = MGEDecomposer(mass_profile=nfw) + convergence = mge_decomp.convergence_2d_via_mge_from( grid=ag.Grid2DIrregular([[0.5, 0.0]]), sigma_log_list=sigmas, @@ -470,8 +503,15 @@ def test__nfw_convergence_2d_via_mge_from(): assert convergence == pytest.approx(1.388511, 1e-2) + +def test__nfw_convergence_2d_via_mge_from__kappa_s_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=2.0, scale_radius=1.0) + radii_min = nfw.scale_radius / 2000.0 + radii_max = nfw.scale_radius * 30.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + mge_decomp = MGEDecomposer(mass_profile=nfw) convergence = mge_decomp.convergence_2d_via_mge_from( @@ -483,6 +523,8 @@ def test__nfw_convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 1.388511, 1e-2) + +def test__nfw_convergence_2d_via_mge_from__scale_radius_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=2.0) radii_min = nfw.scale_radius / 2000.0 @@ -501,6 +543,8 @@ def test__nfw_convergence_2d_via_mge_from(): assert convergence == pytest.approx(1.388511, 1e-2) + +def test__nfw_convergence_2d_via_mge_from__elliptical(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -525,7 +569,7 @@ def test__nfw_convergence_2d_via_mge_from(): assert convergence == pytest.approx(1.388511, 1e-3) -def test__sersic_convergence_2d_via_mge_from(): +def test__sersic_convergence_2d_via_mge_from__intensity_3(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -550,6 +594,8 @@ def test__sersic_convergence_2d_via_mge_from(): assert convergence == pytest.approx(4.90657319276, 1e-3) + +def test__sersic_convergence_2d_via_mge_from__intensity_6(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=6.0, @@ -558,6 +604,11 @@ def test__sersic_convergence_2d_via_mge_from(): mass_to_light_ratio=1.0, ) + radii_min = mp.effective_radius / 100.0 + radii_max = mp.effective_radius * 20.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + mge_decomp = MGEDecomposer(mass_profile=mp) convergence = mge_decomp.convergence_2d_via_mge_from( @@ -569,6 +620,8 @@ def test__sersic_convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__sersic_convergence_2d_via_mge_from__mass_to_light_2(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -577,6 +630,11 @@ def test__sersic_convergence_2d_via_mge_from(): mass_to_light_ratio=2.0, ) + radii_min = mp.effective_radius / 100.0 + radii_max = mp.effective_radius * 20.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + mge_decomp = MGEDecomposer(mass_profile=mp) convergence = mge_decomp.convergence_2d_via_mge_from( @@ -588,6 +646,8 @@ def test__sersic_convergence_2d_via_mge_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__sersic_convergence_2d_via_mge_from__elliptical(): mp = ag.mp.Sersic( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -597,6 +657,11 @@ def test__sersic_convergence_2d_via_mge_from(): mass_to_light_ratio=1.0, ) + radii_min = mp.effective_radius / 100.0 + radii_max = mp.effective_radius * 20.0 + log_sigmas = np.linspace(np.log(radii_min), np.log(radii_max), 20) + sigmas = np.exp(log_sigmas) + mge_decomp = MGEDecomposer(mass_profile=mp) convergence = mge_decomp.convergence_2d_via_mge_from( diff --git a/test_autogalaxy/profiles/mass/dark/test_abstract.py b/test_autogalaxy/profiles/mass/dark/test_abstract.py index a058381bc..567934673 100644 --- a/test_autogalaxy/profiles/mass/dark/test_abstract.py +++ b/test_autogalaxy/profiles/mass/dark/test_abstract.py @@ -4,48 +4,60 @@ import autogalaxy as ag -def test__coord_function_f__from(): +def test__coord_function_f__from__r_greater_than_1(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) - # r > 1 - coord_f = mp.coord_func_f(grid_radius=np.array([2.0, 3.0])) assert coord_f == pytest.approx(np.array([0.604599, 0.435209]), 1.0e-4) - # r < 1 + +def test__coord_function_f__from__r_less_than_1(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 + ) coord_f = mp.coord_func_f(grid_radius=np.array([0.5, 1.0 / 3.0])) assert coord_f == pytest.approx(1.52069, 1.86967, 1.0e-4) - # - # r == 1 + + +def test__coord_function_f__from__r_equals_1(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 + ) coord_f = mp.coord_func_f(grid_radius=np.array([1.0, 1.0])) assert (coord_f == np.array([1.0, 1.0])).all() -def test__coord_function_g__from(): +def test__coord_function_g__from__r_greater_than_1(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) - # r > 1 - coord_g = mp.coord_func_g(grid_radius=np.array([2.0, 3.0])) assert coord_g == pytest.approx(np.array([0.13180, 0.070598]), 1.0e-4) - # r < 1 + +def test__coord_function_g__from__r_less_than_1(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 + ) coord_g = mp.coord_func_g(grid_radius=np.array([0.5, 1.0 / 3.0])) assert coord_g == pytest.approx(np.array([0.69425, 0.97838]), 1.0e-4) - # r == 1 + +def test__coord_function_g__from__r_equals_1(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 + ) coord_g = mp.coord_func_g(grid_radius=np.array([1.0, 1.0])) @@ -62,7 +74,7 @@ def test__coord_function_h__from(): assert coord_h == pytest.approx(np.array([0.134395, 0.840674]), 1.0e-4) -def test__coord_function_k__from(): +def test__coord_function_k__from__truncation_radius_2(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=2.0 ) @@ -71,6 +83,8 @@ def test__coord_function_k__from(): assert coord_k == pytest.approx(np.array([-0.09983408, -0.06661738]), 1.0e-4) + +def test__coord_function_k__from__truncation_radius_4(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=4.0 ) @@ -80,7 +94,7 @@ def test__coord_function_k__from(): assert coord_k == pytest.approx(np.array([-0.19869011, -0.1329414]), 1.0e-4) -def test__coord_function_l__from(): +def test__coord_function_l__from__grid_radius_2_truncation_2(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=2.0 ) @@ -89,6 +103,8 @@ def test__coord_function_l__from(): assert coord_l == pytest.approx(np.array([0.00080191, 0.00080191]), 1.0e-4) + +def test__coord_function_l__from__grid_radius_2_truncation_3(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) @@ -97,6 +113,8 @@ def test__coord_function_l__from(): assert coord_l == pytest.approx(np.array([0.00178711, 0.00178711]), 1.0e-4) + +def test__coord_function_l__from__grid_radius_3_truncation_3(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) @@ -106,7 +124,7 @@ def test__coord_function_l__from(): assert coord_l == pytest.approx(np.array([0.00044044, 0.00044044]), 1.0e-4) -def test__coord_function_m__from(): +def test__coord_function_m__from__grid_radius_2_truncation_2(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=2.0 ) @@ -115,6 +133,8 @@ def test__coord_function_m__from(): assert coord_m == pytest.approx(np.array([0.0398826, 0.0398826]), 1.0e-4) + +def test__coord_function_m__from__grid_radius_2_truncation_3(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) @@ -123,6 +143,8 @@ def test__coord_function_m__from(): assert coord_m == pytest.approx(np.array([0.06726646, 0.06726646]), 1.0e-4) + +def test__coord_function_m__from__grid_radius_3_truncation_3(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=10.0, truncation_radius=3.0 ) @@ -132,7 +154,7 @@ def test__coord_function_m__from(): assert coord_m == pytest.approx(np.array([0.06946888, 0.06946888]), 1.0e-4) -def test__rho_at_scale_radius__unit_conversions(): +def test__rho_at_scale_radius__arcsec_per_kpc_05(): cosmology = ag.m.MockCosmology( arcsec_per_kpc=0.5, kpc_per_arcsec=2.0, critical_surface_density=2.0 ) @@ -147,26 +169,34 @@ def test__rho_at_scale_radius__unit_conversions(): ) assert rho == pytest.approx(0.5 / 2.0, 1e-3) + +def test__rho_at_scale_radius__arcsec_per_kpc_025(): cosmology = ag.m.MockCosmology( arcsec_per_kpc=0.25, kpc_per_arcsec=4.0, critical_surface_density=2.0 ) + mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) + rho = mp.rho_at_scale_radius_solar_mass_per_kpc3( redshift_object=0.5, redshift_source=1.0, cosmology=cosmology ) assert rho == pytest.approx(0.5 / 4.0, 1e-3) + +def test__rho_at_scale_radius__critical_surface_density_4(): cosmology = ag.m.MockCosmology( arcsec_per_kpc=0.25, kpc_per_arcsec=4.0, critical_surface_density=4.0 ) + mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) + rho = mp.rho_at_scale_radius_solar_mass_per_kpc3( redshift_object=0.5, redshift_source=1.0, cosmology=cosmology ) assert rho == pytest.approx(0.25 / 4.0, 1e-3) -def test__delta_concentration_value_in_default_units(): +def test__delta_concentration__kappa_s_1(): mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) cosmology = ag.m.MockCosmology( @@ -181,13 +211,33 @@ def test__delta_concentration_value_in_default_units(): ) assert delta_concentration == pytest.approx(1.0, 1e-3) + +def test__delta_concentration__kappa_s_3(): mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=3.0, scale_radius=1.0) + + cosmology = ag.m.MockCosmology( + arcsec_per_kpc=1.0, + kpc_per_arcsec=1.0, + critical_surface_density=1.0, + cosmic_average_density=1.0, + ) + delta_concentration = mp.delta_concentration( redshift_object=0.5, redshift_source=1.0, cosmology=cosmology ) assert delta_concentration == pytest.approx(3.0, 1e-3) + +def test__delta_concentration__scale_radius_4(): mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=4.0) + + cosmology = ag.m.MockCosmology( + arcsec_per_kpc=1.0, + kpc_per_arcsec=1.0, + critical_surface_density=1.0, + cosmic_average_density=1.0, + ) + delta_concentration = mp.delta_concentration( redshift_object=0.5, redshift_source=1.0, cosmology=cosmology ) @@ -254,7 +304,7 @@ def test__mass_at_200__unit_conversions_work(): assert mass_at_200 == pytest.approx(mass_calc, 1.0e-5) -def test__values_of_quantities_for_real_cosmology(): +def test__values_of_quantities_for_real_cosmology__local_density(): from autogalaxy.cosmology.model import FlatLambdaCDM @@ -308,6 +358,15 @@ def test__values_of_quantities_for_real_cosmology(): assert mass_at_200 == pytest.approx(27664107754813.824, 1.0e-4) assert mass_at_truncation_radius == pytest.approx(14883851437772.34, 1.0e-4) + +def test__values_of_quantities_for_real_cosmology__profile_density(): + + from autogalaxy.cosmology.model import FlatLambdaCDM + + cosmology = FlatLambdaCDM(H0=70.0, Om0=0.3, m_nu=0.06) + + mp = ag.mp.NFWTruncatedSph(kappa_s=0.5, scale_radius=5.0, truncation_radius=10.0) + rho = mp.rho_at_scale_radius_solar_mass_per_kpc3( redshift_object=0.6, redshift_source=2.5, cosmology=cosmology ) diff --git a/test_autogalaxy/profiles/mass/dark/test_gnfw.py b/test_autogalaxy/profiles/mass/dark/test_gnfw.py index 99b70c0cb..df4743075 100644 --- a/test_autogalaxy/profiles/mass/dark/test_gnfw.py +++ b/test_autogalaxy/profiles/mass/dark/test_gnfw.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_2d_via_integral_from(): +def test__deflections_2d_via_integral_from__gnfw_sph_config_1(): mp = ag.mp.gNFWSph( centre=(0.0, 0.0), kappa_s=1.0, inner_slope=0.5, scale_radius=8.0 ) @@ -18,6 +18,8 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 0] == pytest.approx(0.43501, 1e-3) assert deflections[0, 1] == pytest.approx(0.37701, 1e-3) + +def test__deflections_2d_via_integral_from__gnfw_sph_config_2(): mp = ag.mp.gNFWSph( centre=(0.3, 0.2), kappa_s=2.5, inner_slope=1.5, scale_radius=4.0 ) @@ -29,6 +31,8 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 0] == pytest.approx(-9.31254, 1e-3) assert deflections[0, 1] == pytest.approx(-3.10418, 1e-3) + +def test__deflections_2d_via_integral_from__gnfw_ell_config_1(): mp = ag.mp.gNFW( centre=(0.0, 0.0), kappa_s=1.0, @@ -42,6 +46,8 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 0] == pytest.approx(0.26604, 1e-3) assert deflections[0, 1] == pytest.approx(0.58988, 1e-3) + +def test__deflections_2d_via_integral_from__gnfw_ell_config_2(): mp = ag.mp.gNFW( centre=(0.3, 0.2), kappa_s=2.5, @@ -56,7 +62,7 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 1] == pytest.approx(-4.02541, 1e-3) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__gnfw(): mp = ag.mp.gNFW() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -66,6 +72,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__gnfw_sph(): mp = ag.mp.gNFWSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -75,6 +83,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.gNFW( centre=(0.1, 0.2), ell_comps=(0.0, 0.0), @@ -91,7 +101,7 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): +def test__convergence_2d_from__gnfw_sph(): mp = ag.mp.gNFWSph( centre=(0.0, 0.0), kappa_s=1.0, inner_slope=1.5, scale_radius=1.0 ) @@ -100,6 +110,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.30840, 1e-2) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.gNFW( centre=(0.1, 0.2), ell_comps=(0.0, 0.0), @@ -116,7 +128,7 @@ def test__convergence_2d_from(): ) -def test__potential_2d_from(): +def test__potential_2d_from__gnfw_sph_inner_slope_05(): mp = ag.mp.gNFWSph( centre=(0.0, 0.0), kappa_s=1.0, inner_slope=0.5, scale_radius=8.0 ) @@ -125,6 +137,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.00920, 1e-3) + +def test__potential_2d_from__gnfw_sph_inner_slope_15(): mp = ag.mp.gNFWSph( centre=(0.0, 0.0), kappa_s=1.0, inner_slope=1.5, scale_radius=8.0 ) @@ -133,6 +147,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.17448, 1e-3) + +def test__potential_2d_from__gnfw_ell(): mp = ag.mp.gNFW( centre=(1.0, 1.0), kappa_s=5.0, @@ -144,6 +160,8 @@ def test__potential_2d_from(): 2.4718, 1e-4 ) + +def test__potential_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.gNFW( centre=(0.1, 0.2), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/dark/test_nfw.py b/test_autogalaxy/profiles/mass/dark/test_nfw.py index fbdf4d6b3..f9c894d64 100644 --- a/test_autogalaxy/profiles/mass/dark/test_nfw.py +++ b/test_autogalaxy/profiles/mass/dark/test_nfw.py @@ -6,8 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_via_analytical_from(): - +def test__deflections_via_analytical_from__nfw_config_1(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -22,6 +21,8 @@ def test__deflections_via_analytical_from(): assert deflections[0, 0] == pytest.approx(0.56194, 1e-3) assert deflections[0, 1] == pytest.approx(0.56194, 1e-3) + +def test__deflections_via_analytical_from__nfw_config_2(): nfw = ag.mp.NFW( centre=(0.3, 0.2), ell_comps=(0.03669, 0.172614), @@ -36,6 +37,8 @@ def test__deflections_via_analytical_from(): assert deflections[0, 0] == pytest.approx(-2.59480, 1e-3) assert deflections[0, 1] == pytest.approx(-0.44204, 1e-3) + +def test__deflections_via_analytical_from__analytic_vs_cse_config_1(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -52,6 +55,8 @@ def test__deflections_via_analytical_from(): assert deflections_via_analytic == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_via_analytical_from__analytic_vs_cse_config_2(): nfw = ag.mp.NFW( centre=(0.3, -0.2), ell_comps=(0.09, 0.172614), @@ -69,7 +74,7 @@ def test__deflections_via_analytical_from(): assert deflections_via_analytic == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__deflections_via_integral_from(): +def test__deflections_via_integral_from__nfw_sph_config_1(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) deflections = nfw.deflections_2d_via_integral_from( @@ -79,6 +84,8 @@ def test__deflections_via_integral_from(): assert deflections[0, 0] == pytest.approx(0.56194, 1e-3) assert deflections[0, 1] == pytest.approx(0.56194, 1e-3) + +def test__deflections_via_integral_from__nfw_sph_config_2(): nfw = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0) deflections = nfw.deflections_2d_via_integral_from( @@ -88,6 +95,8 @@ def test__deflections_via_integral_from(): assert deflections[0, 0] == pytest.approx(-2.08909, 1e-3) assert deflections[0, 1] == pytest.approx(-0.69636, 1e-3) + +def test__deflections_via_integral_from__nfw_ell_config_1(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -102,6 +111,8 @@ def test__deflections_via_integral_from(): assert deflections[0, 0] == pytest.approx(0.56194, 1e-3) assert deflections[0, 1] == pytest.approx(0.56194, 1e-3) + +def test__deflections_via_integral_from__nfw_ell_config_2(): nfw = ag.mp.NFW( centre=(0.3, 0.2), ell_comps=(0.03669, 0.172614), @@ -117,7 +128,7 @@ def test__deflections_via_integral_from(): assert deflections[0, 1] == pytest.approx(-0.44204, 1e-3) -def test__deflections_2d_via_cse_from(): +def test__deflections_2d_via_cse_from__nfw_sph_config_1(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) deflections_via_integral = nfw.deflections_2d_via_integral_from( @@ -129,6 +140,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__nfw_sph_config_2(): nfw = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0) deflections_via_integral = nfw.deflections_2d_via_integral_from( @@ -140,6 +153,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__nfw_ell_config_1(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -156,6 +171,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__nfw_ell_config_2(): nfw = ag.mp.NFW( centre=(0.3, 0.2), ell_comps=(0.03669, 0.172614), @@ -173,7 +190,7 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__deflections_2d__numerical_precision_of_csv_compared_to_integral(): +def test__deflections_2d_numerical_precision__config_1(): nfw = ag.mp.NFW( centre=(0.3, 0.2), ell_comps=(0.03669, 0.172614), @@ -190,6 +207,8 @@ def test__deflections_2d__numerical_precision_of_csv_compared_to_integral(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_numerical_precision__config_2(): nfw = ag.mp.NFW( centre=(0.3, 0.2), ell_comps=(0.2, 0.3), @@ -206,6 +225,15 @@ def test__deflections_2d__numerical_precision_of_csv_compared_to_integral(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_numerical_precision__config_3(): + nfw = ag.mp.NFW( + centre=(0.3, 0.2), + ell_comps=(0.2, 0.3), + kappa_s=3.5, + scale_radius=40.0, + ) + deflections_via_integral = nfw.deflections_2d_via_integral_from( grid=ag.Grid2DIrregular([[-1000.0, -2000.0]]) ) @@ -227,7 +255,7 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) -def test__convergence_2d_via_cse_from(): +def test__convergence_2d_via_cse_from__nfw_grid_2(): # r = 2.0 (> 1.0) # F(r) = (1/(sqrt(3))*atan(sqrt(3)) = 0.60459978807 # kappa(r) = 2 * kappa_s * (1 - 0.60459978807) / (4-1) = 0.263600141 @@ -238,24 +266,32 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(0.263600141, 1e-2) + +def test__convergence_2d_via_cse_from__nfw_grid_05(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) convergence = nfw.convergence_2d_via_cse_from(grid=ag.Grid2DIrregular([[0.5, 0.0]])) assert convergence == pytest.approx(1.388511, 1e-2) + +def test__convergence_2d_via_cse_from__nfw_kappa_s_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=2.0, scale_radius=1.0) convergence = nfw.convergence_2d_via_cse_from(grid=ag.Grid2DIrregular([[0.5, 0.0]])) assert convergence == pytest.approx(2.0 * 1.388511, 1e-2) + +def test__convergence_2d_via_cse_from__nfw_scale_radius_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=2.0) convergence = nfw.convergence_2d_via_cse_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) assert convergence == pytest.approx(1.388511, 1e-2) + +def test__convergence_2d_via_cse_from__nfw_ell(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -270,7 +306,7 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(1.388511, 1e-3) -def test__convergence_2d_from(): +def test__convergence_2d_from__nfw_sph_grid_2(): # r = 2.0 (> 1.0) # F(r) = (1/(sqrt(3))*atan(sqrt(3)) = 0.60459978807 # kappa(r) = 2 * kappa_s * (1 - 0.60459978807) / (4-1) = 0.263600141 @@ -281,24 +317,32 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.263600141, 1e-3) + +def test__convergence_2d_from__nfw_sph_grid_05(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) convergence = nfw.convergence_2d_from(grid=ag.Grid2DIrregular([[0.5, 0.0]])) assert convergence == pytest.approx(1.388511, 1e-3) + +def test__convergence_2d_from__nfw_sph_kappa_s_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=2.0, scale_radius=1.0) convergence = nfw.convergence_2d_from(grid=ag.Grid2DIrregular([[0.5, 0.0]])) assert convergence == pytest.approx(2.0 * 1.388511, 1e-3) + +def test__convergence_2d_from__nfw_sph_scale_radius_2(): nfw = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=2.0) convergence = nfw.convergence_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) assert convergence == pytest.approx(1.388511, 1e-3) + +def test__convergence_2d_from__nfw_ell(): nfw = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -353,7 +397,7 @@ def test__convergence_2d_from(): # assert potential_spherical == pytest.approx(potential_elliptical, 1e-3) -def test__shear_yx_2d_from(): +def test__shear_yx_2d_from__nfw_sph_config_1(): mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 1.0]])) @@ -361,11 +405,17 @@ def test__shear_yx_2d_from(): assert shear[0, 0] == pytest.approx(-0.24712463, abs=1e-3) assert shear[0, 1] == pytest.approx(0.185340150, abs=1e-3) + +def test__shear_yx_2d_from__nfw_sph_config_2(): + mp = ag.mp.NFWSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0) + shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[3.0, 5.0]])) assert shear[0, 0] == pytest.approx(-0.09588857, abs=1e-3) assert shear[0, 1] == pytest.approx(-0.05114060, abs=1e-3) + +def test__shear_yx_2d_from__nfw_ell(): mp = ag.mp.NFW( centre=(0.0, 0.0), ell_comps=(0.3, 0.4), kappa_s=1.0, scale_radius=1.0 ) diff --git a/test_autogalaxy/profiles/mass/dark/test_nfw_mcr.py b/test_autogalaxy/profiles/mass/dark/test_nfw_mcr.py index 477f65319..0b18a4bb0 100644 --- a/test_autogalaxy/profiles/mass/dark/test_nfw_mcr.py +++ b/test_autogalaxy/profiles/mass/dark/test_nfw_mcr.py @@ -218,7 +218,7 @@ def test__same_as_above_but_generalized_elliptical(): assert (deflections_ludlow == deflections).all() -def test__same_as_above_but_cored_nfw(): +def test__same_as_above_but_cored_nfw__spherical(): from autogalaxy.cosmology.model import FlatLambdaCDM @@ -273,6 +273,13 @@ def test__same_as_above_but_cored_nfw(): assert (deflections_ludlow == deflections).all() + +def test__same_as_above_but_cored_nfw__elliptical(): + + from autogalaxy.cosmology.model import FlatLambdaCDM + + cosmology = FlatLambdaCDM(H0=70.0, Om0=0.3) + mp = ag.mp.cNFWMCRLudlow( centre=(1.0, 2.0), ell_comps=(0.1, 0.2), @@ -324,4 +331,4 @@ def test__same_as_above_but_cored_nfw(): deflections_ludlow = mp.deflections_yx_2d_from(grid=grid) deflections = cnfw_kappa_s.deflections_yx_2d_from(grid=grid) - assert (deflections_ludlow == deflections).all() \ No newline at end of file + assert (deflections_ludlow == deflections).all() diff --git a/test_autogalaxy/profiles/mass/dark/test_nfw_scatter.py b/test_autogalaxy/profiles/mass/dark/test_nfw_scatter.py index 7cd95e008..650b025b3 100644 --- a/test_autogalaxy/profiles/mass/dark/test_nfw_scatter.py +++ b/test_autogalaxy/profiles/mass/dark/test_nfw_scatter.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__scatter_is_nonzero(): +def test__scatter_is_nonzero__sph_scatter_positive(): mp = ag.mp.NFWMCRScatterLudlowSph( mass_at_200=1.0e9, scatter_sigma=1.0, @@ -14,10 +14,10 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncatedSph to check the mass gives a conosistnt kappa_s, given certain radii. - assert mp.scale_radius == pytest.approx(0.14978, 1.0e-4) + +def test__scatter_is_nonzero__sph_scatter_negative(): mp = ag.mp.NFWMCRScatterLudlowSph( mass_at_200=1.0e9, scatter_sigma=-1.0, @@ -25,10 +25,10 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncatedSph to check the mass gives a conosistnt kappa_s, given certain radii. - assert mp.scale_radius == pytest.approx(0.29886, 1.0e-4) + +def test__scatter_is_nonzero__ell_scatter_positive(): nfw_ell = ag.mp.NFWMCRScatterLudlow( ell_comps=(0.5, 0.5), mass_at_200=1.0e9, @@ -37,11 +37,18 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncated to check the mass gives a conosistnt kappa_s, given certain radii. - assert nfw_ell.ell_comps == (0.5, 0.5) assert nfw_ell.scale_radius == pytest.approx(0.14978, 1.0e-4) + +def test__scatter_is_nonzero__ell_scatter_negative__deflections_differ_from_sph(): + mp_sph = ag.mp.NFWMCRScatterLudlowSph( + mass_at_200=1.0e9, + scatter_sigma=-1.0, + redshift_object=0.6, + redshift_source=2.5, + ) + nfw_ell = ag.mp.NFWMCRScatterLudlow( ell_comps=(0.5, 0.5), mass_at_200=1.0e9, @@ -50,18 +57,16 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncated to check the mass gives a conosistnt kappa_s, given certain radii. - assert nfw_ell.ell_comps == (0.5, 0.5) assert nfw_ell.scale_radius == pytest.approx(0.29886, 1.0e-4) - deflections_sph = mp.deflections_yx_2d_from(grid=grid) + deflections_sph = mp_sph.deflections_yx_2d_from(grid=grid) deflections_ell = nfw_ell.deflections_yx_2d_from(grid=grid) assert deflections_sph[0] != pytest.approx(deflections_ell[0], 1.0e-4) -def test__scatter_is_nonzero_cored(): +def test__scatter_is_nonzero_cored__sph_scatter_positive(): cnfw_sph = ag.mp.cNFWMCRScatterLudlowSph( mass_at_200=1.0e9, scatter_sigma=1.0, @@ -72,6 +77,8 @@ def test__scatter_is_nonzero_cored(): assert cnfw_sph.scale_radius == pytest.approx(0.14978, 1.0e-4) + +def test__scatter_is_nonzero_cored__sph_scatter_negative(): cnfw_sph = ag.mp.cNFWMCRScatterLudlowSph( mass_at_200=1.0e9, scatter_sigma=-1.0, @@ -82,6 +89,8 @@ def test__scatter_is_nonzero_cored(): assert cnfw_sph.scale_radius == pytest.approx(0.29886, 1.0e-4) + +def test__scatter_is_nonzero_cored__ell_scatter_positive(): cnfw = ag.mp.cNFWMCRScatterLudlow( ell_comps=(-0.1, 0.2), mass_at_200=1.0e9, @@ -93,6 +102,8 @@ def test__scatter_is_nonzero_cored(): assert cnfw.scale_radius == pytest.approx(0.14978, 1.0e-4) + +def test__scatter_is_nonzero_cored__ell_scatter_negative(): cnfw_sph = ag.mp.cNFWMCRScatterLudlow( mass_at_200=1.0e9, scatter_sigma=-1.0, @@ -102,4 +113,3 @@ def test__scatter_is_nonzero_cored(): ) assert cnfw_sph.scale_radius == pytest.approx(0.29886, 1.0e-4) - diff --git a/test_autogalaxy/profiles/mass/dark/test_nfw_truncated.py b/test_autogalaxy/profiles/mass/dark/test_nfw_truncated.py index 7f5082674..ead493029 100644 --- a/test_autogalaxy/profiles/mass/dark/test_nfw_truncated.py +++ b/test_autogalaxy/profiles/mass/dark/test_nfw_truncated.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__config_1__y_axis(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, truncation_radius=2.0 ) @@ -19,11 +19,25 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(factor * 0.38209715, abs=1.0e-4) assert deflections[0, 1] == pytest.approx(0.0, abs=1.0e-4) + +def test__deflections_yx_2d_from__config_1__x_axis(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, truncation_radius=2.0 + ) + + factor = (4.0 * 1.0 * 1.0) / (2.0 / 1.0) + deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.0, 2.0]])) assert deflections[0, 0] == pytest.approx(0.0, abs=1.0e-4) assert deflections[0, 1] == pytest.approx(factor * 0.38209715, abs=1.0e-4) + +def test__deflections_yx_2d_from__config_1__diagonal(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, truncation_radius=2.0 + ) + deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 1.0]])) factor = (4.0 * 1.0 * 1.0) / (np.sqrt(2) / 1.0) @@ -34,6 +48,8 @@ def test__deflections_yx_2d_from(): (1.0 / np.sqrt(2)) * factor * 0.3125838, abs=1.0e-4 ) + +def test__deflections_yx_2d_from__kappa_s_2(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=2.0, scale_radius=1.0, truncation_radius=2.0 ) @@ -44,6 +60,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(factor * 0.38209715, abs=1.0e-4) assert deflections[0, 1] == pytest.approx(0.0, abs=1.0e-4) + +def test__deflections_yx_2d_from__scale_radius_4(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=1.0, scale_radius=4.0, truncation_radius=2.0 ) @@ -54,7 +72,7 @@ def test__deflections_yx_2d_from(): assert deflections[0, 1] == pytest.approx(0.0, abs=1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__grid_2(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, truncation_radius=2.0 ) @@ -63,10 +81,18 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.046409642, abs=1.0e-4) + +def test__convergence_2d_from__grid_diagonal(): + mp = ag.mp.NFWTruncatedSph( + centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, truncation_radius=2.0 + ) + convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[1.0, 1.0]])) assert convergence == pytest.approx(2.0 * 0.10549515, abs=1.0e-4) + +def test__convergence_2d_from__kappa_s_3(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=3.0, scale_radius=1.0, truncation_radius=2.0 ) @@ -75,6 +101,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(6.0 * 0.046409642, abs=1.0e-4) + +def test__convergence_2d_from__scale_radius_5(): mp = ag.mp.NFWTruncatedSph( centre=(0.0, 0.0), kappa_s=3.0, scale_radius=5.0, truncation_radius=2.0 ) @@ -102,41 +130,6 @@ def test__mass_at_truncation_radius(): assert mass_at_truncation_radius == pytest.approx(0.00009792581, 1.0e-5) - # mp = ag.mp.NFWTruncatedSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=1.0, - # truncation_radius=1.0) - # - # cosmology = ag.m.MockCosmology(arcsec_per_kpc=1.0, kpc_per_arcsec=1.0, critical_surface_density=2.0, - # cosmic_average_density=3.0) - # - # mass_at_truncation_radius = mp.mass_at_truncation_radius(redshift_galaxy=0.5, redshift_source=1.0, - # unit_length='arcsec', unit_mass='solMass', cosmology=cosmology) - # - # assert mass_at_truncation_radius == pytest.approx(0.00008789978, 1.0e-5) - # - # mp = ag.mp.NFWTruncatedSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=2.0, - # truncation_radius=1.0) - # - # mass_at_truncation_radius = mp.mass_at_truncation_radius(redshift_galaxy=0.5, redshift_source=1.0, - # unit_length='arcsec', unit_mass='solMass', cosmology=cosmology) - # - # assert mass_at_truncation_radius == pytest.approx(0.0000418378, 1.0e-5) - # - # mp = ag.mp.NFWTruncatedSph(centre=(0.0, 0.0), kappa_s=1.0, scale_radius=8.0, - # truncation_radius=4.0) - # - # mass_at_truncation_radius = mp.mass_at_truncation_radius(redshift_galaxy=0.5, redshift_source=1.0, - # unit_length='arcsec', unit_mass='solMass', cosmology=cosmology) - # - # assert mass_at_truncation_radius == pytest.approx(0.0000421512, abs=1.0e-4) - # - # mp = ag.mp.NFWTruncatedSph(centre=(0.0, 0.0), kappa_s=2.0, scale_radius=8.0, - # truncation_radius=4.0) - # - # mass_at_truncation_radius = mp.mass_at_truncation_radius(redshift_galaxy=0.5, redshift_source=1.0, - # unit_length='arcsec', unit_mass='solMass', cosmology=cosmology) - # - # assert mass_at_truncation_radius == pytest.approx(0.00033636625, abs=1.0e-4) - def test__compare_nfw_and_truncated_nfw_with_large_truncation_radius(): truncated_nfw = ag.mp.NFWTruncatedSph( diff --git a/test_autogalaxy/profiles/mass/dark/test_nfw_truncated_mcr_scatter.py b/test_autogalaxy/profiles/mass/dark/test_nfw_truncated_mcr_scatter.py index bce1951b3..20c3c31f7 100644 --- a/test_autogalaxy/profiles/mass/dark/test_nfw_truncated_mcr_scatter.py +++ b/test_autogalaxy/profiles/mass/dark/test_nfw_truncated_mcr_scatter.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__scatter_is_nonzero(): +def test__scatter_is_nonzero__scatter_positive(): mp = ag.mp.NFWTruncatedMCRScatterLudlowSph( centre=(1.0, 2.0), mass_at_200=1.0e9, @@ -15,11 +15,11 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncatedSph to check the mass gives a conosistnt kappa_s, given certain radii. - assert mp.scale_radius == pytest.approx(0.14978, 1.0e-4) assert mp.truncation_radius == pytest.approx(33.7134116, 1.0e-4) + +def test__scatter_is_nonzero__scatter_negative(): mp = ag.mp.NFWTruncatedMCRScatterLudlowSph( centre=(1.0, 2.0), mass_at_200=1.0e9, @@ -28,7 +28,5 @@ def test__scatter_is_nonzero(): redshift_source=2.5, ) - # We uare using the NFWTruncatedSph to check the mass gives a conosistnt kappa_s, given certain radii. - assert mp.scale_radius == pytest.approx(0.29886, 1.0e-4) assert mp.truncation_radius == pytest.approx(33.7134116, 1.0e-4) diff --git a/test_autogalaxy/profiles/mass/point/test_point.py b/test_autogalaxy/profiles/mass/point/test_point.py index 22b30b448..ab407fb80 100644 --- a/test_autogalaxy/profiles/mass/point/test_point.py +++ b/test_autogalaxy/profiles/mass/point/test_point.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__einstein_radius_1_grid_11(): # The radial coordinate at (1.0, 1.0) is sqrt(2) # This is decomposed into (y,x) angles of sin(45) = cos(45) = sqrt(2) / 2.0 # Thus, for an EinR of 1.0, the deflection angle is (1.0 / sqrt(2)) * (sqrt(2) / 2.0) @@ -18,30 +18,40 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.5, 1e-3) assert deflections[0, 1] == pytest.approx(0.5, 1e-3) + +def test__deflections_yx_2d_from__einstein_radius_2_grid_11(): mp = ag.mp.PointMass(centre=(0.0, 0.0), einstein_radius=2.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 1.0]])) assert deflections[0, 0] == pytest.approx(2.0, 1e-3) assert deflections[0, 1] == pytest.approx(2.0, 1e-3) + +def test__deflections_yx_2d_from__einstein_radius_1_grid_22(): mp = ag.mp.PointMass(centre=(0.0, 0.0), einstein_radius=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 2.0]])) assert deflections[0, 0] == pytest.approx(0.25, 1e-3) assert deflections[0, 1] == pytest.approx(0.25, 1e-3) + +def test__deflections_yx_2d_from__einstein_radius_1_grid_21(): mp = ag.mp.PointMass(centre=(0.0, 0.0), einstein_radius=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 1.0]])) assert deflections[0, 0] == pytest.approx(0.4, 1e-3) assert deflections[0, 1] == pytest.approx(0.2, 1e-3) + +def test__deflections_yx_2d_from__einstein_radius_2_grid_49(): mp = ag.mp.PointMass(centre=(0.0, 0.0), einstein_radius=2.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[4.0, 9.0]])) assert deflections[0, 0] == pytest.approx(16.0 / 97.0, 1e-3) assert deflections[0, 1] == pytest.approx(36.0 / 97.0, 1e-3) + +def test__deflections_yx_2d_from__offset_centre(): mp = ag.mp.PointMass(centre=(1.0, 2.0), einstein_radius=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 3.0]])) diff --git a/test_autogalaxy/profiles/mass/point/test_smbh_binary.py b/test_autogalaxy/profiles/mass/point/test_smbh_binary.py index fcca199a0..4bcc31fc0 100644 --- a/test_autogalaxy/profiles/mass/point/test_smbh_binary.py +++ b/test_autogalaxy/profiles/mass/point/test_smbh_binary.py @@ -4,7 +4,7 @@ import autogalaxy as ag -def test__x2_smbhs__centres_correct_based_on_angle__init(): +def test__x2_smbhs__centres_correct_based_on_angle__angle_0001(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=1.0, @@ -14,6 +14,8 @@ def test__x2_smbhs__centres_correct_based_on_angle__init(): assert mp.smbh_0.centre == pytest.approx((8.726646259967217e-07, 0.5), 1e-2) assert mp.smbh_1.centre == pytest.approx((-8.726646259967217e-07, -0.5), 1e-2) + +def test__x2_smbhs__centres_correct_based_on_angle__angle_90(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=1.0, @@ -27,6 +29,8 @@ def test__x2_smbhs__centres_correct_based_on_angle__init(): assert mp.smbh_0.centre == pytest.approx((0.5, 0.0), 1e-2) assert mp.smbh_1.centre == pytest.approx((-0.5, 0.0), 1e-2) + +def test__x2_smbhs__centres_correct_based_on_angle__angle_180(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=1.0, @@ -36,6 +40,8 @@ def test__x2_smbhs__centres_correct_based_on_angle__init(): assert mp.smbh_0.centre == pytest.approx((0.0, -0.5), 1e-2) assert mp.smbh_1.centre == pytest.approx((0.0, 0.5), 1e-2) + +def test__x2_smbhs__centres_correct_based_on_angle__angle_270(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=1.0, @@ -45,6 +51,8 @@ def test__x2_smbhs__centres_correct_based_on_angle__init(): assert mp.smbh_0.centre == pytest.approx((-0.5, 0.0), 1e-2) assert mp.smbh_1.centre == pytest.approx((0.5, 0.0), 1e-2) + +def test__x2_smbhs__centres_correct_based_on_angle__angle_360(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=1.0, @@ -55,7 +63,7 @@ def test__x2_smbhs__centres_correct_based_on_angle__init(): assert mp.smbh_1.centre == pytest.approx((8.726646259456063e-06, -0.5), 1e-2) -def test__x2_smbhs__separation__init(): +def test__x2_smbhs__separation__separation_2(): mp = ag.mp.SMBHBinary( centre=(0.0, 0.0), separation=2.0, @@ -66,7 +74,7 @@ def test__x2_smbhs__separation__init(): assert mp.smbh_1.centre == pytest.approx((0.0, -1.0), 1e-2) -def test__x2_smbhs__centres_shifted_based_on_centre__init(): +def test__x2_smbhs__centres_shifted_based_on_centre__positive_centre(): mp = ag.mp.SMBHBinary( centre=(3.0, 1.0), separation=1.0, @@ -76,6 +84,8 @@ def test__x2_smbhs__centres_shifted_based_on_centre__init(): assert mp.smbh_0.centre == pytest.approx((3.0, 1.5), 1e-2) assert mp.smbh_1.centre == pytest.approx((3.0, 0.5), 1e-2) + +def test__x2_smbhs__centres_shifted_based_on_centre__negative_centre(): mp = ag.mp.SMBHBinary( centre=(-3.0, -1.0), separation=1.0, @@ -86,7 +96,7 @@ def test__x2_smbhs__centres_shifted_based_on_centre__init(): assert mp.smbh_1.centre == pytest.approx((-3.0, -1.5), 1e-2) -def test__x2_smbhs__masses_corrected_based_on_mass_and_ratio__init(): +def test__x2_smbhs__masses_corrected_based_on_mass_and_ratio__ratio_2(): mp = ag.mp.SMBHBinary( mass=3.0, mass_ratio=2.0, @@ -95,6 +105,8 @@ def test__x2_smbhs__masses_corrected_based_on_mass_and_ratio__init(): assert mp.smbh_0.mass == pytest.approx(2.0, 1e-2) assert mp.smbh_1.mass == pytest.approx(1.0, 1e-2) + +def test__x2_smbhs__masses_corrected_based_on_mass_and_ratio__ratio_05(): mp = ag.mp.SMBHBinary( mass=3.0, mass_ratio=0.5, diff --git a/test_autogalaxy/profiles/mass/sheets/test_external_shear.py b/test_autogalaxy/profiles/mass/sheets/test_external_shear.py index b24f88973..89125cd4a 100644 --- a/test_autogalaxy/profiles/mass/sheets/test_external_shear.py +++ b/test_autogalaxy/profiles/mass/sheets/test_external_shear.py @@ -5,52 +5,58 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__potential_2d_from(): +def test__potential_2d_from__gamma_2_only(): mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) potential = mp.potential_2d_from(grid=ag.Grid2DIrregular([[0.1, 0.1]])) assert potential == pytest.approx(np.array([0.001]), 1.0e-4) + +def test__potential_2d_from__multiple_grid_points(): mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) potential = mp.potential_2d_from( grid=ag.Grid2DIrregular([[0.1, 0.1], [0.2, 0.2], [0.3, 0.3]]) ) assert potential == pytest.approx(np.array([0.001, 0.004, 0.009]), 1.0e-4) + +def test__potential_2d_from__gamma_1_and_gamma_2(): mp = ag.mp.ExternalShear(gamma_1=0.1, gamma_2=-0.05) potential = mp.potential_2d_from(grid=ag.Grid2DIrregular([[0.1, 0.1]])) assert potential == pytest.approx(np.array([-0.0005]), 1.0e-4) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__gamma_2_only(): mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) assert deflections[0, 0] == pytest.approx(0.01625, 1e-3) assert deflections[0, 1] == pytest.approx(0.01625, 1e-3) - mp = ag.mp.ExternalShear(gamma_1=-0.17320, gamma_2=0.1) - deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) - assert deflections[0, 0] == pytest.approx(0.04439, 1e-3) - assert deflections[0, 1] == pytest.approx(-0.011895, 1e-3) +def test__deflections_yx_2d_from__gamma_1_and_gamma_2(): + mp = ag.mp.ExternalShear(gamma_1=-0.17320, gamma_2=0.1) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) - assert deflections[0, 0] == pytest.approx(0.04439, 1e-3) assert deflections[0, 1] == pytest.approx(-0.011895, 1e-3) -def test__convergence_returns_zeros(): +def test__convergence_returns_zeros__single_point(): mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.1, 0.1]])) assert (convergence == np.array([0.0])).all() + +def test__convergence_returns_zeros__multiple_grid_points(): mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) convergence = mp.convergence_2d_from( grid=ag.Grid2DIrregular([[0.1, 0.1], [0.2, 0.2], [0.3, 0.3]]) ) assert (convergence == np.array([0.0, 0.0, 0.0])).all() + +def test__convergence_returns_zeros__y_axis(): + mp = ag.mp.ExternalShear(gamma_1=0.0, gamma_2=0.1) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) assert convergence[0] == pytest.approx(0.0, 1e-3) diff --git a/test_autogalaxy/profiles/mass/sheets/test_mass_sheet.py b/test_autogalaxy/profiles/mass/sheets/test_mass_sheet.py index 2d0b1b9f0..f66c02ce8 100644 --- a/test_autogalaxy/profiles/mass/sheets/test_mass_sheet.py +++ b/test_autogalaxy/profiles/mass/sheets/test_mass_sheet.py @@ -7,7 +7,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__kappa_1_y_axis(): mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 0.0]])) @@ -15,6 +15,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(2.0, 1e-3) assert deflections[0, 1] == pytest.approx(0.0, abs=1e-3) + +def test__deflections_yx_2d_from__kappa_neg1_y_axis(): mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=-1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 0.0]])) @@ -22,6 +24,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-2.0, 1e-3) assert deflections[0, 1] == pytest.approx(0.0, abs=1e-3) + +def test__deflections_yx_2d_from__kappa_2_diagonal(): # The radial coordinate at (1.0, 1.0) is sqrt(2) # This is decomposed into (y,x) angles of sin(45) = cos(45) = sqrt(2) / 2.0 # Thus, for a mass sheet, the deflection angle is (sqrt(2) * sqrt(2) / 2.0) = 1.0 @@ -32,6 +36,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(4.0, 1e-3) assert deflections[0, 1] == pytest.approx(4.0, 1e-3) + +def test__deflections_yx_2d_from__kappa_1_off_axis(): mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=1.0) # The radial coordinate at (2.0, 1.0) is sqrt(5) @@ -43,6 +49,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.8944271 * np.sqrt(5), 1e-3) assert deflections[0, 1] == pytest.approx(0.4472135 * np.sqrt(5), 1e-3) + +def test__deflections_yx_2d_from__offset_centre(): mp = ag.mp.MassSheet(centre=(1.0, 2.0), kappa=-1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 3.0]])) @@ -50,13 +58,17 @@ def test__deflections_yx_2d_from(): assert deflections[0, 1] == pytest.approx(-1.0, 1e-3) -def test__convergence_2d_from(): +def test__convergence_2d_from__kappa_1(): mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=1.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) assert convergence[0] == pytest.approx(1.0, 1e-3) + +def test__convergence_2d_from__kappa_1_multiple_grid_points(): + mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=1.0) + convergence = mp.convergence_2d_from( grid=ag.Grid2DIrregular([[1.0, 0.0], [3.0, 3.0], [5.0, -9.0]]) ) @@ -65,6 +77,8 @@ def test__convergence_2d_from(): assert convergence[1] == pytest.approx(1.0, 1e-3) assert convergence[2] == pytest.approx(1.0, 1e-3) + +def test__convergence_2d_from__kappa_neg3(): mp = ag.mp.MassSheet(centre=(0.0, 0.0), kappa=-3.0) convergence = mp.convergence_2d_from( diff --git a/test_autogalaxy/profiles/mass/stellar/test_chameleon.py b/test_autogalaxy/profiles/mass/stellar/test_chameleon.py index 2312839d1..a3fe3b13f 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_chameleon.py +++ b/test_autogalaxy/profiles/mass/stellar/test_chameleon.py @@ -24,7 +24,7 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 1] == pytest.approx(1.55252, 1e-3) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__chameleon(): mp = ag.mp.Chameleon() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -34,6 +34,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__chameleon_sph(): mp = ag.mp.ChameleonSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -62,7 +64,7 @@ def test__spherical_and_elliptical_identical(): assert elliptical_deflections == pytest.approx(spherical_deflections.array, 1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__chameleon_config_1(): mp = ag.mp.Chameleon( ell_comps=(0.0, 0.0), intensity=1.0, @@ -75,6 +77,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.018605, 1e-3) + +def test__convergence_2d_from__chameleon_config_2(): mp = ag.mp.Chameleon( ell_comps=(0.5, 0.0), intensity=3.0, @@ -87,6 +91,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.007814, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Chameleon( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/stellar/test_dev_vaucouleurs.py b/test_autogalaxy/profiles/mass/stellar/test_dev_vaucouleurs.py index 55d84ae8d..d49ee19ac 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_dev_vaucouleurs.py +++ b/test_autogalaxy/profiles/mass/stellar/test_dev_vaucouleurs.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__dev_vaucouleurs(): mp = ag.mp.DevVaucouleurs() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -16,6 +16,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_yx_2d_from__dev_vaucouleurs_sph(): mp = ag.mp.DevVaucouleursSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -43,7 +45,7 @@ def test__deflections_via_integral_from(): assert deflections[0, 1] == pytest.approx(-3.37605, 1e-3) -def test__deflections_2d_via_cse_from(): +def test__deflections_2d_via_cse_from__config_1(): mp = ag.mp.DevVaucouleurs( centre=(0.4, 0.2), ell_comps=(0.0180010, 0.0494575), @@ -61,6 +63,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__config_2(): mp = ag.mp.DevVaucouleurs( centre=(0.4, 0.2), ell_comps=(0.4180010, 0.694575), @@ -79,7 +83,7 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__dev_vaucouleurs_config_1(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, 0.333333), intensity=3.0, @@ -91,6 +95,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(5.6697, 1e-3) + +def test__convergence_2d_from__dev_vaucouleurs_config_2(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -102,6 +108,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(7.4455, 1e-3) + +def test__convergence_2d_from__dev_vaucouleurs_intensity_4(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=4.0, @@ -113,6 +121,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 7.4455, 1e-3) + +def test__convergence_2d_from__dev_vaucouleurs_mass_to_light_2(): mp = ag.mp.DevVaucouleurs( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -124,6 +134,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 7.4455, 1e-3) + +def test__convergence_2d_from__dev_vaucouleurs_small_effective_radius(): mp = ag.mp.DevVaucouleurs( centre=(0.0, 0.0), intensity=1.0, @@ -135,6 +147,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.351797, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.DevVaucouleurs( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/stellar/test_exponential.py b/test_autogalaxy/profiles/mass/stellar/test_exponential.py index 45349bf5f..e1b9f4dc3 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_exponential.py +++ b/test_autogalaxy/profiles/mass/stellar/test_exponential.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__exponential(): mp = ag.mp.Exponential() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -16,6 +16,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_yx_2d_from__exponential_sph(): mp = ag.mp.ExponentialSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -26,7 +28,7 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__deflections_2d_via_integral_from(): +def test__deflections_2d_via_integral_from__config_1(): mp = ag.mp.Exponential( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -42,6 +44,8 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 0] == pytest.approx(0.90493, 1e-3) assert deflections[0, 1] == pytest.approx(0.62569, 1e-3) + +def test__deflections_2d_via_integral_from__config_2(): mp = ag.mp.Exponential( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -58,7 +62,7 @@ def test__deflections_2d_via_integral_from(): assert deflections[0, 1] == pytest.approx(0.62569, 1e-3) -def test__deflections_2d_via_cse_from(): +def test__deflections_2d_via_cse_from__config_1(): mp = ag.mp.Exponential( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -76,6 +80,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__config_2(): mp = ag.mp.Exponential( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -94,7 +100,7 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__exponential_config_1(): mp = ag.mp.Exponential( ell_comps=(0.0, 0.333333), intensity=3.0, @@ -106,6 +112,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(4.9047, 1e-3) + +def test__convergence_2d_from__exponential_config_2(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -117,6 +125,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(4.8566, 1e-3) + +def test__convergence_2d_from__exponential_intensity_4(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=4.0, @@ -127,6 +137,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 4.8566, 1e-3) + +def test__convergence_2d_from__exponential_mass_to_light_2(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -138,6 +150,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 4.8566, 1e-3) + +def test__convergence_2d_from__exponential_config_5(): mp = ag.mp.Exponential( ell_comps=(0.0, -0.333333), intensity=2.0, @@ -149,6 +163,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(4.8566, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Exponential( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/stellar/test_gaussian.py b/test_autogalaxy/profiles/mass/stellar/test_gaussian.py index d706a663b..e820ffee4 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_gaussian.py +++ b/test_autogalaxy/profiles/mass/stellar/test_gaussian.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_2d_via_analytic_from(): +def test__deflections_2d_via_analytic_from__config_1_positive_y(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.05263), @@ -22,6 +22,16 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 0] == pytest.approx(1.024423, 1.0e-4) assert deflections[0, 1] == pytest.approx(0.0, abs=1.0e-4) + +def test__deflections_2d_via_analytic_from__config_1_negative_y(): + mp = ag.mp.Gaussian( + centre=(0.0, 0.0), + ell_comps=(0.0, 0.05263), + intensity=1.0, + sigma=3.0, + mass_to_light_ratio=1.0, + ) + deflections = mp.deflections_2d_via_analytic_from( grid=ag.Grid2DIrregular([[-1.0, 0.0]]) ) @@ -29,6 +39,8 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 0] == pytest.approx(-1.024423, 1.0e-4) assert deflections[0, 1] == pytest.approx(0.0, abs=1.0e-4) + +def test__deflections_2d_via_analytic_from__config_2_positive(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -44,6 +56,16 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 0] == pytest.approx(0.554062, 1.0e-4) assert deflections[0, 1] == pytest.approx(0.177336, 1.0e-4) + +def test__deflections_2d_via_analytic_from__config_2_negative(): + mp = ag.mp.Gaussian( + centre=(0.0, 0.0), + ell_comps=(0.0, 0.111111), + intensity=1.0, + sigma=5.0, + mass_to_light_ratio=1.0, + ) + deflections = mp.deflections_2d_via_analytic_from( grid=ag.Grid2DIrregular([[-0.5, -0.2]]) ) @@ -51,6 +73,8 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 0] == pytest.approx(-0.554062, 1.0e-4) assert deflections[0, 1] == pytest.approx(-0.177336, 1.0e-4) + +def test__deflections_2d_via_analytic_from__mass_to_light_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -66,6 +90,8 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 0] == pytest.approx(1.108125, 1.0e-4) assert deflections[0, 1] == pytest.approx(0.35467, 1.0e-4) + +def test__deflections_2d_via_analytic_from__intensity_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -82,7 +108,7 @@ def test__deflections_2d_via_analytic_from(): assert deflections[0, 1] == pytest.approx(0.35467, 1.0e-4) -def test__deflections_2d_via_integral_from(): +def test__deflections_2d_via_integral_from__config_1(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.05263), @@ -100,6 +126,8 @@ def test__deflections_2d_via_integral_from(): assert deflections == pytest.approx(deflections_via_analytic.array, 1.0e-3) + +def test__deflections_2d_via_integral_from__config_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -117,6 +145,8 @@ def test__deflections_2d_via_integral_from(): assert deflections == pytest.approx(deflections_via_analytic.array, 1.0e-3) + +def test__deflections_2d_via_integral_from__mass_to_light_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -134,6 +164,8 @@ def test__deflections_2d_via_integral_from(): assert deflections == pytest.approx(deflections_via_analytic.array, 1.0e-3) + +def test__deflections_2d_via_integral_from__intensity_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.111111), @@ -163,7 +195,7 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__gaussian_config_1(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -176,6 +208,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.60653, 1e-2) + +def test__convergence_2d_from__gaussian_mass_to_light_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -188,6 +222,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.60653, 1e-2) + +def test__convergence_2d_from__gaussian_elliptical(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -223,7 +259,7 @@ def test__intensity_and_convergence_match_for_mass_light_ratio_1(): assert (intensity == convergence).all() -def test__image_2d_via_radii_from__correct_value(): +def test__image_2d_via_radii_from__config_1(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), intensity=1.0, sigma=1.0 ) @@ -232,6 +268,8 @@ def test__image_2d_via_radii_from__correct_value(): assert intensity == pytest.approx(0.60653, 1e-2) + +def test__image_2d_via_radii_from__intensity_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), intensity=2.0, sigma=1.0 ) @@ -240,6 +278,8 @@ def test__image_2d_via_radii_from__correct_value(): assert intensity == pytest.approx(2.0 * 0.60653, 1e-2) + +def test__image_2d_via_radii_from__sigma_2(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), intensity=1.0, sigma=2.0 ) @@ -247,6 +287,8 @@ def test__image_2d_via_radii_from__correct_value(): assert intensity == pytest.approx(0.882496, 1e-2) + +def test__image_2d_via_radii_from__sigma_2_radii_3(): mp = ag.mp.Gaussian( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), intensity=1.0, sigma=2.0 ) @@ -256,7 +298,7 @@ def test__image_2d_via_radii_from__correct_value(): assert intensity == pytest.approx(0.32465, 1e-2) -def test__wofz(): +def test__wofz__regions_1_2_3(): from scipy.special import wofz mp = ag.mp.Gaussian( @@ -271,6 +313,14 @@ def test__wofz(): assert wofz_approx_reg_2 == pytest.approx(wofz(2.0 + 1j * 0.001), 1e-4) assert wofz_approx_reg_3 == pytest.approx(wofz(1.0 + 1j * 0.001), 1e-4) + +def test__wofz__regions_4_5_6(): + from scipy.special import wofz + + mp = ag.mp.Gaussian( + centre=(0.0, 0.0), ell_comps=(0.0, 0.0), intensity=1.0, sigma=2.0 + ) + wofz_approx_reg_1 = mp.wofz(7.0 + 1j * 0.1) wofz_approx_reg_2 = mp.wofz(7.0 + 1j * 1e-11) wofz_approx_reg_3 = mp.wofz(2.0 + 1j * 1.0) diff --git a/test_autogalaxy/profiles/mass/stellar/test_gaussian_gradient.py b/test_autogalaxy/profiles/mass/stellar/test_gaussian_gradient.py index 53d07f2ba..954953628 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_gaussian_gradient.py +++ b/test_autogalaxy/profiles/mass/stellar/test_gaussian_gradient.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__init_constructor__scales_values_correct(): +def test__init_constructor__gradient_0_scales_values_correctly(): mp = ag.mp.GaussianGradient( centre=(0.0, 0.0), ell_comps=(0.0, 0.05), @@ -26,6 +26,8 @@ def test__init_constructor__scales_values_correct(): assert mp.mass_to_light_radius == 1.0 assert mp.mass_to_light_ratio == 1.0 + +def test__init_constructor__gradient_1_scales_values_correctly(): mp = ag.mp.GaussianGradient( centre=(0.0, 0.0), ell_comps=(0.0, 0.05), diff --git a/test_autogalaxy/profiles/mass/stellar/test_sersic.py b/test_autogalaxy/profiles/mass/stellar/test_sersic.py index b010aafbe..46f22bc35 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_sersic.py +++ b/test_autogalaxy/profiles/mass/stellar/test_sersic.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_via_integral_from(): +def test__deflections_via_integral_from__config_1(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -23,6 +23,8 @@ def test__deflections_via_integral_from(): assert deflections[0, 0] == pytest.approx(1.1446, 1e-3) assert deflections[0, 1] == pytest.approx(0.79374, 1e-3) + +def test__deflections_via_integral_from__config_2(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -40,7 +42,7 @@ def test__deflections_via_integral_from(): assert deflections[0, 1] == pytest.approx(1.80719, 1e-3) -def test__deflections_2d_via_cse_from(): +def test__deflections_2d_via_cse_from__config_1(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -59,6 +61,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__config_2(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -77,6 +81,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-3) + +def test__deflections_2d_via_cse_from__config_3(): mp = ag.mp.Sersic( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -96,7 +102,7 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-3) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__sersic(): mp = ag.mp.Sersic() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -106,6 +112,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__sersic_sph(): mp = ag.mp.SersicSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -115,6 +123,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Sersic( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -138,7 +148,7 @@ def test__deflections_yx_2d_from(): assert elliptical_deflections == pytest.approx(spherical_deflections.array, 1.0e-4) -def test__convergence_2d_via_cse_from(): +def test__convergence_2d_via_cse_from__sersic_intensity_3(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -151,6 +161,8 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(4.90657319276, 1e-3) + +def test__convergence_2d_via_cse_from__sersic_intensity_6(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=6.0, @@ -163,6 +175,8 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__convergence_2d_via_cse_from__sersic_mass_to_light_2(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -175,6 +189,8 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__convergence_2d_via_cse_from__sersic_elliptical(): mp = ag.mp.Sersic( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -189,7 +205,7 @@ def test__convergence_2d_via_cse_from(): assert convergence == pytest.approx(5.38066670129, 1e-3) -def test__convergence_2d_from(): +def test__convergence_2d_from__sersic_intensity_3(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -202,6 +218,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_intensity_6(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=6.0, @@ -214,6 +232,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_mass_to_light_2(): mp = ag.mp.Sersic( centre=(0.0, 0.0), intensity=3.0, @@ -226,6 +246,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_elliptical(): mp = ag.mp.Sersic( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -239,6 +261,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(5.38066670129, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Sersic( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/stellar/test_sersic_core.py b/test_autogalaxy/profiles/mass/stellar/test_sersic_core.py index 488c1e107..72c182561 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_sersic_core.py +++ b/test_autogalaxy/profiles/mass/stellar/test_sersic_core.py @@ -98,7 +98,7 @@ # -def test__convergence_2d_from(): +def test__convergence_2d_from__sersic_core_mass_to_light_1(): mp = ag.mp.SersicCore( ell_comps=(0.0, 0.0), effective_radius=5.0, @@ -114,6 +114,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.1, 1e-3) + +def test__convergence_2d_from__sersic_core_mass_to_light_2(): mp = ag.mp.SersicCore( ell_comps=(0.0, 0.0), effective_radius=5.0, @@ -129,6 +131,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.2, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.SersicCore( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/stellar/test_sersic_gradient.py b/test_autogalaxy/profiles/mass/stellar/test_sersic_gradient.py index f85156c26..4d09ed218 100644 --- a/test_autogalaxy/profiles/mass/stellar/test_sersic_gradient.py +++ b/test_autogalaxy/profiles/mass/stellar/test_sersic_gradient.py @@ -5,7 +5,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_via_integral_from(): +def test__deflections_via_integral_from__gradient_1(): mp = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -23,6 +23,8 @@ def test__deflections_via_integral_from(): assert deflections[0, 0] == pytest.approx(3.60324873535244, 1e-3) assert deflections[0, 1] == pytest.approx(2.3638898009652, 1e-3) + +def test__deflections_via_integral_from__gradient_neg_1(): mp = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -62,7 +64,7 @@ def test__deflections_via_integral_from(): # assert deflections_via_integral == pytest.approx(deflections_via_mge.array, 1.0e-3) -def test__deflections_2d_via_cse_from(): +def test__deflections_2d_via_cse_from__gradient_1(): mp = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -82,6 +84,8 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) + +def test__deflections_2d_via_cse_from__gradient_neg_1(): mp = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -102,7 +106,7 @@ def test__deflections_2d_via_cse_from(): assert deflections_via_integral == pytest.approx(deflections_via_cse.array, 1.0e-4) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__sersic_gradient(): mp = ag.mp.SersicGradient() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -112,6 +116,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__sersic_gradient_sph(): mp = ag.mp.SersicGradientSph() deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) @@ -121,6 +127,8 @@ def test__deflections_yx_2d_from(): assert deflections == pytest.approx(deflections_via_integral.array, 1.0e-4) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.SersicGradient( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -146,7 +154,7 @@ def test__deflections_yx_2d_from(): ell_deflections_yx_2d == pytest.approx(sph_deflections_yx_2d.array, 1.0e-4) -def test__convergence_2d_from(): +def test__convergence_2d_from__sersic_gradient_config_1(): # ((axis_ratio*radius/effective_radius)**-mass_to_light_gradient) = (1/0.6)**-1.0 = 0.6 mp = ag.mp.SersicGradient( centre=(0.0, 0.0), @@ -162,6 +170,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.6 * 0.351797, 1e-3) + +def test__convergence_2d_from__sersic_gradient_config_2(): # ((axis_ratio*radius/effective_radius)**-mass_to_light_gradient) = (1.5/2.0)**1.0 = 0.75 mp = ag.mp.SersicGradient( @@ -177,6 +187,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.75 * 4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_gradient_intensity_6(): mp = ag.mp.SersicGradient( ell_comps=(0.0, 0.0), intensity=6.0, @@ -190,6 +202,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.75 * 4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_gradient_mass_to_light_2(): mp = ag.mp.SersicGradient( ell_comps=(0.0, 0.0), intensity=3.0, @@ -203,6 +217,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.75 * 4.90657319276, 1e-3) + +def test__convergence_2d_from__sersic_gradient_elliptical(): # ((axis_ratio*radius/effective_radius)**-mass_to_light_gradient) = ((0.5*1.41)/2.0)**-1.0 = 2.836 mp = ag.mp.SersicGradient( ell_comps=(0.0, 0.333333), @@ -217,6 +233,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.836879 * 5.38066670129, abs=2e-01) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.SersicGradient( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -242,7 +260,7 @@ def test__convergence_2d_from(): assert ell_convergence_2d == pytest.approx(sph_convergence_2d.array, 1.0e-4) -def test__compare_to_sersic(): +def test__compare_to_sersic__gradient_0_vs_exponential(): mp = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), @@ -277,6 +295,8 @@ def test__compare_to_sersic(): ) assert sersic_deflections[0, 1] == pytest.approx(0.62569, 1e-3) + +def test__compare_to_sersic__gradient_0_vs_dev_vaucouleurs(): mp = ag.mp.SersicGradient( centre=(0.4, 0.2), ell_comps=(0.0180010, 0.0494575), @@ -307,6 +327,8 @@ def test__compare_to_sersic(): # assert sersic_deflections[0, 1] == pytest.approx(dev_deflections[0, 1], 1e-3) # assert sersic_deflections[0, 1] == pytest.approx(-3.37605, 1e-3) + +def test__compare_to_sersic__gradient_0_vs_sersic(): sersic_grad = ag.mp.SersicGradient( centre=(-0.4, -0.2), ell_comps=(-0.07142, -0.085116), diff --git a/test_autogalaxy/profiles/mass/test_scaling_relations.py b/test_autogalaxy/profiles/mass/test_scaling_relations.py index e201272c9..0c4432605 100644 --- a/test_autogalaxy/profiles/mass/test_scaling_relations.py +++ b/test_autogalaxy/profiles/mass/test_scaling_relations.py @@ -2,7 +2,7 @@ class TestMassLightRelation: - def test__einstein_radius_from(self): + def test__einstein_radius_from__gradient_1_denominator_1(self): mass_light_relation = ag.sr.MassLightRelation( gradient=1.0, denominator=1.0, power=1.0 ) @@ -15,6 +15,7 @@ def test__einstein_radius_from(self): assert einstein_radius == 2.0 + def test__einstein_radius_from__gradient_2_denominator_1(self): mass_light_relation = ag.sr.MassLightRelation( gradient=2.0, denominator=1.0, power=1.0 ) @@ -23,6 +24,7 @@ def test__einstein_radius_from(self): assert einstein_radius == 4.0 + def test__einstein_radius_from__gradient_2_denominator_05(self): mass_light_relation = ag.sr.MassLightRelation( gradient=2.0, denominator=0.5, power=1.0 ) @@ -31,6 +33,7 @@ def test__einstein_radius_from(self): assert einstein_radius == 16.0 + def test__einstein_radius_from__gradient_2_denominator_05_power_2(self): mass_light_relation = ag.sr.MassLightRelation( gradient=2.0, denominator=0.5, power=2.0 ) @@ -41,7 +44,7 @@ def test__einstein_radius_from(self): class TestIsothermalMLR: - def test__setup_correctly_from_luminosity(self): + def test__setup_correctly_from_luminosity__isothermal_sph(self): relation = ag.sr.MassLightRelation(gradient=2.0, denominator=0.5, power=2.0) sis = ag.sr.IsothermalSphMLR( @@ -53,6 +56,7 @@ def test__setup_correctly_from_luminosity(self): assert sis.centre == (1.0, 1.0) assert sis.einstein_radius == 128.0 + def test__setup_correctly_from_luminosity__isothermal_elliptical(self): relation = ag.sr.MassLightRelation(gradient=2.0, denominator=0.5, power=2.0) sie = ag.sr.IsothermalMLR( diff --git a/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_mass.py b/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_mass.py index ef5b441c0..5d0e30afd 100644 --- a/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_mass.py +++ b/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_mass.py @@ -5,7 +5,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__sph_config_1(): mp = ag.mp.dPIEMassSph(centre=(-0.7, 0.5), b0=5.2, ra=2.0, rs=3.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -13,6 +13,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.033080741, 1e-4) assert deflections[0, 1] == pytest.approx(-0.39286169026, 1e-4) + +def test__deflections_yx_2d_from__sph_config_2(): mp = ag.mp.dPIEMassSph(centre=(-0.1, 0.1), b0=20.0, ra=2.0, rs=3.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -20,6 +22,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.4212977207, 1e-4) assert deflections[0, 1] == pytest.approx(0.308977765378, 1e-4) + +def test__deflections_yx_2d_from__elliptical(): # First deviation from potential case due to ellipticity mp = ag.mp.dPIEMass( @@ -31,6 +35,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.21461366, 1e-3) assert deflections[0, 1] == pytest.approx(0.10753914, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.dPIEMass( centre=(1.1, 1.1), ell_comps=(0.000001, 0.0000001), b0=12.0, ra=2.0, rs=3.0 ) diff --git a/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_potential.py b/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_potential.py index ea43e7c06..2ea341b4c 100644 --- a/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_potential.py +++ b/test_autogalaxy/profiles/mass/total/test_dual_pseudo_isothermal_potential.py @@ -5,7 +5,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__sph_config_1(): mp = ag.mp.dPIEPotentialSph(centre=(-0.7, 0.5), b0=5.2, ra=2.0, rs=3.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -13,6 +13,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.033080741, 1e-4) assert deflections[0, 1] == pytest.approx(-0.39286169026, 1e-4) + +def test__deflections_yx_2d_from__sph_config_2(): mp = ag.mp.dPIEPotentialSph(centre=(-0.1, 0.1), b0=20.0, ra=2.0, rs=3.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -20,6 +22,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.4212977207, 1e-4) assert deflections[0, 1] == pytest.approx(0.308977765378, 1e-4) + +def test__deflections_yx_2d_from__elliptical(): mp = ag.mp.dPIEPotential( centre=(0, 0), ell_comps=(0.0, 0.333333), b0=4.0, ra=2.0, rs=3.0 ) @@ -29,6 +33,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.186341843, 1e-3) assert deflections[0, 1] == pytest.approx(0.13176363087, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.dPIEPotential( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), b0=12.0, ra=2.0, rs=3.0 ) @@ -39,7 +45,7 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): +def test__convergence_2d_from__sph(): # eta = 1.0 # kappa = 0.5 * 1.0 ** 1.0 @@ -49,6 +55,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(1.57182995, 1e-3) + +def test__convergence_2d_from__elliptical_no_ell_comps_b0_4(): mp = ag.mp.dPIEPotential( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), b0=4.0, ra=2.0, rs=3.0 ) @@ -57,6 +65,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.78591498, 1e-3) + +def test__convergence_2d_from__elliptical_no_ell_comps_b0_8(): mp = ag.mp.dPIEPotential( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), b0=8.0, ra=2.0, rs=3.0 ) @@ -65,6 +75,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(1.57182995, 1e-3) + +def test__convergence_2d_from__elliptical_with_ell_comps(): mp = ag.mp.dPIEPotential( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), b0=4.0, ra=2.0, rs=3.0 ) @@ -73,6 +85,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.87182837, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.dPIEPotential( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), b0=12.0, ra=2.0, rs=3.0 ) diff --git a/test_autogalaxy/profiles/mass/total/test_isothermal.py b/test_autogalaxy/profiles/mass/total/test_isothermal.py index 22605bb1b..2f961b45b 100644 --- a/test_autogalaxy/profiles/mass/total/test_isothermal.py +++ b/test_autogalaxy/profiles/mass/total/test_isothermal.py @@ -5,7 +5,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__isothermal_sph_config_1(): mp = ag.mp.IsothermalSph(centre=(-0.7, 0.5), einstein_radius=1.3) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -13,6 +13,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.21510, 1e-4) assert deflections[0, 1] == pytest.approx(-0.46208, 1e-4) + +def test__deflections_yx_2d_from__isothermal_sph_config_2(): mp = ag.mp.IsothermalSph(centre=(-0.1, 0.1), einstein_radius=5.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -20,6 +22,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(4.88588, 1e-4) assert deflections[0, 1] == pytest.approx(1.06214, 1e-4) + +def test__deflections_yx_2d_from__isothermal_ell_config_1(): mp = ag.mp.Isothermal(centre=(0, 0), ell_comps=(0.0, 0.333333), einstein_radius=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) @@ -27,6 +31,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.79421, 1e-3) assert deflections[0, 1] == pytest.approx(0.50734, 1e-3) + +def test__deflections_yx_2d_from__isothermal_ell_config_2(): mp = ag.mp.Isothermal(centre=(0, 0), ell_comps=(0.0, 0.333333), einstein_radius=1.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) @@ -34,6 +40,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.79421, 1e-3) assert deflections[0, 1] == pytest.approx(0.50734, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Isothermal( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), einstein_radius=3.0 ) @@ -44,28 +52,31 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): - # eta = 1.0 - # kappa = 0.5 * 1.0 ** 1.0 - +def test__convergence_2d_from__isothermal_sph(): mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) assert convergence == pytest.approx(0.5 * 2.0, 1e-3) + +def test__convergence_2d_from__isothermal_no_ell(): mp = ag.mp.Isothermal(centre=(0.0, 0.0), ell_comps=(0.0, 0.0), einstein_radius=1.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) assert convergence == pytest.approx(0.5, 1e-3) + +def test__convergence_2d_from__isothermal_einstein_radius_2(): mp = ag.mp.Isothermal(centre=(0.0, 0.0), ell_comps=(0.0, 0.0), einstein_radius=2.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) assert convergence == pytest.approx(0.5 * 2.0, 1e-3) + +def test__convergence_2d_from__isothermal_with_ell_comps(): mp = ag.mp.Isothermal( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), einstein_radius=1.0 ) @@ -74,6 +85,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.66666, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Isothermal( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), einstein_radius=3.0 ) @@ -84,13 +97,15 @@ def test__convergence_2d_from(): ) -def test__potential_2d_from(): +def test__potential_2d_from__isothermal_sph(): mp = ag.mp.IsothermalSph(centre=(-0.7, 0.5), einstein_radius=1.3) potential = mp.potential_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) assert potential == pytest.approx(1.23435, 1e-3) + +def test__potential_2d_from__isothermal_elliptical(): mp = ag.mp.Isothermal( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -101,6 +116,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(1.19268, 1e-3) + +def test__potential_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.Isothermal( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), einstein_radius=3.0 ) @@ -111,37 +128,47 @@ def test__potential_2d_from(): ) -def test__shear_yx_2d_from(): +def test__shear_yx_2d_from__isothermal_sph_grid_1(): mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) - shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) assert shear[0, 0] == pytest.approx(0.0, 1e-4) assert shear[0, 1] == pytest.approx(-convergence.array[0], 1e-4) + +def test__shear_yx_2d_from__isothermal_sph_grid_2(): + mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) + convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[2.0, 1.0]])) shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[2.0, 1.0]])) assert shear[0, 0] == pytest.approx(-(4.0 / 5.0) * convergence.array[0], 1e-4) assert shear[0, 1] == pytest.approx((3.0 / 5.0) * convergence.array[0], 1e-4) + +def test__shear_yx_2d_from__isothermal_sph_grid_3(): + mp = ag.mp.IsothermalSph(centre=(0.0, 0.0), einstein_radius=2.0) + convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[3.0, 5.0]])) shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[3.0, 5.0]])) assert shear[0, 0] == pytest.approx(-(30.0 / 34.0) * convergence.array[0], 1e-4) assert shear[0, 1] == pytest.approx(-(16.0 / 34.0) * convergence.array[0], 1e-4) + +def test__shear_yx_2d_from__isothermal_no_ell(): mp = ag.mp.Isothermal(centre=(0.0, 0.0), ell_comps=(0.0, 0.0), einstein_radius=2.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) - shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) assert shear[0, 0] == pytest.approx(0.0, 1e-4) assert shear[0, 1] == pytest.approx(-convergence.array[0], 1e-4) + +def test__shear_yx_2d_from__isothermal_with_ell_comps(): mp = ag.mp.Isothermal(centre=(0.0, 0.0), ell_comps=(0.3, 0.4), einstein_radius=2.0) shear = mp.shear_yx_2d_from(grid=ag.Grid2DIrregular([[0.0, 1.0]])) diff --git a/test_autogalaxy/profiles/mass/total/test_isothermal_cored.py b/test_autogalaxy/profiles/mass/total/test_isothermal_cored.py index 143a1d294..ed5cedf9c 100644 --- a/test_autogalaxy/profiles/mass/total/test_isothermal_cored.py +++ b/test_autogalaxy/profiles/mass/total/test_isothermal_cored.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__isothermal_core_sph_config_1(): mp = ag.mp.IsothermalCoreSph( centre=(-0.7, 0.5), einstein_radius=1.3, core_radius=0.2 ) @@ -16,6 +16,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.98582, 1e-3) assert deflections[0, 1] == pytest.approx(-0.37489, 1e-3) + +def test__deflections_yx_2d_from__isothermal_core_sph_config_2(): mp = ag.mp.IsothermalCoreSph( centre=(0.2, -0.2), einstein_radius=0.5, core_radius=0.5 ) @@ -25,6 +27,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-0.00559, 1e-3) assert deflections[0, 1] == pytest.approx(0.16216, 1e-3) + +def test__deflections_yx_2d_from__isothermal_core_ell_config_1(): mp = ag.mp.IsothermalCore( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -37,6 +41,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.95429, 1e-3) assert deflections[0, 1] == pytest.approx(-0.52047, 1e-3) + +def test__deflections_yx_2d_from__isothermal_core_ell_config_2(): mp = ag.mp.IsothermalCore( centre=(0.2, -0.2), ell_comps=(-0.216506, -0.125), @@ -49,6 +55,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.02097, 1e-3) assert deflections[0, 1] == pytest.approx(0.20500, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.IsothermalCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -64,19 +72,23 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): +def test__convergence_2d_from__isothermal_core_sph_convergence_func(): mp = ag.mp.IsothermalCoreSph(centre=(1, 1), einstein_radius=1.0, core_radius=0.1) convergence = mp.convergence_func(grid_radius=1.0) assert convergence == pytest.approx(0.49752, 1e-4) + +def test__convergence_2d_from__isothermal_core_sph_config_2(): mp = ag.mp.IsothermalCoreSph(centre=(1, 1), einstein_radius=1.0, core_radius=0.1) convergence = mp.convergence_func(grid_radius=1.0) assert convergence == pytest.approx(0.49752, 1e-4) + +def test__convergence_2d_from__isothermal_core_sph_core_radius_02(): mp = ag.mp.IsothermalCoreSph( centre=(0.0, 0.0), einstein_radius=1.0, core_radius=0.2 ) @@ -85,6 +97,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.49029, 1e-3) + +def test__convergence_2d_from__isothermal_core_sph_einstein_radius_2(): mp = ag.mp.IsothermalCoreSph( centre=(0.0, 0.0), einstein_radius=2.0, core_radius=0.2 ) @@ -93,6 +107,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.49029, 1e-3) + +def test__convergence_2d_from__isothermal_core_sph_y_axis(): mp = ag.mp.IsothermalCoreSph( centre=(0.0, 0.0), einstein_radius=1.0, core_radius=0.2 ) @@ -101,7 +117,9 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.49029, 1e-3) - # axis ratio changes only einstein_rescaled, so wwe can use the above value and times by 1.0/1.5. + +def test__convergence_2d_from__isothermal_core_ell_config_1(): + # axis ratio changes only einstein_rescaled, so we can use the above value and times by 1.0/1.5. mp = ag.mp.IsothermalCore( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -113,6 +131,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.49029 * 1.33333, 1e-3) + +def test__convergence_2d_from__isothermal_core_ell_config_2(): mp = ag.mp.IsothermalCore( centre=(0.0, 0.0), ell_comps=(0.0, 0.0), @@ -124,10 +144,12 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(2.0 * 0.49029, 1e-3) + +def test__convergence_2d_from__isothermal_core_ell_config_3(): # for axis_ratio = 1.0, the factor is 1/2 # for axis_ratio = 0.5, the factor is 1/(1.5) # So the change in the value is 0.5 / (1/1.5) = 1.0 / 0.75 - # axis ratio changes only einstein_rescaled, so wwe can use the above value and times by 1.0/1.5. + # axis ratio changes only einstein_rescaled, so we can use the above value and times by 1.0/1.5. mp = ag.mp.IsothermalCore( centre=(0.0, 0.0), @@ -140,6 +162,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx((1.0 / 0.75) * 0.49029, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.IsothermalCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -155,7 +179,7 @@ def test__convergence_2d_from(): ) -def test__potential_2d_from(): +def test__potential_2d_from__isothermal_core_sph_config_1(): mp = ag.mp.IsothermalCoreSph( centre=(-0.7, 0.5), einstein_radius=1.3, core_radius=0.2 ) @@ -164,6 +188,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.72231, 1e-3) + +def test__potential_2d_from__isothermal_core_sph_config_2(): mp = ag.mp.IsothermalCoreSph( centre=(0.2, -0.2), einstein_radius=0.5, core_radius=0.5 ) @@ -172,6 +198,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.03103, 1e-3) + +def test__potential_2d_from__isothermal_core_ell_config_1(): mp = ag.mp.IsothermalCore( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -183,6 +211,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.74354, 1e-3) + +def test__potential_2d_from__isothermal_core_ell_config_2(): mp = ag.mp.IsothermalCore( centre=(0.2, -0.2), ell_comps=(-0.216506, -0.125), @@ -194,6 +224,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.04024, 1e-3) + +def test__potential_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.IsothermalCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/total/test_power_law.py b/test_autogalaxy/profiles/mass/total/test_power_law.py index 856340d1e..9f9886634 100644 --- a/test_autogalaxy/profiles/mass/total/test_power_law.py +++ b/test_autogalaxy/profiles/mass/total/test_power_law.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__power_law_sph_slope_2(): mp = ag.mp.PowerLawSph(centre=(0.2, 0.2), einstein_radius=1.0, slope=2.0) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -14,6 +14,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-0.31622, 1e-3) assert deflections[0, 1] == pytest.approx(-0.94868, 1e-3) + +def test__deflections_yx_2d_from__power_law_sph_slope_25(): mp = ag.mp.PowerLawSph(centre=(0.2, 0.2), einstein_radius=1.0, slope=2.5) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -21,6 +23,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-1.59054, 1e-3) assert deflections[0, 1] == pytest.approx(-4.77162, 1e-3) + +def test__deflections_yx_2d_from__power_law_sph_slope_15(): mp = ag.mp.PowerLawSph(centre=(0.2, 0.2), einstein_radius=1.0, slope=1.5) deflections = mp.deflections_yx_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]])) @@ -28,6 +32,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-0.06287, 1e-3) assert deflections[0, 1] == pytest.approx(-0.18861, 1e-3) + +def test__deflections_yx_2d_from__power_law_ell_slope_2(): mp = ag.mp.PowerLaw( centre=(0, 0), ell_comps=(0.0, 0.333333), @@ -40,6 +46,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.79421, 1e-3) assert deflections[0, 1] == pytest.approx(0.50734, 1e-3) + +def test__deflections_yx_2d_from__power_law_ell_slope_25(): mp = ag.mp.PowerLaw( centre=(0, 0), ell_comps=(0.0, 0.333333), @@ -52,6 +60,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(1.29641, 1e-3) assert deflections[0, 1] == pytest.approx(0.99629, 1e-3) + +def test__deflections_yx_2d_from__power_law_ell_slope_15(): mp = ag.mp.PowerLaw( centre=(0, 0), ell_comps=(0.0, 0.333333), @@ -64,6 +74,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.48036, 1e-3) assert deflections[0, 1] == pytest.approx(0.26729, 1e-3) + +def test__deflections_yx_2d_from__power_law_ell_slope_19(): mp = ag.mp.PowerLaw( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -76,6 +88,8 @@ def test__deflections_yx_2d_from(): # assert deflections[0, 0] == pytest.approx(1.12841, 1e-3) # assert deflections[0, 1] == pytest.approx(-0.60205, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLaw( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -90,25 +104,31 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): +def test__convergence_2d_from__power_law_sph_config_1(): mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=1.0, slope=2.0) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[1.0, 0.0]])) assert convergence == pytest.approx(0.5, 1e-3) + +def test__convergence_2d_from__power_law_sph_config_2(): mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=2.0, slope=2.2) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[2.0, 0.0]])) assert convergence == pytest.approx(0.4, 1e-3) + +def test__convergence_2d_from__power_law_sph_config_3(): mp = ag.mp.PowerLawSph(centre=(0.0, 0.0), einstein_radius=2.0, slope=2.2) convergence = mp.convergence_2d_from(grid=ag.Grid2DIrregular([[2.0, 0.0]])) assert convergence == pytest.approx(0.4, 1e-3) + +def test__convergence_2d_from__power_law_ell_config_1(): mp = ag.mp.PowerLaw( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -120,6 +140,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.466666, 1e-3) + +def test__convergence_2d_from__power_law_ell_config_2(): mp = ag.mp.PowerLaw( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -131,6 +153,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(1.4079, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLaw( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -145,19 +169,23 @@ def test__convergence_2d_from(): ) -def test__potential_2d_from(): +def test__potential_2d_from__power_law_sph_config_1(): mp = ag.mp.PowerLawSph(centre=(-0.7, 0.5), einstein_radius=1.3, slope=2.3) potential = mp.potential_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) assert potential == pytest.approx(1.90421, 1e-3) + +def test__potential_2d_from__power_law_sph_config_2(): mp = ag.mp.PowerLawSph(centre=(-0.7, 0.5), einstein_radius=1.3, slope=1.8) potential = mp.potential_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]])) assert potential == pytest.approx(0.93758, 1e-3) + +def test__potential_2d_from__power_law_ell_config_1(): mp = ag.mp.PowerLaw( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -169,6 +197,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(1.53341, 1e-3) + +def test__potential_2d_from__power_law_ell_config_2(): mp = ag.mp.PowerLaw( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -180,6 +210,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.96723, 1e-3) + +def test__potential_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLaw( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/total/test_power_law_broken.py b/test_autogalaxy/profiles/mass/total/test_power_law_broken.py index d9859a0e3..8139ea065 100644 --- a/test_autogalaxy/profiles/mass/total/test_power_law_broken.py +++ b/test_autogalaxy/profiles/mass/total/test_power_law_broken.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__power_law_broken_sph__single_and_multi_grid(): mp = ag.mp.PowerLawBrokenSph( centre=(0, 0), einstein_radius=1.0, @@ -29,6 +29,8 @@ def test__deflections_yx_2d_from(): assert deflections[1, 0] == pytest.approx(0.404076, 1e-3) assert deflections[1, 1] == pytest.approx(0.808152, 1e-3) + +def test__deflections_yx_2d_from__power_law_broken_ell_config_1(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(0.096225, 0.055555), @@ -43,6 +45,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.40392, 1e-3) assert deflections[0, 1] == pytest.approx(0.811619, 1e-3) + +def test__deflections_yx_2d_from__power_law_broken_ell_config_2(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(-0.07142, -0.085116), @@ -57,6 +61,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.4005338, 1e-3) assert deflections[0, 1] == pytest.approx(0.8067221, 1e-3) + +def test__deflections_yx_2d_from__power_law_broken_ell_config_3(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(0.109423, 0.019294), @@ -71,6 +77,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.399651, 1e-3) assert deflections[0, 1] == pytest.approx(0.813372, 1e-3) + +def test__deflections_yx_2d_from__power_law_broken_ell_config_4(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(-0.216506, -0.125), @@ -86,7 +94,7 @@ def test__deflections_yx_2d_from(): assert deflections[0, 1] == pytest.approx(0.798795, 1e-3) -def test__convergence_2d_from(): +def test__convergence_2d_from__power_law_broken_sph_single_grid(): mp = ag.mp.PowerLawBrokenSph( centre=(0, 0), einstein_radius=1.0, @@ -99,12 +107,24 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.0355237, 1e-4) + +def test__convergence_2d_from__power_law_broken_sph_two_grids(): + mp = ag.mp.PowerLawBrokenSph( + centre=(0, 0), + einstein_radius=1.0, + inner_slope=1.5, + outer_slope=2.5, + break_radius=0.1, + ) + convergence = mp.convergence_2d_from( grid=ag.Grid2DIrregular([[0.5, 1.0], [0.5, 1.0]]) ) assert convergence == pytest.approx([0.0355237, 0.0355237], 1e-4) + +def test__convergence_2d_from__power_law_broken_ell_config_1(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(0.096225, 0.055555), @@ -118,6 +138,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.05006035, 1e-4) + +def test__convergence_2d_from__power_law_broken_ell_config_2(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(-0.113433, 0.135184), @@ -131,6 +153,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.034768, 1e-4) + +def test__convergence_2d_from__power_law_broken_ell_config_3(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(0.113433, -0.135184), @@ -144,6 +168,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.03622852, 1e-4) + +def test__convergence_2d_from__power_law_broken_ell_config_4(): mp = ag.mp.PowerLawBroken( centre=(0, 0), ell_comps=(-0.173789, -0.030643), @@ -158,7 +184,7 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.026469, 1e-4) -def test__deflections_yx_2d_from__compare_to_power_law(): +def test__deflections_yx_2d_from__compare_to_power_law__slope_2(): mp = ag.mp.PowerLawBrokenSph( centre=(0, 0), einstein_radius=2.0, @@ -181,6 +207,8 @@ def test__deflections_yx_2d_from__compare_to_power_law(): assert broken_yx_ratio == pytest.approx(power_law_yx_ratio, 1.0e-4) + +def test__deflections_yx_2d_from__compare_to_power_law__slope_24(): mp = ag.mp.PowerLawBrokenSph( centre=(0, 0), einstein_radius=2.0, diff --git a/test_autogalaxy/profiles/mass/total/test_power_law_cored.py b/test_autogalaxy/profiles/mass/total/test_power_law_cored.py index 5c4efa896..5ef3ba51f 100644 --- a/test_autogalaxy/profiles/mass/total/test_power_law_cored.py +++ b/test_autogalaxy/profiles/mass/total/test_power_law_cored.py @@ -6,7 +6,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__power_law_core_sph_config_1(): mp = ag.mp.PowerLawCoreSph( centre=(-0.7, 0.5), einstein_radius=1.0, slope=1.8, core_radius=0.2 ) @@ -16,6 +16,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.80677, 1e-3) assert deflections[0, 1] == pytest.approx(-0.30680, 1e-3) + +def test__deflections_yx_2d_from__power_law_core_sph_config_2(): mp = ag.mp.PowerLawCoreSph( centre=(0.2, -0.2), einstein_radius=0.5, slope=2.4, core_radius=0.5 ) @@ -25,6 +27,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-0.00321, 1e-3) assert deflections[0, 1] == pytest.approx(0.09316, 1e-3) + +def test__deflections_yx_2d_from__power_law_core_ell_config_1(): cored_power_law = ag.mp.PowerLawCore( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -40,6 +44,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.9869, 1e-3) assert deflections[0, 1] == pytest.approx(-0.54882, 1e-3) + +def test__deflections_yx_2d_from__power_law_core_ell_config_2(): cored_power_law = ag.mp.PowerLawCore( centre=(0.2, -0.2), ell_comps=(-0.216506, -0.125), @@ -54,6 +60,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(0.01111, 1e-3) assert deflections[0, 1] == pytest.approx(0.11403, 1e-3) + +def test__deflections_yx_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLawCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -70,7 +78,7 @@ def test__deflections_yx_2d_from(): ) -def test__convergence_2d_from(): +def test__convergence_2d_from__power_law_core_sph_convergence_func(): mp = ag.mp.PowerLawCoreSph( centre=(1, 1), einstein_radius=1.0, slope=2.2, core_radius=0.1 ) @@ -79,6 +87,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.39762, 1e-4) + +def test__convergence_2d_from__power_law_core_ell_config_1(): mp = ag.mp.PowerLawCore( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -91,6 +101,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(0.45492, 1e-3) + +def test__convergence_2d_from__power_law_core_ell_config_2(): mp = ag.mp.PowerLawCore( centre=(0.0, 0.0), ell_comps=(0.0, 0.333333), @@ -103,6 +115,8 @@ def test__convergence_2d_from(): assert convergence == pytest.approx(1.3887, 1e-3) + +def test__convergence_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLawCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), @@ -119,7 +133,7 @@ def test__convergence_2d_from(): ) -def test__potential_2d_from(): +def test__potential_2d_from__power_law_core_sph_config_1(): mp = ag.mp.PowerLawCoreSph( centre=(-0.7, 0.5), einstein_radius=1.0, slope=1.8, core_radius=0.2 ) @@ -128,6 +142,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.54913, 1e-3) + +def test__potential_2d_from__power_law_core_sph_config_2(): mp = ag.mp.PowerLawCoreSph( centre=(0.2, -0.2), einstein_radius=0.5, slope=2.4, core_radius=0.5 ) @@ -136,6 +152,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.01820, 1e-3) + +def test__potential_2d_from__power_law_core_ell_config_1(): cored_power_law = ag.mp.PowerLawCore( centre=(0.2, -0.2), ell_comps=(-0.216506, -0.125), @@ -150,6 +168,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.02319, 1e-3) + +def test__potential_2d_from__power_law_core_ell_config_2(): cored_power_law = ag.mp.PowerLawCore( centre=(-0.7, 0.5), ell_comps=(0.152828, -0.088235), @@ -164,6 +184,8 @@ def test__potential_2d_from(): assert potential == pytest.approx(0.71185, 1e-3) + +def test__potential_2d_from__elliptical_vs_spherical(): elliptical = ag.mp.PowerLawCore( centre=(1.1, 1.1), ell_comps=(0.0, 0.0), diff --git a/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py b/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py index 29271d67a..284d43721 100644 --- a/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py +++ b/test_autogalaxy/profiles/mass/total/test_power_law_multipole.py @@ -5,7 +5,7 @@ grid = ag.Grid2DIrregular([[1.0, 1.0], [2.0, 2.0], [3.0, 3.0], [2.0, 4.0]]) -def test__deflections_yx_2d_from(): +def test__deflections_yx_2d_from__config_1(): mp = ag.mp.PowerLawMultipole( m=4, centre=(0.1, 0.2), @@ -19,6 +19,8 @@ def test__deflections_yx_2d_from(): assert deflections[0, 0] == pytest.approx(-0.036120991, 1e-3) assert deflections[0, 1] == pytest.approx(-0.0476260676, 1e-3) + +def test__deflections_yx_2d_from__config_2(): mp = ag.mp.PowerLawMultipole( m=4, centre=(0.2, 0.3), @@ -33,7 +35,7 @@ def test__deflections_yx_2d_from(): assert deflections[0, 1] == pytest.approx(-0.1298677210, 1e-3) -def test__convergence_2d_from(): +def test__convergence_2d_from__config_1(): mp = ag.mp.PowerLawMultipole( m=4, centre=(0.1, 0.2), @@ -46,6 +48,8 @@ def test__convergence_2d_from(): assert convergence[0] == pytest.approx(0.25958037, 1e-3) + +def test__convergence_2d_from__config_2(): mp = ag.mp.PowerLawMultipole( m=4, centre=(0.2, 0.3), diff --git a/test_autogalaxy/profiles/plot/test_basis_plotters.py b/test_autogalaxy/profiles/plot/test_basis_plotters.py index 47fa7bf29..b8b4beee2 100644 --- a/test_autogalaxy/profiles/plot/test_basis_plotters.py +++ b/test_autogalaxy/profiles/plot/test_basis_plotters.py @@ -1,33 +1,33 @@ -from os import path - -import autogalaxy as ag -import autogalaxy.plot as aplt -import pytest - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_profile_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" - ) - - -def test__subplot_image( - lp_0, - lp_1, - grid_2d_7x7, - plot_path, - plot_patch, -): - basis = ag.lp_basis.Basis(profile_list=[lp_0, lp_1]) - - plotter = aplt.BasisPlotter( - basis=basis, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - plotter.subplot_image() - - assert path.join(plot_path, "subplot_basis_image.png") in plot_patch.paths +from os import path + +import autogalaxy as ag +import autogalaxy.plot as aplt +import pytest + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_profile_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" + ) + + +def test__subplot_image( + lp_0, + lp_1, + grid_2d_7x7, + plot_path, + plot_patch, +): + basis = ag.lp_basis.Basis(profile_list=[lp_0, lp_1]) + + aplt.subplot_basis_image( + basis=basis, + grid=grid_2d_7x7, + output_path=plot_path, + output_format="png", + ) + + assert path.join(plot_path, "subplot_basis_image.png") in plot_patch.paths diff --git a/test_autogalaxy/profiles/plot/test_light_profile_plotters.py b/test_autogalaxy/profiles/plot/test_light_profile_plotters.py index 851860afa..84a065ba2 100644 --- a/test_autogalaxy/profiles/plot/test_light_profile_plotters.py +++ b/test_autogalaxy/profiles/plot/test_light_profile_plotters.py @@ -1,33 +1,30 @@ -from os import path - -from autoconf import conf -import autogalaxy as ag -import autogalaxy.plot as aplt -import pytest - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_profile_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" - ) - - -def test__figures_2d__all_are_output( - lp_0, - grid_2d_7x7, - grid_2d_irregular_7x7_list, - plot_path, - plot_patch, -): - light_profile_plotter = aplt.LightProfilePlotter( - light_profile=lp_0, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - light_profile_plotter.figures_2d(image=True) - - assert path.join(plot_path, "image_2d.png") in plot_patch.paths +from os import path + +import autogalaxy.plot as aplt +import pytest + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_profile_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" + ) + + +def test__figures_2d__all_are_output( + lp_0, + grid_2d_7x7, + plot_path, + plot_patch, +): + aplt.plot_array( + array=lp_0.image_2d_from(grid=grid_2d_7x7), + title="Image", + output_path=plot_path, + output_filename="image_2d", + output_format="png", + ) + + assert path.join(plot_path, "image_2d.png") in plot_patch.paths diff --git a/test_autogalaxy/profiles/plot/test_mass_profile_plotters.py b/test_autogalaxy/profiles/plot/test_mass_profile_plotters.py index 304c050c8..6475b51e7 100644 --- a/test_autogalaxy/profiles/plot/test_mass_profile_plotters.py +++ b/test_autogalaxy/profiles/plot/test_mass_profile_plotters.py @@ -1,41 +1,65 @@ -from os import path - -import autogalaxy as ag -import autogalaxy.plot as aplt -import pytest - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_mp_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" - ) - - -def test__figures_2d__all_are_output( - mp_0, - grid_2d_7x7, - grid_2d_irregular_7x7_list, - plot_path, - plot_patch, -): - mass_profile_plotter = aplt.MassProfilePlotter( - mass_profile=mp_0, - grid=grid_2d_7x7, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - mass_profile_plotter.figures_2d( - convergence=True, - potential=True, - deflections_y=True, - deflections_x=True, - magnification=True, - ) - - assert path.join(plot_path, "convergence_2d.png") in plot_patch.paths - assert path.join(plot_path, "potential_2d.png") in plot_patch.paths - assert path.join(plot_path, "deflections_y_2d.png") in plot_patch.paths - assert path.join(plot_path, "deflections_x_2d.png") in plot_patch.paths - assert path.join(plot_path, "magnification_2d.png") in plot_patch.paths +from os import path + +import autoarray as aa +import autogalaxy.plot as aplt +import pytest +from autogalaxy.operate.lens_calc import LensCalc + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_mp_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), "files", "plots", "profiles" + ) + + +def test__figures_2d__all_are_output( + mp_0, + grid_2d_7x7, + plot_path, + plot_patch, +): + aplt.plot_array( + array=mp_0.convergence_2d_from(grid=grid_2d_7x7), + title="Convergence", + output_path=plot_path, + output_filename="convergence_2d", + output_format="png", + ) + aplt.plot_array( + array=mp_0.potential_2d_from(grid=grid_2d_7x7), + title="Potential", + output_path=plot_path, + output_filename="potential_2d", + output_format="png", + ) + deflections = mp_0.deflections_yx_2d_from(grid=grid_2d_7x7) + aplt.plot_array( + array=aa.Array2D(values=deflections.slim[:, 0], mask=grid_2d_7x7.mask), + title="Deflections Y", + output_path=plot_path, + output_filename="deflections_y_2d", + output_format="png", + ) + aplt.plot_array( + array=aa.Array2D(values=deflections.slim[:, 1], mask=grid_2d_7x7.mask), + title="Deflections X", + output_path=plot_path, + output_filename="deflections_x_2d", + output_format="png", + ) + aplt.plot_array( + array=LensCalc.from_mass_obj(mp_0).magnification_2d_from(grid=grid_2d_7x7), + title="Magnification", + output_path=plot_path, + output_filename="magnification_2d", + output_format="png", + ) + + assert path.join(plot_path, "convergence_2d.png") in plot_patch.paths + assert path.join(plot_path, "potential_2d.png") in plot_patch.paths + assert path.join(plot_path, "deflections_y_2d.png") in plot_patch.paths + assert path.join(plot_path, "deflections_x_2d.png") in plot_patch.paths + assert path.join(plot_path, "magnification_2d.png") in plot_patch.paths diff --git a/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py b/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py index 94265e502..bd56aa32c 100644 --- a/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py +++ b/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py @@ -1,67 +1,31 @@ -from os import path - -import pytest - -import autogalaxy.plot as aplt - -directory = path.dirname(path.realpath(__file__)) - - -@pytest.fixture(name="plot_path") -def make_galaxy_fit_plotter_setup(): - return path.join( - "{}".format(path.dirname(path.realpath(__file__))), - "files", - "plots", - "galaxy_fitting", - ) - - -def test__fit_individuals__source_and_galaxy__dependent_on_input( - fit_quantity_7x7_array_2d, - fit_quantity_7x7_vector_yx_2d, - plot_path, - plot_patch, -): - fit_quantity_plotter = aplt.FitQuantityPlotter( - fit=fit_quantity_7x7_array_2d, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_quantity_plotter.figures_2d( - image=True, - ) - - assert path.join(plot_path, "data.png") in plot_patch.paths - assert path.join(plot_path, "noise_map.png") not in plot_patch.paths - - fit_quantity_plotter = aplt.FitQuantityPlotter( - fit=fit_quantity_7x7_vector_yx_2d, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(plot_path, format="png")), - ) - - fit_quantity_plotter.figures_2d( - image=True, - noise_map=False, - ) - - assert path.join(plot_path, "data_y.png") in plot_patch.paths - assert path.join(plot_path, "noise_map_y.png") not in plot_patch.paths - - assert path.join(plot_path, "data_x.png") in plot_patch.paths - assert path.join(plot_path, "noise_map_x.png") not in plot_patch.paths - - -def test__fit_sub_plot__all_types_of_fit( - fit_quantity_7x7_array_2d, - fit_quantity_7x7_vector_yx_2d, - plot_patch, - plot_path, -): - fit_quantity_plotter = aplt.FitQuantityPlotter( - fit=fit_quantity_7x7_array_2d, - mat_plot_2d=aplt.MatPlot2D(output=aplt.Output(path=plot_path, format="png")), - ) - - fit_quantity_plotter.subplot_fit() - assert path.join(plot_path, "subplot_fit.png") in plot_patch.paths +from os import path + +import pytest + +import autogalaxy.plot as aplt + +directory = path.dirname(path.realpath(__file__)) + + +@pytest.fixture(name="plot_path") +def make_galaxy_fit_plotter_setup(): + return path.join( + "{}".format(path.dirname(path.realpath(__file__))), + "files", + "plots", + "galaxy_fitting", + ) + + +def test__fit_sub_plot__all_types_of_fit( + fit_quantity_7x7_array_2d, + fit_quantity_7x7_vector_yx_2d, + plot_patch, + plot_path, +): + aplt.subplot_fit_quantity( + fit=fit_quantity_7x7_array_2d, + output_path=plot_path, + output_format="png", + ) + assert path.join(plot_path, "subplot_fit.png") in plot_patch.paths