feat: MassField — standalone MassProfile-only container for external fields - #621
Merged
Merged
Conversation
…rnal fields `ag.MassField(redshift, **mass_profiles)` holds the mass components that describe the tidal field of everything outside the modelled system (ExternalShear, MassSheet, ExternalPotential; any MassProfile accepted). It is its own class, not a Galaxy subclass: a Galaxies plane can hold it beside galaxies, where it adds to every mass quantity and contributes zeros to every image. The mass-sum methods Galaxy already had (cls_list_from, the memo-routed deflections_yx_2d_from, convergence_2d_from, potential_2d_from, extract_attribute) move verbatim into a MassProfileAggregate mixin shared by both classes. Galaxy's import path, constructor, dict() output and hash/eq are unchanged; the PyAutoFit identifier of a galaxy-attached shear model is pinned as a regression test. No deprecation warning, no prior config changes. Phase 1 of the mass-field epic; the PyAutoLens Tracer(fields=) integration follows as phase 2. Closes #620. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PqfNuorRbom8ZjD9i9hvWo
This was referenced Sep 18, 2026
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
Adds
ag.MassField(redshift, **mass_profiles): a standalone,MassProfile-only container for the mass components that describe the tidal field of everything outside the modelled system (ExternalShear,MassSheet,ExternalPotential; anyMassProfileis accepted). It is its own thing, not aGalaxysubclass, so per-galaxy surfaces never see it, while aGalaxiesplane can hold it beside galaxies: it adds to every mass quantity and contributes zeros to every image.The mass-sum implementation
Galaxyalready had (cls_list_from,deflections_yx_2d_fromwith itsdeflections_memorouting,convergence_2d_from,potential_2d_from,extract_attribute) moves verbatim into aMassProfileAggregatemixin that both classes use.Galaxy's public behaviour is unchanged: its import path, constructor,dict()output and hash/eq are untouched, and a pinned PyAutoFitidentifierfor a galaxy-attached shear model (captured onmainat 67f6ca6) is asserted as a regression test. The galaxy-attached form stays fully supported, with no deprecation warning.Phase 1 of the mass-field epic (PyAutoMind
draft/feature/autogalaxy/mass_field_epic.md). Phase 2,Tracer(galaxies, fields=)and the analysisfieldsslot, is a PyAutoLens task blocked on this merging. Closes #620.API Changes
Added
ag.MassFieldand theMassProfileAggregatemixin (autogalaxy/galaxy/mass_aggregate.py);Galaxynow inherits the mixin last in its MRO, so every existing name resolves as before (hasstill comes fromaf.ModelObject). No removals, renames or signature changes.See full details below.
Test Plan
pytest test_autogalaxy -q -n auto: 1236 passed, 0 failed, 0 skipped (24 new tests intest_autogalaxy/galaxy/test_mass_field.py).af.Collection(galaxies=af.Collection(lens=af.Model(ag.Galaxy, redshift=0.5, mass=af.Model(ag.mp.Isothermal), shear=af.Model(ag.mp.ExternalShear)))).identifier == "7b251058cdae562470abab6203bacf72"onmainand on this branch (asserted intest__galaxy_identifier_pin__unchanged_by_mass_field_refactor).ag.MassField(redshift=0.5, shear=ag.mp.ExternalShear(0.05, 0.05)).deflections_yx_2d_from(grid)equals the same profile on anag.Galaxy;isinstance(field, ag.Galaxy)is False;ag.MassField(redshift=0.5, bulge=ag.lp.Sersic())raisesGalaxyExceptionnamingbulge.af.Model(ag.MassField, redshift=0.5, shear=af.Model(ag.mp.ExternalShear))composes (5 free parameters), instantiates, and renders inmodel.infounder afieldscollection.xp=jnp/jax.jitwithgalaxiestraced, grid closed over, perPyAutoMind/complete/2026/07/public-register-galaxies-classes.md) withag.Galaxies([Galaxy(mass=Isothermal), MassField(shear=ExternalShear)]): all threenp.allclose; the field is not dropped inside the jit; nono_flattenneeded (JAX 0.11.1). Unit tests stay NumPy-only per repo policy.git diff --name-onlytouches nothing underautogalaxy/config/; nowarnings.warnadded; LF endings.Full API Changes (for automation & release notes)
Added
autogalaxy.MassField(redshift, **kwargs)—af.ModelObject+MassProfileAggregate. Validates the redshift asGalaxydoes; rejects a list with the same-shaped message; rejects any non-MassProfilecomponent with aGalaxyExceptionnaming the key. Zero-light interface:image_2d_list_from → [],image_2d_list_unbinned_from → [],image_2d_from → zeros (to_array),image_2d_unbinned_from → zeros on the over-sampled grid;hasfromaf.ModelObject(False for any light class since only mass profiles are stored).profile_dict,dict(),__eq__,__hash__,__repr__(MassField(redshift=0.5, shear, mass_sheet));autonerves.dictable.from_dict(field.dict())rebuilds aMassField.autogalaxy.galaxy.mass_aggregate.MassProfileAggregate— mixin holdingcls_list_from,deflections_yx_2d_from,convergence_2d_from,potential_2d_from,extract_attribute(moved verbatim fromGalaxy; defines nohas).Changed Behaviour
Galaxynow inheritsMassProfileAggregate(last in the MRO:Galaxy, ModelObject, OperateImageList, OperateImage, MassProfileAggregate, object); the moved methods are byte-identical and the identifier pin proves PyAutoFit hashes nothing differently.Member contract a
Galaxiesplane places on aMassFieldCalls made on each member by
Galaxies/OperateImageGalaxies:image_2d_from(grid, xp, operated_only),image_2d_unbinned_from(...),deflections_yx_2d_from(grid, xp),convergence_2d_from(grid, xp),potential_2d_from(grid, xp),has(cls),cls_list_from(cls),__dict__(read byGalaxies.extract_attribute),.redshift, and use as a dict key ingalaxy_image_2d_dict_from(hence__hash__/__eq__). TheOperateImageListlist/dict methods build on the collection's ownimage_2d_list_fromand add no further per-member calls.Docs
docs/api/galaxy.rstlistsMassField;docs/overview/overview_1_start_here.mdgains an "External Fields" section.Migration
Galaxy(redshift=..., shear=ExternalShear(...))scripts keep working and keep their result identifier. A script that adoptsMassFieldcomposes a different model and gets a new identifier, which is expected.Generated by the PyAutoLabs agent workflow.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PqfNuorRbom8ZjD9i9hvWo
Generated by Claude Code