Repository: rhayes777/PyAutoFit (python)

Issue:
`LogGaussianPrior` raises Exception
The following script uses ` LogGaussianPrior` and produces an exception:

https://github.com/Jammy2211/autofit_workspace_test/blob/main/scripts/searches/DynestyStatic.py

The exception:

```
2024-02-12 09:53:38,721 - DynestyStatic - INFO - The output path of this fit is /mnt/c/Users/Jammy/Code/PyAuto/autofit_workspace_test/output/searches/DynestyStatic/431e743943ea071dcffa55a0db2535b6
2024-02-12 09:53:38,721 - DynestyStatic - INFO - Outputting pre-fit files (e.g. model.info, visualization).
2024-02-12 09:53:38,948 - DynestyStatic - INFO - Starting new Dynesty non-linear search (no previous samples found).
2024-02-12 09:53:38,993 - autofit.non_linear.initializer - INFO - Generating initial samples of model, which are subject to prior limits and other constraints.
/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/messages/transform.py:195: RuntimeWarning: divide by zero encountered in log
  return self.func(x, *self.args)
1122it [00:04, 241.60it/s, +50 | bound: 40 | nc: 1 | ncall: 4861 | eff(%): 24.361 | loglstar:   -inf < -43.295 <    inf | logz: -62.652 +/-  0.593 | dlogz:  0.001 >  0.059]
2024-02-12 09:53:43,694 - DynestyStatic - INFO - Fit Still Running: Updating results after 10000 iterations (see output folder for latest visualization, samples, etc.)
Traceback (most recent call last):
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 1053, in samples_from
    return self.samples_via_internal_from(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/nest/dynesty/search/abstract.py", line 237, in samples_via_internal_from
    log_prior_list = model.log_prior_list_from(parameter_lists=parameter_lists)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/mapper/prior_model/abstract.py", line 1578, in log_prior_list_from
    return [
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/mapper/prior_model/abstract.py", line 1579, in <listcomp>
    sum(self.log_prior_list_from_vector(vector=vector))
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/mapper/prior_model/abstract.py", line 1077, in log_prior_list_from_vector
    return list(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/mapper/prior_model/abstract.py", line 1079, in <lambda>
    lambda prior_tuple, value: prior_tuple.prior.log_prior_from_value(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/mapper/prior/abstract.py", line 188, in __getattr__
    return getattr(self.message, item)
AttributeError: 'TransformedMessage' object has no attribute 'log_prior_from_value'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/mnt/c/Users/Jammy/Code/PyAuto/autofit_workspace_test/scripts/searches/DynestyStatic.py", line 89, in <module>
    result = search.fit(model=model, analysis=analysis)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 565, in fit
    result = self.start_resume_fit(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 683, in start_resume_fit
    search_internal = self._fit(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/nest/dynesty/search/abstract.py", line 196, in _fit
    self.perform_update(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 914, in perform_update
    samples = self.samples_from(model=model, search_internal=search_internal)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 1057, in samples_from
    return self.samples_via_csv_from(model=model)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/search/abstract_search.py", line 1080, in samples_via_csv_from
    return self.samples_cls.from_csv(
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/samples/samples.py", line 203, in from_csv
    sample_list = paths.load_samples()
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/paths/directory.py", line 216, in load_samples
    return load_from_table(filename=self._samples_file)
  File "/mnt/c/Users/Jammy/Code/PyAuto/PyAutoFit/autofit/non_linear/samples/sample.py", line 292, in load_from_table
    with open(filename, "r+", newline="") as f:
FileNotFoundError: [Errno 2] No such file or directory: '/mnt/c/Users/Jammy/Code/PyAuto/autofit_workspace_test/output/searches/DynestyStatic/431e743943ea071dcffa55a0db2535b6/files/samples.csv'
```

Pull request description:
feature/log prior from value
Resolves #932


Patch:
diff --git a/autofit/mapper/prior/log_gaussian.py b/autofit/mapper/prior/log_gaussian.py
index 4287c8bec..95caa4bcc 100644
--- a/autofit/mapper/prior/log_gaussian.py
+++ b/autofit/mapper/prior/log_gaussian.py
@@ -1,5 +1,7 @@
 from typing import Optional
 
+import numpy as np
+
 from autofit.messages.normal import NormalMessage
 from .abstract import Prior
 from ...messages.composed_transform import TransformedMessage
@@ -101,3 +103,11 @@ class LogGaussianPrior(Prior):
     @property
     def parameter_string(self) -> str:
         return f"mean = {self.mean}, sigma = {self.sigma}"
