Skip to content

feat: MassField — standalone MassProfile-only container for external fields - #621

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/mass-field-class
Sep 17, 2026
Merged

Jammy2211 merged 1 commit into
mainfrom
feature/mass-field-class

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

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; any MassProfile is accepted). It is its own thing, not a Galaxy subclass, so per-galaxy surfaces never see it, while a Galaxies plane can hold it beside galaxies: it adds to every mass quantity and contributes zeros to every image.

The mass-sum implementation Galaxy already had (cls_list_from, deflections_yx_2d_from with its deflections_memo routing, convergence_2d_from, potential_2d_from, extract_attribute) moves verbatim into a MassProfileAggregate mixin that both classes use. Galaxy's public behaviour is unchanged: its import path, constructor, dict() output and hash/eq are untouched, and a pinned PyAutoFit identifier for a galaxy-attached shear model (captured on main at 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 analysis fields slot, is a PyAutoLens task blocked on this merging. Closes #620.

API Changes

Added ag.MassField and the MassProfileAggregate mixin (autogalaxy/galaxy/mass_aggregate.py); Galaxy now inherits the mixin last in its MRO, so every existing name resolves as before (has still comes from af.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 in test_autogalaxy/galaxy/test_mass_field.py).
  • Identifier pin: 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" on main and on this branch (asserted in test__galaxy_identifier_pin__unchanged_by_mass_field_refactor).
  • Witness: ag.MassField(redshift=0.5, shear=ag.mp.ExternalShear(0.05, 0.05)).deflections_yx_2d_from(grid) equals the same profile on an ag.Galaxy; isinstance(field, ag.Galaxy) is False; ag.MassField(redshift=0.5, bulge=ag.lp.Sersic()) raises GalaxyException naming bulge.
  • af.Model(ag.MassField, redshift=0.5, shear=af.Model(ag.mp.ExternalShear)) composes (5 free parameters), instantiates, and renders in model.info under a fields collection.
  • JAX three-way probe (NumPy / xp=jnp / jax.jit with galaxies traced, grid closed over, per PyAutoMind/complete/2026/07/public-register-galaxies-classes.md) with ag.Galaxies([Galaxy(mass=Isothermal), MassField(shear=ExternalShear)]): all three np.allclose; the field is not dropped inside the jit; no no_flatten needed (JAX 0.11.1). Unit tests stay NumPy-only per repo policy.
  • git diff --name-only touches nothing under autogalaxy/config/; no warnings.warn added; LF endings.
  • CI green on Python 3.12 and 3.13.
Full API Changes (for automation & release notes)

Added

  • autogalaxy.MassField(redshift, **kwargs)af.ModelObject + MassProfileAggregate. Validates the redshift as Galaxy does; rejects a list with the same-shaped message; rejects any non-MassProfile component with a GalaxyException naming 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; has from af.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 a MassField.
  • autogalaxy.galaxy.mass_aggregate.MassProfileAggregate — mixin holding cls_list_from, deflections_yx_2d_from, convergence_2d_from, potential_2d_from, extract_attribute (moved verbatim from Galaxy; defines no has).

Changed Behaviour

  • None. Galaxy now inherits MassProfileAggregate (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 Galaxies plane places on a MassField

Calls 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 by Galaxies.extract_attribute), .redshift, and use as a dict key in galaxy_image_2d_dict_from (hence __hash__/__eq__). The OperateImageList list/dict methods build on the collection's own image_2d_list_from and add no further per-member calls.

Docs

  • docs/api/galaxy.rst lists MassField; docs/overview/overview_1_start_here.md gains an "External Fields" section.

Migration

  • None required. Existing Galaxy(redshift=..., shear=ExternalShear(...)) scripts keep working and keep their result identifier. A script that adopts MassField composes 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

…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
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.

feat: MassField — standalone MassProfile-only container for external fields

2 participants