Skip to content

DRAFT: Handle dni/dhi=0/0 in irradiance.perez without NaNs#2808

Draft
markcampanelli wants to merge 15 commits into
pvlib:mainfrom
markcampanelli:align_perez_with_perez_driesse
Draft

DRAFT: Handle dni/dhi=0/0 in irradiance.perez without NaNs#2808
markcampanelli wants to merge 15 commits into
pvlib:mainfrom
markcampanelli:align_perez_with_perez_driesse

Conversation

@markcampanelli

@markcampanelli markcampanelli commented Jul 5, 2026

Copy link
Copy Markdown
Contributor
  • Inspired by Keep or exclude total diffuse irradiance in the output when return_components=True #2801
  • I am familiar with the contributing guidelines
  • I attest that all AI-generated material has been vetted for accuracy and is in compliance with the pvlib license
  • Tests added
  • Updates entries in docs/sphinx/source/reference for API changes.
  • Adds description and name entries in the appropriate "what's new" file in docs/sphinx/source/whatsnew for all changes. Includes link to the GitHub Issue with :issue:`num` or this Pull Request with :pull:`num`. Includes contributor name and/or GitHub username (link with :ghuser:`user`).
  • New code is fully documented. Includes numpydoc compliant docstrings, examples, and comments where necessary.
  • Pull request is nearly complete and ready for detailed review.
  • Maintainer: Appropriate GitHub Labels (including remote-data) and Milestone are assigned to the Pull Request and linked Issue.

Comment thread pvlib/irradiance.py
# eventually be used as indicies for coeffecient look ups
ebin = np.digitize(eps, (0., 1.065, 1.23, 1.5, 1.95, 2.8, 4.5, 6.2))
ebin = np.array(ebin) # GH 642
ebin[np.isnan(eps)] = 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pvlib/maintainers I question if this is wise. Perhaps if a user passes a NaN for DNI or DHI (presumably resulting in a NaN for eps), then the function should fail?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think nan in -> nan out is the philosophy we try to adhere to. Dunno what makes more sense for this specific case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

K thanks. Sorry I had missed that.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ain't something to be sorry about, it a legitimate question (the motto, better safe than sorry).

Discussions related to this point:

And there was one regarding ModelChain, don't remember exactly where.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added many more combinations of NaN DNI and DHI to the new tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@echedey-ls So in my testing, I realized that if airmass is NaN, then this trumps DNI or DHI being NaN, and the result is zero instead of NaN (including the components, when returned). That sounds like incorrect behavior. Would you agree?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the function's behavior to preserve NaNs inherited from DHI or DNI. New test now passing again.

Comment thread pvlib/irradiance.py
diffuse_components['poa_horizon'] = dhi * term3

# Set values of components to 0 when sky_diffuse is 0
mask = sky_diffuse == 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cbcrespo I think this is where one has to be careful about the order of operations when computing the components first and then summing to get the total.

A very edgy case here would be if the components added up to zero, but are themselves not all zero (indeed some components can legitimately be negative in this model). This would likely be exceedingly rare for typical floating point calculations, however.

That said, I think the algorithm semantics would be easier to follow if:

  • The components were computed first.
  • The sum of components was computed next.
  • Finally, the total and components are all set to zero if the sum is (strictly) negative.

Although, if a negative total comes out, should this function raise instead?

