Implementing (non-diffuse) self shading

191 views
Skip to first unread message

Arnav G

unread,
May 4, 2021, 11:32:46 PM5/4/21
to pvlib-python
Hello all,

While analyzing the results of simulating multi-row array configurations, it seems to me that pvlib-python doesn't natively model row-on-row shading. This is manifesting as power outputs going down as opposed to up when it is indicated that a SingleAxisTracker is backtracking, since the array rotation is adjusting to account for a shading which is not even modeled. Is there any recommended methodology for accounting for such "self-shading"? I understand that some functionality exists for calculating the impact of self-shading on diffuse irradiance, with an example available in the Example Gallery,  but I would like to account for the changes to direct irradiance as well. This doesn't seem apparently available to me, so any insight on accessing or implementing this behavior would be greatly appreciated.

Thank you!
Arnav Gautam

Kevin Anderson

unread,
May 5, 2021, 12:57:38 AM5/5/21
to Arnav G, pvlib-python
Hi Arnav,

I'd like to hear other folks' opinions on this topic, but here's something to get the ball rolling:

Yes, there is currently not much pvlib support for modeling the effect of direct shading.  Nonuniform shading is in general a pretty complex phenomenon to model, but if the system and shadow geometry satisfy a few conditions, a simple loss model can be surprisingly accurate.  Here is one "quick and dirty" approach that might be a place to start.  It assumes that self-shading affects all modules equally so module-module mismatch can be neglected, the array is racked 1-in-portrait so that the row shaded fraction is the same as the module shaded fraction, and that the module has the typical 60/72 Silicon cell layout (no half-cells, cross-ties, etc).  For thin films you might just reduce the in-plane direct component by the module shaded fraction and call that good enough.

1) Calculate the shaded fraction of each row using solar position, array geometry, and module orientation.  Section 6 in this report has a formula to calculate the shaded fraction: https://www.nrel.gov/docs/fy20osti/76626.pdf
2) Use pvlib to find the in-plane diffuse component (poa_diffuse in pvlib lingo) and calculate the in-plane diffuse fraction.
3) A very simple shading power loss model is to assume that the performance of each cell in a submodule cell string is equal to that of the most shaded cell in the cell string -- the most shaded cell acts as a bottleneck.  So in the case of row-to-row shading, the shadow moves up from the bottom of the module, meaning the cells in the bottom row are the ones of interest.  Assuming that the cell's output is proportional to the total irradiance incident on the cell, you can figure out what fraction of that bottom cell the shadow covers and reduce poa_direct proportionally to get the cell's output power.  This means that, as shaded fraction increases, there is an initial steep decline in power output that levels out once the bottom row of cells is fully covered by the shadow -- once that bottom row of cells is fully shaded, shading additional cells makes no difference.  The leveled-out loss is determined by the diffuse fraction of in-plane irradiance.  Here's a plot showing this model, assuming a module 12 cells high (so the corner is at 1/12 = 0.083) and a diffuse fraction of 0.3:

image.png

Some justification for this loss model can be found at https://ieeexplore.ieee.org/abstract/document/9300438.  Code for the above plot is attached.

If your array doesn't satisfy this model's assumptions, then maybe a more complex model like this one is the way to go: https://www.sciencedirect.com/science/article/pii/S0038092X13002739

I don't have any good suggestions for modeling cell temperature under partial shade conditions, other than the speculation that a poor cell temp model might not be worth worrying about if you're already losing 80% of the production to self-shading anyway.

Cheers,
Kevin

--
You received this message because you are subscribed to the Google Groups "pvlib-python" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pvlib-python...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/pvlib-python/3826d740-ecd6-4323-ab1c-00d6751c05fbn%40googlegroups.com.
self-shading-model.py

N U

