| 
 | 1 | +"""  | 
 | 2 | +Shaded fraction of a horizontal single-axis tracker  | 
 | 3 | +====================================================  | 
 | 4 | +
  | 
 | 5 | +This example illustrates how to calculate the 1D shaded fraction of three rows  | 
 | 6 | +in a North-South horizontal single axis tracker (HSAT) configuration.  | 
 | 7 | +"""  | 
 | 8 | + | 
 | 9 | +# %%  | 
 | 10 | +# :py:func:`pvlib.shading.shaded_fraction1d` exposes a useful method for the  | 
 | 11 | +# calculation of the shaded fraction of the width of a solar collector. Here,  | 
 | 12 | +# the width is defined as the dimension perpendicular to the axis of rotation.  | 
 | 13 | +# This method for calculating the shaded fraction only requires minor  | 
 | 14 | +# modifications to be applicable for fixed-tilt systems.  | 
 | 15 | +#  | 
 | 16 | +# It is highly recommended to read :py:func:`pvlib.shading.shaded_fraction1d`  | 
 | 17 | +# documentation to understand the input parameters and the method's  | 
 | 18 | +# capabilities. For more in-depth information, please see the journal paper  | 
 | 19 | +# `10.1063/5.0202220 <https://doi.org/10.1063/5.0202220>`_ describing  | 
 | 20 | +# the methodology.  | 
 | 21 | +#  | 
 | 22 | +# Let's start by obtaining the true-tracking angles for each of the rows and  | 
 | 23 | +# limiting the angles to the range of -50 to 50 degrees. This decision is  | 
 | 24 | +# done to create an example scenario with significant shading.  | 
 | 25 | +#  | 
 | 26 | +# Key functions used in this example are:  | 
 | 27 | +#  | 
 | 28 | +# 1. :py:func:`pvlib.tracking.singleaxis` to calculate the tracking angles.  | 
 | 29 | +# 2. :py:func:`pvlib.shading.projected_solar_zenith_angle` to calculate the  | 
 | 30 | +#    projected solar zenith angle.  | 
 | 31 | +# 3. :py:func:`pvlib.shading.shaded_fraction1d` to calculate the shaded  | 
 | 32 | +#    fractions.  | 
 | 33 | +#  | 
 | 34 | +# .. sectionauthor:: Echedey Luis <echelual (at) gmail.com>  | 
 | 35 | + | 
 | 36 | +import pvlib  | 
 | 37 | + | 
 | 38 | +import numpy as np  | 
 | 39 | +import pandas as pd  | 
 | 40 | +import matplotlib.pyplot as plt  | 
 | 41 | +from matplotlib.dates import DateFormatter  | 
 | 42 | + | 
 | 43 | +# Define the solar system parameters  | 
 | 44 | +latitude, longitude = 28.51, -13.89  | 
 | 45 | +altitude = pvlib.location.lookup_altitude(latitude, longitude)  | 
 | 46 | + | 
 | 47 | +axis_tilt = 3  # degrees, positive is upwards in the axis_azimuth direction  | 
 | 48 | +axis_azimuth = 180  # degrees, N-S tracking axis  | 
 | 49 | +collector_width = 3.2  # m  | 
 | 50 | +pitch = 4.15  # m  | 
 | 51 | +gcr = collector_width / pitch  | 
 | 52 | +cross_axis_slope = -5  # degrees  | 
 | 53 | +surface_to_axis_offset = 0.07  # m  | 
 | 54 | + | 
 | 55 | +# Generate a time range for the simulation  | 
 | 56 | +times = pd.date_range(  | 
 | 57 | +    start="2024-01-01T05",  | 
 | 58 | +    end="2024-01-01T21",  | 
 | 59 | +    freq="5min",  | 
 | 60 | +    tz="Atlantic/Canary",  | 
 | 61 | +)  | 
 | 62 | + | 
 | 63 | +# Calculate the solar position  | 
 | 64 | +solar_position = pvlib.solarposition.get_solarposition(  | 
 | 65 | +    times, latitude, longitude, altitude  | 
 | 66 | +)  | 
 | 67 | +solar_azimuth = solar_position["azimuth"]  | 
 | 68 | +solar_zenith = solar_position["apparent_zenith"]  | 
 | 69 | + | 
 | 70 | +# Calculate the tracking angles  | 
 | 71 | +rotation_angle = pvlib.tracking.singleaxis(  | 
 | 72 | +    solar_zenith,  | 
 | 73 | +    solar_azimuth,  | 
 | 74 | +    axis_tilt,  | 
 | 75 | +    axis_azimuth,  | 
 | 76 | +    max_angle=(-50, 50),  # (min, max) degrees  | 
 | 77 | +    backtrack=False,  | 
 | 78 | +    gcr=gcr,  | 
 | 79 | +    cross_axis_tilt=cross_axis_slope,  | 
 | 80 | +)["tracker_theta"]  | 
 | 81 | + | 
 | 82 | +# %%  | 
 | 83 | +# Once the tracker angles have been obtained, the next step is to calculate  | 
 | 84 | +# the shaded fraction. Special care must be taken  | 
 | 85 | +# to ensure that the shaded or shading tracker roles are correctly assigned  | 
 | 86 | +# depending on the solar position.  | 
 | 87 | +# This means we will have a result for each row, ``eastmost_shaded_fraction``,  | 
 | 88 | +# ``middle_shaded_fraction``, and ``westmost_shaded_fraction``.  | 
 | 89 | +# Switching the parameters will be based on the  | 
 | 90 | +# sign of :py:func:`pvlib.shading.projected_solar_zenith_angle`.  | 
 | 91 | +#  | 
 | 92 | +# The following code is verbose to make it easier to understand the process,  | 
 | 93 | +# but with some effort you may be able to simplify it. This verbosity also  | 
 | 94 | +# allows to change the premises easily per case, e.g., in case of a tracker  | 
 | 95 | +# failure or with a different system configuration.  | 
 | 96 | + | 
 | 97 | +psza = pvlib.shading.projected_solar_zenith_angle(  | 
 | 98 | +    solar_zenith, solar_azimuth, axis_tilt, axis_azimuth  | 
 | 99 | +)  | 
 | 100 | + | 
 | 101 | +# Calculate the shaded fraction for the eastmost row  | 
 | 102 | +eastmost_shaded_fraction = np.where(  | 
 | 103 | +    psza < 0,  | 
 | 104 | +    0,  # no shaded fraction in the morning  | 
 | 105 | +    # shaded fraction in the evening  | 
 | 106 | +    pvlib.shading.shaded_fraction1d(  | 
 | 107 | +        solar_zenith,  | 
 | 108 | +        solar_azimuth,  | 
 | 109 | +        axis_azimuth,  | 
 | 110 | +        shaded_row_rotation=rotation_angle,  | 
 | 111 | +        axis_tilt=axis_tilt,  | 
 | 112 | +        collector_width=collector_width,  | 
 | 113 | +        pitch=pitch,  | 
 | 114 | +        surface_to_axis_offset=surface_to_axis_offset,  | 
 | 115 | +        cross_axis_slope=cross_axis_slope,  | 
 | 116 | +        shading_row_rotation=rotation_angle,  | 
 | 117 | +    ),  | 
 | 118 | +)  | 
 | 119 | + | 
 | 120 | +# Calculate the shaded fraction for the middle row  | 
 | 121 | +middle_shaded_fraction = np.where(  | 
 | 122 | +    psza < 0,  | 
 | 123 | +    # shaded fraction in the morning  | 
 | 124 | +    pvlib.shading.shaded_fraction1d(  | 
 | 125 | +        solar_zenith,  | 
 | 126 | +        solar_azimuth,  | 
 | 127 | +        axis_azimuth,  | 
 | 128 | +        shaded_row_rotation=rotation_angle,  | 
 | 129 | +        axis_tilt=axis_tilt,  | 
 | 130 | +        collector_width=collector_width,  | 
 | 131 | +        pitch=pitch,  | 
 | 132 | +        surface_to_axis_offset=surface_to_axis_offset,  | 
 | 133 | +        cross_axis_slope=cross_axis_slope,  | 
 | 134 | +        shading_row_rotation=rotation_angle,  | 
 | 135 | +    ),  | 
 | 136 | +    # shaded fraction in the evening  | 
 | 137 | +    pvlib.shading.shaded_fraction1d(  | 
 | 138 | +        solar_zenith,  | 
 | 139 | +        solar_azimuth,  | 
 | 140 | +        axis_azimuth,  | 
 | 141 | +        shaded_row_rotation=rotation_angle,  | 
 | 142 | +        axis_tilt=axis_tilt,  | 
 | 143 | +        collector_width=collector_width,  | 
 | 144 | +        pitch=pitch,  | 
 | 145 | +        surface_to_axis_offset=surface_to_axis_offset,  | 
 | 146 | +        cross_axis_slope=cross_axis_slope,  | 
 | 147 | +        shading_row_rotation=rotation_angle,  | 
 | 148 | +    ),  | 
 | 149 | +)  | 
 | 150 | + | 
 | 151 | +# Calculate the shaded fraction for the westmost row  | 
 | 152 | +westmost_shaded_fraction = np.where(  | 
 | 153 | +    psza < 0,  | 
 | 154 | +    # shaded fraction in the morning  | 
 | 155 | +    pvlib.shading.shaded_fraction1d(  | 
 | 156 | +        solar_zenith,  | 
 | 157 | +        solar_azimuth,  | 
 | 158 | +        axis_azimuth,  | 
 | 159 | +        shaded_row_rotation=rotation_angle,  | 
 | 160 | +        axis_tilt=axis_tilt,  | 
 | 161 | +        collector_width=collector_width,  | 
 | 162 | +        pitch=pitch,  | 
 | 163 | +        surface_to_axis_offset=surface_to_axis_offset,  | 
 | 164 | +        cross_axis_slope=cross_axis_slope,  | 
 | 165 | +        shading_row_rotation=rotation_angle,  | 
 | 166 | +    ),  | 
 | 167 | +    0,  # no shaded fraction in the evening  | 
 | 168 | +)  | 
 | 169 | + | 
 | 170 | +# %%  | 
 | 171 | +# Plot the shaded fraction result for each row:  | 
 | 172 | +plt.plot(times, eastmost_shaded_fraction, label="East-most", color="blue")  | 
 | 173 | +plt.plot(times, middle_shaded_fraction, label="Middle", color="green",  | 
 | 174 | +         linewidth=3, linestyle="--")  # fmt: skip  | 
 | 175 | +plt.plot(times, westmost_shaded_fraction, label="West-most", color="red")  | 
 | 176 | +plt.title(r"$shaded\_fraction1d$ of each row vs time")  | 
 | 177 | +plt.xlabel("Time")  | 
 | 178 | +plt.gca().xaxis.set_major_formatter(DateFormatter("%H:%M"))  | 
 | 179 | +plt.ylabel("Shaded Fraction")  | 
 | 180 | +plt.legend()  | 
 | 181 | +plt.show()  | 
0 commit comments