Repository: rhayes777/PyAutoFit (python)

Issue:
instance missing from combined analysis result
The following script is a typical multi-dataset fit from **PyAutoGalaxy**:

https://github.com/Jammy2211/autogalaxy_workspace/blob/release/scripts/multi/modeling/start_here.py

After the fit is complete the following error is raised:

```
Traceback (most recent call last):
  File "/mnt/c/Users/Jammy/Code/PyAuto/autogalaxy_workspace/scripts/multi/modeling/start_here.py", line 177, in <module>
    result_list = search.fit(model=model, analysis=analysis)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 554, in fit
    analysis = analysis.modify_after_fit(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/analysis/free_parameter.py", line 109, in modify_after_fit
    return FreeParameterAnalysis(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/analysis/free_parameter.py", line 111, in <genexpr>
    analysis.modify_after_fit(paths, model, result)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoGalaxy/autogalaxy/analysis/analysis.py", line 381, in modify_after_fit
    self.output_or_check_figure_of_merit_sanity(paths=paths, result=result)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoGalaxy/autogalaxy/analysis/analysis.py", line 603, in output_or_check_figure_of_merit_sanity
    figure_of_merit = result.max_log_likelihood_fit.figure_of_merit
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/analysis/combined.py", line 36, in __getattr__
    return getattr(self.child_results[0], item)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoGalaxy/autogalaxy/imaging/model/result.py", line 53, in max_log_likelihood_fit
    plane = self.analysis.plane_via_instance_from(instance=instance)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoGalaxy/autogalaxy/analysis/analysis.py", line 68, in plane_via_instance_from
    return Plane(galaxies=instance.galaxies, run_time_dict=run_time_dict)
AttributeError: 'ModelInstance' object has no attribute 'galaxies'
```

Probably a simple fix, the script used to work!

Pull request description:
feature/combined result fix
Resolves #834

May require scripts to be started fresh as it changes headers in samples.csv to use . instead of _ path separation


Patch:
diff --git a/autofit/mapper/prior_model/abstract.py b/autofit/mapper/prior_model/abstract.py
index c5383aae7..4c1974cae 100644
--- a/autofit/mapper/prior_model/abstract.py
+++ b/autofit/mapper/prior_model/abstract.py
@@ -1659,7 +1659,7 @@ class AbstractPriorModel(AbstractModel):
         """
         path_modifier = TuplePathModifier(self)
         return [
-            (tuple("_".join(path_modifier(path)) for path in paths), prior)
+            (tuple(".".join(path_modifier(path)) for path in paths), prior)
             for paths, prior in self.all_paths_prior_tuples
         ]
 
@@ -1675,7 +1675,7 @@ class AbstractPriorModel(AbstractModel):
 
         prior_paths = list(map(tuple_filter, prior_paths))
 
-        return ["_".join(path) for path in prior_paths]
+        return [".".join(path) for path in prior_paths]
 
     @property
     def parameter_names(self) -> List[str]:
diff --git a/autofit/non_linear/analysis/indexed.py b/autofit/non_linear/analysis/indexed.py
index 93a9f47c6..0ef39f256 100644
--- a/autofit/non_linear/analysis/indexed.py
+++ b/autofit/non_linear/analysis/indexed.py
@@ -85,12 +85,8 @@ class IndexCollectionAnalysis(CombinedAnalysis):
         Associate each model with an analysis when creating the result.
         """
         child_results = [
-            analysis.make_result(
-                samples.subsamples(analysis.model)
-                if hasattr(analysis, "model")
-                else samples
-            )
-            for analysis in self.analyses
+            analysis.make_result(samples.subsamples(model))
+            for model, analysis in zip(samples.model, self.analyses)
         ]
         return CombinedResult(child_results)
 


Test patch:
diff --git a/test_autofit/mapper/model/test_model_mapper.py b/test_autofit/mapper/model/test_model_mapper.py
index 7fd017c43..7fb6c795f 100644
--- a/test_autofit/mapper/model/test_model_mapper.py
+++ b/test_autofit/mapper/model/test_model_mapper.py
@@ -61,12 +61,12 @@ class TestRegression:
         mm.one.one.id = mm.two.three.id + 1
 
         assert mm.model_component_and_parameter_names == [
-            "one_two",
-            "one_three",
-            "two_one",
-            "two_two",
-            "two_three",
-            "one_one",
+            "one.two",
+            "one.three",
+            "two.one",
+            "two.two",
+            "two.three",
+            "one.one",
         ]
 
     def test_parameter_name_list(self):
@@ -85,12 +85,12 @@ class TestRegression:
             ]
         )
         assert mm.model_component_and_parameter_names == [
-            "ls_0_one",
-            "ls_0_two",
-            "ls_0_three",
-            "ls_1_one",
-            "ls_1_two",
-            "ls_1_three",
+            "ls.0.one",
+            "ls.0.two",
+            "ls.0.three",
+            "ls.1.one",
+            "ls.1.two",
+            "ls.1.three",
         ]
 
     def test__parameter_labels(self):
