Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy2211 committed Apr 15, 2024
2 parents 9a798cf + 55eadc5 commit 6313bd1
Show file tree
Hide file tree
Showing 12 changed files with 423 additions and 178 deletions.
1 change: 1 addition & 0 deletions autofit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
from .mapper.prior_model.prior_model import Model
from .mapper.prior_model.util import PriorModelNameValue
from .non_linear.search.abstract_search import NonLinearSearch
from .non_linear.analysis.visualize import Visualizer
from .non_linear.analysis.analysis import Analysis
from .non_linear.analysis.combined import CombinedAnalysis
from .non_linear.grid.grid_search import GridSearchResult
Expand Down
4 changes: 1 addition & 3 deletions autofit/config/output.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,4 @@ covariance: true # `covariance.csv`: The [free parameters x free parameters] cov
data: true # `data.json`: The value of every data point in the data.
noise_map: true # `noise_map.json`: The value of every RMS noise map value.

search_log: true # `search.log`: logging produced whilst running the fit or fit_sequential method

default: false
search_log: true # `search.log`: logging produced whilst running the fit or fit_sequential method
25 changes: 19 additions & 6 deletions autofit/example/analysis.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import os
import matplotlib.pyplot as plt

from typing import Dict, List, Optional

from autofit.example.result import ResultExample
from autofit.jax_wrapper import numpy as np

import autofit as af

from autofit.example.result import ResultExample
from autofit.example.visualize import VisualizerExample

