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 ed6cdc0b51..8041d8f49b 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 @@ -10,5 +10,6 @@ Spectrum spectrum.get_example_spectral_response spectrum.get_am15g spectrum.calc_spectral_mismatch_field + spectrum.spectral_factor_caballero spectrum.spectral_factor_firstsolar spectrum.spectral_factor_sapm diff --git a/docs/sphinx/source/whatsnew/v0.9.6.rst b/docs/sphinx/source/whatsnew/v0.9.6.rst index 7ce9f89ed7..50b11750bf 100644 --- a/docs/sphinx/source/whatsnew/v0.9.6.rst +++ b/docs/sphinx/source/whatsnew/v0.9.6.rst @@ -49,6 +49,8 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* Added a function :py:func:`pvlib.spectrum.spectral_factor_caballero` + to estimate spectral mismatch modifiers from atmospheric conditions. (:pull:`1296`) * Added a new irradiance decomposition model :py:func:`pvlib.irradiance.louche`. (:pull:`1705`) * Add optional encoding parameter to :py:func:`pvlib.iotools.read_tmy3`. (:issue:`1732`, :pull:`1737`) @@ -112,6 +114,7 @@ Contributors * Siddharth Kaul (:ghuser:`k10blogger`) * Kshitiz Gupta (:ghuser:`kshitiz305`) * Stefan de Lange (:ghuser:`langestefan`) +* Jose Antonio Caballero (:ghuser:`Jacc0027`) * Andy Lam (:ghuser:`@andylam598`) * :ghuser:`ooprathamm` * Kevin Anderson (:ghuser:`kandersolar`) diff --git a/pvlib/spectrum/__init__.py b/pvlib/spectrum/__init__.py index 70d3918b49..6c97df978e 100644 --- a/pvlib/spectrum/__init__.py +++ b/pvlib/spectrum/__init__.py @@ -3,6 +3,7 @@ calc_spectral_mismatch_field, get_am15g, get_example_spectral_response, + spectral_factor_caballero, spectral_factor_firstsolar, spectral_factor_sapm, ) diff --git a/pvlib/spectrum/mismatch.py b/pvlib/spectrum/mismatch.py index 3319ee12cd..10f8db3564 100644 --- a/pvlib/spectrum/mismatch.py +++ b/pvlib/spectrum/mismatch.py @@ -450,3 +450,123 @@ def spectral_factor_sapm(airmass_absolute, module): spectral_loss = pd.Series(spectral_loss, airmass_absolute.index) return spectral_loss + + +def spectral_factor_caballero(precipitable_water, airmass_absolute, aod500, + module_type=None, coefficients=None): + r""" + Estimate a technology-specific spectral mismatch modifier from + airmass, aerosol optical depth, and atmospheric precipitable water, + using the Caballero model. + + The model structure was motivated by examining the effect of these three + atmospheric parameters on simulated irradiance spectra and spectral + modifiers. However, the coefficient values reported in [1]_ and + available here via the ``module_type`` parameter were determined + by fitting the model equations to spectral factors calculated from + global tilted spectral irradiance measurements taken in the city of + Jaén, Spain. See [1]_ for details. + + Parameters + ---------- + precipitable_water : numeric + atmospheric precipitable water. [cm] + + airmass_absolute : numeric + absolute (pressure-adjusted) airmass. [unitless] + + aod500 : numeric + atmospheric aerosol optical depth at 500 nm. [unitless] + + module_type : str, optional + One of the following PV technology strings from [1]_: + + * ``'cdte'`` - anonymous CdTe module. + * ``'monosi'``, - anonymous sc-si module. + * ``'multisi'``, - anonymous mc-si- module. + * ``'cigs'`` - anonymous copper indium gallium selenide module. + * ``'asi'`` - anonymous amorphous silicon module. + * ``'perovskite'`` - anonymous pervoskite module. + + coefficients : array-like, optional + user-defined coefficients, if not using one of the default coefficient + sets via the ``module_type`` parameter. + + Returns + ------- + modifier: 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. + + References + ---------- + .. [1] Caballero, J.A., Fernández, E., Theristis, M., + Almonacid, F., and Nofuentes, G. "Spectral Corrections Based on + Air Mass, Aerosol Optical Depth and Precipitable Water + for PV Performance Modeling." + IEEE Journal of Photovoltaics 2018, 8(2), 552-558. + :doi:`10.1109/jphotov.2017.2787019` + """ + + if module_type is None and coefficients is None: + raise ValueError('Must provide either `module_type` or `coefficients`') + if module_type is not None and coefficients is not None: + raise ValueError('Only one of `module_type` and `coefficients` should ' + 'be provided') + + # Experimental coefficients from [1]_. + # The extra 0/1 coefficients at the end are used to enable/disable + # terms to match the different equation forms in Table 1. + _coefficients = {} + _coefficients['cdte'] = ( + 1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046, + -0.0182, 0, 0.0095, 0.0068, 0, 1) + _coefficients['monosi'] = ( + 0.9706, 0.0377, -0.0123, 0.0025, -0.0002, 0.0159, + -0.0165, 0, -0.0016, -0.0027, 1, 0) + _coefficients['multisi'] = ( + 0.9836, 0.0254, -0.0085, 0.0016, -0.0001, 0.0094, + -0.0132, 0, -0.0002, -0.0011, 1, 0) + _coefficients['cigs'] = ( + 0.9801, 0.0283, -0.0092, 0.0019, -0.0001, 0.0117, + -0.0126, 0, -0.0011, -0.0019, 1, 0) + _coefficients['asi'] = ( + 1.1060, -0.0848, 0.0302, -0.0076, 0.0006, -0.1283, + 0.0986, -0.0254, 0.0156, 0.0146, 1, 0) + _coefficients['perovskite'] = ( + 1.0637, -0.0491, 0.0180, -0.0047, 0.0004, -0.0773, + 0.0583, -0.0159, 0.01251, 0.0109, 1, 0) + + if module_type is not None: + coeff = _coefficients[module_type] + else: + coeff = coefficients + + # Evaluate spectral correction factor + ama = airmass_absolute + aod500_ref = 0.084 + pw_ref = 1.4164 + + f_AM = ( + coeff[0] + + coeff[1] * ama + + coeff[2] * ama**2 + + coeff[3] * ama**3 + + coeff[4] * ama**4 + ) + # Eq 6, with Table 1 + f_AOD = (aod500 - aod500_ref) * ( + coeff[5] + + coeff[10] * coeff[6] * ama + + coeff[11] * coeff[6] * np.log(ama) + + coeff[7] * ama**2 + ) + # Eq 7, with Table 1 + f_PW = (precipitable_water - pw_ref) * ( + coeff[8] + + coeff[9] * np.log(ama) + ) + modifier = f_AM + f_AOD + f_PW # Eq 5 + return modifier diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 23a7c837ee..793eaacfdf 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -271,3 +271,47 @@ def test_spectral_factor_sapm(sapm_module_params, airmass, expected): assert_series_equal(out, expected, check_less_precise=4) else: assert_allclose(out, expected, atol=1e-4) + + +@pytest.mark.parametrize("module_type,expected", [ + ('asi', np.array([0.9108, 0.9897, 0.9707, 1.0265, 1.0798, 0.9537])), + ('perovskite', np.array([0.9422, 0.9932, 0.9868, 1.0183, 1.0604, 0.9737])), + ('cdte', np.array([0.9824, 1.0000, 1.0065, 1.0117, 1.042, 0.9979])), + ('multisi', np.array([0.9907, 0.9979, 1.0203, 1.0081, 1.0058, 1.019])), + ('monosi', np.array([0.9935, 0.9987, 1.0264, 1.0074, 0.9999, 1.0263])), + ('cigs', np.array([1.0014, 1.0011, 1.0270, 1.0082, 1.0029, 1.026])), +]) +def test_spectral_factor_caballero(module_type, expected): + ams = np.array([3.0, 1.5, 3.0, 1.5, 1.5, 3.0]) + aods = np.array([1.0, 1.0, 0.02, 0.02, 0.08, 0.08]) + pws = np.array([1.42, 1.42, 1.42, 1.42, 4.0, 1.0]) + out = spectrum.spectral_factor_caballero(pws, ams, aods, + module_type=module_type) + assert np.allclose(expected, out, atol=1e-3) + + +def test_spectral_factor_caballero_supplied(): + # use the cdte coeffs + coeffs = ( + 1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046, + -0.0182, 0, 0.0095, 0.0068, 0, 1) + out = spectrum.spectral_factor_caballero(1, 1, 1, coefficients=coeffs) + expected = 1.0021964 + assert_allclose(out, expected, atol=1e-3) + + +def test_spectral_factor_caballero_supplied_redundant(): + # Error when specifying both module_type and coefficients + coeffs = ( + 1.0044, 0.0095, -0.0037, 0.0002, 0.0000, -0.0046, + -0.0182, 0, 0.0095, 0.0068, 0, 1) + with pytest.raises(ValueError): + spectrum.spectral_factor_caballero(1, 1, 1, module_type='cdte', + coefficients=coeffs) + + +def test_spectral_factor_caballero_supplied_ambiguous(): + # Error when specifying neither module_type nor coefficients + with pytest.raises(ValueError): + spectrum.spectral_factor_caballero(1, 1, 1, module_type=None, + coefficients=None)