From 969d39bea0ea79c924612f8efff7326263a2396e Mon Sep 17 00:00:00 2001 From: adi-IL Date: Mon, 13 Jul 2026 00:50:21 +0530 Subject: [PATCH 1/2] Fix delta_kt_prime halving single valid neighbor difference (gh-1847) In irradiance._delta_kt_prime_dirint, when an interior point has only one valid neighbor (a missing value elsewhere in the series), the previous code substituted 0 for the missing neighbor difference and then multiplied by 0.5, halving the remaining valid difference. This produced an incorrect stability index delta_kt_prime and, downstream, incorrect DIRINT beam irradiance. Replace the manual endpoint patching with a row-wise mean of the next and previous absolute differences. pandas skips NaN values, so Perez eqn 2 (two neighbors), eqn 3 for endpoints, and interior gaps with one missing neighbor are all handled correctly. Add a regression test and a whatsnew entry. --- docs/sphinx/source/whatsnew/v0.15.3.rst | 7 ++++++- pvlib/irradiance.py | 18 ++++++++++-------- tests/test_irradiance.py | 11 +++++++++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.15.3.rst b/docs/sphinx/source/whatsnew/v0.15.3.rst index b79b9aed66..9d626c6fb1 100644 --- a/docs/sphinx/source/whatsnew/v0.15.3.rst +++ b/docs/sphinx/source/whatsnew/v0.15.3.rst @@ -13,7 +13,11 @@ Deprecations Bug fixes -~~~~~~~~~ +~~~~~~~~ + +* Fixed ``irradiance.dirint`` so that an interior missing value in the input + series no longer halves the single valid neighbor difference when computing + the stability index ``delta_kt_prime`` (Perez eqn 3). (:issue:`1847`) Enhancements @@ -53,4 +57,5 @@ Contributors * Karl Hill (:ghuser:`karlhillx`) * Mathias Aschwanden (:ghuser:`maschwanden`) * Yonry Zhu (:ghuser:`yonryzhu`) +* Aditya (:ghuser:`adi-IL`) * Darshan Gowda (:ghuser:`dgowdaan-cmyk`) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 98bd948286..f2c31fdafe 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -2035,16 +2035,18 @@ def _delta_kt_prime_dirint(kt_prime, use_delta_kt_prime, times): for use with :py:func:`_dirint_bins`. """ if use_delta_kt_prime: - # Perez eqn 2 + # Perez eqn 2 (interior points). Perez eqn 3 (boundary points and + # interior gaps where one neighbor is missing) is handled by taking + # the row-wise mean, which averages zero, one, or two valid neighbor + # differences because pandas skips NaN values. This avoids the + # previous behavior of halving a single valid difference when one + # neighbor is NaN (gh-1847). kt_next = kt_prime.shift(-1) kt_previous = kt_prime.shift(1) - # replace nan with values that implement Perez Eq 3 for first and last - # positions. Use kt_previous and kt_next to handle series of length 1 - kt_next.iloc[-1] = kt_previous.iloc[-1] - kt_previous.iloc[0] = kt_next.iloc[0] - delta_kt_prime = 0.5 * ((kt_prime - kt_next).abs().add( - (kt_prime - kt_previous).abs(), - fill_value=0)) + delta_kt_prime = pd.DataFrame({ + 'next': (kt_prime - kt_next).abs(), + 'prev': (kt_prime - kt_previous).abs(), + }).mean(axis=1) else: # do not change unless also modifying _dirint_bins delta_kt_prime = pd.Series(-1, index=times) diff --git a/tests/test_irradiance.py b/tests/test_irradiance.py index fbe9906358..cbb24fe4d6 100644 --- a/tests/test_irradiance.py +++ b/tests/test_irradiance.py @@ -719,6 +719,17 @@ def test_dirint_nans(): np.array([np.nan, np.nan, np.nan, np.nan, 893.1]), 1) +def test_delta_kt_prime_interior_nan(): + # regression test for gh-1847: an interior NaN in kt_prime must not + # halve the single valid neighbor difference. When only one neighbor + # is available (Perez eqn 3) delta_kt_prime equals that difference. + times = pd.date_range(start='2020-01-01', periods=5, freq='1h') + kt_prime = pd.Series([0.5, 0.6, np.nan, 0.7, 0.4], index=times) + result = irradiance._delta_kt_prime_dirint(kt_prime, True, times) + expected = pd.Series([0.1, 0.1, np.nan, 0.3, 0.3], index=times) + assert_series_equal(result, expected) + + def test_dirint_tdew(): times = pd.DatetimeIndex(['2014-06-24T12-0700', '2014-06-24T18-0700']) ghi = pd.Series([1038.62, 254.53], index=times) From 9045a4818d641e0f6c94d9694c34ad94a788304a Mon Sep 17 00:00:00 2001 From: adi-IL Date: Tue, 14 Jul 2026 04:55:50 +0530 Subject: [PATCH 2/2] Trim verbose comment in _delta_kt_prime_dirint (ponytail pass) --- pvlib/irradiance.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index f2c31fdafe..622f4303e6 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -2035,12 +2035,8 @@ def _delta_kt_prime_dirint(kt_prime, use_delta_kt_prime, times): for use with :py:func:`_dirint_bins`. """ if use_delta_kt_prime: - # Perez eqn 2 (interior points). Perez eqn 3 (boundary points and - # interior gaps where one neighbor is missing) is handled by taking - # the row-wise mean, which averages zero, one, or two valid neighbor - # differences because pandas skips NaN values. This avoids the - # previous behavior of halving a single valid difference when one - # neighbor is NaN (gh-1847). + # row-wise mean of neighbor abs-differences; pandas skips NaN so this + # covers 0/1/2 valid neighbors (Perez eqn 2 interior, eqn 3 boundary/gap) kt_next = kt_prime.shift(-1) kt_previous = kt_prime.shift(1) delta_kt_prime = pd.DataFrame({