Skip to content

Commit 2cc8701

Browse files
Jammy2211claude
authored andcommitted
Merge main into feature/rst-to-myst-md (port Colab tag bumps to .md)
Catches the branch up to main. The 2026.5.1.1 and 2026.5.1.4 release commits bumped the Colab URL tag in docs/index.rst on main; that file was deleted in the conversion, so the modify/delete conflict is resolved by keeping the deletion and porting the bump (2026.4.13.6 → 2026.5.1.4) to the MyST docs/index.md sibling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2 parents 327edc2 + f6d3b4a commit 2cc8701

23 files changed

Lines changed: 213 additions & 926 deletions

File tree

README.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ PyAutoFit: Classy Probabilistic Programming
22
===========================================
33

44
.. |colab| image:: https://colab.research.google.com/assets/colab-badge.svg
5-
:target: https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.4.13.6/start_here.ipynb
5+
:target: https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.5.1.4/start_here.ipynb
66

77
.. |RTD| image:: https://readthedocs.org/projects/pyautofit/badge/?version=latest
88
:target: https://pyautofit.readthedocs.io/en/latest/?badge=latest
@@ -33,7 +33,7 @@ PyAutoFit: Classy Probabilistic Programming
3333

3434
`Installation Guide <https://pyautofit.readthedocs.io/en/latest/installation/overview.html>`_ |
3535
`readthedocs <https://pyautofit.readthedocs.io/en/latest/index.html>`_ |
36-
`Introduction on Colab <https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.4.13.6/notebooks/overview/overview_1_the_basics.ipynb>`_ |
36+
`Introduction on Colab <https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.5.1.4/notebooks/overview/overview_1_the_basics.ipynb>`_ |
3737
`HowToFit <https://github.com/PyAutoLabs/HowToFit>`_
3838

3939
**PyAutoFit** is a Python based probabilistic programming language for model fitting and Bayesian inference
@@ -55,7 +55,7 @@ The following links are useful for new starters:
5555

5656
- `The PyAutoFit readthedocs <https://pyautofit.readthedocs.io/en/latest>`_, which includes an `installation guide <https://pyautofit.readthedocs.io/en/latest/installation/overview.html>`_ and an overview of **PyAutoFit**'s core features.
5757

58-
- `The introduction Jupyter Notebook on Colab <https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.4.13.6/notebooks/overview/overview_1_the_basics.ipynb>`_, where you can try **PyAutoFit** in a web browser (without installation).
58+
- `The introduction Jupyter Notebook on Colab <https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.5.1.4/notebooks/overview/overview_1_the_basics.ipynb>`_, where you can try **PyAutoFit** in a web browser (without installation).
5959

6060
- `The autofit_workspace GitHub repository <https://github.com/Jammy2211/autofit_workspace>`_, which includes example scripts demonstrating **PyAutoFit**'s features.
6161

autofit/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from .database.aggregator.aggregator import GridSearchAggregator
1616
from .graphical.expectation_propagation.history import EPHistory
1717
from .graphical.declarative.factor.analysis import AnalysisFactor
18+
from .graphical.declarative.factor.analysis import EPAnalysisFactor
1819
from .graphical.declarative.collection import FactorGraphModel
1920
from .graphical.declarative.factor.hierarchical import HierarchicalFactor
2021
from .graphical.laplace import LaplaceOptimiser
@@ -138,7 +139,7 @@ def save_abc(pickler, obj):
138139
pickle._Pickler.save_type(pickler, obj)
139140

140141

141-
__version__ = "2026.4.13.6"
142+
__version__ = "2026.5.1.4"
142143

143144
from autoconf import check_version
144145

autofit/graphical/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from . import utils
22
from .declarative.abstract import PriorFactor
33
from .declarative.collection import FactorGraphModel
4-
from .declarative.factor.analysis import AnalysisFactor
4+
from .declarative.factor.analysis import AnalysisFactor, EPAnalysisFactor
55
from .declarative.factor.hierarchical import _HierarchicalFactor, HierarchicalFactor
66
from .expectation_propagation.ep_mean_field import EPMeanField
77
from .expectation_propagation.optimiser import EPOptimiser

