Skip to content

Commit 5a6b65c

Browse files
kandersolarwholmgren
authored andcommitted
Add gallery of examples using sphinx-gallery (#846)
* draft of sunpath diagram example * add explanation text and cartesian diagram * Delete solarposition.rst * Add sphinx-gallery and configuration * add sunpath example * add singleaxis tracker example * linting fixes * rename "Intro Examples" to "Intro Tutorial", move "Example Gallery" up in the toctree * Update introtutorial.rst * more linting fixes * last linting fix, probably * Mention example gallery in whatsnew * update introtutorial link in package_overview.rst; move warnings import to top of conf.py
1 parent 7ff8421 commit 5a6b65c

File tree

9 files changed

+244
-6
lines changed

9 files changed

+244
-6
lines changed

docs/examples/README.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Example Gallery
2+
===============
3+
4+
This gallery shows examples of pvlib functionality. Community contributions are welcome!
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""
2+
Single-axis tracking
3+
====================
4+
5+
Examples of modeling tilt angles for single-axis tracker arrays.
6+
"""
7+
8+
#%%
9+
# This example shows basic usage of pvlib's tracker position calculations with
10+
# :py:meth:`pvlib.tracking.singleaxis`. The examples shown here demonstrate
11+
# how the tracker parameters affect the generated tilt angles.
12+
#
13+
# Because tracker angle is based on where the sun is in the sky, calculating
14+
# solar position is always the first step.
15+
#
16+
# True-tracking
17+
# -------------
18+
#
19+
# The basic tracking algorithm is called "true-tracking". It orients the panels
20+
# towards the sun as much as possible in order to maximize the cross section
21+
# presented towards incoming beam irradiance.
22+
23+
from pvlib import solarposition, tracking
24+
import pandas as pd
25+
import matplotlib.pyplot as plt
26+
27+
tz = 'US/Eastern'
28+
lat, lon = 40, -80
29+
30+
times = pd.date_range('2019-01-01', '2019-01-02', closed='left', freq='5min',
31+
tz=tz)
32+
solpos = solarposition.get_solarposition(times, lat, lon)
33+
34+
truetracking_angles = tracking.singleaxis(
35+
apparent_zenith=solpos['apparent_zenith'],
36+
apparent_azimuth=solpos['azimuth'],
37+
axis_tilt=0,
38+
axis_azimuth=180,
39+
max_angle=90,
40+
backtrack=False, # for true-tracking
41+
gcr=0.5) # irrelevant for true-tracking
42+
43+
truetracking_position = truetracking_angles['tracker_theta'].fillna(0)
44+
truetracking_position.plot(title='Truetracking Curve')
45+
46+
plt.show()
47+
48+
#%%
49+
# Backtracking
50+
# -------------
51+
#
52+
# Because truetracking yields steep tilt angle in morning and afternoon, it
53+
# will cause row to row shading as the shadows from adjacent rows fall on each
54+
# other. To prevent this, the trackers can rotate backwards when the sun is
55+
# near the horizon -- "backtracking". The shading angle depends on row
56+
# geometry, so the gcr parameter must be specified. The greater the gcr, the
57+
# tighter the row spacing and the more aggressively the array must backtrack.
58+
59+
fig, ax = plt.subplots()
60+
61+
for gcr in [0.2, 0.4, 0.6]:
62+
backtracking_angles = tracking.singleaxis(
63+
apparent_zenith=solpos['apparent_zenith'],
64+
apparent_azimuth=solpos['azimuth'],
65+
axis_tilt=0,
66+
axis_azimuth=180,
67+
max_angle=90,
68+
backtrack=True,
69+
gcr=gcr)
70+
71+
backtracking_position = backtracking_angles['tracker_theta'].fillna(0)
72+
backtracking_position.plot(title='Backtracking Curve',
73+
label='GCR:{:0.01f}'.format(gcr),
74+
ax=ax)
75+
76+
plt.legend()
77+
plt.show()
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""
2+
Sun path diagram
3+
================
4+
5+
Examples of generating sunpath diagrams.
6+
"""
7+
8+
#%%
9+
# This example shows basic usage of pvlib's solar position calculations with
10+
# :py:meth:`pvlib.solarposition.get_solarposition`. The examples shown here
11+
# will generate sunpath diagrams that shows solar position over a year.
12+
#
13+
# Polar plot
14+
# ----------
15+
#
16+
# Below is an example plot of solar position in
17+
# `polar coordinates <https://en.wikipedia.org/wiki/Polar_coordinate_system>`_.
18+
19+
from pvlib import solarposition
20+
import pandas as pd
21+
import numpy as np
22+
import matplotlib.pyplot as plt
23+
24+
tz = 'Asia/Calcutta'
25+
lat, lon = 28.6, 77.2
26+
27+
times = pd.date_range('2019-01-01 00:00:00', '2020-01-01', closed='left',
28+
freq='H', tz=tz)
29+
solpos = solarposition.get_solarposition(times, lat, lon)
30+
# remove nighttime
31+
solpos = solpos.loc[solpos['apparent_elevation'] > 0, :]
32+
33+
ax = plt.subplot(1, 1, 1, projection='polar')
34+
# draw the analemma loops
35+
points = ax.scatter(np.radians(solpos.azimuth), solpos.apparent_zenith,
36+
s=2, label=None, c=solpos.index.dayofyear)
37+
ax.figure.colorbar(points)
38+
39+
# draw hour labels
40+
for hour in np.unique(solpos.index.hour):
41+
# choose label position by the smallest radius for each hour
42+
subset = solpos.loc[solpos.index.hour == hour, :]
43+
r = subset.apparent_zenith
44+
pos = solpos.loc[r.idxmin(), :]
45+
ax.text(np.radians(pos['azimuth']), pos['apparent_zenith'], str(hour))
46+
47+
# draw individual days
48+
for date in pd.to_datetime(['2019-03-21', '2019-06-21', '2019-12-21']):
49+
times = pd.date_range(date, date+pd.Timedelta('24h'), freq='5min', tz=tz)
50+
solpos = solarposition.get_solarposition(times, lat, lon)
51+
solpos = solpos.loc[solpos['apparent_elevation'] > 0, :]
52+
label = date.strftime('%Y-%m-%d')
53+
ax.plot(np.radians(solpos.azimuth), solpos.apparent_zenith, label=label)
54+
55+
ax.figure.legend(loc='upper left')
56+
57+
# change coordinates to be like a compass
58+
ax.set_theta_zero_location('N')
59+
ax.set_theta_direction(-1)
60+
ax.set_rmax(90)
61+
62+
plt.show()
63+
64+
#%%
65+
# This is a polar plot of hourly solar zenith and azimuth. The figure-8
66+
# patterns are called `analemmas <https://en.wikipedia.org/wiki/Analemma>`_ and
67+
# show how the sun's path slowly shifts over the course of the year . The
68+
# colored lines show the single-day sun paths for the winter and summer
69+
# solstices as well as the spring equinox.
70+
#
71+
# The soltice paths mark the boundary of the sky area that the sun traverses
72+
# over a year. The diagram shows that there is no point in the
73+
# year when is the sun directly overhead (zenith=0) -- note that this location
74+
# is north of the Tropic of Cancer.
75+
#
76+
# Examining the sun path for the summer solstice in particular shows that
77+
# the sun rises north of east, crosses into the southern sky around 10 AM for a
78+
# few hours before crossing back into the northern sky around 3 PM and setting
79+
# north of west. In contrast, the winter solstice sun path remains in the
80+
# southern sky the entire day. Moreover, the diagram shows that the winter
81+
# solstice is a shorter day than the summer soltice -- in December, the sun
82+
# rises after 7 AM and sets before 6 PM, whereas in June the sun is up before
83+
# 6 AM and sets after 7 PM.
84+
#
85+
# Another use of this diagram is to determine what times of year the sun is
86+
# blocked by obstacles. For instance, for a mountain range on the western side
87+
# of an array that extends 10 degrees above the horizon, the sun is blocked:
88+
#
89+
# - after about 6:30 PM on the summer solstice
90+
# - after about 5:30 PM on the spring equinox
91+
# - after about 4:30 PM on the winter solstice
92+
93+
#%%
94+
# PVSyst Plot
95+
# -----------
96+
#
97+
# PVSyst users will be more familiar with sunpath diagrams in Cartesian
98+
# coordinates:
99+
100+
from pvlib import solarposition
101+
import pandas as pd
102+
import numpy as np
103+
import matplotlib.pyplot as plt
104+
105+
tz = 'Asia/Calcutta'
106+
lat, lon = 28.6, 77.2
107+
times = pd.date_range('2019-01-01 00:00:00', '2020-01-01', closed='left',
108+
freq='H', tz=tz)
109+
110+
solpos = solarposition.get_solarposition(times, lat, lon)
111+
# remove nighttime
112+
solpos = solpos.loc[solpos['apparent_elevation'] > 0, :]
113+
114+
fig, ax = plt.subplots()
115+
points = ax.scatter(solpos.azimuth, solpos.apparent_elevation, s=2,
116+
c=solpos.index.dayofyear, label=None)
117+
fig.colorbar(points)
118+
119+
for hour in np.unique(solpos.index.hour):
120+
# choose label position by the largest elevation for each hour
121+
subset = solpos.loc[solpos.index.hour == hour, :]
122+
height = subset.apparent_elevation
123+
pos = solpos.loc[height.idxmax(), :]
124+
ax.text(pos['azimuth'], pos['apparent_elevation'], str(hour))
125+
126+
for date in pd.to_datetime(['2019-03-21', '2019-06-21', '2019-12-21']):
127+
times = pd.date_range(date, date+pd.Timedelta('24h'), freq='5min', tz=tz)
128+
solpos = solarposition.get_solarposition(times, lat, lon)
129+
solpos = solpos.loc[solpos['apparent_elevation'] > 0, :]
130+
label = date.strftime('%Y-%m-%d')
131+
ax.plot(solpos.azimuth, solpos.apparent_elevation, label=label)
132+
133+
ax.figure.legend(loc='upper left')
134+
ax.set_xlabel('Solar Azimuth (degrees)')
135+
ax.set_ylabel('Solar Elevation (degrees)')
136+
137+
plt.show()

docs/sphinx/source/conf.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# Mock modules so RTD works
1919
from unittest.mock import MagicMock
2020

21+
# for warning suppression
22+
import warnings
23+
2124

2225
class Mock(MagicMock):
2326
@classmethod
@@ -55,7 +58,8 @@ def __getattr__(cls, name):
5558
'sphinx.ext.napoleon',
5659
'sphinx.ext.autosummary',
5760
'IPython.sphinxext.ipython_directive',
58-
'IPython.sphinxext.ipython_console_highlighting'
61+
'IPython.sphinxext.ipython_console_highlighting',
62+
'sphinx_gallery.gen_gallery',
5963
]
6064