unread,
Jan 20, 2022, 7:58:35 PM1/20/22
to pvlib-python
Hi Kevin,
I am trying to implement your suggestion here to determine self-shading on a SAT array with a simple model. As you suggest the diffuse shading can be calculated relatively easily with pvlib functions. For beam shading, I am trying to calculate shade fraction using the paper https://www.nrel.gov/docs/fy20osti/76626.pdf (eqn. 32) for the following a sample case with SAT doing true-tracking on a flat terrain (cross-axis slope = 0). I'd expect with a true-track the shade fractions to be >0 in the shoulder hours of the day. When I implemented this, the shade fraction comes out to be 0 for all times. Can you please validate my implementation here? or have I misunderstood your suggestion above incorrectly?

sample code and the weather file attached.

Best,
Neelesh

pv_shading1.py
test.csv

kevina...@gmail.com

unread,
Jan 20, 2022, 8:19:14 PM1/20/22
to pvlib-python
Hi Neelesh,

I didn't check the code in detail so it's possible there are additional issues, but two things jump out at me:

- theta_true_track must be calculated without consideration of mechanical constraints, i.e. with max_angle=90 or 180.  In hindsight I wish we had called that parameter something else -- it's mathematically equivalent to the ideal (unconstrained) true-tracking angle, so that's why we called it that, but the relevance to shaded fraction is more that the same number also represents solar position in the tracker's reference frame.  We should have called it the projected zenith angle or something to avoid confusion.  Anyway, to get theta_t values appropriate for that Eqn 32, don't set max_angle=60.  Of course you should still apply the mechanical constraint to whatever the actual tracker angle is (theta).
- There is an error in the denominator of the shaded fraction equation; one of the theta should be theta_true_track

FYI pvlib pull request #717 will add a function to calculate the shaded fraction, so hopefully in a future release you can just call that function instead of implementing the equation yourself.

Kevin

cwh...@sandia.gov

unread,
Jan 20, 2022, 8:30:36 PM1/20/22
to pvlib-python
Is theta in Eq. 32 the tracker rotation (-90 to +90) or is it the angle from horizontal (surface_tilt, in pvlib lingo)?  Bears on the function "shaded_fraction" being added to pvlib/shading.py

Cliff

kevina...@gmail.com

unread,
Jan 20, 2022, 8:37:16 PM1/20/22
to pvlib-python
Theta and theta_t are each signed rotations in the same coordinate system (same axis_azimuth).  I think technically they span [-180, +180] if we consider tilted-axis trackers. 

Kevin

cwh...@sandia.gov

unread,
Jan 20, 2022, 9:00:40 PM1/20/22
to pvlib-python
That may be the source of the problem. At DateTime 2020-01-01 06:00:00-08:00, solpos_elevation=0.13828644666479967 so the shaded fraction should be nearly 1. But at theta = -60, GCR=0.4, theta_t = theta = -60, and beta_c = 0, the numerator of equation 32 is negative, denominator is positive, so the max() step sets the value to 0.

kevina...@gmail.com

unread,
Jan 20, 2022, 9:18:45 PM1/20/22
to pvlib-python
Well for solar elevation ~ 0, theta_t must be +/- ~90.  theta_t is only +/- 60 when incorrectly applying a mechanical constraint to theta_t as the original code does.  If theta_t is left unconstrained as intended, it will approach +/- 90 as the sun nears the horizon, so tan(theta_t) will dominate and the signs of the numerator and denominator are the same.

cwh...@sandia.gov

unread,
Jan 20, 2022, 9:32:05 PM1/20/22
to pvlib-python
> solar elevation ~ 0, theta_t must be +/- ~90. 
yes, of course my mistake there.

I think there is a mistake in the formula for df['shade_fraction'] in the posted code: in the denominator, "np.tan(np.radians(theta))" should be "np.tan(np.radians(theta_true_track))"

Correcting that error may fix the problem. Here's the first 24 hours.
Figure 2022-01-20 142546.png

N U

unread,
Jan 20, 2022, 9:55:48 PM1/20/22
to pvlib-python
Thanks Kevin and Cliff for the discussion and quickly pointing out the mistake. I set max_angle = 90 to calc theta_t, and left the max_angle = 60 for theta. This change seems to give expected results for shade fraction. 

sample plot for one day (01-01-2020) and output csv
Figure 2022-01-20 133453.png
test_fs_slope_aware_paper.csv
Reply all
Reply to author
Forward
0 new messages