+
+    def log_prior_from_value(self, value):
+        if value <= 0:
+            return float("-inf")
+
+        return self.message.base_message.log_prior_from_value(np.log(value)) - np.log(
+            value
+        )
diff --git a/autofit/mapper/prior/log_uniform.py b/autofit/mapper/prior/log_uniform.py
index b39f2d191..baeb14a14 100644
--- a/autofit/mapper/prior/log_uniform.py
+++ b/autofit/mapper/prior/log_uniform.py
@@ -98,8 +98,7 @@ class LogUniformPrior(Prior):
 
     __identifier_fields__ = ("lower_limit", "upper_limit")
 
-    @staticmethod
-    def log_prior_from_value(value) -> float:
+    def log_prior_from_value(self, value) -> float:
         """
         Returns the log prior of a physical value, so the log likelihood of a model evaluation can be converted to a
         posterior as log_prior + log_likelihood.
diff --git a/autofit/mapper/prior/uniform.py b/autofit/mapper/prior/uniform.py
index 2fc60bfd4..1926a059a 100644
--- a/autofit/mapper/prior/uniform.py
+++ b/autofit/mapper/prior/uniform.py
@@ -101,9 +101,7 @@ class UniformPrior(Prior):
             super().value_for(unit, ignore_prior_limits=ignore_prior_limits), 14
         )
 
-    # noinspection PyUnusedLocal
-    @staticmethod
-    def log_prior_from_value(value):
+    def log_prior_from_value(self, value):
         """
         Returns the log prior of a physical value, so the log likelihood of a model evaluation can be converted to a
         posterior as log_prior + log_likelihood.


Test patch:
diff --git a/test_autofit/mapper/prior/test_prior.py b/test_autofit/mapper/prior/test_prior.py
index 614554003..469e46bca 100644
--- a/test_autofit/mapper/prior/test_prior.py
+++ b/test_autofit/mapper/prior/test_prior.py
@@ -260,7 +260,6 @@ class TestUniformPrior:
 
 
 class TestLogUniformPrior:
-
     def test__simple_assumptions(self):
         log_uniform_simple = af.LogUniformPrior(lower_limit=1.0e-8, upper_limit=1.0)
 
@@ -327,37 +326,28 @@ class TestGaussianPrior:
         assert gaussian_half.value_for(0.9) == pytest.approx(3.0631031, 1.0e-4)
         assert gaussian_half.value_for(0.5) == 0.5
 
-    def test__log_prior_from_value(self):
-        gaussian_simple = af.GaussianPrior(mean=0.0, sigma=1.0)
-
-        log_prior = gaussian_simple.log_prior_from_value(value=0.0)
-
-        assert log_prior == 0.0
-
-        log_prior = gaussian_simple.log_prior_from_value(value=1.0)
-
-        assert log_prior == 0.5
-
-        log_prior = gaussian_simple.log_prior_from_value(value=2.0)
-
-        assert log_prior == 2.0
-
-        gaussian_simple = af.GaussianPrior(mean=1.0, sigma=2.0)
-
-        log_prior = gaussian_simple.log_prior_from_value(value=0.0)
-
-        assert log_prior == 0.125
-
-        log_prior = gaussian_simple.log_prior_from_value(value=1.0)
-
-        assert log_prior == 0.0
-
-        log_prior = gaussian_simple.log_prior_from_value(value=2.0)
-
-        assert log_prior == 0.125
-
-        gaussian_simple = af.GaussianPrior(mean=30.0, sigma=60.0)
-
-        log_prior = gaussian_simple.log_prior_from_value(value=2.0)
-
-        assert log_prior == pytest.approx(0.108888, 1.0e-4)
+    @pytest.mark.parametrize(
+        "mean, sigma, value, expected",
+        [
+            (0.0, 1.0, 0.0, 0.0),
+            (0.0, 1.0, 1.0, 0.5),
+            (0.0, 1.0, 2.0, 2.0),
+            (1.0, 2.0, 0.0, 0.125),
+            (1.0, 2.0, 1.0, 0.0),
+            (1.0, 2.0, 2.0, 0.125),
+            (30.0, 60.0, 2.0, pytest.approx(0.108888, 1.0e-4)),
+        ],
+    )
+    def test__log_prior_from_value(self, mean, sigma, value, expected):
+        gaussian = af.GaussianPrior(mean=mean, sigma=sigma)
+        log_prior = gaussian.log_prior_from_value(value=value)
+        assert log_prior == expected
+
+
+def test_log_gaussian_prior_log_prior_from_value():
+    log_gaussian_prior = af.LogGaussianPrior(
+        mean=0.0, sigma=1.0, lower_limit=0.0, upper_limit=1.0
+    )
+
+    assert log_gaussian_prior.log_prior_from_value(value=0.0) == float("-inf")
+    assert log_gaussian_prior.log_prior_from_value(value=0.5) == 0.9333736875190459