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,