diff --git a/docs/examples/README.rst b/docs/examples/README.rst new file mode 100644 index 0000000000..4f0e8ab71c --- /dev/null +++ b/docs/examples/README.rst @@ -0,0 +1,4 @@ +Example Gallery +=============== + +This gallery shows examples of pvlib functionality. Community contributions are welcome! \ No newline at end of file diff --git a/docs/examples/plot_single_axis_tracking.py b/docs/examples/plot_single_axis_tracking.py new file mode 100644 index 0000000000..75ea3ba1ca --- /dev/null +++ b/docs/examples/plot_single_axis_tracking.py @@ -0,0 +1,77 @@ +""" +Single-axis tracking +==================== + +Examples of modeling tilt angles for single-axis tracker arrays. +""" + +#%% +# This example shows basic usage of pvlib's tracker position calculations with +# :py:meth:`pvlib.tracking.singleaxis`. The examples shown here demonstrate +# how the tracker parameters affect the generated tilt angles. +# +# Because tracker angle is based on where the sun is in the sky, calculating +# solar position is always the first step. +# +# True-tracking +# ------------- +# +# The basic tracking algorithm is called "true-tracking". It orients the panels +# towards the sun as much as possible in order to maximize the cross section +# presented towards incoming beam irradiance. + +from pvlib import solarposition, tracking +import pandas as pd +import matplotlib.pyplot as plt + +tz = 'US/Eastern' +lat, lon = 40, -80 + +times = pd.date_range('2019-01-01', '2019-01-02', closed='left', freq='5min', + tz=tz) +solpos = solarposition.get_solarposition(times, lat, lon) + +truetracking_angles = tracking.singleaxis( + apparent_zenith=solpos['apparent_zenith'], + apparent_azimuth=solpos['azimuth'], + axis_tilt=0, + axis_azimuth=180, + max_angle=90, + backtrack=False, # for true-tracking + gcr=0.5) # irrelevant for true-tracking + +truetracking_position = truetracking_angles['tracker_theta'].fillna(0) +truetracking_position.plot(title='Truetracking Curve') + +plt.show() + +#%% +# Backtracking +# ------------- +# +# Because truetracking yields steep tilt angle in morning and afternoon, it +# will cause row to row shading as the shadows from adjacent rows fall on each +# other. To prevent this, the trackers can rotate backwards when the sun is +# near the horizon -- "backtracking". The shading angle depends on row +# geometry, so the gcr parameter must be specified. The greater the gcr, the +# tighter the row spacing and the more aggressively the array must backtrack. + +fig, ax = plt.subplots() + +for gcr in [0.2, 0.4, 0.6]: + backtracking_angles = tracking.singleaxis( + apparent_zenith=solpos['apparent_zenith'], + apparent_azimuth=solpos['azimuth'], + axis_tilt=0, + axis_azimuth=180, + max_angle=90, + backtrack=True, + gcr=gcr) + + backtracking_position = backtracking_angles['tracker_theta'].fillna(0) + backtracking_position.plot(title='Backtracking Curve', + label='GCR:{:0.01f}'.format(gcr), + ax=ax) + +plt.legend() +plt.show() diff --git a/docs/examples/plot_sunpath_diagrams.py b/docs/examples/plot_sunpath_diagrams.py new file mode 100644 index 0000000000..2b36b7e6b4 --- /dev/null +++ b/docs/examples/plot_sunpath_diagrams.py @@ -0,0 +1,137 @@ +""" +Sun path diagram +================ + +Examples of generating sunpath diagrams. +""" + +#%% +# This example shows basic usage of pvlib's solar position calculations with +# :py:meth:`pvlib.solarposition.get_solarposition`. The examples shown here +# will generate sunpath diagrams that shows solar position over a year. +# +# Polar plot +# ---------- +# +# Below is an example plot of solar position in +# `polar coordinates `_. + +from pvlib import solarposition +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +tz = 'Asia/Calcutta' +lat, lon = 28.6, 77.2 + +times = pd.date_range('2019-01-01 00:00:00', '2020-01-01', closed='left', + freq='H', tz=tz) +solpos = solarposition.get_solarposition(times, lat, lon) +# remove nighttime +solpos = solpos.loc[solpos['apparent_elevation'] > 0, :] + +ax = plt.subplot(1, 1, 1, projection='polar') +# draw the analemma loops +points = ax.scatter(np.radians(solpos.azimuth), solpos.apparent_zenith, + s=2, label=None, c=solpos.index.dayofyear) +ax.figure.colorbar(points) + +# draw hour labels +for hour in np.unique(solpos.index.hour): + # choose label position by the smallest radius for each hour + subset = solpos.loc[solpos.index.hour == hour, :] + r = subset.apparent_zenith + pos = solpos.loc[r.idxmin(), :] + ax.text(np.radians(pos['azimuth']), pos['apparent_zenith'], str(hour)) + +# draw individual days +for date in pd.to_datetime(['2019-03-21', '2019-06-21', '2019-12-21']): + times = pd.date_range(date, date+pd.Timedelta('24h'), freq='5min', tz=tz) + solpos = solarposition.get_solarposition(times, lat, lon) + solpos = solpos.loc[solpos['apparent_elevation'] > 0, :] + label = date.strftime('%Y-%m-%d') + ax.plot(np.radians(solpos.azimuth), solpos.apparent_zenith, label=label) + +ax.figure.legend(loc='upper left') + +# change coordinates to be like a compass +ax.set_theta_zero_location('N') +ax.set_theta_direction(-1) +ax.set_rmax(90) + +plt.show() + +#%% +# This is a polar plot of hourly solar zenith and azimuth. The figure-8 +# patterns are called `analemmas `_ and +# show how the sun's path slowly shifts over the course of the year . The +# colored lines show the single-day sun paths for the winter and summer +# solstices as well as the spring equinox. +# +# The soltice paths mark the boundary of the sky area that the sun traverses +# over a year. The diagram shows that there is no point in the +# year when is the sun directly overhead (zenith=0) -- note that this location +# is north of the Tropic of Cancer. +# +# Examining the sun path for the summer solstice in particular shows that +# the sun rises north of east, crosses into the southern sky around 10 AM for a +# few hours before crossing back into the northern sky around 3 PM and setting +# north of west. In contrast, the winter solstice sun path remains in the +# southern sky the entire day. Moreover, the diagram shows that the winter +# solstice is a shorter day than the summer soltice -- in December, the sun +# rises after 7 AM and sets before 6 PM, whereas in June the sun is up before +# 6 AM and sets after 7 PM. +# +# Another use of this diagram is to determine what times of year the sun is +# blocked by obstacles. For instance, for a mountain range on the western side +# of an array that extends 10 degrees above the horizon, the sun is blocked: +# +# - after about 6:30 PM on the summer solstice +# - after about 5:30 PM on the spring equinox +# - after about 4:30 PM on the winter solstice + +#%% +# PVSyst Plot +# ----------- +# +# PVSyst users will be more familiar with sunpath diagrams in Cartesian +# coordinates: + +from pvlib import solarposition +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt + +tz = 'Asia/Calcutta' +lat, lon = 28.6, 77.2 +times = pd.date_range('2019-01-01 00:00:00', '2020-01-01', closed='left', + freq='H', tz=tz) + +solpos = solarposition.get_solarposition(times, lat, lon) +# remove nighttime +solpos = solpos.loc[solpos['apparent_elevation'] > 0, :] + +fig, ax = plt.subplots() +points = ax.scatter(solpos.azimuth, solpos.apparent_elevation, s=2, + c=solpos.index.dayofyear, label=None) +fig.colorbar(points) + +for hour in np.unique(solpos.index.hour): + # choose label position by the largest elevation for each hour + subset = solpos.loc[solpos.index.hour == hour, :] + height = subset.apparent_elevation + pos = solpos.loc[height.idxmax(), :] + ax.text(pos['azimuth'], pos['apparent_elevation'], str(hour)) + +for date in pd.to_datetime(['2019-03-21', '2019-06-21', '2019-12-21']): + times = pd.date_range(date, date+pd.Timedelta('24h'), freq='5min', tz=tz) + solpos = solarposition.get_solarposition(times, lat, lon) + solpos = solpos.loc[solpos['apparent_elevation'] > 0, :] + label = date.strftime('%Y-%m-%d') + ax.plot(solpos.azimuth, solpos.apparent_elevation, label=label) + +ax.figure.legend(loc='upper left') +ax.set_xlabel('Solar Azimuth (degrees)') +ax.set_ylabel('Solar Elevation (degrees)') + +plt.show() diff --git a/docs/sphinx/source/conf.py b/docs/sphinx/source/conf.py index d4de03fa5f..524a77a048 100644 --- a/docs/sphinx/source/conf.py +++ b/docs/sphinx/source/conf.py @@ -18,6 +18,9 @@ # Mock modules so RTD works from unittest.mock import MagicMock +# for warning suppression +import warnings + class Mock(MagicMock): @classmethod @@ -55,7 +58,8 @@ def __getattr__(cls, name): 'sphinx.ext.napoleon', 'sphinx.ext.autosummary', 'IPython.sphinxext.ipython_directive', - 'IPython.sphinxext.ipython_console_highlighting' + 'IPython.sphinxext.ipython_console_highlighting', + 'sphinx_gallery.gen_gallery', ] napoleon_use_rtype = False # group rtype on same line together with return @@ -324,3 +328,16 @@ def setup(app): # suppress "WARNING: Footnote [1] is not referenced." messages # https://github.com/pvlib/pvlib-python/issues/837 suppress_warnings = ['ref.footnote'] + +# settings for sphinx-gallery +sphinx_gallery_conf = { + 'examples_dirs': ['../../examples'], # location of gallery scripts + 'gallery_dirs': ['auto_examples'], # location of generated output + # sphinx-gallery only shows plots from plot_*.py files by default: + # 'filename_pattern': '*.py', +} +# supress warnings in gallery output +# https://sphinx-gallery.github.io/stable/configuration.html +warnings.filterwarnings("ignore", category=UserWarning, + message='Matplotlib is currently using agg, which is a' + ' non-GUI backend, so cannot show the figure.') diff --git a/docs/sphinx/source/index.rst b/docs/sphinx/source/index.rst index eea01dd9df..06b7578a06 100644 --- a/docs/sphinx/source/index.rst +++ b/docs/sphinx/source/index.rst @@ -80,7 +80,8 @@ Contents :maxdepth: 1 package_overview - introexamples + introtutorial + auto_examples/index whatsnew installation contributing diff --git a/docs/sphinx/source/introexamples.rst b/docs/sphinx/source/introtutorial.rst similarity index 99% rename from docs/sphinx/source/introexamples.rst rename to docs/sphinx/source/introtutorial.rst index 184346e7ba..2ad8c967c4 100644 --- a/docs/sphinx/source/introexamples.rst +++ b/docs/sphinx/source/introtutorial.rst @@ -1,6 +1,6 @@ -.. _introexamples: +.. _introtutorial: -Intro Examples +Intro Tutorial ============== This page contains introductory examples of pvlib python usage. diff --git a/docs/sphinx/source/package_overview.rst b/docs/sphinx/source/package_overview.rst index a5cf1eb818..5bbbece5a5 100644 --- a/docs/sphinx/source/package_overview.rst +++ b/docs/sphinx/source/package_overview.rst @@ -12,7 +12,7 @@ interoperable, and benchmark implementations of PV system models. There are at least as many opinions about how to model PV systems as there are modelers of PV systems, so pvlib-python provides several modeling paradigms: functions, the Location/PVSystem classes, and the -ModelChain class. Read more about this in the :ref:`introexamples` +ModelChain class. Read more about this in the :ref:`introtutorial` section. diff --git a/docs/sphinx/source/whatsnew/v0.7.1.rst b/docs/sphinx/source/whatsnew/v0.7.1.rst index 53dace9cfe..f4d47a714a 100644 --- a/docs/sphinx/source/whatsnew/v0.7.1.rst +++ b/docs/sphinx/source/whatsnew/v0.7.1.rst @@ -27,6 +27,7 @@ Testing Documentation ~~~~~~~~~~~~~ +* Created an Example Gallery. (:pull:`846`) * Updated list of allowed years for `iotools.get_psm3`. Contributors diff --git a/setup.py b/setup.py index 35543bf7e0..b52a8ce731 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,8 @@ EXTRAS_REQUIRE = { 'optional': ['ephem', 'cython', 'netcdf4', 'nrel-pysam', 'numba', 'pvfactors', 'scipy', 'siphon', 'tables'], - 'doc': ['ipython', 'matplotlib', 'sphinx == 1.8.5', 'sphinx_rtd_theme'], + 'doc': ['ipython', 'matplotlib', 'sphinx == 1.8.5', 'sphinx_rtd_theme', + 'sphinx-gallery'], 'test': TESTS_REQUIRE } EXTRAS_REQUIRE['all'] = sorted(set(sum(EXTRAS_REQUIRE.values(), [])))