Skip to content

Multi-band grid rotation: cleaning up Qiuhan's prototype #511

@Jammy2211

Description

@Jammy2211

Tagging @qiuhan06 — this issue tracks finishing the multi-band grid-rotation work you prototyped on the dev_Q branches, so Hannah Stacey's JWST multi-band fits can model both pointing offsets and roll-angle misalignments through a single DatasetModel parameter.

How this started

From Slack earlier today (paraphrased):

Jam: Hannah mentioned modelling rotations in multi-band JWST fits — when you get a chance, could you push your PR so I can implement it properly? Doesn't need to be anything proper, with the right code Claude can do the rest.

Qiuhan: My previous implementation (transferring and rotating the grids) failed — source reconstruction behaved very weird and I thought I was missing something. I'll push what I have to dev_Q branches on PyAutoArray / PyAutoGalaxy / PyAutoLens.

Qiuhan: Worry is, I don't have a good description of the issue I see 😅.

Jam: Claude will figure it out from the code.

Diagnosis

Reproduced your failure mode on ephemeral worktrees patched with the autoarray-side Track 1 changes only. Five chi² ≈ 0 self-consistency tests covering offset, rotation, both, parametric source, and pixelization source — all pass.

What was actually going wrong: on main, AdaptImages.galaxy_name_image_plane_mesh_grid_dict was never being shifted by grid_offset, let alone rotated. For multi-band fits with an adaptive pixelization (Hilbert / KMeans), the cached mesh grid sits in the original image-plane frame while the data grid (correctly) sits in the offset-and-rotated frame — they don't share a coordinate system, so the source reconstruction is genuinely misaligned. Your adapt_images.py patch ("saving before updates" commit) already fixes this; you just hadn't re-tested it.

So the prototype was the right approach. We've forward-ported the minimum set of changes from dev_Q onto current main, dropped the *MultiBand mass profile workaround (PowerLawMultiBand, ExternalShearMultiBand, NFWMCRScatterLudlowSphMultiBand, PowerLawMultipoleMultiBand) and the unrelated mge_model_V2_from, cleaned up debug prints, and added regression tests.

What ships

  • aa.DatasetModel.grid_rotation_angle — new degrees-CCW float parameter, default 0.0, mirroring the existing grid_offset pattern exactly.
  • Grid2D.subtracted_and_rotated_from(offset, angle, xp) and Grid2DIrregular.subtracted_and_rotated_from(...) — shift-then-rotate transform, used by FitDataset.grids and AdaptImages.updated_via_instance_from.
  • AdaptImages.updated_via_instance_from now takes a dataset_model argument and applies the same shift+rotate to cached mesh grids. This is the actual fix for the "weird source reconstruction".
  • Library tests (1,917 passing): new subtracted_and_rotated_from unit tests in PyAutoArray + new chi² ≈ 0 simulate-and-fit tests in both test_autogalaxy/imaging/test_simulate_and_fit_imaging.py and test_autolens/imaging/test_simulate_and_fit_imaging.py for offset-only, rotation-only, and combined cases. These will catch any future regression of the latent mesh-grid misalignment bug.
  • Workspace example: autolens_workspace/scripts/multi/features/dataset_offsets/{simulator,modeling}.py extended to inject and recover a 1.5° relative roll angle alongside the existing half-pixel offset.

PRs

Library-first ordering. Each repo's PR references this issue.

Verification

test_autoarray/ ............ 767 passed
test_autogalaxy/ ........... 873 passed
test_autolens/ ............. 277 passed

Plus the four self-consistent simulate-and-fit chi² ≈ 0 tests (three in test_autogalaxy, three in test_autolens) — each one simulates a profile with an offset centre and/or rotated ellipse, fits with an identity-frame profile + matching DatasetModel parameters, and asserts chi_squared = pytest.approx(0.0, abs=1e-4). These are the regression guards for this entire feature.

Time-to-fix narrative

For Qiuhan & anyone curious how this kind of work is shaped now: this whole job — diagnose the failure, port the prototype forward across three libraries + workspace, add ten regression tests, run all 1,917 unit tests green — was one CLI conversation. No JIRA tickets, no follow-up meetings, no "I'll get to it next sprint". The hardest part was the Phase 0 reproduction step that proved the math was right before changing any production code; everything after that was mechanical.

Closes once the four PRs merge.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions