diff --git a/docs/sphinx/source/whatsnew/v0.9.1.rst b/docs/sphinx/source/whatsnew/v0.9.1.rst index fdd7f7184c..d8831ad9cd 100644 --- a/docs/sphinx/source/whatsnew/v0.9.1.rst +++ b/docs/sphinx/source/whatsnew/v0.9.1.rst @@ -1,6 +1,6 @@ .. _whatsnew_0910: -v0.9.1 (December 1, 2021) +v0.9.1 (TBD) -------------------------- Breaking changes @@ -16,6 +16,9 @@ Bug fixes ~~~~~~~~~ * Address round-off effects in :py:func:`pvlib.ivtools.utils._schumaker_qspline` (:issue:`1311`, :pull:`1315`) +* Fixed a bug in :py:func:`pvlib.spectrum.spectrl2` where negative spectral irradiance + values were returned when the sun is behind the plane of array (:issue:`1348`, :pull:`1349`) + Testing ~~~~~~~ @@ -31,3 +34,5 @@ Contributors * Cliff Hansen (:ghuser:`cwhanse`) * :ghuser:`Antoine-0` * :ghuser:`Carlosbogo` +* Christian Weickhmann (:ghuser:`cweickhmann`) +* Kevin Anderson (:ghuser:`kanderso-nrel`) diff --git a/pvlib/spectrum/spectrl2.py b/pvlib/spectrum/spectrl2.py index 15f006b288..f679825014 100644 --- a/pvlib/spectrum/spectrl2.py +++ b/pvlib/spectrum/spectrl2.py @@ -260,6 +260,11 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, 2-5 kasten1966 kasten1966 kastenyoung1989 =================== ========== ========== =============== + This implementation also deviates from the reference by including a + check for angles of incidence greater than 90 degrees; without this, + the model might return negative spectral irradiance values when the + sun is behind the plane of array. + References ---------- .. [1] Bird, R, and Riordan, C., 1984, "Simple solar spectral model for @@ -357,10 +362,16 @@ def spectrl2(apparent_zenith, aoi, surface_tilt, ground_albedo, Is = (Ir + Ia + Ig) * Cs # Eq 3-1 # calculate spectral irradiance on a tilted surface, Eq 3-18 - Ibeam = Id * cosd(aoi) - - # don't need surface_azimuth if we provide projection_ratio - projection_ratio = cosd(aoi) / cosZ + # Note: clipping cosd(aoi) to >=0 is not in the reference, but is necessary + # to prevent nonsense values when the sun is behind the plane of array. + # The same constraint is applied in irradiance.haydavies when not + # supplying `projection_ratio`. + aoi_projection_nn = np.maximum(cosd(aoi), 0) # GH 1348 + Ibeam = Id * aoi_projection_nn + + # don't need surface_azimuth if we provide projection_ratio. + # Also constrain cos zenith to avoid blowup, as in irradiance.haydavies + projection_ratio = aoi_projection_nn / np.maximum(cosZ, 0.01745) Isky = pvlib.irradiance.haydavies(surface_tilt=surface_tilt, surface_azimuth=None, dhi=Is, diff --git a/pvlib/tests/test_spectrum.py b/pvlib/tests/test_spectrum.py index 657fa2675d..cfcd50d1fe 100644 --- a/pvlib/tests/test_spectrum.py +++ b/pvlib/tests/test_spectrum.py @@ -92,3 +92,17 @@ def test_dayofyear_missing(spectrl2_data): kwargs.pop('dayofyear') with pytest.raises(ValueError, match='dayofyear must be specified'): _ = spectrum.spectrl2(**kwargs) + + +def test_aoi_gt_90(spectrl2_data): + # test that returned irradiance values are non-negative when aoi > 90 + # see GH #1348 + kwargs, _ = spectrl2_data + kwargs['apparent_zenith'] = 70 + kwargs['aoi'] = 130 + kwargs['surface_tilt'] = 60 + + spectra = spectrum.spectrl2(**kwargs) + for key in ['poa_direct', 'poa_global']: + message = f'{key} contains negative values for aoi>90' + assert np.all(spectra[key] >= 0), message