pvlib-python¶
PVLIB Python is a community supported tool that provides a set of functions and classes for simulating the performance of photovoltaic energy systems. PVLIB Python was originally ported from the PVLIB MATLAB toolbox developed at Sandia National Laboratories and it implements many of the models and methods developed at the Labs. More information on Sandia Labs PV performance modeling programs can be found at https://pvpmc.sandia.gov/. We collaborate with the PVLIB MATLAB project, but operate independently of it.
The source code for pvlib-python is hosted on github.
Please see the Installation page for installation help.
For examples of how to use pvlib-python, please see Package Overview and our Jupyter Notebook tutorials. The documentation assumes general familiarity with Python, NumPy, and Pandas. Google searches will yield many excellent tutorials for these packages.
The pvlib-python GitHub wiki has a Projects and publications that use pvlib python page for inspiration and listing of your application.
There is a variable naming convention to ensure consistency throughout the library.
Citing pvlib-python¶
Many of the contributors to pvlib-python work in institutions where citation metrics are used in performance or career evaluations. If you use pvlib-python in a published work, please cite the most appropriate of:
- J. S. Stein, “The photovoltaic performance modeling collaborative (PVPMC),” in Photovoltaic Specialists Conference, 2012.
- R.W. Andrews, J.S. Stein, C. Hansen, and D. Riley, “Introduction to the open source pvlib for python photovoltaic system modelling package,” in 40th IEEE Photovoltaic Specialist Conference, 2014. (paper)
- W.F. Holmgren, R.W. Andrews, A.T. Lorenzo, and J.S. Stein, “PVLIB Python 2015,” in 42nd Photovoltaic Specialists Conference, 2015. (paper and the notebook to reproduce the figures)
- J.S. Stein, W.F. Holmgren, J. Forbess, and C.W. Hansen, “PVLIB: Open Source Photovoltaic Performance Modeling Functions for Matlab and Python,” in 43rd Photovoltaic Specialists Conference, 2016.
- W.F. Holmgren and D.G. Groenendyk, “An Open Source Solar Power Forecasting Tool Using PVLIB-Python,” in 43rd Photovoltaic Specialists Conference, 2016.
Specific released versions of pvlib-python can be cited using their Zenodo DOI.
Contents¶
Package Overview¶
Introduction¶
The core mission of pvlib-python is to provide open, reliable, 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.
Modeling paradigms¶
The backbone of pvlib-python is well-tested procedural code that implements PV system models. pvlib-python also provides a collection of classes for users that prefer object-oriented programming. These classes can help users keep track of data in a more organized way, provide some “smart” functions with more flexible inputs, and simplify the modeling process for common situations. The classes do not add any algorithms beyond what’s available in the procedural code, and most of the object methods are simple wrappers around the corresponding procedural code.
Let’s use each of these pvlib modeling paradigms to calculate the yearly energy yield for a given hardware configuration at a handful of sites listed below.
In [1]: import pandas as pd
In [2]: import matplotlib.pyplot as plt
# seaborn makes the plots look nicer
In [3]: import seaborn as sns
In [4]: sns.set_color_codes()
In [5]: naive_times = pd.DatetimeIndex(start='2015', end='2016', freq='1h')
# very approximate
# latitude, longitude, name, altitude, timezone
In [6]: coordinates = [(30, -110, 'Tucson', 700, 'Etc/GMT+7'),
...: (35, -105, 'Albuquerque', 1500, 'Etc/GMT+7'),
...: (40, -120, 'San Francisco', 10, 'Etc/GMT+8'),
...: (50, 10, 'Berlin', 34, 'Etc/GMT-1')]
...:
In [7]: import pvlib
# get the module and inverter specifications from SAM
In [8]: sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
In [9]: sapm_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
In [10]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [11]: inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
# specify constant ambient air temp and wind for simplicity
In [12]: temp_air = 20
In [13]: wind_speed = 0
Procedural¶
The straightforward procedural code can be used for all modeling steps in pvlib-python.
The following code demonstrates how to use the procedural code to accomplish our system modeling goal:
In [14]: system = {'module': module, 'inverter': inverter,
....: 'surface_azimuth': 180}
....:
In [15]: energies = {}
In [16]: for latitude, longitude, name, altitude, timezone in coordinates:
....: times = naive_times.tz_localize(timezone)
....: system['surface_tilt'] = latitude
....: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
....: dni_extra = pvlib.irradiance.extraradiation(times)
....: dni_extra = pd.Series(dni_extra, index=times)
....: airmass = pvlib.atmosphere.relativeairmass(solpos['apparent_zenith'])
....: pressure = pvlib.atmosphere.alt2pres(altitude)
....: am_abs = pvlib.atmosphere.absoluteairmass(airmass, pressure)
....: tl = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
....: cs = pvlib.clearsky.ineichen(solpos['apparent_zenith'], am_abs, tl,
....: dni_extra=dni_extra, altitude=altitude)
....: aoi = pvlib.irradiance.aoi(system['surface_tilt'], system['surface_azimuth'],
....: solpos['apparent_zenith'], solpos['azimuth'])
....: total_irrad = pvlib.irradiance.total_irrad(system['surface_tilt'],
....: system['surface_azimuth'],
....: solpos['apparent_zenith'],
....: solpos['azimuth'],
....: cs['dni'], cs['ghi'], cs['dhi'],
....: dni_extra=dni_extra,
....: model='haydavies')
....: temps = pvlib.pvsystem.sapm_celltemp(total_irrad['poa_global'],
....: wind_speed, temp_air)
....: effective_irradiance = pvlib.pvsystem.sapm_effective_irradiance(
....: total_irrad['poa_direct'], total_irrad['poa_diffuse'],
....: am_abs, aoi, module)
....: dc = pvlib.pvsystem.sapm(effective_irradiance, temps['temp_cell'], module)
....: ac = pvlib.pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter)
....: annual_energy = ac.sum()
....: energies[name] = annual_energy
....:
In [17]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [18]: print(energies.round(0))
Albuquerque 512504.0
Berlin 399321.0
San Francisco 458076.0
Tucson 476874.0
dtype: float64
In [19]: energies.plot(kind='bar', rot=0)
Out[19]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc20ddf6a90>
In [20]: plt.ylabel('Yearly energy yield (W hr)')
Out[20]: <matplotlib.text.Text at 0x7fc20e4137d0>

pvlib-python provides a basic_chain()
function that implements much of the code above. Use this function with
a full understanding of what it is doing internally!
In [21]: from pvlib.modelchain import basic_chain
In [22]: energies = {}
In [23]: for latitude, longitude, name, altitude, timezone in coordinates:
....: dc, ac = basic_chain(naive_times.tz_localize(timezone),
....: latitude, longitude,
....: module, inverter,
....: altitude=altitude,
....: orientation_strategy='south_at_latitude_tilt')
....: annual_energy = ac.sum()
....: energies[name] = annual_energy
....:
In [24]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [25]: print(energies.round(0))
Albuquerque 512420.0
Berlin 399318.0
San Francisco 458075.0
Tucson 476834.0
dtype: float64
In [26]: energies.plot(kind='bar', rot=0)
Out[26]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc20e2bd650>
In [27]: plt.ylabel('Yearly energy yield (W hr)')
Out[27]: <matplotlib.text.Text at 0x7fc20e353590>

Object oriented (Location, PVSystem, ModelChain)¶
The first object oriented paradigm uses a model where a
PVSystem
object represents an assembled
collection of modules, inverters, etc., a
Location
object represents a particular
place on the planet, and a ModelChain
object describes the modeling chain used to calculate PV output at that
Location. This can be a useful paradigm if you prefer to think about the
PV system and its location as separate concepts or if you develop your
own ModelChain subclasses. It can also be helpful if you make extensive
use of Location-specific methods for other calculations.
The following code demonstrates how to use
Location
,
PVSystem
, and
ModelChain
objects to accomplish our system modeling goal:
In [28]: from pvlib.pvsystem import PVSystem
In [29]: from pvlib.location import Location
In [30]: from pvlib.modelchain import ModelChain
In [31]: system = PVSystem(module_parameters=module,
....: inverter_parameters=inverter)
....:
In [32]: energies = {}
In [33]: for latitude, longitude, name, altitude, timezone in coordinates:
....: location = Location(latitude, longitude, name=name, altitude=altitude,
....: tz=timezone)
....: mc = ModelChain(system, location,
....: orientation_strategy='south_at_latitude_tilt')
....: mc.run_model(naive_times.tz_localize(timezone))
....: annual_energy = mc.ac.sum()
....: energies[name] = annual_energy
....:
In [34]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [35]: print(energies.round(0))
Albuquerque 512420.0
Berlin 399318.0
San Francisco 458075.0
Tucson 476834.0
dtype: float64
In [36]: energies.plot(kind='bar', rot=0)
Out[36]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc20e03ef10>
In [37]: plt.ylabel('Yearly energy yield (W hr)')
Out[37]: <matplotlib.text.Text at 0x7fc234377f50>

See Will Holmgren’s ModelChain gist for more discussion about new features in ModelChain.
Object oriented (LocalizedPVSystem)¶
The second object oriented paradigm uses a model where a
LocalizedPVSystem
represents a
PV system at a particular place on the planet. This can be a useful
paradigm if you’re thinking about a power plant that already exists.
The following code demonstrates how to use a
LocalizedPVSystem
object to accomplish our modeling goal:
In [38]: from pvlib.pvsystem import LocalizedPVSystem
In [39]: energies = {}
In [40]: for latitude, longitude, name, altitude, timezone in coordinates:
....: localized_system = LocalizedPVSystem(module_parameters=module,
....: inverter_parameters=inverter,
....: surface_tilt=latitude,
....: surface_azimuth=180,
....: latitude=latitude,
....: longitude=longitude,
....: name=name,
....: altitude=altitude,
....: tz=timezone)
....: times = naive_times.tz_localize(timezone)
....: clearsky = localized_system.get_clearsky(times)
....: solar_position = localized_system.get_solarposition(times)
....: total_irrad = localized_system.get_irradiance(solar_position['apparent_zenith'],
....: solar_position['azimuth'],
....: clearsky['dni'],
....: clearsky['ghi'],
....: clearsky['dhi'])
....: temps = localized_system.sapm_celltemp(total_irrad['poa_global'],
....: wind_speed, temp_air)
....: aoi = localized_system.get_aoi(solar_position['apparent_zenith'],
....: solar_position['azimuth'])
....: airmass = localized_system.get_airmass(solar_position=solar_position)
....: effective_irradiance = localized_system.sapm_effective_irradiance(
....: total_irrad['poa_direct'], total_irrad['poa_diffuse'],
....: airmass['airmass_absolute'], aoi)
....: dc = localized_system.sapm(effective_irradiance, temps['temp_cell'])
....: ac = localized_system.snlinverter(dc['v_mp'], dc['p_mp'])
....: annual_energy = ac.sum()
....: energies[name] = annual_energy
....:
In [41]: energies = pd.Series(energies)
# based on the parameters specified above, these are in W*hrs
In [42]: print(energies.round(0))
Albuquerque 512420.0
Berlin 399318.0
San Francisco 458075.0
Tucson 476834.0
dtype: float64
In [43]: energies.plot(kind='bar', rot=0)
Out[43]: <matplotlib.axes._subplots.AxesSubplot at 0x7fc20e0df6d0>
In [44]: plt.ylabel('Yearly energy yield (W hr)')
Out[44]: <matplotlib.text.Text at 0x7fc233efde90>

