Repository navigation
feat: MassField in PyAutoLens — Tracer(fields=), analysis slot, COOLEST, LOS, model_util - #742
Merged
Merged
Conversation
…ST, LOS, model_util Phase 2 of the mass-field epic. `Tracer(galaxies, cosmology=None, fields=None)` stores external fields in `tracer.fields`; only the planes merge galaxies and fields (`members` / `members_ascending_redshift`), so `tracer.galaxies` never holds a field and per-galaxy surfaces (the galaxy image dictionary, fit plane images, subplots) skip fields explicitly while every lensing quantity runs over members. The analysis folds a model's `fields` collection (AnalysisLens, the aggregator tracer builder, Result.max_log_likelihood_fields); COOLEST exports fields as their own MassField entities and imports MassField entities into `tracer.fields` (the one import behaviour change; export of legacy galaxy-attached sheets is pinned byte-identical); the LOS sampler gains fields_from()/galaxies_and_fields_from() with its legacy return kept as the default; model_util.mass_field_from ties the MassSheet / ExternalPotential centre to the lens mass centre as a shared prior. al.MassField is re-exported; API docs and the overview updated. Legacy galaxy-attached sheets: no warning, no behaviour change, identifier pinned (fef2697b5c32ba56bb18a7baecb7b0f6). No prior config changes. Closes #741. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqfNuorRbom8ZjD9i9hvWo
This was referenced Sep 18, 2026
Collaborator
Author
|
Workspace PRs (phase 3 of the MassField epic, drafts held until this lands in a release): PyAutoLabs/autolens_workspace#560, PyAutoLabs/autolens_workspace_test#322. Issue: PyAutoLabs/autolens_workspace#559 |
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
Teaches PyAutoLens about
ag.MassField(PyAutoGalaxy#621), phase 2 of the mass-field epic. A field is its own thing:Tracer(galaxies, cosmology=None, fields=None)stores it intracer.fields, and only the tracer's planes merge galaxies and fields at each redshift (members/members_ascending_redshift).tracer.galaxiesnever contains a field, so every script that indexes galaxies positionally keeps working, and every per-galaxy surface (galaxy_image_2d_dict_from, the fit plane images, the galaxy subplots) skips fields explicitly. Lensing quantities (deflections, convergence, potential, ray-tracing,has,cls_list_from) run over members.Around the tracer: the analysis folds a model's
fieldscollection (AnalysisLens.tracer_via_instance_from, the aggregator tracer builder,Result.max_log_likelihood_fields); COOLEST exports each field as its ownMassFieldentity and importsMassFieldentities asal.MassFieldintracer.fields; the line-of-sight sampler gainsfields_from()/galaxies_and_fields_from()with its legacy single-list return kept as the default; andal.model_util.mass_field_from(lens, shear=True, mass_sheet=False, potential=False, redshift=None)composes the field model with theMassSheet/ExternalPotentialcentre tied tolens.mass.centreas a shared prior.al.MassFieldis re-exported; API docs and the start-here overview gain the new surfaces.Legacy galaxy-attached sheets are untouched: no warning, no behaviour change, and the COOLEST export of a galaxy-attached tracer is pinned byte-identical against a fixture captured on
main. The PyAutoFit identifier of a galaxy-attached lens/source model is asserted unchanged. Closes #741.API Changes
Added:
Tracer(..., fields=),Tracer.fields,Tracer.members,Tracer.members_ascending_redshift,Tracer.sliced_tracer_from(..., fields=),Result.max_log_likelihood_fields,LOSSampler.fields_from()/galaxies_and_fields_from()/galaxies_from(sheets_as_fields=False),model_util.mass_field_from, and theal.MassFieldre-export. One behaviour change on COOLEST import only: aMassFieldentity now lands intracer.fieldsas anal.MassFieldrather than as an extraGalaxyintracer.galaxies(export is unchanged).LOSSamplernow caches its sampled population, so repeatedgalaxies_from()calls describe one line of sight. No removals or renames.See full details below.
Test Plan
pytest test_autolens -q -n auto: 710 passed, 1 xfailed (pre-existing); baseline onmain653 passed, 1 xfailed — 57 new tests, no regressions.af.Collection(galaxies=af.Collection(lens=af.Model(al.Galaxy, redshift=0.5, mass=af.Model(al.mp.Isothermal), shear=af.Model(al.mp.ExternalShear)), source=af.Model(al.Galaxy, redshift=1.0, bulge=af.Model(al.lp.Sersic)))).identifier == "fef2697b5c32ba56bb18a7baecb7b0f6"onmain(223132c) and on this branch, asserted in a subprocess (the suite's conftest pushes a test config that changes identifiers, so the pinned value is evaluated under the shipped config).Tracer(galaxies=[lens, source], fields=[MassField(redshift=0.5, shear=...)]).traced_grid_2d_list_from(grid)allclose to the same shear onlens;tracer.galaxies == [lens, source]; a model withfields=af.Collection(field=af.Model(al.MassField, ...))builds throughAnalysisImagingwith the field intracer.fieldsand its log likelihood equal to the galaxy-attached form at the same values; COOLEST round trip returns anal.MassFieldwith parameters equal to 1e-12; legacy galaxy-attached export byte-identical totest_autolens/interop/fixtures/legacy_galaxy_attached.json;mass_field_from(lens=lens_model, potential=True).potential.centre is lens_model.mass.centre.FitImaging.galaxy_model_image_dictkeys are the galaxies only;model_images_of_planes_listequals the galaxy-attached equivalent;subplot_fit,subplot_of_planes,subplot_tracer,subplot_galaxies_imagesrender with a field; a pixelized-source fit andFitPointDataset.figure_of_meritwith a field equal the galaxy-attached values.xp=jnp/jax.jitwith the tracer traced and the grid closed over) ontraced_grid_2d_list_fromwith a field: all allclose atatol=1e-6(the 7.5e-8 float32 residual is pre-existing and identical for a galaxy-attached shear); the jit result with the field differs from without. Unit tests stay NumPy-only per repo policy.git diff --name-onlytouches nothing underautolens/config/; nowarnings.warnadded; LF endings.Full API Changes (for automation & release notes)
Added
Tracer.__init__(galaxies, cosmology=None, fields=None)—fieldsis last, so positionalTracer(galaxies, cosmology)callers are unaffected; validated by_validate_fields(mirrors_validate_galaxies, namesMassField, rejects aGalaxyinfields); stored astracer.fields(a list,[]by default; serialises via autonerves likegalaxies).Tracer.members/Tracer.members_ascending_redshift— galaxies followed by fields;plane_redshifts,planes,grid_2d_at_redshift_from,deflections_yx_2d_from,convergence_2d_from,potential_2d_from,time_delays_from,has,cls_list_from,extract_attribute,extract_profilenow run over members.galaxies_ascending_redshift,extract_attributes_of_galaxiesandgalaxy_image_2d_dict_from(and everything keyed off it) stay galaxies-only.Tracer.sliced_tracer_from(..., fields=None)— fields snapped to the nearest sliced plane likeline_of_sight_galaxies._warn_if_no_light_is_behind_any_mass(galaxies, fields=None)— fields are mass-bearing for the ordering check.Result.max_log_likelihood_fields—[]when the model declares nofields; passed to the tracer built for the positions threshold.LOSSampler.fields_from() -> List[ag.MassField],LOSSampler.galaxies_and_fields_from() -> (halos, sheets),LOSSampler.galaxies_from(sheets_as_fields=False); sampling moved into a cached_sample_population().autolens.analysis.model_util.mass_field_from(lens, shear=True, mass_sheet=False, potential=False, redshift=None) -> af.Model—ValueErrorwhenlenshas nomass;mass_sheet.centre/potential.centreare the sameTuplePriorobject aslens.mass.centre.autolens.MassFieldre-export (from autogalaxy.galaxy.mass_field import MassField).autolens.jax.register_tracer_classesregistersMassFieldwithno_flatten=("redshift",)and walkstracer.fields.Changed Behaviour
from_coolest: a COOLESTMassFieldentity becomes anal.MassFieldintracer.fields, not aGalaxyintracer.galaxies. Lensing quantities of the returned tracer are unchanged; only where the profiles land differs. Two existing tests that asserted the old landing place were updated minimally (test__round_trip__relative_file_path,test__from_coolest__skipped_template_returns_mass_profiles_only).to_coolest: a tracer'sfieldsare exported asfield_{j}entities; the legacy peel of galaxy-attached sheets keeps itsmass_field_{i}names and output (byte-identity test).FitImaging.model_images_of_planes_list/FitInterferometer.model_visibilities_of_planes_listskipMassFieldplane members (they are not keys of the galaxy dictionary).LOSSampler: the sampled population is cached on the instance, sogalaxies_from()followed byfields_from()describe a single line of sight (previously eachgalaxies_from()call withseed=Noneredrew).Migration
Galaxy(redshift=..., shear=ExternalShear(...))scripts and models keep working with the same result identifier. To adopt fields:al.Tracer(galaxies=[lens, source], fields=[al.MassField(redshift=0.5, shear=al.mp.ExternalShear(...))]), or in a modelfields=af.Collection(field=al.model_util.mass_field_from(lens=lens)). Workspace adoption is the epic's phases 3–5.Docs
docs/api/galaxy.rst(MassField,Tracer(fields=)),docs/api/modeling.rst(new "Model Utilities" block incl.mass_field_from),docs/overview/overview_1_start_here.md("External fields").Fixture
test_autolens/interop/fixtures/legacy_galaxy_attached.json— captured frommainbefore this change; lives underfixtures/because the repo's.gitignoreexcludes every directory namedfiles.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PqfNuorRbom8ZjD9i9hvWo
Generated by Claude Code