"""
The `analysis.py` module contains the dataset and log likelihood function which given a model instance (set up by
the non-linear search) fits the dataset and returns the log likelihood of that model.
Expand All @@ -16,10 +17,22 @@
class Analysis(af.Analysis):

"""
This overwrite means the `ResultExample` class is returned after the model-fit.
This over-write means the `Visualizer` class is used for visualization throughout the model-fit.
This `VisualizerExample` object is in the `autofit.example.visualize` module and is used to customize the
plots output during the model-fit.
It has been extended with visualize methods that output visuals specific to the fitting of `1D` data.
"""
Visualizer = VisualizerExample

"""
This over-write means the `ResultExample` class is returned after the model-fit.
This result has been extended, based on the model that is input into the analysis, to include a property
`max_log_likelihood_model_data`, which is the model data of the best-fit model.
This `ResultExample` object in the `autofit.example.result` module.
It has been extended, based on the model that is input into the analysis, to include a
property `max_log_likelihood_model_data`, which is the model data of the best-fit model.
"""

Result = ResultExample
Expand Down
203 changes: 203 additions & 0 deletions autofit/example/visualize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
import os
import matplotlib.pyplot as plt
import numpy as np
from typing import List

import autofit as af


class VisualizerExample(af.Visualizer):
"""
Methods associated with visualising analysis, model and data before, during
or after an optimisation.
"""

@staticmethod
def visualize_before_fit(
analysis,
paths: af.AbstractPaths,
model: af.AbstractPriorModel,
):
"""
Before a model-fit begins, the `visualize_before_fit` method is called and is used to output images
of quantities that do not change during the fit (e.g. the data).
The function receives as input an instance of the `Analysis` class which is being used to perform the fit,
which is used to perform the visualization (e.g. it contains the data and noise map which are plotted).
For your model-fitting problem this function will be overwritten with plotting functions specific to your
problem.
Parameters
----------
analysis
The analysis class used to perform the model-fit whose quantities are being visualized.
paths
The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
visualization, and the pickled objects used by the aggregator output by this function.
model
The model which is fitted to the data, which may be used to customize the visualization.
"""

xvalues = np.arange(analysis.data.shape[0])

plt.errorbar(
x=xvalues,
y=analysis.data,
yerr=analysis.noise_map,
color="k",
ecolor="k",
elinewidth=1,
capsize=2,
)
plt.title("The 1D Dataset.")
plt.xlabel("x values of profile")
plt.ylabel("Profile normalization")

os.makedirs(paths.image_path, exist_ok=True)
plt.savefig(paths.image_path / "data.png")
plt.clf()
plt.close()

@staticmethod
def visualize(
analysis,
paths: af.DirectoryPaths,
instance: af.ModelInstance,
during_analysis : bool
):
"""
During a model-fit, the `visualize` method is called throughout the non-linear search and is used to output
images indicating the quality of the fit so far.
The function receives as input an instance of the `Analysis` class which is being used to perform the fit,
which is used to perform the visualization (e.g. it generates the model data which is plotted).
The `instance` passed into the visualize method is maximum log likelihood solution obtained by the model-fit
so far which can output on-the-fly images showing the best-fit model so far.
For your model-fitting problem this function will be overwritten with plotting functions specific to your
problem.
Parameters
----------
analysis
The analysis class used to perform the model-fit whose quantities are being visualized.
paths
The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
visualization, and the pickled objects used by the aggregator output by this function.
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).
during_analysis
If True the visualization is being performed midway through the non-linear search before it is finished,
which may change which images are output.
"""

xvalues = np.arange(analysis.data.shape[0])
model_data_1d = np.zeros(analysis.data.shape[0])

try:
for profile in instance:
try:
model_data_1d += profile.model_data_1d_via_xvalues_from(xvalues=xvalues)
except AttributeError:
pass
except TypeError:
model_data_1d += instance.model_data_1d_via_xvalues_from(xvalues=xvalues)

plt.errorbar(
x=xvalues,
y=analysis.data,
yerr=analysis.noise_map,
color="k",
ecolor="k",
elinewidth=1,
capsize=2,
)
plt.plot(xvalues, model_data_1d, color="r")
plt.title("Model fit to 1D Gaussian + Exponential dataset.")
plt.xlabel("x values of profile")
plt.ylabel("Profile normalization")

os.makedirs(paths.image_path, exist_ok=True)
plt.savefig(paths.image_path / "model_fit.png")
plt.clf()
plt.close()

@staticmethod
def visualize_before_fit_combined(
analyses,
paths: af.AbstractPaths,
model: af.AbstractPriorModel,
):
"""
Multiple instances of the `Analysis` class can be summed together, meaning that the model is fitted to all
datasets simultaneously via a summed likelihood function.
The function receives as input a list of instances of every `Analysis` class which is being used to perform
the summed analysis fit. This is used which is used to perform the visualization which combines the
information spread across all analyses (e.g. plotting the data of each analysis on the same subplot).
The `visualize_before_fit_combined` method is called before the model-fit begins and is used to output images
of quantities that do not change during the fit (e.g. the data).
When summed analysis is used, the `visualize_before_fit` method is also called for each individual analysis.
Each individual dataset may therefore also be visualized in that function. This method is specifically for
visualizing the combined information of all datasets.
For your model-fitting problem this function will be overwritten with plotting functions specific to your
problem.
The example does not use analysis summing and therefore this function is not implemented.
Parameters
----------
analyses
A list of the analysis classes used to perform the model-fit whose quantities are being visualized.
paths
The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
visualization, and the pickled objects used by the aggregator output by this function.
model
The model which is fitted to the data, which may be used to customize the visualization.
"""
pass

@staticmethod
def visualize_combined(
analyses: List[af.Analysis],
paths: af.DirectoryPaths,
instance: af.ModelInstance,
during_analysis: bool,
):
"""
Multiple instances of the `Analysis` class can be summed together, meaning that the model is fitted to all
datasets simultaneously via a summed likelihood function.
The function receives as input a list of instances of every `Analysis` class which is being used to perform
the summed analysis fit. This is used which is used to perform the visualization which combines the
information spread across all analyses (e.g. plotting the data of each analysis on the same subplot).
The `visualize_combined` method is called throughout the non-linear search and is used to output images
indicating the quality of the fit so far.
When summed analysis is used, the `visualize_before_fit` method is also called for each individual analysis.
Each individual dataset may therefore also be visualized in that function. This method is specifically for
visualizing the combined information of all datasets.
For your model-fitting problem this function will be overwritten with plotting functions specific to your
problem.
The example does not use analysis summing and therefore this function is not implemented.
Parameters
----------
analyses
A list of the analysis classes used to perform the model-fit whose quantities are being visualized.
paths
The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
visualization, and the pickled objects used by the aggregator output by this function.
model
The model which is fitted to the data, which may be used to customize the visualization.
"""
pass
56 changes: 3 additions & 53 deletions autofit/non_linear/analysis/analysis.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
import logging
from abc import ABC
import os
from typing import Optional, Dict

from autoconf import conf

from autofit.mapper.prior_model.abstract import AbstractPriorModel
from autofit.non_linear.paths.abstract import AbstractPaths
from autofit.non_linear.paths.database import DatabasePaths
from autofit.non_linear.paths.null import NullPaths
from autofit.non_linear.samples.summary import SamplesSummary
from autofit.non_linear.samples.pdf import SamplesPDF
from autofit.non_linear.result import Result
Expand Down Expand Up @@ -40,8 +36,8 @@ def __getattr__(self, item: str):
It may be desirable to remove this behaviour as the visualizer component of
the system becomes more sophisticated.
"""
if item.startswith("visualize"):
_method = getattr(Visualizer, item)
if item.startswith("visualize") or item.startswith("should_visualize"):
_method = getattr(self.Visualizer, item)
else:
raise AttributeError(f"Analysis has no attribute {item}")

