Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/sphinx/source/whatsnew/v0.16.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ Deprecations

Bug fixes
~~~~~~~~~
* :py:func:`~pvlib.irradiance.perez` no longer raises ``ZeroDivisionError`` on
scalar ``dhi=0`` input (e.g. nighttime); the scalar path now returns the same
finite value as the array path. (:pull:`2826`)


Enhancements
Expand Down Expand Up @@ -48,3 +51,4 @@ Maintenance
Contributors
~~~~~~~~~~~~
* Carolina Crespo (:ghuser:`cbcrespo`)
* Andrew Chen (:ghuser:`chuenchen309`)
8 changes: 6 additions & 2 deletions pvlib/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1146,8 +1146,12 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra,
delta = dhi * airmass / dni_extra

# epsilon is the sky's "clearness"
with np.errstate(invalid='ignore'):
eps = ((dhi + dni) / dhi + kappa * (z ** 3)) / (1 + kappa * (z ** 3))
# np.true_divide so a Python-scalar dhi=0 yields inf/nan like the array
# path (handled below via digitize) instead of raising ZeroDivisionError,
# which np.errstate cannot suppress for native scalar division.
with np.errstate(invalid='ignore', divide='ignore'):
eps = (np.true_divide(dhi + dni, dhi) + kappa * (z ** 3)) \
/ (1 + kappa * (z ** 3))

# numpy indexing below will not work with a Series
if isinstance(eps, pd.Series):
Expand Down
10 changes: 10 additions & 0 deletions tests/test_irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,16 @@ def test_perez_scalar():
assert_allclose(out, 109.084332)


def test_perez_scalar_dhi_zero():
# dhi=0 (e.g. nighttime) is documented-valid input (dhi >= 0). A native
# Python scalar division raised ZeroDivisionError (which np.errstate cannot
# suppress) while the array path returns a finite value; they must match.
out = irradiance.perez(30, 180, 0.0, 800.0, 1400.0, 40.0, 120.0, 1.5)
expected = irradiance.perez(30, 180, np.array([0.0]), np.array([800.0]),
1400.0, 40.0, 120.0, np.array([1.5]))
assert_allclose(out, expected[0])


def test_perez_driesse_scalar():
# copied values from fixtures
out = irradiance.perez_driesse(40, 180, 118.458, 939.954,
Expand Down