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.

Consider extracting the surface orientation calculation in pvlib.tracking.singleaxis() to its own function

See original GitHub issue

Is your feature request related to a problem? Please describe. The usual workflow for modeling single-axis tracking in pvlib is to treat tracker rotation (tracker_theta) as an unknown to be calculated from solar position and array geometry. However, sometimes a user might have their own tracker rotations but not have the corresponding surface_tilt and surface_azimuth values. Here are a few motivating examples:

  • Using measured rotation angles
  • Post-processing the output of tracking.singleaxis() to include wind stow events or tracker stalls
  • Other tracking algorithms that determine rotation differently from the astronomical method

Assuming I have my tracker rotations already in hand, getting the corresponding surface_tilt and surface_azimuth angles is not as easy as it should be. For the specific case of horizontal N-S axis the math isn’t so bad, but either way it’s annoying to have to DIY when pvlib already has code to calculate those angles from tracker rotation.

Describe the solution you’d like A function pvlib.tracking.rotation_to_orientation that implements the same math in pvlib.tracking.singleaxis to go from tracker_theta to surface_tilt and surface_azimuth. Basically extract out the second half of tracking.singleaxis into a new function. Suggestions for the function name are welcome. To be explicit, this is more or less what I’m imagining:

def rotation_to_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0, max_angle=90):
    # insert math from second half of tracking.singleaxis() here
    out = {'tracker_theta': tracker_theta, 'aoi': aoi,
           'surface_tilt': surface_tilt, 'surface_azimuth': surface_azimuth}
    return pandas_if_needed(out)

Describe alternatives you’ve considered Continue suffering

Additional context This is one step towards a broader goal I have for pvlib.tracking to house other methods to determine tracker rotation in addition to the current astronomical method, the same way we have multiple temperature and transposition models. These functions would be responsible for determining tracker rotations, and they’d all use this rotation_to_orientation function to convert rotation to module orientation.

Separately, I wonder if the code could be simplified using the tilt and azimuth equations in Bill’s technical report (https://www.nrel.gov/docs/fy13osti/58891.pdf) – seems like what we’re doing is overly complicated, although maybe I’ve just not studied it closely enough.

cc @williamhobbs @spaneja

Issue Analytics

  • State:closed
  • Created a year ago
  • Reactions:5
  • Comments:17 (15 by maintainers)

github_iconTop GitHub Comments

3reactions
kanderso-nrelcommented, Jun 17, 2022

Thanks @kurt-rhee for this investigation. Trying some simple examples on my end, things seem to line up. Here’s a complete copy/pasteable example where I get negligible difference between the current pvlib approach and your code. Note that I did replace the hard-coded axis_azimuth of 180 in the surface_azimuth calculation.

Click to expand!
import pvlib
import pandas as pd
import numpy as np

axis_tilt = 20
axis_azimuth = 230
loc = pvlib.location.Location(40, -80)
times = pd.date_range('2019-06-01', '2019-06-02', freq='5T', tz='Etc/GMT+5')
sp = loc.get_solarposition(times)
tr = pvlib.tracking.singleaxis(sp.apparent_zenith, sp.azimuth,
                               axis_tilt=axis_tilt, axis_azimuth=axis_azimuth)


def rotation_to_orientation(tracker_theta, axis_tilt=0, axis_azimuth=0, max_angle=90):
    surface_tilt = np.rad2deg(
        np.arccos(
            np.cos(np.deg2rad(tracker_theta)) * np.cos(np.deg2rad(axis_tilt))
        )
    )
    surface_azimuth = np.rad2deg(
        np.deg2rad(axis_azimuth) +
        np.arcsin(
            (
                np.sin(np.deg2rad(tracker_theta)) /
                np.sin(np.deg2rad(surface_tilt))
            ).clip(upper=1, lower=-1)
        )
    )
    return pd.DataFrame({
        'tracker_theta': tracker_theta,
        'surface_tilt': surface_tilt,
        'surface_azimuth': surface_azimuth,
    })

tr2 = rotation_to_orientation(tr.tracker_theta, axis_tilt=axis_tilt, axis_azimuth=axis_azimuth)
In [53]: (tr[['surface_tilt', 'surface_azimuth']] - tr2[['surface_tilt', 'surface_azimuth']]).describe()
Out[53]: 
       surface_tilt  surface_azimuth
count  1.780000e+02     1.780000e+02
mean  -6.586492e-16     3.193450e-15
std    8.916369e-15     2.864187e-14
min   -2.842171e-14    -5.684342e-14
25%   -7.105427e-15     0.000000e+00
50%    0.000000e+00     0.000000e+00
75%    3.552714e-15     2.842171e-14
max    2.131628e-14     5.684342e-14
2reactions
kanderso-nrelcommented, Jul 6, 2022

Great, thanks for checking! Any interest in submitting a PR for clarifying the >=0 requirement in the pvlib.tracking docstrings?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Sign (negative) ignored for 1 axis tilt · Issue #1087 · NREL/SAM ...
Consider extracting the surface orientation calculation in pvlib.tracking.singleaxis() to its own function pvlib/pvlib-python#1471.
Read more >
pvlib.tracking.singleaxis
Determine the rotation angle of a single axis tracker using the equations in ... surface, and the positive z-axis is normal and oriented...
Read more >
Source code for pvlib.tracking
getLogger('pvlib') import numpy as np import pandas as pd from pvlib.tools ... base class Tracker # All children would define their own ``track``...
Read more >
pvlib.tracking.singleaxis — pvlib python 0.9.3 documentation
Determine the rotation angle of a single-axis tracker when given particular solar zenith and azimuth angles. See 1 for details about the equations....
Read more >
Backtracking on sloped terrain - pvlib python - Read the Docs
This example shows how the backtracking angle changes based on a vertical offset between rows caused by sloped terrain. It uses pvlib.tracking.calc_axis_tilt() ......
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