@@ -111,11 +111,11 @@ class TestRegression:
         mm.one = af.m.MockClassRelativeWidth
         mm.two = af.m.MockClassx2NoSuperScript
 
-        assert mm.superscripts == ['r', 'r', 'r', 'two', 'two']
+        assert mm.superscripts == ["r", "r", "r", "two", "two"]
 
         model = af.Collection(group=mm)
 
-        assert model.superscripts == ['r', 'r', 'r', "two", "two"]
+        assert model.superscripts == ["r", "r", "r", "two", "two"]
 
     def test__superscript_overwrite_via_config(self):
         mm = af.ModelMapper()
@@ -123,7 +123,16 @@ class TestRegression:
         mm.two = af.m.MockClassx2NoSuperScript
         mm.three = af.m.MockClassx3
 
-        assert mm.superscripts_overwrite_via_config == ['r', 'r', 'r', "", "", "", "", ""]
+        assert mm.superscripts_overwrite_via_config == [
+            "r",
+            "r",
+            "r",
+            "",
+            "",
+            "",
+            "",
+            "",
+        ]
 
     def test__parameter_labels_with_superscripts_latex(self):
         mm = af.ModelMapper()
@@ -198,7 +207,6 @@ class TestModelingMapper:
 
 
 class TestInstances:
-
     def test_attribute(self):
         mm = af.ModelMapper()
         mm.cls_1 = af.m.MockClassx2
@@ -245,9 +253,7 @@ class TestInstances:
         assert isinstance(model_map.mock_child_tuple, af.m.MockChildTuple)
 
         assert isinstance(model_map.mock_child_cls_0, af.m.MockChildTuplex3)
-        assert isinstance(
-            model_map.mock_child_cls_3, af.m.MockChildTuplex3
-        )
+        assert isinstance(model_map.mock_child_cls_3, af.m.MockChildTuplex3)
 
     def test__in_order_of_class_constructor(self):
         mapper = af.ModelMapper(mock_cls_0=af.m.MockChildTuplex2)
@@ -313,7 +319,7 @@ class TestInstances:
         assert model_map.mock_cls_2.two == 1.0
 
     def test__check_order_for_different_unit_values_and_set_priors_equal_to_one_another(
-            self
+        self,
     ):
         mapper = af.ModelMapper(
             mock_cls_0=af.m.MockChildTuplex2,
@@ -467,9 +473,7 @@ class TestInstances:
         assert vector == pytest.approx([0.268, 0.402], abs=0.1)
 
     def test_random_vector_from_prior(self):
-        mapper = af.Collection(
-            mock_class=af.Model(af.m.MockClassx2)
-        )
+        mapper = af.Collection(mock_class=af.Model(af.m.MockClassx2))
 
         random.seed(1)
 
@@ -485,11 +489,7 @@ class TestInstances:
 
         mapper = af.Collection(
             mock_class=af.Model(
-                af.m.MockClassx2,
-                one=af.UniformPrior(
-                    lower_limit=0.15,
-                    upper_limit=1.0
-                )
+                af.m.MockClassx2, one=af.UniformPrior(lower_limit=0.15, upper_limit=1.0)
             )
         )
 
@@ -512,12 +512,12 @@ class TestUtility:
 
         assert len(mapper.prior_prior_model_dict) == 2
         assert (
-                mapper.prior_prior_model_dict[mapper.prior_tuples_ordered_by_id[0][1]].cls
-                == af.m.MockClassx2
+            mapper.prior_prior_model_dict[mapper.prior_tuples_ordered_by_id[0][1]].cls
+            == af.m.MockClassx2
         )
         assert (
-                mapper.prior_prior_model_dict[mapper.prior_tuples_ordered_by_id[1][1]].cls
-                == af.m.MockClassx2
+            mapper.prior_prior_model_dict[mapper.prior_tuples_ordered_by_id[1][1]].cls
+            == af.m.MockClassx2
         )
 
     def test_name_for_prior(self):
@@ -533,11 +533,9 @@ class TestPriorReplacement:
         result = mapper.mapper_from_gaussian_tuples([(10, 3), (5, 3)])
 
         assert isinstance(result.mock_class.one, af.GaussianPrior)
-        assert {
-                   prior.id for prior in mapper.priors
-               } == {
-                   prior.id for prior in result.priors
-               }
+        assert {prior.id for prior in mapper.priors} == {
+            prior.id for prior in result.priors
+        }
 
     def test_replace_priors_with_gaussians_from_tuples(self):
         mapper = af.ModelMapper(mock_class=af.m.MockClassx2)
@@ -601,9 +599,7 @@ class TestIndependentPriorModel:
 
 @pytest.fixture(name="list_prior_model")
 def make_list_prior_model():
-    return af.Collection(
-        [af.Model(af.m.MockClassx2), af.Model(af.m.MockClassx2)]
-    )
+    return af.Collection([af.Model(af.m.MockClassx2), af.Model(af.m.MockClassx2)])
 
 
 class TestListPriorModel:
@@ -640,7 +636,7 @@ class TestListPriorModel:
         assert gaussian_mapper.list[1].two.sigma == 5
 
     def test_prior_results_for_gaussian_tuples__include_override_from_width_file(
-            self, list_prior_model
+        self, list_prior_model
     ):
         mapper = af.ModelMapper()
         mapper.list = list_prior_model
diff --git a/test_autofit/non_linear/samples/test_pdf.py b/test_autofit/non_linear/samples/test_pdf.py
index 09ed424dd..ee6a840b8 100644
--- a/test_autofit/non_linear/samples/test_pdf.py
+++ b/test_autofit/non_linear/samples/test_pdf.py
@@ -91,7 +91,7 @@ def test_format(samples_x5):
 
     assert (
         text
-        == """mock_class_1_one,mock_class_1_two,mock_class_1_three,mock_class_1_four,log_likelihood,log_prior,log_posterior,weight
+        == """mock_class_1.one,mock_class_1.two,mock_class_1.three,mock_class_1.four,log_likelihood,log_prior,log_posterior,weight
              0.0,             1.0,               2.0,              3.0,           1.0,      0.0,          1.0,   1.0
              0.0,             1.0,               2.0,              3.0,           2.0,      0.0,          2.0,   1.0
              0.0,             1.0,               2.0,              3.0,           3.0,      0.0,          3.0,   1.0
@@ -374,7 +374,7 @@ def test__unconverged_sample_size():
             log_prior_list=[1.0, 1.0, 1.0, 1.0, 1.0],
             weight_list=weight_list,
         ),
-        samples_info={"unconverged_sample_size" : 2},
+        samples_info={"unconverged_sample_size": 2},
     )
 
     assert samples_x5.pdf_converged is False
