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 0x7fea500d0e10>
In [20]: plt.ylabel('Yearly energy yield (W hr)')
Out[20]: <matplotlib.text.Text at 0x7fea50002a50>

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 0x7fea50341310>
In [27]: plt.ylabel('Yearly energy yield (W hr)')
Out[27]: <matplotlib.text.Text at 0x7fea5042b690>

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 0x7fea505b6e50>
In [37]: plt.ylabel('Yearly energy yield (W hr)')
Out[37]: <matplotlib.text.Text at 0x7fea50729a50>

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 0x7fea50147c90>
In [44]: plt.ylabel('Yearly energy yield (W hr)')
Out[44]: <matplotlib.text.Text at 0x7fea50310e90>

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:
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/doc-reorg2/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 0x7fea5132aa10>
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/doc-reorg2/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)');

Modules¶
pvlib.atmosphere |
The atmosphere module contains methods to calculate relative and absolute airmass and to determine pressure from altitude or vice versa. |
pvlib.clearsky |
The clearsky module contains several methods to calculate clear sky GHI, DNI, and DHI. |
pvlib.forecast |
|
pvlib.irradiance |
The irradiance module contains functions for modeling global horizontal irradiance, direct normal irradiance, diffuse horizontal irradiance, and total irradiance under various conditions. |
pvlib.location |
This module contains the Location class. |
pvlib.modelchain |
The modelchain module contains functions and classes that combine many of the PV power modeling steps. |
pvlib.pvsystem |
The pvsystem module contains functions for modeling the output and performance of PV modules and inverters. |
pvlib.solarposition |
Calculate the solar position using a variety of methods/packages. |
pvlib.tmy |
Import functions for TMY2 and TMY3 data files. |
pvlib.tools |
Collection of functions used in pvlib_python |
pvlib.tracking |
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¶
-
class
pvlib.location.
Location
(latitude, longitude, tz='UTC', altitude=0, name=None, **kwargs)[source] Bases:
object
Location objects are convenient containers for latitude, longitude, timezone, and altitude data associated with a particular geographic location. You can also assign a name to a location object.
Location objects have two timezone attributes:
tz
is a IANA timezone string.pytz
is a pytz timezone object.
Location objects support the print method.
Parameters: latitude : float.
Positive is north of the equator. Use decimal degrees notation.
longitude : float.
Positive is east of the prime meridian. Use decimal degrees notation.
tz : str, int, float, or pytz.timezone.
See http://en.wikipedia.org/wiki/List_of_tz_database_time_zones for a list of valid time zones. pytz.timezone objects will be converted to strings. ints and floats must be in hours from UTC.
alitude : float.
Altitude from sea level in meters.
name : None or string.
Sets the name attribute of the Location object.
**kwargs
Arbitrary keyword arguments. Included for compatibility, but not used.
See also
pvsystem.PVSystem
-
classmethod
from_tmy
(tmy_metadata, tmy_data=None, **kwargs)[source] Create an object based on a metadata dictionary from tmy2 or tmy3 data readers.
Parameters: tmy_metadata : dict
Returned from tmy.readtmy2 or tmy.readtmy3
tmy_data : None or DataFrame
Optionally attach the TMY data to this object.
Returns: Location object (or the child class of Location that you
called this method from).
-
get_airmass
(times=None, solar_position=None, model='kastenyoung1989')[source] Calculate the relative and absolute airmass.
Automatically chooses zenith or apparant zenith depending on the selected model.
Parameters: times : None or DatetimeIndex
Only used if solar_position is not provided.
solar_position : None or DataFrame
DataFrame with with columns ‘apparent_zenith’, ‘zenith’.
model : str
Relative airmass model
Returns: airmass : DataFrame
Columns are ‘airmass_relative’, ‘airmass_absolute’
-
get_clearsky
(times, model='ineichen', solar_position=None, dni_extra=None, **kwargs)[source] Calculate the clear sky estimates of GHI, DNI, and/or DHI at this location.
Parameters: times: DatetimeIndex
model: str
The clear sky model to use. Must be one of ‘ineichen’, ‘haurwitz’, ‘simplified_solis’.
solar_position : None or DataFrame
DataFrame with with columns ‘apparent_zenith’, ‘zenith’, ‘apparent_elevation’.
dni_extra: None or numeric
If None, will be calculated from times.
kwargs passed to the relevant functions. Climatological values
are assumed in many cases. See source code for details!
Returns: clearsky : DataFrame
Column names are:
ghi, dni, dhi
.
-
get_solarposition
(times, pressure=None, temperature=12, **kwargs)[source] Uses the
solarposition.get_solarposition()
function to calculate the solar zenith, azimuth, etc. at this location.Parameters: times : DatetimeIndex
pressure : None, float, or array-like
If None, pressure will be calculated using
atmosphere.alt2pres()
andself.altitude
.temperature : None, float, or array-like
kwargs passed to :py:func:`solarposition.get_solarposition`
Returns: solar_position : DataFrame
Columns depend on the
method
kwarg, but always includezenith
andazimuth
.
PVSystem¶
-
class
pvlib.pvsystem.
PVSystem
(surface_tilt=0, surface_azimuth=180, albedo=None, surface_type=None, module=None, module_parameters=None, modules_per_string=1, strings_per_inverter=1, inverter=None, inverter_parameters=None, racking_model='open_rack_cell_glassback', **kwargs)[source] Bases:
object
The PVSystem class defines a standard set of PV system attributes and modeling functions. This class describes the collection and interactions of PV system components rather than an installed system on the ground. It is typically used in combination with
Location
andModelChain
objects.See the
LocalizedPVSystem
class for an object model that describes an installed PV system.The class supports basic system topologies consisting of:
- N total modules arranged in series (modules_per_string=N, strings_per_inverter=1).
- M total modules arranged in parallel (modules_per_string=1, strings_per_inverter=M).
- NxM total modules arranged in M strings of N modules each (modules_per_string=N, strings_per_inverter=M).
The class is complementary to the module-level functions.
The attributes should generally be things that don’t change about the system, such the type of module and the inverter. The instance methods accept arguments for things that do change, such as irradiance and temperature.
Parameters: surface_tilt: float or array-like
Tilt angle of the module surface. Up=0, horizon=90.
surface_azimuth: float or array-like
Azimuth angle of the module surface. North=0, East=90, South=180, West=270.
albedo : None, float
The ground albedo. If
None
, will attempt to usesurface_type
andirradiance.SURFACE_ALBEDOS
to lookup albedo.surface_type : None, string
The ground surface type. See
irradiance.SURFACE_ALBEDOS
for valid values.module : None, string
The model name of the modules. May be used to look up the module_parameters dictionary via some other method.
module_parameters : None, dict or Series
Module parameters as defined by the SAPM, CEC, or other.
modules_per_string: int or float
See system topology discussion above.
strings_per_inverter: int or float
See system topology discussion above.
inverter : None, string
The model name of the inverters. May be used to look up the inverter_parameters dictionary via some other method.
inverter_parameters : None, dict or Series
Inverter parameters as defined by the SAPM, CEC, or other.
racking_model : None or string
Used for cell and module temperature calculations.
**kwargs
Arbitrary keyword arguments. Included for compatibility, but not used.
See also
pvlib.location.Location
,pvlib.tracking.SingleAxisTracker
,pvlib.pvsystem.LocalizedPVSystem
-
ashraeiam
(aoi)[source] Determine the incidence angle modifier using
self.module_parameters['b']
,aoi
, and theashraeiam()
function.Uses default arguments if keys not in module_parameters.
Parameters: aoi : numeric
The angle of incidence in degrees.
Returns: modifier : numeric
The AOI modifier.
-
calcparams_desoto
(poa_global, temp_cell, **kwargs)[source] Use the
calcparams_desoto()
function, the input parameters andself.module_parameters
to calculate the module currents and resistances.Parameters: poa_global : float or Series
The irradiance (in W/m^2) absorbed by the module.
temp_cell : float or Series
The average cell temperature of cells within a module in C.
**kwargs
See pvsystem.calcparams_desoto for details
Returns: See pvsystem.calcparams_desoto for details
-
get_aoi
(solar_zenith, solar_azimuth)[source] Get the angle of incidence on the system.
Parameters: solar_zenith : float or Series.
Solar zenith angle.
solar_azimuth : float or Series.
Solar azimuth angle.
Returns: aoi : Series
The angle of incidence
-
get_irradiance
(solar_zenith, solar_azimuth, dni, ghi, dhi, dni_extra=None, airmass=None, model='haydavies', **kwargs)[source] Uses the
irradiance.total_irrad()
function to calculate the plane of array irradiance components on a tilted surface defined byself.surface_tilt
,self.surface_azimuth
, andself.albedo
.Parameters: solar_zenith : float or Series.
Solar zenith angle.
solar_azimuth : float or Series.
Solar azimuth angle.
dni : float or Series
Direct Normal Irradiance
ghi : float or Series
Global horizontal irradiance
dhi : float or Series
Diffuse horizontal irradiance
dni_extra : float or Series
Extraterrestrial direct normal irradiance
airmass : float or Series
Airmass
model : String
Irradiance model.
**kwargs
Passed to
irradiance.total_irrad()
.Returns: poa_irradiance : DataFrame
Column names are:
total, beam, sky, ground
.
-
i_from_v
(resistance_shunt, resistance_series, nNsVth, voltage, saturation_current, photocurrent)[source] Wrapper around the
i_from_v()
function.Parameters: See pvsystem.i_from_v for details Returns: See pvsystem.i_from_v for details
-
localize
(location=None, latitude=None, longitude=None, **kwargs)[source] Creates a LocalizedPVSystem object using this object and location data. Must supply either location object or latitude, longitude, and any location kwargs
Parameters: location : None or Location
latitude : None or float
longitude : None or float
**kwargs : see Location
Returns: localized_system : LocalizedPVSystem
-
physicaliam
(aoi)[source] Determine the incidence angle modifier using
aoi
,self.module_parameters['K']
,self.module_parameters['L']
,self.module_parameters['n']
, and thephysicaliam()
function.Uses default arguments if keys not in module_parameters.
Parameters: aoi : numeric
The angle of incidence in degrees.
Returns: modifier : numeric
The AOI modifier.
-
pvwatts_ac
(pdc)[source] Calculates AC power according to the PVWatts model using
pvwatts_ac()
, self.module_parameters[‘pdc0’], and eta_inv_nom=self.inverter_parameters[‘eta_inv_nom’].See
pvwatts_ac()
for details.
-
pvwatts_dc
(g_poa_effective, temp_cell)[source] Calcuates DC power according to the PVWatts model using
pvwatts_dc()
, self.module_parameters[‘pdc0’], and self.module_parameters[‘gamma_pdc’].See
pvwatts_dc()
for details.
-
pvwatts_losses
(**kwargs)[source] Calculates DC power losses according the PVwatts model using
pvwatts_losses()
. No attributes are used in this calculation, but all keyword arguments will be passed to the function.See
pvwatts_losses()
for details.
-
sapm
(effective_irradiance, temp_cell, **kwargs)[source] Use the
sapm()
function, the input parameters, andself.module_parameters
to calculate Voc, Isc, Ix, Ixx, Vmp/Imp.Parameters: poa_direct : Series
The direct irradiance incident upon the module (W/m^2).
poa_diffuse : Series
The diffuse irradiance incident on module.
temp_cell : Series
The cell temperature (degrees C).
airmass_absolute : Series
Absolute airmass.
aoi : Series
Angle of incidence (degrees).
**kwargs
See pvsystem.sapm for details
Returns: See pvsystem.sapm for details
-
sapm_aoi_loss
(aoi)[source] Use the
sapm_aoi_loss()
function, the input parameters, andself.module_parameters
to calculate F2.Parameters: aoi : numeric
Angle of incidence in degrees.
Returns: F2 : numeric
The SAPM angle of incidence loss coefficient.
-
sapm_celltemp
(irrad, wind, temp)[source] Uses
sapm_celltemp()
to calculate module and cell temperatures based onself.racking_model
and the input parameters.Parameters: See pvsystem.sapm_celltemp for details Returns: See pvsystem.sapm_celltemp for details
-
sapm_effective_irradiance
(poa_direct, poa_diffuse, airmass_absolute, aoi, reference_irradiance=1000)[source] Use the
sapm_effective_irradiance()
function, the input parameters, andself.module_parameters
to calculate effective irradiance.Parameters: poa_direct : numeric
The direct irradiance incident upon the module.
poa_diffuse : numeric
The diffuse irradiance incident on module.
airmass_absolute : numeric
Absolute airmass.
aoi : numeric
Angle of incidence in degrees.
reference_irradiance : numeric
Reference irradiance by which to divide the input irradiance.
Returns: effective_irradiance : numeric
The SAPM effective irradiance.
-
sapm_spectral_loss
(airmass_absolute)[source] Use the
sapm_spectral_loss()
function, the input parameters, andself.module_parameters
to calculate F1.Parameters: airmass_absolute : numeric
Absolute airmass.
Returns: F1 : numeric
The SAPM spectral loss coefficient.
-
scale_voltage_current_power
(data)[source] Scales the voltage, current, and power of the DataFrames returned by
singlediode()
andsapm()
by self.modules_per_string and self.strings_per_inverter.Parameters: data: DataFrame
Must contain columns ‘v_mp’, ‘v_oc’, ‘i_mp’ ,’i_x’, ‘i_xx’, ‘i_sc’, ‘p_mp’.
Returns: scaled_data: DataFrame
A scaled copy of the input data.
-
singlediode
(photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth, ivcurve_pnts=None)[source] Wrapper around the
singlediode()
function.Parameters: See pvsystem.singlediode for details Returns: See pvsystem.singlediode for details
-
snlinverter
(v_dc, p_dc)[source] Uses
snlinverter()
to calculate AC power based onself.inverter_parameters
and the input parameters.Parameters: See pvsystem.snlinverter for details Returns: See pvsystem.snlinverter for details
ModelChain¶
-
class
pvlib.modelchain.
ModelChain
(system, location, orientation_strategy='south_at_latitude_tilt', clearsky_model='ineichen', transposition_model='haydavies', solar_position_method='nrel_numpy', airmass_model='kastenyoung1989', dc_model=None, ac_model=None, aoi_model=None, spectral_model=None, temp_model='sapm', losses_model='no_loss', **kwargs)[source] Bases:
object
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.
CEC module specifications and the single diode model are not yet supported.
Parameters: system : PVSystem
A
PVSystem
object that represents the connected set of modules, inverters, etc.location : Location
A
Location
object that represents the physical location at which to evaluate the model.orientation_strategy : None or str
The strategy for aligning the modules. If not None, sets the
surface_azimuth
andsurface_tilt
properties of thesystem
. Allowed strategies include ‘flat’, ‘south_at_latitude_tilt’. Ignored for SingleAxisTracker systems.clearsky_model : str
Passed to location.get_clearsky.
transposition_model : str
Passed to system.get_irradiance.
solar_position_method : str
Passed to location.get_solarposition.
airmass_model : str
Passed to location.get_airmass.
dc_model: None, str, or function
If None, the model will be inferred from the contents of system.module_parameters. Valid strings are ‘sapm’, ‘singlediode’, ‘pvwatts’. The ModelChain instance will be passed as the first argument to a user-defined function.
ac_model: None, str, or function
If None, the model will be inferred from the contents of system.inverter_parameters and system.module_parameters. Valid strings are ‘snlinverter’, ‘adrinverter’ (not implemented), ‘pvwatts’. The ModelChain instance will be passed as the first argument to a user-defined function.
aoi_model: None, str, or function
If None, the model will be inferred from the contents of system.module_parameters. Valid strings are ‘physical’, ‘ashrae’, ‘sapm’, ‘no_loss’. The ModelChain instance will be passed as the first argument to a user-defined function.
spectral_model: None, str, or function
If None, the model will be inferred from the contents of system.module_parameters. Valid strings are ‘sapm’, ‘first_solar’ (not implemented), ‘no_loss’. The ModelChain instance will be passed as the first argument to a user-defined function.
temp_model: str or function
Valid strings are ‘sapm’. The ModelChain instance will be passed as the first argument to a user-defined function.
losses_model: str or function
Valid strings are ‘pvwatts’, ‘no_loss’. The ModelChain instance will be passed as the first argument to a user-defined function.
**kwargs
Arbitrary keyword arguments. Included for compatibility, but not used.
-
ac_model
-
adrinverter
()[source]
-
aoi_model
-
ashrae_aoi_loss
()[source]
-
complete_irradiance
(times=None, weather=None)[source] Determine the missing irradiation columns. Only two of the following data columns (dni, ghi, dhi) are needed to calculate the missing data.
This function is not safe at the moment. Results can be too high or negative. Please contribute and help to improve this function on https://github.com/pvlib/pvlib-python
Parameters: times : DatetimeIndex
Times at which to evaluate the model. Can be None if attribute times is already set.
weather : pandas.DataFrame
Table with at least two columns containing one of the following data sets: dni, dhi, ghi. Can be None if attribute weather is already set.
Returns: self
Assigns attributes: times, weather
Examples
This example does not work until the parameters my_system, my_location, my_datetime and my_weather are not defined properly but shows the basic idea how this method can be used.
>>> from pvlib.modelchain import ModelChain
>>> # my_weather containing 'dhi' and 'ghi'. >>> mc = ModelChain(my_system, my_location) >>> mc.complete_irradiance(my_datetime, my_weather) >>> mc.run_model()
>>> # my_weather containing 'dhi', 'ghi' and 'dni'. >>> mc = ModelChain(my_system, my_location) >>> mc.run_model(my_datetime, my_weather)
-
dc_model
-
effective_irradiance_model
()[source]
-
first_solar_spectral_loss
()[source]
-
infer_ac_model
()[source]
-
infer_aoi_model
()[source]
-
infer_dc_model
()[source]
-
infer_losses_model
()[source]
-
infer_spectral_model
()[source]
-
infer_temp_model
()[source]
-
losses_model
-
no_aoi_loss
()[source]
-
no_extra_losses
()[source]
-
no_spectral_loss
()[source]
-
orientation_strategy
-
physical_aoi_loss
()[source]
-
prepare_inputs
(times=None, irradiance=None, weather=None)[source] Prepare the solar position, irradiance, and weather inputs to the model.
Parameters: times : DatetimeIndex
Times at which to evaluate the model. Can be None if attribute times is already set.
irradiance : None or DataFrame
This parameter is deprecated. Please use weather instead.
weather : None or DataFrame
If None, the weather attribute is used. If the weather attribute is also None assumes air temperature is 20 C, wind speed is 0 m/s and irradiation calculated from clear sky data. Column names must be ‘wind_speed’, ‘temp_air’, ‘dni’, ‘ghi’, ‘dhi’. Do not pass incomplete irradiation data. Use method
complete_irradiance()
instead.Returns: self
Assigns attributes: times, solar_position, airmass, total_irrad, aoi
-
pvwatts_dc
()[source]
-
pvwatts_inverter
()[source]
-
pvwatts_losses
()[source]
-
run_model
(times=None, irradiance=None, weather=None)[source] Run the model.
Parameters: times : DatetimeIndex
Times at which to evaluate the model. Can be None if attribute times is already set.
irradiance : None or DataFrame
This parameter is deprecated. Please use weather instead.
weather : None or DataFrame
If None, assumes air temperature is 20 C, wind speed is 0 m/s and irradiation calculated from clear sky data. Column names must be ‘wind_speed’, ‘temp_air’, ‘dni’, ‘ghi’, ‘dhi’. Do not pass incomplete irradiation data. Use method
complete_irradiance()
instead.Returns: self
Assigns attributes: times, solar_position, airmass, irradiance,
total_irrad, effective_irradiance, weather, temps, aoi,
aoi_modifier, spectral_modifier, dc, ac, losses.
-
sapm
()[source]
-
sapm_aoi_loss
()[source]
-
sapm_spectral_loss
()[source]
-
sapm_temp
()[source]
-
singlediode
()[source]
-
snlinverter
()[source]
-
spectral_model
-
temp_model
-
LocalizedPVSystem¶
-
class
pvlib.pvsystem.
LocalizedPVSystem
(pvsystem=None, location=None, **kwargs)[source] Bases:
pvlib.pvsystem.PVSystem
,pvlib.location.Location
The LocalizedPVSystem class defines a standard set of installed PV system attributes and modeling functions. This class combines the attributes and methods of the PVSystem and Location classes.
See the
PVSystem
class for an object model that describes an unlocalized PV system.
SingleAxisTracker¶
-
class
pvlib.tracking.
SingleAxisTracker
(axis_tilt=0, axis_azimuth=0, max_angle=90, backtrack=True, gcr=0.2857142857142857, **kwargs)[source] Bases:
pvlib.pvsystem.PVSystem
Inherits all of the PV modeling methods from PVSystem.
-
get_irradiance
(dni, ghi, dhi, dni_extra=None, airmass=None, model='haydavies', **kwargs)[source] Uses the
irradiance.total_irrad()
function to calculate the plane of array irradiance components on a tilted surface defined byself.surface_tilt
,self.surface_azimuth
, andself.albedo
.Parameters: solar_zenith : float or Series.
Solar zenith angle.
solar_azimuth : float or Series.
Solar azimuth angle.
dni : float or Series
Direct Normal Irradiance
ghi : float or Series
Global horizontal irradiance
dhi : float or Series
Diffuse horizontal irradiance
dni_extra : float or Series
Extraterrestrial direct normal irradiance
airmass : float or Series
Airmass
model : String
Irradiance model.
**kwargs
Passed to
irradiance.total_irrad()
.Returns: poa_irradiance : DataFrame
Column names are:
total, beam, sky, ground
.
-
localize
(location=None, latitude=None, longitude=None, **kwargs)[source] Creates a
LocalizedSingleAxisTracker
object using this object and location data. Must supply either location object or latitude, longitude, and any location kwargsParameters: location : None or Location
latitude : None or float
longitude : None or float
**kwargs : see Location
Returns: localized_system : LocalizedSingleAxisTracker
-
singleaxis
(apparent_zenith, apparent_azimuth)[source]
-
LocalizedSingleAxisTracker¶
-
class
pvlib.tracking.
LocalizedSingleAxisTracker
(pvsystem=None, location=None, **kwargs)[source] Bases:
pvlib.tracking.SingleAxisTracker
,pvlib.location.Location
Highly experimental.
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.