Skip to content

Support fixed Array elements through the EP fitting pipeline#1250

Merged
Jammy2211 merged 1 commit intomainfrom
feature/array-fixed-element-gaussian-prior
May 4, 2026
Merged

Support fixed Array elements through the EP fitting pipeline#1250
Jammy2211 merged 1 commit intomainfrom
feature/array-fixed-element-gaussian-prior

Conversation

@Jammy2211
Copy link
Copy Markdown
Collaborator

Summary

PyAutoFit's composition API documents the constant-element pattern for af.Array (arr[i, j] = scalar to fix that element at a literal value). Array._instance_for_arguments already supports this pattern — but Array.gaussian_prior_model_for_arguments, called by AbstractSearch.optimise to rebuild a posterior prior model from search arguments, was crashing with AttributeError: 'float' object has no attribute 'gaussian_prior_model_for_arguments'. The same try/except pattern that _instance_for_arguments uses is now applied here, so fixed scalar elements pass through unchanged.

On top of that, this PR generalises the EP factor-step machinery so a hierarchical / population-level factor can freeze a subset of its priors at sibling factors' posterior means between EP iterations — the documented use case being a "global" Analysis whose likelihood compares predictions to per-dataset posterior summaries (the "EP message comparison" form). Three small additive changes make this safe:

  • EPAnalysisFactor.set_model_approx(model_approx) — a new opt-in hook delivering the full EPMeanField to the factor before each step. (The existing set_cavity_dist only exposes the cavity over the factor's own variables, which is insufficient once a variable has been frozen and dropped from the factor's variable list — that variable's posterior message is no longer in the cavity, but it is still in model_approx.factor_mean_field for sibling factors. EPOptimiser.run now propagates model_approx through factor_step.)
  • AnalysisFactor.name_for_variable returns None (instead of crashing) when a variable has been removed from the factor's prior model.
  • DeclarativeFactorGraph._related_factor_names skips None returns from name_for_variable so info-only callers (graph.info, results text) don't trip on the variables that have left a factor.

Plus a unit test reproducing the original crash.

API Changes

Purely additive. New opt-in EPAnalysisFactor.set_model_approx(model_approx) hook called from factor_step if the factor implements it; default factors are unaffected. factor_step and EPOptimiser.factor_step now accept an additional keyword-only model_approx=None argument so the optimiser loop can pass the full mean field through. Two info-formatter callsites learn to skip variables that have been removed from a factor's prior model. No symbols renamed, removed, or behaviour-changed for existing factors. See full details below.

Test Plan

  • pytest test_autofit/mapper/test_array.py — new test reproduces and verifies the original crash fix
  • pytest test_autofit/graphical -q — 184 graphical tests pass (no regressions)
  • pytest test_autofit/mapper test_autofit/graphical -q — 676 combined tests pass
  • End-to-end exercised on the cancer-IC50 EP validation script: 10-dataset EP fit drops the global factor's prior_count from 93 → 63 (coef_matrix + coef_mean only) by freezing each hill_coef[i, j] at its local Hill factor's posterior mean; recovered coef_mean matches simulator truth within 0.2σ on every channel.
Full API Changes (for automation & release notes)

Added

  • autofit.graphical.declarative.factor.analysis.EPAnalysisFactor.set_model_approx(model_approx) — opt-in hook called by factor_step before each EP step, receiving the full EPMeanField. The default implementation attaches the mean field to the wrapped Analysis as _mean_field. Subclasses can override to e.g. freeze a subset of the factor's priors at sibling factors' posterior means before optimisation.

Changed Signature

  • autofit.graphical.expectation_propagation.optimiser.factor_step(factor_approx, optimiser, model_approx=None) — added trailing keyword argument model_approx. Defaults to None; when supplied the function calls factor.set_model_approx(model_approx) if the factor implements it. Backwards compatible — existing callers (e.g. ParallelEPOptimiser's pool path) pass two positional args and behave as before.
  • autofit.graphical.expectation_propagation.optimiser.EPOptimiser.factor_step(factor_approx, optimiser, model_approx=None) — same signature change on the instance method, which now forwards model_approx to the module-level function.

Changed Behaviour

  • autofit.mapper.prior_model.array.Array.gaussian_prior_model_for_arguments — now mirrors _instance_for_arguments: scalar elements (set via arr[i, j] = float) pass through unchanged instead of raising AttributeError. Fixes a crash on AbstractSearch.optimise → mapper_from_prior_arguments when any element has been pinned via the constant-element pattern.
  • autofit.graphical.declarative.factor.analysis.AnalysisFactor.name_for_variable(variable) — now returns None when prior_model.path_for_prior(variable) returns None (i.e. the variable has been removed from the factor's prior model after registration). Previously raised TypeError: can only join an iterable.
  • autofit.graphical.declarative.graph.DeclarativeFactorGraph._related_factor_names(...) — skips factors whose name_for_variable returns None, so graph.info and make_results_text no longer trip on variables that have left a factor.

Migration

None required — purely additive. Existing factor implementations and existing Array usage see no behaviour change unless they were already crashing on the pinned-scalar case.

🤖 Generated with Claude Code

Adds the missing handling that lets ``af.Array`` elements set to fixed
scalars (the documented composition pattern ``arr[i, j] = float``)
flow through the full EP fit + posterior reconstruction without
crashing, and a ``set_model_approx`` hook on ``EPAnalysisFactor`` that
hierarchical factors can use to inspect sibling messages on variables
their own cavity no longer contains.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@Jammy2211 Jammy2211 added the pending-release PR queued for the next release build label May 4, 2026
@Jammy2211 Jammy2211 merged commit 5b3b8fd into main May 4, 2026
3 checks passed
@Jammy2211 Jammy2211 deleted the feature/array-fixed-element-gaussian-prior branch May 4, 2026 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant