-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Consider extracting the surface orientation calculation in pvlib.tracking.singleaxis() to its own function #1471
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I like this. This is related to an issue submitted for NREL SAM, NREL/SAM#850, and I think @mjprilliman is looking at something related. |
@kanderso-nrel Nice meeting you at PVSC the other day. I've been working on this a bit using the tilt and azimuth equations in the technical report by Bill Marion mentioned. I would like to use this for the custom backtracking schedules Nevados generates for the terrain following trackers. I have surface tilt working. It requires a dataframe
Unfortunately I can't seem to get surface azimuth working correctly. sadf (surface angle data frame) is almost equal to surface azimuth as calculated by pvlib, but not quite in the middle of the day.
|
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 |
Not that there was much doubt, but I've convinced myself that Bill's surface orientation equations are mathematically equivalent to the approach pvlib takes. Here are some notes if anyone is interested: https://gist.github.com/kanderso-nrel/ac3051de41261df317180c794144d6a9 If we do switch to Bill's equations we should be sure to preserve the handling of NaN and edge cases of the current implementation. |
I realize that my small error metric is due to a small timeshift that I had in my data that changed my answer when resampling / averaging. Cheers |
@kanderso-nrel I tried this code with a negative axis tilt and had some issues:
|
Oh interesting, thanks for pointing that out. Clearly for a tracker inclined slightly facing north as I interpret As a point of reference, SAM/SSC claims @kurt-rhee do you think people will want to use pvlib with negative |
.
|
Hey @kanderso-nrel, Thanks for that, very illuminating and clear as always. I think just a note in the documentation to keep axis_tilt >= 0 is enough. Better to keep 1 right way of doing things to avoid confusion. Another thing I noticed about the calculation is that keeping axis_tilt positive and rotating axis azimuth by 180 degrees gives different answers using the Marion equations. This should be apparent given the below equation where azimuth is added to, but I was curious about the difference between pvlib's implementation and Marion's at axis azimuth >270 degrees. and <0 degrees.
The left column of these plots are surface angles calculated by pvlib, the right column are calculated by equations. The top row is at 0 degrees, the bottom row is at 360 degrees. |
I just submitted this issue for NREL SAM: NREL/SAM#1087. |
@kurt-rhee are all four of those surface_azimuth curves equivalent (once the angle is constrained to be in [0, 360))? Maybe I'm missing something. |
@kurt-rhee instead of the quick mockup code from earlier in this thread, I suggest testing the implementation in #1480: https://github.com/pvlib/pvlib-python/pull/1480/files#diff-85ef163d90257651a76daf9d07052201a78139739b429ea96947451caad3dfb8R470-R521 One relevant difference is that |
@cwhanse They are equivalent! Thank you for pointing that out. Calculating POA using both methods gives slightly different answers. @kanderso-nrel Nice, I will try that out right now.
|
@kanderso-nrel Works perfectly!
|
Great, thanks for checking! Any interest in submitting a PR for clarifying the >=0 requirement in the |
I would love to! |
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 correspondingsurface_tilt
andsurface_azimuth
values. Here are a few motivating examples:tracking.singleaxis()
to include wind stow events or tracker stallsAssuming I have my tracker rotations already in hand, getting the corresponding
surface_tilt
andsurface_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 inpvlib.tracking.singleaxis
to go fromtracker_theta
tosurface_tilt
andsurface_azimuth
. Basically extract out the second half oftracking.singleaxis
into a new function. Suggestions for the function name are welcome. To be explicit, this is more or less what I'm imagining: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 thisrotation_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
The text was updated successfully, but these errors were encountered: