Repository: lenstronomy/lenstronomy

Issue:
LensParam class instantiation
The LensParam class creates an instance of LensModel internally from just the lens_model_list argument in order to access information stored in each lens profile class. However, this feature causes a crash when a lens profile requires some keyword arguments in order to be created. For example, the TabulatedDeflections class requires a user-specified class to be passed to the profile. I believe this issue can be solved by passing in profile_kwargs_list argument to the LensParam class and using it to create the instance of LensModel

Hints from issue comments:

Solution patch:
diff --git a/lenstronomy/LensModel/lens_param.py b/lenstronomy/LensModel/lens_param.py
index ee518e53..2789c5b2 100644
--- a/lenstronomy/LensModel/lens_param.py
+++ b/lenstronomy/LensModel/lens_param.py
@@ -17,6 +17,7 @@ class LensParam(object):
         num_images=0,
         solver_type="NONE",
         num_shapelet_lens=0,
+        profile_kwargs_list=None,
     ):
         """
 
@@ -31,6 +32,9 @@ class LensParam(object):
          (only relevant in this class when 'SHAPELETS' is the solver type)
         :param num_shapelet_lens: integer, number of shapelets in the lensing potential
          (only relevant when 'SHAPELET' lens model is used)
+         :param profile_kwargs_list: list of dicts, keyword arguments used to initialize profile classes
+            in the same order of the lens_model_list. If any of the profile_kwargs are None, then that
+            profile will be initialized using default settings.
         """
 
         self.model_list = lens_model_list
@@ -42,7 +46,9 @@ class LensParam(object):
         # PF: Here the instantiation of SinglePlane is only made to deal with
         # a list of lens parameters. It does not seem to matter whether multi-
         # plane lensing or line-of-sight effects are activated.
-        lens_model = SinglePlane(lens_model_list=lens_model_list)
+        lens_model = SinglePlane(
+            lens_model_list=lens_model_list, profile_kwargs_list=profile_kwargs_list
+        )
 
         name_list = []
         for func in lens_model.func_list:
diff --git a/lenstronomy/LightModel/light_param.py b/lenstronomy/LightModel/light_param.py
index 75cc1fec..9ad38240 100644
--- a/lenstronomy/LightModel/light_param.py
+++ b/lenstronomy/LightModel/light_param.py
@@ -17,6 +17,7 @@ class LightParam(object):
         kwargs_upper=None,
         param_type="light",
         linear_solver=True,
+        profile_kwargs_list=None,
     ):
         """
 
@@ -28,8 +29,13 @@ class LightParam(object):
          source light)
         :param linear_solver: bool, if True fixes the linear amplitude parameters 'amp' (avoid sampling) such that they
          get overwritten by the linear solver solution.
+        :param profile_kwargs_list: list of dicts, keyword arguments used to initialize light model
+            profile classes in the same order of the light_model_list. If any of the profile_kwargs
+            are None, then that profile will be initialized using default settings.
         """
-        self._lightModel = LightModel(light_model_list=light_model_list)
+        self._lightModel = LightModel(
+            light_model_list=light_model_list, profile_kwargs_list=profile_kwargs_list
+        )
         self._param_name_list = self._lightModel.param_name_list
         self._type = param_type
         self.model_list = light_model_list
diff --git a/lenstronomy/Sampling/parameters.py b/lenstronomy/Sampling/parameters.py
index c965b074..ff44c617 100644
--- a/lenstronomy/Sampling/parameters.py
+++ b/lenstronomy/Sampling/parameters.py
@@ -427,6 +427,10 @@ class Param(object):
         kwargs_fixed_tracer_source_updated = self._fix_joint_param(
             kwargs_fixed_tracer_source, self._joint_source_light_with_tracer
         )
+        if "lens_profile_kwargs_list" in kwargs_model.keys():
+            lens_profile_kwargs_list = kwargs_model["lens_profile_kwargs_list"]
+        else:
+            lens_profile_kwargs_list = None
         self.lensParams = LensParam(
             self._lens_model_list,
             kwargs_fixed_lens_updated,
@@ -436,7 +440,14 @@ class Param(object):
             kwargs_lower=kwargs_lower_lens,
             kwargs_upper=kwargs_upper_lens,
             num_shapelet_lens=num_shapelet_lens,
+            profile_kwargs_list=lens_profile_kwargs_list,
         )
