|
| 1 | +""" |
| 2 | +Modelling shading losses in modules with bypass diodes |
| 3 | +====================================================== |
| 4 | +""" |
| 5 | + |
| 6 | +# %% |
| 7 | +# This example illustrates how to use the loss model proposed by Martinez et |
| 8 | +# al. [1]_. The model proposes a power output losses factor by adjusting |
| 9 | +# the incident direct and circumsolar beam irradiance fraction of a PV module |
| 10 | +# based on the number of shaded *blocks*. A *block* is defined as a group of |
| 11 | +# cells protected by a bypass diode. More information on *blocks* can be found |
| 12 | +# in the original paper [1]_ and in the |
| 13 | +# :py:func:`pvlib.shading.direct_martinez` documentation. |
| 14 | +# |
| 15 | +# The following key functions are used in this example: |
| 16 | +# |
| 17 | +# 1. :py:func:`pvlib.shading.direct_martinez` to calculate the power output |
| 18 | +# losses fraction due to shading. |
| 19 | +# 2. :py:func:`pvlib.shading.shaded_fraction1d` to calculate the fraction of |
| 20 | +# shaded surface and consequently the number of shaded *blocks* due to |
| 21 | +# row-to-row shading. |
| 22 | +# 3. :py:func:`pvlib.tracking.singleaxis` to calculate the rotation angle of |
| 23 | +# the trackers. |
| 24 | +# |
| 25 | +# .. sectionauthor:: Echedey Luis <echelual (at) gmail.com> |
| 26 | +# |
| 27 | +# Problem description |
| 28 | +# ------------------- |
| 29 | +# Let's consider a PV system with the following characteristics: |
| 30 | +# |
| 31 | +# - Two north-south single-axis trackers, each one having 6 modules. |
| 32 | +# - The rows have the same true-tracking tilt angles. True tracking |
| 33 | +# is chosen in this example, so shading is significant. |
| 34 | +# - Terrain slope is 7 degrees downward to the east. |
| 35 | +# - Row axes are horizontal. |
| 36 | +# - The modules are comprised of multiple cells. We will compare these cases: |
| 37 | +# - modules with one bypass diode |
| 38 | +# - modules with three bypass diodes |
| 39 | +# - half-cut cell modules with three bypass diodes in portrait and landscape |
| 40 | +# |
| 41 | +# Setting up the system |
| 42 | +# ---------------------- |
| 43 | +# Let's start by defining the system characteristics, location and the time |
| 44 | +# range for the analysis. |
| 45 | + |
| 46 | +import pvlib |
| 47 | +import pandas as pd |
| 48 | +import numpy as np |
| 49 | +import matplotlib.pyplot as plt |
| 50 | +from matplotlib.dates import ConciseDateFormatter |
| 51 | + |
| 52 | +pitch = 4 # meters |
| 53 | +width = 1.5 # meters |
| 54 | +gcr = width / pitch # ground coverage ratio |
| 55 | +N_modules_per_row = 6 |
| 56 | +axis_azimuth = 180 # N-S axis |
| 57 | +axis_tilt = 0 # flat because the axis is perpendicular to the slope |
| 58 | +cross_axis_tilt = -7 # 7 degrees downward to the east |
| 59 | + |
| 60 | +latitude, longitude = 40.2712, -3.7277 |
| 61 | +locus = pvlib.location.Location( |
| 62 | + latitude, |
| 63 | + longitude, |
| 64 | + tz="Europe/Madrid", |
| 65 | + altitude=pvlib.location.lookup_altitude(latitude, longitude), |
| 66 | +) |
| 67 | + |
| 68 | +times = pd.date_range("2001-04-11T04", "2001-04-11T20", freq="10min") |
| 69 | + |
| 70 | +# %% |
| 71 | +# True-tracking algorithm and shaded fraction |
| 72 | +# ------------------------------------------- |
| 73 | +# Since this model is about row-to-row shading, we will use the true-tracking |
| 74 | +# algorithm to calculate the trackers rotation. Back-tracking eliminates |
| 75 | +# shading between rows, and since this example is about shading, we will not |
| 76 | +# use it. |
| 77 | +# |
| 78 | +# Then, the next step is to calculate the fraction of shaded surface. This is |
| 79 | +# done using :py:func:`pvlib.shading.shaded_fraction1d`. Using this function is |
| 80 | +# straightforward with the variables we already have defined. |
| 81 | +# Then, we can calculate the number of shaded blocks by rounding up the shaded |
| 82 | +# fraction by the number of blocks along the shaded length. |
| 83 | + |
| 84 | +# Calculate solar position to get single-axis tracker rotation and irradiance |
| 85 | +solar_pos = locus.get_solarposition(times) |
| 86 | +solar_apparent_zenith, solar_azimuth = ( |
| 87 | + solar_pos["apparent_zenith"], |
| 88 | + solar_pos["azimuth"], |
| 89 | +) # unpack for better readability |
| 90 | + |
| 91 | +tracking_result = pvlib.tracking.singleaxis( |
| 92 | + apparent_zenith=solar_apparent_zenith, |
| 93 | + apparent_azimuth=solar_azimuth, |
| 94 | + axis_tilt=axis_tilt, |
| 95 | + axis_azimuth=axis_azimuth, |
| 96 | + max_angle=(-90 + cross_axis_tilt, 90 + cross_axis_tilt), # (min, max) |
| 97 | + backtrack=False, |
| 98 | + gcr=gcr, |
| 99 | + cross_axis_tilt=cross_axis_tilt, |
| 100 | +) |
| 101 | + |
| 102 | +tracker_theta, aoi, surface_tilt, surface_azimuth = ( |
| 103 | + tracking_result["tracker_theta"], |
| 104 | + tracking_result["aoi"], |
| 105 | + tracking_result["surface_tilt"], |
| 106 | + tracking_result["surface_azimuth"], |
| 107 | +) # unpack for better readability |
| 108 | + |
| 109 | +# Calculate the shade fraction |
| 110 | +shaded_fraction = pvlib.shading.shaded_fraction1d( |
| 111 | + solar_apparent_zenith, |
| 112 | + solar_azimuth, |
| 113 | + axis_azimuth, |
| 114 | + axis_tilt=axis_tilt, |
| 115 | + shaded_row_rotation=tracker_theta, |
| 116 | + shading_row_rotation=tracker_theta, |
| 117 | + collector_width=width, |
| 118 | + pitch=pitch, |
| 119 | + cross_axis_slope=cross_axis_tilt, |
| 120 | +) |
| 121 | + |
| 122 | +# %% |
| 123 | +# Number of shaded blocks |
| 124 | +# ----------------------- |
| 125 | +# The number of shaded blocks depends on the module configuration and number |
| 126 | +# of bypass diodes. For example, |
| 127 | +# modules with one bypass diode will behave like one block. |
| 128 | +# On the other hand, modules with three bypass diodes will have three blocks, |
| 129 | +# except for the half-cut cell modules, which will have six blocks; 2x3 blocks |
| 130 | +# where the two rows are along the longest side of the module. |
| 131 | +# We can argue that the dimensions of the system change when you switch from |
| 132 | +# portrait to landscape, but for this example, we will consider it the same. |
| 133 | +# |
| 134 | +# The number of shaded blocks is calculated by rounding up the shaded fraction |
| 135 | +# by the number of blocks along the shaded length. So let's define the number |
| 136 | +# of blocks for each module configuration: |
| 137 | +# |
| 138 | +# - 1 bypass diode: 1 block |
| 139 | +# - 3 bypass diodes: 3 blocks in landscape; 1 in portrait |
| 140 | +# - 3 bypass diodes half-cut cells: |
| 141 | +# - 2 blocks in portrait |
| 142 | +# - 3 blocks in landscape |
| 143 | +# |
| 144 | +# .. figure:: ../../_images/PV_module_layout_cesardd.jpg |
| 145 | +# :align: center |
| 146 | +# :width: 75% |
| 147 | +# :alt: Normal and half-cut cells module layouts |
| 148 | +# |
| 149 | +# Left: common module layout. Right: half-cut cells module layout. |
| 150 | +# Each module has three bypass diodes. On the left, they connect cell |
| 151 | +# columns 1-2, 2-3 & 3-4. On the right, they connect cell columns 1-2, 3-4 & |
| 152 | +# 5-6. |
| 153 | +# *Source: César Domínguez. CC BY-SA 4.0, Wikimedia Commons* |
| 154 | +# |
| 155 | +# In the image above, each orange U-shaped string section is a block. |
| 156 | +# By symmetry, the yellow inverted-U's of the subcircuit are also blocks. |
| 157 | +# For this reason, the half-cut cell modules have 6 blocks in total: two along |
| 158 | +# the longest side and three along the shortest side. |
| 159 | + |
| 160 | +blocks_per_module = { |
| 161 | + "1 bypass diode": 1, |
| 162 | + "3 bypass diodes": 3, |
| 163 | + "3 bypass diodes half-cut, portrait": 2, |
| 164 | + "3 bypass diodes half-cut, landscape": 3, |
| 165 | +} |
| 166 | + |
| 167 | +# Calculate the number of shaded blocks during the day |
| 168 | +shaded_blocks_per_module = { |
| 169 | + k: np.ceil(blocks_N * shaded_fraction) |
| 170 | + for k, blocks_N in blocks_per_module.items() |
| 171 | +} |
| 172 | + |
| 173 | +# %% |
| 174 | +# Plane of array irradiance example data |
| 175 | +# -------------------------------------- |
| 176 | +# To calculate the power output losses due to shading, we need the plane of |
| 177 | +# array irradiance. For this example, we will use synthetic data: |
| 178 | + |
| 179 | +clearsky = locus.get_clearsky( |
| 180 | + times, solar_position=solar_pos, model="ineichen" |
| 181 | +) |
| 182 | +dni_extra = pvlib.irradiance.get_extra_radiation(times) |
| 183 | +airmass = pvlib.atmosphere.get_relative_airmass(solar_apparent_zenith) |
| 184 | +sky_diffuse = pvlib.irradiance.perez_driesse( |
| 185 | + surface_tilt, surface_azimuth, clearsky["dhi"], clearsky["dni"], |
| 186 | + solar_apparent_zenith, solar_azimuth, dni_extra, airmass, |
| 187 | +) # fmt: skip |
| 188 | +poa_components = pvlib.irradiance.poa_components( |
| 189 | + aoi, clearsky["dni"], sky_diffuse, poa_ground_diffuse=0 |
| 190 | +) # ignore ground diffuse for brevity |
| 191 | +poa_global, poa_direct = ( |
| 192 | + poa_components["poa_global"], |
| 193 | + poa_components["poa_direct"], |
| 194 | +) |
| 195 | + |
| 196 | +# %% |
| 197 | +# Results |
| 198 | +# ------- |
| 199 | +# Now that we have the number of shaded blocks for each module configuration, |
| 200 | +# we can apply the model and estimate the power loss due to shading. |
| 201 | +# |
| 202 | +# Note that this model is not linear with the shaded blocks ratio, so there is |
| 203 | +# a difference between applying it to just a module or a whole row. |
| 204 | + |
| 205 | +shade_losses_per_module = { |
| 206 | + k: pvlib.shading.direct_martinez( |
| 207 | + poa_global=poa_global, |
| 208 | + poa_direct=poa_direct, |
| 209 | + shaded_fraction=shaded_fraction, |
| 210 | + shaded_blocks=module_shaded_blocks, |
| 211 | + total_blocks=blocks_per_module[k], |
| 212 | + ) |
| 213 | + for k, module_shaded_blocks in shaded_blocks_per_module.items() |
| 214 | +} |
| 215 | + |
| 216 | +shade_losses_per_row = { |
| 217 | + k: pvlib.shading.direct_martinez( |
| 218 | + poa_global=poa_global, |
| 219 | + poa_direct=poa_direct, |
| 220 | + shaded_fraction=shaded_fraction, |
| 221 | + shaded_blocks=module_shaded_blocks * N_modules_per_row, |
| 222 | + total_blocks=blocks_per_module[k] * N_modules_per_row, |
| 223 | + ) |
| 224 | + for k, module_shaded_blocks in shaded_blocks_per_module.items() |
| 225 | +} |
| 226 | + |
| 227 | +# %% |
| 228 | +# Plotting the results |
| 229 | +# ^^^^^^^^^^^^^^^^^^^^ |
| 230 | + |
| 231 | +fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True) |
| 232 | +fig.suptitle("Martinez power losses due to shading") |
| 233 | +for k, shade_losses in shade_losses_per_module.items(): |
| 234 | + linestyle = "--" if k == "3 bypass diodes half-cut, landscape" else "-" |
| 235 | + ax1.plot(times, shade_losses, label=k, linestyle=linestyle) |
| 236 | +ax1.legend(loc="upper center") |
| 237 | +ax1.grid() |
| 238 | +ax1.set_xlabel("Time") |
| 239 | +ax1.xaxis.set_major_formatter( |
| 240 | + ConciseDateFormatter("%H:%M", tz="Europe/Madrid") |
| 241 | +) |
| 242 | +ax1.set_ylabel(r"$P_{out}$ losses") |
| 243 | +ax1.set_title("Per module") |
| 244 | + |
| 245 | +for k, shade_losses in shade_losses_per_row.items(): |
| 246 | + linestyle = "--" if k == "3 bypass diodes half-cut, landscape" else "-" |
| 247 | + ax2.plot(times, shade_losses, label=k, linestyle=linestyle) |
| 248 | +ax2.legend(loc="upper center") |
| 249 | +ax2.grid() |
| 250 | +ax2.set_xlabel("Time") |
| 251 | +ax2.xaxis.set_major_formatter( |
| 252 | + ConciseDateFormatter("%H:%M", tz="Europe/Madrid") |
| 253 | +) |
| 254 | +ax2.set_ylabel(r"$P_{out}$ losses") |
| 255 | +ax2.set_title("Per row") |
| 256 | +fig.tight_layout() |
| 257 | +fig.show() |
| 258 | + |
| 259 | +# %% |
| 260 | +# Note how the half-cut cell module in portrait performs better than the |
| 261 | +# normal module with three bypass diodes. This is because the number of shaded |
| 262 | +# blocks is less along the shaded length is higher in the half-cut module. |
| 263 | +# This is the reason why half-cut cell modules are preferred in portrait |
| 264 | +# orientation. |
| 265 | + |
| 266 | +# %% |
| 267 | +# References |
| 268 | +# ---------- |
| 269 | +# .. [1] F. Martínez-Moreno, J. Muñoz, and E. Lorenzo, 'Experimental model |
| 270 | +# to estimate shading losses on PV arrays', Solar Energy Materials and |
| 271 | +# Solar Cells, vol. 94, no. 12, pp. 2298-2303, Dec. 2010, |
| 272 | +# :doi:`10.1016/j.solmat.2010.07.029`. |
0 commit comments