Skip to content

Commit bf21779

Browse files
authored
Add transposition gain gallery example (pvlib#979)
* add transposition gain example * whatsnew * review suggestions * flat -> horizontal
1 parent d2f15e3 commit bf21779

File tree

2 files changed

+123
-0
lines changed

2 files changed

+123
-0
lines changed
+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""
2+
Modeling Transposition Gain
3+
===========================
4+
5+
Calculating the gain in insolation of a tilted module over a flat module.
6+
"""
7+
8+
# %%
9+
# This example shows how to evaluate the transposition gain of a racking
10+
# strategy. The transposition gain is the additional insolation collected
11+
# by orienting at a tilt instead of horizontal; using PV modeling lingo, it's
12+
# the increase in POA (plane of array) insolation over GHI (global horizontal
13+
# irradiance) insolation.
14+
#
15+
# This example uses a TMY dataset and the
16+
# :py:meth:`pvlib.irradiance.get_total_irradiance` function to transpose
17+
# irradiance components to POA irradiance for various fixed tilts. It also
18+
# models a single-axis tracking system for comparison. The monthly POA
19+
# insolation is calculated for each strategy to show how orientation affects
20+
# seasonal irradiance collection.
21+
22+
import pvlib
23+
from pvlib import location
24+
from pvlib import irradiance
25+
from pvlib import tracking
26+
from pvlib.iotools import read_tmy3
27+
import pandas as pd
28+
from matplotlib import pyplot as plt
29+
import pathlib
30+
31+
# get full path to the data directory
32+
DATA_DIR = pathlib.Path(pvlib.__file__).parent / 'data'
33+
34+
# get TMY3 dataset
35+
tmy, metadata = read_tmy3(DATA_DIR / '723170TYA.CSV', coerce_year=1990)
36+
# TMY3 datasets are right-labeled (AKA "end of interval") which means the last
37+
# interval of Dec 31, 23:00 to Jan 1 00:00 is labeled Jan 1 00:00. When rolling
38+
# up hourly irradiance to monthly insolation, a spurious January value is
39+
# calculated from that last row, so we'll just go ahead and drop it here:
40+
tmy = tmy.iloc[:-1, :]
41+
42+
# create location object to store lat, lon, timezone
43+
location = location.Location.from_tmy(metadata)
44+
45+
# calculate the necessary variables to do transposition. Note that solar
46+
# position doesn't depend on array orientation, so we just calculate it once.
47+
# Note also that TMY datasets are right-labeled hourly intervals, e.g. the
48+
# 10AM to 11AM interval is labeled 11. We should calculate solar position in
49+
# the middle of the interval (10:30), so we subtract 30 minutes:
50+
times = tmy.index - pd.Timedelta('30min')
51+
solar_position = location.get_solarposition(times)
52+
# but remember to shift the index back to line up with the TMY data:
53+
solar_position.index += pd.Timedelta('30min')
54+
55+
56+
# create a helper function to do the transposition for us
57+
def calculate_poa(tmy, solar_position, surface_tilt, surface_azimuth):
58+
# Use the get_total_irradiance function to transpose the irradiance
59+
# components to POA irradiance
60+
poa = irradiance.get_total_irradiance(
61+
surface_tilt=surface_tilt,
62+
surface_azimuth=surface_azimuth,
63+
dni=tmy['DNI'],
64+
ghi=tmy['GHI'],
65+
dhi=tmy['DHI'],
66+
solar_zenith=solar_position['apparent_zenith'],
67+
solar_azimuth=solar_position['azimuth'],
68+
model='isotropic')
69+
return poa['poa_global'] # just return the total in-plane irradiance
70+
71+
72+
# create a dataframe to keep track of our monthly insolations
73+
df_monthly = pd.DataFrame()
74+
75+
# fixed-tilt:
76+
for tilt in range(0, 50, 10):
77+
# we will hardcode azimuth=180 (south) for all fixed-tilt cases
78+
poa_irradiance = calculate_poa(tmy, solar_position, tilt, 180)
79+
column_name = "FT-{}".format(tilt)
80+
# TMYs are hourly, so we can just sum up irradiance [W/m^2] to get
81+
# insolation [Wh/m^2]:
82+
df_monthly[column_name] = poa_irradiance.resample('m').sum()
83+
84+
# single-axis tracking:
85+
orientation = tracking.singleaxis(solar_position['apparent_zenith'],
86+
solar_position['azimuth'],
87+
axis_tilt=0, # flat array
88+
axis_azimuth=180, # south-facing azimuth
89+
max_angle=60, # a common maximum rotation
90+
backtrack=True, # backtrack for a c-Si array
91+
gcr=0.4) # a common ground coverage ratio
92+
93+
poa_irradiance = calculate_poa(tmy,
94+
solar_position,
95+
orientation['surface_tilt'],
96+
orientation['surface_azimuth'])
97+
df_monthly['SAT-0.4'] = poa_irradiance.resample('m').sum()
98+
99+
# calculate the percent difference from GHI
100+
ghi_monthly = tmy['GHI'].resample('m').sum()
101+
df_monthly = 100 * (df_monthly.divide(ghi_monthly, axis=0) - 1)
102+
103+
df_monthly.plot()
104+
plt.xlabel('Month of Year')
105+
plt.ylabel('Monthly Transposition Gain [%]')
106+
plt.show()
107+
108+
109+
# %%
110+
# Note that in summer, steeper tilts actually collect less insolation than
111+
# flatter tilts because the sun is so high in the sky at solar noon. However,
112+
# the steeper tilts significantly increase insolation capture in winter when
113+
# the sun is lower in the sky. In contrast to the highly seasonal gain shown
114+
# by fixed tilts, the tracker system shows a much more consistent gain
115+
# year-round.
116+
#
117+
# Because the seasonality of the fixed-tilt transposition gain is driven by
118+
# solar position angles, the relative behavior of different orientations will
119+
# be different for different locations. For example, a common rule of thumb
120+
# (considered somewhat outdated today) used to be to set tilt equal to the
121+
# latitude of the system location. At higher latitudes, the sun doesn't get
122+
# as high in the sky, so steeper tilts make more sense.

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

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ Documentation
3232
* Clarify units for heat loss factors in
3333
:py:func:`pvlib.temperature.pvsyst_cell` and
3434
:py:func:`pvlib.temperature.faiman`. (:pull:`960`)
35+
* Add a transposition gain example to the gallery. (:pull:`979`)
3536
* Add a gallery example of calculating diffuse IAM using
3637
:py:func:`pvlib.iam.marion_diffuse`. (:pull:`984`)
3738

0 commit comments

Comments
 (0)