diff --git a/docs/api.rst b/docs/api.rst index 94e149b1..2c3a800b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -47,6 +47,14 @@ Irradiance measurements can also be checked for consistency. quality.irradiance.check_irradiance_consistency_qcrad +The validity of daytime GHI and DHI measurements can also be checked using +a more restrictive lower limit provided in [2]_. + +.. autosummary:: + :toctree: generated/ + + quality.irradiance.check_ghi_lower_limit_nollas + GHI and POA irradiance can be validated against clearsky values to eliminate data that is unrealistically high. @@ -196,6 +204,12 @@ the quality check. .. [1] C. N. Long and Y. Shi, An Automated Quality Assessment and Control Algorithm for Surface Radiation Measurements, The Open Atmospheric Science Journal 2, pp. 23-37, 2008. + :doi:`10.2174/1874282300802010023` +.. [2] F. M. Nollas, G. A. Salazar, and C. A. Gueymard, Quality control + procedure for 1-minute pyranometric measurements of global and + shadowband-based diffuse solar irradiance, Renewable Energy, 202, + pp. 40-55, 2023. + :doi:`10.1016/j.renene.2022.11.056` Features ======== diff --git a/docs/whatsnew/0.2.0.rst b/docs/whatsnew/0.2.0.rst index d93f9d83..1cc58f1b 100644 --- a/docs/whatsnew/0.2.0.rst +++ b/docs/whatsnew/0.2.0.rst @@ -15,7 +15,8 @@ Breaking Changes Enhancements ~~~~~~~~~~~~ - +* Add QC function for checking GHI lower limit according to Nollas et al. (2023). + :py:func:`~pvanalytics.quality.irradiance.check_ghi_lower_limit_nollas` (:pull:`174`) Bug Fixes ~~~~~~~~~ @@ -44,3 +45,4 @@ Contributors * Kevin Anderson (:ghuser:`kanderso-nrel`) * Cliff Hansen (:ghuser:`cwhanse`) * Abhishek Parikh (:ghuser:`abhisheksparikh`) +* Adam R. Jensen (:ghuser:`AdamRJensen`) diff --git a/pvanalytics/quality/irradiance.py b/pvanalytics/quality/irradiance.py index 508ff459..07df7b2b 100644 --- a/pvanalytics/quality/irradiance.py +++ b/pvanalytics/quality/irradiance.py @@ -357,6 +357,83 @@ def check_irradiance_consistency_qcrad(solar_zenith, ghi, dhi, dni, return consistent_components, diffuse_ratio_limit +def _ghi_lower_limit_nollas(solar_zenith): + r"""Calculate lower limit for GHI according to Nollas et al. + + See [1]_ for further information. + + .. math:: + ghi_min = (6.5331 - 0.065502 * solar\_zenith + 0.00018312 * solar\_zenith^{2}) / + (1 + 0.01113*solar\_zenith) + + Parameters + ---------- + solar_zenith : Series + Solar zenith angle in degrees + + Returns + ------- + Series + Minimum possible GHI in :math:`W/m^2` + + References + ---------- + .. [1] F. M. Nollas, G. A. Salazar, and C. A. Gueymard, Quality control + procedure for 1-minute pyranometric measurements of global and + shadowband-based diffuse solar irradiance, Renewable Energy, 202, + pp. 40-55, 2023. + :doi:`10.1016/j.renene.2022.11.056` + """ # noqa: E501 + ghi_min = ((6.5331-0.065502*solar_zenith + 0.00018312*solar_zenith**2) / + (1 + 0.01113*solar_zenith)) + + # Set limit to nan when the sun is below the horizon + ghi_min[solar_zenith >= 90] = np.nan + + return ghi_min + + +def check_ghi_lower_limit_nollas(ghi, solar_zenith): + r"""Test for lower limit on GHI using empirical limit from Nollas (2023). + + Test is applied to each GHI value. A GHI value passes if value > + lower bound. The lower bound is from [1]_ and calculated as: + + .. math:: + lb = (6.5331 - 0.065502 * solar\_zenith + 0.00018312 * solar\_zenith^{2}) / + (1 + 0.01113*solar\_zenith) + + Parameters + ---------- + ghi : Series + Global horizontal irradiance in :math:`W/m^2` + solar_zenith : Series + Solar zenith angle in degrees + + Returns + ------- + Series + False where valuez are below the minimum limit. + + References + ---------- + .. [1] F. M. Nollas, G. A. Salazar, and C. A. Gueymard, Quality control + procedure for 1-minute pyranometric measurements of global and + shadowband-based diffuse solar irradiance, Renewable Energy, 202, + pp. 40-55, 2023. + :doi:`10.1016/j.renene.2022.11.056` + """ # noqa: E501 + ghi_lb = _ghi_lower_limit_nollas(solar_zenith) + + ghi_lower_limit_flag = quality.util.check_limits( + ghi, lower_bound=ghi_lb, inclusive_lower=False) + + # Set flags to True when ghi_lb is nan (e.g., when solar_zenith>=90) + ghi_lower_limit_flag[np.isnan(ghi_lb)] = True + + return ghi_lower_limit_flag + + def clearsky_limits(measured, clearsky, csi_max=1.1): """Identify irradiance values which do not exceed clearsky values. diff --git a/pvanalytics/tests/quality/test_irradiance.py b/pvanalytics/tests/quality/test_irradiance.py index d5f36295..2bbb7746 100644 --- a/pvanalytics/tests/quality/test_irradiance.py +++ b/pvanalytics/tests/quality/test_irradiance.py @@ -179,6 +179,26 @@ def test_check_irradiance_consistency_qcrad(irradiance_qcrad): check_names=False) +def test_ghi_lower_limit_nollas(irradiance_qcrad): + """Test thet minimum GHI is correctly calculated using Nollas equation.""" + data = irradiance_qcrad + actual_ghi = irradiance._ghi_lower_limit_nollas(data['solar_zenith']) + expected_ghi = pd.Series([3.548128, 3.548128, 3.548128, 6.5331, 4.7917837, + 1.9559971, 1.3039082, np.nan, 6.5331, 6.5331, + 1.9559971, 1.3039082, 1.3039082, np.nan]) + assert_series_equal(actual_ghi, expected_ghi, check_names=False) + + +def test_check_ghi_lower_limit_nollas(irradiance_qcrad): + """Test that min GHI is checked correctly.""" + data = irradiance_qcrad + actual_flags = irradiance.check_ghi_lower_limit_nollas( + data['ghi'], data['solar_zenith']) + expected_flags = pd.Series([0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + dtype=bool) + assert_series_equal(actual_flags, expected_flags, check_names=False) + + @pytest.fixture def times(): """One hour of times at 10 minute frequency.