From 7062bd13692972d3ff17954d8277bc70133f28f8 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 2 May 2025 15:57:57 +0100 Subject: [PATCH 1/3] removing combined analysis --- autofit/__init__.py | 1 - autofit/graphical/declarative/collection.py | 2 +- autofit/non_linear/analysis/__init__.py | 2 - autofit/non_linear/analysis/combined.py | 426 ------------------ autofit/non_linear/analysis/free_parameter.py | 190 -------- autofit/non_linear/analysis/indexed.py | 156 ------- autofit/non_linear/analysis/model_analysis.py | 27 -- autofit/non_linear/combined_result.py | 60 +++ autofit/non_linear/search/abstract_search.py | 63 --- .../analysis/test_combined_visualise.py | 78 ---- test_autofit/analysis/test_free_parameter.py | 220 --------- test_autofit/analysis/test_regression.py | 35 -- test_autofit/analysis/test_relation.py | 175 ------- .../non_linear/test_fit_sequential.py | 79 ---- 14 files changed, 61 insertions(+), 1453 deletions(-) delete mode 100644 autofit/non_linear/analysis/combined.py delete mode 100644 autofit/non_linear/analysis/free_parameter.py delete mode 100644 autofit/non_linear/analysis/indexed.py create mode 100644 autofit/non_linear/combined_result.py delete mode 100644 test_autofit/analysis/test_combined_visualise.py delete mode 100644 test_autofit/analysis/test_free_parameter.py delete mode 100644 test_autofit/analysis/test_relation.py delete mode 100644 test_autofit/non_linear/test_fit_sequential.py diff --git a/autofit/__init__.py b/autofit/__init__.py index 740bf7dca..1c57e4a1b 100644 --- a/autofit/__init__.py +++ b/autofit/__init__.py @@ -65,7 +65,6 @@ 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 from .non_linear.grid.sensitivity import Sensitivity from .non_linear.initializer import InitializerBall diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index 06caa0a8a..a8a984815 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -7,9 +7,9 @@ from autofit.non_linear.paths.abstract import AbstractPaths from autofit.non_linear.samples.pdf import SamplesPDF from autofit.non_linear.samples.summary import SamplesSummary -from autofit.non_linear.analysis.combined import CombinedResult from autofit.jax_wrapper import register_pytree_node_class +from ...non_linear.combined_result import CombinedResult @register_pytree_node_class diff --git a/autofit/non_linear/analysis/__init__.py b/autofit/non_linear/analysis/__init__.py index 5c33a4082..3a610680e 100644 --- a/autofit/non_linear/analysis/__init__.py +++ b/autofit/non_linear/analysis/__init__.py @@ -1,3 +1 @@ from .analysis import Analysis -from .combined import CombinedAnalysis -from .free_parameter import FreeParameterAnalysis diff --git a/autofit/non_linear/analysis/combined.py b/autofit/non_linear/analysis/combined.py deleted file mode 100644 index 9fd6fd778..000000000 --- a/autofit/non_linear/analysis/combined.py +++ /dev/null @@ -1,426 +0,0 @@ -import logging -from typing import Union, List, Optional - -from autoconf import conf -from autofit.mapper.prior.abstract import Prior -from autofit.mapper.prior.tuple_prior import TuplePrior -from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.non_linear.analysis.multiprocessing import AnalysisPool -from autofit.non_linear.paths.abstract import AbstractPaths -from autofit.non_linear.result import Result -from .analysis import Analysis -from autofit.non_linear.samples.summary import SamplesSummary -from autofit.non_linear.samples import SamplesPDF - -logger = logging.getLogger(__name__) - - -class CombinedResult(Result): - def __init__( - self, - results: List[Result], - samples: Optional[SamplesPDF] = None, - samples_summary: Optional[SamplesSummary] = None, - paths: Optional[AbstractPaths] = None, - search_internal: Optional[object] = None, - analysis: Optional[Analysis] = None, - ): - """ - A `Result` object that is composed of multiple `Result` objects. This is used to combine the results of - multiple `Analysis` objects into a single `Result` object, for example when performing a model-fitting - analysis where there are multiple datasets. - - Parameters - ---------- - results - The list of `Result` objects that are combined into this `CombinedResult` object. - """ - super().__init__( - samples_summary=samples_summary, - samples=samples, - paths=paths, - search_internal=search_internal, - analysis=analysis, - ) - self.child_results = results - - def __getattr__(self, item: str): - """ - Get an attribute of the first `Result` object in the list of `Result` objects. - """ - if item in ("__getstate__", "__setstate__"): - raise AttributeError(item) - return getattr(self.child_results[0], item) - - def __iter__(self): - return iter(self.child_results) - - def __len__(self): - return len(self.child_results) - - def __getitem__(self, item: int) -> Result: - """ - Get a `Result` object from the list of `Result` objects. - """ - return self.child_results[item] - - -class CombinedAnalysis(Analysis): - def __new__(cls, *analyses, **kwargs): - from .model_analysis import ModelAnalysis, CombinedModelAnalysis - - if any(isinstance(analysis, ModelAnalysis) for analysis in analyses): - return object.__new__(CombinedModelAnalysis) - return object.__new__(cls) - - def __init__(self, *analyses: Analysis): - """ - Computes the summed log likelihood of multiple analyses - applied to a single model. - - Either analyses are performed sequentially and summed, - or they are mapped out to processes. - - If the number of cores is greater than one then the - analyses are distributed across a number of processes - equal to the number of cores. - - Parameters - ---------- - analyses - """ - self.analyses = analyses - self._analysis_pool = None - self._n_cores = None - self._log_likelihood_function = None - self.n_cores = conf.instance["general"]["analysis"]["n_cores"] - - def __getitem__(self, item): - return self.analyses[item] - - def modify_before_fit(self, paths: AbstractPaths, model: AbstractPriorModel): - """ - Modify the analysis before fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - """ - - def func(child_paths, analysis): - return analysis.modify_before_fit(child_paths, model) - - return CombinedAnalysis(*self._for_each_analysis(func, paths)) - - def modify_after_fit( - self, paths: AbstractPaths, model: AbstractPriorModel, result: Result - ): - """ - Modify the analysis after fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - result - The result of the fit. - """ - - def func(child_paths, analysis, result_): - return analysis.modify_after_fit(child_paths, model, result_) - - return CombinedAnalysis( - *self._for_each_analysis(func, paths, result.child_results) - ) - - @property - def n_cores(self): - return self._n_cores - - @n_cores.setter - def n_cores(self, n_cores: int): - """ - Set the number of cores this analysis should use. - - If the number of cores is greater than 1 then log likelihood - computations are distributed across multiple processes. - """ - self._n_cores = n_cores - if self.n_cores > 1: - self._analysis_pool = AnalysisPool(self.analyses, self.n_cores) - self._log_likelihood_function = self._analysis_pool - else: - self._log_likelihood_function = self._summed_log_likelihood - - def _summed_log_likelihood(self, instance) -> float: - """ - Compute a log likelihood by simply summing the log likelihood - of each individual analysis computed for some instance. - - Parameters - ---------- - instance - An instance of a model - - Returns - ------- - A combined log likelihood - """ - return sum( - analysis.log_likelihood_function(instance) for analysis in self.analyses - ) - - def log_likelihood_function(self, instance): - return self._log_likelihood_function(instance) - - def _for_each_analysis(self, func, paths, *args) -> List[Union[Result, Analysis]]: - """ - Convenience function to call an underlying function for each - analysis with a paths object with an integer attached to the - end. - - Parameters - ---------- - func - Some function of the analysis class - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - """ - results = [] - for (i, analysis), *args in zip(enumerate(self.analyses), *args): - child_paths = paths.for_sub_analysis(analysis_name=f"analyses/analysis_{i}") - results.append(func(child_paths, analysis, *args)) - - return results - - def save_attributes(self, paths: AbstractPaths): - def func(child_paths, analysis): - analysis.save_attributes( - child_paths, - ) - - self._for_each_analysis(func, paths) - - def save_results(self, paths: AbstractPaths, result: Result): - def func(child_paths, analysis, result_): - analysis.save_results(paths=child_paths, result=result_) - - self._for_each_analysis(func, paths, result) - - def save_results_combined(self, paths: AbstractPaths, result: Result): - self.analyses[0].save_results_combined( - paths=paths, - result=result, - ) - - def visualize_before_fit(self, paths: AbstractPaths, model: AbstractPriorModel): - """ - Visualise the model before fitting. - - Visualisation output is distinguished by using an integer suffix - for each analysis path. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - """ - if self._analysis_pool: - self._analysis_pool.map( - "visualize_before_fit", - paths, - model, - ) - return - - def func(child_paths, analysis): - analysis.visualize_before_fit(child_paths, model) - - self._for_each_analysis(func, paths) - - def visualize_before_fit_combined( - self, paths: AbstractPaths, model: AbstractPriorModel - ): - """ - Visualise images and quantities which are shared across all analyses. - - For example, each Analysis may have a different dataset, where the data in each dataset is intended to all - be plotted on the same matplotlib subplot. This function can be overwritten to allow the visualization of such - a plot. - - Only the first analysis is used to visualize the combined results, where it is assumed that it uses the - `analyses` property to access the other analyses and perform visualization. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - """ - - self.analyses[0].Visualizer.visualize_before_fit_combined( - analyses=self.analyses, - paths=paths, - model=model, - ) - - def visualize(self, paths: AbstractPaths, instance, during_analysis): - """ - Visualise the instance according to each analysis. - - Visualisation output is distinguished by using an integer suffix - for each analysis path. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - instance - The maximum likelihood instance of the model so far in the non-linear search. - during_analysis - Is this visualisation during analysis? - """ - if self._analysis_pool: - self._analysis_pool.map( - "visualize", - paths, - instance, - during_analysis, - ) - return - - def func(child_paths, analysis): - analysis.visualize(child_paths, instance, during_analysis) - - self._for_each_analysis(func, paths) - - def visualize_combined( - self, - instance, - paths: AbstractPaths, - during_analysis, - ): - """ - Visualise the instance using images and quantities which are shared across all analyses. - - For example, each Analysis may have a different dataset, where the fit to each dataset is intended to all - be plotted on the same matplotlib subplot. This function can be overwritten to allow the visualization of such - a plot. - - Only the first analysis is used to visualize the combined results, where it is assumed that it uses the - `analyses` property to access the other analyses and perform visualization. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - instance - The maximum likelihood instance of the model so far in the non-linear search. - during_analysis - Is this visualisation during analysis? - """ - self.analyses[0].Visualizer.visualize_combined( - analyses=self.analyses, - paths=paths, - instance=instance, - during_analysis=during_analysis, - ) - - def profile_log_likelihood_function( - self, - paths: AbstractPaths, - instance, - ): - """ - Profile the log likelihood function of the maximum likelihood model instance using each analysis. - - Profiling output is distinguished by using an integer suffix for each analysis path. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - instance - The maximum likliehood instance of the model so far in the non-linear search. - """ - - def func(child_paths, analysis): - analysis.profile_log_likelihood_function( - child_paths, - instance, - ) - - self._for_each_analysis(func, paths) - - def make_result( - self, - samples_summary: SamplesSummary, - paths: AbstractPaths, - samples: Optional[SamplesPDF] = None, - search_internal: Optional[object] = None, - analysis: Optional[object] = None, - ): - child_results = [ - analysis.make_result( - samples_summary=samples_summary, - paths=paths, - samples=samples, - search_internal=search_internal, - analysis=analysis, - ) - for analysis in self.analyses - ] - return CombinedResult( - child_results, - samples, - samples_summary, - ) - - def __len__(self): - return len(self.analyses) - - def __add__(self, other: Analysis): - """ - Adding anything to a CombinedAnalysis results in another - analysis containing all underlying analyses (no combined - analysis children) - - Parameters - ---------- - other - Some analysis - - Returns - ------- - An overarching analysis - """ - if isinstance(other, CombinedAnalysis): - return type(self)(*self.analyses, *other.analyses) - return type(self)(*self.analyses, other) - - def with_free_parameters( - self, *free_parameters: Union[Prior, TuplePrior, AbstractPriorModel] - ): - """ - Set some parameters as free parameters. The are priors which vary - independently for each analysis in the collection. - - Parameters - ---------- - free_parameters - Parameters that are allowed to vary independently. - - Returns - ------- - An analysis with freely varying parameters. - """ - from .free_parameter import FreeParameterAnalysis - - return FreeParameterAnalysis(*self.analyses, free_parameters=free_parameters) - - def compute_latent_samples(self, samples): - return self.analyses[0].compute_latent_samples(samples=samples) diff --git a/autofit/non_linear/analysis/free_parameter.py b/autofit/non_linear/analysis/free_parameter.py deleted file mode 100644 index c3e42b8a4..000000000 --- a/autofit/non_linear/analysis/free_parameter.py +++ /dev/null @@ -1,190 +0,0 @@ -import logging -from collections import defaultdict -from typing import Tuple - -from autofit.mapper.prior.abstract import Prior -from autofit.mapper.prior.tuple_prior import TuplePrior -from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.mapper.prior_model.collection import Collection -from .analysis import Analysis -from .indexed import IndexCollectionAnalysis -from ..paths.abstract import AbstractPaths - - -logger = logging.getLogger(__name__) - - -def _unpack(free_parameters: Tuple[Prior, ...]): - """ - Unpack free parameters from a tuple of free parameters and priors. - - Parameters - ---------- - free_parameters - A tuple of free parameters and priors. - - Returns - ------- - A list of free parameters. - """ - return [ - parameter for parameter in free_parameters if isinstance(parameter, Prior) - ] + [ - prior - for parameter in free_parameters - if isinstance(parameter, (AbstractPriorModel, TuplePrior)) - for prior in parameter.priors - ] - - -class PositionalParameters: - def __init__( - self, - analysis: "FreeParameterAnalysis", - position: int, - ): - """ - Manage overriding positional parameters for a given analysis. - - Parameters - ---------- - analysis - The analysis - position - The position of the model in the collection - """ - self.analysis = analysis - self.position = position - - def __setitem__(self, key, value): - """ - Override some component of the model at the index. - - Parameters - ---------- - key - The key of the component (e.g. a prior) - value - The new value - """ - self.analysis.positional_parameters[self.position][key] = value - - -class FreeParameterAnalysis(IndexCollectionAnalysis): - def __init__(self, *analyses: Analysis, free_parameters: Tuple[Prior, ...]): - """ - A combined analysis with free parameters. - - All parameters for the model are shared across every analysis except - for the free parameters which are allowed to vary for individual - analyses. - - Parameters - ---------- - analyses - A list of analyses - free_parameters - A list of priors which are independent for each analysis - """ - super().__init__(*analyses) - self.free_parameters = _unpack(free_parameters) - self.positional_parameters = defaultdict(dict) - - def __getitem__(self, item: int): - """ - Used to override some model component for the model at a given index. - - Parameters - ---------- - item - The index of the model in the collection - - Returns - ------- - A manager for overriding the model components - """ - return PositionalParameters(self, item) - - def modify_model(self, model: AbstractPriorModel) -> AbstractPriorModel: - """ - Create prior models where free parameters are replaced with new - priors. Return those prior models as a collection. - - The number of dimensions of the new prior model is the number of the - old one plus the number of free parameters multiplied by the number - of free parameters. - - Parameters - ---------- - model - The original model - - Returns - ------- - A new model with all the same priors except for those associated - with free parameters. - """ - collection = Collection( - [ - analysis.modify_model( - model.mapper_from_partial_prior_arguments(self._arguments) - ) - for analysis in self.analyses - ] - ) - for i, positional_parameters in self.positional_parameters.items(): - for key, value in positional_parameters.items(): - path = model.path_for_object(key) - if path == (): - collection[i] = value - else: - collection[i].set_item_at_path(path, value) - - return collection - - @property - def _arguments(self): - return { - free_parameter: free_parameter.new() - for free_parameter in self.free_parameters - } - - def modify_before_fit(self, paths: AbstractPaths, model: Collection): - """ - Modify the analysis before fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - """ - return FreeParameterAnalysis( - *( - analysis.modify_before_fit(paths, model_) - for analysis, model_ in zip(self.analyses, model) - ), - free_parameters=tuple(self.free_parameters), - ) - - def modify_after_fit(self, paths: AbstractPaths, model: Collection, result): - """ - Modify the analysis after fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - result - The result of the fit. - """ - return FreeParameterAnalysis( - *( - analysis.modify_after_fit(paths, model, result) - for analysis, model_ in zip(self.analyses, model) - ), - free_parameters=tuple(self.free_parameters), - ) diff --git a/autofit/non_linear/analysis/indexed.py b/autofit/non_linear/analysis/indexed.py deleted file mode 100644 index e1b6b8295..000000000 --- a/autofit/non_linear/analysis/indexed.py +++ /dev/null @@ -1,156 +0,0 @@ -import logging -from typing import Optional - -from .analysis import Analysis -from .combined import CombinedAnalysis, CombinedResult -from ..paths.abstract import AbstractPaths - -from autofit.non_linear.paths.abstract import AbstractPaths -from autofit.non_linear.samples.summary import SamplesSummary -from autofit.mapper.prior_model.collection import Collection -from autofit.non_linear.samples import SamplesPDF - -logger = logging.getLogger(__name__) - - -class IndexedAnalysis: - def __init__(self, analysis: Analysis, index: int): - """ - One instance in a collection corresponds to this analysis. That - instance is identified by its index in the collection. - - Parameters - ---------- - analysis - An analysis that can be applied to an instance in a collection - index - The index of the instance that should be passed to the analysis - """ - if isinstance(analysis, IndexedAnalysis): - analysis = analysis.analysis - self.analysis = analysis - self.index = index - - def log_likelihood_function(self, instance): - """ - Compute the log likelihood by taking the instance at the index - """ - return self.analysis.log_likelihood_function(instance[self.index]) - - # TODO : Add before fit methods here? - - def visualize(self, paths: AbstractPaths, instance, during_analysis): - return self.analysis.visualize(paths, instance[self.index], during_analysis) - - def visualize_combined( - self, analyses, paths: AbstractPaths, instance, during_analysis - ): - return self.analysis.visualize_combined( - analyses, paths, instance[self.index], during_analysis - ) - - def profile_log_likelihood_function(self, paths: AbstractPaths, instance): - return self.profile_log_likelihood_function(paths, instance[self.index]) - - def __getattr__(self, item): - if item in ("__getstate__", "__setstate__"): - raise AttributeError(item) - return getattr(self.analysis, item) - - def make_result( - self, - samples_summary: SamplesSummary, - paths: AbstractPaths, - samples: Optional[SamplesPDF] = None, - search_internal: Optional[object] = None, - analysis: Optional[object] = None, - ): - return self.analysis.make_result( - samples_summary, paths, samples, search_internal, analysis - ) - - -class IndexCollectionAnalysis(CombinedAnalysis): - def __init__(self, *analyses): - """ - Collection of analyses where each analysis has a different - corresponding model. - - Parameters - ---------- - analyses - A list of analyses each with a separate model - """ - super().__init__( - *[ - IndexedAnalysis( - analysis, - index, - ) - for index, analysis in enumerate(analyses) - ] - ) - - def make_result( - self, - samples_summary: SamplesSummary, - paths: AbstractPaths, - samples: Optional[SamplesPDF] = None, - search_internal: Optional[object] = None, - analysis: Optional[object] = None, - ): - """ - Associate each model with an analysis when creating the result. - """ - child_results = [ - analysis.make_result( - samples_summary.subsamples(model) - if samples_summary is not None - else None, - paths, - samples.subsamples(model) if samples is not None else None, - search_internal, - analysis, - ) - for model, analysis in zip(samples_summary.model, self.analyses) - ] - return CombinedResult( - child_results, - samples=samples, - samples_summary=samples_summary, - ) - - def modify_before_fit(self, paths: AbstractPaths, model: Collection): - """ - Modify the analysis before fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - """ - return CombinedAnalysis( - *(analysis.modify_before_fit(paths, model) for analysis in self.analyses) - ) - - def modify_after_fit(self, paths: AbstractPaths, model: Collection, result): - """ - Modify the analysis after fitting. - - Parameters - ---------- - paths - An object describing the paths for saving data (e.g. hard-disk directories or entries in sqlite database). - model - The model which is to be fitted. - result - The result of the fit. - """ - return CombinedAnalysis( - *( - analysis.modify_after_fit(paths, model, result) - for analysis in self.analyses - ) - ) diff --git a/autofit/non_linear/analysis/model_analysis.py b/autofit/non_linear/analysis/model_analysis.py index 90802f5ba..f743297f9 100644 --- a/autofit/non_linear/analysis/model_analysis.py +++ b/autofit/non_linear/analysis/model_analysis.py @@ -1,9 +1,7 @@ from typing import Optional from autofit.mapper.prior_model.abstract import AbstractPriorModel -from autofit.mapper.prior_model.collection import Collection from .analysis import Analysis -from .indexed import IndexCollectionAnalysis from ... import SamplesSummary, AbstractPaths, SamplesPDF @@ -48,28 +46,3 @@ def make_result( ) except TypeError: raise - - -class CombinedModelAnalysis(IndexCollectionAnalysis): - def modify_model(self, model: AbstractPriorModel) -> Collection: - """ - Creates a collection with one model for each analysis. For each ModelAnalysis - the model is used; for other analyses the default model is used. - - Parameters - ---------- - model - A default model - - Returns - ------- - A collection of models, one for each analysis. - """ - return Collection( - [ - analysis.modify_model(analysis.analysis.model) - if isinstance(analysis.analysis, ModelAnalysis) - else analysis.modify_model(model) - for analysis in self.analyses - ] - ) diff --git a/autofit/non_linear/combined_result.py b/autofit/non_linear/combined_result.py new file mode 100644 index 000000000..d5a963f73 --- /dev/null +++ b/autofit/non_linear/combined_result.py @@ -0,0 +1,60 @@ +import logging +from typing import List, Optional + +from autofit.non_linear.paths.abstract import AbstractPaths +from autofit.non_linear.result import Result +from .analysis import Analysis +from autofit.non_linear.samples.summary import SamplesSummary +from autofit.non_linear.samples import SamplesPDF + +logger = logging.getLogger(__name__) + + +class CombinedResult(Result): + def __init__( + self, + results: List[Result], + samples: Optional[SamplesPDF] = None, + samples_summary: Optional[SamplesSummary] = None, + paths: Optional[AbstractPaths] = None, + search_internal: Optional[object] = None, + analysis: Optional[Analysis] = None, + ): + """ + A `Result` object that is composed of multiple `Result` objects. This is used to combine the results of + multiple `Analysis` objects into a single `Result` object, for example when performing a model-fitting + analysis where there are multiple datasets. + + Parameters + ---------- + results + The list of `Result` objects that are combined into this `CombinedResult` object. + """ + super().__init__( + samples_summary=samples_summary, + samples=samples, + paths=paths, + search_internal=search_internal, + analysis=analysis, + ) + self.child_results = results + + def __getattr__(self, item: str): + """ + Get an attribute of the first `Result` object in the list of `Result` objects. + """ + if item in ("__getstate__", "__setstate__"): + raise AttributeError(item) + return getattr(self.child_results[0], item) + + def __iter__(self): + return iter(self.child_results) + + def __len__(self): + return len(self.child_results) + + def __getitem__(self, item: int) -> Result: + """ + Get a `Result` object from the list of `Result` objects. + """ + return self.child_results[item] diff --git a/autofit/non_linear/search/abstract_search.py b/autofit/non_linear/search/abstract_search.py index 44a42e4fa..273725969 100644 --- a/autofit/non_linear/search/abstract_search.py +++ b/autofit/non_linear/search/abstract_search.py @@ -46,8 +46,6 @@ from autofit.non_linear.samples.summary import SamplesSummary from autofit.non_linear.timer import Timer from autofit.non_linear.analysis import Analysis -from autofit.non_linear.analysis.combined import CombinedResult -from autofit.non_linear.analysis.indexed import IndexCollectionAnalysis from autofit.non_linear.paths.null import NullPaths from autofit.graphical.declarative.abstract import PriorFactor from autofit.graphical.expectation_propagation import AbstractFactorOptimiser @@ -468,67 +466,6 @@ def using_mpi(self) -> bool: except ModuleNotFoundError: return False - def fit_sequential( - self, - model: AbstractPriorModel, - analysis: IndexCollectionAnalysis, - info: Optional[Dict] = None, - ) -> CombinedResult: - """ - Fit multiple analyses contained within the analysis sequentially. - - This can be useful for avoiding very high dimensional parameter spaces. - - Parameters - ---------- - analysis - Multiple analyses that are fit sequentially - model - An object that represents possible instances of some model with a - given dimensionality which is the number of free dimensions of the - model. - info - Optional dictionary containing information about the fit that can be loaded by the aggregator. - - Returns - ------- - An object combining the results of each individual optimisation. - - Raises - ------ - AssertionError - If the model has 0 dimensions. - ValueError - If the analysis is not a combined analysis - """ - results = [] - - _paths = self.paths - original_name = self.paths.name or "analysis" - - model = analysis.modify_model(model=model) - - try: - if not isinstance(model, Collection): - model = [model for _ in range(len(analysis.analyses))] - except AttributeError: - raise ValueError( - f"Analysis with type {type(analysis)} is not supported by fit_sequential" - ) - - for i, (model, analysis) in enumerate(zip(model, analysis.analyses)): - self.paths = copy.copy(_paths) - self.paths.name = f"{original_name}/{i}" - results.append( - self.fit( - model=model, - analysis=analysis, - info=info, - ) - ) - self.paths = _paths - return CombinedResult(results) - def fit( self, model: AbstractPriorModel, diff --git a/test_autofit/analysis/test_combined_visualise.py b/test_autofit/analysis/test_combined_visualise.py deleted file mode 100644 index 20ef7999f..000000000 --- a/test_autofit/analysis/test_combined_visualise.py +++ /dev/null @@ -1,78 +0,0 @@ -import pytest - -import autofit as af - - -class Analysis(af.Analysis): - def visualize(self, paths, instance, during_analysis): - assert isinstance(instance, af.Gaussian) - assert during_analysis is True - - paths.output_path.mkdir(parents=True, exist_ok=True) - with open(f"{paths.output_path}/visualize.txt", "w+") as f: - f.write("test") - - def visualize_before_fit(self, paths, model): - assert model.cls is af.Gaussian - - paths.output_path.mkdir(parents=True, exist_ok=True) - with open(f"{paths.output_path}/visualize_before_fit.txt", "w+") as f: - f.write("test") - - -@pytest.fixture(name="analysis") -def make_analysis(): - return Analysis() - - -@pytest.fixture(name="paths") -def make_paths(): - return af.DirectoryPaths() - - -def test_visualize(analysis, paths): - analysis.visualize(paths, af.Gaussian(), True) - - assert (paths.output_path / "visualize.txt").exists() - - -@pytest.fixture(name="combined") -def make_combined(analysis): - combined = analysis + analysis - combined.n_cores = 2 - yield combined - combined._analysis_pool.terminate() - - -@pytest.fixture(name="analyses_path") -def make_analyses_path(paths): - return paths.output_path / "analyses" - - -def test_combined_visualize( - combined, - paths, - analyses_path, -): - combined.visualize( - paths, - af.Gaussian(), - True, - ) - - assert (analyses_path / "analysis_0/visualize.txt").exists() - assert (analyses_path / "analysis_1/visualize.txt").exists() - - -def test_visualize_before_fit( - combined, - paths, - analyses_path, -): - combined.visualize_before_fit( - paths, - af.Model(af.Gaussian), - ) - - assert (analyses_path / "analysis_0/visualize_before_fit.txt").exists() - assert (analyses_path / "analysis_1/visualize_before_fit.txt").exists() diff --git a/test_autofit/analysis/test_free_parameter.py b/test_autofit/analysis/test_free_parameter.py deleted file mode 100644 index ab41ef6fa..000000000 --- a/test_autofit/analysis/test_free_parameter.py +++ /dev/null @@ -1,220 +0,0 @@ -import pytest - -import autofit as af -from autofit.non_linear.analysis import FreeParameterAnalysis -from autofit.non_linear.mock.mock_search import MockMLE - - -def test_copy(): - model = af.Model(af.Gaussian) - copy = model.copy() - - collection = af.Collection(model, copy) - - assert collection.prior_count == model.prior_count - - -def test_log_likelihood(modified, combined_analysis): - assert ( - combined_analysis.log_likelihood_function( - modified.instance_from_prior_medians() - ) - == 2 - ) - - -def test_analyses_example(Analysis): - model = af.Model(af.Gaussian) - analyses = [] - - for prior, image in [ - (af.UniformPrior(), 0), - (af.UniformPrior(), 1), - ]: - copy = model.copy() - copy.centre = prior - analyses.append(Analysis()) - - -@pytest.fixture(name="combined_analysis") -def make_combined_analysis(model, Analysis): - return (Analysis() + Analysis()).with_free_parameters(model.centre) - - -def test_override_specific_free_parameter(model, combined_analysis): - combined_analysis[0][model.centre] = 2 - - new_model = combined_analysis.modify_model(model) - assert new_model[0].centre == 2 - assert new_model[1].centre != 2 - - -def test_override_multiple(model, combined_analysis): - combined_analysis[0][model.centre] = 2 - combined_analysis[1][model.centre] = 3 - - new_model = combined_analysis.modify_model(model) - assert new_model[0].centre == 2 - assert new_model[1].centre == 3 - - -def test_override_multiple_one_analysis(model, combined_analysis): - combined_analysis[0][model.centre] = 2 - combined_analysis[0][model.sigma] = 3 - - new_model = combined_analysis.modify_model(model) - assert new_model[0].centre == 2 - assert new_model[0].sigma == 3 - - -def test_complex_path(Analysis): - model = af.Collection( - collection=af.Collection( - gaussian=af.Model(af.Gaussian), - ) - ) - combined_analysis = (Analysis() + Analysis()).with_free_parameters( - model.collection.gaussian - ) - - combined_analysis[0][model.collection.gaussian.centre] = 2 - combined_analysis[0][model.collection.gaussian.sigma] = 3 - - new_model = combined_analysis.modify_model(model) - assert new_model[0].collection.gaussian.centre == 2 - assert new_model[0].collection.gaussian.sigma == 3 - - -def test_tuple_prior_override(Analysis): - model = af.Collection( - model=af.Model( - af.mock.MockChildTuple, - ) - ) - - combined_analysis = (Analysis() + Analysis()).with_free_parameters( - model.model.tup, - ) - - first = af.UniformPrior(lower_limit=0.0, upper_limit=1.0) - second = af.UniformPrior(lower_limit=0.0, upper_limit=1.0) - - combined_analysis[0][model.model.tup.tup_0] = first - combined_analysis[0][model.model.tup.tup_1] = second - - new_model = combined_analysis.modify_model(model) - assert new_model[0].model.tup.tup_0 is first - assert new_model[0].model.tup.tup_1 is second - - -def test_override_model(model, combined_analysis): - new_model = af.Model(af.Gaussian, centre=2) - combined_analysis[0][model] = new_model - - new_model = combined_analysis.modify_model(model) - assert new_model[0].centre == 2 - assert new_model[1].centre != 2 - - -def test_override_child_model(Analysis): - model = af.Collection(gaussian=af.Gaussian) - combined_analysis = (Analysis() + Analysis()).with_free_parameters( - model.gaussian.centre - ) - combined_analysis[0][model.gaussian] = af.Model(af.Gaussian, centre=2) - - new_model = combined_analysis.modify_model(model) - assert new_model[0].gaussian.centre == 2 - - -def test_multiple_free_parameters(model, Analysis): - combined_analysis = (Analysis() + Analysis()).with_free_parameters( - model.centre, model.sigma - ) - first, second = combined_analysis.modify_model(model) - assert first.centre is not second.centre - assert first.sigma is not second.sigma - - -def test_free_parameters_for_constants(combined_analysis): - model = af.Model(af.Gaussian, centre=1.0, sigma=1.0) - - combined_analysis[0][model.centre] = 2 - combined_analysis[0][model.sigma] = 3 - - new_model = combined_analysis.modify_model(model) - - assert new_model[0].centre == 2 - assert new_model[0].sigma == 3 - - -def test_add_free_parameter(combined_analysis): - assert isinstance(combined_analysis, FreeParameterAnalysis) - - -@pytest.fixture(name="modified") -def make_modified(model, combined_analysis): - return combined_analysis.modify_model(model) - - -def test_modify_model(modified): - assert isinstance(modified, af.Collection) - assert len(modified) == 2 - - -def test_modified_models(modified): - first, second = modified - - assert isinstance(first.sigma, af.Prior) - assert first.sigma == second.sigma - assert first.centre != second.centre - - -@pytest.fixture(name="result") -def make_result( - combined_analysis, - model, -): - search = MockMLE() - return search.fit(model, combined_analysis) - - -@pytest.fixture(autouse=True) -def do_remove_output(remove_output): - yield - remove_output() - - -def test_tuple_prior(model, Analysis): - model.centre = af.TuplePrior(centre_0=af.UniformPrior()) - combined = (Analysis() + Analysis()).with_free_parameters(model.centre) - - first, second = combined.modify_model(model) - assert first.centre.centre_0 != second.centre.centre_0 - - -def test_prior_model(model, Analysis): - model = af.Collection(model=model) - combined = (Analysis() + Analysis()).with_free_parameters(model.model) - modified = combined.modify_model(model) - first = modified[0].model - second = modified[1].model - - assert first is not second - assert first != second - assert first.centre != second.centre - - -def test_split_samples(modified): - samples = af.Samples( - modified, - af.Sample.from_lists(modified, [[1, 2, 3, 4]], [1], [1], [1]), - ) - - combined = samples.max_log_likelihood() - - first = samples.subsamples(modified[0]) - second = samples.subsamples(modified[1]) - - assert first.max_log_likelihood().centre == combined[0].centre - assert second.max_log_likelihood().centre == combined[1].centre diff --git a/test_autofit/analysis/test_regression.py b/test_autofit/analysis/test_regression.py index 0236ffe08..35bef73bb 100644 --- a/test_autofit/analysis/test_regression.py +++ b/test_autofit/analysis/test_regression.py @@ -1,15 +1,4 @@ -import pickle - -import pytest - import autofit as af -from autofit.non_linear.analysis import CombinedAnalysis - - -def test_pickle(Analysis): - analysis = Analysis() + Analysis() - loaded = pickle.loads(pickle.dumps(analysis)) - assert isinstance(loaded, CombinedAnalysis) class MyResult(af.mock.MockResult): @@ -51,27 +40,3 @@ def test_result_type(): result = analysis.make_result(None, None) assert isinstance(result, MyResult) - - -@pytest.fixture(name="combined_analysis") -def make_combined_analysis(): - return MyAnalysis() + MyAnalysis() - - -@pytest.fixture(name="paths") -def make_paths(): - return af.DirectoryPaths() - - -def test_combined_before_fit(combined_analysis, paths): - combined_analysis = combined_analysis.modify_before_fit(paths, [None]) - - assert combined_analysis[0].is_modified_before - - -def test_combined_after_fit(combined_analysis, paths): - result = combined_analysis.make_result(None, None) - - combined_analysis = combined_analysis.modify_after_fit(paths, [None], result) - - assert combined_analysis[0].is_modified_after diff --git a/test_autofit/analysis/test_relation.py b/test_autofit/analysis/test_relation.py deleted file mode 100644 index 189a5e8b4..000000000 --- a/test_autofit/analysis/test_relation.py +++ /dev/null @@ -1,175 +0,0 @@ -import pickle - -import pytest - -import autofit as af -from autofit.mapper.prior.arithmetic.compound import SumPrior -from autofit.non_linear.analysis.indexed import IndexedAnalysis -from autofit.non_linear.analysis.model_analysis import ( - CombinedModelAnalysis, - ModelAnalysis, -) - - -def test_pickle_indexed_analysis(): - analysis = IndexedAnalysis(LinearAnalysis(1), 0) - pickle.loads(pickle.dumps(analysis)) - - -@pytest.fixture(name="model_analysis") -def make_model_analysis(Analysis, model): - return Analysis().with_model(model) - - -def test_analysis_model(model_analysis, model): - assert model_analysis.modify_model(model) is model - - -@pytest.fixture(name="combined_model_analysis") -def make_combined_model_analysis(model_analysis): - return model_analysis + model_analysis - - -def test_combined_model_analysis(combined_model_analysis): - assert isinstance(combined_model_analysis, CombinedModelAnalysis) - - for analysis in combined_model_analysis.analyses: - assert isinstance(analysis, IndexedAnalysis) - assert isinstance(analysis.analysis, ModelAnalysis) - - -def test_sum(model): - analyses = 3 * [af.Analysis().with_model(model)] - analysis = sum(analyses) - - for analysis_ in analysis.analyses: - assert isinstance(analysis_, IndexedAnalysis) - assert isinstance(analysis_.analysis, ModelAnalysis) - - -def test_modify(combined_model_analysis, model): - modified = combined_model_analysis.modify_model(model) - first, second = modified - assert first is model - assert second is model - - -def test_default(combined_model_analysis, Analysis, model): - analysis = combined_model_analysis + Analysis() - assert isinstance(analysis, CombinedModelAnalysis) - modified = analysis.modify_model(model) - - first, second, third = modified - assert first is model - assert second is model - assert third is model - - -def test_fit(combined_model_analysis, model): - assert ( - combined_model_analysis.log_likelihood_function( - af.Collection([model, model]).instance_from_prior_medians() - ) - == 2 - ) - - -def test_prior_arithmetic(): - m = af.UniformPrior() - c = af.UniformPrior() - - y = m * 10 + c - - assert y.prior_count == 2 - assert y.instance_from_prior_medians() == 5.5 - - -class LinearAnalysis(af.Analysis): - def __init__(self, value): - self.value = value - - def log_likelihood_function(self, instance): - return -abs(self.value - instance) - - -class ComplexLinearAnalysis(LinearAnalysis): - def log_likelihood_function(self, instance): - return super().log_likelihood_function(instance.centre) - - -def test_embedded_model(): - model = af.Model(af.Gaussian) - copy = model.replacing({model.centre: af.UniformPrior()}) - assert copy is not model - assert copy.centre != model.centre - assert copy.sigma == model.sigma - - -def test_names(): - one = 1 - two = af.UniformPrior() - assert (one + two).paths == [("two",)] - assert af.Add(one, two).paths == [("two",)] - - -def data(x): - return 3 * x + 5 - - -@pytest.fixture(name="m") -def make_m(): - return af.GaussianPrior(mean=3, sigma=1) - - -@pytest.fixture(name="c") -def make_c(): - return af.GaussianPrior(mean=5, sigma=1) - - -def test_multiple_models(m, c): - models = [x * m + c for x in (1, 2, 3)] - - one, two, three = [model.instance_from_prior_medians() for model in models] - assert one < two < three - - -def _test_embedded_integration(m, c): - base_model = af.Model(af.Gaussian) - - analyses = [ - ComplexLinearAnalysis(data(x)).with_model( - base_model.replacing({base_model.centre: m * x + c}) - ) - for x in range(10) - ] - - combined = sum(analyses) - - optimiser = af.DynestyStatic() - result = optimiser.fit(None, combined) - - centres = [result.instance.centre for result in result.child_results] - assert centres == pytest.approx( - list(map(data, range(10))), - rel=0.01, - ) - - -def _test_integration(m, c): - analyses = [LinearAnalysis(data(x)).with_model(m * x + c) for x in range(10)] - - combined = sum(analyses) - - optimiser = af.DynestyStatic() - result = optimiser.fit(None, combined) - - instances = [result.instance for result in result.child_results] - assert instances == pytest.approx( - list(map(data, range(10))), - rel=0.01, - ) - - -def test_remove(model): - model = model.replacing({model.centre: None}) - assert model.centre is None diff --git a/test_autofit/non_linear/test_fit_sequential.py b/test_autofit/non_linear/test_fit_sequential.py deleted file mode 100644 index 1e940b003..000000000 --- a/test_autofit/non_linear/test_fit_sequential.py +++ /dev/null @@ -1,79 +0,0 @@ -import os -import warnings -from pathlib import Path -from random import random - -import pytest - -import autofit as af -from autofit.non_linear.analysis.combined import CombinedResult - - -class Analysis(af.Analysis): - def log_likelihood_function(self, instance): - return -random() - - -@pytest.fixture(name="search") -def make_search(): - return af.LBFGS(name="test_lbfgs") - - -@pytest.fixture(name="model") -def make_model(): - return af.Model(af.Gaussian) - - -@pytest.fixture(name="analysis") -def make_analysis(): - return Analysis() - - -def count_output(paths): - return len(os.listdir(Path(str(paths.output_path)).parent)) - - -def test_with_model(analysis, model, search): - combined_analysis = sum([analysis.with_model(model) for _ in range(10)]) - - with warnings.catch_warnings(): - warnings.simplefilter("ignore") - result = search.fit_sequential(model=model, analysis=combined_analysis) - - assert count_output(search.paths) == 10 - assert len(result.child_results) == 10 - - -@pytest.fixture(name="combined_analysis") -def make_combined_analysis(analysis): - return sum([analysis for _ in range(10)]) - - -def test_combined_analysis(combined_analysis, model, search): - search.fit_sequential(model=model, analysis=combined_analysis) - - assert count_output(search.paths) == 10 - - -def test_with_free_parameter(combined_analysis, model, search): - combined_analysis = combined_analysis.with_free_parameters([model.centre]) - search.fit_sequential( - model=model, - analysis=combined_analysis, - ) - - assert count_output(search.paths) == 10 - - -def test_singular_analysis(analysis, model, search): - with pytest.raises(ValueError): - search.fit_sequential(model=model, analysis=analysis) - - -# noinspection PyTypeChecker -def test_index_combined_result(): - combined_result = CombinedResult([0, 1, 2]) - - assert combined_result[0] == 0 - assert combined_result[1] == 1 - assert combined_result[2] == 2 From 6cddf9ec2fbe151a31f7de9e8f011e3e90bc4b10 Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 2 May 2025 15:59:12 +0100 Subject: [PATCH 2/3] set maxcall to speed up test --- test_autofit/jax/test_jit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test_autofit/jax/test_jit.py b/test_autofit/jax/test_jit.py index d99ee10ca..d405fda91 100644 --- a/test_autofit/jax/test_jit.py +++ b/test_autofit/jax/test_jit.py @@ -55,6 +55,7 @@ def test_jit_dynesty_static( search = af.DynestyStatic( use_gradient=True, number_of_cores=1, + maxcall=1, ) print(search.fit(model=model, analysis=analysis)) From b548b93a88cfd4cc536d6aa04262c4db8fa0dd7b Mon Sep 17 00:00:00 2001 From: Richard Date: Fri, 2 May 2025 16:00:26 +0100 Subject: [PATCH 3/3] remove tests --- test_autofit/non_linear/test_analysis.py | 200 ----------------------- 1 file changed, 200 deletions(-) delete mode 100644 test_autofit/non_linear/test_analysis.py diff --git a/test_autofit/non_linear/test_analysis.py b/test_autofit/non_linear/test_analysis.py deleted file mode 100644 index dbfcd7109..000000000 --- a/test_autofit/non_linear/test_analysis.py +++ /dev/null @@ -1,200 +0,0 @@ -import os -from pathlib import Path - -import pytest - -import autofit as af -from autoconf.conf import with_config -from autofit.non_linear.analysis.multiprocessing import AnalysisPool -from autofit.non_linear.paths.abstract import AbstractPaths -from autofit.non_linear.paths.sub_directory_paths import SubDirectoryPaths - - -class Analysis(af.Analysis): - def __init__(self): - self.did_visualise = False - self.did_visualise_combined = False - self.did_profile = False - - def log_likelihood_function(self, instance): - return -1 - - def visualize_before_fit(self, paths: AbstractPaths, model): - self.did_visualise = True - os.makedirs(paths.image_path, exist_ok=True) - open(f"{paths.image_path}/image.png", "w+").close() - - def visualize(self, paths: AbstractPaths, instance, during_analysis): - self.did_visualise = True - os.makedirs(paths.image_path, exist_ok=True) - open(f"{paths.image_path}/image.png", "w+").close() - - def visualize_before_fit_combined(self, analyses, paths, model): - self.did_visualise_combined = True - - def visualize_combined( - self, analyses, paths: AbstractPaths, instance, during_analysis - ): - self.did_visualise_combined = True - - def profile_log_likelihood_function(self, paths: AbstractPaths, instance): - self.did_profile = True - - -def test_visualise_before_fit(): - analysis_1 = Analysis() - analysis_2 = Analysis() - - (analysis_1 + analysis_2).visualize_before_fit(af.DirectoryPaths(), None) - - assert analysis_1.did_visualise is True - assert analysis_2.did_visualise is True - - -def test_visualise(): - analysis_1 = Analysis() - analysis_2 = Analysis() - - (analysis_1 + analysis_2).visualize(af.DirectoryPaths(), None, None) - - assert analysis_1.did_visualise is True - assert analysis_2.did_visualise is True - - -# def test_visualise_before_fit_combined(): -# analysis_1 = Analysis() -# analysis_2 = Analysis() -# -# (analysis_1 + analysis_2).visualize_before_fit_combined( -# None, af.DirectoryPaths(), None -# ) -# -# assert analysis_1.did_visualise_combined is True -# assert analysis_2.did_visualise_combined is False -# -# -# def test_visualise_combined(): -# analysis_1 = Analysis() -# analysis_2 = Analysis() -# -# (analysis_1 + analysis_2).visualize_combined(None, af.DirectoryPaths(), None, None) -# -# assert analysis_1.did_visualise_combined is True -# assert analysis_2.did_visualise_combined is False - - -def test__profile_log_likelihood(): - analysis_1 = Analysis() - analysis_2 = Analysis() - - (analysis_1 + analysis_2).profile_log_likelihood_function( - af.DirectoryPaths(), - None, - ) - - assert analysis_1.did_profile is True - assert analysis_2.did_profile is True - - -def test_make_result(): - analysis_1 = Analysis() - analysis_2 = Analysis() - - result = (analysis_1 + analysis_2).make_result( - samples_summary=None, - paths=None, - samples=None, - ) - - assert len(result) == 2 - - -def test_add_analysis(): - assert (Analysis() + Analysis()).log_likelihood_function(None) == -2 - - -@pytest.mark.parametrize( - "number, first, second", - [ - (3, 2, 1), - (4, 2, 2), - (5, 3, 2), - (6, 3, 3), - (7, 4, 3), - ], -) -def test_analysis_pool(number, first, second): - pool = AnalysisPool(number * [Analysis()], 2) - - process_1, process_2 = pool.processes - - assert len(process_1.analyses) == first - assert len(process_2.analyses) == second - - -@with_config("general", "analysis", "n_cores", value=2) -@pytest.mark.parametrize("number", list(range(1, 3))) -def test_two_cores(number): - analysis = Analysis() - for _ in range(number - 1): - analysis += Analysis() - assert analysis.log_likelihood_function(None) == -number - - -def test_still_flat(): - analysis = (Analysis() + Analysis()) + Analysis() - - assert len(analysis) == 3 - - analysis = Analysis() + (Analysis() + Analysis()) - - assert len(analysis) == 3 - - -def test_sum_analyses(): - combined = sum(Analysis() for _ in range(5)) - assert len(combined) == 5 - - -@pytest.fixture(name="search") -def make_search(): - return af.m.MockSearch("search_name") - - -def test_child_paths(search): - paths = search.paths - sub_paths = SubDirectoryPaths(paths, analysis_name="analysis_0") - assert sub_paths.output_path == paths.output_path / "analysis_0" - - -@pytest.fixture(name="multi_analysis") -def make_multi_analysis(): - return Analysis() + Analysis() - - -@pytest.fixture(name="multi_search") -def make_multi_search(search, multi_analysis): - search.paths.remove_files = False - - search.fit(af.Model(af.Gaussian), multi_analysis) - search.paths.save_all({}, {}) - return search - - -@with_config("general", "output", "remove_files", value=False) -def test_visualise(multi_search, multi_analysis): - multi_analysis.visualize(multi_search.paths, af.Gaussian(), True) - search_path = Path(multi_search.paths.output_path) - assert search_path.exists() - assert (search_path / "analyses/analysis_0/image/image.png").exists() - assert (search_path / "analyses/analysis_1/image/image.png").exists() - - -def test_set_number_of_cores(multi_analysis): - multi_analysis.n_cores = 1 - assert multi_analysis._log_likelihood_function.__name__ == "_summed_log_likelihood" - - multi_analysis.n_cores = 2 - assert isinstance(multi_analysis._log_likelihood_function, AnalysisPool) - multi_analysis.n_cores = 1 - assert multi_analysis._log_likelihood_function.__name__ == "_summed_log_likelihood"