User extensions¶
There are many other ways to organize PV modeling code. We encourage you to build on these paradigms and to share your experiences with the pvlib community via issues and pull requests.
Getting support¶
The best way to get support is to make an issue on our GitHub issues page .
How do I contribute?¶
We’re so glad you asked! Please see our wiki for information and instructions on how to contribute. We really appreciate it!
Credits¶
The pvlib-python community thanks Sandia National Lab for developing PVLIB Matlab and for supporting Rob Andrews of Calama Consulting to port the library to Python. Will Holmgren thanks the DOE EERE Postdoctoral Fellowship program for support. The pvlib-python maintainers thank all of pvlib’s contributors of issues and especially pull requests. The pvlib-python community thanks all of the maintainers and contributors to the PyData stack.
What’s New¶
These are new features and improvements of note in each release.
v0.4.2 ()¶
This is a minor release from 0.4.0. ....
Bug fixes¶
API Changes¶
- The run_model method of the ModelChain will use the weather parameter of all weather data instead of splitting it to irradiation and weather. The irradiation parameter still works but will be removed soon.(GH239)
- delta_t kwarg is now 67.0 instead of None. IMPORTANT: Setting delta_t as None will break the code for Numba calculation. This will be fixed in a future version. (GH165)
Enhancements¶
- Adding a complete_irradiance method to the ModelChain to make it possible to calculate missing irradiation data from the existing columns [beta] (GH239)
- Added calculate_deltat method to the spa module to calculate the time difference between terrestrial time and UT1. Specifying a scalar is sufficient for most calculations. (GH165)
Code Contributors¶
- Uwe Krien
- Will Holmgren
- Volker Beutner
v0.4.1 (October 5, 2016)¶
This is a minor release from 0.4.0. We recommend that all users upgrade to this version, especially if they want to use the latest versions of pandas.
Bug fixes¶
Documentation¶
Code Contributors¶
- Mark Mikofski
- Johannes Dollinger
- Will Holmgren
v0.4.0 (July 28, 2016)¶
This is a major release from 0.3.3. We recommend that all users upgrade to this version after reviewing the API Changes. Please see the Bug Fixes for changes that will result in slightly different modeling results.
API Changes¶
- Remove unneeded module argument from singlediode function. (GH200)
- In
pvlib.irradiance.perez
, renamed argumentmodelt
tomodel
. (GH196) - Functions in the irradiance module now work with scalar inputs in addition to arrays and Series. Furthermore, these functions no longer promote scalar or array input to Series output. Also applies to atmosphere.relativeairmass. (GH201, GH214)
- Reorder the ashraeiam, physicaliam, and snlinverter arguments to put the most variable arguments first. Adds default arguments for the IAM functions. (GH197)
- The irradiance.extraradiation function input/output type consistency across different methods has been dramatically improved. (GH217, GH219)
- Updated to pvsystem.sapm to be consistent with the PVLIB MATLAB API. pvsystem.sapm now takes an effective irradiance argument instead of POA irradiances, airmass, and AOI. Implements closely related sapm_spectral_loss, sapm_aoi_loss, and sapm_effective_irradiance functions, as well as PVSystem methods. The sapm_aoi_loss function includes an optional argument to apply an upper limit to the output (output can be ~1% larger than 1 for AOI of ~30 degrees). (GH198, GH205, GH218)
- The pvsystem.retrieve_sam keyword argument
samfile
has been replaced withpath
. A selection dialog window is now activated by not supplying any arguments to the function. The API for typical usage remains unchanged, however, the data will be loaded from a local file rather than the SAM website. (GH52)
Enhancements¶
- Adds the First Solar spectral correction model. (GH115)
- Adds the Gueymard 1994 integrated precipitable water model. (GH115)
- Adds the PVWatts DC, AC, and system losses model. (GH195)
- Improve PEP8 conformity in irradiance module. (GH214)
- irradiance.disc is up to 10x faster. (GH214)
- Add solarposition.nrel_earthsun_distance function and option to calculate extraterrestrial radiation using the NREL solar position algorithm. (GH211, GH215)
- pvsystem.singlediode can now calculate IV curves if a user supplies an ivcurve_pnts keyword argument. (GH83)
- Includes SAM data files in the distribution. (GH52)
- ModelChain now implements SAPM, PVWatts, Single Diode and user-defined modeling options. See Will Holmgren’s ModelChain refactor gist for more discussion about new features in ModelChain. (GH143, GH194)
- Added
forecast.py
module for solar power forecasts. (GH86, GH124, GH180)
Bug fixes¶
- Fixed an error in pvsystem.singlediode’s i_mp, v_mp, and p_mp calculations when using array or Series input. The function wrongly returned solutions when any single point is within the error tolerance, rather than requiring that the solution for all points be within the error tolerance. Results in test scenarios changed by 1-10%. (GH221)
- Fixed a numerical overflow error in pvsystem.singlediode’s v_oc determination for some combinations of parameters. (GH225)
- dirint function yielded the wrong results for non-sea-level pressures. Fixed. (GH212)
- Fixed a bug in the day angle calculation used by the ‘spencer’ and ‘asce’ extraterrestrial radiation options. Most modeling results will be changed by less than 1 part in 1000. (GH211)
- irradiance.extraradiation now raises a ValueError for invalid method input. It previously failed silently. (GH215)
Documentation¶
Other¶
- pvlib-python is now available on the conda-forge channel: conda install pvlib-python -c conda-forge (GH154)
- Switch to the py.test testing framework. (GH204)
- Reconfigure Appveyor CI builds and resolve an issue in which the command line length was too long. (GH207)
- Manually build numpy and pandas for the min requirements test. This is needed to avoid Continuum’s bad practice of bundling scipy with pandas. (GH214)
Requirements¶
- pvlib now requires pandas >= 0.14.0 and numpy >= 1.9.0, both released in 2014. Most of pvlib will work with lesser versions. (GH214)
Code Contributors¶
- Will Holmgren
- Jonathan Chambers
- Mitchell Lee
- Derek Groenendyk
v0.3.3 (June 15, 2016)¶
This is a minor release from 0.3.2. We recommend that all users upgrade to this version.
API Changes¶
- Renamed
series_modules
tomodules_per_string
andparallel_modules
tostrings_per_inverter
. (GH176) - Changed two of the TMY3 data reader fields for consistency with the rest of the fields and with PVLIB MATLAB. Changed ‘PresWth source’ to ‘PresWthSource’, and ‘PresWth uncert’ to ‘PresWthUncertainty’. (GH193)
Enhancements¶
- Adds the Erbs model. (GH2)
- Adds the
scale_voltage_current_power
function andPVSystem
method to support simple array modeling. (GH159) - Adds support for
SingleAxisTracker
objects inModelChain
. (GH169) - Add
__repr__
method to PVSystem, LocalizedPVSystem, ModelChain, SingleAxisTracker, Location. (GH142) - Add
v_from_i
function for solving the single diode model. (GH190) - Improve speed of
singlediode
function by usingv_from_i
to determinev_oc
. Speed is ~2x faster. (GH190) - Adds the Simplified Solis clear sky model. (GH148)
Bug fixes¶
- Fix another bug with the Appveyor continuous integration builds. (GH170)
- Add classifiers to setup.py. (GH181)
- Fix snlinverter and singlediode documentation. They incorrectly said that inverter/module must be a DataFrame, when in reality they can be any dict-like object. (GH157)
- Fix numpy 1.11 deprecation warnings caused by some functions using non-integer indices.
- Propagate airmass data through ModelChain’s get_irradiance call so that the perez model can use it, if necessary. (GH172)
- Fix problem in which the perez function dropped nighttime values. Nighttime values are now set to 0. (GH191)
Documentation¶
- Localize datetime indices in package overview examples. (GH156)
- Clarify that
ModelChain
andbasic_chain
currently only supports SAPM. (GH177) - Fix version number in 0.3.2 whatsnew file.
- Shorten README.md file and move information to official documentation. (GH182)
- Change authors to PVLIB Python Developers and clean up setup.py. (GH184)
- Document the PresWth, PresWthSource, and PresWthUncertainty fields in the TMY3 data reader. (GH193)
Contributors¶
- Will Holmgren
- Mark Mikofski
- Johannes Oos
- Tony Lorenzo
v0.3.2 (May 3, 2016)¶
This is a minor release from 0.3.1. We recommend that all users upgrade to this version.
Contributors¶
- Will Holmgren
v0.3.1 (April 19, 2016)¶
This is a minor release from 0.3.0. We recommend that all users upgrade to this version.
Enhancements¶
- Added versioneer to keep track of version changes instead of manually updating pvlib/version.py. This will aid developers because the version string includes the specific git commit of the library code currently imported. (issue:150)
Bug fixes¶
- Fixes night tare issue in snlinverter. When the DC input power (p_dc) to an inverter is below the inversion startup power (Ps0), the model should set the AC output (ac_power) to the night tare value (Pnt). The night tare power indicates the power consumed by the inverter to sense PV array voltage. The model was erroneously comparing Ps0 with the AC output power (ac_power), rather than the DC input power (p_dc). (GH140)
- Fixed the azimuth calculation of rotated PV panel in function pvlib.tracking.singleaxis(...) so that the results are consistent with PVsyst. (GH144)
Contributors¶
- ejmiller2
- Yudong Ma
- Tony Lorenzo
- Will Holmgren
v0.3.0 (March 21, 2016)¶
This is a major release from 0.2.2. It will almost certainly break your code, but it’s worth it! We recommend that all users upgrade to this version after testing their code for compatibility and updating as necessary.
API changes¶
- The
location
argument insolarposition.get_solarposition
andclearsky.ineichen
has been replaced withlatitude
,longitude
,altitude
, andtz
as appropriate. This separates the object-oriented API from the procedural API. (GH17) Location
classes gain theget_solarposition
,get_clearsky
, andget_airmass
functions.- Adds
ModelChain
,PVSystem
,LocalizedPVSystem
,SingleAxisTracker
, andLocalizedSingleAxisTracker
classes. (GH17) Location
objects can be created from TMY2/TMY3 metadata using thefrom_tmy
constructor.- Change default
Location
timezone to'UTC'
. - The solar position calculators now assume UTC time if the input time is not localized. The calculators previously tried to infer the timezone from the now defunct location argument.
pvsystem.sapm_celltemp
argument names now follow the variable conventions.irradiance.total_irrad
now follows the variable conventions. (GH105)atmosphere.relativeairmass
now raises a ValueError instead of assuming'kastenyoung1989'
if an invalid model is supplied. (GH119)
Enhancements¶
Added new sections to the documentation:
- Package Overview (GH93)
- Installation (GH135)
- Contributing (GH46)
- Time and time zones (GH47)
- Variables and Symbols (GH102)
- classes (GH93)
Adds support for Appveyor, a Windows continuous integration service. (GH111)
The readthedocs documentation build now uses conda packages instead of mock packages. This enables code to be run and figures to be generated during the documentation builds. (GH104)
Reconfigures TravisCI builds and adds e.g.
has_numba
decorators to the test suite. The result is that the TravisCI test suite runs almost 10x faster and users do not have to install all optional dependencies to run the test suite. (GH109)Adds more unit tests that test that the return values are actually correct.
Add
atmosphere.APPARENT_ZENITH_MODELS
andatmosphere.TRUE_ZENITH_MODELS
to enable code that can automatically determine which type of zenith data to use e.g.Location.get_airmass
.Modify
sapm
documentation to clarify that it does not work with the CEC database. (GH122)Adds citation information to the documentation. (GH73)
Updates the Comparison with PVLIB MATLAB documentation. (GH116)
Bug fixes¶
- Fixed the metadata key specification in documentation of the
readtmy2
function. - Fixes the import of tkinter on Python 3 (GH112)
- Add a decorator to skip
test_calcparams_desoto
on pandas 0.18.0. (GH130) - Fixes
i_from_v
documentation. (GH126) - Fixes two minor sphinx documentation errors: a too short heading underline in whatsnew/v0.2.2.txt and a table format in pvsystem. (GH123)
Contributors¶
- Will Holmgren
- pyElena21
- DaCoEx
- Uwe Krien
Will Holmgren, Jessica Forbess, bmu, Cliff Hansen, Tony Lorenzo, Uwe Krien, and bt- contributed to the object model discussion.
v0.2.2 (November 13, 2015)¶
This is a minor release from 0.2.1. We recommend that all users upgrade to this version.
Enhancements¶
Bug fixes¶
irradiance.total_irrad
had a typo that required the Klucher model to be accessed with'klutcher'
. Both spellings will work for the remaining 0.2.* versions of pvlib, but the misspelled method will be removed in 0.3. (GH97)- Fixes an import and KeyError in the IPython notebook tutorials (GH94).
- Uses the
logging
module properly by replacingformat
calls withargs
. This results in a 5x speed increase fortracking.singleaxis
(GH89). - Adds a link to the 2015 PVSC paper (GH81)
Contributors¶
- Will Holmgren
- jetheurer
- dacoex
v0.2.1 (July 16, 2015)¶
This is a minor release from 0.2. It includes a large number of bug fixes for the IPython notebook tutorials. We recommend that all users upgrade to this version.
Bug fixes¶
Contributors¶
- Will Holmgren
- Jessica Forbess
v0.2.0 (July 6, 2015)¶
This is a major release from 0.1 and includes a large number of API changes, several new features and enhancements along with a number of bug fixes. We recommend that all users upgrade to this version.
Due to the large number of API changes, you will probably need to update your code.
API changes¶
- Change variable names to conform with new Variables and style rules wiki. This impacts many function declarations and return values. Your existing code probably will not work! (GH37, GH54).
- Move
dirint
anddisc
algorithms fromclearsky.py
toirradiance.py
(GH42) - Mark some
pvsystem.py
methods as private (GH20) - Make output of
pvsystem.sapm_celltemp
a DataFrame (GH54)
Enhancements¶
Bug fixes¶
- fix local build of the documentation (GH49, GH56)
- The release date of 0.1 was fixed in the documentation (see v0.1.0 (April 20, 2015))
- fix casting of DateTimeIndex to int64 epoch timestamp on machines with 32 bit python int (GH63)
- fixed some docstrings with failing doctests (GH62)
Contributors¶
- Will Holmgren
- Rob Andrews
- bmu
- Tony Lorenzo
v0.1.0 (April 20, 2015)¶
This is the first official release of the pvlib-python project. As such, a “What’s new” document is a little hard to write. There will be significant overlap with the to-be-written document that describes the differences between pvlib-python and PVLIB_Matlab.
API changes¶
- Remove
pvl_
from module names. - Consolidation of similar modules. For example, functions from
pvl_clearsky_ineichen.py
andpvl_clearsky_haurwitz.py
have been consolidated intoclearsky.py
. - Return one DataFrame instead of a tuple of DataFrames.
- Change function and module names so that they do not conflict.
New features¶
- Library is Python 3.3 and 3.4 compatible
- Add What’s New section to docs (GH10)
- Add PyEphem option to solar position calculations.
- Add a Python translation of NREL’s SPA algorithm.
irradiance.py
has more AOI, projection, and irradiance sum and calculation functions- TMY data import has a
coerce_year
option - TMY data can be loaded from a url (GH5)
- Locations are now
pvlib.location.Location
objects, not “structs”. - Specify time zones using a string from the standard IANA Time Zone Database naming conventions or using a pytz.timezone instead of an integer GMT offset. We may add dateutils support in the future.
clearsky.ineichen
supports interpolating monthly Linke Turbidities to daily resolution.
Other changes¶
- Removed
Vars=Locals(); Expect...; var=pvl\_tools.Parse(Vars,Expect);
pattern. Very few tests of input validitity remain. Garbage in, garbage ornan
out. - Removing unnecssary and sometimes undesired behavior such as setting maximum zenith=90 or airmass=0. Instead, we make extensive use of
nan
values. - Adding logging calls, removing print calls.
- Improved PEP8 compliance.
- Added
/pvlib/data
for lookup tables, test, and tutorial data. - Limited the scope of
clearsky.py
‘sscipy
dependency.clearsky.ineichen
will work withoutscipy
so long as the Linke Turbidity is supplied as a keyword argument. (GH13) - Removed NREL’s SPA code to comply with their license (GH9).
- Revised the globalinplane function and added a test_globalinplane (GH21, GH33).
Documentation¶
Testing¶
- Tests are cleaner and more thorough. They are still nowhere near complete.
- Using Coveralls to measure test coverage.
- Using TravisCI for automated testing.
- Using
nosetests
for more concise test code.
Bug fixes¶
- Fixed DISC algorithm bugs concerning
modifying input zenith Series (GH24),
the
Kt
conditional evaluation (GH6), and ignoring the input pressure (GH25). - Many more bug fixes were made, but you’ll have to look at the detailed commit history.
- Fixed inconsistent azimuth angle in the ephemeris function (GH40)
Contributors¶
This list includes all (I hope) contributors to pvlib/pvlib-python, Sandia-Labs/PVLIB_Python, and UARENForecasting/PVLIB_Python.
- Rob Andrews
- Will Holmgren
- bmu
- Tony Lorenzo
- jforbess
- Jorissup
- dacoex
- alexisph
- Uwe Krien
Installation¶
Installing pvlib-python ranges from trivial to difficult depending on your python experience, how you want to use pvlib, and your system configuration.
Do you already have Python and the NumPy and Pandas libraries?
If the answer to this is No, follow the If you don’t have Python instructions to obtain the Anaconda Python distribution before proceeding.
Do you want to use the pvlib-python as-is, or do you want to be able to edit the source code?
If you want to use pvlib-python as-is, follow the simple Install standard release instructions.
If you want to be able to edit the source code, follow the Install as an editable library instructions.
Installing pvlib-python is similar to installing most scientific python packages, so see the References section for further help.
If you don’t have Python¶
There are many ways to install Python on your system, but the Anaconda Scientific Python distribution provides by far the easiest way for new users to get started. Anaconda includes all of the popular libraries that you’ll need for pvlib, including Pandas, NumPy, and SciPy.
Anaconda installs cleanly into a single directory, does not require Administrator or root privileges, does not affect other Python installs on your system, or interfere with OSX Frameworks. – The Anaconda Documentation
- Install the full Anaconda Scientific Python distribution available at Continuum.io
See the Anaconda FAQ for more information.
You can now install pvlib-python by one of the methods below.
Install standard release¶
To install the most recent stable release of pvlib-python in a non-editable way, use conda (recommended if you use the Anaconda Python distribution) or pip (works with any Python distribution):
conda install -c pvlib pvlib
pip install pvlib
If your system complains that you don’t have access privileges or asks for a password then you’re probably trying to install pvlib into your system’s Python distribution. This is usually a bad idea and you should follow the If you don’t have Python instructions before installing pvlib.
You may still want to download the Python source code so that you can easily get all of the Jupyter Notebook tutorials. Either clone the git repository or go to the Releases page to download the zip file of the most recent release. You can also use the nbviewer website to choose a tutorial to experiment with. Go to our nbviewer tutorial page.
Install as an editable library¶
Installing pvlib-python as an editable library involves 3 steps:
None of these steps are particularly challenging, but they become more difficult when combined. With a little bit of practice the process will be fast and easy. Experienced users can easily execute these steps in less than a minute. You’ll get there.
Obtain the source code¶
We will briefly describe how to obtain the pvlib-python source code using the git/GitHub version control system. We strongly encourage users to learn how to use these powerful tools (see the References!), but we also recognize that they can be a substantial roadblock to getting started with pvlib-python. Therefore, you should know that you can download a zip file of the most recent development version of the source code by clicking on the Download Zip button on the right side of our GitHub page or download a zip file of any stable release from our Releases page.
Follow these steps to obtain the library using git/GitHub:
- Download the GitHub Desktop application.
- Fork the pvlib-python project by clicking on the “Fork” button on the upper right corner of the pvlib-python GitHub page.
- Clone your fork to your computer using the GitHub Desktop application by clicking on the Clone to Desktop button on your fork’s homepage. This button is circled in the image below. Remember the system path that you clone the library to.

