question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

irradiance.aoi can return NaN when module orientation is perfectly aligned with solar position

See original GitHub issue

Describe the bug I was playing with a dual-axis tracking mount with #1176 and found that when the modules are perfectly aligned with the sun (i.e. AOI should be exactly zero), floating point round-off can result in aoi projection values slightly greater than one, resulting in NaN aoi. This only happens for some perfectly-aligned inputs (for example tilt=zenith=20, azimuth=180 returns aoi=0 as expected).

To Reproduce

import pvlib
zenith = 89.26778228223463
azimuth = 60.932028605997004
print(pvlib.irradiance.aoi_projection(zenith, azimuth, zenith, azimuth))
print(pvlib.irradiance.aoi(zenith, azimuth, zenith, azimuth))

# output:
1.0000000000000002
RuntimeWarning: invalid value encountered in arccos:  aoi_value = np.rad2deg(np.arccos(projection))
nan

Expected behavior I expect aoi=0 whenever module orientation and solar position angles are identical.

Versions:

  • pvlib.__version__: 0.9.0-alpha.4+14.g61650e9
  • pandas.__version__: 0.25.1
  • numpy.__version__: 1.17.0
  • python: 3.7.7 (default, May 6 2020, 11:45:54) [MSC v.1916 64 bit (AMD64)]

Additional context Some ideas for fixes:

  1. In irradiance.aoi_projection, return a hard-coded 1.0 for inputs within some small tolerance
  2. In irradiance.aoi_projection, clamp return value to [-1, +1]
  3. In irradiance.aoi, clamp aoi_projection values to [-1, +1] before calling arccos
  4. Rework the irradiance.aoi_projection trig equations to not generate impossible values?

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
cwhansecommented, Mar 11, 2021

I have a mild preference for the aoi version, it is somewhat more readable and looks more likely to be further optimized.

1reaction
kanderso-nrelcommented, Mar 11, 2021

Cool, thanks! The problem was a missing sqrt. Here’s an updated version with some additional tweaks so that it works for arrays in addition to scalars:

Click to expand!
import pvlib
from pvlib.tools import sind, cosd
import numpy as np


def spherical_to_cartesian(zenith, azimuth):
    sin_zen = sind(zenith)
    return np.array([sin_zen*cosd(azimuth), sin_zen*sind(azimuth), cosd(zenith)])


def aoi(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth):
    solar_vec = spherical_to_cartesian(solar_zenith, solar_azimuth)
    surface_vec = spherical_to_cartesian(surface_tilt, surface_azimuth)

    c_vec = solar_vec - surface_vec
    c = np.linalg.norm(c_vec, axis=0)
    c_sqrd = np.sum(c_vec*c_vec, axis=0)  # couldn't get np.dot working for vectors
    c = np.sqrt(c_sqrd)

    aoi = 2 * np.arctan(c / np.sqrt((1+(1+c))*((1-c)+1)))
    aoi_deg = np.rad2deg(aoi)
    return aoi_deg


def aoi_sum_diff(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth):
    solar_vec = spherical_to_cartesian(solar_zenith, solar_azimuth)
    surface_vec = spherical_to_cartesian(surface_tilt, surface_azimuth)

    c_diff = solar_vec - surface_vec
    c_sum = solar_vec + surface_vec

    aoi = 2 * np.arctan2(np.linalg.norm(c_diff, axis=0), np.linalg.norm(c_sum, axis=0))
    aoi_deg = np.rad2deg(aoi)
    return aoi_deg

Testing the three functions with many random inputs (where solar position and module orientation are parallel) shows that the two methods from that SO post reliably return aoi=zero while the current pvlib function returns nan for ~3.7% of inputs. The downside is that the two SO functions are half as fast as the current pvlib function. Avoiding redundant conversions to radians (by not using cosd and sind) squeak a bit of extra speed but not much.

import time

def count_nan(x):
    return np.sum(np.isnan(x))

np.random.seed(0)
N = 1_000_000
surface_tilt = solar_zenith = np.random.uniform(0, 180, size=N)
surface_azimuth = solar_azimuth = np.random.uniform(0, 360, size=N)

for function in [aoi, aoi_sum_diff, pvlib.irradiance.aoi]:
    st = time.time()
    result = function(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth)
    ed = time.time()
    print(all(result == 0), count_nan(result), ed - st)

Output:

True 0 0.26395654678344727
True 0 0.2891671657562256
False 37653 0.12442183494567871

I don’t love cutting execution speed in half, but it’s still pretty fast. Ok with me to use one of these functions instead of np.clip. @cwhanse?

Edit to add: using this wouldn’t fix the underlying issue of irradiance.aoi_projection returning values > 1. Is aoi_projection useful for anything other than calculating aoi?

Read more comments on GitHub >

github_iconTop Results From Across the Web

What's New — pvlib python 0.9.3 documentation
Fix a bug in pvlib.solarposition.sun_rise_set_transit_ephem() where passing localized timezones with large UTC offsets could return rise/set/transit times ...
Read more >
pvlib_python Documentation - Read the Docs
pvlib python is a community supported tool that provides a set of functions and classes for simulating the performance.
Read more >
Solar Radiation on a Tilted Surface - PVEducation
The module tilt angle is measured from the horizontal. The Incident Power is the solar radiation perpendicular to the sun's rays and is...
Read more >
A LiDAR DSM based geometry modelling method to improve solar ...
method to improve solar irradiance simulation and PV yield prediction in urban environments. Master Thesis. Bowen Tian. Student number: 1486241.
Read more >
1 BIFACIAL SOLAR MODULES SYSTEM DESIGN ...
Most importantly, bifacial PV modules can capture direct and diffuse irradiance, resulting in a higher energy yield (Figure 1-1). Figure 1-1: ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found