From 9ef321d11cc33e22406c32da70f15b61f76b6c93 Mon Sep 17 00:00:00 2001 From: adi-IL Date: Mon, 13 Jul 2026 01:14:47 +0530 Subject: [PATCH] Stop ModelChain from filling missing DC/AC values with zero (gh-1409) ModelChain previously called fillna(0) on the single diode DC results and on the pvwatts AC result. This silently turned genuine missing (NaN) inputs into zero power, which is inconsistent with pvlib's 'NaN in, NaN out' policy and hides data gaps from users. Remove both fillna(0) calls. Nighttime already returns zero power from the single diode model (zero photocurrent), so only genuine gaps are now left as NaN. Add regression tests for the DC (cec) and pvwatts AC paths, and a whatsnew entry. --- docs/sphinx/source/whatsnew/v0.15.3.rst | 6 +++++ pvlib/modelchain.py | 10 +++++-- tests/test_modelchain.py | 36 +++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.15.3.rst b/docs/sphinx/source/whatsnew/v0.15.3.rst index b79b9aed66..7196127907 100644 --- a/docs/sphinx/source/whatsnew/v0.15.3.rst +++ b/docs/sphinx/source/whatsnew/v0.15.3.rst @@ -14,6 +14,12 @@ Deprecations Bug fixes ~~~~~~~~~ +~~~~~~~~ + +* Fixed ``ModelChain`` no longer replacing missing (NaN) values in the DC + and AC results with zero. Genuine gaps in the input now propagate as NaN, + following the "NaN in, NaN out" policy, while nighttime correctly remains + zero power. (:issue:`1409`) Enhancements diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 45374bd6fa..d417faeb01 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -665,7 +665,10 @@ def _make_diode_params(photocurrent, saturation_current, self.results.dc, unwrap=False ) - self.results.dc = tuple(dc.fillna(0) for dc in self.results.dc) + # Missing (NaN) values in the inputs are intentionally preserved so + # that the result follows the "NaN in, NaN out" policy. At night the + # single diode model already returns zero power, so night is not + # affected by not filling NaNs here. # If the system has one Array, unwrap the single return value # to preserve the original behavior of ModelChain if self.system.num_arrays == 1: @@ -766,7 +769,10 @@ def adr_inverter(self): def pvwatts_inverter(self): ac = self.system.get_ac('pvwatts', self.results.dc) - self.results.ac = ac.fillna(0) + # Missing (NaN) values in the DC input are preserved so that the + # result follows the "NaN in, NaN out" policy instead of silently + # reporting zero AC for missing data. + self.results.ac = ac return self @property diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index 9ea804f014..49ee8354a3 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -360,6 +360,42 @@ def test_run_model_with_irradiance(sapm_dc_snl_ac_system, location): assert_series_equal(ac, expected) +def test_run_model_preserves_nan_inputs(cec_dc_snl_ac_system, location): + # regression test for gh-1409: genuine missing (NaN) values in the + # weather must not be silently replaced with zero in the DC results. + # Nighttime (zero irradiance) should still yield zero power. + mc = ModelChain(cec_dc_snl_ac_system, location, dc_model='cec') + times = pd.date_range('20160101 1200-0700', periods=3, freq='6h') + weather = pd.DataFrame( + {'ghi': [500, np.nan, 0], 'dni': [800, np.nan, 0], + 'dhi': [100, np.nan, 0]}, + index=times) + mc.run_model(weather) + dc = mc.results.dc + # a missing input is preserved as NaN ... + assert np.isnan(dc['p_mp'].iloc[1]) + # ... while nighttime remains zero power, not NaN ... + assert dc['p_mp'].iloc[2] == pytest.approx(0) + # ... and the daytime value is a sensible positive number. + assert dc['p_mp'].iloc[0] > 0 + + +def test_pvwatts_inverter_preserves_nan_inputs( + pvwatts_dc_pvwatts_ac_system, location): + # regression test for gh-1409: the pvwatts inverter must not replace + # missing DC values with zero AC power. + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, + aoi_model='no_loss') + times = pd.date_range('20160101 1200-0700', periods=3, freq='6h') + weather = pd.DataFrame( + {'ghi': [500, np.nan, 0], 'dni': [800, np.nan, 0], + 'dhi': [100, np.nan, 0]}, + index=times) + ac = mc.run_model(weather).results.ac + assert np.isnan(ac.iloc[1]) + assert ac.iloc[2] == pytest.approx(0) + + @pytest.fixture(scope='function') def multi_array_sapm_dc_snl_ac_system( sapm_temperature_cs5p_220m, sapm_module_params,