Expand Down Expand Up @@ -145,52 +141,6 @@ def with_model(self, model):

return ModelAnalysis(analysis=self, model=model)

def should_visualize(
self, paths: AbstractPaths, during_analysis: bool = True
) -> bool:
"""
Whether a visualize method should be called perform visualization, which depends on the following:
1) If a model-fit has already completed, the default behaviour is for visualization to be bypassed in order
to make model-fits run faster.
2) If a model-fit has completed, but it is the final visualization output where `during_analysis` is False,
it should be performed.
3) Visualization can be forced to run via the `force_visualization_overwrite`, for example if a user
wants to plot additional images that were not output on the original run.
4) If the analysis is running a database session visualization is switched off.
5) If PyAutoFit test mode is on visualization is disabled, irrespective of the `force_visualization_overwite`
config input.
Parameters
----------
paths
The PyAutoFit paths object which manages all paths, e.g. where the non-linear search outputs are stored,
visualization and the pickled objects used by the aggregator output by this function.
Returns
-------
A bool determining whether visualization should be performed or not.
"""

if os.environ.get("PYAUTOFIT_TEST_MODE") == "1":
return False

if isinstance(paths, DatabasePaths) or isinstance(paths, NullPaths):
return False

if conf.instance["general"]["output"]["force_visualize_overwrite"]:
return True

if not during_analysis:
return True

return not paths.is_complete

def log_likelihood_function(self, instance):
raise NotImplementedError()

Expand Down Expand Up @@ -280,7 +230,7 @@ def make_result(
paths=paths,
samples=samples,
search_internal=search_internal,
analysis=None,
analysis=analysis,
)

def profile_log_likelihood_function(self, paths: AbstractPaths, instance):
Expand Down
8 changes: 4 additions & 4 deletions autofit/non_linear/analysis/combined.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ def func(child_paths, analysis):
self._for_each_analysis(func, paths)

def visualize_before_fit_combined(
self, analyses, paths: AbstractPaths, model: AbstractPriorModel
self, paths: AbstractPaths, model: AbstractPriorModel
):
"""
Visualise images and quantities which are shared across all analyses.
Expand All @@ -246,7 +246,8 @@ def visualize_before_fit_combined(
paths
An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database).
"""
self.analyses[0].visualize_before_fit_combined(

self.analyses[0].Visualizer.visualize_before_fit_combined(
analyses=self.analyses,
paths=paths,
model=model,
Expand Down Expand Up @@ -284,7 +285,6 @@ def func(child_paths, analysis):

def visualize_combined(
self,
analyses: List["Analysis"],
instance,
paths: AbstractPaths,
during_analysis,
Expand All @@ -308,7 +308,7 @@ def visualize_combined(
during_analysis
Is this visualisation during analysis?
"""
self.analyses[0].visualize_combined(
self.analyses[0].Visualizer.visualize_combined(
analyses=self.analyses,
paths=paths,
instance=instance,
Expand Down
Loading

0 comments on commit 6313bd1

Please sign in to comment.