diff --git a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst index 8dd74444a2..15fd1d09b8 100644 --- a/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst +++ b/docs/sphinx/source/reference/effects_on_pv_system_output/spectrum.rst @@ -15,5 +15,6 @@ Spectrum spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm spectrum.spectral_factor_pvspec + spectrum.spectral_factor_jrc spectrum.sr_to_qe spectrum.qe_to_sr diff --git a/docs/sphinx/source/whatsnew/v0.11.0.rst b/docs/sphinx/source/whatsnew/v0.11.0.rst index 2fdcd1c444..fe137a92c7 100644 --- a/docs/sphinx/source/whatsnew/v0.11.0.rst +++ b/docs/sphinx/source/whatsnew/v0.11.0.rst @@ -44,9 +44,14 @@ Enhancements efficiency ([unitless]) and vice versa. The conversion functions are :py:func:`pvlib.spectrum.sr_to_qe` and :py:func:`pvlib.spectrum.qe_to_sr` respectively. (:issue:`2040`, :pull:`2041`) -* Add function :py:func:`pvlib.spectrum.spectral_factor_pvspec`, which calculates the - spectral mismatch factor as a function of absolute airmass and clearsky index - using the PVSPEC model. (:issue:`1950`, :issue:`2065`, :pull:`2072`) +* Add function :py:func:`pvlib.spectrum.spectral_factor_pvspec`, which + calculates the spectral mismatch factor as a function of absolute airmass and + clearsky index using the PVSPEC model. + (:issue:`1950`, :issue:`2065`, :pull:`2072`) +* Add function :py:func:`pvlib.spectrum.spectral_factor_jrc`, which calculates + the spectral mismatch factor as a function of airmass and clearsky + index using the JRC model. + (:issue:`1950`, :issue:`2065`, :issue:`2087`, :pull:`2088`) * Added extraterrestrial and direct spectra of the ASTM G173-03 standard with the new function :py:func:`pvlib.spectrum.get_reference_spectra`. (:issue:`1963`, :pull:`2039`) diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 3194fd17a2..b95f221066 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -8,6 +8,7 @@ spectral_factor_firstsolar, spectral_factor_sapm, spectral_factor_pvspec, + spectral_factor_jrc, sr_to_qe, qe_to_sr ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 9b644b2ef9..d2fc4dcf1d 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -794,6 +794,116 @@ def spectral_factor_pvspec(airmass_absolute, clearsky_index, return mismatch +def spectral_factor_jrc(airmass, clearsky_index, module_type=None, + coefficients=None): + r""" + Estimate a technology-specific spectral mismatch modifier from + airmass and clear sky index using the JRC model. + + The JRC spectral mismatch model includes the effects of cloud cover on + the irradiance spectrum. Model coefficients are derived using measurements + of irradiance and module performance at the Joint Research Centre (JRC) in + Ispra, Italy (45.80N, 8.62E). Coefficients for two module types are + available via the ``module_type`` parameter. More details on the model can + be found in [1]_. + + Parameters + ---------- + airmass : numeric + relative airmass. [unitless] + + clearsky_index: numeric + clear sky index. [unitless] + + module_type : str, optional + One of the following PV technology strings from [1]_: + + * ``'cdte'`` - anonymous CdTe module. + * ``'multisi'`` - anonymous multicrystalline Si module. + + coefficients : array-like, optional + user-defined coefficients, if not using one of the default coefficient + sets via the ``module_type`` parameter. + + Returns + ------- + mismatch: numeric + spectral mismatch factor (unitless) which is multiplied + with broadband irradiance reaching a module's cells to estimate + effective irradiance, i.e., the irradiance that is converted to + electrical current. + + Notes + ----- + The JRC model parameterises the spectral mismatch factor as a function + of air mass and the clear sky index as follows: + + .. math:: + + M = 1 + a_1(e^{-k_c}-e^{-1}) + a_2(k_c-1)+a_3(AM-1.5), + + where :math:`M` is the spectral mismatch factor, :math:`k_c` is the clear + sky index, :math:`AM` is the air mass, :math:`e` is Euler's number, and + :math:`a_1, a_2, a_3` are module-specific coefficients. The :math:`a_n` + coefficients available via the ``coefficients`` parameter differ from the + :math:`k_n` coefficients documented in [1]_ in that they are normalised by + the specific short-circuit current value, :math:`I_{sc0}^*`, which is the + expected short-circuit current at standard test conditions indoors. The + model used to estimate the air mass (denoted as :math:`AM`) is not stated + in the original publication. The authors of [1]_ used the ESRA model [2]_ + to estimate the clear sky GHI for the clear sky index, which is the ratio + of GHI to clear sky GHI. Also, prior to the calculation of :math:`k_c`, the + irradiance measurements were corrected for angle of incidence using the + Martin and Ruiz model [3]_. + + References + ---------- + .. [1] Huld, T., Sample, T., and Dunlop, E., 2009. A simple model + for estimating the influence of spectrum variations on PV performance. + In Proceedings of the 24th European Photovoltaic Solar Energy + Conference, Hamburg, Germany pp. 3385-3389. 2009. Accessed at: + https://www.researchgate.net/publication/256080247 + .. [2] Rigollier, C., Bauer, O., and Wald, L., 2000. On the clear sky model + of the ESRA—European Solar Radiation Atlas—with respect to the Heliosat + method. Solar energy, 68(1), pp.33-48. + :doi:`10.1016/S0038-092X(99)00055-9` + .. [3] Martin, N. and Ruiz, J. M., 2001. Calculation of the PV modules + angular losses under field conditions by means of an analytical model. + Solar Energy Materials and Solar Cells, 70(1), 25-38. + :doi:`10.1016/S0927-0248(00)00408-6` + """ + + _coefficients = {} + _coefficients['multisi'] = (0.00172, 0.000508, 0.00000357) + _coefficients['cdte'] = (0.000643, 0.000130, 0.0000108) + # normalise coefficients by I*sc0, see [1] + _coefficients = { + 'multisi': tuple(x / 0.00348 for x in _coefficients['multisi']), + 'cdte': tuple(x / 0.001150 for x in _coefficients['cdte']) + } + if module_type is not None and coefficients is None: + coefficients = _coefficients[module_type.lower()] + elif module_type is None and coefficients is not None: + pass + elif module_type is None and coefficients is None: + raise ValueError('No valid input provided, both module_type and ' + + 'coefficients are None. module_type can be one of ' + + ", ".join(_coefficients.keys())) + else: + raise ValueError('Cannot resolve input, must supply only one of ' + + 'module_type and coefficients. module_type can be ' + + 'one of' ", ".join(_coefficients.keys())) + + coeff = coefficients + mismatch = ( + 1 + + coeff[0] * (np.exp(-clearsky_index) - np.exp(-1)) + + coeff[1] * (clearsky_index - 1) + + coeff[2] * (airmass - 1.5) + ) + return mismatch + + def sr_to_qe(sr, wavelength=None, normalize=False): """ Convert spectral responsivities to quantum efficiencies. diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 5c89078bc1..969fb819b8 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -426,6 +426,54 @@ def test_spectral_factor_pvspec_supplied_ambiguous(): coefficients=None) +@pytest.mark.parametrize("module_type,expected", [ + ('multisi', np.array([1.06129, 1.03098, 1.01155, 0.99849])), + ('cdte', np.array([1.09657, 1.05594, 1.02763, 0.97740])), +]) +def test_spectral_factor_jrc(module_type, expected): + ams = np.array([1.0, 1.5, 2.0, 1.5]) + kcs = np.array([0.4, 0.6, 0.8, 1.4]) + out = spectrum.spectral_factor_jrc(ams, kcs, + module_type=module_type) + assert np.allclose(expected, out, atol=1e-4) + + +@pytest.mark.parametrize("module_type,expected", [ + ('multisi', np.array([1.06129, 1.03098, 1.01155, 0.99849])), + ('cdte', np.array([1.09657, 1.05594, 1.02763, 0.97740])), +]) +def test_spectral_factor_jrc_series(module_type, expected): + ams = pd.Series([1.0, 1.5, 2.0, 1.5]) + kcs = pd.Series([0.4, 0.6, 0.8, 1.4]) + out = spectrum.spectral_factor_jrc(ams, kcs, + module_type=module_type) + assert isinstance(out, pd.Series) + assert np.allclose(expected, out, atol=1e-4) + + +def test_spectral_factor_jrc_supplied(): + # use the multisi coeffs + coeffs = (0.494, 0.146, 0.00103) + out = spectrum.spectral_factor_jrc(1.0, 0.8, coefficients=coeffs) + expected = 1.01052106 + assert_allclose(out, expected, atol=1e-4) + + +def test_spectral_factor_jrc_supplied_redundant(): + # Error when specifying both module_type and coefficients + coeffs = (0.494, 0.146, 0.00103) + with pytest.raises(ValueError, match='supply only one of'): + spectrum.spectral_factor_jrc(1.0, 0.8, module_type='multisi', + coefficients=coeffs) + + +def test_spectral_factor_jrc_supplied_ambiguous(): + # Error when specifying neither module_type nor coefficients + with pytest.raises(ValueError, match='No valid input provided'): + spectrum.spectral_factor_jrc(1.0, 0.8, module_type=None, + coefficients=None) + + @pytest.fixture def sr_and_eqe_fixture(): # Just some arbitrary data for testing the conversion functions