6165
napoleon_use_rtype = False # group rtype on same line together with return
@@ -324,3 +328,16 @@ def setup(app):
324328
# suppress "WARNING: Footnote [1] is not referenced." messages
325329
# https://github.com/pvlib/pvlib-python/issues/837
326330
suppress_warnings = ['ref.footnote']
331+
332+
# settings for sphinx-gallery
333+
sphinx_gallery_conf = {
334+
'examples_dirs': ['../../examples'], # location of gallery scripts
335+
'gallery_dirs': ['auto_examples'], # location of generated output
336+
# sphinx-gallery only shows plots from plot_*.py files by default:
337+
# 'filename_pattern': '*.py',
338+
}
339+
# supress warnings in gallery output
340+
# https://sphinx-gallery.github.io/stable/configuration.html
341+
warnings.filterwarnings("ignore", category=UserWarning,
342+
message='Matplotlib is currently using agg, which is a'
343+
' non-GUI backend, so cannot show the figure.')

docs/sphinx/source/index.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ Contents
8080
:maxdepth: 1
8181

8282
package_overview
83-
introexamples
83+
introtutorial
84+
auto_examples/index
8485
whatsnew
8586
installation
8687
contributing

docs/sphinx/source/introexamples.rst renamed to docs/sphinx/source/introtutorial.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
.. _introexamples:
1+
.. _introtutorial:
22