Please see GitHub’s Forking Projects, Fork A Repo, and the git-scm for more details.
Set up a virtual environment¶
We strongly recommend working in a virtual environment if you’re going to use an editable version of the library. You can skip this step if:
- You already have Anaconda or another scientific Python distribution
- You don’t mind polluting your Python installation with your development version of pvlib.
- You don’t want to work with multiple versions of pvlib.
There are many ways to use virtual environments in Python, but Anaconda again provides the easiest solution. These are often referred to as conda environments, but they’re the same for our purposes.
- Create a new conda environment for pvlib and pre-install
the required packages into the environment:
conda create --name pvlibdev python pandas scipy
- Activate the new conda environment:
source activate pvlibdev
- Install additional packages into your development environment:
conda install jupyter ipython matplotlib seaborn pytest nose flake8
The conda documentation has more information
on how to use conda virtual environments. You can also add -h
to most
pip and conda commands to get help (e.g. conda -h
or conda env -h
)
Install the source code¶
Good news – installing the source code is the easiest part! With your conda/virtual environment still active...
- Install pvlib-python in “development mode” by running
pip install -e /path/to/your/pvlib-python
. You remember this path from the clone step, right? It’s probably something likeC:\Users\%USER%\Documents\GitHub\pvlib-python
(Windows) or/Users/%USER%/Documents/pvlib-python
(Mac). - Test your installation by running
python -c 'import pvlib'
. You’re good to go if it returns without an exception.
The version of pvlib-python that is on that path is now available as an installed package inside your conda/virtual environment.
Any changes that you make to this pvlib-python will be available inside
your environment. If you run a git checkout, branch, or pull command the
result will be applied to your pvlib-python installation. This
is great for development. Note, however, that you will need to use
Python’s reload
function (python 2, python 3)
if you make changes to pvlib during an interactive Python
session (including a Jupyter notebook). Restarting the Python
interpreter will also work.
Remember to source activate pvlibdev
(or whatever you named your
environment) when you start a new shell or terminal.
NREL SPA algorithm¶
pvlib-python is distributed with several validated, high-precision, and high-performance solar position calculators. We strongly recommend using the built-in solar position calculators.
pvlib-python also includes unsupported wrappers for the official NREL SPA algorithm. NREL’s license does not allow redistribution of the source code, so you must jump through some hoops to use it with pvlib. You will need a C compiler to use this code.
To install the NREL SPA algorithm for use with pvlib:
- Download the pvlib repository (as described in Obtain the source code)
- Download the SPA files from NREL
- Copy the SPA files into
pvlib-python/pvlib/spa_c_files
- From the
pvlib-python
directory, runpip uninstall pvlib
followed bypip install .
Compatibility¶
pvlib-python is compatible with Python versions 2.7, 3.4, 3.5 and Pandas versions 0.13.1 or newer.
There have been several problems with Continuum’s Anaconda packages that have impacted pvlib users. The current problems that we’re aware of are listed below:
- For Windows + Python 2.7 users: Continuum’s Python 2.7 SciPy 0.16.1, 0.17.0, 0.17.1 packages are not compiled properly and will crash your Python interpreter if you use our Linke turbidity lookup function. See Anaconda issue 650 for more.
Note that our Numba-accelerated solar position algorithms have more specific version requirements that will be resolved by the Numba installer.
References¶
Here are a few recommended references for installing Python packages:
- The Pandas installation page
- python4astronomers Modules, Packages, and all that
- Python Packaging User Guide
- Conda User Guide
Here are a few recommended references for git and GitHub:
- The git documentation: detailed explanations, videos, more links, and cheat sheets. Go here first!
- Forking Projects
- Fork A Repo
- Cloning a repository
- Aha! Moments When Learning Git
Contributing¶
Encouraging more people to help develop pvlib-python is essential to our success. Therefore, we want to make it easy and rewarding for you to contribute.
Easy ways to contribute¶
Here are a few ideas for you can contribute, even if you are new to pvlib-python, git, or Python:
- Make GitHub issues and contribute to the conversation about how to resolve them.
- Read issues and pull requests that other people created and contribute to the conversation about how to resolve them.
- Improve the documentation and the unit tests.
- Improve the IPython/Jupyter Notebook tutorials or write new ones that demonstrate how to use pvlib-python in your area of expertise.
- If you have MATLAB experience, you can help us keep pvlib-python up to date with PVLIB_MATLAB or help us develop common unit tests. For more, see Issue #2 and Issue #3.
- Tell your friends and colleagues about pvlib-python.
- Add your project to our Projects and publications that use pvlib-python wiki.
How to contribute new code¶
Contributors to pvlib-python use GitHub’s pull requests to add/modify its source code. The GitHub pull request process can be intimidating for new users, but you’ll find that it becomes straightforward once you use it a few times. Please let us know if you get stuck at any point in the process. Here’s an outline of the process:
- Create a GitHub issue and get initial feedback from users and maintainers. If the issue is a bug report, please include the code needed to reproduce the problem.
- Obtain the latest version of pvlib-python: Fork the pvlib-python
project to your GitHub account,
git clone
your fork to your computer. - Make some or all of your changes/additions and
git commit
them to your local repository. - Share your changes with us via a pull request:
git push
your local changes to your GitHub fork, then go to GitHub make a pull request.
The Pandas project maintains an excellent contributing page that goes into detail on each of these steps. Also see GitHub’s Set Up Git and Using Pull Requests.
Note that you do not need to make all of your changes before creating a pull request. Your pull requests will automatically be updated when you commit new changes and push them to GitHub. This gives everybody an easy way to comment on the code and can make the process more efficient.
We strongly recommend using virtual environments for development. Virtual environments make it trivial to switch between different versions of software. This astropy guide is a good reference for virtual environments. If this is your first pull request, don’t worry about using a virtual environment.
You must include documentation and unit tests for any new or improved code. We can provide help and advice on this after you start the pull request.
The maintainers will follow same procedures, rather than making direct commits to the main repo. Exceptions may be made for extremely minor changes, such as fixing documentation typos.
Testing¶
pvlib’s unit tests can easily be run by executing py.test
on the
pvlib directory:
py.test pvlib
or, for a single module:
py.test pvlib/test/test_clearsky.py
While copy/paste coding should generally be avoided, it’s a great way to learn how to write unit tests!
Unit test code should be placed in the corresponding test module in the pvlib/test directory.
Developers must include comprehensive tests for any additions or modifications to pvlib.
This documentation¶
If this documentation is unclear, help us improve it! Consider looking at the pandas documentation for inspiration.
Time and time zones¶
Dealing with time and time zones can be a frustrating experience in any
programming language and for any application. pvlib-python relies on
pandas
and pytz to handle
time and time zones. Therefore, the vast majority of the information in
this document applies to any time series analysis using pandas and is
not specific to pvlib-python.
General functionality¶
pvlib makes extensive use of pandas due to its excellent time series
functionality. Take the time to become familiar with pandas’ Time
Series / Date functionality page.
It is also worthwhile to become familiar with pure Python’s
datetime
module, although we usually recommend
using the corresponding pandas functionality where possible.
First, we’ll import the libraries that we’ll use to explore the basic time and time zone functionality in python and pvlib.
In [1]: import datetime
In [2]: import pandas as pd
In [3]: import pytz
Finding a time zone¶
pytz is based on the Olson time zone database. You can obtain a list of
all valid time zone strings with pytz.all_timezones
. It’s a long
list, so we only print every 20th time zone.
In [4]: len(pytz.all_timezones)
Out[4]: 589
In [5]: pytz.all_timezones[::20]
Out[5]:
['Africa/Abidjan',
'Africa/Douala',
'Africa/Mbabane',
'America/Argentina/Catamarca',
'America/Belize',
'America/Curacao',
'America/Guatemala',
'America/Kentucky/Louisville',
'America/Mexico_City',
'America/Port-au-Prince',
'America/St_Barthelemy',
'Antarctica/Davis',
'Asia/Baghdad',
'Asia/Gaza',
'Asia/Kuala_Lumpur',
'Asia/Riyadh',
'Asia/Urumqi',
'Australia/ACT',
'Australia/Victoria',
'Cuba',
'Etc/GMT-0',
'Etc/Zulu',
'Europe/Istanbul',
'Europe/Riga',
'Europe/Zagreb',
'Indian/Maldives',
'PST8PDT',
'Pacific/Kwajalein',
'Pacific/Truk',
'US/Mountain']
Wikipedia’s List of tz database time zones is also good reference.
The pytz.country_timezones
function is useful, too.
In [6]: pytz.country_timezones('US')
Out[6]:
[u'America/New_York',
u'America/Detroit',
u'America/Kentucky/Louisville',
u'America/Kentucky/Monticello',
u'America/Indiana/Indianapolis',
u'America/Indiana/Vincennes',
u'America/Indiana/Winamac',
u'America/Indiana/Marengo',
u'America/Indiana/Petersburg',
u'America/Indiana/Vevay',
u'America/Chicago',
u'America/Indiana/Tell_City',
u'America/Indiana/Knox',
u'America/Menominee',
u'America/North_Dakota/Center',
u'America/North_Dakota/New_Salem',
u'America/North_Dakota/Beulah',
u'America/Denver',
u'America/Boise',
u'America/Phoenix',
u'America/Los_Angeles',
u'America/Anchorage',
u'America/Juneau',
u'America/Sitka',
u'America/Metlakatla',
u'America/Yakutat',
u'America/Nome',
u'America/Adak',
u'Pacific/Honolulu']
And don’t forget about Python’s filter()
function.
In [7]: list(filter(lambda x: 'GMT' in x, pytz.all_timezones))
Out[7]:
['Etc/GMT',
'Etc/GMT+0',
'Etc/GMT+1',
'Etc/GMT+10',
'Etc/GMT+11',
'Etc/GMT+12',
'Etc/GMT+2',
'Etc/GMT+3',
'Etc/GMT+4',
'Etc/GMT+5',
'Etc/GMT+6',
'Etc/GMT+7',
'Etc/GMT+8',
'Etc/GMT+9',
'Etc/GMT-0',
'Etc/GMT-1',
'Etc/GMT-10',
'Etc/GMT-11',
'Etc/GMT-12',
'Etc/GMT-13',
'Etc/GMT-14',
'Etc/GMT-2',
'Etc/GMT-3',
'Etc/GMT-4',
'Etc/GMT-5',
'Etc/GMT-6',
'Etc/GMT-7',
'Etc/GMT-8',
'Etc/GMT-9',
'Etc/GMT0',
'GMT',
'GMT+0',
'GMT-0',
'GMT0']
Note that while pytz has 'EST'
and 'MST'
, it does not have
'PST'
. Use 'Etc/GMT+8'
instead, or see Fixed offsets.
Timestamps¶
pandas.Timestamp
and pandas.DatetimeIndex
can be created in many ways. Here we focus on the time zone issues
surrounding them; see the pandas documentation for more information.
First, create a time zone naive pandas.Timestamp.
In [8]: pd.Timestamp('2015-1-1 00:00')
Out[8]: Timestamp('2015-01-01 00:00:00')
You can specify the time zone using the tz
keyword argument or the
tz_localize
method of Timestamp and DatetimeIndex objects.
In [9]: pd.Timestamp('2015-1-1 00:00', tz='America/Denver')
Out[9]: Timestamp('2015-01-01 00:00:00-0700', tz='America/Denver')
In [10]: pd.Timestamp('2015-1-1 00:00').tz_localize('America/Denver')
Out[10]: Timestamp('2015-01-01 00:00:00-0700', tz='America/Denver')
Localized Timestamps can be converted from one time zone to another.
In [11]: midnight_mst = pd.Timestamp('2015-1-1 00:00', tz='America/Denver')
In [12]: corresponding_utc = midnight_mst.tz_convert('UTC') # returns a new Timestamp
In [13]: corresponding_utc
Out[13]: Timestamp('2015-01-01 07:00:00+0000', tz='UTC')
It does not make sense to convert a time stamp that has not been localized, and pandas will raise an exception if you try to do so.
In [14]: midnight = pd.Timestamp('2015-1-1 00:00')
In [15]: midnight.tz_convert('UTC')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-d106ce42c2eb> in <module>()
----> 1 midnight.tz_convert('UTC')
pandas/tslib.pyx in pandas.tslib.Timestamp.tz_convert (pandas/tslib.c:13312)()
TypeError: Cannot convert tz-naive Timestamp, use tz_localize to localize
The difference between tz_localize
and tz_convert
is a common
source of confusion for new users. Just remember: localize first,
convert later.
Daylight savings time¶
Some time zones are aware of daylight savings time and some are not. For example the winter time results are the same for US/Mountain and MST, but the summer time results are not.
Note the UTC offset in winter...
In [16]: pd.Timestamp('2015-1-1 00:00').tz_localize('US/Mountain')
Out[16]: Timestamp('2015-01-01 00:00:00-0700', tz='US/Mountain')
In [17]: pd.Timestamp('2015-1-1 00:00').tz_localize('Etc/GMT+7')
Out[17]: Timestamp('2015-01-01 00:00:00-0700', tz='Etc/GMT+7')
vs. the UTC offset in summer...
In [18]: pd.Timestamp('2015-6-1 00:00').tz_localize('US/Mountain')
Out[18]: Timestamp('2015-06-01 00:00:00-0600', tz='US/Mountain')
In [19]: pd.Timestamp('2015-6-1 00:00').tz_localize('Etc/GMT+7')
Out[19]: Timestamp('2015-06-01 00:00:00-0700', tz='Etc/GMT+7')
pandas and pytz make this time zone handling possible because pandas stores all times as integer nanoseconds since January 1, 1970. Here is the pandas time representation of the integers 1 and 1e9.
In [20]: pd.Timestamp(1)
Out[20]: Timestamp('1970-01-01 00:00:00.000000001')
In [21]: pd.Timestamp(1e9)
Out[21]: Timestamp('1970-01-01 00:00:01')
So if we specify times consistent with the specified time zone, pandas will use the same integer to represent them.
# US/Mountain
In [22]: pd.Timestamp('2015-6-1 01:00', tz='US/Mountain').value
Out[22]: 1433142000000000000
# MST
In [23]: pd.Timestamp('2015-6-1 00:00', tz='Etc/GMT+7').value
Out[23]: 1433142000000000000
# Europe/Berlin
In [24]: pd.Timestamp('2015-6-1 09:00', tz='Europe/Berlin').value
Out[24]: 1433142000000000000
# UTC
In [25]: pd.Timestamp('2015-6-1 07:00', tz='UTC').value
Out[25]: 1433142000000000000
# UTC
In [26]: pd.Timestamp('2015-6-1 07:00').value
Out[26]: 1433142000000000000
It’s ultimately these integers that are used when calculating quantities in pvlib such as solar position.
As stated above, pandas will assume UTC if you do not specify a time zone. This is dangerous, and we recommend using localized timeseries, even if it is UTC.
Fixed offsets¶
The 'Etc/GMT*'
time zones mentioned above provide fixed offset
specifications, but watch out for the counter-intuitive sign convention.
In [27]: pd.Timestamp('2015-1-1 00:00', tz='Etc/GMT-2')
Out[27]: Timestamp('2015-01-01 00:00:00+0200', tz='Etc/GMT-2')
Fixed offset time zones can also be specified as offset minutes
from UTC using pytz.FixedOffset
.
In [28]: pd.Timestamp('2015-1-1 00:00', tz=pytz.FixedOffset(120))
Out[28]: Timestamp('2015-01-01 00:00:00+0200', tz='pytz.FixedOffset(120)')
You can also specify the fixed offset directly in the tz_localize
method, however, be aware that this is not documented and that the
offset must be in seconds, not minutes.
In [29]: pd.Timestamp('2015-1-1 00:00', tz=7200)
Out[29]: Timestamp('2015-01-01 00:00:00+0200', tz='pytz.FixedOffset(120)')
Yet another way to specify a time zone with a fixed offset is by using the string formulation.
In [30]: pd.Timestamp('2015-1-1 00:00+0200')
Out[30]: Timestamp('2015-01-01 00:00:00+0200', tz='pytz.FixedOffset(120)')
Native Python objects¶
Sometimes it’s convenient to use native Python
datetime.date
and
datetime.datetime
objects, so we demonstrate their
use next. pandas Timestamp objects can also be created from time zone
aware or naive
datetime.datetime
objects. The behavior is as
expected.
# tz naive python datetime.datetime object
In [31]: naive_python_dt = datetime.datetime(2015, 6, 1, 0)
# tz naive pandas Timestamp object
In [32]: pd.Timestamp(naive_python_dt)
Out[32]: Timestamp('2015-06-01 00:00:00')
# tz aware python datetime.datetime object
In [33]: aware_python_dt = pytz.timezone('US/Mountain').localize(naive_python_dt)
# tz aware pandas Timestamp object
In [34]: pd.Timestamp(aware_python_dt)
Out[34]: Timestamp('2015-06-01 00:00:00-0600', tz='US/Mountain')
One thing to watch out for is that python
datetime.date
objects gain time information when
passed to Timestamp
.
# tz naive python datetime.date object (no time info)
In [35]: naive_python_date = datetime.date(2015, 6, 1)
# tz naive pandas Timestamp object (time=midnight)
In [36]: pd.Timestamp(naive_python_date)
Out[36]: Timestamp('2015-06-01 00:00:00')
You cannot localize a native Python date object.
# fail
In [37]: pytz.timezone('US/Mountain').localize(naive_python_date)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-37-46064241d938> in <module>()
----> 1 pytz.timezone('US/Mountain').localize(naive_python_date)
/home/docs/checkouts/readthedocs.org/user_builds/wholmgren-pvlib-python-new/conda/manualdoc/lib/python2.7/site-packages/pytz/tzinfo.pyc in localize(self, dt, is_dst)
301 Non-existent
302 '''
--> 303 if dt.tzinfo is not None:
304 raise ValueError('Not naive datetime (tzinfo is already set)')
305
AttributeError: 'datetime.date' object has no attribute 'tzinfo'
pvlib-specific functionality¶
Note
This section applies to pvlib >= 0.3. Version 0.2 of pvlib used a
Location
object’s tz
attribute to auto-magically correct for
some time zone issues. This behavior was counter-intuitive to many
users and was removed in version 0.3.
How does this general functionality interact with pvlib? Perhaps the two most common places to get tripped up with time and time zone issues in solar power analysis occur during data import and solar position calculations.
Data import¶
Let’s first examine how pvlib handles time when it imports a TMY3 file.
In [38]: import os
In [39]: import inspect
In [40]: import pvlib
# some gymnastics to find the example file
In [41]: pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(pvlib)))
In [42]: file_abspath = os.path.join(pvlib_abspath, 'data', '703165TY.csv')
In [43]: tmy3_data, tmy3_metadata = pvlib.tmy.readtmy3(file_abspath)
In [44]: tmy3_metadata
Out[44]:
{'Name': '"SAND POINT"',
'State': 'AK',
'TZ': -9.0,
'USAF': 703165,
'altitude': 7.0,
'latitude': 55.317,
'longitude': -160.517}
The metadata has a 'TZ'
key with a value of -9.0
. This is the
UTC offset in hours in which the data has been recorded. The
readtmy3()
function read the data in the file,
created a DataFrame
with that data, and then
localized the DataFrame’s index to have this fixed offset. Here, we
print just a few of the rows and columns of the large dataframe.
In [45]: tmy3_data.index.tz
Out[45]: pytz.FixedOffset(-540)
In [46]: tmy3_data.ix[0:3, ['GHI', 'DNI', 'AOD']]
Out[46]:
GHI DNI AOD
datetime
1997-01-01 01:00:00-09:00 0 0 0.051
1997-01-01 02:00:00-09:00 0 0 0.051
1997-01-01 03:00:00-09:00 0 0 0.051
The readtmy2()
function also returns a DataFrame
with a localized DatetimeIndex.
Solar position¶
The correct solar position can be immediately calculated from the DataFrame’s index since the index has been localized.
In [47]: solar_position = pvlib.solarposition.get_solarposition(tmy3_data.index,
....: tmy3_metadata['latitude'],
....: tmy3_metadata['longitude'])
....:
In [48]: ax = solar_position.ix[0:24, ['apparent_zenith', 'apparent_elevation', 'azimuth']].plot()
In [49]: ax.legend(loc=1);
In [50]: ax.axhline(0, color='darkgray'); # add 0 deg line for sunrise/sunset
In [51]: ax.axhline(180, color='darkgray'); # add 180 deg line for azimuth at solar noon
In [52]: ax.set_ylim(-60, 200); # zoom in, but cuts off full azimuth range
In [53]: ax.set_xlabel('Local time ({})'.format(solar_position.index.tz));
In [54]: ax.set_ylabel('(degrees)');

According to the US Navy, on January 1, 1997 at Sand Point, Alaska, sunrise was at 10:09 am, solar noon was at 1:46 pm, and sunset was at 5:23 pm. This is consistent with the data plotted above (and depressing).
Solar position (assumed UTC)¶
What if we had a DatetimeIndex that was not localized, such as the one below? The solar position calculator will assume UTC time.
In [55]: index = pd.DatetimeIndex(start='1997-01-01 01:00', freq='1h', periods=24)
In [56]: index
Out[56]:
DatetimeIndex(['1997-01-01 01:00:00', '1997-01-01 02:00:00',
'1997-01-01 03:00:00', '1997-01-01 04:00:00',
'1997-01-01 05:00:00', '1997-01-01 06:00:00',
'1997-01-01 07:00:00', '1997-01-01 08:00:00',
'1997-01-01 09:00:00', '1997-01-01 10:00:00',
'1997-01-01 11:00:00', '1997-01-01 12:00:00',
'1997-01-01 13:00:00', '1997-01-01 14:00:00',
'1997-01-01 15:00:00', '1997-01-01 16:00:00',
'1997-01-01 17:00:00', '1997-01-01 18:00:00',
'1997-01-01 19:00:00', '1997-01-01 20:00:00',
'1997-01-01 21:00:00', '1997-01-01 22:00:00',
'1997-01-01 23:00:00', '1997-01-02 00:00:00'],
dtype='datetime64[ns]', freq='H')
In [57]: solar_position_notz = pvlib.solarposition.get_solarposition(index,
....: tmy3_metadata['latitude'],
....: tmy3_metadata['longitude'])
....:
In [58]: ax = solar_position_notz.ix[0:24, ['apparent_zenith', 'apparent_elevation', 'azimuth']].plot()
In [59]: ax.legend(loc=1);
In [60]: ax.axhline(0, color='darkgray'); # add 0 deg line for sunrise/sunset
In [61]: ax.axhline(180, color='darkgray'); # add 180 deg line for azimuth at solar noon
In [62]: ax.set_ylim(-60, 200); # zoom in, but cuts off full azimuth range
In [63]: ax.set_xlabel('Time (UTC)');
In [64]: ax.set_ylabel('(degrees)');

This looks like the plot above, but shifted by 9 hours.
Solar position (calculate and convert)¶
In principle, one could localize the tz-naive solar position data to UTC, and then convert it to the desired time zone.
In [65]: fixed_tz = pytz.FixedOffset(tmy3_metadata['TZ'] * 60)
In [66]: solar_position_hack = solar_position_notz.tz_localize('UTC').tz_convert(fixed_tz)
In [67]: solar_position_hack.index
Out[67]:
DatetimeIndex(['1996-12-31 16:00:00-09:00', '1996-12-31 17:00:00-09:00',
'1996-12-31 18:00:00-09:00', '1996-12-31 19:00:00-09:00',
'1996-12-31 20:00:00-09:00', '1996-12-31 21:00:00-09:00',
'1996-12-31 22:00:00-09:00', '1996-12-31 23:00:00-09:00',
'1997-01-01 00:00:00-09:00', '1997-01-01 01:00:00-09:00',
'1997-01-01 02:00:00-09:00', '1997-01-01 03:00:00-09:00',
'1997-01-01 04:00:00-09:00', '1997-01-01 05:00:00-09:00',
'1997-01-01 06:00:00-09:00', '1997-01-01 07:00:00-09:00',
'1997-01-01 08:00:00-09:00', '1997-01-01 09:00:00-09:00',
'1997-01-01 10:00:00-09:00', '1997-01-01 11:00:00-09:00',
'1997-01-01 12:00:00-09:00', '1997-01-01 13:00:00-09:00',
'1997-01-01 14:00:00-09:00', '1997-01-01 15:00:00-09:00'],
dtype='datetime64[ns, pytz.FixedOffset(-540)]', freq='H')
In [68]: ax = solar_position_hack.ix[0:24, ['apparent_zenith', 'apparent_elevation', 'azimuth']].plot()
In [69]: ax.legend(loc=1);
In [70]: ax.axhline(0, color='darkgray'); # add 0 deg line for sunrise/sunset
In [71]: ax.axhline(180, color='darkgray'); # add 180 deg line for azimuth at solar noon
In [72]: ax.set_ylim(-60, 200); # zoom in, but cuts off full azimuth range
In [73]: ax.set_xlabel('Local time ({})'.format(solar_position_hack.index.tz));
In [74]: ax.set_ylabel('(degrees)');

Note that the time has been correctly localized and converted, however, the calculation bounds still correspond to the original assumed-UTC range.
For this and other reasons, we recommend that users supply time zone information at the beginning of a calculation rather than localizing and converting the results at the end of a calculation.
Clear sky¶
This section reviews the clear sky modeling capabilities of pvlib-python.
pvlib-python supports two ways to generate clear sky irradiance:
- A
Location
object’sget_clearsky()
method. - The functions contained in the
clearsky
module, includingineichen()
andsimplified_solis()
.
Users that work with simple time series data may prefer to use
get_clearsky()
, while users
that want finer control, more explicit code, or work with
multidimensional data may prefer to use the basic functions in the
clearsky
module.
The Location subsection demonstrates the easiest way to obtain a time series of clear sky data for a location. The Ineichen and Perez and Simplified Solis subsections detail the clear sky algorithms and input data.
We’ll need these imports for the examples below.
In [1]: import itertools
In [2]: import matplotlib.pyplot as plt
In [3]: import pandas as pd
# seaborn makes the plots look nicer
In [4]: import seaborn as sns
In [5]: sns.set_color_codes()
In [6]: import pvlib
In [7]: from pvlib import clearsky, atmosphere
In [8]: from pvlib.location import Location
Location¶
The easiest way to obtain a time series of clear sky irradiance is to use a
Location
object’s
get_clearsky()
method. The
get_clearsky()
method does the dirty
work of calculating solar position, extraterrestrial irradiance,
airmass, and atmospheric pressure, as appropriate, leaving the user to
only specify the most important parameters: time and atmospheric
attenuation. The time input must be a pandas.DatetimeIndex
,
while the atmospheric attenuation inputs may be constants or arrays.
The get_clearsky()
method always
returns a pandas.DataFrame
.
In [9]: tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
In [10]: times = pd.DatetimeIndex(start='2016-07-01', end='2016-07-04', freq='1min', tz=tus.tz)
In [11]: cs = tus.get_clearsky(times) # ineichen with climatology table by default
In [12]: cs.plot();
In [13]: plt.ylabel('Irradiance $W/m^2$');
In [14]: plt.title('Ineichen, climatological turbidity');

The get_clearsky()
method accepts a
model keyword argument and propagates additional arguments to the
functions that do the computation.
In [15]: cs = tus.get_clearsky(times, model='ineichen', linke_turbidity=3)
In [16]: cs.plot();
In [17]: plt.title('Ineichen, linke_turbidity=3');
In [18]: plt.ylabel('Irradiance $W/m^2$');

In [19]: cs = tus.get_clearsky(times, model='simplified_solis', aod700=0.2, precipitable_water=3)
In [20]: cs.plot();
In [21]: plt.title('Simplfied Solis, aod700=0.2, precipitable_water=3');
In [22]: plt.ylabel('Irradiance $W/m^2$');

See the sections below for more detail on the clear sky models.
Ineichen and Perez¶
The Ineichen and Perez clear sky model parameterizes irradiance in terms
of the Linke turbidity [Ine02]. pvlib-python implements this model in
the pvlib.clearsky.ineichen()
function.
Turbidity data¶
pvlib includes a file with monthly climatological turbidity values for the globe. The code below creates turbidity maps for a few months of the year. You could run it in a loop to create plots for all months.
In [23]: import calendar
In [24]: import os
In [25]: import scipy.io
In [26]: pvlib_path = os.path.dirname(os.path.abspath(pvlib.clearsky.__file__))
In [27]: filepath = os.path.join(pvlib_path, 'data', 'LinkeTurbidities.mat')
In [28]: mat = scipy.io.loadmat(filepath)
# data is in units of 20 x turbidity
In [29]: linke_turbidity_table = mat['LinkeTurbidity'] # / 20. # crashes on rtd
In [30]: def plot_turbidity_map(month, vmin=1, vmax=100):
....: plt.figure();
....: plt.imshow(linke_turbidity_table[:, :, month-1], vmin=vmin, vmax=vmax);
....: plt.title('Linke turbidity x 20, ' + calendar.month_name[month]);
....: plt.colorbar(shrink=0.5);
....: plt.tight_layout();
....:
In [31]: plot_turbidity_map(1)
In [32]: plot_turbidity_map(7)


The lookup_linke_turbidity()
function takes a
time, latitude, and longitude and gets the corresponding climatological
turbidity value for that time at those coordinates. By default, the
lookup_linke_turbidity()
function will linearly
interpolate turbidity from month to month, assuming that the raw data is
valid on 15th of each month. This interpolation removes discontinuities
in multi-month PV models. Here’s a plot of a few locations in the
Southwest U.S. with and without interpolation. We chose points that are
relatively close so that you can get a better sense of the spatial noise
and variability of the data set. Note that the altitude of these sites
varies from 300 m to 1500 m.
In [33]: times = pd.DatetimeIndex(start='2015-01-01', end='2016-01-01', freq='1D')
In [34]: sites = [(32, -111, 'Tucson1'), (32.2, -110.9, 'Tucson2'),
....: (33.5, -112.1, 'Phoenix'), (35.1, -106.6, 'Albuquerque')]
....:
In [35]: plt.figure();
In [36]: for lat, lon, name in sites:
....: turbidity = pvlib.clearsky.lookup_linke_turbidity(times, lat, lon, interp_turbidity=False)
....: turbidity.plot(label=name)
....:
In [37]: plt.legend();
In [38]: plt.title('Raw data (no interpolation)');
In [39]: plt.ylabel('Linke Turbidity');
In [40]: plt.figure();
In [41]: for lat, lon, name in sites:
....: turbidity = pvlib.clearsky.lookup_linke_turbidity(times, lat, lon)
....: turbidity.plot(label=name)
....:
In [42]: plt.legend();
In [43]: plt.title('Interpolated to the day');
In [44]: plt.ylabel('Linke Turbidity');


Examples¶
A clear sky time series using only basic pvlib functions.
In [45]: latitude, longitude, tz, altitude, name = 32.2, -111, 'US/Arizona', 700, 'Tucson'
In [46]: times = pd.date_range(start='2014-01-01', end='2014-01-02', freq='1Min', tz=tz)
In [47]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [48]: apparent_zenith = solpos['apparent_zenith']
In [49]: airmass = pvlib.atmosphere.relativeairmass(apparent_zenith)
In [50]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [51]: airmass = pvlib.atmosphere.absoluteairmass(airmass, pressure)
In [52]: linke_turbidity = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
In [53]: dni_extra = pvlib.irradiance.extraradiation(times.dayofyear)
# an input is a pandas Series, so solis is a DataFrame
In [54]: ineichen = clearsky.ineichen(apparent_zenith, airmass, linke_turbidity, altitude, dni_extra)
In [55]: plt.figure();
In [56]: ax = ineichen.plot()
In [57]: ax.set_ylabel('Irradiance $W/m^2$');
In [58]: ax.set_title('Ineichen Clear Sky Model');
In [59]: ax.legend(loc=2);
In [60]: plt.show();

The input data types determine the returned output type. Array input results in an OrderedDict of array output, and Series input results in a DataFrame output. The keys are ‘ghi’, ‘dni’, and ‘dhi’.
Grid with a clear sky irradiance for a few turbidity values.
In [61]: times = pd.date_range(start='2014-09-01', end='2014-09-02', freq='1Min', tz=tz)
In [62]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [63]: apparent_zenith = solpos['apparent_zenith']
In [64]: airmass = pvlib.atmosphere.relativeairmass(apparent_zenith)
In [65]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [66]: airmass = pvlib.atmosphere.absoluteairmass(airmass, pressure)
In [67]: linke_turbidity = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
In [68]: print('climatological linke_turbidity = {}'.format(linke_turbidity.mean()))
climatological linke_turbidity = 3.38555591669
In [69]: dni_extra = pvlib.irradiance.extraradiation(times.dayofyear)
In [70]: linke_turbidities = [linke_turbidity.mean(), 2, 4]
In [71]: fig, axes = plt.subplots(ncols=3, nrows=1, sharex=True, sharey=True, squeeze=True, figsize=(12, 4))
In [72]: axes = axes.flatten()
In [73]: for linke_turbidity, ax in zip(linke_turbidities, axes):
....: ineichen = clearsky.ineichen(apparent_zenith, airmass, linke_turbidity, altitude, dni_extra)
....: ineichen.plot(ax=ax, title='Linke turbidity = {:0.1f}'.format(linke_turbidity));
....:
In [74]: ax.legend(loc=1);
In [75]: plt.show();

Validation¶
Will Holmgren compared pvlib’s Ineichen model and climatological turbidity to SoDa’s McClear service in Arizona. Here are links to an ipynb notebook and its html rendering.
Simplified Solis¶
The Simplified Solis model parameterizes irradiance in terms of
precipitable water and aerosol optical depth [Ine08ss]. pvlib-python
implements this model in the pvlib.clearsky.simplified_solis()
function.
Aerosol and precipitable water data¶
There are a number of sources for aerosol and precipitable water data
of varying accuracy, global coverage, and temporal resolution.
Ground based aerosol data can be obtained from
Aeronet. Precipitable water can be obtained
from radiosondes,
ESRL GPS-MET, or
derived from surface relative humidity using functions such as
pvlib.atmosphere.gueymard94_pw()
.
Numerous gridded products from satellites, weather models, and climate models
contain one or both of aerosols and precipitable water. Consider data
from the ECMWF
and SoDa.
Aerosol optical depth is a function of wavelength, and the Simplified Solis model requires AOD at 700 nm. Models exist to convert AOD between different wavelengths, as well as convert Linke turbidity to AOD and PW [Ine08con], [Ine16].
Examples¶
A clear sky time series using only basic pvlib functions.
In [76]: latitude, longitude, tz, altitude, name = 32.2, -111, 'US/Arizona', 700, 'Tucson'
In [77]: times = pd.date_range(start='2014-01-01', end='2014-01-02', freq='1Min', tz=tz)
In [78]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [79]: apparent_elevation = solpos['apparent_elevation']
In [80]: aod700 = 0.1
In [81]: precipitable_water = 1
In [82]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [83]: dni_extra = pvlib.irradiance.extraradiation(times.dayofyear)
# an input is a Series, so solis is a DataFrame
In [84]: solis = clearsky.simplified_solis(apparent_elevation, aod700, precipitable_water,
....: pressure, dni_extra)
....:
In [85]: ax = solis.plot();
In [86]: ax.set_ylabel('Irradiance $W/m^2$');
In [87]: ax.set_title('Simplified Solis Clear Sky Model');
In [88]: ax.legend(loc=2);
In [89]: plt.show();

The input data types determine the returned output type. Array input results in an OrderedDict of array output, and Series input results in a DataFrame output. The keys are ‘ghi’, ‘dni’, and ‘dhi’.
Irradiance as a function of solar elevation.
In [90]: apparent_elevation = pd.Series(np.linspace(-10, 90, 101))
In [91]: aod700 = 0.1
In [92]: precipitable_water = 1
In [93]: pressure = 101325
In [94]: dni_extra = 1364
In [95]: solis = clearsky.simplified_solis(apparent_elevation, aod700,
....: precipitable_water, pressure, dni_extra)
....:
In [96]: ax = solis.plot();
In [97]: ax.set_xlabel('Apparent elevation (deg)');
In [98]: ax.set_ylabel('Irradiance $W/m^2$');
In [99]: ax.set_title('Irradiance vs Solar Elevation')
Out[99]: <matplotlib.text.Text at 0x7fc20ee98990>
In [100]: ax.legend(loc=2);

Grid with a clear sky irradiance for a few PW and AOD values.
In [101]: times = pd.date_range(start='2014-09-01', end='2014-09-02', freq='1Min', tz=tz)
In [102]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [103]: apparent_elevation = solpos['apparent_elevation']
In [104]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [105]: dni_extra = pvlib.irradiance.extraradiation(times.dayofyear)
In [106]: aod700 = [0.01, 0.1]
In [107]: precipitable_water = [0.5, 5]
In [108]: fig, axes = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True, squeeze=True)
In [109]: axes = axes.flatten()
In [110]: for (aod, pw), ax in zip(itertools.chain(itertools.product(aod700, precipitable_water)), axes):
.....: cs = clearsky.simplified_solis(apparent_elevation, aod, pw, pressure, dni_extra)
.....: cs.plot(ax=ax, title='aod700={}, pw={}'.format(aod, pw))
.....:
In [111]: plt.show();

Contour plots of irradiance as a function of both PW and AOD.
In [112]: aod700 = np.linspace(0, 0.5, 101)
In [113]: precipitable_water = np.linspace(0, 10, 101)
In [114]: apparent_elevation = 70
In [115]: pressure = 101325
In [116]: dni_extra = 1364
In [117]: aod700, precipitable_water = np.meshgrid(aod700, precipitable_water)
# inputs are arrays, so solis is an OrderedDict
In [118]: solis = clearsky.simplified_solis(apparent_elevation, aod700,
.....: precipitable_water, pressure,
.....: dni_extra)
.....:
In [119]: cmap = plt.get_cmap('viridis')
In [120]: n = 15
In [121]: vmin = None
In [122]: vmax = None
In [123]: def plot_solis(key):
.....: irrad = solis[key]
.....: fig, ax = plt.subplots()
.....: im = ax.contour(aod700, precipitable_water, irrad[:, :], n, cmap=cmap, vmin=vmin, vmax=vmax)
.....: imf = ax.contourf(aod700, precipitable_water, irrad[:, :], n, cmap=cmap, vmin=vmin, vmax=vmax)
.....: ax.set_xlabel('AOD')
.....: ax.set_ylabel('Precipitable water (cm)')
.....: ax.clabel(im, colors='k', fmt='%.0f')
.....: fig.colorbar(imf, label='{} (W/m**2)'.format(key))
.....: ax.set_title('{}, elevation={}'.format(key, apparent_elevation))
.....:
In [124]: plot_solis('ghi')
In [125]: plt.show()
In [126]: plot_solis('dni')
In [127]: plt.show()
In [128]: plot_solis('dhi')
In [129]: plt.show()



Validation¶
See [Ine16].
We encourage users to compare the pvlib implementation to Ineichen’s Excel tool.
References¶
[Ine02] | (1, 2) P. Ineichen and R. Perez, “A New airmass independent formulation for the Linke turbidity coefficient”, Solar Energy, 73, pp. 151-157, 2002. |
[Ine08ss] | P. Ineichen, “A broadband simplified version of the Solis clear sky model,” Solar Energy, 82, 758-762 (2008). |
[Ine16] | (1, 2) P. Ineichen, “Validation of models that estimate the clear sky global and beam solar irradiance,” Solar Energy, 132, 332-344 (2016). |
[Ine08con] | P. Ineichen, “Conversion function between the Linke turbidity and the atmospheric water vapor and aerosol content”, Solar Energy, 82, 1095 (2008). |
[Ren12] | M. Reno, C. Hansen, and J. Stein, “Global Horizontal Irradiance Clear Sky Models: Implementation and Analysis”, Sandia National Laboratories, SAND2012-2389, 2012. |
Forecasting¶
pvlib-python provides a set of functions and classes that make it easy to obtain weather forecast data and convert that data into a PV power forecast. Users can retrieve standardized weather forecast data relevant to PV power modeling from NOAA/NCEP/NWS models including the GFS, NAM, RAP, HRRR, and the NDFD. A PV power forecast can then be obtained using the weather data as inputs to the comprehensive modeling capabilities of PVLIB-Python. Standardized, open source, reference implementations of forecast methods using publicly available data may help advance the state-of-the-art of solar power forecasting.
pvlib-python uses Unidata’s Siphon library to simplify access to forecast data hosted on the Unidata THREDDS catalog. Siphon is great for programatic access of THREDDS data, but we also recommend using tools such as Panoply to easily browse the catalog and become more familiar with its contents.
This document demonstrates how to use pvlib-python to create a PV power forecast using these tools. The forecast and forecast_to_power Jupyter notebooks provide additional example code.
Warning
The forecast module algorithms and features are highly experimental. The API may change, the functionality may be consolidated into an io module, or the module may be separated into its own package.
Note
This documentation is difficult to reliably build on readthedocs. If you do not see images, try building the documentation on your own machine or see the notebooks linked to above.
Accessing Forecast Data¶
The Siphon library provides access to, among others, forecasts from the
Global Forecast System (GFS), North American Model (NAM), High
Resolution Rapid Refresh (HRRR), Rapid Refresh (RAP), and National
Digital Forecast Database (NDFD) on a Unidata THREDDS server.
Unfortunately, many of these models use different names to describe the
same quantity (or a very similar one), and not all variables are present
in all models. For example, on the THREDDS server, the GFS has a field
named
Total_cloud_cover_entire_atmosphere_Mixed_intervals_Average
,
while the NAM has a field named
Total_cloud_cover_entire_atmosphere_single_layer
, and a
similar field in the HRRR is named
Total_cloud_cover_entire_atmosphere
.
PVLIB-Python aims to simplify the access of the model fields relevant
for solar power forecasts. Model data accessed with PVLIB-Python is
returned as a pandas DataFrame with consistent column names:
temp_air, wind_speed, total_clouds, low_clouds, mid_clouds,
high_clouds, dni, dhi, ghi
. To accomplish this, we use an
object-oriented framework in which each weather model is represented by
a class that inherits from a parent
ForecastModel
class.
The parent ForecastModel
class contains the
common code for accessing and parsing the data using Siphon, while the
child model-specific classes (GFS
,
HRRR
, etc.) contain the code necessary to
map and process that specific model’s data to the standardized fields.
The code below demonstrates how simple it is to access and plot forecast data using PVLIB-Python. First, we set up make the basic imports and then set the location and time range data.
In [1]: import pandas as pd
In [2]: import matplotlib.pyplot as plt
In [3]: import datetime
# seaborn makes the plots look nicer
In [4]: import seaborn as sns; sns.set_color_codes()
# import pvlib forecast models
In [5]: from pvlib.forecast import GFS, NAM, NDFD, HRRR, RAP
---------------------------------------------------------------------------
ImportError Traceback (most recent call last)
<ipython-input-5-755839b9f9c0> in <module>()
----> 1 from pvlib.forecast import GFS, NAM, NDFD, HRRR, RAP
/home/docs/checkouts/readthedocs.org/user_builds/wholmgren-pvlib-python-new/checkouts/manualdoc/pvlib/forecast.py in <module>()
4 '''
5 import datetime
----> 6 from netCDF4 import num2date
7 import numpy as np
8 import pandas as pd
ImportError: No module named netCDF4
# specify location (Tucson, AZ)
In [6]: latitude, longitude, tz = 32.2, -110.9, 'US/Arizona'
# specify time range.
In [7]: start = pd.Timestamp(datetime.date.today(), tz=tz)
In [8]: end = start + pd.Timedelta(days=7)
In [9]: irrad_vars = ['ghi', 'dni', 'dhi']
Next, we instantiate a GFS model object and get the forecast data from Unidata.
# GFS model, defaults to 0.5 degree resolution
# 0.25 deg available
In [10]: model = GFS()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-10-d96e33776baa> in <module>()
----> 1 model = GFS()
NameError: name 'GFS' is not defined
# retrieve data. returns pandas.DataFrame object
In [11]: raw_data = model.get_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-11-09fefd435874> in <module>()
----> 1 raw_data = model.get_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [12]: print(raw_data.head())
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-12-84e1dd9c1edc> in <module>()
----> 1 print(raw_data.head())
NameError: name 'raw_data' is not defined
It will be useful to process this data before using it with pvlib. For example, the column names are non-standard, the temperature is in Kelvin, the wind speed is broken into east/west and north/south components, and most importantly, most of the irradiance data is missing. The forecast module provides a number of methods to fix these problems.
In [13]: data = raw_data
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-13-6d08630bb917> in <module>()
----> 1 data = raw_data
NameError: name 'raw_data' is not defined
# rename the columns according the key/value pairs in model.variables.
In [14]: data = model.rename(data)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-14-dc9c4c319bf0> in <module>()
----> 1 data = model.rename(data)
NameError: name 'model' is not defined
# convert temperature
In [15]: data['temp_air'] = model.kelvin_to_celsius(data['temp_air'])
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-15-c4eec0a4cb0b> in <module>()
----> 1 data['temp_air'] = model.kelvin_to_celsius(data['temp_air'])
NameError: name 'model' is not defined
# convert wind components to wind speed
In [16]: data['wind_speed'] = model.uv_to_speed(data)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-16-a16d97a9a611> in <module>()
----> 1 data['wind_speed'] = model.uv_to_speed(data)
NameError: name 'model' is not defined
# calculate irradiance estimates from cloud cover.
# uses a cloud_cover to ghi to dni model or a
# uses a cloud cover to transmittance to irradiance model.
# this step is discussed in more detail in the next section
In [17]: irrad_data = model.cloud_cover_to_irradiance(data['total_clouds'])
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-17-eb1a7bd50164> in <module>()
----> 1 irrad_data = model.cloud_cover_to_irradiance(data['total_clouds'])
NameError: name 'model' is not defined
In [18]: data = data.join(irrad_data, how='outer')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-18-18ba18bbbe26> in <module>()
----> 1 data = data.join(irrad_data, how='outer')
NameError: name 'data' is not defined
# keep only the final data
In [19]: data = data.ix[:, model.output_variables]
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-19-f5a23455a138> in <module>()
----> 1 data = data.ix[:, model.output_variables]
NameError: name 'data' is not defined
In [20]: print(data.head())
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-20-d64a39515b41> in <module>()
----> 1 print(data.head())
NameError: name 'data' is not defined
Much better.
The GFS class’s
process_data()
method combines these steps
in a single function. In fact, each forecast model class
implements its own process_data
method since the data from each
weather model is slightly different. The process_data
functions are
designed to be explicit about how the data is being processed, and users
are strongly encouraged to read the source code of these methods.
In [21]: data = model.process_data(raw_data)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-21-ac861ce136ab> in <module>()
----> 1 data = model.process_data(raw_data)
NameError: name 'model' is not defined
In [22]: print(data.head())
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-22-d64a39515b41> in <module>()
----> 1 print(data.head())
NameError: name 'data' is not defined
Users can easily implement their own process_data
methods on
inherited classes or implement similar stand-alone functions.
The forecast model classes also implement a
get_processed_data()
method that
combines the get_data()
and
process_data()
calls.
In [23]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-23-6e64de0b0a93> in <module>()
----> 1 data = model.get_processed_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [24]: print(data.head())
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-24-d64a39515b41> in <module>()
----> 1 print(data.head())
NameError: name 'data' is not defined
Cloud cover and radiation¶
All of the weather models currently accessible by pvlib include one or more cloud cover forecasts. For example, below we plot the GFS cloud cover forecasts.
# plot cloud cover percentages
In [25]: cloud_vars = ['total_clouds', 'low_clouds',
....: 'mid_clouds', 'high_clouds']
....:
In [26]: data[cloud_vars].plot();
In [27]: plt.ylabel('Cloud cover %');
In [28]: plt.xlabel('Forecast Time ({})'.format(tz));
In [29]: plt.title('GFS 0.5 deg forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [30]: plt.legend();

However, many of forecast models do not include radiation components in their output fields, or if they do then the radiation fields suffer from poor solar position or radiative transfer algorithms. It is often more accurate to create empirically derived radiation forecasts from the weather models’ cloud cover forecasts.
PVLIB-Python provides two basic ways to convert cloud cover forecasts to irradiance forecasts. One method assumes a linear relationship between cloud cover and GHI, applies the scaling to a clear sky climatology, and then uses the DISC model to calculate DNI. The second method assumes a linear relationship between cloud cover and atmospheric transmittance, and then uses the Liu-Jordan [Liu60] model to calculate GHI, DNI, and DHI.
Caveat emptor: these algorithms are not rigorously verified! The purpose of the forecast module is to provide a few exceedingly simple options for users to play with before they develop their own models. We strongly encourage pvlib users first read the source code and second to implement new cloud cover to irradiance algorithms.
The essential parts of the clear sky scaling algorithm are as follows. Clear sky scaling of climatological GHI is also used in Larson et. al. [Lar16].
solpos = location.get_solarposition(cloud_cover.index)
cs = location.get_clearsky(cloud_cover.index, model='ineichen')
# offset and cloud cover in decimal units here
# larson et. al. use offset = 0.35
ghi = (offset + (1 - offset) * (1 - cloud_cover)) * ghi_clear
dni = disc(ghi, solpos['zenith'], cloud_cover.index)['dni']
dhi = ghi - dni * np.cos(np.radians(solpos['zenith']))
The figure below shows the result of the total cloud cover to irradiance conversion using the clear sky scaling algorithm.
# plot irradiance data
In [31]: data = model.rename(raw_data)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-31-e1131492c602> in <module>()
----> 1 data = model.rename(raw_data)
NameError: name 'model' is not defined
In [32]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='clearsky_scaling')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-32-54e8054795ab> in <module>()
----> 1 irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='clearsky_scaling')
NameError: name 'model' is not defined
In [33]: irrads.plot();
In [34]: plt.ylabel('Irradiance ($W/m^2$)');
In [35]: plt.xlabel('Forecast Time ({})'.format(tz));
In [36]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "clearsky_scaling"'
....: .format(latitude, longitude));
....:
In [37]: plt.legend();

