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