rhayes777 / PyAutoFit

PyAutoFit: Classy Probabilistic Programming
https://pyautofit.readthedocs.io/
MIT License
59 stars 11 forks source link

Access `physical_values` nd other quantities via model name #1028

Open Jammy2211 opened 1 month ago

Jammy2211 commented 1 month ago

This issue closely mirrors the issue below, so you should read that one first before this one:

https://github.com/rhayes777/PyAutoFit/issues/1026

The following PR adds the physical_values of a sensitivity map to its result:

https://github.com/rhayes777/PyAutoFit/pull/1027

The physical_values are the list of lists (...of lists) of all values on the sensitivity mapping grid where sensitivity mapping is performed.

For example, for PyAutoLens subhalo analysis, it would be the grid of (y,x,mass) coordinates that the sensitivity map is computed on.

Analogous to lower_limits_lists in GridSearchResult, the physical_values are used to visualize the subhalo results in PyAutoLens

    def _array_2d_from(self, values) -> aa.Array2D:
        """
        Returns an `Array2D` where the input values are reshaped from list of lists to a 2D array, which is
        suitable for plotting.

        For example, this function may return the 2D array of the increases in log evidence for every lens model
        fitted with a DM subhalo in the sensitivity mapping compared to the model without a DM subhalo.

        The orientation of the 2D array and its values are chosen to ensure that when this array is plotted, DM
        subhalos with positive y and negative x `centre` coordinates appear in the top-left of the image.

        Parameters
        ----------
        values_native
            The list of list of values which are mapped to the 2D array (e.g. the `log_evidence` difference of every
            lens model with a DM subhalo compared to the one without).

        Returns
        -------
        The 2D array of values, where the values are mapped from the input list of lists.
        """
        values_reshaped = [value for values in values.native for value in values]

        y = [centre[0] for centre in self.physical_values]
        x = [centre[1] for centre in self.physical_values]

        pixel_scales = abs(y[0] - y[1])

        return aa.Array2D.from_yx_and_values(
            y=[centre[0] for centre in self.physical_values],
            x=[centre[1] for centre in self.physical_values],
            values=values_reshaped,
            pixel_scales=pixel_scales,
            shape_native=self.shape,
        )

This issue is pretty much the same one as for the GridSearchResult, in that the use of physical_values relied on a subhalo search being 2D (only being over the y and x coordinates).

Therefore, we again just need a function which for an input physical_values and input model path returns the values for that attribute, so something like:

centre_0_lists = self.physical_values_from("galaxies.subhalo.mass.centre.centre_0")

We also need support for multiple attribute inputs:

centre_lists = self.physical_values_from(["galaxies.subhalo.mass.centre.centre_0", "galaxies.subhalo.mass.centre.centre_1"])

rhayes777 commented 1 month ago

physical_centres_lists_from is now available for both GridSearchResult and SensitivityResult

Jammy2211 commented 1 month ago

For a SensitivityResult, the method physical_centres_lists_from cannot retrieve attributes associated with the perturbation part of the model:

self.physical_centres_lists_from(
            path="perturbation.mass.centre.centre_0"
        )

This is because the model object passed around the Result does not include the perturbation, which is only added at each individual fit of the Sensitivity run:

The model printed below does not have the pertubation in its paths:

        if isinstance(path, str):
            path = path.split(".")

            def value_for_samples(samples):
                return samples.model.object_for_path(path).mean

        else:
            paths = [p.split(".") for p in path]

            def value_for_samples(samples):
                return tuple(samples.model.object_for_path(p).mean for p in paths)

        return [value_for_samples(samples) for samples in self.samples]

The code either needs the perturbation to be added to this model or for the values to be accessed via a different method.

rhayes777 commented 1 month ago

SensitivityResult now has a perturbed_physical_centres_list_from method