autofit/graphical/declarative/factor/analysis.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,52 @@ def save_results(self, paths: AbstractPaths, result):
187187

188188
def log_likelihood_function(self, instance: ModelInstance) -> float:
189189
return self.analysis.log_likelihood_function(instance)
190+
191+
192+
class EPAnalysisFactor(AnalysisFactor):
193+
"""
194+
An ``AnalysisFactor`` that exposes the EP cavity distribution to its
195+
``Analysis`` on each likelihood evaluation.
196+
197+
On every iteration of the EP optimiser, the cavity distribution
198+
``q⁻ᵃ`` — the product of the posterior approximations from all
199+
*other* factors over the variables this factor shares with them —
200+
is computed in
201+
:class:`autofit.graphical.mean_field.FactorApproximation`. For most
202+
factors that distribution is consumed implicitly: it becomes the
203+
prior the search samples from.
204+
205+
Some hierarchical / population-level analyses want to read those
206+
cavity messages directly. A canonical example is a "global"
207+
Analysis whose log-likelihood compares model predictions to the
208+
per-dataset Gaussian posterior summaries produced by upstream
209+
local fits, e.g.::
210+
211+
log L = -0.5 * sum_i || (pred_i - cavity_mean_i) / cavity_sigma_i ||^2
212+
213+
To support that, ``EPAnalysisFactor`` attaches the current cavity
214+
distribution to its ``Analysis`` immediately before optimisation,
215+
via the attribute ``_cavity_mean_field``. The user's
216+
``log_likelihood_function`` can then read each shared variable's
217+
cavity message (``.mean`` and ``.scale`` on the
218+
``AbstractMessage`` value) out of the dict.
219+
220+
The hook is invoked from
221+
:func:`autofit.graphical.expectation_propagation.optimiser.factor_step`
222+
via duck-typing (``hasattr(factor, "set_cavity_dist")``), so the
223+
behaviour of plain ``AnalysisFactor`` is unaffected.
224+
"""
225+
226+
def set_cavity_dist(self, cavity_dist):
227+
"""
228+
Store the cavity distribution on the wrapped ``Analysis``.
229+
230+
Called by :func:`factor_step` once per EP iteration, before this
231+
factor's local search runs. The Analysis can read the messages
232+
inside ``log_likelihood_function`` by inspecting
233+
``self._cavity_mean_field`` — a ``MeanField`` mapping each
234+
shared :class:`Variable` (i.e. ``Prior``) to an
235+
``AbstractMessage`` whose ``.mean`` and ``.scale`` give the
236+
cavity Gaussian summary.
237+
"""
238+
self.analysis._cavity_mean_field = cavity_dist