3-
Intro Examples
3+
Intro Tutorial
44
==============
55

66
This page contains introductory examples of pvlib python usage.

docs/sphinx/source/package_overview.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interoperable, and benchmark implementations of PV system models.
1212
There are at least as many opinions about how to model PV systems as
1313
there are modelers of PV systems, so pvlib-python provides several
1414
modeling paradigms: functions, the Location/PVSystem classes, and the
15-
ModelChain class. Read more about this in the :ref:`introexamples`
15+
ModelChain class. Read more about this in the :ref:`introtutorial`
1616
section.
1717

1818

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Testing
2727

2828
Documentation
2929
~~~~~~~~~~~~~
30+
* Created an Example Gallery. (:pull:`846`)
3031
* Updated list of allowed years for `iotools.get_psm3`.
3132

3233
Contributors

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@
4646
EXTRAS_REQUIRE = {
4747
'optional': ['ephem', 'cython', 'netcdf4', 'nrel-pysam', 'numba',
4848
'pvfactors', 'scipy', 'siphon', 'tables'],
49-
'doc': ['ipython', 'matplotlib', 'sphinx == 1.8.5', 'sphinx_rtd_theme'],
49+
'doc': ['ipython', 'matplotlib', 'sphinx == 1.8.5', 'sphinx_rtd_theme',
50+
'sphinx-gallery'],
5051
'test': TESTS_REQUIRE
5152
}
5253
EXTRAS_REQUIRE['all'] = sorted(set(sum(EXTRAS_REQUIRE.values(), [])))

0 commit comments

Comments
 (0)