The essential parts of the Liu-Jordan cloud cover to irradiance algorithm are as follows.
# cloud cover in percentage units here
transmittance = ((100.0 - cloud_cover) / 100.0) * 0.75
# irrads is a DataFrame containing ghi, dni, dhi
irrads = liujordan(apparent_zenith, transmittance, airmass_absolute)
The figure below shows the result of the Liu-Jordan total cloud cover to irradiance conversion.
# plot irradiance data
In [38]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='liujordan')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-38-e418cea08958> in <module>()
----> 1 irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='liujordan')
NameError: name 'model' is not defined
In [39]: irrads.plot();
In [40]: plt.ylabel('Irradiance ($W/m^2$)');
In [41]: plt.xlabel('Forecast Time ({})'.format(tz));
In [42]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "liujordan"'
....: .format(latitude, longitude));
....:
In [43]: plt.legend();

Most weather model output has a fairly coarse time resolution, at least an hour. The irradiance forecasts have the same time resolution as the weather data. However, it is straightforward to interpolate the cloud cover forecasts onto a higher resolution time domain, and then recalculate the irradiance.
In [44]: resampled_data = data.resample('5min').interpolate()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-44-521a68031715> in <module>()
----> 1 resampled_data = data.resample('5min').interpolate()
NameError: name 'data' is not defined
In [45]: resampled_irrads = model.cloud_cover_to_irradiance(resampled_data['total_clouds'], how='clearsky_scaling')
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-45-0eb03b28005b> in <module>()
----> 1 resampled_irrads = model.cloud_cover_to_irradiance(resampled_data['total_clouds'], how='clearsky_scaling')
NameError: name 'model' is not defined
In [46]: resampled_irrads.plot();
In [47]: plt.ylabel('Irradiance ($W/m^2$)');
In [48]: plt.xlabel('Forecast Time ({})'.format(tz));
In [49]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} resampled'
....: .format(latitude, longitude));
....:
In [50]: plt.legend();

