From 32cd8566f41bf4f50dfa274f35063fd446dd1137 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 15:57:24 +0000 Subject: [PATCH 1/7] adopt make result from combined analysis --- autofit/graphical/declarative/collection.py | 86 +++++++++++---------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index 9bad8fb29..185afc7b2 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -1,20 +1,21 @@ -from typing import Union +from typing import Union, Optional from autofit.graphical.declarative.factor.hierarchical import HierarchicalFactor from autofit.mapper.model import ModelInstance from autofit.tools.namer import namer from .abstract import AbstractDeclarativeFactor +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 class FactorGraphModel(AbstractDeclarativeFactor): def __init__( - self, - *model_factors: Union[ - AbstractDeclarativeFactor, - HierarchicalFactor - ], - name=None, - include_prior_factors=True, + self, + *model_factors: Union[AbstractDeclarativeFactor, HierarchicalFactor], + name=None, + include_prior_factors=True, ): """ A collection of factors that describe models, which can be @@ -40,11 +41,10 @@ def prior_model(self): in each model factor """ from autofit.mapper.prior_model.collection import Collection - return Collection({ - factor.name: factor.prior_model - for factor - in self.model_factors - }) + + return Collection( + {factor.name: factor.prior_model for factor in self.model_factors} + ) @property def optimiser(self): @@ -61,21 +61,13 @@ def info(self) -> str: def name(self): return self._name - def add( - self, - model_factor: AbstractDeclarativeFactor - ): + def add(self, model_factor: AbstractDeclarativeFactor): """ Add another factor to this collection. """ - self._model_factors.append( - model_factor - ) + self._model_factors.append(model_factor) - def log_likelihood_function( - self, - instance: ModelInstance - ) -> float: + def log_likelihood_function(self, instance: ModelInstance) -> float: """ Compute the combined likelihood of each factor from a collection of instances with the same ordering as the factors. @@ -90,13 +82,8 @@ def log_likelihood_function( The combined likelihood of all factors """ log_likelihood = 0 - for model_factor, instance_ in zip( - self.model_factors, - instance - ): - log_likelihood += model_factor.log_likelihood_function( - instance_ - ) + for model_factor, instance_ in zip(self.model_factors, instance): + log_likelihood += model_factor.log_likelihood_function(instance_) return log_likelihood @@ -104,15 +91,32 @@ def log_likelihood_function( def model_factors(self): model_factors = list() for model_factor in self._model_factors: - if isinstance( - model_factor, - HierarchicalFactor - ): - model_factors.extend( - model_factor.factors - ) + if isinstance(model_factor, HierarchicalFactor): + model_factors.extend(model_factor.factors) else: - model_factors.append( - model_factor - ) + model_factors.append(model_factor) return model_factors + + def make_result( + self, + samples_summary: SamplesSummary, + paths: AbstractPaths, + samples: Optional[SamplesPDF] = None, + search_internal: Optional[object] = None, + analysis: Optional[object] = None, + ) -> CombinedResult: + child_results = [ + model_factor.analysis.make_result( + samples_summary=samples_summary, + paths=paths, + samples=samples, + search_internal=search_internal, + analysis=analysis, + ) + for model_factor in self.model_factors + ] + return CombinedResult( + child_results, + samples, + samples_summary, + ) From 70ab11915697084cd845b6e6aefc5c378eaffca2 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:07:15 +0000 Subject: [PATCH 2/7] added test... --- .../graphical/test_combined_analysis.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 test_autofit/graphical/test_combined_analysis.py diff --git a/test_autofit/graphical/test_combined_analysis.py b/test_autofit/graphical/test_combined_analysis.py new file mode 100644 index 000000000..1be98ef96 --- /dev/null +++ b/test_autofit/graphical/test_combined_analysis.py @@ -0,0 +1,20 @@ +import autofit as af +from autofit.non_linear.paths.null import NullPaths + + +def test_make_result(): + model = af.Model(af.Gaussian) + factor_graph_model = af.FactorGraphModel( + af.AnalysisFactor( + model, + af.Analysis(), + ) + ) + result = factor_graph_model.make_result( + samples_summary=af.SamplesSummary( + max_log_likelihood_sample=af.Sample(0, 0, 0), + model=af.Collection(model), + ), + paths=NullPaths(), + ) + assert len(result.child_results) == 1 From e1c5b633444f0bd709ada0e33f916549e07b8019 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:16:57 +0000 Subject: [PATCH 3/7] subsampling --- autofit/graphical/declarative/collection.py | 2 +- test_autofit/graphical/test_combined_analysis.py | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index 185afc7b2..8d72db739 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -107,7 +107,7 @@ def make_result( ) -> CombinedResult: child_results = [ model_factor.analysis.make_result( - samples_summary=samples_summary, + samples_summary=samples_summary.subsamples(model_factor.prior_model), paths=paths, samples=samples, search_internal=search_internal, diff --git a/test_autofit/graphical/test_combined_analysis.py b/test_autofit/graphical/test_combined_analysis.py index 1be98ef96..bca5bb301 100644 --- a/test_autofit/graphical/test_combined_analysis.py +++ b/test_autofit/graphical/test_combined_analysis.py @@ -12,7 +12,16 @@ def test_make_result(): ) result = factor_graph_model.make_result( samples_summary=af.SamplesSummary( - max_log_likelihood_sample=af.Sample(0, 0, 0), + max_log_likelihood_sample=af.Sample( + 0, + 0, + 0, + kwargs={ + ("0", "centre"): 1.0, + ("0", "normalization"): 1.0, + ("0", "sigma"): 1.0, + }, + ), model=af.Collection(model), ), paths=NullPaths(), From 0787ed8a25f7a65af1ab3f649d670f7f681a7791 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:22:38 +0000 Subject: [PATCH 4/7] inherit from normal result to provide defaults --- autofit/graphical/declarative/collection.py | 4 +++- autofit/non_linear/analysis/combined.py | 10 ++++++---- test_autofit/graphical/test_combined_analysis.py | 1 + 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index 8d72db739..0a47df0c1 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -107,7 +107,9 @@ def make_result( ) -> CombinedResult: child_results = [ model_factor.analysis.make_result( - samples_summary=samples_summary.subsamples(model_factor.prior_model), + samples_summary=samples_summary.subsamples( + model_factor.prior_model, + ), paths=paths, samples=samples, search_internal=search_internal, diff --git a/autofit/non_linear/analysis/combined.py b/autofit/non_linear/analysis/combined.py index 0717c7fdb..35fbd439a 100644 --- a/autofit/non_linear/analysis/combined.py +++ b/autofit/non_linear/analysis/combined.py @@ -15,7 +15,7 @@ logger = logging.getLogger(__name__) -class CombinedResult: +class CombinedResult(Result): def __init__( self, results: List[Result], @@ -32,9 +32,11 @@ def __init__( results The list of `Result` objects that are combined into this `CombinedResult` object. """ + super().__init__( + samples_summary=samples_summary, + samples=samples, + ) self.child_results = results - self.samples = samples - self.samples_summary = samples_summary def __getattr__(self, item: str): """ @@ -415,4 +417,4 @@ def with_free_parameters( return FreeParameterAnalysis(*self.analyses, free_parameters=free_parameters) def compute_latent_samples(self, samples): - return self.analyses[0].compute_latent_samples(samples=samples) \ No newline at end of file + return self.analyses[0].compute_latent_samples(samples=samples) diff --git a/test_autofit/graphical/test_combined_analysis.py b/test_autofit/graphical/test_combined_analysis.py index bca5bb301..de10fccb1 100644 --- a/test_autofit/graphical/test_combined_analysis.py +++ b/test_autofit/graphical/test_combined_analysis.py @@ -27,3 +27,4 @@ def test_make_result(): paths=NullPaths(), ) assert len(result.child_results) == 1 + assert isinstance(result.model, af.Collection) From 3616ac910039f4194b0266e0769dbe1e32272711 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:23:55 +0000 Subject: [PATCH 5/7] extra assertion --- test_autofit/graphical/test_combined_analysis.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test_autofit/graphical/test_combined_analysis.py b/test_autofit/graphical/test_combined_analysis.py index de10fccb1..b19fa8b21 100644 --- a/test_autofit/graphical/test_combined_analysis.py +++ b/test_autofit/graphical/test_combined_analysis.py @@ -28,3 +28,6 @@ def test_make_result(): ) assert len(result.child_results) == 1 assert isinstance(result.model, af.Collection) + + (child_result,) = result.child_results + assert child_result.model == model From f8fcd1268e9a39fa65a9c9c8bf95a43df4328568 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:27:34 +0000 Subject: [PATCH 6/7] docs and fixes --- autofit/graphical/declarative/collection.py | 27 +++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index 0a47df0c1..b90fcbfa0 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -105,15 +105,38 @@ def make_result( search_internal: Optional[object] = None, analysis: Optional[object] = None, ) -> CombinedResult: + """ + Make a result from the samples summary and paths. + + The top level result accounts for the combined model. + There is one child result for each model factor. + + Parameters + ---------- + samples_summary + A summary of the samples + paths + Handles saving and loading data + samples + The full list of samples + search_internal + analysis + + Returns + ------- + + """ child_results = [ model_factor.analysis.make_result( samples_summary=samples_summary.subsamples( model_factor.prior_model, ), paths=paths, - samples=samples, + samples=samples.subsamples(model_factor.prior_model) + if samples + else None, search_internal=search_internal, - analysis=analysis, + analysis=model_factor, ) for model_factor in self.model_factors ] From c9a884c191787f426534aeb7c62a414a52681c96 Mon Sep 17 00:00:00 2001 From: Richard Date: Wed, 5 Mar 2025 16:33:36 +0000 Subject: [PATCH 7/7] pass more arguments --- autofit/graphical/declarative/collection.py | 9 ++++++--- autofit/non_linear/analysis/combined.py | 6 ++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/autofit/graphical/declarative/collection.py b/autofit/graphical/declarative/collection.py index b90fcbfa0..341e51c24 100644 --- a/autofit/graphical/declarative/collection.py +++ b/autofit/graphical/declarative/collection.py @@ -124,7 +124,7 @@ def make_result( Returns ------- - + A result with child results for each model factor """ child_results = [ model_factor.analysis.make_result( @@ -142,6 +142,9 @@ def make_result( ] return CombinedResult( child_results, - samples, - samples_summary, + samples_summary=samples_summary, + paths=paths, + samples=samples, + search_internal=search_internal, + analysis=analysis, ) diff --git a/autofit/non_linear/analysis/combined.py b/autofit/non_linear/analysis/combined.py index 35fbd439a..9fd6fd778 100644 --- a/autofit/non_linear/analysis/combined.py +++ b/autofit/non_linear/analysis/combined.py @@ -21,6 +21,9 @@ def __init__( 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 @@ -35,6 +38,9 @@ def __init__( super().__init__( samples_summary=samples_summary, samples=samples, + paths=paths, + search_internal=search_internal, + analysis=analysis, ) self.child_results = results