autofit/graphical/expectation_propagation/optimiser.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ def factor_step(factor_approx, optimiser):
108108
factor = factor_approx.factor
109109
factor_logger = logging.getLogger(factor.name)
110110
factor_logger.debug("Optimising...")
111+
# Cavity-message opt-in: factors that implement ``set_cavity_dist``
112+
# (e.g. ``EPAnalysisFactor``) receive the current cavity distribution
113+
# before optimisation so their Analysis can read per-variable cavity
114+
# messages inside ``log_likelihood_function``. Default factors lack
115+
# the method, so this is a no-op for them.
116+
if hasattr(factor, "set_cavity_dist"):
117+
factor.set_cavity_dist(factor_approx.cavity_dist)
111118
try:
112119
with LogWarnings(
113120
logger=factor_logger.debug, action="always"

autofit/graphical/factor_graphs/factor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,6 @@ def _set_jacobians(
284284
numerical_jacobian=True,
285285
jacfwd=True,
286286
):
287-
import jax
288-
289287
self._vjp = vjp
290288
self._jacfwd = jacfwd
291289
if vjp or factor_vjp:
@@ -302,8 +300,10 @@ def _set_jacobians(
302300
elif numerical_jacobian:
303301
self._factor_jacobian = self._numerical_factor_jacobian
304302
elif jacfwd:
303+
import jax
305304
self._jacobian = jax.jacfwd(self._factor, range(self.n_args))
306305
else:
306+
import jax
307307
self._jacobian = jax.jacobian(self._factor, range(self.n_args))
308308

309309
def _factor_value(self, raw_fval) -> FactorValue:

autofit/mapper/prior_model/array.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ def _instance_for_arguments(
8787
pass
8888

8989
if make_array:
90-
if isinstance(value, np.ndarray) or isinstance(value, np.float64):
90+
if isinstance(value, (np.ndarray, np.float64, float, int)):
9191
array = np.zeros(self.shape)
9292
make_array = False
9393
else:
9494
import jax.numpy as jnp
9595
array = jnp.zeros(self.shape)
9696
make_array = False
9797

98-
if isinstance(value, np.ndarray) or isinstance(value, np.float64):
98+
if isinstance(value, (np.ndarray, np.float64, float, int)):
9999
array[index] = value
100100
else:
101101
array = array.at[index].set(value)

autofit/messages/normal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def value_for(self, unit: float) -> float:
417417
>>> prior = af.GaussianPrior(mean=1.0, sigma=2.0)
418418
>>> physical_value = prior.value_for(unit=0.5)
419419
"""
420-
if isinstance(unit, np.ndarray) or isinstance(unit, np.float64):
420+
if isinstance(unit, (np.ndarray, np.float64, float, int, list)):
421421
from scipy.special import erfinv as scipy_erfinv
422422
inv = scipy_erfinv(1 - 2.0 * (1.0 - unit))
423423
else:

autofit/non_linear/analysis/analysis.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,30 @@ def __init__(
4444
use_jax = False
4545
use_jax_for_visualization = False
4646

47+
# If the user requested JAX but it isn't installed (e.g. Python <3.11
48+
# without the [jax] extra), fall back to numpy with a loud warning
49+
# rather than crashing later when the analysis tries to jit-compile.
50+
if use_jax:
51+
import importlib.util
52+
import warnings
53+
if importlib.util.find_spec("jax") is None:
54+
warnings.warn(
55+
"\n"
56+
"+----------------------------------------------------------------------+\n"
57+
"| use_jax=True was requested but JAX is not installed. |\n"
58+
"| |\n"
59+
"| Falling back to numpy. The fit will run, but JAX acceleration |\n"
60+
"| (typically 10-100x for large lens models) is unavailable. |\n"
61+
"| |\n"
62+
"| To enable JAX, install on Python 3.11+ via your library's [jax] |\n"
63+
"| extra, e.g.: pip install autolens[jax] |\n"
64+
"+----------------------------------------------------------------------+",
65+
UserWarning,
66+
stacklevel=2,
67+
)
68+
use_jax = False
69+
use_jax_for_visualization = False
70+
4771
if use_jax_for_visualization and not use_jax:
4872
logger.warning(
4973
"use_jax_for_visualization=True requires use_jax=True; "

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ notably [a big data framework for Bayesian hierarchical analysis](https://pyauto
1717
The following links are useful for new starters:
1818

1919
- [The PyAutoFit readthedocs](https://pyautofit.readthedocs.io/en/latest), which includes an [installation guide](https://pyautofit.readthedocs.io/en/latest/installation/overview.html) and an overview of **PyAutoFit**'s core features.
20-
- [The introduction Jupyter Notebook on Colab](https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.4.13.6/notebooks/overview/overview_1_the_basics.ipynb), where you can try **PyAutoFit** in a web browser (without installation).
20+
- [The introduction Jupyter Notebook on Colab](https://colab.research.google.com/github/PyAutoLabs/autofit_workspace/blob/2026.5.1.4/notebooks/overview/overview_1_the_basics.ipynb), where you can try **PyAutoFit** in a web browser (without installation).
2121
- [The autofit_workspace GitHub repository](https://github.com/Jammy2211/autofit_workspace), which includes example scripts demonstrating **PyAutoFit**'s features.
2222
- [The standalone HowToFit repository](https://github.com/PyAutoLabs/HowToFit), a series of Jupyter notebook lectures which give new users a step-by-step introduction to **PyAutoFit**.
2323

0 commit comments

Comments
 (0)