Skip to content

Commit 9ef321d

Browse files
committed
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.
1 parent 9a0ad55 commit 9ef321d

3 files changed

Lines changed: 50 additions & 2 deletions

File tree

docs/sphinx/source/whatsnew/v0.15.3.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ Deprecations
1414

1515
Bug fixes
1616
~~~~~~~~~
17+
~~~~~~~~
18+
19+
* Fixed ``ModelChain`` no longer replacing missing (NaN) values in the DC
20+
and AC results with zero. Genuine gaps in the input now propagate as NaN,
21+
following the "NaN in, NaN out" policy, while nighttime correctly remains
22+
zero power. (:issue:`1409`)
1723

1824

1925
Enhancements

pvlib/modelchain.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,10 @@ def _make_diode_params(photocurrent, saturation_current,
665665
self.results.dc,
666666
unwrap=False
667667
)
668-
self.results.dc = tuple(dc.fillna(0) for dc in self.results.dc)
668+
# Missing (NaN) values in the inputs are intentionally preserved so
669+
# that the result follows the "NaN in, NaN out" policy. At night the
670+
# single diode model already returns zero power, so night is not
671+
# affected by not filling NaNs here.
669672
# If the system has one Array, unwrap the single return value
670673
# to preserve the original behavior of ModelChain
671674
if self.system.num_arrays == 1:
@@ -766,7 +769,10 @@ def adr_inverter(self):
766769

767770
def pvwatts_inverter(self):
768771
ac = self.system.get_ac('pvwatts', self.results.dc)
769-
self.results.ac = ac.fillna(0)
772+
# Missing (NaN) values in the DC input are preserved so that the
773+
# result follows the "NaN in, NaN out" policy instead of silently
774+
# reporting zero AC for missing data.
775+
self.results.ac = ac
770776
return self
771777

772778
@property

tests/test_modelchain.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,42 @@ def test_run_model_with_irradiance(sapm_dc_snl_ac_system, location):
360360
assert_series_equal(ac, expected)
361361

362362

363+
def test_run_model_preserves_nan_inputs(cec_dc_snl_ac_system, location):
364+
# regression test for gh-1409: genuine missing (NaN) values in the
365+
# weather must not be silently replaced with zero in the DC results.
366+
# Nighttime (zero irradiance) should still yield zero power.
367+
mc = ModelChain(cec_dc_snl_ac_system, location, dc_model='cec')
368+
times = pd.date_range('20160101 1200-0700', periods=3, freq='6h')
369+
weather = pd.DataFrame(
370+
{'ghi': [500, np.nan, 0], 'dni': [800, np.nan, 0],
371+
'dhi': [100, np.nan, 0]},
372+
index=times)
373+
mc.run_model(weather)
374+
dc = mc.results.dc
375+
# a missing input is preserved as NaN ...
376+
assert np.isnan(dc['p_mp'].iloc[1])
377+
# ... while nighttime remains zero power, not NaN ...
378+
assert dc['p_mp'].iloc[2] == pytest.approx(0)
379+
# ... and the daytime value is a sensible positive number.
380+
assert dc['p_mp'].iloc[0] > 0
381+
382+
383+
def test_pvwatts_inverter_preserves_nan_inputs(
384+
pvwatts_dc_pvwatts_ac_system, location):
385+
# regression test for gh-1409: the pvwatts inverter must not replace
386+
# missing DC values with zero AC power.
387+
mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location,
388+
aoi_model='no_loss')
389+
times = pd.date_range('20160101 1200-0700', periods=3, freq='6h')
390+
weather = pd.DataFrame(
391+
{'ghi': [500, np.nan, 0], 'dni': [800, np.nan, 0],
392+
'dhi': [100, np.nan, 0]},
393+
index=times)
394+
ac = mc.run_model(weather).results.ac
395+
assert np.isnan(ac.iloc[1])
396+
assert ac.iloc[2] == pytest.approx(0)
397+
398+
363399
@pytest.fixture(scope='function')
364400
def multi_array_sapm_dc_snl_ac_system(
365401
sapm_temperature_cs5p_220m, sapm_module_params,

0 commit comments

Comments
 (0)