Users may then recombine resampled_irrads and resampled_data using
slicing pandas.concat()
or pandas.DataFrame.join()
.
We reiterate that the open source code enables users to customize the model processing to their liking.
[Lar16] | Larson et. al. “Day-ahead forecasting of solar power output from photovoltaic plants in the American Southwest” Renewable Energy 91, 11-20 (2016). |
[Liu60] | B. Y. Liu and R. C. Jordan, The interrelationship and characteristic distribution of direct, diffuse, and total solar radiation, Solar Energy 4, 1 (1960). |
Weather Models¶
Next, we provide a brief description of the weather models available to pvlib users. Note that the figures are generated when this documentation is compiled so they will vary over time.
GFS¶
The Global Forecast System (GFS) is the US model that provides forecasts for the entire globe. The GFS is updated every 6 hours. The GFS is run at two resolutions, 0.25 deg and 0.5 deg, and is available with 3 hour time resolution. Forecasts from GFS model were shown above. Use the GFS, among others, if you want forecasts for 1-7 days or if you want forecasts for anywhere on Earth.
HRRR¶
The High Resolution Rapid Refresh (HRRR) model is perhaps the most accurate model, however, it is only available for ~15 hours. It is updated every hour and runs at 3 km resolution. The HRRR excels in severe weather situations. A major upgrade to the HRRR model is expected in Spring, 2016. See the NOAA ESRL HRRR page for more information. Use the HRRR, among others, if you want forecasts for less than 24 hours. The HRRR model covers the continental United States.
In [51]: model = HRRR()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-51-f3f84dbe7f4c> in <module>()
----> 1 model = HRRR()
NameError: name 'HRRR' is not defined
In [52]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-52-6e64de0b0a93> in <module>()
----> 1 data = model.get_processed_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [53]: data[irrad_vars].plot();
In [54]: plt.ylabel('Irradiance ($W/m^2$)');
In [55]: plt.xlabel('Forecast Time ({})'.format(tz));
In [56]: plt.title('HRRR 3 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [57]: plt.legend();

RAP¶
The Rapid Refresh (RAP) model is the parent model for the HRRR. It is updated every hour and runs at 40, 20, and 13 km resolutions. Only the 20 and 40 km resolutions are currently available in pvlib. It is also excels in severe weather situations. A major upgrade to the RAP model is expected in Spring, 2016. See the NOAA ESRL HRRR page for more information. Use the RAP, among others, if you want forecasts for less than 24 hours. The RAP model covers most of North America.
In [58]: model = RAP()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-58-0f26fd7a9618> in <module>()
----> 1 model = RAP()
NameError: name 'RAP' is not defined
In [59]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-59-6e64de0b0a93> in <module>()
----> 1 data = model.get_processed_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [60]: data[irrad_vars].plot();
In [61]: plt.ylabel('Irradiance ($W/m^2$)');
In [62]: plt.xlabel('Forecast Time ({})'.format(tz));
In [63]: plt.title('RAP 13 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [64]: plt.legend();

NAM¶
The North American Mesoscale model covers, not surprisingly, North America. It is updated every 6 hours. pvlib provides access to 20 km resolution NAM data with a time horizon of up to 4 days.
In [65]: model = NAM()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-65-2c315c08c66a> in <module>()
----> 1 model = NAM()
NameError: name 'NAM' is not defined
In [66]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-66-6e64de0b0a93> in <module>()
----> 1 data = model.get_processed_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [67]: data[irrad_vars].plot();
In [68]: plt.ylabel('Irradiance ($W/m^2$)');
In [69]: plt.xlabel('Forecast Time ({})'.format(tz));
In [70]: plt.title('NAM 20 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [71]: plt.legend();

NDFD¶
The National Digital Forecast Database is not a model, but rather a collection of forecasts made by National Weather Service offices across the country. It is updated every 6 hours. Use the NDFD, among others, for forecasts at all time horizons. The NDFD is available for the United States.
In [72]: model = NDFD()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-72-036a2b112dce> in <module>()
----> 1 model = NDFD()
NameError: name 'NDFD' is not defined
In [73]: data = model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-73-6e64de0b0a93> in <module>()
----> 1 data = model.get_processed_data(latitude, longitude, start, end)
NameError: name 'model' is not defined
In [74]: data[irrad_vars].plot();
In [75]: plt.ylabel('Irradiance ($W/m^2$)');
In [76]: plt.xlabel('Forecast Time ({})'.format(tz));
In [77]: plt.title('NDFD forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [78]: plt.legend();

PV Power Forecast¶
Finally, we demonstrate the application of the weather forecast data to a PV power forecast. Please see the remainder of the pvlib documentation for details.
In [79]: from pvlib.pvsystem import PVSystem, retrieve_sam
In [80]: from pvlib.tracking import SingleAxisTracker
In [81]: from pvlib.modelchain import ModelChain
In [82]: sandia_modules = retrieve_sam('sandiamod')
In [83]: cec_inverters = retrieve_sam('cecinverter')
In [84]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [85]: inverter = cec_inverters['SMA_America__SC630CP_US_315V__CEC_2012_']
# model a big tracker for more fun
In [86]: system = SingleAxisTracker(module_parameters=module,
....: inverter_parameters=inverter,
....: modules_per_string=15,
....: strings_per_inverter=300)
....:
# fx is a common abbreviation for forecast
In [87]: fx_model = GFS()
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-87-80398973d7dc> in <module>()
----> 1 fx_model = GFS()
NameError: name 'GFS' is not defined
In [88]: fx_data = fx_model.get_processed_data(latitude, longitude, start, end)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-88-09b0dab0b4fe> in <module>()
----> 1 fx_data = fx_model.get_processed_data(latitude, longitude, start, end)
NameError: name 'fx_model' is not defined
# use a ModelChain object to calculate modeling intermediates
In [89]: mc = ModelChain(system, fx_model.location)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-89-584a20eeff97> in <module>()
----> 1 mc = ModelChain(system, fx_model.location)
NameError: name 'fx_model' is not defined
# extract relevant data for model chain
In [90]: irradiance = fx_data[['ghi', 'dni', 'dhi']]
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-90-a0619940b2d8> in <module>()
----> 1 irradiance = fx_data[['ghi', 'dni', 'dhi']]
NameError: name 'fx_data' is not defined
In [91]: weather = fx_data[['wind_speed', 'temp_air']]
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-91-460dc6bea2b9> in <module>()
----> 1 weather = fx_data[['wind_speed', 'temp_air']]
NameError: name 'fx_data' is not defined
In [92]: mc.run_model(fx_data.index, irradiance=irradiance, weather=weather);
Now we plot a couple of modeling intermediates and the forecast power. Here’s the forecast plane of array irradiance...
In [93]: mc.total_irrad.plot();
In [94]: plt.ylabel('Plane of array irradiance ($W/m**2$)');

...the cell and module temperature...
In [95]: mc.temps.plot();
In [96]: plt.ylabel('Temperature (C)');

...and finally AC power...
In [97]: mc.ac.plot();
In [98]: plt.ylim(0, None);
In [99]: plt.ylabel('AC Power (W)');

API reference¶
Classes¶
pvlib-python provides a collection of classes for users that prefer object-oriented programming. These classes can help users keep track of data in a more organized way, and can help to simplify the modeling process. The classes do not add any functionality beyond the procedural code. Most of the object methods are simple wrappers around the corresponding procedural code.
location.Location (latitude, longitude[, tz, ...]) |
Location objects are convenient containers for latitude, longitude, timezone, and altitude data associated with a particular geographic location. |
pvsystem.PVSystem ([surface_tilt, ...]) |
The PVSystem class defines a standard set of PV system attributes and modeling functions. |
tracking.SingleAxisTracker ([axis_tilt, ...]) |
Inherits all of the PV modeling methods from PVSystem. |
modelchain.ModelChain (system, location[, ...]) |
An experimental class that represents all of the modeling steps necessary for calculating power or energy for a PV system at a given location using the SAPM. |
pvsystem.LocalizedPVSystem ([pvsystem, location]) |
The LocalizedPVSystem class defines a standard set of installed PV system attributes and modeling functions. |
tracking.LocalizedSingleAxisTracker ([...]) |
Highly experimental. |
Solar Position¶
Functions and methods for calculating solar position.
location.Location.get_solarposition (times[, ...]) |
Uses the solarposition.get_solarposition() function to calculate the solar zenith, azimuth, etc. |
solarposition.get_solarposition (time, ...[, ...]) |
A convenience wrapper for the solar position calculators. |
solarposition.spa_python (time, latitude, ...) |
Calculate the solar position using a python implementation of the NREL SPA algorithm described in [1]. |
solarposition.ephemeris (time, latitude, ...) |
Python-native solar position calculator. |
solarposition.pyephem (time, latitude, longitude) |
Calculate the solar position using the PyEphem package. |
solarposition.spa_c (time, latitude, longitude) |
Calculate the solar position using the C implementation of the NREL |
Additional functions for quantities closely related to solar position.
solarposition.calc_time (lower_bound, ...[, ...]) |
Calculate the time between lower_bound and upper_bound where the attribute is equal to value. |
solarposition.pyephem_earthsun_distance (time) |
Calculates the distance from the earth to the sun using pyephem. |
solarposition.nrel_earthsun_distance (time[, ...]) |
Calculates the distance from the earth to the sun using the NREL SPA algorithm described in [1]. |
spa.calculate_deltat (year, month) |
Calculate the difference between Terrestrial Dynamical Time (TD) and Universal Time (UT). |
The spa module contains the implementation of the built-in NREL SPA algorithm.
spa |
Calculate the solar position using the NREL SPA algorithm either using numpy arrays or compiling the code to machine language with numba. |
Clear sky¶
location.Location.get_clearsky (times[, ...]) |
Calculate the clear sky estimates of GHI, DNI, and/or DHI at this location. |
clearsky.ineichen (apparent_zenith, ...[, ...]) |
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model. |
clearsky.lookup_linke_turbidity (time, ...[, ...]) |
Look up the Linke Turibidity from the LinkeTurbidities.mat data file supplied with pvlib. |
clearsky.simplified_solis (apparent_elevation) |
Calculate the clear sky GHI, DNI, and DHI according to the simplified Solis model [R1]. |
clearsky.haurwitz (apparent_zenith) |
Determine clear sky GHI from Haurwitz model. |
Airmass and atmospheric models¶
location.Location.get_airmass ([times, ...]) |
Calculate the relative and absolute airmass. |
atmosphere.absoluteairmass (airmass_relative) |
Determine absolute (pressure corrected) airmass from relative |
atmosphere.relativeairmass (zenith[, model]) |
Gives the relative (not pressure-corrected) airmass. |
atmosphere.pres2alt (pressure) |
Determine altitude from site pressure. |
atmosphere.alt2pres (altitude) |
Determine site pressure from altitude. |
atmosphere.gueymard94_pw (temp_air, ...) |
Calculates precipitable water (cm) from ambient air temperature (C) and relatively humidity (%) using an empirical model. |
atmosphere.first_solar_spectral_correction (pw, ...) |
Spectral mismatch modifier based on precipitable water and absolute (pressure corrected) airmass. |
Irradiance¶
Methods for irradiance calculations¶
pvsystem.PVSystem.get_irradiance (...[, ...]) |
Uses the irradiance.total_irrad() function to calculate the plane of array irradiance components on a tilted surface defined by self.surface_tilt , self.surface_azimuth , and self.albedo . |
pvsystem.PVSystem.get_aoi (solar_zenith, ...) |
Get the angle of incidence on the system. |
tracking.SingleAxisTracker.get_irradiance (...) |
Uses the irradiance.total_irrad() function to calculate the plane of array irradiance components on a tilted surface defined by self.surface_tilt , self.surface_azimuth , and self.albedo . |
Decomposing and combining irradiance¶
irradiance.extraradiation (datetime_or_doy[, ...]) |
Determine extraterrestrial radiation from day of year. |
irradiance.aoi (surface_tilt, ...) |
Calculates the angle of incidence of the solar vector on a surface. |
irradiance.aoi_projection (surface_tilt, ...) |
Calculates the dot product of the solar vector and the surface normal. |
irradiance.poa_horizontal_ratio (...) |
Calculates the ratio of the beam components of the plane of array irradiance and the horizontal irradiance. |
irradiance.beam_component (surface_tilt, ...) |
Calculates the beam component of the plane of array irradiance. |
irradiance.globalinplane (aoi, dni, ...) |
Determine the three components on in-plane irradiance |
irradiance.grounddiffuse (surface_tilt, ghi) |
Estimate diffuse irradiance from ground reflections given |
Transposition models¶
irradiance.total_irrad (surface_tilt, ...[, ...]) |
Determine diffuse irradiance from the sky on a tilted surface. |
irradiance.isotropic (surface_tilt, dhi) |
Determine diffuse irradiance from the sky on a tilted surface using the isotropic sky model. |
irradiance.perez (surface_tilt, ...[, model]) |
Determine diffuse irradiance from the sky on a tilted surface using one of the Perez models. |
irradiance.haydavies (surface_tilt, ...[, ...]) |
Determine diffuse irradiance from the sky on a tilted surface using |
irradiance.klucher (surface_tilt, ...) |
Determine diffuse irradiance from the sky on a tilted surface |
irradiance.reindl (surface_tilt, ...) |
Determine diffuse irradiance from the sky on a tilted surface using |
irradiance.king (surface_tilt, dhi, ghi, ...) |
Determine diffuse irradiance from the sky on a tilted surface using the King model. |
DNI estimation models¶
irradiance.disc (ghi, zenith, datetime_or_doy) |
Estimate Direct Normal Irradiance from Global Horizontal Irradiance using the DISC model. |
irradiance.dirint (ghi, zenith, times[, ...]) |
Determine DNI from GHI using the DIRINT modification of the DISC model. |
irradiance.erbs (ghi, zenith, doy) |
Estimate DNI and DHI from GHI using the Erbs model. |
irradiance.liujordan (zenith, transmittance, ...) |
Determine DNI, DHI, GHI from extraterrestrial flux, transmittance, and optical air mass number. |
PV Modeling¶
Classes¶
The PVSystem
class provides many methods that
wrap the functions listed below. See its documentation for details.
pvsystem.PVSystem ([surface_tilt, ...]) |
The PVSystem class defines a standard set of PV system attributes and modeling functions. |
pvsystem.LocalizedPVSystem ([pvsystem, location]) |
The LocalizedPVSystem class defines a standard set of installed PV system attributes and modeling functions. |
AOI modifiers¶
pvsystem.physicaliam (aoi[, n, K, L]) |
Determine the incidence angle modifier using refractive index, |
pvsystem.ashraeiam (aoi[, b]) |
Determine the incidence angle modifier using the ASHRAE transmission model. |
pvsystem.sapm_aoi_loss (aoi, module[, upper]) |
Calculates the SAPM angle of incidence loss coefficient, F2. |
Single diode model¶
Functions relevant for the single diode model.
pvsystem.singlediode (photocurrent, ...[, ...]) |
Solve the single-diode model to obtain a photovoltaic IV curve. |
pvsystem.calcparams_desoto (poa_global, ...) |
Applies the temperature and irradiance corrections to inputs for singlediode. |
pvsystem.v_from_i (resistance_shunt, ...) |
Calculates voltage from current per Eq 3 Jain and Kapoor 2004 [1]. |
pvsystem.i_from_v (resistance_shunt, ...) |
Calculates current from voltage per Eq 2 Jain and Kapoor 2004 [1]. |
SAPM model¶
Functions relevant for the SAPM model.
pvsystem.sapm (effective_irradiance, ...) |
The Sandia PV Array Performance Model (SAPM) generates 5 points on a PV module’s I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to SAND2004-3535. |
pvsystem.sapm_effective_irradiance (...[, ...]) |
Calculates the SAPM effective irradiance using the SAPM spectral loss and SAPM angle of incidence loss functions. |
pvsystem.sapm_celltemp (poa_global, ...[, model]) |
Estimate cell and module temperatures per the Sandia PV Array Performance Model (SAPM, SAND2004-3535), from the incident irradiance, wind speed, ambient temperature, and SAPM module parameters. |
pvsystem.sapm_spectral_loss (...) |
Calculates the SAPM spectral loss coefficient, F1. |
pvsystem.sapm_aoi_loss (aoi, module[, upper]) |
Calculates the SAPM angle of incidence loss coefficient, F2. |
pvsystem.snlinverter (v_dc, p_dc, inverter) |
Converts DC power and voltage to AC power using Sandia’s Grid-Connected PV Inverter model. |
PVWatts model¶
pvsystem.pvwatts_dc (g_poa_effective, ...[, ...]) |
Implements NREL’s PVWatts DC power model [R12]: |
pvsystem.pvwatts_ac (pdc, pdc0[, ...]) |
Implements NREL’s PVWatts inverter model [R13]. |
pvsystem.pvwatts_losses ([soiling, shading, ...]) |
Implements NREL’s PVWatts system loss model [R14]: |
Other¶
pvsystem.retrieve_sam ([name, path]) |
Retrieve latest module and inverter info from a local file or the SAM website. |
pvsystem.systemdef (meta, surface_tilt, ...) |
Generates a dict of system parameters used throughout a simulation. |
pvsystem.scale_voltage_current_power (data[, ...]) |
Scales the voltage, current, and power of the DataFrames returned by singlediode() and sapm() . |
Tracking¶
SingleAxisTracker¶
The SingleAxisTracker
inherits from
PVSystem
.
tracking.SingleAxisTracker ([axis_tilt, ...]) |
Inherits all of the PV modeling methods from PVSystem. |
tracking.SingleAxisTracker.singleaxis (...) |
|
tracking.SingleAxisTracker.get_irradiance (...) |
Uses the irradiance.total_irrad() function to calculate the plane of array irradiance components on a tilted surface defined by self.surface_tilt , self.surface_azimuth , and self.albedo . |
tracking.SingleAxisTracker.localize ([...]) |
Creates a LocalizedSingleAxisTracker object using this object and location data. |
tracking.LocalizedSingleAxisTracker ([...]) |
Highly experimental. |
Functions¶
tracking.singleaxis (apparent_zenith, ...[, ...]) |
Determine the rotation angle of a single axis tracker using the equations in [1] when given a particular sun zenith and azimuth angle. |
TMY¶
Methods and functions for reading data from TMY files.
location.Location.from_tmy (tmy_metadata[, ...]) |
Create an object based on a metadata dictionary from tmy2 or tmy3 data readers. |
tmy.readtmy2 (filename) |
Read a TMY2 file in to a DataFrame. |
tmy.readtmy3 ([filename, coerce_year, recolumn]) |
Read a TMY3 file in to a pandas dataframe. |
Forecasting¶
Forecast models¶
forecast.GFS |
|
forecast.NAM |
|
forecast.RAP |
|
forecast.HRRR |
|
forecast.HRRR_ESRL |
|
forecast.NDFD |
Getting data¶
forecast.ForecastModel.get_data |
|
forecast.ForecastModel.get_processed_data |
Processing data¶
forecast.ForecastModel.process_data |
|
forecast.ForecastModel.rename |
|
forecast.ForecastModel.cloud_cover_to_ghi_linear |
|
forecast.ForecastModel.cloud_cover_to_irradiance_clearsky_scaling |
|
forecast.ForecastModel.cloud_cover_to_transmittance_linear |
|
forecast.ForecastModel.cloud_cover_to_irradiance_liujordan |
|
forecast.ForecastModel.cloud_cover_to_irradiance |
|
forecast.ForecastModel.kelvin_to_celsius |
|
forecast.ForecastModel.isobaric_to_ambient_temperature |
|
forecast.ForecastModel.uv_to_speed |
|
forecast.ForecastModel.gust_to_speed |
IO support¶
These are public for now, but use at your own risk.
forecast.ForecastModel.set_dataset |
|
forecast.ForecastModel.set_query_latlon |
|
forecast.ForecastModel.set_location |
|
forecast.ForecastModel.set_time |
ModelChain¶
Creating a ModelChain object.
modelchain.ModelChain (system, location[, ...]) |
An experimental class that represents all of the modeling steps necessary for calculating power or energy for a PV system at a given location using the SAPM. |
Running¶
Running a ModelChain.
modelchain.ModelChain.run_model ([times, ...]) |
Run the model. |
modelchain.ModelChain.complete_irradiance ([...]) |
Determine the missing irradiation columns. |
modelchain.ModelChain.prepare_inputs ([...]) |
Prepare the solar position, irradiance, and weather inputs to the model. |
Attributes¶
Simple ModelChain attributes:
system, location, clearsky_model, transposition_model,
solar_position_method, airmass_model
Properties¶
ModelChain properties that are aliases for your specific modeling functions.
Model definitions¶
ModelChain model definitions.
Inference methods¶
Methods that automatically determine which models should be used based
on the information in the associated PVSystem
object.
Functions¶
Functions for power modeling.
modelchain.basic_chain (times, latitude, ...) |
An experimental function that computes all of the modeling steps necessary for calculating power or energy for a PV system at a given location. |
modelchain.get_orientation (strategy, **kwargs) |
Determine a PV system’s surface tilt and surface azimuth using a named strategy. |
Comparison with PVLIB MATLAB¶
PVLIB was originally developed as a library for MATLAB at Sandia National Lab, and Sandia remains the official maintainer of the MATLAB library. Sandia supported the initial Python port and then released further project maintenance and development to the pvlib-python maintainers.
The pvlib-python maintainers collaborate with the PVLIB MATLAB maintainers but operate independently. We’d all like to keep the core functionality of the Python and MATLAB projects synchronized, but this will require the efforts of the larger pvlib-python community, not just the maintainers. Therefore, do not expect feature parity between the libaries, only similarity.
The PV_LIB Matlab help webpage is a good reference for this comparison.
Missing functions¶
See pvlib-python GitHub issue #2 for a list of functions missing from the Python version of the library.
Major differences¶
pvlib-python uses git version control to track all changes to the code. A summary of changes is included in the whatsnew file for each release. PVLIB MATLAB documents changes in Changelog.docx
pvlib-python has a comprehensive test suite, whereas PVLIB MATLAB does not have a test suite at all. Specifically, pvlib-python
- Uses TravisCI for automated testing on Linux.
- Uses Appveyor for automated testing on Windows.
- Uses Coveralls to measure test coverage.
Using readthedocs for automated documentation building and hosting.
Removed
pvl_
from module/function names.Consolidated similar functions into topical modules. For example, functions from
pvl_clearsky_ineichen.m
andpvl_clearsky_haurwitz.m
have been consolidated intoclearsky.py
.PVLIB MATLAB uses
location
structs as the input to some functions. pvlib-python just uses the lat, lon, etc. as inputs to the functions. Furthermore, pvlib-python replaces the structs with classes, and these classes have methods, such asget_solarposition()
, that automatically reference the appropriate data. See Modeling paradigms for more information.pvlib-python implements a handful of class designed to simplify the PV modeling process. These include
Location
,PVSystem
,LocalizedPVSystem
,SingleAxisTracker
, andModelChain
.
Other differences¶
- Very few tests of input validitity exist in the Python code. We believe that the vast majority of these tests were not necessary. We also make use of Python’s robust support for raising and catching exceptions.
- Removed unnecessary and sometimes undesired behavior such as setting
maximum zenith=90 or airmass=0. Instead, we make extensive use of
nan
values in returned arrays. - Implemented the NREL solar position calculation algorithm. Also added a PyEphem option to solar position calculations.
- Specify time zones using a string from the standard IANA Time Zone Database naming conventions or using a pytz.timezone instead of an integer GMT offset.
clearsky.ineichen
supports interpolating monthly Linke Turbidities to daily resolution.- Instead of requiring effective irradiance as an input,
pvsystem.sapm
calculates and returns it based on input POA irradiance, AM, and AOI. - pvlib-python does not come with as much example data.
- pvlib-python does not currently implement as many algorithms as PVLIB MATLAB.
Documentation¶
- Using Sphinx to build the documentation, including dynamically created inline examples.
- Additional Jupyter tutorials in
/docs/tutorials
.
Variables and Symbols¶
There is a convention on consistent variable names throughout the library:
variable | description |
---|---|
tz | timezone |
latitude | latitude |
longitude | longitude |
dni | direct normal irradiance |
dni_extra | direct normal irradiance at top of atmosphere (extraterrestrial) |
dhi | diffuse horizontal irradiance |
ghi | global horizontal irradiance |
aoi | angle of incidence |
aoi_projection | cos(aoi) |
airmass | airmass |
airmass_relative | relative airmass |
airmass_absolute | absolute airmass |
poa_ground_diffuse | in plane ground reflected irradiation |
poa_direct | direct/beam irradiation in plane |
poa_diffuse | total diffuse irradiation in plane. sum of ground and sky diffuse. |
poa_global | global irradiation in plane. sum of diffuse and beam projection. |
poa_sky_diffuse | diffuse irradiation in plane from scattered light in the atmosphere (without ground reflected irradiation) |
g_poa_effective | broadband plane of array effective irradiance. |
surface_tilt | tilt angle of the surface |
surface_azimuth | azimuth angle of the surface |
solar_zenith | zenith angle of the sun in degrees |
apparent_zenith | refraction-corrected solar zenith angle in degrees |
solar_azimuth | azimuth angle of the sun in degrees East of North |
temp_cell | temperature of the cell |
temp_module | temperature of the module |
temp_air | temperature of the air |
temp_dew | dewpoint temperature |
relative_humidity | relative humidity |
v_mp, i_mp, p_mp | module voltage, current, power at the maximum power point |
v_oc | open circuit module voltage |
i_sc | short circuit module current |
i_x, i_xx | Sandia Array Performance Model IV curve parameters |
effective_irradiance | effective irradiance |
photocurrent | photocurrent |
saturation_current | diode saturation current |
resistance_series | series resistance |
resistance_shunt | shunt resistance |
transposition_factor | the gain ratio of the radiation on inclined plane to global horizontal irradiation: \(\frac{poa\_global}{ghi}\) |
pdc0 | nameplate DC rating |
pdc, dc | dc power |
gamma_pdc | module temperature coefficient. Typically in units of 1/C. |
pac, ac | ac powe. |
eta_inv | inverter efficiency |
eta_inv_ref | reference inverter efficiency |
eta_inv_nom | nominal inverter efficiency |
For a definition and further explanation on the variables, common symbols and units refer to the following sources:
IEC 61724:1998 – Photovoltaic system performance monitoring - Guidelines for measurement, data exchange and analysis ; the Indian Standard referencing the global IEC standard is available online: IS/IEC 61724 (1998)
Explanation of Solar irradiation and solar geometry by SoDa Service
Note
These further references might not use the same terminology as pvlib. But the physical process referred to is the same.