|
| 1 | +""" |
| 2 | +Backtracking on sloped terrain |
| 3 | +============================== |
| 4 | +
|
| 5 | +Modeling backtracking for single-axis tracker arrays on sloped terrain. |
| 6 | +""" |
| 7 | + |
| 8 | +# %% |
| 9 | +# Tracker systems use backtracking to avoid row-to-row shading when the |
| 10 | +# sun is low in the sky. The backtracking strategy orients the modules exactly |
| 11 | +# on the boundary between shaded and unshaded so that the modules are oriented |
| 12 | +# as much towards the sun as possible while still remaining unshaded. |
| 13 | +# Unlike the true-tracking calculation (which only depends on solar position), |
| 14 | +# calculating the backtracking angle requires knowledge of the relative spacing |
| 15 | +# of adjacent tracker rows. This example shows how the backtracking angle |
| 16 | +# changes based on a vertical offset between rows caused by sloped terrain. |
| 17 | +# It uses :py:func:`pvlib.tracking.calc_axis_tilt` and |
| 18 | +# :py:func:`pvlib.tracking.calc_cross_axis_tilt` to calculate the necessary |
| 19 | +# array geometry parameters and :py:func:`pvlib.tracking.singleaxis` to |
| 20 | +# calculate the backtracking angles. |
| 21 | +# |
| 22 | +# Angle conventions |
| 23 | +# ----------------- |
| 24 | +# |
| 25 | +# First let's go over the sign conventions used for angles. In contrast to |
| 26 | +# fixed-tilt arrays where the azimuth is that of the normal to the panels, the |
| 27 | +# convention for the azimuth of a single-axis tracker is along the tracker |
| 28 | +# axis. Note that the axis azimuth is a property of the array and is distinct |
| 29 | +# from the azimuth of the panel orientation, which changes based on tracker |
| 30 | +# rotation angle. Because the tracker axis points in two directions, there are |
| 31 | +# two choices for the axis azimuth angle, and by convention (at least in the |
| 32 | +# northern hemisphere), the more southward angle is chosen: |
| 33 | +# |
| 34 | +# .. image:: ../_images/tracker_azimuth_angle_convention.png |
| 35 | +# :alt: Image showing the azimuth convention for single-axis tracker arrays. |
| 36 | +# :width: 500 |
| 37 | +# :align: center |
| 38 | +# |
| 39 | +# Note that, as with fixed-tilt arrays, the axis azimuth is determined as the |
| 40 | +# angle clockwise from north. The azimuth of the terrain's slope is also |
| 41 | +# determined as an angle clockwise from north, pointing in the direction |
| 42 | +# of falling slope. So for example, a hillside that slopes down to the east |
| 43 | +# has an azimuth of 90 degrees. |
| 44 | +# |
| 45 | +# Using the axis azimuth convention above, the sign convention for tracker |
| 46 | +# rotations is given by the |
| 47 | +# `right-hand rule <https://en.wikipedia.org/wiki/Right-hand_rule>`_. |
| 48 | +# Point the right hand thumb along the axis in the direction of the axis |
| 49 | +# azimuth and the fingers curl in the direction of positive rotation angle: |
| 50 | +# |
| 51 | +# .. image:: ../_images/tracker_rotation_angle_convention.png |
| 52 | +# :alt: Image showing the rotation sign convention for single-axis trackers. |
| 53 | +# :width: 500 |
| 54 | +# :align: center |
| 55 | +# |
| 56 | +# So for an array with ``axis_azimuth=180`` (tracker axis aligned perfectly |
| 57 | +# north-south), pointing the right-hand thumb along the axis azimuth has the |
| 58 | +# fingers curling towards the west, meaning rotations towards the west are |
| 59 | +# positive and rotations towards the east are negative. |
| 60 | +# |
| 61 | +# The ground slope itself is always positive, but the component of the slope |
| 62 | +# perpendicular to the tracker axes can be positive or negative. The convention |
| 63 | +# for the cross-axis slope angle follows the right-hand rule: align |
| 64 | +# the right-hand thumb along the tracker axis in the direction of the axis |
| 65 | +# azimuth and the fingers curl towards positive angles. So in this example, |
| 66 | +# with the axis azimuth coming out of the page, an east-facing, downward slope |
| 67 | +# is a negative rotation from horizontal: |
| 68 | +# |
| 69 | +# .. image:: ../_images/ground_slope_angle_convention.png |
| 70 | +# :alt: Image showing the ground slope sign convention. |
| 71 | +# :width: 500 |
| 72 | +# :align: center |
| 73 | +# |
| 74 | + |
| 75 | +# %% |
| 76 | +# Rotation curves |
| 77 | +# --------------- |
| 78 | +# |
| 79 | +# Now, let's plot the simple case where the tracker axes are at right angles |
| 80 | +# to the direction of the slope. In this case, the cross-axis tilt angle |
| 81 | +# is the same as the slope of the terrain and the tracker axis itself is |
| 82 | +# horizontal. |
| 83 | + |
| 84 | +from pvlib import solarposition, tracking |
| 85 | +import pandas as pd |
| 86 | +import matplotlib.pyplot as plt |
| 87 | + |
| 88 | +# PV system parameters |
| 89 | +tz = 'US/Eastern' |
| 90 | +lat, lon = 40, -80 |
| 91 | +gcr = 0.4 |
| 92 | + |
| 93 | +# calculate the solar position |
| 94 | +times = pd.date_range('2019-01-01 06:00', '2019-01-01 18:00', closed='left', |
| 95 | + freq='1min', tz=tz) |
| 96 | +solpos = solarposition.get_solarposition(times, lat, lon) |
| 97 | + |
| 98 | +# compare the backtracking angle at various terrain slopes |
| 99 | +fig, ax = plt.subplots() |
| 100 | +for cross_axis_tilt in [0, 5, 10]: |
| 101 | + tracker_data = tracking.singleaxis( |
| 102 | + apparent_zenith=solpos['apparent_zenith'], |
| 103 | + apparent_azimuth=solpos['azimuth'], |
| 104 | + axis_tilt=0, # flat because the axis is perpendicular to the slope |
| 105 | + axis_azimuth=180, # N-S axis, azimuth facing south |
| 106 | + max_angle=90, |
| 107 | + backtrack=True, |
| 108 | + gcr=gcr, |
| 109 | + cross_axis_tilt=cross_axis_tilt) |
| 110 | + |
| 111 | + # tracker rotation is undefined at night |
| 112 | + backtracking_position = tracker_data['tracker_theta'].fillna(0) |
| 113 | + label = 'cross-axis tilt: {}°'.format(cross_axis_tilt) |
| 114 | + backtracking_position.plot(label=label, ax=ax) |
| 115 | + |
| 116 | +plt.legend() |
| 117 | +plt.title('Backtracking Curves') |
| 118 | +plt.show() |
| 119 | + |
| 120 | +# %% |
| 121 | +# This plot shows how backtracking changes based on the slope between rows. |
| 122 | +# For example, unlike the flat-terrain backtracking curve, the sloped-terrain |
| 123 | +# curves do not approach zero at the end of the day. Because of the vertical |
| 124 | +# offset between rows introduced by the sloped terrain, the trackers can be |
| 125 | +# slightly tilted without shading each other. |
| 126 | +# |
| 127 | +# Now let's examine the general case where the terrain slope makes an |
| 128 | +# inconvenient angle to the tracker axes. For example, consider an array |
| 129 | +# with north-south axes on terrain that slopes down to the south-south-east. |
| 130 | +# Assuming the axes are installed parallel to the ground, the northern ends |
| 131 | +# of the axes will be higher than the southern ends. But because the slope |
| 132 | +# isn't purely parallel or perpendicular to the axes, the axis tilt and |
| 133 | +# cross-axis tilt angles are not immediately obvious. We can use pvlib |
| 134 | +# to calculate them for us: |
| 135 | + |
| 136 | +# terrain slopes 10 degrees downward to the south-south-east. note: because |
| 137 | +# slope_azimuth is defined in the direction of falling slope, slope_tilt is |
| 138 | +# always positive. |
| 139 | +slope_azimuth = 155 |
| 140 | +slope_tilt = 10 |
| 141 | +axis_azimuth = 180 # tracker axis is still N-S |
| 142 | + |
| 143 | +# calculate the tracker axis tilt, assuming that the axis follows the terrain: |
| 144 | +axis_tilt = tracking.calc_axis_tilt(slope_azimuth, slope_tilt, axis_azimuth) |
| 145 | + |
| 146 | +# calculate the cross-axis tilt: |
| 147 | +cross_axis_tilt = tracking.calc_cross_axis_tilt(slope_azimuth, slope_tilt, |
| 148 | + axis_azimuth, axis_tilt) |
| 149 | + |
| 150 | +print('Axis tilt:', '{:0.01f}°'.format(axis_tilt)) |
| 151 | +print('Cross-axis tilt:', '{:0.01f}°'.format(cross_axis_tilt)) |
| 152 | + |
| 153 | +# %% |
| 154 | +# And now we can pass use these values to generate the tracker curve as |
| 155 | +# before: |
| 156 | + |
| 157 | +tracker_data = tracking.singleaxis( |
| 158 | + apparent_zenith=solpos['apparent_zenith'], |
| 159 | + apparent_azimuth=solpos['azimuth'], |
| 160 | + axis_tilt=axis_tilt, # no longer flat because the terrain imparts a tilt |
| 161 | + axis_azimuth=axis_azimuth, |
| 162 | + max_angle=90, |
| 163 | + backtrack=True, |
| 164 | + gcr=gcr, |
| 165 | + cross_axis_tilt=cross_axis_tilt) |
| 166 | + |
| 167 | +backtracking_position = tracker_data['tracker_theta'].fillna(0) |
| 168 | +backtracking_position.plot() |
| 169 | + |
| 170 | +title_template = 'Axis tilt: {:0.01f}° Cross-axis tilt: {:0.01f}°' |
| 171 | +plt.title(title_template.format(axis_tilt, cross_axis_tilt)) |
| 172 | +plt.show() |
| 173 | + |
| 174 | +# %% |
| 175 | +# Note that the backtracking curve is roughly mirrored compared with the |
| 176 | +# earlier example -- it is because the terrain is now sloped somewhat to the |
| 177 | +# east instead of west. |
0 commit comments