diff --git a/AGENTS.md b/AGENTS.md index 9e8f03e4..12aa7e91 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ NUMBA_CACHE_DIR=/tmp/numba_cache MPLCONFIGDIR=/tmp/matplotlib python -m pytest t - **Profiles**: `LightProfile` (`lp.*`), `MassProfile` (`mp.*`), `LightProfileLinear` (`lp_linear.*`) - **Galaxy** (`galaxy/galaxy.py`): holds light/mass profiles, pixelizations -- **Fit classes**: `FitImaging`, `FitInterferometer`, `FitQuantity`, `FitEllipse` +- **Fit classes**: `FitImaging`, `FitInterferometer`, `FitEllipse` - **Analysis classes**: `AnalysisImaging`, `AnalysisInterferometer` — implement `log_likelihood_function` - **Decorator system** (from autoarray): `@to_array`, `@to_grid`, `@to_vector_yx`, `@transform` - **Operate mixins**: `OperateImage`, `OperateDeflections`, `LensCalc` diff --git a/CLAUDE.md b/CLAUDE.md index 615f00df..6b524bdf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,7 +83,6 @@ Each dataset type has a `Fit*` class that orchestrates the full fitting pipeline - `FitImaging` (`imaging/fit_imaging.py`) – CCD imaging - `FitInterferometer` (`interferometer/fit_interferometer.py`) – ALMA/interferometry -- `FitQuantity` (`quantity/fit_quantity.py`) – arbitrary quantity datasets - `FitEllipse` (`ellipse/fit_ellipse.py`) – isophote/ellipse fitting All inherit from `AbstractFitInversion` (`abstract_fit.py`), which handles the linear algebra inversion step when `LightProfileLinear` or pixelization-based profiles are present. @@ -94,7 +93,6 @@ Each dataset type has an `Analysis*` class that implements `log_likelihood_funct - `AnalysisImaging` (`imaging/model/analysis.py`) - `AnalysisInterferometer` (`interferometer/model/analysis.py`) -- `AnalysisQuantity` (`quantity/model/analysis.py`) - `AnalysisEllipse` (`ellipse/model/analysis.py`) These inherit from `AnalysisDataset` → `Analysis` (in `analysis/analysis/`), which inherits `af.Analysis`. The `log_likelihood_function` builds a `Fit*` object from the `af.ModelInstance` and returns its `figure_of_merit`. diff --git a/autogalaxy/__init__.py b/autogalaxy/__init__.py index 3fc3fb09..9602c8c3 100644 --- a/autogalaxy/__init__.py +++ b/autogalaxy/__init__.py @@ -79,9 +79,6 @@ from .interferometer.fit_interferometer import FitInterferometer from .interferometer.model.analysis import AnalysisInterferometer -from .quantity.fit_quantity import FitQuantity -from .quantity.model.analysis import AnalysisQuantity -from .quantity.dataset_quantity import DatasetQuantity from .galaxy.galaxy import Galaxy from .galaxy.galaxies import Galaxies from .galaxy.galaxy_table import GalaxyTable diff --git a/autogalaxy/analysis/analysis/analysis.py b/autogalaxy/analysis/analysis/analysis.py index 49e10952..492c19f3 100644 --- a/autogalaxy/analysis/analysis/analysis.py +++ b/autogalaxy/analysis/analysis/analysis.py @@ -2,7 +2,7 @@ Abstract `Analysis` class providing shared functionality across all **PyAutoGalaxy** model-fitting analyses. This module provides `Analysis`, the root analysis class from which all dataset-specific analysis classes -inherit (`AnalysisImaging`, `AnalysisInterferometer`, `AnalysisEllipse`, `AnalysisQuantity`). +inherit (`AnalysisImaging`, `AnalysisInterferometer`, `AnalysisEllipse`). `Analysis` itself inherits from `af.Analysis` (from **PyAutoFit**) and extends it with: diff --git a/autogalaxy/config/visualize/plots.yaml b/autogalaxy/config/visualize/plots.yaml index b6aa4cf2..39275c46 100644 --- a/autogalaxy/config/visualize/plots.yaml +++ b/autogalaxy/config/visualize/plots.yaml @@ -51,6 +51,4 @@ fit_ellipse: # Settings for plots of ellipse fitti subplot_fit_ellipse : true # Plot subplot of all fit quantities for ellipse fits (e.g. the model data, residual-map, etc.)? data : true # Plot the data of the ellipse fit? data_no_ellipse: true # Plot the data without the black data ellipses, which obscure noisy data? - ellipse_residuals: true # Plot the residuals of the ellipse fit? - -fit_quantity: {} # Settings for plots of fit quantities (e.g. FitQuantity). \ No newline at end of file + ellipse_residuals: true # Plot the residuals of the ellipse fit? \ No newline at end of file diff --git a/autogalaxy/fixtures.py b/autogalaxy/fixtures.py index 510758a7..db4f0506 100644 --- a/autogalaxy/fixtures.py +++ b/autogalaxy/fixtures.py @@ -149,43 +149,6 @@ def make_dataset_interp_7x7(): return ag.DatasetInterp(dataset=imaging_7x7) -# QUANTITY DATASET AND FIT # - - -def make_dataset_quantity_7x7_array_2d(): - return ag.DatasetQuantity( - data=aa.Array2D.ones(shape_native=(7, 7), pixel_scales=1.0), - noise_map=aa.Array2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - ) - - -def make_dataset_quantity_7x7_vector_yx_2d(): - return ag.DatasetQuantity( - data=aa.VectorYX2D.ones(shape_native=(7, 7), pixel_scales=1.0), - noise_map=aa.VectorYX2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - ) - - -def make_fit_quantity_7x7_array_2d(): - return ag.FitQuantity( - dataset=make_dataset_quantity_7x7_array_2d(), - light_mass_obj=make_galaxies_7x7(), - func_str="convergence_2d_from", - ) - - -def make_fit_quantity_7x7_vector_yx_2d(): - return ag.FitQuantity( - dataset=make_dataset_quantity_7x7_vector_yx_2d(), - light_mass_obj=make_galaxies_7x7(), - func_str="deflections_yx_2d_from", - ) - - # galaxies # diff --git a/autogalaxy/plot/__init__.py b/autogalaxy/plot/__init__.py index ed7886b3..573da8be 100644 --- a/autogalaxy/plot/__init__.py +++ b/autogalaxy/plot/__init__.py @@ -46,10 +46,6 @@ 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/quantity/__init__.py b/autogalaxy/quantity/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autogalaxy/quantity/dataset_quantity.py b/autogalaxy/quantity/dataset_quantity.py deleted file mode 100644 index a8dca31d..00000000 --- a/autogalaxy/quantity/dataset_quantity.py +++ /dev/null @@ -1,236 +0,0 @@ -""" -`DatasetQuantity` — a dataset wrapper for derived lensing quantities. - -This module provides `DatasetQuantity`, which wraps an arbitrary array-valued quantity (e.g. convergence, -deflection angles, surface brightness) together with a noise-map, so that the quantity can be treated as -a dataset and fitted via `FitQuantity` and `AnalysisQuantity`. - -The noise-map enables the computation of chi-squared values and log-likelihoods, turning a pure -comparison of model quantities into a statistically principled goodness-of-fit metric. -""" -import logging -import numpy as np -from pathlib import Path -from typing import Optional, Union - -import autoarray as aa - -from autoarray.dataset.abstract.dataset import AbstractDataset -from autoarray.dataset.grids import GridsDataset - -logger = logging.getLogger(__name__) - - -class DatasetQuantity(AbstractDataset): - def __init__( - self, - data: Union[aa.Array2D, aa.VectorYX2D], - noise_map: Union[aa.Array2D, aa.VectorYX2D], - over_sample_size_lp: Union[int, aa.Array2D] = 4, - over_sample_size_pixelization: Union[int, aa.Array2D] = 4, - ): - """ - A quantity dataset, which represents a derived quantity of a light profile, mass profile, galaxy or galaxies - that can be fitted with a model via a non-linear. - - For example, the `DatasetQuantity` could represent the `convergence` of a mass profile, and this dataset could - then be fit with a galaxies via the `AnalysisQuantity` class. The benefit of doing this is that the components - of different models can be fitted to one another and matched for example, the power-law model whose convergence - best matches the convergence of a dark matter profile could be inferred). - - The following quantities of the data are available and used for the following tasks: - - - `data`: The quantity data, which shows the signal that is analysed and fitted with a model data of a model - object. - - - `noise_map`: The RMS standard deviation error in every pixel, which is used to compute the chi-squared value - and likelihood of a fit. - - Datasets also contains following properties: - - - `grid`: A grids of (y,x) coordinates which align with the image pixels, whereby each coordinate corresponds to - the centre of an image pixel. This may be used in fits to calculate the model image of the imaging data. - - - `grids.pixelization`: A grid of (y,x) coordinates which align with the pixels of a pixelization. This grid - is specifically used for pixelizations computed via the `invserion` module, which often use different - oversampling and sub-size values to the grid above. - - The `over_sampling` and `over_sampling_pixelization` define how over sampling is performed for these grids. - - This is used in the project PyAutoGalaxy to load imaging data of a galaxy and fit it with galaxy light profiles. - It is used in PyAutoLens to load imaging data of a strong lens and fit it with a lens model. - - Parameters - ---------- - data - The data of the quantity (e.g. 2D convergence, 2D potential, 2D deflections) that is fitted. - noise_map - The 2D noise map of the quantity's data. - noise_map - An array describing the RMS standard deviation error in each pixel used for computing quantities like the - chi-squared in a fit, which is often chosen in an arbitrary way for a quantity dataset given the quantities - are not observed using real astronomical instruments. - over_sample_size_lp - The over sampling scheme size, which divides the grid into a sub grid of smaller pixels when computing - values (e.g. images) from the grid to approximate the 2D line integral of the amount of light that falls - into each pixel. - over_sample_size_pixelization - How over sampling is performed for the grid which is associated with a pixelization, which is therefore - passed into the calculations performed in the `inversion` module. - """ - if data.shape != noise_map.shape: - if data.shape[0:-1] == noise_map.shape[0:]: - noise_map = aa.VectorYX2D.no_mask( - values=np.stack((noise_map, noise_map), axis=-1), - pixel_scales=data.pixel_scales, - shape_native=data.shape_native, - origin=data.origin, - ) - - self.unmasked = None - - super().__init__( - data=data, - noise_map=noise_map, - over_sample_size_lp=over_sample_size_lp, - over_sample_size_pixelization=over_sample_size_pixelization, - ) - - self.grids = GridsDataset( - mask=self.data.mask, - over_sample_size_lp=self.over_sample_size_lp, - over_sample_size_pixelization=self.over_sample_size_pixelization, - ) - - @classmethod - def via_signal_to_noise_map( - cls, - data: Union[aa.Array2D, aa.VectorYX2D], - signal_to_noise_map: Union[aa.Array2D], - over_sample_size_lp: Union[int, aa.Array2D] = 4, - over_sample_size_pixelization: Union[int, aa.Array2D] = 4, - ): - """ - Represents a derived quantity of a light profile, mass profile, galaxy or galaxies as a dataset that can be - fitted with a model via a non-linear (see `DatasetQuantity.__init__`). - - This classmethod takes as input a signal-to-noise map, as opposed to the noise map used in the `__init__` - constructor. The noise-map is then derived from this signal-to-noise map, such that this is the signal to - noise of the `DatasetQuantity` that is returned. - - Parameters - ---------- - data - The data of the quantity (e.g. 2D convergence, 2D potential, 2D deflections) that is fitted. - signal_to_noise_map - The 2D signal to noise map of the quantity's data. - over_sample_size_lp - The over sampling scheme size, which divides the grid into a sub grid of smaller pixels when computing - values (e.g. images) from the grid to approximate the 2D line integral of the amount of light that falls - into each pixel. - over_sample_size_pixelization - How over sampling is performed for the grid which is associated with a pixelization, which is therefore - passed into the calculations performed in the `inversion` module. - """ - try: - noise_map = data / signal_to_noise_map - except ValueError: - noise_map = aa.VectorYX2D.zeros( - shape_native=data.shape_native, pixel_scales=data.pixel_scales - ) - noise_map = noise_map.apply_mask(mask=data.mask) - - signal_to_noise_map[signal_to_noise_map < 1e-8] = 1e-8 - - noise_map[:, 0] = np.abs(data.slim[:, 0]) / signal_to_noise_map - noise_map[:, 1] = np.abs(data.slim[:, 1]) / signal_to_noise_map - - return DatasetQuantity( - data=data, - noise_map=noise_map, - over_sample_size_lp=over_sample_size_lp, - over_sample_size_pixelization=over_sample_size_pixelization, - ) - - @property - def y(self) -> "DatasetQuantity": - """ - If the `DatasetQuantity` contains a `VectorYX2D` as its data, this property returns a new `DatasetQuantity` - with just the y-values of the vectors as the data, alongside the noise-map. - - This is primarily used for visualizing a fit to the `DatasetQuantity` containing vectors, as it allows one to - reuse tools which visualize `Array2D` objects. - """ - if isinstance(self.data, aa.VectorYX2D): - return DatasetQuantity( - data=self.data.y, - noise_map=self.noise_map.y, - over_sample_size_lp=self.over_sample_size_lp, - over_sample_size_pixelization=self.over_sample_size_pixelization, - ) - - @property - def x(self) -> "DatasetQuantity": - """ - If the `DatasetQuantity` contains a `VectorYX2D` as its data, this property returns a new `DatasetQuantity` - with just the x-values of the vectors as the data, alongside the noise-map. - - This is primarily used for visualizing a fit to the `DatasetQuantity` containing vectors, as it allows one to - reuse tools which visualize `Array2D` objects. - """ - if isinstance(self.data, aa.VectorYX2D): - return DatasetQuantity( - data=self.data.x, - noise_map=self.noise_map.x, - over_sample_size_lp=self.over_sample_size_lp, - over_sample_size_pixelization=self.over_sample_size_pixelization, - ) - - def apply_mask(self, mask: aa.Mask2D) -> "DatasetQuantity": - """ - Apply a mask to the quantity dataset, whereby the mask is applied to the data and noise-map one-by-one. - - The original unmasked qunatity dataset is stored as the `self.unmasked` attribute. This is used to ensure that - if the `apply_mask` function is called multiple times, every mask is always applied to the original unmasked - imaging dataset. - - Parameters - ---------- - mask - The 2D mask that is applied to the image. - """ - if self.data.mask.is_all_false: - unmasked_dataset = self - else: - unmasked_dataset = self.unmasked - - data = self.data.apply_mask(mask=mask) - noise_map = self.noise_map.apply_mask(mask=mask) - over_sample_size_lp = self.over_sample_size_lp.apply_mask(mask=mask) - over_sample_size_pixelization = self.over_sample_size_pixelization.apply_mask( - mask=mask - ) - - dataset = DatasetQuantity( - data=data, - noise_map=noise_map, - over_sample_size_lp=over_sample_size_lp, - over_sample_size_pixelization=over_sample_size_pixelization, - ) - - dataset.unmasked = unmasked_dataset - - logger.info( - f"IMAGING - Data masked, contains a total of {mask.pixels_in_mask} image-pixels" - ) - - return dataset - - @property - def shape_native(self): - return self.data.shape_native - - @property - def pixel_scales(self): - return self.data.pixel_scales - diff --git a/autogalaxy/quantity/fit_quantity.py b/autogalaxy/quantity/fit_quantity.py deleted file mode 100644 index 6cf407b6..00000000 --- a/autogalaxy/quantity/fit_quantity.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Fit a `DatasetQuantity` object with model quantities computed from light or mass profiles. - -`FitQuantity` provides a general-purpose fitting framework for derived lensing quantities (e.g. convergence, -deflection angles, surface brightness). Given a `DatasetQuantity` (which contains a target quantity array and -a noise-map), `FitQuantity` evaluates the same quantity from a model object and computes the likelihood of -the model matching the target. - -This is useful for, for example: - -- Matching the convergence of a power-law model to the convergence of an NFW profile. -- Comparing deflection angles from two different mass distributions. -- Fitting a pixelized reconstruction to a separately computed model quantity. - -The `AnalysisQuantity` class uses `FitQuantity` internally to enable non-linear fitting of these quantities. -""" -from typing import List, Optional, Union - -import autoarray as aa - -from autogalaxy.quantity.dataset_quantity import DatasetQuantity -from autogalaxy.profiles.light.abstract import LightProfile -from autogalaxy.profiles.mass.abstract.abstract import MassProfile -from autogalaxy.galaxy.galaxy import Galaxy -from autogalaxy.galaxy.galaxies import Galaxies - - -class FitQuantity(aa.FitImaging): - def __init__( - self, - dataset: DatasetQuantity, - light_mass_obj: Union[LightProfile, MassProfile, Galaxy, Galaxies], - func_str: str, - model_data_manual: Optional[Union[aa.Array2D, aa.VectorYX2D]] = None, - ): - """ - Fits a `DatasetQuantity` object with model data. - - This is used to fit a quantity (e.g. a convergence, deflection angles), from an object like - a `LightProfile`, `MassProfile`, `Galaxy`, to the same quantity derived from another of that object. - - For example, we may have the 2D convergence of a power-law mass profile and wish to determine how closely the - 2D convergence of an nfw mass profile's matches it. The `FitQuantity` can fit the two, where a noise-map - is associated with the quantity's dataset such that figure of merits like a chi-squared and log likelihood - can be computed. - - This is ultimately used in the `AnalysisQuantity` class to perform model-fitting of quantities of different - mass profiles, light profiles, galaxies, etc. - - Parameters - ---------- - dataset - The quantity that is to be fitted, which has a noise-map associated it with for computing goodness-of-fit - metrics. - light_mass_obj - An object containing functions which computes a light and / or mass quantity (e.g. galaxies) - whose model quantities are used to fit the quantity data. - func_str - A string giving the name of the method of the input galaxy used to compute the quantity that fits - the dataset. - model_data_manual - Manually pass the model-data, omitting its calculation via the function defined by the `func_str`. - """ - - self.light_mass_obj = light_mass_obj - self.func_str = func_str - self.model_data_manual = model_data_manual - - super().__init__(dataset=dataset, use_mask_in_fit=False) - - @property - def model_data(self): - if self.model_data_manual is None: - func = getattr(self.light_mass_obj, self.func_str) - return func(grid=self.grids.lp) - - return self.model_data_manual - - @property - def y(self) -> "FitQuantity": - """ - If the `FitQuantity` contains a `VectorYX2D` as its data, this property returns a new `FitQuantity` - with just the y-values of the vectors as the data, alongside the noise-map. The y values of the model-data are - also extracted and used in this fit. - - This is primarily used for visualizing a fit to the `FitQuantity` containing vectors, as it allows one to - reuse tools which visualize `Array2D` objects. - """ - if isinstance(self.data, aa.VectorYX2D): - return FitQuantity( - dataset=self.dataset.y, - light_mass_obj=self.light_mass_obj, - func_str=self.func_str, - model_data_manual=self.model_data.y, - ) - - @property - def x(self) -> "FitQuantity": - """ - If the `FitQuantity` contains a `VectorYX2D` as its data, this property returns a new `FitQuantity` - with just the x-values of the vectors as the data, alongside the noise-map. The x values of the model-data are - also extracted and used in this fit. - - This is primarily used for visualizing a fit to the `FitQuantity` containing vectors, as it allows one to - reuse tools which visualize `Array2D` objects. - """ - if isinstance(self.data, aa.VectorYX2D): - return FitQuantity( - dataset=self.dataset.x, - light_mass_obj=self.light_mass_obj, - func_str=self.func_str, - model_data_manual=self.model_data.x, - ) - - @property - def mask(self): - return self.dataset.mask - - @property - def inversion(self): - return None - - @property - def galaxies(self): - return self.light_mass_obj diff --git a/autogalaxy/quantity/model/__init__.py b/autogalaxy/quantity/model/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autogalaxy/quantity/model/analysis.py b/autogalaxy/quantity/model/analysis.py deleted file mode 100644 index 2001168f..00000000 --- a/autogalaxy/quantity/model/analysis.py +++ /dev/null @@ -1,191 +0,0 @@ -import numpy as np - -from autoconf.dictable import to_dict - -import autofit as af - -from autogalaxy.analysis.analysis.analysis import Analysis -from autogalaxy.cosmology.model import LensingCosmology -from autogalaxy.quantity.dataset_quantity import DatasetQuantity -from autogalaxy.quantity.model.result import ResultQuantity -from autogalaxy.quantity.model.visualizer import VisualizerQuantity -from autogalaxy.quantity.fit_quantity import FitQuantity - - -class AnalysisQuantity(Analysis): - Result = ResultQuantity - Visualizer = VisualizerQuantity - - def __init__( - self, - dataset: DatasetQuantity, - func_str: str, - cosmology: LensingCosmology = None, - title_prefix: str = None, - use_jax: bool = True, - **kwargs, - ): - """ - Fits a galaxy model to a quantity dataset via a non-linear search. - - The `Analysis` class defines the `log_likelihood_function` which fits the model to the dataset and returns the - log likelihood value defining how well the model fitted the data. - - It handles many other tasks, such as visualization, outputting results to hard-disk and storing results in - a format that can be loaded after the model-fit is complete. - - This class is used for model-fits which fit derived quantity of galaxies, for example their - convergence, potential or deflection angles, to another model for that quantity. For example, one could find - the `PowerLaw` mass profile model that best fits the deflection angles of an `NFW` mass profile. - - The `func_str` input defines what quantity is fitted, it corresponds to the function of the model galaxy - objects that is called to create the model quantity. For example, if `func_str="convergence_2d_from"`, the - convergence is computed from each model galaxy. - - This class stores the settings used to perform the model-fit for certain components of the model (e.g. the - Cosmology used for the analysis). - - Parameters - ---------- - dataset - The `DatasetQuantity` dataset that the model is fitted too. - func_str - A string giving the name of the method of the input galaxy used to compute the quantity that fits - the dataset. - cosmology - The Cosmology assumed for this analysis. - 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. - """ - super().__init__(cosmology=cosmology, use_jax=use_jax, **kwargs) - - self.dataset = dataset - self.func_str = func_str - self.title_prefix = title_prefix - - if use_jax: - self._register_fit_quantity_pytrees() - - @staticmethod - def _register_fit_quantity_pytrees() -> None: - """Register every type reachable from a ``FitQuantity`` return value - so ``jax.jit`` can flatten its output. - - ``dataset``, ``func_str``, ``use_mask_in_fit`` are per-analysis- - constant — ride as aux so JAX does not recurse into them. - ``light_mass_obj`` (a ``Galaxies``) and ``model_data_manual`` - carry the traced model arrays and ride as pytree children. - """ - from autoarray.abstract_ndarray import register_instance_pytree - from autoarray.dataset.dataset_model import DatasetModel - from autogalaxy.analysis.jax_pytrees import register_galaxies_pytree - - register_instance_pytree( - FitQuantity, - no_flatten=("dataset", "func_str", "use_mask_in_fit"), - ) - register_instance_pytree(DatasetModel) - register_galaxies_pytree() - - def log_likelihood_function(self, instance: af.ModelInstance) -> float: - """ - Given an instance of the model, where the model parameters are set via a non-linear search, fit the model - instance to the quantity's dataset. - - This function returns a log likelihood which is used by the non-linear search to guide the model-fit. - - For this analysis class, this function performs the following steps: - - 1) Use the input quantity of the analysis to determine the function that is passed to `FitQuantity`, which - generates the quantity from the model which is compared to data. - - 2) Use this function to create a `FitQuantity` object, which performs steps such as creating the `model_data` - of the quantity and computing residuals, a chi-squared statistic and the log likelihood. - - Certain models will fail to fit the dataset and raise an exception. For example if extreme values of the model - create numerical infinities. In such circumstances the model is discarded and its likelihood value is passed to - the non-linear search in a way that it ignores it (for example, using a value of -1.0e99). - - Parameters - ---------- - instance - An instance of the model that is being fitted to the data by this analysis (whose parameters have been set - via a non-linear search). - - Returns - ------- - float - The log likelihood indicating how well this model instance fitted the imaging data. - """ - return self.fit_quantity_for_instance(instance=instance).figure_of_merit - - def fit_quantity_for_instance(self, instance: af.ModelInstance) -> FitQuantity: - """ - Given a model instance create a `FitImaging` object. - - This function is used in the `log_likelihood_function` to fit the model to the imaging data and compute the - log likelihood. - - Parameters - ---------- - instance - An instance of the model that is being fitted to the data by this analysis (whose parameters have been set - via a non-linear search). - - Returns - ------- - FitQuantity - The fit of the galaxies to the imaging dataset, which includes the log likelihood. - """ - - galaxies = self.galaxies_via_instance_from(instance=instance) - - return FitQuantity( - dataset=self.dataset, light_mass_obj=galaxies, func_str=self.func_str - ) - - def fit_from(self, instance: af.ModelInstance) -> FitQuantity: - """ - Standard-name alias for :meth:`fit_quantity_for_instance`. - - Exposing ``fit_from`` lets the autofit base ``Analysis.fit_for_visualization`` - helper dispatch through the same JIT-cached wrapper as imaging / - interferometer (it calls ``self.fit_from`` unconditionally). Without - this method, ``use_jax_for_visualization=True`` on ``AnalysisQuantity`` - would be a silent no-op — see the Phase 0c shipped notes in - ``PyAutoPrompt/complete.md``. - """ - return self.fit_quantity_for_instance(instance=instance) - - def save_attributes(self, paths: af.DirectoryPaths): - """ - Before the non-linear search begins, this routine saves attributes of the `Analysis` object to the `files` - folder such that they can be loaded after the analysis using PyAutoFit's database and aggregator tools. - - For this analysis, it uses the `AnalysisDataset` object's method to output the following: - - - The settings associated with the dataset. - - The Cosmology. - - The following .fits files are also output via the plotter interface: - - - The mask applied to the dataset, in the `PrimaryHDU` of `dataset.fits`. - - The imaging dataset as `dataset.fits` (data / noise-map / psf / over sampler / etc.). - - It is common for these attributes to be loaded by many of the template aggregator functions given in the - `aggregator` modules. For example, when using the database tools to perform a fit, the default behaviour is for - the dataset, settings and other attributes necessary to perform the fit to be loaded via the pickle files - output by this function. - - Parameters - ---------- - paths - The paths object which manages all paths, e.g. where the non-linear search outputs are stored, - visualization, and the pickled objects used by the aggregator output by this function. - """ - - paths.save_json( - name="cosmology", - object_dict=to_dict(self.cosmology), - ) diff --git a/autogalaxy/quantity/model/plotter.py b/autogalaxy/quantity/model/plotter.py deleted file mode 100644 index 17b4594b..00000000 --- a/autogalaxy/quantity/model/plotter.py +++ /dev/null @@ -1,72 +0,0 @@ -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 import Plotter, plot_setting - - -class PlotterQuantity(Plotter): - def dataset_quantity(self, dataset: DatasetQuantity): - """ - Output visualization of a ``DatasetQuantity`` dataset. - - Writes a FITS file containing the mask, data, and noise-map arrays - regardless of config toggles (always-on output for quantity datasets). - - Parameters - ---------- - dataset - The quantity dataset to visualize. - """ - 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, - ): - """ - Output visualization of a ``FitQuantity`` object. - - Controlled by the ``[fit_quantity]`` section of - ``config/visualize/plots.yaml``. Outputs a fit subplot. - - Parameters - ---------- - fit - The quantity fit to visualize. - fit_quantity_plots_module - Optional override for the plots module; defaults to - ``autogalaxy.quantity.plot.fit_quantity_plots``. - """ - 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, - title_prefix=self.title_prefix, - ) diff --git a/autogalaxy/quantity/model/result.py b/autogalaxy/quantity/model/result.py deleted file mode 100644 index d73f9fa2..00000000 --- a/autogalaxy/quantity/model/result.py +++ /dev/null @@ -1,13 +0,0 @@ -from autogalaxy.analysis.result import Result -from autogalaxy.quantity.fit_quantity import FitQuantity - - -class ResultQuantity(Result): - @property - def max_log_likelihood_fit(self) -> FitQuantity: - """ - An instance of a `FitQuantity` corresponding to the maximum log likelihood model inferred by the non-linear - search. - """ - - return self.analysis.fit_quantity_for_instance(instance=self.instance) diff --git a/autogalaxy/quantity/model/visualizer.py b/autogalaxy/quantity/model/visualizer.py deleted file mode 100644 index 6363cdab..00000000 --- a/autogalaxy/quantity/model/visualizer.py +++ /dev/null @@ -1,77 +0,0 @@ -import os - -import autofit as af -from autoconf.test_mode import skip_visualization - -from autogalaxy.quantity.model.plotter import PlotterQuantity - - -class VisualizerQuantity(af.Visualizer): - @staticmethod - def visualize_before_fit( - analysis, - paths: af.AbstractPaths, - model: af.AbstractPriorModel, - ): - """ - PyAutoFit calls this function immediately before the non-linear search begins. - - It visualizes objects which do not change throughout the model fit like the dataset. - - Parameters - ---------- - paths - The paths object which manages all paths, e.g. where the non-linear search outputs are stored, - visualization and the pickled objects used by the aggregator output by this function. - model - The model object, which includes model components representing the galaxies that are fitted to - the imaging data. - """ - dataset = analysis.dataset - - plotter = PlotterQuantity( - image_path=paths.image_path, title_prefix=analysis.title_prefix - ) - - plotter.dataset_quantity(dataset=dataset) - - @staticmethod - def visualize( - analysis, - paths: af.DirectoryPaths, - instance: af.ModelInstance, - during_analysis: bool, - ): - """ - Output images of the maximum log likelihood model inferred by the model-fit. This function is called throughout - the non-linear search at regular intervals, and therefore provides on-the-fly visualization of how well the - model-fit is going. - - The visualization performed by this function includes: - - - Images of the best-fit galaxy. - - - Images of the best-fit `FitQuantity`, including the model-image, residuals and chi-squared of its fit to - the imaging data. - - The images output by this function are customized using the file `config/visualize/plots.yaml`. - - Parameters - ---------- - paths - The paths object which manages all paths, e.g. where the non-linear search outputs are stored, - visualization, and the pickled objects used by the aggregator output by this function. - instance - An instance of the model that is being fitted to the data by this analysis (whose parameters have been set - via a non-linear search). - """ - - if skip_visualization(): - return - - fit = analysis.fit_for_visualization(instance=instance) - - plotter = PlotterQuantity( - image_path=paths.image_path, title_prefix=analysis.title_prefix - ) - plotter.fit_quantity(fit=fit) diff --git a/autogalaxy/quantity/plot/__init__.py b/autogalaxy/quantity/plot/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/autogalaxy/quantity/plot/fit_quantity_plots.py b/autogalaxy/quantity/plot/fit_quantity_plots.py deleted file mode 100644 index 2d60ee20..00000000 --- a/autogalaxy/quantity/plot/fit_quantity_plots.py +++ /dev/null @@ -1,105 +0,0 @@ - -import autoarray as aa -from autoarray.plot.utils import subplots, conf_subplot_figsize, tight_layout - -from autogalaxy.quantity.fit_quantity import FitQuantity -from autogalaxy.util.plot_utils import plot_array, _save_subplot - - -def _subplot_fit_array(fit, output_path, output_format, colormap, use_log10, positions, filename="fit", title_prefix=None): - """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"``). - """ - _pf = (lambda t: f"{title_prefix.rstrip()} {t}") if title_prefix else (lambda t: t) - panels = [ - (fit.data, _pf("Data")), - (fit.signal_to_noise_map, _pf("Signal-To-Noise Map")), - (fit.model_data, _pf("Model Image")), - (fit.residual_map, _pf("Residual Map")), - (fit.normalized_residual_map, _pf("Normalized Residual Map")), - (fit.chi_squared_map, _pf("Chi-Squared Map")), - ] - n = len(panels) - fig, axes = subplots(1, n, figsize=conf_subplot_figsize(1, n)) - 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], - ) - - tight_layout() - _save_subplot(fig, output_path, filename, output_format) - - -def subplot_fit( - fit: FitQuantity, - output_path=None, - output_format=None, - colormap="default", - use_log10=False, - positions=None, - title_prefix: str = 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, title_prefix=title_prefix) - else: - _subplot_fit_array( - fit.y, output_path, output_format, colormap, use_log10, positions, filename="fit_y", title_prefix=title_prefix - ) - _subplot_fit_array( - fit.x, output_path, output_format, colormap, use_log10, positions, filename="fit_x", title_prefix=title_prefix - ) diff --git a/docs/api/fitting.rst b/docs/api/fitting.rst index b74e045f..1b9b2451 100644 --- a/docs/api/fitting.rst +++ b/docs/api/fitting.rst @@ -27,18 +27,4 @@ For fitting ellipses (isophotes) to an imaging dataset to characterise galaxy mo :template: custom-class-template.rst :recursive: - FitEllipse - -Quantity --------- - -For fitting a derived quantity (e.g. convergence, deflection angles) computed by one mass -or light profile to the same quantity computed by another, enabling model comparison between -different profile families. - -.. autosummary:: - :toctree: _autosummary - :template: custom-class-template.rst - :recursive: - - FitQuantity \ No newline at end of file + FitEllipse \ No newline at end of file diff --git a/docs/api/modeling.rst b/docs/api/modeling.rst index 9c7420a8..b887007c 100644 --- a/docs/api/modeling.rst +++ b/docs/api/modeling.rst @@ -19,7 +19,6 @@ It acts as an interface between the data, model and the non-linear search. AnalysisImaging AnalysisInterferometer AnalysisEllipse - AnalysisQuantity Non-linear Searches ------------------- diff --git a/docs/api/plot.rst b/docs/api/plot.rst index b605a165..4d5ca4f0 100644 --- a/docs/api/plot.rst +++ b/docs/api/plot.rst @@ -56,13 +56,6 @@ Create figures and subplots showing quantities of standard **PyAutoGalaxy** obje subplot_fit_dirty_images subplot_fit_real_space -**Quantity Fit Subplots:** - -.. autosummary:: - :toctree: _autosummary - - subplot_fit_quantity - **Ellipse Fit Subplots:** .. autosummary:: diff --git a/test_autogalaxy/config/visualize.yaml b/test_autogalaxy/config/visualize.yaml index 0bd92eac..1251f8b5 100644 --- a/test_autogalaxy/config/visualize.yaml +++ b/test_autogalaxy/config/visualize.yaml @@ -39,8 +39,6 @@ plots: fit_imaging: {} fit_interferometer: fits_dirty_images: true - fit_quantity: - subplot_fit: false adapt: subplot_adapt_images: true inversion: diff --git a/test_autogalaxy/conftest.py b/test_autogalaxy/conftest.py index 49713451..9f993842 100644 --- a/test_autogalaxy/conftest.py +++ b/test_autogalaxy/conftest.py @@ -321,29 +321,6 @@ def make_dataset_interp_7x7(): return fixtures.make_dataset_interp_7x7() -### QUANTITY ### - - -@pytest.fixture(name="dataset_quantity_7x7_array_2d") -def make_dataset_quantity_7x7_array_2d(): - return fixtures.make_dataset_quantity_7x7_array_2d() - - -@pytest.fixture(name="dataset_quantity_7x7_vector_yx_2d") -def make_dataset_quantity_7x7_vector_yx_2d(): - return fixtures.make_dataset_quantity_7x7_vector_yx_2d() - - -@pytest.fixture(name="fit_quantity_7x7_array_2d") -def make_fit_quantity_7x7_array_2d(): - return fixtures.make_fit_quantity_7x7_array_2d() - - -@pytest.fixture(name="fit_quantity_7x7_vector_yx_2d") -def make_fit_quantity_7x7_vector_yx_2d(): - return fixtures.make_fit_quantity_7x7_vector_yx_2d() - - ### FITS ### diff --git a/test_autogalaxy/quantity/__init__.py b/test_autogalaxy/quantity/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test_autogalaxy/quantity/files/array/output_test/data.fits b/test_autogalaxy/quantity/files/array/output_test/data.fits deleted file mode 100644 index 177e8d1d..00000000 Binary files a/test_autogalaxy/quantity/files/array/output_test/data.fits and /dev/null differ diff --git a/test_autogalaxy/quantity/files/array/output_test/noise_map.fits b/test_autogalaxy/quantity/files/array/output_test/noise_map.fits deleted file mode 100644 index aceebfc2..00000000 Binary files a/test_autogalaxy/quantity/files/array/output_test/noise_map.fits and /dev/null differ diff --git a/test_autogalaxy/quantity/model/__init__.py b/test_autogalaxy/quantity/model/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test_autogalaxy/quantity/model/test_analysis_quantity.py b/test_autogalaxy/quantity/model/test_analysis_quantity.py deleted file mode 100644 index ad373bd0..00000000 --- a/test_autogalaxy/quantity/model/test_analysis_quantity.py +++ /dev/null @@ -1,61 +0,0 @@ -from pathlib import Path - -import autofit as af -import autogalaxy as ag - -from autogalaxy.quantity.model.result import ResultQuantity - -directory = Path(__file__).resolve().parent - - -class TestAnalysisQuantity: - def test__make_result__result_quantity_is_returned( - self, dataset_quantity_7x7_array_2d - ): - model = af.Collection(galaxies=af.Collection(galaxy_0=ag.Galaxy(redshift=0.5))) - - analysis = ag.AnalysisQuantity( - dataset=dataset_quantity_7x7_array_2d, - func_str="convergence_2d_from", - use_jax=False, - ) - - search = ag.m.MockSearch(name="test_search") - - result = search.fit(model=model, analysis=analysis) - - assert isinstance(result, ResultQuantity) - - def test__figure_of_merit__matches_correct_fit_given_galaxy_profiles( - self, dataset_quantity_7x7_array_2d - ): - galaxy = ag.Galaxy(redshift=0.5, light=ag.mp.Isothermal(einstein_radius=1.0)) - - model = af.Collection(galaxies=af.Collection(galaxy=galaxy)) - - analysis = ag.AnalysisQuantity( - dataset=dataset_quantity_7x7_array_2d, - func_str="convergence_2d_from", - use_jax=False, - ) - - instance = model.instance_from_unit_vector([]) - fit_figure_of_merit = analysis.log_likelihood_function(instance=instance) - - galaxies = analysis.galaxies_via_instance_from(instance=instance) - - fit = ag.FitQuantity( - dataset=dataset_quantity_7x7_array_2d, - light_mass_obj=galaxies, - func_str="convergence_2d_from", - ) - - assert fit.log_likelihood == fit_figure_of_merit - - fit = ag.FitQuantity( - dataset=dataset_quantity_7x7_array_2d, - light_mass_obj=galaxies, - func_str="potential_2d_from", - ) - - assert fit.log_likelihood != fit_figure_of_merit diff --git a/test_autogalaxy/quantity/model/test_plotter_quantity.py b/test_autogalaxy/quantity/model/test_plotter_quantity.py deleted file mode 100644 index 6d1e6f16..00000000 --- a/test_autogalaxy/quantity/model/test_plotter_quantity.py +++ /dev/null @@ -1,49 +0,0 @@ -import shutil -import pytest - -import autogalaxy as ag - -from autogalaxy.quantity.model.plotter import PlotterQuantity -from pathlib import Path - -directory = Path(__file__).resolve().parent - - -@pytest.fixture(name="plot_path") -def make_plotter_plotter_setup(): - return directory / "files" - - -def test__dataset( - dataset_quantity_7x7_array_2d, - plot_path, - plot_patch, -): - if Path(plot_path).exists(): - shutil.rmtree(plot_path) - - plotter = PlotterQuantity(image_path=plot_path) - - plotter.dataset_quantity(dataset=dataset_quantity_7x7_array_2d) - - image = ag.ndarray_via_fits_from( - file_path=Path(plot_path) / "dataset.fits", hdu=1 - ) - - assert image.shape == (7, 7) - - -def test__fit_quantity( - fit_quantity_7x7_array_2d, - fit_quantity_7x7_vector_yx_2d, - plot_path, - plot_patch, -): - if Path(plot_path).exists(): - shutil.rmtree(plot_path) - - plotter = PlotterQuantity(image_path=plot_path) - - plotter.fit_quantity(fit=fit_quantity_7x7_array_2d) - - assert str(Path(plot_path) / "fit.png") not in plot_patch.paths diff --git a/test_autogalaxy/quantity/plot/__init__.py b/test_autogalaxy/quantity/plot/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py b/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py deleted file mode 100644 index 83afb3cd..00000000 --- a/test_autogalaxy/quantity/plot/test_fit_quantity_plotters.py +++ /dev/null @@ -1,26 +0,0 @@ -from pathlib import Path - -import pytest - -import autogalaxy.plot as aplt - -directory = Path(__file__).resolve().parent - - -@pytest.fixture(name="plot_path") -def make_galaxy_fit_plotter_setup(): - return Path(__file__).resolve().parent / "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 str(plot_path / "fit.png") in plot_patch.paths diff --git a/test_autogalaxy/quantity/test_dataset_quantity.py b/test_autogalaxy/quantity/test_dataset_quantity.py deleted file mode 100644 index 86072ddd..00000000 --- a/test_autogalaxy/quantity/test_dataset_quantity.py +++ /dev/null @@ -1,210 +0,0 @@ -import numpy as np -import os -import pytest -import shutil - -import autogalaxy as ag -from pathlib import Path - - -def test__via_signal_to_noise_map__array_2d_data__correct_noise_map( - dataset_quantity_7x7_array_2d, mask_2d_7x7 -): - data = ag.Array2D.no_mask(values=[[1.0, 2.0], [3.0, 4.0]], pixel_scales=1.0) - signal_to_noise_map = ag.Array2D.no_mask( - values=[[1.0, 5.0], [15.0, 40.0]], pixel_scales=1.0 - ) - - dataset_quantity = ag.DatasetQuantity.via_signal_to_noise_map( - data=data, signal_to_noise_map=signal_to_noise_map - ) - - assert dataset_quantity.signal_to_noise_map == pytest.approx( - signal_to_noise_map, 1.0e-4 - ) - assert dataset_quantity.noise_map.native == pytest.approx( - np.array([[1.0, 0.4], [0.2, 0.1]]), 1.0e-4 - ) - - -def test__via_signal_to_noise_map__vector_yx_2d_data__correct_noise_map( - dataset_quantity_7x7_array_2d, mask_2d_7x7 -): - data = ag.VectorYX2D.no_mask( - values=[[[1.0, 1.0], [2.0, 2.0]], [[3.0, 3.0], [4.0, 4.0]]], pixel_scales=1.0 - ) - signal_to_noise_map = ag.Array2D.no_mask( - values=[[1.0, 5.0], [15.0, 40.0]], pixel_scales=1.0 - ) - - dataset_quantity = ag.DatasetQuantity.via_signal_to_noise_map( - data=data, signal_to_noise_map=signal_to_noise_map - ) - - assert dataset_quantity.signal_to_noise_map == pytest.approx( - np.array([[1.0, 1.0], [5.0, 5.0], [15.0, 15.0], [40.0, 40.0]]), 1.0e-4 - ) - assert dataset_quantity.noise_map.native == pytest.approx( - np.array([[[1.0, 1.0], [0.4, 0.4]], [[0.2, 0.2], [0.1, 0.1]]]), 1.0e-4 - ) - - -def test__apply_mask__array_2d_dataset__slim_and_native_values_correct( - dataset_quantity_7x7_array_2d, mask_2d_7x7 -): - dataset_quantity_7x7 = dataset_quantity_7x7_array_2d.apply_mask(mask=mask_2d_7x7) - - assert (dataset_quantity_7x7.data.slim == np.ones(9)).all() - assert ( - dataset_quantity_7x7.data.native == np.ones((7, 7)) * np.invert(mask_2d_7x7) - ).all() - - assert (dataset_quantity_7x7.noise_map.slim == 2.0 * np.ones(9)).all() - assert ( - dataset_quantity_7x7.noise_map.native - == 2.0 * np.ones((7, 7)) * np.invert(mask_2d_7x7) - ).all() - - -def test__apply_mask__vector_yx_2d_dataset__slim_values_correct( - dataset_quantity_7x7_vector_yx_2d, mask_2d_7x7 -): - dataset_quantity_7x7 = dataset_quantity_7x7_vector_yx_2d.apply_mask( - mask=mask_2d_7x7 - ) - - assert (dataset_quantity_7x7.data.slim == np.ones((9, 2))).all() - assert (dataset_quantity_7x7.noise_map.slim == 2.0 * np.ones((9, 2))).all() - - -def test__grid__default_over_sample__matches_fixture_grid( - dataset_quantity_7x7_array_2d, - mask_2d_7x7, - grid_2d_7x7, - blurring_grid_2d_7x7, -): - dataset = dataset_quantity_7x7_array_2d.apply_mask(mask=mask_2d_7x7) - - assert isinstance(dataset.grids.lp, ag.Grid2D) - assert (dataset.grids.lp == grid_2d_7x7).all() - - -def test__grid__custom_over_sample_size_4__matches_fixture_grid( - mask_2d_7x7, - grid_2d_7x7, -): - dataset_quantity = ag.DatasetQuantity( - data=ag.Array2D.ones(shape_native=(7, 7), pixel_scales=1.0), - noise_map=ag.Array2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - over_sample_size_lp=4, - ) - - dataset = dataset_quantity.apply_mask(mask=mask_2d_7x7) - - assert (dataset.grids.lp == grid_2d_7x7).all() - - -def test__vector_data__y_x(): - data = ag.VectorYX2D.no_mask( - values=[[[1.0, 5.0], [2.0, 6.0]], [[3.0, 7.0], [4.0, 8.0]]], - pixel_scales=1.0, - ) - - noise_map = ag.VectorYX2D.no_mask( - values=[[[1.1, 5.1], [2.1, 6.1]], [[3.1, 7.1], [4.1, 8.1]]], - pixel_scales=1.0, - ) - - dataset_quantity = ag.DatasetQuantity(data=data, noise_map=noise_map) - - assert isinstance(dataset_quantity.y, ag.DatasetQuantity) - assert (dataset_quantity.y.data.slim == np.array([1.0, 2.0, 3.0, 4.0])).all() - assert dataset_quantity.y.noise_map.slim == pytest.approx( - np.array([1.1, 2.1, 3.1, 4.1]), 1.0e-4 - ) - - assert isinstance(dataset_quantity.y, ag.DatasetQuantity) - assert (dataset_quantity.x.data.slim == np.array([5.0, 6.0, 7.0, 8.0])).all() - assert dataset_quantity.x.noise_map.slim == pytest.approx( - np.array([5.1, 6.1, 7.1, 8.1]), 1.0e-4 - ) - - -@pytest.fixture(name="test_data_path") -def make_test_data_path(): - test_data_path = Path(__file__).resolve().parent / "files" / "array" / "output_test" - - if test_data_path.exists(): - shutil.rmtree(test_data_path) - - os.makedirs(test_data_path) - - return test_data_path - - -def test__output_to_fits__array_2d_data__data_and_noise_map_written_correctly( - dataset_quantity_7x7_array_2d, test_data_path -): - from autoconf.fitsable import output_to_fits - - output_to_fits( - values=dataset_quantity_7x7_array_2d.data.native.array.astype("float"), - file_path=Path(test_data_path) / "data.fits", - overwrite=True, - header_dict=dataset_quantity_7x7_array_2d.data.mask.header_dict, - ) - output_to_fits( - values=dataset_quantity_7x7_array_2d.noise_map.native.array.astype("float"), - file_path=Path(test_data_path) / "noise_map.fits", - overwrite=True, - header_dict=dataset_quantity_7x7_array_2d.noise_map.mask.header_dict, - ) - - data = ag.Array2D.from_fits( - file_path=Path(test_data_path) / "data.fits", hdu=0, pixel_scales=1.0 - ) - noise_map = ag.Array2D.from_fits( - file_path=Path(test_data_path) / "noise_map.fits", hdu=0, pixel_scales=1.0 - ) - - assert (data.native == np.ones((7, 7))).all() - assert (noise_map.native == 2.0 * np.ones((7, 7))).all() - - -def test__output_to_fits__vector_yx_2d_data__first_pixel_written_correctly( - test_data_path, -): - data = ag.VectorYX2D.no_mask( - values=[[[1.0, 5.0], [2.0, 6.0]], [[3.0, 7.0], [4.0, 8.0]]], - pixel_scales=1.0, - ) - - noise_map = ag.VectorYX2D.no_mask( - values=[[[1.1, 5.1], [2.1, 6.1]], [[3.1, 7.1], [4.1, 8.1]]], - pixel_scales=1.0, - ) - - dataset_quantity = ag.DatasetQuantity(data=data, noise_map=noise_map) - - from autoconf.fitsable import output_to_fits - - output_to_fits( - values=dataset_quantity.data.native.array.astype("float"), - file_path=Path(test_data_path) / "data.fits", - overwrite=True, - header_dict=dataset_quantity.data.mask.header_dict, - ) - output_to_fits( - values=dataset_quantity.noise_map.native.array.astype("float"), - file_path=Path(test_data_path) / "noise_map.fits", - overwrite=True, - header_dict=dataset_quantity.noise_map.mask.header_dict, - ) - - data = ag.Array2D.from_fits( - file_path=Path(test_data_path) / "data.fits", hdu=0, pixel_scales=1.0 - ) - - assert data[0, 0] == pytest.approx([1.0, 5.0], 1.0e-4) diff --git a/test_autogalaxy/quantity/test_fit_quantity.py b/test_autogalaxy/quantity/test_fit_quantity.py deleted file mode 100644 index 4a29e3bf..00000000 --- a/test_autogalaxy/quantity/test_fit_quantity.py +++ /dev/null @@ -1,104 +0,0 @@ -import numpy as np -import pytest - -import autogalaxy as ag - - -def test__fit_via_mock_profile__convergence__chi_squared_zero_and_correct_log_likelihood( - dataset_quantity_7x7_array_2d, -): - mass = ag.m.MockMassProfile( - convergence_2d=ag.Array2D.ones(shape_native=(7, 7), pixel_scales=1.0), - potential_2d=ag.Array2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - deflections_yx_2d=ag.VectorYX2D.full( - fill_value=3.0, shape_native=(7, 7), pixel_scales=1.0 - ), - ) - - galaxies = ag.Galaxies(galaxies=[ag.Galaxy(redshift=0.5, mass=mass)]) - - fit_quantity = ag.FitQuantity( - dataset=dataset_quantity_7x7_array_2d, - light_mass_obj=galaxies, - func_str="convergence_2d_from", - ) - - assert fit_quantity.chi_squared == pytest.approx(0.0, 1.0e-4) - assert fit_quantity.log_likelihood == pytest.approx( - -0.5 * 49.0 * np.log(2 * np.pi * 2.0**2.0), 1.0e-4 - ) - - -def test__fit_via_mock_profile__potential__nonzero_chi_squared_and_correct_log_likelihood( - dataset_quantity_7x7_array_2d, -): - mass = ag.m.MockMassProfile( - convergence_2d=ag.Array2D.ones(shape_native=(7, 7), pixel_scales=1.0), - potential_2d=ag.Array2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - deflections_yx_2d=ag.VectorYX2D.full( - fill_value=3.0, shape_native=(7, 7), pixel_scales=1.0 - ), - ) - - galaxies = ag.Galaxies(galaxies=[ag.Galaxy(redshift=0.5, mass=mass)]) - - fit_quantity = ag.FitQuantity( - dataset=dataset_quantity_7x7_array_2d, - light_mass_obj=galaxies, - func_str="potential_2d_from", - ) - - assert fit_quantity.chi_squared == pytest.approx(12.25, 1.0e-4) - assert fit_quantity.log_likelihood == pytest.approx(-85.1171999, 1.0e-4) - - -def test__fit_via_mock_profile__deflections__vector_dataset__correct_chi_squared_and_log_likelihood( - dataset_quantity_7x7_vector_yx_2d, -): - mass = ag.m.MockMassProfile( - convergence_2d=ag.Array2D.ones(shape_native=(7, 7), pixel_scales=1.0), - potential_2d=ag.Array2D.full( - fill_value=2.0, shape_native=(7, 7), pixel_scales=1.0 - ), - deflections_yx_2d=ag.VectorYX2D.full( - fill_value=3.0, shape_native=(7, 7), pixel_scales=1.0 - ), - ) - - galaxies = ag.Galaxies(galaxies=[ag.Galaxy(redshift=0.5, mass=mass)]) - - fit_quantity = ag.FitQuantity( - dataset=dataset_quantity_7x7_vector_yx_2d, - light_mass_obj=galaxies, - func_str="deflections_yx_2d_from", - ) - - assert fit_quantity.chi_squared == pytest.approx(98.0, 1.0e-4) - assert fit_quantity.log_likelihood == pytest.approx(-206.98438, 1.0e-4) - - -def test__y_x(dataset_quantity_7x7_vector_yx_2d): - model_object = ag.m.MockMassProfile( - deflections_yx_2d=ag.VectorYX2D.full( - fill_value=3.0, shape_native=(7, 7), pixel_scales=1.0 - ) - ) - - galaxies = ag.Galaxies(galaxies=[ag.Galaxy(redshift=0.5, mass=model_object)]) - - fit_quantity = ag.FitQuantity( - dataset=dataset_quantity_7x7_vector_yx_2d, - light_mass_obj=galaxies, - func_str="deflections_yx_2d_from", - ) - - assert ( - fit_quantity.y.dataset.data == dataset_quantity_7x7_vector_yx_2d.y.data - ).all() - assert ( - fit_quantity.x.dataset.data == dataset_quantity_7x7_vector_yx_2d.x.data - ).all()