Support fixed Array elements through the EP fitting pipeline#1250
Merged
Support fixed Array elements through the EP fitting pipeline#1250
Conversation
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]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PyAutoFit's composition API documents the constant-element pattern for
af.Array(arr[i, j] = scalarto fix that element at a literal value).Array._instance_for_argumentsalready supports this pattern — butArray.gaussian_prior_model_for_arguments, called byAbstractSearch.optimiseto rebuild a posterior prior model from search arguments, was crashing withAttributeError: 'float' object has no attribute 'gaussian_prior_model_for_arguments'. The sametry/exceptpattern that_instance_for_argumentsuses 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 fullEPMeanFieldto the factor before each step. (The existingset_cavity_distonly 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 inmodel_approx.factor_mean_fieldfor sibling factors.EPOptimiser.runnow propagatesmodel_approxthroughfactor_step.)AnalysisFactor.name_for_variablereturnsNone(instead of crashing) when a variable has been removed from the factor's prior model.DeclarativeFactorGraph._related_factor_namesskipsNonereturns fromname_for_variableso 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 fromfactor_stepif the factor implements it; default factors are unaffected.factor_stepandEPOptimiser.factor_stepnow accept an additional keyword-onlymodel_approx=Noneargument 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 fixpytest test_autofit/graphical -q— 184 graphical tests pass (no regressions)pytest test_autofit/mapper test_autofit/graphical -q— 676 combined tests passprior_countfrom 93 → 63 (coef_matrix+coef_meanonly) by freezing eachhill_coef[i, j]at its local Hill factor's posterior mean; recoveredcoef_meanmatches 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 byfactor_stepbefore each EP step, receiving the fullEPMeanField. 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 argumentmodel_approx. Defaults toNone; when supplied the function callsfactor.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 forwardsmodel_approxto 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 viaarr[i, j] = float) pass through unchanged instead of raisingAttributeError. Fixes a crash onAbstractSearch.optimise → mapper_from_prior_argumentswhen any element has been pinned via the constant-element pattern.autofit.graphical.declarative.factor.analysis.AnalysisFactor.name_for_variable(variable)— now returnsNonewhenprior_model.path_for_prior(variable)returnsNone(i.e. the variable has been removed from the factor's prior model after registration). Previously raisedTypeError: can only join an iterable.autofit.graphical.declarative.graph.DeclarativeFactorGraph._related_factor_names(...)— skips factors whosename_for_variablereturnsNone, sograph.infoandmake_results_textno longer trip on variables that have left a factor.Migration
None required — purely additive. Existing factor implementations and existing
Arrayusage see no behaviour change unless they were already crashing on the pinned-scalar case.🤖 Generated with Claude Code