Single-axis tracking and backtracking, part 2

The last post showed a simple derivation of the tracking pattern that minimizes the incidence angle of incoming direct irradiance from the sun. There's just one problem -- when the sun is at low elevation, adjacent rows shade each other.

/images/self-shading.png

For crystalline silicon modules, this is a big problem! The electrical power loss associated with partial shading is sometimes called "catastrophic" because shading a small portion of a module results in the majority of the module's power being lost. For a detailed explanation, check out Mark Mikofski's excellent post on it here.

Backtracking

So it turns out that our original premise was only part of the story: we should point the panels towards the sun as directly as possible while avoiding self-shading. Think about the shape of a row's shadow when the sun is at low elevation. If the row is oriented almost flat, the bottom edge will be fairly high and the top edge fairly low. Here's an image of two rows rotated at the perfect angle so that the shadow from one is exactly tangent to the next.

/images/backtracking_sideview.png

As the sun gets lower in the sky, the rows will have to rotate even closer to horizontal to avoid self-shading. This behavior is called backtracking because the rows track backwards in morning and afternoon. Let's derive the backtracking angle!

Here's a helper diagram to get us started. It compares truetracking (positions \(C\) and \(H\)) with backtracking (positions \(D\) and \(F\)). If we can calculate the angle \(\angle DAB = \theta_{C}\) (the difference between truetracking and backtracking), adding it to the truetracking position \(\theta_{TT}\) will give the backtracking position \(\theta_{BT}\).

/images/backtracking_diagram.png

This is a side-view diagram of two adjacent tracker rows. The rows are separated by a distance \(p\) for pitch and the height (or "collector bandwidth" in PVSyst) of each row is \(l\). This visualization is heavily inspired by one in Lorenzo et al.'s "Tracking and back-tracking" 1. The approach to derive \(\theta_C\) is built on the fact that triangles \(EAB\) and \(DAB\) share side \(AB\).

First, let's recognize that \(\angle EAB\) is the truetracking position \(\theta_{TT}\) and triangle \(EAB\) is a right triangle. We also know by symmetry that \(|EA| = p/2\), where \(p\) is the pitch or on-center row spacing. With our old friend soh-cah-toa, that means

\begin{equation*} \cos \theta_{TT} = \frac{|AB|}{|EA|} = \frac{|AB|}{p/2} \end{equation*}

Now let's consider triangle \(DAB\). By symmetry again, we know that \(|AD| = l/2\), where \(l\) is the height of the row. Therefore:

\begin{equation*} \cos \theta_C = \frac{|AB|}{|AD|} = \frac{|AB|}{l/2} \end{equation*}

Combining the two equations and eliminating \(|AB|\) gives us:

\begin{equation*} \cos \theta_C = \frac{\cos \theta_{TT}}{l/p} = \frac{\cos \theta_{TT}}{\mathrm{gcr}} \end{equation*}

Where \(\mathrm{gcr} = l/p\) is the ground coverage ratio, the fraction of the ground that the array covers when it is rotated flat.

Inspection gives a little insight: because \(\cos\) spans \([-1, 1]\), our backtracking correction angle \(\theta_C\) is not defined in the middle of the day -- \(\cos 0 = 1\), and since \(\mathrm{gcr} < 1\), that would mean \(\cos \theta_C > 1\), which isn't possible. That makes sense, since in the middle of the day, there's no self-shading to avoid! Further, the smaller \(\mathrm{gcr}\) is, the larger the fraction of the day when there's no need to avoid self-shading.

import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import pvlib

gcr = 0.5

times = pd.date_range('2019-03-01', '2019-03-02', freq='5min', tz='US/Eastern')
location = pvlib.location.Location(40, -80, tz='US/Eastern')
solpos = location.get_solarposition(times)
solpos = np.radians(solpos)

x = np.sin(solpos.zenith) * np.sin(solpos.azimuth)
z = np.cos(solpos.zenith)

theta_tt = np.arctan2(x, z)
theta_tt[z < 0] = 0

theta_c = np.arccos(np.cos(theta_tt) / gcr)
theta_c[np.isnan(theta_c)] = 0
theta_c[x > 0] *= -1

theta_bt = theta_tt + theta_c

np.degrees(theta_tt).plot(label=r'$\theta_{TT}$')
np.degrees(theta_bt).plot(label=r'$\theta_{BT}$')
plt.legend()
/images/tracking_backtracking_plot.png

This is the basis of the most common tracking algorithm used in tracker arrays. It requires array lat and lon for the truetracking position and array gcr for the backtracking correction angle. The same style of derivation can be used to generalize the geometry to arrays on N-S slopes, E-W slopes, and shifted azimuths.

Extra Credit

If you go back and look at the labeled points diagram, you'll notice that there are actually two distinct positions that prevent self-shading -- the one we already talked about has the row rotate backwards away from the sun, but it could also rotate forwards towards the sun until self-shading is eliminated. I've never seen this strategy mentioned anywhere and in the absence of a pre-existing name for it, I call it "overtracking". It has exactly the same self-shading characteristics as backtracking and also has the same beam irradiance collection, but tends to point the panels down at the ground instead of up at the sky. It also requires huge tracker angles since the array needs to be able to flip upside down in morning and evening. The only reason overtracking could ever be better than normal backtracking would be when there is more diffuse irradiance coming up from below than down from above -- for example, on the moon where there's no atmosphere. Here's what it looks like:

/images/tracking_backtracking_overtracking_plot.png

References

1

Lorenzo, E et al., 2011, "Tracking and back-tracking", Prog. in Photovoltaics: Research and Applications, v. 19, pp. 747-753.