(I tend to like things to break obviously rather than fail silently, and a negative total sounds like a model breakage to me, but I could be mis-interpreting the situation.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, the "spirit of pvlib-python" might be to set everything to NaN if the total comes out negative, instead of raising.

@markcampanelli

markcampanelli commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@pvlib/maintainers I have two outstanding issues before moving this out of DRAFT and requesting a full review:

  1. I raise a new FloatingPointError when dividing a non-zero (and non-NaN) DHI by a zero DNI.
  2. I think that if the total sky diffuse POA comes out negative after summing all the components, then we should raise an error OR set this value and all corresponding components to NaN (current behavior sets everything to zero, which I think is significantly misleading).

I think returning NaNs instead of raising is probably closer to what users expect, but I am not sure here. If this is true, then I can change both of the above cases to return NaNs.

@cwhanse cwhanse left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markcampanelli can you populate this table? I'm having trouble following the intent of handling the combinations of 0 and/or NaN

air mass DHI DNI sky diffuse output
0 0 0
0 0 NaN
0 NaN 0
0 NaN NaN
NaN 0 0
NaN 0 NaN
NaN NaN 0
NaN NaN NaN

Comment thread docs/sphinx/source/whatsnew/v0.15.3.rst Outdated
Comment thread docs/sphinx/source/whatsnew/v0.15.3.rst Outdated
Comment thread pvlib/irradiance.py
Comment on lines 1044 to +1047
surface using the surface tilt angle, surface azimuth angle, diffuse
horizontal irradiance, direct normal irradiance, extraterrestrial
irradiance, sun zenith angle, sun azimuth angle, and relative (not
pressure-corrected) airmass. Optionally a selector may be used to
use any of Perez's model coefficient sets.
horizontal irradiance (DHI), direct normal irradiance (DNI),
extraterrestrial irradiance, sun zenith angle, sun azimuth angle, and
relative (not pressure-corrected) airmass. Optionally a selector may be

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove the list of input quantities. That was ported from Matlab before the current docstring format was settled.

@markcampanelli

markcampanelli commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@markcampanelli can you populate this table? I'm having trouble following the intent of handling the combinations of 0 and/or NaN

case air mass DHI DNI total circumsolar isotropic horizon
a 42 0 0 0 0 0 0
b 42 0 NaN NaN NaN NaN NaN
c 42 NaN 0 NaN NaN NaN NaN
d 42 NaN NaN NaN NaN NaN NaN
e NaN 0 0 0 0 0 0
f NaN 0 NaN NaN NaN NaN NaN
g NaN NaN 0 NaN NaN NaN NaN
h NaN NaN NaN NaN NaN NaN NaN
i 42 100 0 -22.2 -> NaN -65.5 -> NaN 37.0 -> NaN 6.34 -> NaN
j NaN 100 0 NaN or 0? NaN or 0? NaN or 0? NaN or 0?
k 42 0 100 NaN NaN NaN NaN
l NaN 0 100 NaN or 0? NaN or 0? NaN or 0? NaN or 0?

I added components columns and four rows to the end of your table template.

The first added row shows a negative sky-diffuse total result that is negative, along with examples of components that produce it. The row after this is same input but with NaN airmass.

The last two added rows actually raise a FloatingPointError in my current code, but I think, for consistency, this should change to the NaNs shown (or zeros when airmass is NaN?).

After making this table, it is unclear to me if a NaN airmass should supersede a "nonsensical" DHI+DNI combo, for example, DHI=0 and DNI=100. If "NaN airmass wins", then the output is all zeros, if "nonsensical DHI+DNI" wins, then output is all NaNs. Which to choose?

@markcampanelli

Copy link
Copy Markdown
Contributor Author

@cwhanse If I remove raising the FloatingPointError when, say, dni/dhi = 100/0, then the eps (epsilon) parameter evaluates to inf (following standard IEEE 754), and subsequently the total sky diffuse and all components compute to zero due to how inf is treated in the subsequent lookup table for eps (considered clear sky, I think). TBH: I'm not sure exactly what to do here, but semantically I still think that NaNs make more sense here than zeros or raising an exception.

More broadly, should this perez function be first checking the inputs to ensure that 0 <= dni < inf and 0 <= dhi < inf, but (possibly) allowing upstream NaNs to "pass through"?

@cwhanse

cwhanse commented Jul 7, 2026

Copy link
Copy Markdown
Member

@markcampanelli as a general rule (but not always), pvlib avoids checking inputs. GIGO applies.

Case e is an exception to the usual nan-in/nan-out philosophy, but it's been that way forever so I'm not inclined to suggest changing it. Case j is a variation of case e, so to be consistent, 0 output.

Case l is a problem that illustrates why the nan-in/nan-out rule has value. If pvlib outputs nan, then we'd do that by removing the overwriting airmass nan with 0 (Cases e and j). Since I'm not inclined to change that behavior, pvlib outputs 0 for case l. Otherwise we write some more complicated logic - ugh.

Case i: I'd prefer to output the negative numbers rather than substituting nan where the values are non-physical (this was likely the motivation for overwriting airmass nan). Outputting nan should be a clue that you've got nan inputs. Outputting negative irradiance is a different signal - you're using the model outside it's intended range.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants