From 10907bce6476d14f0f16b9ff0134db7c86567d66 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 13 Apr 2026 09:40:59 +0100 Subject: [PATCH 1/2] docs: update add_a_profile guide for transform(rotate_back=True) Update the Isothermal example, "Do I Rotate Back?" section, and template to use the new automatic back-rotation decorator parameter. Co-Authored-By: Claude Opus 4.6 --- scripts/guides/advanced/add_a_profile.py | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/guides/advanced/add_a_profile.py b/scripts/guides/advanced/add_a_profile.py index 5fd2fb3f5..a8ad480b0 100644 --- a/scripts/guides/advanced/add_a_profile.py +++ b/scripts/guides/advanced/add_a_profile.py @@ -111,7 +111,7 @@ def __init__( self.slope = 2.0 @aa.grid_dec.to_vector_yx - @aa.grid_dec.transform + @aa.grid_dec.transform(rotate_back=True) def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): """ Calculate the deflection angles on a grid of (y,x) arc-second coordinates. @@ -119,6 +119,9 @@ def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): The input grid of (y,x) coordinates are transformed to a coordinate system centred on the profile centre with and rotated based on the position angle defined from its `ell_comps` (this is described fully below). + The ``rotate_back=True`` parameter tells the decorator to automatically rotate the returned deflection + vectors back from the profile's reference frame to the original observer frame. + The numerical backend can be selected via the ``xp`` argument, allowing this method to be used with both NumPy and JAX (e.g. inside ``jax.jit``-compiled code). This is described fully later in this example. @@ -154,11 +157,7 @@ def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): psi, ) ) - return self.rotated_grid_from_reference_frame_from( - grid=xp.multiply(factor, xp.vstack((deflection_y, deflection_x)).T), - xp=xp, - **kwargs, - ) + return xp.multiply(factor, xp.vstack((deflection_y, deflection_x)).T) """ @@ -446,11 +445,16 @@ def deflections_yx_2d_from(self, grid: aa.Grid2D, xp=np, **kwargs): """ __Do I Rotate Back?__ -The ``aa.grid_dec.transform`` decorator does not rotate the deflection angles back to the original reference frame. +When computing deflection angles in the profile's rotated reference frame, the results must be rotated back to the +original observer frame before they can be used for ray-tracing. -This is because deflection angles are typically applied in the same reference frame as the mass profile itself, -making such a rotation unnecessary. When implementing a custom mass profile, you should therefore ensure that any -required rotation of the deflection angles is correctly accounted for after they are calculated. +The ``@aa.grid_dec.transform(rotate_back=True)`` parameter handles this automatically. When ``rotate_back=True`` is +set, the decorator calls ``self.rotated_grid_from_reference_frame_from(...)`` on the returned deflection vectors, +rotating them back to the observer frame. This is why the ``Isothermal`` example above uses +``@aa.grid_dec.transform(rotate_back=True)`` on its ``deflections_yx_2d_from`` method. + +For scalar quantities like ``convergence_2d_from`` and ``potential_2d_from``, no back-rotation is needed — scalars +are the same in any reference frame. These methods use ``@aa.grid_dec.transform`` without ``rotate_back``. __Lens Modeling__ @@ -579,10 +583,14 @@ def __init__( # super().__init__(centre=centre, ell_comps=(0.0, 0.0)) @aa.grid_dec.to_vector_yx - @aa.grid_dec.transform + @aa.grid_dec.transform(rotate_back=True) def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): """ REQUIRED: The function is key for all lensing calculations and must be implemented. + + The ``rotate_back=True`` ensures the returned deflection vectors are automatically rotated + back from the profile's reference frame to the observer frame. Just return the raw + deflection (y, x) array — the decorator handles the rotation. """ pass From 419f71811df966fb0f2771926b1e39685abd515f Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Mon, 13 Apr 2026 09:47:52 +0100 Subject: [PATCH 2/2] docs: precise frame-convention explanation in Do I Rotate Back Clarify that back-rotation depends on which frame the returned components are expressed in, not unconditionally on the quantity being a vector. Document scalar, vector, and spin-2 cases. Co-Authored-By: Claude Opus 4.6 --- scripts/guides/advanced/add_a_profile.py | 41 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/scripts/guides/advanced/add_a_profile.py b/scripts/guides/advanced/add_a_profile.py index a8ad480b0..f9e151329 100644 --- a/scripts/guides/advanced/add_a_profile.py +++ b/scripts/guides/advanced/add_a_profile.py @@ -119,8 +119,9 @@ def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): The input grid of (y,x) coordinates are transformed to a coordinate system centred on the profile centre with and rotated based on the position angle defined from its `ell_comps` (this is described fully below). - The ``rotate_back=True`` parameter tells the decorator to automatically rotate the returned deflection - vectors back from the profile's reference frame to the original observer frame. + Because this method computes deflection components using the rotated grid coordinates (i.e. the + components are expressed in the profile's frame), ``rotate_back=True`` is set so the decorator + automatically rotates them back to the observer frame. The numerical backend can be selected via the ``xp`` argument, allowing this method to be used with both NumPy and JAX (e.g. inside ``jax.jit``-compiled @@ -445,16 +446,29 @@ def deflections_yx_2d_from(self, grid: aa.Grid2D, xp=np, **kwargs): """ __Do I Rotate Back?__ -When computing deflection angles in the profile's rotated reference frame, the results must be rotated back to the -original observer frame before they can be used for ray-tracing. +Whether the returned result needs back-rotation depends on what kind of quantity the function returns +and which coordinate frame its components are expressed in. -The ``@aa.grid_dec.transform(rotate_back=True)`` parameter handles this automatically. When ``rotate_back=True`` is -set, the decorator calls ``self.rotated_grid_from_reference_frame_from(...)`` on the returned deflection vectors, -rotating them back to the observer frame. This is why the ``Isothermal`` example above uses -``@aa.grid_dec.transform(rotate_back=True)`` on its ``deflections_yx_2d_from`` method. +**Scalars** (convergence, potential): frame-invariant — no back-rotation needed. -For scalar quantities like ``convergence_2d_from`` and ``potential_2d_from``, no back-rotation is needed — scalars -are the same in any reference frame. These methods use ``@aa.grid_dec.transform`` without ``rotate_back``. +**Vectors** (deflection angles): it depends on how the function computes them. The ``@aa.grid_dec.transform`` +decorator transforms the input grid into the profile's rotated reference frame. If the function then computes +deflection components ``(alpha_y, alpha_x)`` using that rotated grid — as the ``Isothermal`` example above does — +those components are expressed in the profile's frame. Since the ray-tracing code expects observer-frame +components, they must be rotated back. Setting ``rotate_back=True`` handles this automatically by calling +``self.rotated_grid_from_reference_frame_from(...)`` on the result. + +However, back-rotation is **not** always needed for vectors. If a function computes a scalar quantity in the +profile frame (e.g. a radial deflection magnitude) and then reconstructs the Cartesian vector using +observer-frame geometry, the result is already in the observer frame. In that case, ``rotate_back`` should +remain ``False``. + +The rule is: look at which coordinate basis the returned components are expressed in. If they use the rotated +basis, set ``rotate_back=True``. If they are already observer-frame components, leave it as ``False``. + +**Spin-2 quantities** (shear): these transform under a coordinate rotation by twice the profile angle (the +spin-2 transformation law). This is not handled by ``rotate_back`` — shear methods must apply the ``2 * angle`` +rotation manually via ``self.rotated_grid_from_reference_frame_from(grid=..., angle=self.angle(xp) * 2)``. __Lens Modeling__ @@ -588,9 +602,10 @@ def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): """ REQUIRED: The function is key for all lensing calculations and must be implemented. - The ``rotate_back=True`` ensures the returned deflection vectors are automatically rotated - back from the profile's reference frame to the observer frame. Just return the raw - deflection (y, x) array — the decorator handles the rotation. + Set ``rotate_back=True`` if your function computes deflection components using the rotated + grid coordinates (i.e. the components are expressed in the profile's frame). The decorator + will rotate them back to the observer frame automatically. If your function reconstructs + observer-frame components from scalar quantities, leave ``rotate_back=False``. """ pass