diff --git a/docs/sphinx/source/whatsnew/v0.16.0.rst b/docs/sphinx/source/whatsnew/v0.16.0.rst index fe59c79b9b..1316a7a9c1 100644 --- a/docs/sphinx/source/whatsnew/v0.16.0.rst +++ b/docs/sphinx/source/whatsnew/v0.16.0.rst @@ -48,6 +48,9 @@ Enhancements * Map beam horizontal irradiance to ``bhi`` when :py:func:`~pvlib.iotools.get_era5` is called with ``map_variables=True``. (:pull:`2819`) +* Add support for :py:func:`pvlib.iam.schlick` to :py:class:`pvlib.modelchain.ModelChain`, + :py:meth:`pvlib.pvsystem.PVSystem.get_iam`, and :py:meth:`pvlib.pvsystem.Array.get_iam`. + (:issue:`2828`, :pull:`2832`) * Allow variables from multiple datasets to be requested at once in :py:func:`~pvlib.iotools.get_merra2`. (:pull:`2839`) diff --git a/pvlib/iam.py b/pvlib/iam.py index a540dca0ca..9ba981c5ea 100644 --- a/pvlib/iam.py +++ b/pvlib/iam.py @@ -816,6 +816,10 @@ def schlick(aoi): integrable alternative to the Fresnel equations for estimating IAM for diffuse irradiance [2]_ (see :py:func:`schlick_diffuse`). + .. warning:: The Schlick IAM model has not been validated for PV + performance modeling and is not commonly used in PV applications. + Users should consider these limitations when selecting models. + Parameters ---------- aoi : numeric @@ -874,6 +878,10 @@ def schlick_diffuse(surface_tilt): This function implements the integration of the Schlick approximation provided by Xie et al. [2]_. + .. warning:: The Schlick IAM model has not been validated for PV + performance modeling and is not commonly used in PV applications. + Users should consider these limitations when selecting models. + Parameters ---------- surface_tilt : numeric diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index d50c74596c..cf7a5bf7e5 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -336,8 +336,8 @@ class ModelChain: If not specified, the model will be inferred from the parameters that are common to all of system.arrays[i].module_parameters. Valid strings are 'physical', 'ashrae', 'sapm', 'martin_ruiz', - 'interp' and 'no_loss'. The ModelChain instance will be passed as the - first argument to a user-defined function. + 'schlick', 'interp' and 'no_loss'. The ModelChain instance will be + passed as the first argument to a user-defined function. spectral_model : str or function, optional Valid strings are: @@ -787,6 +787,8 @@ def aoi_model(self, model): self._aoi_model = self.sapm_aoi_loss elif model == 'martin_ruiz': self._aoi_model = self.martin_ruiz_aoi_loss + elif model == 'schlick': + self._aoi_model = self.schlick_aoi_loss elif model == 'interp': self._aoi_model = self.interp_aoi_loss elif model == 'no_loss': @@ -810,6 +812,10 @@ def infer_aoi_model(self): return self.martin_ruiz_aoi_loss elif iam._IAM_MODEL_PARAMS['interp'] <= params: return self.interp_aoi_loss + # 'schlick' is intentionally excluded from inference. Since it + # requires no parameters, it would always match and effectively + # become the default, which is undesirable because it is not + # commonly used for PV applications. else: raise ValueError('could not infer AOI model from ' 'system.arrays[i].module_parameters. Check that ' @@ -846,6 +852,12 @@ def martin_ruiz_aoi_loss(self): ) return self + def schlick_aoi_loss(self): + self.results.aoi_modifier = self.system.get_iam( + self.results.aoi, iam_model='schlick' + ) + return self + def interp_aoi_loss(self): self.results.aoi_modifier = self.system.get_iam( self.results.aoi, diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 5a070f5a51..02b16edaf1 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -396,7 +396,7 @@ def get_iam(self, aoi, iam_model='physical'): iam_model : string, default 'physical' The IAM model to be used. Valid strings are 'physical', 'ashrae', - 'martin_ruiz', 'sapm' and 'interp'. + 'martin_ruiz', 'sapm', 'interp', and 'schlick'. Returns ------- iam : numeric or tuple of numeric @@ -1187,7 +1187,7 @@ def get_iam(self, aoi, iam_model='physical'): iam_model : string, default 'physical' The IAM model to be used. Valid strings are 'physical', 'ashrae', - 'martin_ruiz', 'sapm' and 'interp'. + 'martin_ruiz', 'sapm', 'interp' and 'schlick'. Returns ------- @@ -1200,7 +1200,7 @@ def get_iam(self, aoi, iam_model='physical'): if `iam_model` is not a valid model name. """ model = iam_model.lower() - if model in ['ashrae', 'physical', 'martin_ruiz', 'interp']: + if model in ['ashrae', 'physical', 'martin_ruiz', 'interp', 'schlick']: func = getattr(iam, model) # get function at pvlib.iam # get all parameters from function signature to retrieve them from # module_parameters if present diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index c0db414cc5..6948e0f3bb 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -1451,7 +1451,7 @@ def constant_aoi_loss(mc): @pytest.mark.parametrize('aoi_model', [ - 'sapm', 'ashrae', 'physical', 'martin_ruiz' + 'sapm', 'ashrae', 'physical', 'martin_ruiz', 'schlick' ]) def test_aoi_models(sapm_dc_snl_ac_system, location, aoi_model, weather, mocker): @@ -1467,7 +1467,7 @@ def test_aoi_models(sapm_dc_snl_ac_system, location, aoi_model, @pytest.mark.parametrize('aoi_model', [ - 'sapm', 'ashrae', 'physical', 'martin_ruiz' + 'sapm', 'ashrae', 'physical', 'martin_ruiz', 'schlick' ]) def test_aoi_models_singleon_weather_single_array( sapm_dc_snl_ac_system, location, aoi_model, weather): diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 22a7789537..78aaed70c4 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -75,6 +75,15 @@ def test_PVSystem_get_iam_interp(mocker): spy.assert_called_once_with(aoi[0], **interp_module_params) +def test_PVSystem_get_iam_schlick(mocker): + system = pvsystem.PVSystem() + mocker.spy(_iam, 'schlick') + aoi = 0 + out = system.get_iam(aoi, 'schlick') + _iam.schlick.assert_called_once_with(aoi) + assert_allclose(out, 1.0, atol=0.01) + + def test__normalize_sam_product_names(): BAD_NAMES = [' -.()[]:+/",', 'Module[1]']