diff --git a/test_autofit/non_linear/samples/test_samples.py b/test_autofit/non_linear/samples/test_samples.py
index 2cb6a78b1..b0036ece0 100644
--- a/test_autofit/non_linear/samples/test_samples.py
+++ b/test_autofit/non_linear/samples/test_samples.py
@@ -8,10 +8,10 @@ pytestmark = pytest.mark.filterwarnings("ignore::FutureWarning")
 
 def test__table__headers(samples_x5):
     assert samples_x5._headers == [
-        "mock_class_1_one",
-        "mock_class_1_two",
-        "mock_class_1_three",
-        "mock_class_1_four",
+        "mock_class_1.one",
+        "mock_class_1.two",
+        "mock_class_1.three",
+        "mock_class_1.four",
         "log_likelihood",
         "log_prior",
         "log_posterior",
@@ -31,7 +31,6 @@ def test__table__rows(samples_x5):
 
 
 def test__table__write_table():
-
     model = af.Collection(mock_class_1=af.m.MockClassx4)
 
     parameters = [
@@ -61,7 +60,6 @@ def test__table__write_table():
 
 
 def test__max_log_likelihood(samples_x5):
-
     assert samples_x5.max_log_likelihood(as_instance=False) == [21.0, 22.0, 23.0, 24.0]
 
     instance = samples_x5.max_log_likelihood(as_instance=True)
@@ -171,7 +169,6 @@ def test__instance_from_sample_index():
 
 
 def test__addition_of_samples(samples_x5):
-
     samples = samples_x5 + samples_x5
 
     assert len(samples.sample_list) == 10
@@ -180,8 +177,8 @@ def test__addition_of_samples(samples_x5):
     assert samples.sample_list[5].log_likelihood == 1.0
     assert samples.sample_list[9].log_likelihood == 5.0
 
-def test__sum_of_samples(samples_x5):
 
+def test__sum_of_samples(samples_x5):
     samples = sum([samples_x5, samples_x5, samples_x5])
 
     assert len(samples.sample_list) == 15
@@ -192,8 +189,8 @@ def test__sum_of_samples(samples_x5):
     assert samples.sample_list[10].log_likelihood == 1.0
     assert samples.sample_list[14].log_likelihood == 5.0
 
-def test__addition_of_samples__raises_error_if_model_mismatch(samples_x5):
 
+def test__addition_of_samples__raises_error_if_model_mismatch(samples_x5):
     model = af.Collection(mock_class_1=af.m.MockClassx2)
 
     parameters = [
@@ -216,4 +213,4 @@ def test__addition_of_samples__raises_error_if_model_mismatch(samples_x5):
     )
 
     with pytest.raises(af.exc.SamplesException):
-        samples_x5 + samples_different_model
\ No newline at end of file
+        samples_x5 + samples_different_model