Skip to content

Commit d53f97e

Browse files
echedey-lskandersolaradriessewholmgrenmikofski
authored
Shaded Fraction on Sloped Terrains - PR1725 partial continuation (#1962)
* Update shading.py * Minimal test * Implementation From NREL paper * Fix, fix, fix, fix & format * Format issues * Extend tests (compare with singleaxis) & format with ruff * Format fixes * Upgrade tests * Array -> Axis * type * Whatsnew * xd * bruh * Minor Python optimization a la tracking.singleaxis * Comment and minor optimizations * Surface -> Axis Co-Authored-By: Kevin Anderson <[email protected]> * Elevation -> Zenith Co-Authored-By: Kevin Anderson <[email protected]> * Elev -> Zenith Co-Authored-By: Kevin Anderson <[email protected]> * Update shading.py * Update docstring Co-Authored-By: Anton Driesse <[email protected]> * Add comments from `tracking.singleaxis` Co-Authored-By: Will Holmgren <[email protected]> Co-Authored-By: Mark Mikofski <[email protected]> * Singleaxis implementation port & test addition, based on old pvlib.tracking.singleaxis * Update v0.10.4.rst * Linter * Code review Co-Authored-By: Cliff Hansen <[email protected]> * Add Fig 5 [1] (still gotta check the built output) * Add caption, change size and describe in alternate text * rST fixes ? * Figures have captions, images do not https://pandemic-overview.readthedocs.io/en/latest/myGuides/reStructuredText-Images-and-Figures-Examples.html#id18 * Flip arguments order * I forgot 💀 * Linter are you happy now? * Remove port test and add edge cases test Co-Authored-By: Kevin Anderson <[email protected]> * Update test_shading.py Co-Authored-By: Kevin Anderson <[email protected]> * Indentation xd * Update test_shading.py * I forgot how to code * Align data * Docstring suggestion from Kevin Co-Authored-By: Kevin Anderson <[email protected]> * Update link to example? * add linear shade loss for thin films * add tests, update docs, what's new * fix what's new gh issue and pr links * fix trailing whitespace * responding to comments - move linear shade loss to shading module - don't use ternary, doesn't work on vectors, instead use np.where() - set cross axis default to zero - test vectors - update docs * update docstring for linear shade loss - applicable to other monolithic thin film like CIGS, not just CdTe - only when shade is perpendicular to scribe lines * update example in linear_shade_loss * add figure and formulas to shaded fraction * shaded fraction consistently * Add alternative text to image * Update implementation based on PSZ PR. See description. Commit highlights ✨ : * I think I made all the code a bit more legible; sorry for the big changes @mikofski * Tests a bit more complete (not much, still consider the same test data) * Rename shaded fraction acronym from `fs` to `sf` * Asserts changed to `assert_allclose` for a more legible output in case of failure Co-Authored-By: Mark Mikofski <[email protected]> * Whatsnew entries Co-Authored-By: Mark Mikofski <[email protected]> * Linter * Clear things, convert Mark's reference to a reference * Linter * Update according to changes at PSZA PR * Another commit, another try * Ahhh, I rebased too fast * whatsnews * Update v0.10.4.rst * Update v0.10.3.rst * Rename to `shaded_fraction1d`, change params to `surface_*` instead of `tracker_*` * Left this tracker refs behind * Change rename in rst entries * Add another testcase * Improve docs references, clarify nomenclature Co-Authored-By: Kevin Anderson <[email protected]> * Update test_shading.py * Remove linear_shade_loss * First implementation of the new shaded fraction model (missing figure) * Create Anderson_Jensen_2024_Fig3.png * Update shading.py * Update shading.py * Update shading.py * lintaaargggg * Fill reference * Next release 0.10.5? * Fix tests * Update test_shading.py * Little improvement to table definitions * Change `l` to `\ell` Co-Authored-By: Kevin Anderson <[email protected]> * `pvlib.tracking.projected_solar_zenith_angle` to `pvlib.shading.projected_solar_zenith_angle` Co-Authored-By: Kevin Anderson <[email protected]> * pitch references to `pitch` Co-Authored-By: Kevin Anderson <[email protected]> * `trackers_axis_azimuth` to `axis_azimuth` Co-Authored-By: Kevin Anderson <[email protected]> * whatsnews Co-Authored-By: Kevin Anderson <[email protected]> * Update v0.10.5.rst Co-Authored-By: Kevin Anderson <[email protected]> * Change `tilt`s to `rotation`s and add `axis_tilt` Co-Authored-By: Kevin Anderson <[email protected]> * Forgot to update tests 💀 Co-Authored-By: Kevin Anderson <[email protected]> * Add examples section * roles assumption messin w/ me docs 😲 * roles assumption messin w/ me docs 😲 Co-Authored-By: Kevin Anderson <[email protected]> * Update shading.py * Update shading.py * Add gallery example * This was fixed in recent sphinx-gallery releases IIRC * Extra empty line or admonition type unsupported * Fix example link (hopefully 🙏 ) * Update shading.py * Fix subsubsections? * Nah, bulleted list didn't work * tilted -> tracker, only affects text * Typos and unreasonable physical values Co-Authored-By: Kevin Anderson <[email protected]> * See the Examples section below, not the unlinkable link Co-Authored-By: Kevin Anderson <[email protected]> * tracker -> row, param names, code and docs Co-Authored-By: Kevin Anderson <[email protected]> * Fix broken example 🔧 Co-Authored-By: Kevin Anderson <[email protected]> * Apply suggestions from code review Co-authored-by: Adam R. Jensen <[email protected]> * "the row axis/axes" instead of ``axis_azimuth`` * Unnecessary math mode Co-Authored-By: Adam R. Jensen <[email protected]> * Example suggestions and text trimming Co-Authored-By: Adam R. Jensen <[email protected]> * whatsmes * Add test to fix coverage issue * Apply suggestions from code review (Adam) Co-authored-by: Adam R. Jensen <[email protected]> * Update docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py * Update docs/examples/shading/plot_shaded_fraction1d_ns_hsat_example.py --------- Co-authored-by: Kevin Anderson <[email protected]> Co-authored-by: Anton Driesse <[email protected]> Co-authored-by: Will Holmgren <[email protected]> Co-authored-by: Mark Mikofski <[email protected]> Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Adam R. Jensen <[email protected]> Co-authored-by: Kevin Anderson <[email protected]>
1 parent eeec98c commit d53f97e

File tree

6 files changed

+505
-2
lines changed

6 files changed

+505
-2
lines changed
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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()
Loading

docs/sphinx/source/reference/effects_on_pv_system_output/shading.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,5 @@ Shading
1111
shading.masking_angle_passias
1212
shading.sky_diffuse_passias
1313
shading.projected_solar_zenith_angle
14+
shading.shaded_fraction1d
15+

docs/sphinx/source/whatsnew/v0.11.0.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Deprecations
1515

1616
Enhancements
1717
~~~~~~~~~~~~
18+
* Add function :py:func:`pvlib.shading.shaded_fraction1d`, to calculate the
19+
shade perpendicular to ``axis_azimuth``. The function is applicable to both
20+
fixed-tilt and one-axis tracking systems.
21+
(:issue:`1689`, :pull:`1725`, :pull:`1962`)
1822

1923

2024
Bug fixes
@@ -36,4 +40,5 @@ Requirements
3640
Contributors
3741
~~~~~~~~~~~~
3842
* Cliff Hansen (:ghuser:`cwhanse`)
39-
* Siddharth Kaul (:ghuser:`k10blogger`)
43+
* Mark Mikofski (:ghuser:`mikofski`)
44+
* Siddharth Kaul (:ghuser:`k10blogger`)

0 commit comments

Comments
 (0)