+        if "lens_light_profile_kwargs_list" in kwargs_model.keys():
+            lens_light_profile_kwargs_list = kwargs_model[
+                "lens_light_profile_kwargs_list"
+            ]
+        else:
+            lens_light_profile_kwargs_list = None
         self.lensLightParams = LightParam(
             self._lens_light_model_list,
             kwargs_fixed_lens_light_updated,
@@ -444,7 +455,14 @@ class Param(object):
             linear_solver=linear_solver,
             kwargs_lower=kwargs_lower_lens_light,
             kwargs_upper=kwargs_upper_lens_light,
+            profile_kwargs_list=lens_light_profile_kwargs_list,
         )
+        if "source_light_profile_kwargs_list" in kwargs_model.keys():
+            source_light_profile_kwargs_list = kwargs_model[
+                "source_light_profile_kwargs_list"
+            ]
+        else:
+            source_light_profile_kwargs_list = None
         self.sourceParams = LightParam(
             self._source_light_model_list,
             kwargs_fixed_source_updated,
@@ -452,6 +470,7 @@ class Param(object):
             linear_solver=linear_solver,
             kwargs_lower=kwargs_lower_source,
             kwargs_upper=kwargs_upper_source,
+            profile_kwargs_list=source_light_profile_kwargs_list,
         )
         self.pointSourceParams = PointSourceParam(
             self._point_source_model_list,
@@ -621,7 +640,6 @@ class Param(object):
         :param kwargs_tracer_source: tracer of the source light keyword argument list
         :return: numpy array of parameters
         """
-
         args = self.lensParams.set_params(kwargs_lens)
         args += self.sourceParams.set_params(kwargs_source)
         args += self.lensLightParams.set_params(kwargs_lens_light)


Test patch:
diff --git a/test/test_Sampling/test_parameters.py b/test/test_Sampling/test_parameters.py
index b28d7ca7..7cb2f370 100644
--- a/test/test_Sampling/test_parameters.py
+++ b/test/test_Sampling/test_parameters.py
@@ -46,6 +46,45 @@ class TestParam(object):
         )
         self.param_class.print_setting()
 
+    def test_instantiate_with_profile_kwargs_list(self):
+
+        kwargs_model = {
+            "lens_model_list": ["TABULATED_DEFLECTIONS"],
+            "source_light_model_list": ["GAUSSIAN"],
+            "lens_light_model_list": ["SERSIC"],
+            "point_source_model_list": ["LENSED_POSITION"],
+            "multi_plane": True,
+            "lens_redshift_list": [0.5],
+            "z_source": 2,
+            "source_redshift_list": [0.5],
+            "distance_ratio_sampling": None,
+            "cosmology_sampling": None,
+            "lens_profile_kwargs_list": [{"custom_class": None}],
+            "lens_light_profile_kwargs_list": [None],
+            "source_light_profile_kwargs_list": [None],
+        }
+        kwargs_param = {
+            "num_point_source_list": [2],
+            "lens_redshift_sampling_indexes": [0],
+            "source_redshift_sampling_indexes": [0],
+            "image_plane_source_list": [True],
+        }
+        kwargs_fixed_lens = [{"gamma": 1.9}]  # for SPEP lens
+        kwargs_fixed_source = [{"sigma": 0.1, "center_x": 0.2, "center_y": 0.2}]
+        kwargs_fixed_ps = [{"ra_image": [-1, 1], "dec_image": [-1, 1]}]
+        kwargs_fixed_lens_light = [{}]
+        kwargs_fixed_cosmo = [{}]
+        self.param_class = Param(
+            kwargs_model,
+            kwargs_fixed_lens=kwargs_fixed_lens,
+            kwargs_fixed_source=kwargs_fixed_source,
+            kwargs_fixed_lens_light=kwargs_fixed_lens_light,
+            kwargs_fixed_ps=kwargs_fixed_ps,
+            kwargs_fixed_special=kwargs_fixed_cosmo,
+            **kwargs_param
+        )
+        self.param_class.print_setting()
+
     def test_num_param(self):
         num_param, list = self.param_class.num_param()
         assert list[0] == "theta_E_lens0"