Repository: lenstronomy/lenstronomy
Pull request #719: some docstring changes, small bug fix in LensedPosition
Author: login: ahuang314
Created: 2025-03-06T01:15:26Z | Merged: 2025-03-07T18:37:12Z

Small bug fix in the amplitude calculations in LensedPosition when self.k_list is not None

point_source_frame_list is a bit confusing and the docstring is not very clear; we should address this at some point


Diff:
diff --git a/lenstronomy/LensModel/profile_list_base.py b/lenstronomy/LensModel/profile_list_base.py
index 9aa4931a3..f417417b1 100644
--- a/lenstronomy/LensModel/profile_list_base.py
+++ b/lenstronomy/LensModel/profile_list_base.py
@@ -247,9 +247,8 @@ def lens_class(
     """Generate class instance of single lens.
 
     :param lens_type: string, lens model type
-    :param profile_kwargs: dict, keyword arguments used to initialize profile classes If
-        None, then the profile is initialized using default settings
-    :param custom_class: custom class
+    :param profile_kwargs: dict, keyword arguments used to initialize profile classes.
+        If None, then the profile is initialized using default settings
     :return: class instance of the lens model type
     """
     if profile_kwargs is None:
diff --git a/lenstronomy/PointSource/Types/base_ps.py b/lenstronomy/PointSource/Types/base_ps.py
index 9440323af..cf6123330 100644
--- a/lenstronomy/PointSource/Types/base_ps.py
+++ b/lenstronomy/PointSource/Types/base_ps.py
@@ -20,19 +20,25 @@ def __init__(
 
         :param lens_model: instance of the LensModel() class
         :param fixed_magnification: bool. If True, magnification
-         ratio of point sources is fixed to the one given by the lens model
+            ratio of point sources is fixed to the one given by the lens model
         :param additional_images: bool. If True, search for additional images of the same source is conducted.
-        :param index_lens_model_list: list (length of different patches/bands) of integer lists, e.g., [[0, 1], [2, 3]];
-         evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
-         to have a specified band/frame assigned to it
-        :param point_source_frame_list: list of lists mirroring the structure of the image positions.
-         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
-         appearing
+        :param index_lens_model_list: list (length of different patches/bands) of integer lists, evaluating a subset of
+            the lens models per individual bands. e.g., [[0], [2, 3], [1]] assigns the 0th lens model to the 0th band,
+            the 2nd and 3rd lens models to the 1st band, and the 1st lens model to the 2nd band.
+            If this keyword is set, the image positions need to have a specified band/frame assigned to it
+        :param point_source_frame_list: list of ints assigning each image to a specific band/frame. Only relevant in
+            LENSED_POSITION. e.g. if LENSED_POSITION contains 4 images, we can assign them each to one of the bands with
+            point_source_frame_list = [1, 2, 0, 1], where point_source_frame_list[i] = n means that the i-th image belongs
+            to band n.
         :param redshift: redshift of the source, only required for multiple source redshifts
         :type redshift: None or float
         """
         self._redshift = redshift
         self._lens_model = lens_model
+
+        # Combine point_source_frame_list and index_lens_model_list to obtain k_list,
+        # which assigns each image the corresponding lens models from its band
+        # e.g. in the example used above, k_list = [[2, 3], [1], [0], [2, 3]]
         if index_lens_model_list is not None:
             k_list = []
             for point_source_frame in point_source_frame_list:
@@ -40,10 +46,12 @@ def __init__(
             self.k_list = k_list
         else:
             self.k_list = None
+
         if self._lens_model is None:
             self._solver = None
         else:
             self._solver = LensEquationSolver(lens_model)
+
         self._fixed_magnification = fixed_magnification
         self.additional_images = additional_images
         if fixed_magnification is True and additional_images is True:
diff --git a/lenstronomy/PointSource/Types/lensed_position.py b/lenstronomy/PointSource/Types/lensed_position.py
index a63c1689c..0a4d62772 100644
--- a/lenstronomy/PointSource/Types/lensed_position.py
+++ b/lenstronomy/PointSource/Types/lensed_position.py
@@ -130,7 +130,7 @@ def image_amplitude(
                 for i in range(len(ra_image)):
                     mag.append(
                         self._lens_model.magnification(
-                            ra_image, dec_image, kwargs_lens, k=self.k_list[i]
+                            ra_image[i], dec_image[i], kwargs_lens, k=self.k_list[i]
                         )
                     )
             point_amp = kwargs_ps["source_amp"] * np.abs(mag)
@@ -162,7 +162,7 @@ def source_amplitude(self, kwargs_ps, kwargs_lens=None):
                 for i in range(len(ra_image)):
                     mag.append(
                         self._lens_model.magnification(
-                            ra_image, dec_image, kwargs_lens, k=self.k_list[i]
+                            ra_image[i], dec_image[i], kwargs_lens, k=self.k_list[i]
                         )
                     )
             point_amp = kwargs_ps["point_amp"]
diff --git a/lenstronomy/PointSource/point_source.py b/lenstronomy/PointSource/point_source.py
index 23e497d5f..ce1961505 100644
--- a/lenstronomy/PointSource/point_source.py
+++ b/lenstronomy/PointSource/point_source.py
@@ -27,30 +27,32 @@ def __init__(
         :param point_source_type_list: list of point source types
         :param lens_model: instance of the LensModel() class
         :param fixed_magnification_list: list of booleans (same length as point_source_type_list).
-         If True, magnification ratio of point sources is fixed to the one given by the lens model.
-         This option then requires to provide a 'source_amp' amplitude of the source brightness instead of
-         'point_amp' the list of image brightnesses.
+            If True, magnification ratio of point sources is fixed to the one given by the lens model.
+            This option then requires to provide a 'source_amp' amplitude of the source brightness instead of
+            'point_amp' the list of image brightnesses.
         :param additional_images_list: list of booleans (same length as point_source_type_list). If True, search for
-         additional images of the same source is conducted.
+            additional images of the same source is conducted.
         :param flux_from_point_source_list: list of booleans (optional), if set, will only return image positions
-         (for imaging modeling) for the subset of the point source lists that =True. This option enables to model
-         imaging data with transient point sources, when the point source positions are measured and present at a
-         different time than the imaging data, or when the image position is not known (such as for lensed GW)
+            (for imaging modeling) for the subset of the point source lists that =True. This option enables to model
+            imaging data with transient point sources, when the point source positions are measured and present at a
+            different time than the imaging data, or when the image position is not known (such as for lensed GW)
         :param magnification_limit: float >0 or None, if float is set and additional images are computed, only those
-         images will be computed that exceed the lensing magnification (absolute value) limit
+            images will be computed that exceed the lensing magnification (absolute value) limit
         :param save_cache: bool, saves image positions and only if delete_cache is executed, a new solution of the lens
-         equation is conducted with the lens model parameters provided. This can increase the speed as multiple times
-         the image positions are requested for the same lens model. Attention in usage!
+            equation is conducted with the lens model parameters provided. This can increase the speed as multiple times
+            the image positions are requested for the same lens model. Attention in usage!
         :param kwargs_lens_eqn_solver: keyword arguments specifying the numerical settings for the lens equation solver
-         see LensEquationSolver() class for details, such as:
-         min_distance=0.01, search_window=5, precision_limit=10**(-10), num_iter_max=100
-        :param index_lens_model_list: list (length of different patches/bands) of integer lists, e.g., [[0, 1], [2, 3]];
-         evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
-         to have a specified band/frame assigned to it
-        :param point_source_frame_list: list of lists mirroring the structure of the image positions.
-         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
-         appearing, e.g., four images and four cutouts, you can do [[0], [1], [2], [3]] when the frames
-         (ordered as the list) are in the same order as the point sources (indices)
+            see LensEquationSolver() class for details, such as:
+            min_distance=0.01, search_window=5, precision_limit=10**(-10), num_iter_max=100
+        :param index_lens_model_list: list (length of different patches/bands) of integer lists, evaluating a subset of
+            the lens models per individual bands. e.g., [[0], [2, 3], [1]] assigns the 0th lens model to the 0th band,
+            the 2nd and 3rd lens models to the 1st band, and the 1st lens model to the 2nd band.
+            If this keyword is set, the image positions need to have a specified band/frame assigned to it
+        :param point_source_frame_list: list of list of ints, assigns each model in point_source_type_list a frame list.
+            Only relevent for LENSED_POSITION. e.g. if point_source_type_list = ["UNLENSED", "LENSED_POSITION", "LENSED_POSITION"]
+            with point_source_frame_list = [None, [0, 1, 2], [1, 2, 0, 1]], then the first LENSED_POSITION will have a frame list of
+            [0, 1, 2] and the second LENSED_POSITION will have a frame list of [1, 2, 0, 1]. See docstring for point_source_frame_list
+            in PSBase for further details.
         :param redshift_list: list of redshifts (only required for multiple source redshifts)
         :type redshift_list: None or list
         """


Reviews:
user: login: sibirrer
body: Thank you! I have some comments
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T16:15:03Z
user: login: ahuang314
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T16:38:11Z
user: login: ahuang314
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T16:56:05Z
user: login: ahuang314
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-07T17:18:49Z
user: login: sibirrer
body: Looks great, thank you very much @ahuang314 !
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T18:37:06Z

Review comments:
diff_hunk: @@ -130,7 +130,7 @@ def image_amplitude(
                 for i in range(len(ra_image)):
                     mag.append(
                         self._lens_model.magnification(
-                            ra_image, dec_image, kwargs_lens, k=self.k_list[i]
+                            ra_image[i], dec_image[i], kwargs_lens, k=self.k_list[i]
path: lenstronomy/PointSource/Types/lensed_position.py
user: login: sibirrer
body: I see this was a bug. Is the format that is returned the same as for k_list = None (i.e. structure of the list), or do we have to re-map to a list of arrays?
created_at: 2025-03-07T16:14:14Z
author_association: COLLABORATOR
diff_hunk: @@ -20,14 +20,14 @@ def __init__(
 
         :param lens_model: instance of the LensModel() class
         :param fixed_magnification: bool. If True, magnification
-         ratio of point sources is fixed to the one given by the lens model
+            ratio of point sources is fixed to the one given by the lens model
         :param additional_images: bool. If True, search for additional images of the same source is conducted.
         :param index_lens_model_list: list (length of different patches/bands) of integer lists, e.g., [[0, 1], [2, 3]];
-         evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
-         to have a specified band/frame assigned to it
-        :param point_source_frame_list: list of lists mirroring the structure of the image positions.
-         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
-         appearing
+            evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
+            to have a specified band/frame assigned to it
+        :param point_source_frame_list: list of ints mirroring the structure of the image positions.
path: lenstronomy/PointSource/Types/base_ps.py
user: login: sibirrer
body: it is a list of lists of ints (complicated)
How about

:param point_source_frame_list: list of lists mirroring the structure of the image positions.
         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
         appearing, e.g., if you have one "LENSED_POSITION" model with four point sources appearing in four different
         frames, then point_source_frame_list = [[0, 1, 2, 3]]. If you have four types of "UNLENSED" point source models
         appearing in four different frames, then point_source_frame_list = [[0], [1], [2], [3]]
created_at: 2025-03-07T16:14:54Z
author_association: COLLABORATOR
diff_hunk: @@ -20,14 +20,14 @@ def __init__(
 
         :param lens_model: instance of the LensModel() class
         :param fixed_magnification: bool. If True, magnification
-         ratio of point sources is fixed to the one given by the lens model
+            ratio of point sources is fixed to the one given by the lens model
         :param additional_images: bool. If True, search for additional images of the same source is conducted.
         :param index_lens_model_list: list (length of different patches/bands) of integer lists, e.g., [[0, 1], [2, 3]];
-         evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
-         to have a specified band/frame assigned to it
-        :param point_source_frame_list: list of lists mirroring the structure of the image positions.
-         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
-         appearing
+            evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
+            to have a specified band/frame assigned to it
+        :param point_source_frame_list: list of ints mirroring the structure of the image positions.
path: lenstronomy/PointSource/Types/base_ps.py
user: login: ahuang314
body: Ah, this is in PSBase though. In PointSource class, this is a list of list of ints, but when it gets passed into PSBase, it would just be a list of ints
created_at: 2025-03-07T16:38:11Z
author_association: COLLABORATOR
diff_hunk: @@ -20,14 +20,14 @@ def __init__(
 
         :param lens_model: instance of the LensModel() class
         :param fixed_magnification: bool. If True, magnification
-         ratio of point sources is fixed to the one given by the lens model
+            ratio of point sources is fixed to the one given by the lens model
         :param additional_images: bool. If True, search for additional images of the same source is conducted.
         :param index_lens_model_list: list (length of different patches/bands) of integer lists, e.g., [[0, 1], [2, 3]];
-         evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
-         to have a specified band/frame assigned to it
-        :param point_source_frame_list: list of lists mirroring the structure of the image positions.
-         Integers correspond to the i'th list entry of index_lens_model_list indicating in which frame/band the image is
-         appearing
+            evaluating a subset of the lens models per individual bands. If this keyword is set, the image positions need
+            to have a specified band/frame assigned to it
+        :param point_source_frame_list: list of ints mirroring the structure of the image positions.
path: lenstronomy/PointSource/Types/base_ps.py
user: login: ahuang314
body: Also, point_source_frame_list seems to be completely unused in UNLENSED so I'm not sure that part is relevant.
created_at: 2025-03-07T16:56:05Z
author_association: COLLABORATOR
diff_hunk: @@ -130,7 +130,7 @@ def image_amplitude(
                 for i in range(len(ra_image)):
                     mag.append(
                         self._lens_model.magnification(
-                            ra_image, dec_image, kwargs_lens, k=self.k_list[i]
+                            ra_image[i], dec_image[i], kwargs_lens, k=self.k_list[i]
path: lenstronomy/PointSource/Types/lensed_position.py
user: login: ahuang314
body: The output format in both cases will be a 1 dimensional list/array containing the mags of each image
created_at: 2025-03-07T17:18:49Z
author_association: COLLABORATOR

PR comments:
user: login: codecov[bot]
created_at: 2025-03-06T01:35:04Z
body: ## [Codecov](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 96.42%. Comparing base [(`d0bb5d0`)](https://app.codecov.io/gh/lenstronomy/lenstronomy/commit/d0bb5d0ef6a6bc354a49ef166cc65839fda47615?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy) to head [(`751730d`)](https://app.codecov.io/gh/lenstronomy/lenstronomy/commit/751730d8cff42ca9cf751a4da7d8593a1bc9ce1d?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy).
> Report is 15 commits behind head on main.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##             main     #719   +/-   ##
=======================================
  Coverage   96.42%   96.42%           
=======================================
  Files         271      271           
  Lines       23519    23519           
=======================================
  Hits        22678    22678           
  Misses        841      841           
```

| [Files with missing lines](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy) | Coverage Δ | |
|---|---|---|
| [lenstronomy/LensModel/profile\_list\_base.py](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?src=pr&el=tree&filepath=lenstronomy%2FLensModel%2Fprofile_list_base.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy#diff-bGVuc3Ryb25vbXkvTGVuc01vZGVsL3Byb2ZpbGVfbGlzdF9iYXNlLnB5) | `99.70% <ø> (ø)` | |
| [lenstronomy/PointSource/Types/base\_ps.py](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?src=pr&el=tree&filepath=lenstronomy%2FPointSource%2FTypes%2Fbase_ps.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy#diff-bGVuc3Ryb25vbXkvUG9pbnRTb3VyY2UvVHlwZXMvYmFzZV9wcy5weQ==) | `100.00% <ø> (ø)` | |
| [lenstronomy/PointSource/Types/lensed\_position.py](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?src=pr&el=tree&filepath=lenstronomy%2FPointSource%2FTypes%2Flensed_position.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy#diff-bGVuc3Ryb25vbXkvUG9pbnRTb3VyY2UvVHlwZXMvbGVuc2VkX3Bvc2l0aW9uLnB5) | `100.00% <ø> (ø)` | |
| [lenstronomy/PointSource/point\_source.py](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719?src=pr&el=tree&filepath=lenstronomy%2FPointSource%2Fpoint_source.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy#diff-bGVuc3Ryb25vbXkvUG9pbnRTb3VyY2UvcG9pbnRfc291cmNlLnB5) | `98.32% <ø> (ø)` | |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/lenstronomy/lenstronomy/pull/719/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lenstronomy)

</details>
<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: ahuang314
created_at: 2025-03-07T17:48:18Z
body: @sibirrer OK I updated the docstring again, it should be extremely clear now
author_association: COLLABORATOR