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.
Tagging @qiuhan06 — this issue tracks finishing the multi-band grid-rotation work you prototyped on the
dev_Qbranches, so Hannah Stacey's JWST multi-band fits can model both pointing offsets and roll-angle misalignments through a singleDatasetModelparameter.How this started
From Slack earlier today (paraphrased):
Diagnosis
Reproduced your failure mode on ephemeral worktrees patched with the autoarray-side Track 1 changes only. Five
chi² ≈ 0self-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_dictwas never being shifted bygrid_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. Youradapt_images.pypatch ("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_Qonto currentmain, dropped the*MultiBandmass profile workaround (PowerLawMultiBand,ExternalShearMultiBand,NFWMCRScatterLudlowSphMultiBand,PowerLawMultipoleMultiBand) and the unrelatedmge_model_V2_from, cleaned up debug prints, and added regression tests.What ships
aa.DatasetModel.grid_rotation_angle— new degrees-CCW float parameter, default0.0, mirroring the existinggrid_offsetpattern exactly.Grid2D.subtracted_and_rotated_from(offset, angle, xp)andGrid2DIrregular.subtracted_and_rotated_from(...)— shift-then-rotate transform, used byFitDataset.gridsandAdaptImages.updated_via_instance_from.AdaptImages.updated_via_instance_fromnow takes adataset_modelargument and applies the same shift+rotate to cached mesh grids. This is the actual fix for the "weird source reconstruction".subtracted_and_rotated_fromunit tests in PyAutoArray + newchi² ≈ 0simulate-and-fit tests in bothtest_autogalaxy/imaging/test_simulate_and_fit_imaging.pyandtest_autolens/imaging/test_simulate_and_fit_imaging.pyfor offset-only, rotation-only, and combined cases. These will catch any future regression of the latent mesh-grid misalignment bug.autolens_workspace/scripts/multi/features/dataset_offsets/{simulator,modeling}.pyextended 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
Plus the four self-consistent simulate-and-fit
chi² ≈ 0tests (three intest_autogalaxy, three intest_autolens) — each one simulates a profile with an offset centre and/or rotated ellipse, fits with an identity-frame profile + matchingDatasetModelparameters, and assertschi_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.