
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:
William F. Holmgren, Clifford W. Hansen, and Mark A. Mikofski. “pvlib python: a python package for modeling solar energy systems.” Journal of Open Source Software, 3(29), 884, (2018). https://doi.org/10.21105/joss.00884
Please also cite the DOI corresponding to the specific version of pvlib python that you used. pvlib python DOIs are listed at Zenodo.org
Additional pvlib python publications include:
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.
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: functions, the Location/PVSystem classes, and the ModelChain class. Read more about this in the Intro Examples section.
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¶
pvlib usage questions can be asked on Stack Overflow and tagged with the pvlib tag.
The pvlib-python google group is used for discussing various topics of interest to the pvlib-python community. We also make new version announcements on the google group.
If you suspect that you may have discovered a bug or if you’d like to change something about pvlib, then please make an issue on our GitHub issues page .
How do I contribute?¶
We’re so glad you asked! Please see Contributing 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 Department of Energy’s Energy Efficiency and Renewable Energy Postdoctoral Fellowship Program (2014-2016), the University of Arizona Institute for Energy Solutions (2017-2018), and the DOE Solar Forecasting 2 program (2018). 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.
Intro Examples¶
This page contains introductory examples of pvlib python usage.
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
In [3]: naive_times = pd.date_range(start='2015', end='2016', freq='1h')
# very approximate
# latitude, longitude, name, altitude, timezone
In [4]: 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 [5]: import pvlib
# get the module and inverter specifications from SAM
In [6]: sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
In [7]: sapm_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
In [8]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [9]: inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2896 try:
-> 2897 return self._engine.get_loc(key)
2898 except KeyError:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'
During handling of the above exception, another exception occurred:
KeyError Traceback (most recent call last)
<ipython-input-9-cb0c5fa8a3e2> in <module>
----> 1 inverter = sapm_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
2978 if self.columns.nlevels > 1:
2979 return self._getitem_multilevel(key)
-> 2980 indexer = self.columns.get_loc(key)
2981 if is_integer(indexer):
2982 indexer = [indexer]
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2897 return self._engine.get_loc(key)
2898 except KeyError:
-> 2899 return self._engine.get_loc(self._maybe_cast_indexer(key))
2900 indexer = self.get_indexer([key], method=method, tolerance=tolerance)
2901 if indexer.ndim > 1 or indexer.size > 1:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'
In [10]: temperature_model_parameters = pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
# specify constant ambient air temp and wind for simplicity
In [11]: temp_air = 20
In [12]: 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 [13]: system = {'module': module, 'inverter': inverter, ....: 'surface_azimuth': 180} ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-13-8f44442cad28> in <module> ----> 1 system = {'module': module, 'inverter': inverter, 2 'surface_azimuth': 180} NameError: name 'inverter' is not defined In [14]: energies = {} In [15]: 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.get_extra_radiation(times) ....: airmass = pvlib.atmosphere.get_relative_airmass(solpos['apparent_zenith']) ....: pressure = pvlib.atmosphere.alt2pres(altitude) ....: am_abs = pvlib.atmosphere.get_absolute_airmass(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.get_total_irradiance(system['surface_tilt'], ....: system['surface_azimuth'], ....: solpos['apparent_zenith'], ....: solpos['azimuth'], ....: cs['dni'], cs['ghi'], cs['dhi'], ....: dni_extra=dni_extra, ....: model='haydavies') ....: tcell = pvlib.temperature.sapm_cell(total_irrad['poa_global'], ....: temp_air, wind_speed, ....: **temperature_model_parameters) ....: 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, tcell, module) ....: ac = pvlib.pvsystem.snlinverter(dc['v_mp'], dc['p_mp'], inverter) ....: annual_energy = ac.sum() ....: energies[name] = annual_energy ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-15-aa55f107abd4> in <module> 1 for latitude, longitude, name, altitude, timezone in coordinates: 2 times = naive_times.tz_localize(timezone) ----> 3 system['surface_tilt'] = latitude 4 solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude) 5 dni_extra = pvlib.irradiance.get_extra_radiation(times) NameError: name 'system' is not defined In [16]: energies = pd.Series(energies) # based on the parameters specified above, these are in W*hrs In [17]: print(energies.round(0)) Series([], dtype: float64) In [18]: energies.plot(kind='bar', rot=0) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-18-ce643011a4ea> in <module> ----> 1 energies.plot(kind='bar', rot=0) ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 792 data.columns = label_name 793 --> 794 return plot_backend.plot(data, kind=kind, **kwargs) 795 796 def line(self, x=None, y=None, **kwargs): ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 60 kwargs["ax"] = getattr(ax, "left_ax", ax) 61 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 62 plot_obj.generate() 63 plot_obj.draw() 64 return plot_obj.result ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 277 def generate(self): 278 self._args_adjust() --> 279 self._compute_plot_data() 280 self._setup_subplots() 281 self._make_plot() ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self) 412 # no non-numeric frames or series allowed 413 if is_empty: --> 414 raise TypeError("no numeric data to plot") 415 416 # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to TypeError: no numeric data to plot In [19]: plt.ylabel('Yearly energy yield (W hr)') Out[19]: Text(0, 0.5, 'Yearly energy yield (W hr)')

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. pvlib-python
also includes a SingleAxisTracker
class that
is a subclass of PVSystem
.
The following code demonstrates how to use
Location
,
PVSystem
, and
ModelChain
objects to accomplish our
system modeling goal. ModelChain objects provide convenience methods
that can provide default selections for models and can also fill
necessary input with modeled data. For example, no air temperature
or wind speed data is provided in the input weather DataFrame,
so the ModelChain object defaults to 20 C and 0 m/s. Also, no irradiance
transposition model is specified (keyword argument transposition for
ModelChain) so the ModelChain defaults to the haydavies model. In this
example, ModelChain infers the DC power model from the module provided
by examining the parameters defined for the module.
In [20]: from pvlib.pvsystem import PVSystem In [21]: from pvlib.location import Location In [22]: from pvlib.modelchain import ModelChain In [23]: system = PVSystem(module_parameters=module, ....: inverter_parameters=inverter, ....: temperature_model_parameters=temperature_model_parameters) ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-23-8644c4c6fde8> in <module> 1 system = PVSystem(module_parameters=module, ----> 2 inverter_parameters=inverter, 3 temperature_model_parameters=temperature_model_parameters) NameError: name 'inverter' is not defined In [24]: energies = {} In [25]: for latitude, longitude, name, altitude, timezone in coordinates: ....: times = naive_times.tz_localize(timezone) ....: location = Location(latitude, longitude, name=name, altitude=altitude, ....: tz=timezone) ....: weather = location.get_clearsky(times) ....: mc = ModelChain(system, location, ....: orientation_strategy='south_at_latitude_tilt') ....: mc.run_model(times=times, weather=weather) ....: annual_energy = mc.ac.sum() ....: energies[name] = annual_energy ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-25-09581f5c3eaf> in <module> 4 tz=timezone) 5 weather = location.get_clearsky(times) ----> 6 mc = ModelChain(system, location, 7 orientation_strategy='south_at_latitude_tilt') 8 mc.run_model(times=times, weather=weather) NameError: name 'system' is not defined In [26]: energies = pd.Series(energies) # based on the parameters specified above, these are in W*hrs In [27]: print(energies.round(0)) Series([], dtype: float64) In [28]: energies.plot(kind='bar', rot=0) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-28-ce643011a4ea> in <module> ----> 1 energies.plot(kind='bar', rot=0) ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 792 data.columns = label_name 793 --> 794 return plot_backend.plot(data, kind=kind, **kwargs) 795 796 def line(self, x=None, y=None, **kwargs): ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 60 kwargs["ax"] = getattr(ax, "left_ax", ax) 61 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 62 plot_obj.generate() 63 plot_obj.draw() 64 return plot_obj.result ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 277 def generate(self): 278 self._args_adjust() --> 279 self._compute_plot_data() 280 self._setup_subplots() 281 self._make_plot() ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self) 412 # no non-numeric frames or series allowed 413 if is_empty: --> 414 raise TypeError("no numeric data to plot") 415 416 # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to TypeError: no numeric data to plot In [29]: plt.ylabel('Yearly energy yield (W hr)') Out[29]: Text(0, 0.5, 'Yearly energy yield (W hr)')

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 LocalizedPVSystem
inherits from both
PVSystem
and
Location
, while the
LocalizedSingleAxisTracker
inherits from
SingleAxisTracker
(itself a subclass of
PVSystem
) and
Location
. The
LocalizedPVSystem
and
LocalizedSingleAxisTracker
classes may
contain bugs due to the relative difficulty of implementing multiple
inheritance. The LocalizedPVSystem
and
LocalizedSingleAxisTracker
may be deprecated
in a future release. We recommend that most modeling workflows implement
Location
,
PVSystem
, and
ModelChain
.
The following code demonstrates how to use a
LocalizedPVSystem
object to accomplish our
modeling goal:
In [30]: from pvlib.pvsystem import LocalizedPVSystem In [31]: energies = {} In [32]: for latitude, longitude, name, altitude, timezone in coordinates: ....: localized_system = LocalizedPVSystem(module_parameters=module, ....: inverter_parameters=inverter, ....: temperature_model_parameters=temperature_model_parameters, ....: 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']) ....: tcell = localized_system.sapm_celltemp(total_irrad['poa_global'], ....: temp_air, wind_speed) ....: 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, tcell) ....: ac = localized_system.snlinverter(dc['v_mp'], dc['p_mp']) ....: annual_energy = ac.sum() ....: energies[name] = annual_energy ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-32-5b001ffad2db> in <module> 1 for latitude, longitude, name, altitude, timezone in coordinates: 2 localized_system = LocalizedPVSystem(module_parameters=module, ----> 3 inverter_parameters=inverter, 4 temperature_model_parameters=temperature_model_parameters, 5 surface_tilt=latitude, NameError: name 'inverter' is not defined In [33]: energies = pd.Series(energies) # based on the parameters specified above, these are in W*hrs In [34]: print(energies.round(0)) Series([], dtype: float64) In [35]: energies.plot(kind='bar', rot=0) --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-35-ce643011a4ea> in <module> ----> 1 energies.plot(kind='bar', rot=0) ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_core.py in __call__(self, *args, **kwargs) 792 data.columns = label_name 793 --> 794 return plot_backend.plot(data, kind=kind, **kwargs) 795 796 def line(self, x=None, y=None, **kwargs): ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/__init__.py in plot(data, kind, **kwargs) 60 kwargs["ax"] = getattr(ax, "left_ax", ax) 61 plot_obj = PLOT_CLASSES[kind](data, **kwargs) ---> 62 plot_obj.generate() 63 plot_obj.draw() 64 return plot_obj.result ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in generate(self) 277 def generate(self): 278 self._args_adjust() --> 279 self._compute_plot_data() 280 self._setup_subplots() 281 self._make_plot() ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self) 412 # no non-numeric frames or series allowed 413 if is_empty: --> 414 raise TypeError("no numeric data to plot") 415 416 # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to TypeError: no numeric data to plot In [36]: plt.ylabel('Yearly energy yield (W hr)') Out[36]: Text(0, 0.5, 'Yearly energy yield (W hr)')

What’s New¶
These are new features and improvements of note in each release.
v0.7.0 (MONTH DAY, YEAR)¶
This is a major release that drops support for Python 2 and Python 3.4. We recommend all users of v0.6.3 upgrade to this release after checking API compatibility notes.
Python 2.7 support ended on June 1, 2019. (GH501) Minimum numpy version is now 1.10.4. Minimum pandas version is now 0.18.1.
API Changes¶
- Changes related to cell temperature models (GH678):
- Changes to functions
Moved functions for cell temperature from pvsystem.py to temperature.py.
Renamed pvsystem.sapm_celltemp and pvsystem.pvsyst_celltemp to temperature.sapm_cell and temperature.pvsyst_cell.
- temperature.sapm_cell returns only the cell temperature, whereas the
old pvsystem.sapm_celltemp returned a DataFrame with both cell and module temperatures.
Created temperature.sapm_module to return module temperature using the SAPM temperature model.
Changed the order of arguments for`pvsystem.sapm_celltemp`, pvsystem.pvsyst_celltemp and PVSystem.sapm_celltemp to be consistent among cell temperature model functions.
Removed model as a kwarg from temperature.sapm_cell and temperature.pvsyst_cell. These functions now require model-specific parameters.
Added the argument irrad_ref, default value 1000, to temperature.sapm_cell.
- Changes to named temperature model parameter sets
Renamed pvsystem.TEMP_MODEL_PARAMS to temperature.TEMPERATURE_MODEL_PARAMETERS.
temperature.TEMPERATURE_MODEL_PARAMETERS uses dict rather than tuple for a parameter set.
Names for parameter sets in temperature.TEMPERATURE_MODEL_PARAMETERS have changed.
Parameter sets for the SAPM cell temperature model named ‘open_rack_polymer_thinfilm_steel’ and ‘22x_concentrator_tracker’ are considered obsolete and have been removed.
- Changes to PVSystem class
Changed the model kwarg in PVSystem.sapm_celltemp and PVSystem.pvsyst_celltemp to parameter_set. parameter_set expects a str which is a valid key for temperature.TEMPERATURE_MODEL_PARAMETERS for the corresponding temperature model.
Added an attribute PVSystem.module_type (str) to record module front and back materials, default is glass_polymer.
Changed meaning of PVSystem.racking_model to describe racking only, e.g., default is open_rack.
Added an attribute PVSystem.temperature_model_parameters (dict). to contain temperature model parameters.
If PVSystem.temperature_model_parameters is not specified and PVSystem.racking_model and PVSystem.module_type combine to a valid parameter set name for the SAPM cell temperature model, that parameter set is assigned to PVSystem.temperature_model_parameters. Otherwise PVSystem.temperature_model_parameters is assigned an empty dict. The result is that the default parameter set for SAPM cell temperature model is open_rack_glass_polymer; the old default was open_rack_glass_glass.
- Changes to ModelChain class
ModelChain.temp_model renamed to ModelChain.temperature_model.
ModelChain.temperature_model now defaults to None. The temperature model can be inferred from PVSystem.temperature_model_parameters.
ModelChain.temperature_model_parameters now defaults to None. The temperature model can be inferred from PVSystem.temperature_model_parameters.
- ModelChain.temps attribute renamed to ModelChain.cell_temperature,
and its datatype is now numeric rather than DataFrame.
If PVSystem.temperature_model_parameters is not specified, ModelChain defaults to old behavior, using the SAPM temperature model with parameter set open_rack_glass_glass. This behavior is deprecated, and will be
- removed in v0.8. In v0.8 PVSystem.temperature_model_parameters will
be required for ModelChain.
Implemented pvsyst as an option for ModelChain.temperature_model.
modelchain.basic_chain has a new required argument temperature_model_parameters.
Calling
pvlib.pvsystem.retrieve_sam()
with no parameters will raise an exception instead of displaying a dialog.The times keyword argument has been deprecated in the
pvlib.modelchain.ModelChain.run_model()
,pvlib.modelchain.ModelChain.prepare_inputs()
, andpvlib.modelchain.ModelChain.complete_irradiance()
methods. Model times are now determined by the input weather. DataFrame. Therefore, the weather DataFrame must have a DatetimeIndex. The weather argument of the above methods is now the first, required positional argument and the times argument is kept as the second keyword argument for capability during the deprecation period.
Enhancements¶
Created two new incidence angle modifier functions:
pvlib.pvsystem.iam_martin_ruiz()
andpvlib.pvsystem.iam_interp()
. (GH751)Updated the file for module parameters for the CEC model, from the SAM file dated 2017-6-5 to the SAM file dated 2019-03-05. (GH761)
Updated the file for inverter parameters for the CEC model, from the SAM file dated 2018-3-18 to the SAM file dated 2019-03-05. (GH761)
Added recombination current parameters to bishop88 single-diode functions and also to
pvlib.pvsystem.max_power_point()
. (GH762)Add ivtools module to contain functions for IV model fitting.
Add
fit_sde_sandia()
, a simple method to fit the single diode equation to an IV curve.Add
fit_sdm_cec_sam()
, a wrapper for the CEC single diode model fitting function ‘6parsolve’ from NREL’s System Advisor Model.Add
fit_sdm_desoto()
, a method to fit the single diode equation to the typical specifications given in manufacturers datasheets.
Bug fixes¶
Fix handling of keyword arguments in forecasts.get_processed_data. (GH745)
Fix output as Series feature in
pvlib.pvsystem.ashraeiam()
.Fix rounding issue in clearsky._linearly_scale, a function that converts longitude or latitude degree to an index number in a Linke turbidity lookup table. Also rename the function to clearsky._degrees_to_index. (GH754)
Testing¶
Added 30 minutes to timestamps in test_psm3.csv to match change in NSRDB (GH733)
Added tests for methods in bifacial.py.
Added tests for changes to cell temperature models.
Documentation¶
Corrected docstring for pvsystem.PVSystem.sapm
Removal of prior version deprecations¶
Removed irradiance.extraradiation.
Removed irradiance.grounddiffuse.
Removed irradiance.total_irrad.
Removed irradiance.globalinplane.
Removed atmosphere.relativeairmass.
Removed atmosphere.relativeairmass.
Removed solarposition.get_sun_rise_set_transit.
Removed tmy module.
Removed ModelChain.singlediode method.
Removed ModelChain.prepare_inputs clearsky assumption when no irradiance data was provided.
Enhancements¶
Add timeout to
pvlib.iotools.get_psm3()
.
Contributors¶
Mark Campanellli (@markcampanelli)
Will Holmgren (@wholmgren)
Cliff Hansen (@cwhanse)
Oscar Dowson (@odow)
Anton Driesse (@adriesse)
Alexander Morgan (@alexandermorgan)
Miguel Sánchez de León Peque (@Peque)
v0.6.3 (May 15, 2019)¶
This is a minor release on top of v0.6.2 to fix an installation issue. We recommend that all users of v0.6.1 and v0.6.2 upgrade to this release.
Python 2.7 support will end on June 1, 2019. Releases made after this date will require Python 3. This release is likely to be the last that supports Python 2.7. (GH501)
Contributors¶
Will Holmgren (@wholmgren)
v0.6.2 (May 15, 2019)¶
This is a minor release. We recommend all users of v0.6.1 upgrade to this release.
Python 2.7 support will end on June 1, 2019. Releases made after this date will require Python 3. This release is likely to be the last that supports Python 2.7. (GH501)
Minimum pandas requirement bumped 0.15.0=>0.16.0
API Changes¶
erbs()
doy argument changed to datetime_or_doy to be consistent with allowed types and similar functions (disc()
,get_extra_radiation()
). (GH681)erbs()
DataFrame vs. OrderedDict return behavior now determined by type of datetime_or_doy instead of ghi or zenith. (GH681)Added min_cos_zenith and max_zenith keyword arguments to
erbs()
. (GH681)Deprecated
prepare_inputs()
assumption of clear sky if no irradiance fields were provided. (GH705, GH707)Remove automatic column name mapping from
read_midc()
andread_midc_raw_data_from_nrel()
and added optional keyword argument variable_map to map columns. (GH721)Update
pvfactors_timeseries()
and tests to usepvfactors
v1.0.1 (GH699)
Enhancements¶
Bug fixes¶
Compatibility with pandas 0.24 deprecations. (GH659)
pvwatts_ac()
raisedZeroDivisionError
when called with scalarpdc=0
and aRuntimeWarning
forarray(0)
input. Now correctly returns 0s of the appropriate type. (GH675)Fixed
erbs()
behavior when zenith is near 90 degrees. (GH681)dni()
now referenced in API under Decomposing and Combining irradiance header. (GH686)Fixed NaN output from
singleaxis()
when sun near horizon. (GH656)Fixed numpy warnings in
singleaxis()
when comparing NaN values to limits. (GH622)Change ModelChain to apply
pvwatts_losses
tomc.dc
instead ofmc.ac
. (GH696)Fixed a bug in the day angle equation for the ASCE extraterrestrial irradiance model. (GH211)
Silenced divide by 0 irradiance warnings in
klucher()
andcalcparams_desoto()
. (GH698)Fix
NDFD
model by updating variables.Fix
format_index()
to parse non one-minute data correctly. (GH709)
Contributors¶
Cliff Hansen (@cwhanse)
Will Holmgren (@wholmgren)
Roel Loonen (@roelloonen)
Todd Hendricks (@tahentx)
Kevin Anderson (@kevinsa5)
Jonathan Gaffiot (@jgaffiot)
Leland Boeman (@lboeman)
Marc Anoma (@anomam)
v0.6.1 (January 31, 2019)¶
This is a minor release. We recommend all users of v0.6.0 upgrade to this release.
Python 2.7 support will end on June 1, 2019. Releases made after this date will require Python 3. (GH501)
Minimum pandas requirement bumped 0.14.0=>0.15.0
API Changes¶
Deprecated
tmy
,tmy.readtmy2
andtmy.readtmy3
; they will be removed in v0.7. Use the newpvlib.iotools.read_tmy2()
andpvlib.iotools.read_tmy3()
instead. (GH261)Added keyword argument
horizon
topyephem()
andcalc_time()
with default value'+0:00'
. (GH588)Add max_airmass keyword argument to
pvlib.irradiance.disc()
. Default value (max_airmass=12) is consistent with polynomial fit in original paper describing the model. This change may result in different output of functions that use the disc Kn calculation for times when input zenith angles approach 90 degrees. This includespvlib.irradiance.dirint()
andpvlib.irradiance.dirindex()
when min_cos_zenith and max_zenith kwargs are used, as well aspvlib.irradiance.gti_dirint()
. (GH450)Changed key names for components returned from
pvlib.clearsky.detect_clearsky()
. (GH596)Changed function name from pvlib.solarposition.get_rise_set_transit (deprecated) to
pvlib.solarposition.sun_rise_set_transit_spa. `sun_rise_set_transit_spa()
requires time input to be localized to the specified latitude/longitude. (GH316)Created new bifacial section for pvfactors limited implementation (GH421)
Enhancements¶
Add
sun_rise_set_transit_ephem`to calculate sunrise, sunset and transit times using pyephem (:issue:`114()
)Add geometric functions for sunrise, sunset, and sun transit times,
sun_rise_set_transit_geometric()
(GH114)Add Location class method
get_sun_rise_set_transit()
Created
pvlib.iotools.read_srml()
andpvlib.iotools.read_srml_month_from_solardat()
to read University of Oregon Solar Radiation Monitoring Laboratory data. (GH589)Created
pvlib.iotools.read_surfrad()
to read NOAA SURFRAD data. (GH590)Created
pvlib.iotools.read_midc()
andpvlib.iotools.read_midc_raw_data_from_nrel()
to read NREL MIDC data. (GH601)Created
pvlib.iotools.get_ecmwf_macc()
andpvlib.iotools.read_ecmwf_macc()
to get and read ECMWF MACC data. (GH602)Use HRRR modeled surface temperature values instead of inferring from isobaric values and modeled wind speed instead of inferring from gust. (GH604)
Change
pvlib.pvsystem.sapm_spectral_loss()
to avoid numpy warning.Add warning message when
pvlib.spa()
is reloaded. (GH401)Add option for
pvlib.irradiance.disc()
to use relative airmass by supplying pressure=None. (GH449)Created
pvlib.pvsystem.pvsyst_celltemp()
to implement PVsyst’s cell temperature model. (GH552)Created
pvlib.bifacial.pvfactors_timeseries()
to use open-source pvfactors package to calculate back surface irradiance (GH421)Add PVSystem class method
pvsyst_celltemp()
(GH633)Add
pvlib.irradiance.clearsky_index()
to calculate clear-sky index from measured GHI and modeled clear-sky GHI. (GH551)
Bug fixes¶
Fix when building documentation using Matplotlib 3.0 or greater.
~pvlib.spa.calculate_deltat: Fix constant coefficient of the polynomial expression for years >= 1860 and < 1900, fix year 2050 which was returning 0. (GH600)
Fix and improve
hour_angle()
(GH598)Fix error in
pvlib.clearsky.detect_clearsky()
(GH506)Fix documentation errors when using IPython >= 7.0.
Fix error in
pvlib.modelchain.ModelChain.infer_spectral_model()
(GH619)Fix error in
pvlib.spa
when using Python 3.7 on some platforms.Fix error in
pvlib.irradiance._delta_kt_prime_dirint()
(GH637). The error affects the first and last values of DNI calculated by the functionpvlib.irradiance.dirint()
Fix errors on Python 2.7 and Numpy 1.6. (GH642)
Replace deprecated np.asscalar with array.item(). (GH642)
Testing¶
Add test for
hour_angle()
(GH597)Update tests to be compatible with pytest 4.0. (GH623)
Add tests for
pvlib.bifacial.pvfactors_timeseries()
implementation (GH421)
Contributors¶
Will Holmgren (@wholmgren)
Leland Boeman (@lboeman)
Cedric Leroy (@cedricleroy)
Ben Ellis (@bhellis725)
Cliff Hansen (@cwhanse)
Mark Mikofski (@mikofski)
Anton Driesse (@adriesse)
Cameron Stark (@camerontstark)
Jonathan Gaffiot (@jgaffiot)
Marc Anoma (@anomam)
Anton Driesse (@adriesse)
Kevin Anderson (@kevinsa5)
v0.6.0 (September 17, 2018)¶
This is a major release and contains a large number of API changes, new features, and bug fixes. Users should carefully read the changelog below before upgrading.
Python 2.7 support will end on June 1, 2019. Releases made after this date will require Python 3. (GH501)
API Changes¶
pvlib python is changing a handful of function names. In general, functions that can calculate a quantity using multiple algorithms now start with the prefix
get_
. For example,relativeairmass
can calculate airmass using one of manymodel
arguments. Its name has been changed toget_relative_airmass()
. The old function names remain in this release, but will emit aPVLibDeprecationWarning
when called. The old functions will be removed in the v0.7 release. Functions composed of multiple words jammed together have been renamed with underscores separating the words (see above). Each change is detailed below. (GH427)Deprecated
relativeairmass
; it will be removed in v0.7. Use the newget_relative_airmass()
instead. (GH427)Deprecated
absoluteairmass
; it will be removed in v0.7. Use the newget_absolute_airmass()
instead. (GH427)Deprecated
irradiance.globalinplane
; it will be removed in v0.7. Use the newpoa_components()
instead. (GH427)Added
poa_components()
. Function is the same as the now-deprecatedirradiance.globalinplane
, but adds'poa_sky_diffuse'
and'poa_ground_diffuse'
to the output. (GH427)Deprecated
irradiance.extraradiation
; it will be removed in v0.7. Usepvlib.irradiance.get_extra_radiation()
instead. (GH427)Deprecated
irradiance.grounddiffuse
; it will be removed in v0.7. Useget_ground_diffuse()
instead. (GH427)Added
get_poa_sky_diffuse()
. (GH427)Deprecated
irradiance.total_irrad
; it will be removed in v0.7. Useget_total_poa_irradiance()
instead. (GH427)Removed
'klutcher'
fromget_sky_diffuse
/total_irrad
. This misspelling was deprecated long ago but never removed. (GH97)calcparams_desoto()
now requires arguments for each module model parameter. (GH462)Add losses_parameters attribute to PVSystem objects and remove the kwargs support from PVSystem.pvwatts_losses. Enables custom losses specification in ModelChain calculations. (GH484)
removed irradiance parameter from ModelChain.run_model and ModelChain.prepare_inputs
Add
perez_enhancement
keyword argument to clearsky.ineichen to control whether or not the “perez enhancement factor” is applied. The enhancement factor was always applied until now. Now it is turned off by default. The enhancement factor can yield unphysical results, especially for latitudes closer to the poles and especially in the winter months. It may yield improved results under other conditions. (GH435)Add min_cos_zenith, max_zenith keyword arguments to disc, dirint, and dirindex functions. (GH311, GH396)
Method ModelChain.infer_dc_model now returns a tuple (function handle, model name string) instead of only the function handle (GH417)
Add DC model methods desoto and pvsyst to ModelChain, and deprecates DC model method singlediode (singlediode defaults to desoto until v0.7.0) (GH487)
Add the CEC module model in pvsystem.calcparams_cec and ModelChain.cec. The CEC model differs from the desoto model by using the parameter Adjust. Modules selected from the SAM CEC library sam-library-cec-modules-2017-6-5.csv include the Adjust parameter and ModelChain.infer_dc_model will now select the cec model rather than the desoto model. (GH463)
The behavior of irradiance.perez(return_components=True) has changed. The function previously returned a tuple of total sky diffuse and an OrderedDict/DataFrame of components. The function now returns an OrderedDict/DataFrame with total sky diffuse and each component. The behavior for return_components=False remains unchanged. (GH434)
Enhancements¶
Add sea surface albedo in
irradiance.py
(GH458)Implement
first_solar_spectral_loss()
inmodelchain.py
(GH359)Clarify arguments
Egref
anddEgdT
forcalcparams_desoto()
(GH462)Add pvsystem.calcparams_pvsyst to compute values for the single diode equation using the PVsyst v6 model (GH470)
Extend
singlediode()
with an additional keyword argumentmethod
in('lambertw', 'newton', 'brentq')
, default is'lambertw'
, to select a method to solve the single diode equation for points on the IV curve. Selecting either'brentq'
or'newton'
as the method usesbishop88()
with the corresponding method. (GH410)Implement new methods
'brentq'
and'newton'
for solving the single diode equation for points on the IV curve.'brentq'
uses a bisection method (Brent, 1973) that may be slow but guarantees a solution.'newton'
uses the Newton-Raphson method and may be faster but is not guaranteed to converge. However,'newton'
should be safe for well-behaved IV curves. (GH408)Implement
bishop88()
for explicit calculation of arbitrary IV curve points using diode voltage instead of cell voltage. Ifmethod
is either'newton'
or'brentq'
andivcurve_pnts
insinglediode()
is provided, the IV curve points will be log spaced instead of linear.Implement
estimate_voc()
to estimate open circuit voltage by assuming \(R_{sh} \to \infty\) and \(R_s=0\) as an upper bound in bisection method forsinglediode()
when method is either'newton'
or'brentq'
.Add
max_power_point()
method to compute the max power point using the new'brentq'
method.Add new module
pvlib.singlediode
with low-level functions for solving the single diode equation such as:bishop88()
,estimate_voc()
,bishop88_i_from_v()
,bishop88_v_from_i()
, andbishop88_mpp()
.Add PVSyst thin-film recombination losses for CdTe and a:Si (GH163)
Python 3.7 officially supported. (GH496)
Improve performance of solarposition.ephemeris. (GH512)
Improve performance of Location.get_airmass. Most noticeable when solar position is supplied, time index length is less than 10000, and method is looped over. (GH502)
Add irradiance.gti_dirint function. (GH396)
Add irradiance.clearness_index function. (GH396)
Add irradiance.clearness_index_zenith_independent function. (GH396)
Add checking for consistency between module_parameters and dc_model. (GH417)
Add DC model methods
'desoto'
and'pvsyst'
to ModelChain (GH487)Add the CEC module model in pvsystem.calcparams_cec and ModelChain.cec. (GH463)
Add DC model methods desoto and pvsyst to ModelChain (GH487)
pvlib now ships with a pvlib[optional] installation option to automatically install packages needed to support additional pvlib features:
pip install pvlib[optional]
. Additional installation options include doc (requirements for minimal documentation build), test (requirements for testing), and all (optional + doc + test). (GH553, GH483)Set default alpha to 1.14 in
angstrom_aod_at_lambda()
(GH563)tracking.singleaxis now accepts scalar and 1D-array input.
Bug fixes¶
Unset executable bits of irradiance.py and test_irradiance.py (GH460)
Fix failing tests due to column order on Python 3.6+ and Pandas 0.23+ (GH464)
ModelChain.prepare_inputs failed to pass solar_position and airmass to Location.get_clearsky. Fixed. (GH481)
Add User-Agent specification to TMY3 remote requests to avoid rejection. (GH493)
Fix
pvlib.irradiance.klucher
output is different for Pandas Series vs. floats and NumPy arrays. (GH508)Make GitHub recognize the license, add AUTHORS.md, clarify shared copyright. (GH503)
Fix issue with non-zero direct irradiance contribution to Reindl, Klucher, and Hay-Davies diffuse sky algorithms when the sun is behind the array. (GH526)
Fix issue with dividing by near-0 cos(solar_zenith) values in Reindl and Hay-Davies diffuse sky algorithms. (GH432)
Fix argument order of longitude and latitude when querying weather forecasts by lonlat bounding box (GH521)
Fix issue with unbounded clearness index calculation in disc. (GH540)
Limit pvwatts_ac results to be greater than or equal to 0. (GH541)
Fix bug in get_relative_airmass(model=’youngirvine1967’). (GH545)
Fix bug in variable names returned by forecast.py’s HRRR_ESRL model. (GH557)
Fixed bug in tracking.singleaxis that mistakenly assigned nan values when the Sun was still above the horizon. No effect on systems with axis_tilt=0. (GH569)
Source distribution did not contain LICENSE file. Added LICENSE, AUTHORS.md, and some docs to MANIFEST. (GH579)
Patch SPA C-files to fix timezone macro name clash with pyconfig.h. (GH168)
Documentation¶
Expand testing section with guidelines for functions, PVSystem/Location objects, and ModelChain.
Updated several incorrect statements in ModelChain documentation regarding implementation status and default values. (GH480)
Expanded general contributing and pull request guidelines.
Added section on single diode equation with some detail on solutions used in pvlib-python (GH518)
Minor improvements and updates to installation documentation. (GH531)
Improve LocalizedPVSystem and LocalizedSingleAxisTracker documentation. (GH532)
Move the “Getting Started”/”Modeling Paradigms” section to a new top-level “Intro Examples” page.
Copy pvlib documentation’s “Getting support” section to README.md.
Add example of Kasten Linke Turbidity calculation, discuss broadband AOD and Angstrom Turbidity Model. (GH302)
Add JOSS paper to “Citing pvlib-python” section.
Testing¶
Add pytest-mock dependency
Use pytest-mock to ensure that PVSystem and ModelChain methods call corresponding functions correctly. Removes implicit dependence on precise return values of some function/methods. (GH394)
Additional test refactoring to limit test result dependence to a single function per test. (GH394)
Use pytest-mock to ensure that ModelChain DC model is set up correctly.
Add Python 3.7 to build matrix
Make test_forecast.py more robust. (GH293)
Improve test_atmosphere.py. (GH158)
Add LGTM.com integration. (GH554)
Add SticklerCI integration.
Add codecov integration.
Contributors¶
Will Holmgren (@wholmgren)
Yu Cao (@tsaoyu)
Cliff Hansen (@cwhanse)
Mark Mikofski (@mikofski)
Alan Mathew (@alamathe1)
Xavier Rene-Corail (@xcorail)
Anton Driesse (@adriesse)
Mark Campanelli (@thunderfish24)
Cedric Leroy (@cedricleroy)
Jessica Forbess (@jforbess)
Jeff Newmiller (@jdnewmil)
Marc A. Anoma (@anomam)
William C Grisaitis (@grisaitis)
Karel De Brabandere (@kdebrab)
v0.5.2 (May 13, 2018)¶
API Changes¶
Enhancements¶
Improve clearsky.lookup_linke_turbidity speed, changing .mat source file to .h5 (GH437)
Updated libraries for CEC module parameters to SAM release 2017.9.5 (library dated 2017.6.30) and CEC inverter parameters to file posted to www.github.com/NREL/SAM on 2018.3.18, with one entry removed due to a missing parameter value. (:issue:’440’)
Bug fixes¶
fixed redeclaration of test_simplified_solis_series_elevation (GH387)
physicaliam now returns a Series if called with a Series as an argument. (GH397)
corrected docstring for irradiance.total_irrad (:issue: ‘423’)
modified solar_azimuth_analytical to handle some borderline cases, thereby avoiding the NaN values and/or warnings that were previously produced (:issue: ‘420’)
removed RuntimeWarnings due to divide by 0 or nans in input data within irradiance.perez, clearsky.simplified_solis, pvsystem.adrinverter, pvsystem.ashraeiam, pvsystem.physicaliam, pvsystem.sapm_aoi_loss, pvsystem.v_from_i. (GH428)
Testing¶
Contributors¶
Cliff Hansen
Will Holmgren
KonstantinTr
Anton Driesse
Cedric Leroy
v0.5.1 (October 17, 2017)¶
API Changes¶
pvsystem.v_from_i and pvsystem.i_from_v functions now accept resistance_series = 0 and/or resistance_shunt = numpy.inf as inputs (GH340)
Enhancements¶
Improve clearsky.lookup_linke_turbidity speed. (GH368)
Ideal devices supported in single diode model, e.g., resistance_series = 0 and/or resistance_shunt = numpy.inf (GH340)
pvsystem.v_from_i and pvsystem.i_from_v computations for near ideal devices are more numerically stable. However, very, very near ideal resistance_series and/or resistance_shunt may still cause issues with the implicit solver (GH340)
Bug fixes¶
Remove condition causing Overflow warning from clearsky.haurwitz (GH363)
modelchain.basic_chain now correctly passes ‘solar_position_method’ arg to solarposition.get_solarposition (GH370)
Fixed: Variables and Symbols extra references not available (GH380)
Removed unnecessary calculations of alpha_prime in spa.solar_position_numpy and spa.solar_position_loop (GH366)
Fixed args mismatch for solarposition.pyephem call from solarposition.get_solarposition with method=’pyephem’ arg to solarposition.get_solarposition (GH370)
ModelChain.prepare_inputs and ModelChain.complete_irradiance now correctly pass the ‘solar_position_method’ argument to solarposition.get_solarposition (GH377)
Fixed usage of inplace parameter for tmy._recolumn (GH342)
Documentation¶
Testing¶
Changed test for clearsky.haurwitz to operate on zenith angles
Significant new test cases added for pvsystem.v_from_i and pvsystem.i_from_v (GH340)
Contributors¶
Cliff Hansen
KonstantinTr
Will Holmgren
Mark Campanelli
DaCoEx
v0.5.0 (August 11, 2017)¶
API Changes¶
Removed parameter w from _calc_d (GH344)
SingleAxisTracker.get_aoi and SingleAxisTracker.get_irradiance now require surface_zenith and surface_azimuth (GH351)
Changes calculation of the Incidence Angle Modifier to return 0 instead of np.nan for angles >= 90°. This improves the calculation of effective irradiance close to sunrise and sunset. (GH338)
Change the default ModelChain orientation strategy from ‘south_at_latitude_tilt’ to
None
. (GH290)
Bug fixes¶
Method of multi-inheritance has changed to make it possible to use kwargs in the parent classes of LocalizedPVSystem and LocalizedSingleAxisTracker (GH330)
Fix the __repr__ method of ModelChain, crashing when orientation_strategy is set to ‘None’ (GH352)
Fix the ModelChain’s angle of incidence calculation for SingleAxisTracker objects (GH351)
Fix issue with ForecastModel.cloud_cover_to_transmittance_linear method of forecast.py ignoring ‘offset’ parameter. (GH343)
Enhancements¶
Documentation¶
Added ModelChain documentation page
Added nbsphinx to documentation build configuration.
Added a pull request template file (GH354)
Testing¶
Added explicit tests for aoi and aoi_projection functions.
Update test of ModelChain.__repr__ to take in account GH352
Added a test for solar_azimuth_analytical function.
Contributors¶
Johannes Kaufmann
Will Holmgren
Uwe Krien
Alaina Kafkes
Birgit Schachler
Jonathan Gaffiot
Siyan (Veronica) Guo
KonstantinTr
v0.4.5 (June 5, 2017)¶
Bug fixes¶
Fix pandas 0.20 incompatibilities in Location.get_clearsky, solarposition.ephemeris (GH325)
Fixes timezone issue in solarposition spa_c function (GH237)
Added NREL Bird clear sky model. (GH276)
Added lower accuracy formulas for equation of time, declination, hour angle and solar zenith.
Remove all instances of .ix (GH322)
Update docstring in pvlib.spa.solar_position - change units of pressure to millibars. NOTE: units of pressure in pvlib.solar_position.spa_python and pvlib.solar_position.spa_c are still Pascals. This update should have no effect on most users, since it only applies to the low-level spa.py module. (GH327)
Enhancements¶
Added irradiance.dni method that determines DNI from GHI and DHI and corrects unreasonable DNI values during sunrise/sunset transitions
ForecastModel will now only connect to the Unidata server when necessary, rather than when the object is created. This supports offline work and speeds up analysis of previously downloaded data.
Contributors¶
Will Holmgren
Marc Anoma
Mark Mikofski
Birgit Schachler
v0.4.4 (February 18, 2017)¶
Enhancements¶
Added Anton Driesse Inverter database and made compatible with pvsystem.retrieve_sam. (GH169)
Ported Anton Driesse Inverter model from PV_LIB Toolbox. (GH160)
Added Kasten pyrheliometric formula to calculate Linke turbidity factors with improvements by Ineichen and Perez to extend range of air mass (GH278)
Added coefficients for CIGS and a-Si modules types to the first_solar_spectral_correction function (GH308)
API Changes¶
Change PVSystem default module_parameters and inverter_parameters to empty dict. Code that relied on these attributes being None or raising a TypeError will need to be updated. (issue:294)
Documentation¶
Fixes the Forecasting page’s broken links to the tutorials.
Fixes the Forecasting page’s broken examples. (GH299)
Fixes broken Classes link in the v0.3.0 documentation.
Bug fixes¶
Resolved several issues with the forecast module tests. Library import errors were resolved by prioritizing the conda-forge channel over the default channel. Stalled ci runs were resolved by adding a timeout to the HRRR_ESRL test. (GH293)
Fixed issue with irradiance jupyter notebook tutorial. (GH309)
Contributors¶
Will Holmgren
Volker Beutner
Mark Mikofski
Anton Driesse
Mitchell Lee
v0.4.3 (December 28, 2016)¶
Enhancements¶
Contributors¶
Marc Anoma
Will Holmgren
Cliff Hansen
Tony Lorenzo
v0.4.2 (December 7, 2016)¶
This is a minor release from 0.4.1.
Bug fixes¶
Fixed typo in __repr__ method of ModelChain and in its regarding test.
PVSystem.pvwatts_ac could not use the eta_inv_ref kwarg and PVSystem.pvwatts_dc could not use the temp_ref kwarg. Fixed. (GH252)
Fixed typo in ModelChain.infer_spectral_model error message. (GH251)
Fixed Linke turbdity factor out of bounds error at 90-degree latitude or at 180-degree longitude (GH262)
Fixed Linke turbidity factor grid spacing and centers (GH263)
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 the Numba accelerated calculations. 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)
Added more attributes to ModelChain, PVSystem, and Location printed representations. (GH254)
Added name attribute to ModelChain and PVSystem. (GH254)
Restructured API section of the documentation so that there are separate pages for each function, class, or method. (GH258)
Improved Linke turbidity factor time interpolation with Python calendar month days and leap years (GH265)
Added option to return diffuse components from Perez transposition model.
Other¶
Code Contributors¶
Uwe Krien
Will Holmgren
Volker Beutner
Mark Mikofski
Marc Anoma
Giuseppe Peronato
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:
Classes (GH93) (Moved to API reference in GH258)
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¶
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 functionsTMY data import has a
coerce_year
optionTMY 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.
Please see the Compatibility section for information on the optional packages that are needed for some pvlib-python features.
If you don’t have Python¶
There are many ways to install Python on your system, but the Anaconda Python distribution is the easiest way for most new users to get started. Anaconda includes all of the popular libraries that you’ll need for pvlib, including Pandas, NumPy, and SciPy.
Install the Anaconda Python distribution available at Anaconda.com.
See What is Anaconda? and the Anaconda Documentation for more information.
You can now install pvlib-python by one of the methods below.
Install standard release¶
Users may install pvlib-python using either the conda or pip package manager. We recommend that most users install pvlib-python using the conda package manager in the Anaconda Python distribution. To install the most recent stable release of pvlib-python in a non-editable way, use one of the following commands to install pvlib-python:
# get the package from the pvlib conda channel
# best option for installing pvlib in the base Anaconda distribution
conda install -c pvlib pvlib
# get the package from the conda-forge conda channel
# best option if using pvlib.forecast module
# strongly recommend installing in a separate conda env as shown below
conda create -n pvlib -c conda-forge pvlib-python; conda activate pvlib
# get the package from the Python Package Index
# best option if you know what you are doing
pip install pvlib
# get pvlib and optional dependencies from the Python Package Index
# another option if you know what you are doing
pip install pvlib[optional]
Note
By default, pvlib will not install some infrequently used dependencies. If you run into an error such as ModuleNotFoundError: No module named ‘netCDF4’ you can either install pvlib with all optional dependencies using pip install pvlib[optional], or you can install pvlib from conda-forge conda create -n pvlib -c conda-forge pvlib-python; conda activate 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:
conda activate pvlibdev
Install additional packages into your development environment:
conda install jupyter ipython matplotlib 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 .
from within the directory you previously cloned. Consider installing pvlib usingpip install -e .[all]
so that you can run the unit tests and build the documentation. Your clone directory is probably similar toC:\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 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 conda activate pvlibdev
(or whatever you named your
environment) when you start a new shell or terminal.
Compatibility¶
pvlib-python is compatible with Python 3.5 and above.
pvlib-python requires Pandas and Numpy. The minimum version requirements are specified in setup.py. They are typically releases from several years ago.
A handful of pvlib-python features require additional packages that must be installed separately using pip or conda. These packages/features include:
scipy: single diode model, clear sky detection
pytables (tables on PyPI): Linke turbidity look up for clear sky models
numba: fastest solar position calculations
pyephem: solar positions calculations using an astronomical library
siphon: forecasting PV power using the pvlib.forecast module
The Anaconda distribution includes most of the above packages.
Alternatively, users may install all optional dependencies using
pip install pvlib[optional]
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 .
References¶
Here are a few recommended references for installing Python packages:
Here are a few recommended references for git and GitHub:
The git documentation: detailed explanations, videos, more links, and cheat sheets. Go here first!
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.
There is a lot of material in this section, aimed at a variety of contributors from novice to expert. Don’t worry if you don’t (yet) understand parts of it.
Easy ways to contribute¶
Here are a few ideas for how you can contribute, even if you are new to pvlib-python, git, or Python:
Ask and answer pvlib questions on StackOverflow and participate in discussions in the pvlib-python google group.
Make GitHub issues and contribute to the conversations about how to resolve them.
Read issues and pull requests that other people created and contribute to the conversation about how to resolve them. Look for issues tagged with good first issue, easy, or help wanted.
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¶
The basics¶
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.
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. See the Testing section below.
Pull request scope¶
This section can be summed up as “less is more”.
A pull request can quickly become unmanageable if too many lines are added or changed. “Too many” is hard to define, but as a rule of thumb, we encourage contributions that contain less than 50 lines of primary code. 50 lines of primary code will typically need at least 250 lines of documentation and testing. This is about the limit of what the maintainers can review on a regular basis.
A pull request can also quickly become unmanageable if it proposes changes to the API in order to implement another feature. Consider clearly and concisely documenting all proposed API changes before implementing any code. Modifying api.rst and/or the latest whatsnew file can help formalize this process.
Questions about related issues frequently come up in the process of addressing implementing code for a pull request. Please try to avoid expanding the scope of your pull request (this also applies to reviewers!). We’d rather see small, well-documented additions to the project’s technical debt than see a pull request languish because its scope expanded beyond what the reviewer community is capable of processing.
Of course, sometimes it is necessary to make a large pull request. We only ask that you take a few minutes to consider how to break it into smaller chunks before proceeding.
pvlib-python contains 3 “layers” of code: functions, PVSystem/Location, and ModelChain. We recommend that contributors focus their work on only one or two of those layers in a single pull request. New models are not required to be available to the higher-level API!
When should I submit a pull request?¶
The short answer: anytime.
The long answer: it depends. If in doubt, go ahead and submit. 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.
There are pros and cons to submitting incomplete pull-requests. On the plus side, it gives everybody an easy way to comment on the code and can make the process more efficient. On the minus side, it’s easy for an incomplete pull request to grow into a multi-month saga that leaves everyone unhappy. If you submit an incomplete pull request, please be very clear about what you would like feedback on and what we should ignore. Alternatives to incomplete pull requests include creating a gist or experimental branch and linking to it in the corresponding issue.
The best way to ensure that a pull request will be reviewed and merged in a timely manner is to:
Start by creating an issue. The issue should be well-defined and actionable.
Ask the maintainers to tag the issue with the appropriate milestone.
Make a limited-scope pull request. It can be a lot of work to check all of the boxes in pull request guidelines, especially for pull requests with a lot of new primary code. See Pull request scope.
Tag pvlib community members or
@pvlib/maintainer
when the pull request is ready for review. (see Pull request reviews)
Pull request reviews¶
The pvlib community and maintainers will review your pull request in a
timely fashion. Please “ping” @pvlib/maintainer
if it seems that
your pull request has been forgotten at any point in the pull request
process.
Keep in mind that the PV modeling community is diverse and each pvlib community member brings a different perspective when reviewing code. Some reviewers bring years of expertise in the sub-field that your code contributes to and will focus on the details of the algorithm. Other reviewers will be more focused on integrating your code with the rest of pvlib, ensuring that it is feasible to maintain, that it meets the code style guidelines, and that it is comprehensively tested. Limiting the scope of the pull request makes it much more likely that all of these reviews can be conducted and any issues can be resolved in a timely fashion.
Sometimes it’s hard for reviewers to be immediately available, so the right amount of patience is to be expected. That said, interested reviewers should do their best to not wait until the last minute to put in their two cents.
Code style¶
pvlib python generally follows the PEP 8 – Style Guide for Python Code. Maximum line length for code is 79 characters.
Code must be compatible with Python 3.5 and above.
pvlib python uses a mix of full and abbreviated variable names. See Variables and Symbols. We could be better about consistency. Prefer full names for new contributions. This is especially important for the API. Abbreviations can be used within a function to improve the readability of formulae.
Set your editor to strip extra whitespace from line endings. This prevents the git commit history from becoming cluttered with whitespace changes.
Please see Documentation for information specific to documentation style.
Remove any logging
calls and print
statements that you added
during development. warning
is ok.
We typically use GitHub’s “squash and merge <https://help.github.com/articles/about-pull-request-merges/#squash-and-merge-your-pull-request-commits>_” feature to merge your pull request into pvlib. GitHub will condense the commit history of your branch into a single commit when merging into pvlib-python/master (the commit history on your branch remains unchanged). Therefore, you are free to make commits that are as big or small as you’d like while developing your pull request.
Documentation¶
Documentation must be written in numpydoc format format which is rendered using the Sphinx Napoleon extension.
The numpydoc format includes a specification for the allowable input types. Python’s duck typing allows for multiple input types to work for many parameters. pvlib uses the following generic descriptors as short-hand to indicate which specific types may be used:
dict-like : dict, OrderedDict, pd.Series
numeric : scalar, np.array, pd.Series. Typically int or float dtype.
array-like : np.array, pd.Series. Typically int or float dtype.
Parameters that specify a specific type require that specific input type.
A relatively easy way to test your documentation is to build it on readthedocs.org <https://readthedocs.org> by following their Import Your Docs instructions and enabling your branch on the readthedocs versions admin page.
Another option is to install the required dependencies in your virtual/conda environment. See docs/environment.yml for the latest dependences for building the complete documentation. Some doc files can be compiled with fewer dependencies, but this is beyond the scope of this guide.
Testing¶
pvlib’s unit tests can easily be run by executing pytest
on the
pvlib directory:
pytest pvlib
or, for a single module:
pytest pvlib/test/test_clearsky.py
or, for a single test:
pytest pvlib/test/test_clearsky.py::test_ineichen_nans
We suggest using pytest’s --pdb
flag to debug test failures rather
than using print
or logging
calls. For example:
pytest pvlib --pdb
will drop you into the
pdb debugger at the
location of a test failure. As described in Code style, pvlib
code does not use print
or logging
calls, and this also applies
to the test suite (with rare exceptions).
New 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.
pvlib-python contains 3 “layers” of code: functions, PVSystem/Location, and ModelChain. Contributors will need to add tests that correspond to the layers that they modify.
Functions¶
Tests of core pvlib functions should ensure that the function returns the desired output for a variety of function inputs. The tests should be independent of other pvlib functions (see GH394). The tests should ensure that all reasonable combinations of input types (floats, nans, arrays, series, scalars, etc) work as expected. Remember that your use case is likely not the only way that this function will be used, and your input data may not be generic enough to fully test the function. Write tests that cover the full range of validity of the algorithm. It is also important to write tests that assert the return value of the function or that the function throws an exception when input data is beyond the range of algorithm validity.
PVSystem/Location¶
The PVSystem and Location classes provide convenience wrappers around the core pvlib functions. The tests in test_pvsystem.py and test_location.py should ensure that the method calls correctly wrap the function calls. Many PVSystem/Location methods pass one or more of their object’s attributes (e.g. PVSystem.module_parameters, Location.latitude) to a function. Tests should ensure that attributes are passed correctly. These tests should also ensure that the method returns some reasonable data, though the precise values of the data should be covered by function-specific tests discussed above.
We prefer to use the pytest-mock
framework to write these tests. The
test below shows an example of testing the PVSystem.ashraeiam
method. mocker
is a pytest-mock
object. mocker.spy
adds
features to the pvsystem.ashraeiam
function that keep track of how
it was called. Then a PVSystem
object is created and the
PVSystem.ashraeiam
method is called in the usual way. The
PVSystem.ashraeiam
method is supposed to call the
pvsystem.ashraeiam
function with the angles supplied to the method
call and the value of b
that we defined in module_parameters
.
The pvsystem.ashraeiam.assert_called_once_with
tests that this does,
in fact, happen. Finally, we check that the output of the method call is
reasonable.
def test_PVSystem_ashraeiam(mocker):
# mocker is a pytest-mock object.
# mocker.spy adds code to a function to keep track of how it is called
mocker.spy(pvsystem, 'ashraeiam')
# set up inputs
module_parameters = {'b': 0.05}
system = pvsystem.PVSystem(module_parameters=module_parameters)
thetas = 1
# call the method
iam = system.ashraeiam(thetas)
# did the method call the function as we expected?
# mocker.spy added assert_called_once_with to the function
pvsystem.ashraeiam.assert_called_once_with(thetas, b=module_parameters['b'])
# check that the output is reasonable, but no need to duplicate
# the rigorous tests of the function
assert iam < 1.
Avoid writing PVSystem/Location tests that depend sensitively on the return value of a statement as a substitute for using mock. These tests are sensitive to changes in the functions, which is not what we want to test here, and are difficult to maintain.
ModelChain¶
The tests in test_modelchain.py should ensure that
ModelChain.__init__
correctly configures the ModelChain object to
eventually run the selected models. A test should ensure that the
appropriate method is actually called in the course of
ModelChain.run_model
. A test should ensure that the model selection
does have a reasonable effect on the subsequent calculations, though the
precise values of the data should be covered by the function tests
discussed above. pytest-mock
can also be used for testing ModelChain
.
The example below shows how mock can be used to assert that the correct
PVSystem method is called through ModelChain.run_model
.
def test_modelchain_dc_model(mocker):
# set up location and system for model chain
location = location.Location(32, -111)
system = pvsystem.PVSystem(module_parameters=some_sandia_mod_params,
inverter_parameters=some_cecinverter_params)
# mocker.spy adds code to the system.sapm method to keep track of how
# it is called. use returned mock object m to make assertion later,
# but see example above for alternative
m = mocker.spy(system, 'sapm')
# make and run the model chain
mc = ModelChain(system, location,
aoi_model='no_loss', spectral_model='no_loss')
times = pd.date_range('20160101 1200-0700', periods=2, freq='6H')
mc.run_model(times)
# assertion fails if PVSystem.sapm is not called once
# if using returned m, prefer this over m.assert_called_once()
# for compatibility with python < 3.6
assert m.call_count == 1
# ensure that dc attribute now exists and is correct type
assert isinstance(mc.dc, (pd.Series, pd.DataFrame))
This documentation¶
If this documentation is unclear, help us improve it! Consider looking at the pandas documentation for inspiration.
PVSystem¶
The PVSystem
class wraps many of the
functions in the pvsystem
module. This simplifies the
API by eliminating the need for a user to specify arguments such as
module and inverter properties when calling PVSystem methods.
PVSystem
is not better or worse than the
functions it wraps – it is simply an alternative way of organizing
your data and calculations.
This guide aims to build understanding of the PVSystem class. It assumes basic familiarity with object-oriented code in Python, but most information should be understandable without a solid understanding of classes. Keep in mind that functions are independent of objects, while methods are attached to objects.
See ModelChain
for an application of
PVSystem to time series modeling.
Design philosophy¶
The PVSystem class allows modelers to easily separate the data that represents a PV system (e.g. tilt angle or module parameters) from the data that influences the PV system (e.g. the weather).
The data that represents the PV system is intrinsic. The data that influences the PV system is extrinsic.
Intrinsic data is stored in object attributes. For example, the data that describes a PV system’s module parameters is stored in PVSystem.module_parameters.
In [1]: module_parameters = {'pdc0': 10, 'gamma_pdc': -0.004}
In [2]: system = pvsystem.PVSystem(module_parameters=module_parameters)
In [3]: print(system.module_parameters)
{'pdc0': 10, 'gamma_pdc': -0.004}
Extrinsic data is passed to a PVSystem as method arguments. For example,
the pvwatts_dc()
method accepts extrinsic
data irradiance and temperature.
In [4]: pdc = system.pvwatts_dc(1000, 30)
In [5]: print(pdc)
9.8
Methods attached to a PVSystem object wrap corresponding functions in
pvsystem
. The methods simplify the argument list by
using data stored in the PVSystem attributes. Compare the
pvwatts_dc()
method signature to the
pvwatts_dc()
function signature:
PVSystem.pvwatts_dc(g_poa_effective, temp_cell)
pvwatts_dc(g_poa_effective, temp_cell, pdc0, gamma_pdc, temp_ref=25.)
How does this work? The pvwatts_dc()
method looks in PVSystem.module_parameters for the pdc0, and
gamma_pdc arguments. Then the PVSystem.pvwatts_dc
method calls the
pvsystem.pvwatts_dc
function with
all of the arguments and returns the result to the user. Note that the
function includes a default value for the parameter temp_ref. This
default value may be overridden by specifying the temp_ref key in the
PVSystem.module_parameters dictionary.
In [6]: system.module_parameters['temp_ref'] = 0
# lower temp_ref should lead to lower DC power than calculated above
In [7]: pdc = system.pvwatts_dc(1000, 30)
In [8]: print(pdc)
8.8
Multiple methods may pull data from the same attribute. For example, the PVSystem.module_parameters attribute is used by the DC model methods as well as the incidence angle modifier methods.
PVSystem attributes¶
Here we review the most commonly used PVSystem attributes. Please see
the PVSystem
class documentation for a
comprehensive list.
The first PVSystem parameters are surface_tilt and surface_azimuth.
These parameters are used in PVSystem methods such as
get_aoi()
and
get_irradiance()
. Angle of incidence
(AOI) calculations require surface_tilt, surface_azimuth and also
the sun position. The get_aoi()
method
uses the surface_tilt and surface_azimuth attributes in its PVSystem
object, and so requires only solar_zenith and solar_azimuth as
arguments.
# 20 deg tilt, south-facing
In [9]: system = pvsystem.PVSystem(surface_tilt=20, surface_azimuth=180)
In [10]: print(system.surface_tilt, system.surface_azimuth)
20 180
# call get_aoi with solar_zenith, solar_azimuth
In [11]: aoi = system.get_aoi(30, 180)
In [12]: print(aoi)
9.999999999999975
module_parameters and inverter_parameters contain the data
necessary for computing DC and AC power using one of the available
PVSystem methods. These are typically specified using data from
the retrieve_sam()
function:
# retrieve_sam returns a dict. the dict keys are module names, # and the values are model parameters for that module In [13]: modules = pvsystem.retrieve_sam('cecmod') In [14]: module_parameters = modules['Example_Module'] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2896 try: -> 2897 return self._engine.get_loc(key) 2898 except KeyError: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: 'Example_Module' During handling of the above exception, another exception occurred: KeyError Traceback (most recent call last) <ipython-input-14-412284c56432> in <module> ----> 1 module_parameters = modules['Example_Module'] ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key) 2978 if self.columns.nlevels > 1: 2979 return self._getitem_multilevel(key) -> 2980 indexer = self.columns.get_loc(key) 2981 if is_integer(indexer): 2982 indexer = [indexer] ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2897 return self._engine.get_loc(key) 2898 except KeyError: -> 2899 return self._engine.get_loc(self._maybe_cast_indexer(key)) 2900 indexer = self.get_indexer([key], method=method, tolerance=tolerance) 2901 if indexer.ndim > 1 or indexer.size > 1: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: 'Example_Module' In [15]: inverters = pvsystem.retrieve_sam('cecinverter') In [16]: inverter_parameters = inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'] --------------------------------------------------------------------------- KeyError Traceback (most recent call last) ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2896 try: -> 2897 return self._engine.get_loc(key) 2898 except KeyError: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_' During handling of the above exception, another exception occurred: KeyError Traceback (most recent call last) <ipython-input-16-3357ac02d500> in <module> ----> 1 inverter_parameters = inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'] ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key) 2978 if self.columns.nlevels > 1: 2979 return self._getitem_multilevel(key) -> 2980 indexer = self.columns.get_loc(key) 2981 if is_integer(indexer): 2982 indexer = [indexer] ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance) 2897 return self._engine.get_loc(key) 2898 except KeyError: -> 2899 return self._engine.get_loc(self._maybe_cast_indexer(key)) 2900 indexer = self.get_indexer([key], method=method, tolerance=tolerance) 2901 if indexer.ndim > 1 or indexer.size > 1: pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item() KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_' In [17]: system = pvsystem.PVSystem(module_parameters=module_parameters, inverter_parameters=inverter_parameters) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-17-e25aeba9b3e9> in <module> ----> 1 system = pvsystem.PVSystem(module_parameters=module_parameters, inverter_parameters=inverter_parameters) NameError: name 'inverter_parameters' is not defined
The module and/or inverter parameters can also be specified manually. This is useful for specifying modules and inverters that are not included in the supplied databases. It is also useful for specifying systems for use with the PVWatts models, as demonstrated in Design philosophy.
The losses_parameters attribute contains data that may be used with
methods that calculate system losses. At present, these methods include
only PVSystem.pvwatts_losses
and
pvsystem.pvwatts_losses
, but
we hope to add more related functions and methods in the future.
The attributes modules_per_string and strings_per_inverter are used
in the scale_voltage_current_power()
method. Some DC power models in ModelChain
automatically call this method and make use of these attributes. As an
example, consider a system with 35 modules arranged into 5 strings of 7
modules each.
In [18]: system = pvsystem.PVSystem(modules_per_string=7, strings_per_inverter=5)
# crude numbers from a single module
In [19]: data = pd.DataFrame({'v_mp': 8, 'v_oc': 10, 'i_mp': 5, 'i_x': 6,
....: 'i_xx': 4, 'i_sc': 7, 'p_mp': 40}, index=[0])
....:
In [20]: data_scaled = system.scale_voltage_current_power(data)
In [21]: print(data_scaled)
v_mp v_oc i_mp i_x i_xx i_sc p_mp
0 56 70 25 30 20 35 1400
SingleAxisTracker¶
The SingleAxisTracker
is a subclass of
PVSystem
. The SingleAxisTracker class
includes a few more keyword arguments and attributes that are specific
to trackers, plus the
singleaxis()
method. It also
overrides the get_aoi and get_irradiance methods.
ModelChain¶
The ModelChain
class provides a high-level
interface for standardized PV modeling. The class aims to automate much
of the modeling process while providing user-control and remaining
extensible. This guide aims to build users’ understanding of the
ModelChain class. It assumes some familiarity with object-oriented
code in Python, but most information should be understandable even
without a solid understanding of classes.
A ModelChain
is composed of a
PVSystem
object and a
Location
object. 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 a system’s output at that
location. The PVSystem and Location objects will be described in detail
in another guide.
Modeling with a ModelChain
typically involves 3 steps:
Creating the
ModelChain
.Executing the
ModelChain.run_model()
method with prepared weather data.Examining the model results that
run_model()
stored in attributes of theModelChain
.
A simple ModelChain example¶
Before delving into the intricacies of ModelChain, we provide a brief example of the modeling steps using ModelChain. First, we import pvlib’s objects, module data, and inverter data.
In [1]: import pandas as pd
In [2]: import numpy as np
# pvlib imports
In [3]: import pvlib
In [4]: from pvlib.pvsystem import PVSystem
In [5]: from pvlib.location import Location
In [6]: from pvlib.modelchain import ModelChain
In [7]: from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
In [8]: temperature_model_parameters = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
# load some module and inverter specifications
In [9]: sandia_modules = pvlib.pvsystem.retrieve_sam('SandiaMod')
In [10]: cec_inverters = pvlib.pvsystem.retrieve_sam('cecinverter')
In [11]: sandia_module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [12]: cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2896 try:
-> 2897 return self._engine.get_loc(key)
2898 except KeyError:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'
During handling of the above exception, another exception occurred:
KeyError Traceback (most recent call last)
<ipython-input-12-7e123d4a37af> in <module>
----> 1 cec_inverter = cec_inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_']
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
2978 if self.columns.nlevels > 1:
2979 return self._getitem_multilevel(key)
-> 2980 indexer = self.columns.get_loc(key)
2981 if is_integer(indexer):
2982 indexer = [indexer]
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2897 return self._engine.get_loc(key)
2898 except KeyError:
-> 2899 return self._engine.get_loc(self._maybe_cast_indexer(key))
2900 indexer = self.get_indexer([key], method=method, tolerance=tolerance)
2901 if indexer.ndim > 1 or indexer.size > 1:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'
Now we create a Location object, a PVSystem object, and a ModelChain object.
In [13]: location = Location(latitude=32.2, longitude=-110.9) In [14]: system = PVSystem(surface_tilt=20, surface_azimuth=200, ....: module_parameters=sandia_module, ....: inverter_parameters=cec_inverter, ....: temperature_model_parameters=temperature_model_parameters) ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-14-9ac9b596dc7c> in <module> 1 system = PVSystem(surface_tilt=20, surface_azimuth=200, 2 module_parameters=sandia_module, ----> 3 inverter_parameters=cec_inverter, 4 temperature_model_parameters=temperature_model_parameters) NameError: name 'cec_inverter' is not defined In [15]: mc = ModelChain(system, location) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-15-c237d7250396> in <module> ----> 1 mc = ModelChain(system, location) NameError: name 'system' is not defined
Printing a ModelChain object will display its models.
In [16]: print(mc)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-16-4b7954403baa> in <module>
----> 1 print(mc)
NameError: name 'mc' is not defined
Next, we run a model with some simple weather data.
In [17]: weather = pd.DataFrame([[1050, 1000, 100, 30, 5]],
....: columns=['ghi', 'dni', 'dhi', 'temp_air', 'wind_speed'],
....: index=[pd.Timestamp('20170401 1200', tz='US/Arizona')])
....:
In [18]: mc.run_model(times=weather.index, weather=weather);
ModelChain stores the modeling results on a series of attributes. A few examples are shown below.
In [19]: mc.aoi
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-19-9af3190081df> in <module>
----> 1 mc.aoi
NameError: name 'mc' is not defined
In [20]: mc.cell_temperature
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-20-ab0b6163d9b2> in <module>
----> 1 mc.cell_temperature
NameError: name 'mc' is not defined
In [21]: mc.dc
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-21-b9022cfccb62> in <module>
----> 1 mc.dc
NameError: name 'mc' is not defined
In [22]: mc.ac
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-22-db2d87a3a7b7> in <module>
----> 1 mc.ac
NameError: name 'mc' is not defined
The remainder of this guide examines the ModelChain functionality and explores common pitfalls.
Defining a ModelChain¶
A ModelChain
object is defined by:
The keyword arguments passed to it at construction
ModelChain uses the keyword arguments passed to it to determine the models for the simulation. The documentation describes the allowed values for each keyword argument. If a keyword argument is not supplied, ModelChain will attempt to infer the correct set of models by inspecting the Location and PVSystem attributes.
Below, we show some examples of how to define a ModelChain.
Let’s make the most basic Location and PVSystem objects and build from there.
In [23]: location = Location(32.2, -110.9) In [24]: poorly_specified_system = PVSystem() In [25]: print(location) Location: name: None latitude: 32.2 longitude: -110.9 altitude: 0 tz: UTC In [26]: print(poorly_specified_system) PVSystem: name: None surface_tilt: 0 surface_azimuth: 180 module: None inverter: None albedo: 0.25 racking_model: open_rack
These basic objects do not have enough information for ModelChain to be able to automatically determine its set of models, so the ModelChain will throw an error when we try to create it.
In [27]: ModelChain(poorly_specified_system, location)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-27-4e9151e6ff63> in <module>
----> 1 ModelChain(poorly_specified_system, location)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py in __init__(self, system, location, orientation_strategy, clearsky_model, transposition_model, solar_position_method, airmass_model, dc_model, ac_model, aoi_model, spectral_model, temperature_model, losses_model, name, **kwargs)
317
318 # calls setters
--> 319 self.dc_model = dc_model
320 self.ac_model = ac_model
321 self.aoi_model = aoi_model
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py in dc_model(self, model)
389 # guess at model if None
390 if model is None:
--> 391 self._dc_model, model = self.infer_dc_model()
392
393 # Set model and validate parameters
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py in infer_dc_model(self)
433 return self.pvwatts_dc, 'pvwatts'
434 else:
--> 435 raise ValueError('could not infer DC model from '
436 'system.module_parameters. Check '
437 'system.module_parameters or explicitly '
ValueError: could not infer DC model from system.module_parameters. Check system.module_parameters or explicitly set the model with the dc_model kwarg.
Next, we define a PVSystem with a module from the SAPM database and an inverter from the CEC database. ModelChain will examine the PVSystem object’s properties and determine that it should choose the SAPM DC model, AC model, AOI loss model, and spectral loss model.
In [28]: sapm_system = PVSystem( ....: module_parameters=sandia_module, ....: inverter_parameters=cec_inverter, ....: temperature_model_parameters=temperature_model_parameters) ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-28-13767fce5736> in <module> 1 sapm_system = PVSystem( 2 module_parameters=sandia_module, ----> 3 inverter_parameters=cec_inverter, 4 temperature_model_parameters=temperature_model_parameters) NameError: name 'cec_inverter' is not defined In [29]: mc = ModelChain(sapm_system, location) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-29-9886b976fbf1> in <module> ----> 1 mc = ModelChain(sapm_system, location) NameError: name 'sapm_system' is not defined In [30]: print(mc) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-30-4b7954403baa> in <module> ----> 1 print(mc) NameError: name 'mc' is not defined
In [31]: mc.run_model(times=weather.index, weather=weather); In [32]: mc.ac --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-32-db2d87a3a7b7> in <module> ----> 1 mc.ac NameError: name 'mc' is not defined
Alternatively, we could have specified single diode or PVWatts related
information in the PVSystem construction. Here we pass PVWatts data to
the PVSystem. ModelChain will automatically determine that it should
choose PVWatts DC and AC models. ModelChain still needs us to specify
aoi_model
and spectral_model
keyword arguments because the
system.module_parameters
dictionary does not contain enough
information to determine which of those models to choose.
In [33]: pvwatts_system = PVSystem(
....: module_parameters={'pdc0': 240, 'gamma_pdc': -0.004},
....: inverter_parameters={'pdc0': 240},
....: temperature_model_parameters=temperature_model_parameters)
....:
In [34]: mc = ModelChain(pvwatts_system, location,
....: aoi_model='physical', spectral_model='no_loss')
....:
In [35]: print(mc)
ModelChain:
name: None
orientation_strategy: None
clearsky_model: ineichen
transposition_model: haydavies
solar_position_method: nrel_numpy
airmass_model: kastenyoung1989
dc_model: pvwatts_dc
ac_model: pvwatts_inverter
aoi_model: physical_aoi_loss
spectral_model: no_spectral_loss
temperature_model: sapm_temp
losses_model: no_extra_losses
In [36]: mc.run_model(times=weather.index, weather=weather);
In [37]: mc.ac
Out[37]:
2017-04-01 12:00:00-07:00 198.519999
dtype: float64
User-supplied keyword arguments override ModelChain’s inspection methods. For example, we can tell ModelChain to use different loss functions for a PVSystem that contains SAPM-specific parameters.
In [38]: sapm_system = PVSystem( ....: module_parameters=sandia_module, ....: inverter_parameters=cec_inverter, ....: temperature_model_parameters=temperature_model_parameters) ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-38-13767fce5736> in <module> 1 sapm_system = PVSystem( 2 module_parameters=sandia_module, ----> 3 inverter_parameters=cec_inverter, 4 temperature_model_parameters=temperature_model_parameters) NameError: name 'cec_inverter' is not defined In [39]: mc = ModelChain(sapm_system, location, aoi_model='physical', spectral_model='no_loss') --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-39-1fd631bb9eb3> in <module> ----> 1 mc = ModelChain(sapm_system, location, aoi_model='physical', spectral_model='no_loss') NameError: name 'sapm_system' is not defined In [40]: print(mc) ModelChain: name: None orientation_strategy: None clearsky_model: ineichen transposition_model: haydavies solar_position_method: nrel_numpy airmass_model: kastenyoung1989 dc_model: pvwatts_dc ac_model: pvwatts_inverter aoi_model: physical_aoi_loss spectral_model: no_spectral_loss temperature_model: sapm_temp losses_model: no_extra_losses
In [41]: mc.run_model(times=weather.index, weather=weather);
In [42]: mc.ac
Out[42]:
2017-04-01 12:00:00-07:00 198.519999
dtype: float64
Of course, these choices can also lead to failure when executing
run_model()
if your system objects
do not contain the required parameters for running the model.
Demystifying ModelChain internals¶
The ModelChain class has a lot going in inside it in order to make users’ code as simple as possible.
The key parts of ModelChain are:
The
ModelChain.run_model()
methodA set of methods that wrap and call the PVSystem methods.
A set of methods that inspect user-supplied objects to determine the appropriate default models.
run_model¶
Most users will only interact with the
run_model()
method. The
run_model()
method, shown below,
calls a series of methods to complete the modeling steps. The first
method, prepare_inputs()
, computes
parameters such as solar position, airmass, angle of incidence, and
plane of array irradiance. The
prepare_inputs()
method also
assigns default values for temperature (20 C)
and wind speed (0 m/s) if these inputs are not provided.
prepare_inputs()
requires all irradiance
components (GHI, DNI, and DHI). See
complete_irradiance()
and
DNI estimation models for methods and functions that can help fully define
the irradiance inputs.
Next, run_model()
calls the
wrapper methods for AOI loss, spectral loss, effective irradiance, cell
temperature, DC power, AC power, and other losses. These methods are
assigned to standard names, as described in the next section.
The methods called by run_model()
store their results in a series of ModelChain attributes: times
,
solar_position
, airmass
, irradiance
, total_irrad
,
effective_irradiance
, weather
, temps
, aoi
,
aoi_modifier
, spectral_modifier
, dc
, ac
, losses
.
In [43]: mc.run_model??
Signature: mc.run_model(weather, times=None)
Source:
def run_model(self, weather, times=None):
"""
Run the model.
Parameters
----------
weather : DataFrame
Column names must be ``'dni'``, ``'ghi'``, ``'dhi'``,
``'wind_speed'``, ``'temp_air'``. All irradiance components
are required. Air temperature of 20 C and wind speed
of 0 m/s will be added to the DataFrame if not provided.
times : None, deprecated
Deprecated argument included for API compatibility, but not
used internally. The index of the weather DataFrame is used
for times.
Returns
-------
self
Assigns attributes: solar_position, airmass, irradiance,
total_irrad, effective_irradiance, weather, cell_temperature, aoi,
aoi_modifier, spectral_modifier, dc, ac, losses.
"""
if times is not None:
warnings.warn('times keyword argument is deprecated and will be '
'removed in 0.8. The index of the weather DataFrame '
'is used for times.', pvlibDeprecationWarning)
self.prepare_inputs(weather)
self.aoi_model()
self.spectral_model()
self.effective_irradiance_model()
self.temperature_model()
self.dc_model()
self.losses_model()
self.ac_model()
return self
File: ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py
Type: method
Finally, the complete_irradiance()
method is available for calculating the full set of GHI, DNI, or DHI if
only two of these three series are provided. The completed dataset can
then be passed to run_model()
.
Wrapping methods into a unified API¶
Readers may notice that the source code of the ModelChain.run_model
method is model-agnostic. ModelChain.run_model calls generic methods
such as self.dc_model
rather than a specific model such as
singlediode
. So how does the ModelChain.run_model know what models
it’s supposed to run? The answer comes in two parts, and allows us to
explore more of the ModelChain API along the way.
First, ModelChain has a set of methods that wrap the PVSystem methods
that perform the calculations (or further wrap the pvsystem.py module’s
functions). Each of these methods takes the same arguments (self
)
and sets the same attributes, thus creating a uniform API. For example,
the ModelChain.pvwatts_dc method is shown below. Its only argument is
self
, and it sets the dc
attribute.
In [44]: mc.pvwatts_dc??
Signature: mc.pvwatts_dc()
Docstring: <no docstring>
Source:
def pvwatts_dc(self):
self.dc = self.system.pvwatts_dc(self.effective_irradiance,
self.cell_temperature)
return self
File: ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py
Type: method
The ModelChain.pvwatts_dc method calls the pvwatts_dc method of the
PVSystem object that we supplied using data that is stored in its own
effective_irradiance
and cell_temperature
attributes. Then it assigns the
result to the dc
attribute of the ModelChain object. The code below
shows a simple example of this.
# make the objects
In [45]: pvwatts_system = PVSystem(
....: module_parameters={'pdc0': 240, 'gamma_pdc': -0.004},
....: inverter_parameters={'pdc0': 240},
....: temperature_model_parameters=temperature_model_parameters)
....:
In [46]: mc = ModelChain(pvwatts_system, location,
....: aoi_model='no_loss', spectral_model='no_loss')
....:
# manually assign data to the attributes that ModelChain.pvwatts_dc will need.
# for standard workflows, run_model would assign these attributes.
In [47]: mc.effective_irradiance = pd.Series(1000, index=[pd.Timestamp('20170401 1200-0700')])
In [48]: mc.cell_temperature = pd.Series(50, index=[pd.Timestamp('20170401 1200-0700')])
# run ModelChain.pvwatts_dc and look at the result
In [49]: mc.pvwatts_dc();
In [50]: mc.dc
Out[50]:
2017-04-01 12:00:00-07:00 216.0
dtype: float64
The ModelChain.sapm method works similarly to the ModelChain.pvwatts_dc
method. It calls the PVSystem.sapm method using stored data, then
assigns the result to the dc
attribute. The ModelChain.sapm method
differs from the ModelChain.pvwatts_dc method in three notable ways.
First, the PVSystem.sapm method expects different units for effective
irradiance, so ModelChain handles the conversion for us. Second, the
PVSystem.sapm method (and the PVSystem.singlediode method) returns a
DataFrame with current, voltage, and power parameters rather than a
simple Series of power. Finally, this current and voltage information
allows the SAPM and single diode model paths to support the concept of
modules in series and parallel, which is handled by the
PVSystem.scale_voltage_current_power method.
In [51]: mc.sapm??
Signature: mc.sapm()
Docstring: <no docstring>
Source:
def sapm(self):
self.dc = self.system.sapm(self.effective_irradiance/1000.,
self.cell_temperature)
self.dc = self.system.scale_voltage_current_power(self.dc)
return self
File: ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py
Type: method
# make the objects In [52]: sapm_system = PVSystem( ....: module_parameters=sandia_module, ....: inverter_parameters=cec_inverter, ....: temperature_model_parameters=temperature_model_parameters) ....: --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-52-13767fce5736> in <module> 1 sapm_system = PVSystem( 2 module_parameters=sandia_module, ----> 3 inverter_parameters=cec_inverter, 4 temperature_model_parameters=temperature_model_parameters) NameError: name 'cec_inverter' is not defined In [53]: mc = ModelChain(sapm_system, location) --------------------------------------------------------------------------- NameError Traceback (most recent call last) <ipython-input-53-9886b976fbf1> in <module> ----> 1 mc = ModelChain(sapm_system, location) NameError: name 'sapm_system' is not defined # manually assign data to the attributes that ModelChain.sapm will need. # for standard workflows, run_model would assign these attributes. In [54]: mc.effective_irradiance = pd.Series(1000, index=[pd.Timestamp('20170401 1200-0700')]) In [55]: mc.cell_temperature = pd.Series(50, index=[pd.Timestamp('20170401 1200-0700')]) # run ModelChain.sapm and look at the result In [56]: mc.sapm(); In [57]: mc.dc Out[57]: 2017-04-01 12:00:00-07:00 216.0 dtype: float64
We’ve established that the ModelChain.pvwatts_dc
and
ModelChain.sapm
have the same API: they take the same arugments
(self
) and they both set the dc
attribute.* Because the methods
have the same API, we can call them in the same way. ModelChain includes
a large number of methods that perform the same API-unification roles
for each modeling step.
Again, so how does the ModelChain.run_model know which models it’s supposed to run?
At object construction, ModelChain assigns the desired model’s method
(e.g. ModelChain.pvwatts_dc
) to the corresponding generic attribute
(e.g. ModelChain.dc_model
) using a method described in the next
section.
In [58]: pvwatts_system = PVSystem(
....: module_parameters={'pdc0': 240, 'gamma_pdc': -0.004},
....: inverter_parameters={'pdc0': 240},
....: temperature_model_parameters=temperature_model_parameters)
....:
In [59]: mc = ModelChain(pvwatts_system, location,
....: aoi_model='no_loss', spectral_model='no_loss')
....:
In [60]: mc.dc_model.__func__
Out[60]: <function pvlib.modelchain.ModelChain.pvwatts_dc(self)>
The ModelChain.run_model method can ignorantly call self.dc_module
because the API is the same for all methods that may be assigned to this
attribute.
* some readers may object that the API is not actually the same
because the type of the dc
attribute is different (Series
vs. DataFrame)!
Inferring models¶
How does ModelChain infer the appropriate model types? ModelChain uses a series of methods (ModelChain.infer_dc_model, ModelChain.infer_ac_model, etc.) that examine the user-supplied PVSystem object. The inference methods use set logic to assign one of the model-specific methods, such as ModelChain.sapm or ModelChain.snlinverter, to the universal method names ModelChain.dc_model and ModelChain.ac_model. A few examples are shown below.
In [61]: mc.infer_dc_model??
Signature: mc.infer_dc_model()
Docstring: <no docstring>
Source:
def infer_dc_model(self):
params = set(self.system.module_parameters.keys())
if set(['A0', 'A1', 'C7']) <= params:
return self.sapm, 'sapm'
elif set(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
'R_s', 'Adjust']) <= params:
return self.cec, 'cec'
elif set(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref',
'R_s']) <= params:
return self.desoto, 'desoto'
elif set(['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref',
'R_sh_ref', 'R_sh_0', 'R_sh_exp', 'R_s']) <= params:
return self.pvsyst, 'pvsyst'
elif set(['pdc0', 'gamma_pdc']) <= params:
return self.pvwatts_dc, 'pvwatts'
else:
raise ValueError('could not infer DC model from '
'system.module_parameters. Check '
'system.module_parameters or explicitly '
'set the model with the dc_model kwarg.')
File: ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py
Type: method
In [62]: mc.infer_ac_model??
Signature: mc.infer_ac_model()
Docstring: <no docstring>
Source:
def infer_ac_model(self):
inverter_params = set(self.system.inverter_parameters.keys())
if set(['C0', 'C1', 'C2']) <= inverter_params:
return self.snlinverter
elif set(['ADRCoefficients']) <= inverter_params:
return self.adrinverter
elif set(['pdc0']) <= inverter_params:
return self.pvwatts_inverter
else:
raise ValueError('could not infer AC model from '
'system.inverter_parameters. Check '
'system.inverter_parameters or explicitly '
'set the model with the ac_model kwarg.')
File: ~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/checkouts/stable/pvlib/modelchain.py
Type: method
User-defined models¶
Users may also write their own functions and pass them as arguments to ModelChain. The first argument of the function must be a ModelChain instance. For example, the functions below implement the PVUSA model and a wrapper function appropriate for use with ModelChain. This follows the pattern of implementing the core models using the simplest possible functions, and then implementing wrappers to make them easier to use in specific applications. Of course, you could implement it in a single function if you wanted to.
In [63]: def pvusa(poa_global, wind_speed, temp_air, a, b, c, d):
....: """
....: Calculates system power according to the PVUSA equation
....: P = I * (a + b*I + c*W + d*T)
....: where
....: P is the output power,
....: I is the plane of array irradiance,
....: W is the wind speed, and
....: T is the temperature
....: a, b, c, d are empirically derived parameters.
....: """
....: return poa_global * (a + b*poa_global + c*wind_speed + d*temp_air)
....:
In [64]: def pvusa_mc_wrapper(mc):
....: mc.dc = pvusa(mc.total_irrad['poa_global'], mc.weather['wind_speed'], mc.weather['temp_air'],
....: mc.system.module_parameters['a'], mc.system.module_parameters['b'],
....: mc.system.module_parameters['c'], mc.system.module_parameters['d'])
....:
# returning mc is optional, but enables method chaining
In [65]: def pvusa_ac_mc(mc):
....: mc.ac = mc.dc
....: return mc
....:
In [66]: def no_loss_temperature(mc):
....: mc.cell_temperature = mc.weather['temp_air']
....: return mc
....:
In [67]: module_parameters = {'a': 0.2, 'b': 0.00001, 'c': 0.001, 'd': -0.00005}
In [68]: pvusa_system = PVSystem(module_parameters=module_parameters)
In [69]: mc = ModelChain(pvusa_system, location,
....: dc_model=pvusa_mc_wrapper, ac_model=pvusa_ac_mc,
....: temperature_model=no_loss_temperature,
....: aoi_model='no_loss', spectral_model='no_loss')
....:
A ModelChain object uses Python’s functools.partial function to assign itself as the argument to the user-supplied functions.
In [70]: mc.dc_model.func
Out[70]: <function __main__.pvusa_mc_wrapper(mc)>
The end result is that ModelChain.run_model works as expected!
In [71]: mc.run_model(times=weather.index, weather=weather);
In [72]: mc.dc
Out[72]:
2017-04-01 12:00:00-07:00 209.519752
dtype: float64
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]: 592 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/Sitka', 'Antarctica/Casey', 'Asia/Ashkhabad', 'Asia/Dubai', 'Asia/Khandyga', 'Asia/Qatar', 'Asia/Tomsk', 'Atlantic/Reykjavik', 'Australia/Queensland', 'Canada/Yukon', 'Etc/GMT+7', 'Etc/UCT', 'Europe/Guernsey', 'Europe/Paris', 'Europe/Vienna', 'Indian/Cocos', 'NZ', 'Pacific/Honolulu', 'Pacific/Samoa', 'US/Eastern']
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]:
['America/New_York',
'America/Detroit',
'America/Kentucky/Louisville',
'America/Kentucky/Monticello',
'America/Indiana/Indianapolis',
'America/Indiana/Vincennes',
'America/Indiana/Winamac',
'America/Indiana/Marengo',
'America/Indiana/Petersburg',
'America/Indiana/Vevay',
'America/Chicago',
'America/Indiana/Tell_City',
'America/Indiana/Knox',
'America/Menominee',
'America/North_Dakota/Center',
'America/North_Dakota/New_Salem',
'America/North_Dakota/Beulah',
'America/Denver',
'America/Boise',
'America/Phoenix',
'America/Los_Angeles',
'America/Anchorage',
'America/Juneau',
'America/Sitka',
'America/Metlakatla',
'America/Yakutat',
'America/Nome',
'America/Adak',
'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-f99dcf3885a1> in <module>
----> 1 midnight.tz_convert('UTC')
pandas/_libs/tslibs/timestamps.pyx in pandas._libs.tslibs.timestamps.Timestamp.tz_convert()
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-c4cced4ef9d9> in <module>
----> 1 pytz.timezone('US/Mountain').localize(naive_python_date)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pytz/tzinfo.py in localize(self, dt, is_dst)
315 Non-existent
316 '''
--> 317 if dt.tzinfo is not None:
318 raise ValueError('Not naive datetime (tzinfo is already set)')
319
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.iotools.read_tmy3(file_abspath)
In [44]: tmy3_metadata
Out[44]:
{'USAF': 703165,
'Name': '"SAND POINT"',
'State': 'AK',
'TZ': -9.0,
'latitude': 55.317,
'longitude': -160.517,
'altitude': 7.0}
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.loc[tmy3_data.index[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.loc[solar_position.index[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.loc[solar_position_notz.index[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.loc[solar_position_hack.index[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. The Detect Clearsky subsection demonstrates the use of the clear sky detection algorithm.
We’ll need these imports for the examples below.
In [1]: import os
In [2]: import itertools
In [3]: import matplotlib.pyplot as plt
In [4]: import pandas as pd
In [5]: import pvlib
In [6]: from pvlib import clearsky, atmosphere, solarposition
In [7]: from pvlib.location import Location
In [8]: from pvlib.iotools import read_tmy3
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.date_range(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 tables
In [26]: pvlib_path = os.path.dirname(os.path.abspath(pvlib.clearsky.__file__))
In [27]: filepath = os.path.join(pvlib_path, 'data', 'LinkeTurbidities.h5')
In [28]: def plot_turbidity_map(month, vmin=1, vmax=100):
....: plt.figure();
....: with tables.open_file(filepath) as lt_h5_file:
....: ltdata = lt_h5_file.root.LinkeTurbidity[:, :, month-1]
....: plt.imshow(ltdata, vmin=vmin, vmax=vmax);
....: # data is in units of 20 x turbidity
....: plt.title('Linke turbidity x 20, ' + calendar.month_name[month]);
....: plt.colorbar(shrink=0.5);
....: plt.tight_layout();
....:
In [29]: plot_turbidity_map(1)
In [30]: 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 [31]: times = pd.date_range(start='2015-01-01', end='2016-01-01', freq='1D')
In [32]: sites = [(32, -111, 'Tucson1'), (32.2, -110.9, 'Tucson2'),
....: (33.5, -112.1, 'Phoenix'), (35.1, -106.6, 'Albuquerque')]
....:
In [33]: plt.figure();
In [34]: for lat, lon, name in sites:
....: turbidity = pvlib.clearsky.lookup_linke_turbidity(times, lat, lon, interp_turbidity=False)
....: turbidity.plot(label=name)
....:
In [35]: plt.legend();
In [36]: plt.title('Raw data (no interpolation)');
In [37]: plt.ylabel('Linke Turbidity');
In [38]: plt.figure();
In [39]: for lat, lon, name in sites:
....: turbidity = pvlib.clearsky.lookup_linke_turbidity(times, lat, lon)
....: turbidity.plot(label=name)
....:
In [40]: plt.legend();
In [41]: plt.title('Interpolated to the day');
In [42]: plt.ylabel('Linke Turbidity');


The kasten96_lt()
function can be used to calculate
Linke turbidity [Kas96] as input to the clear sky Ineichen and Perez function.
The Kasten formulation requires precipitable water and broadband aerosol
optical depth (AOD). According to Molineaux, broadband AOD can be approximated
by a single measurement at 700-nm [Mol98]. An alternate broadband AOD
approximation from Bird and Hulstrom combines AOD measured at two
wavelengths [Bir80], and is implemented in
bird_hulstrom80_aod_bb()
.
In [43]: pvlib_data = os.path.join(os.path.dirname(pvlib.__file__), 'data')
In [44]: mbars = 100 # conversion factor from mbars to Pa
In [45]: tmy_file = os.path.join(pvlib_data, '703165TY.csv') # TMY file
In [46]: tmy_data, tmy_header = read_tmy3(tmy_file, coerce_year=1999) # read TMY data
In [47]: tl_historic = clearsky.lookup_linke_turbidity(time=tmy_data.index,
....: latitude=tmy_header['latitude'], longitude=tmy_header['longitude'])
....:
In [48]: solpos = solarposition.get_solarposition(time=tmy_data.index,
....: latitude=tmy_header['latitude'], longitude=tmy_header['longitude'],
....: altitude=tmy_header['altitude'], pressure=tmy_data['Pressure']*mbars,
....: temperature=tmy_data['DryBulb'])
....:
In [49]: am_rel = atmosphere.get_relative_airmass(solpos.apparent_zenith)
In [50]: am_abs = atmosphere.get_absolute_airmass(am_rel, tmy_data['Pressure']*mbars)
In [51]: airmass = pd.concat([am_rel, am_abs], axis=1).rename(
....: columns={0: 'airmass_relative', 1: 'airmass_absolute'})
....:
In [52]: tl_calculated = atmosphere.kasten96_lt(
....: airmass.airmass_absolute, tmy_data['Pwat'], tmy_data['AOD'])
....:
In [53]: tl = pd.concat([tl_historic, tl_calculated], axis=1).rename(
....: columns={0:'Historic', 1:'Calculated'})
....:
In [54]: tl.index = tmy_data.index.tz_convert(None) # remove timezone
In [55]: tl.resample('W').mean().plot();
In [56]: plt.grid()
In [57]: plt.title('Comparison of Historic Linke Turbidity Factors vs. \n'
....: 'Kasten Pyrheliometric Formula at {name:s}, {state:s} ({usaf:d}TY)'.format(
....: name=tmy_header['Name'], state=tmy_header['State'], usaf=tmy_header['USAF']));
....:
In [58]: plt.ylabel('Linke Turbidity Factor, TL');
In [59]: plt.tight_layout()

Examples¶
A clear sky time series using only basic pvlib functions.
In [60]: latitude, longitude, tz, altitude, name = 32.2, -111, 'US/Arizona', 700, 'Tucson'
In [61]: times = pd.date_range(start='2014-01-01', end='2014-01-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.get_relative_airmass(apparent_zenith)
In [65]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [66]: airmass = pvlib.atmosphere.get_absolute_airmass(airmass, pressure)
In [67]: linke_turbidity = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
In [68]: dni_extra = pvlib.irradiance.get_extra_radiation(times)
# an input is a pandas Series, so solis is a DataFrame
In [69]: ineichen = clearsky.ineichen(apparent_zenith, airmass, linke_turbidity, altitude, dni_extra)
In [70]: plt.figure();
In [71]: ax = ineichen.plot()
In [72]: ax.set_ylabel('Irradiance $W/m^2$');
In [73]: ax.set_title('Ineichen Clear Sky Model');
In [74]: ax.legend(loc=2);

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 [75]: times = pd.date_range(start='2014-09-01', end='2014-09-02', freq='1Min', tz=tz)
In [76]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [77]: apparent_zenith = solpos['apparent_zenith']
In [78]: airmass = pvlib.atmosphere.get_relative_airmass(apparent_zenith)
In [79]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [80]: airmass = pvlib.atmosphere.get_absolute_airmass(airmass, pressure)
In [81]: linke_turbidity = pvlib.clearsky.lookup_linke_turbidity(times, latitude, longitude)
In [82]: print('climatological linke_turbidity = {}'.format(linke_turbidity.mean()))
climatological linke_turbidity = 3.329496820286459
In [83]: dni_extra = pvlib.irradiance.get_extra_radiation(times)
In [84]: linke_turbidities = [linke_turbidity.mean(), 2, 4]
In [85]: fig, axes = plt.subplots(ncols=3, nrows=1, sharex=True, sharey=True, squeeze=True, figsize=(12, 4))
In [86]: axes = axes.flatten()
In [87]: 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 [88]: ax.legend(loc=1);

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 (AOD) is a function of wavelength, and the Simplified
Solis model requires AOD at 700 nm.
angstrom_aod_at_lambda()
is useful for converting
AOD between different wavelengths using the Angstrom turbidity model. The
Angstrom exponent, \(\alpha\), can be calculated from AOD at two
wavelengths with angstrom_alpha()
.
[Ine08con], [Ine16], [Ang61].
In [89]: aod1240nm = 1.2 # fictitious AOD measured at 1240-nm
In [90]: aod550nm = 3.1 # fictitious AOD measured at 550-nm
In [91]: alpha_exponent = atmosphere.angstrom_alpha(aod1240nm, 1240, aod550nm, 550)
In [92]: aod700nm = atmosphere.angstrom_aod_at_lambda(aod1240nm, 1240, alpha_exponent, 700)
In [93]: aod380nm = atmosphere.angstrom_aod_at_lambda(aod550nm, 550, alpha_exponent, 380)
In [94]: aod500nm = atmosphere.angstrom_aod_at_lambda(aod550nm, 550, alpha_exponent, 500)
In [95]: aod_bb = atmosphere.bird_hulstrom80_aod_bb(aod380nm, aod500nm)
In [96]: print('compare AOD at 700-nm = {:g}, to estimated broadband AOD = {:g}, '
....: 'with alpha = {:g}'.format(aod700nm, aod_bb, alpha_exponent))
....:
compare AOD at 700-nm = 2.33931, to estimated broadband AOD = 2.52936, with alpha = 1.16745
Examples¶
A clear sky time series using only basic pvlib functions.
In [97]: latitude, longitude, tz, altitude, name = 32.2, -111, 'US/Arizona', 700, 'Tucson'
In [98]: times = pd.date_range(start='2014-01-01', end='2014-01-02', freq='1Min', tz=tz)
In [99]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [100]: apparent_elevation = solpos['apparent_elevation']
In [101]: aod700 = 0.1
In [102]: precipitable_water = 1
In [103]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [104]: dni_extra = pvlib.irradiance.get_extra_radiation(times)
# an input is a Series, so solis is a DataFrame
In [105]: solis = clearsky.simplified_solis(apparent_elevation, aod700, precipitable_water,
.....: pressure, dni_extra)
.....:
In [106]: ax = solis.plot();
In [107]: ax.set_ylabel('Irradiance $W/m^2$');
In [108]: ax.set_title('Simplified Solis Clear Sky Model');
In [109]: ax.legend(loc=2);

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 [110]: apparent_elevation = pd.Series(np.linspace(-10, 90, 101))
In [111]: aod700 = 0.1
In [112]: precipitable_water = 1
In [113]: pressure = 101325
In [114]: dni_extra = 1364
In [115]: solis = clearsky.simplified_solis(apparent_elevation, aod700,
.....: precipitable_water, pressure, dni_extra)
.....:
In [116]: ax = solis.plot();
In [117]: ax.set_xlabel('Apparent elevation (deg)');
In [118]: ax.set_ylabel('Irradiance $W/m^2$');
In [119]: ax.set_title('Irradiance vs Solar Elevation')
Out[119]: Text(0.5, 1.0, 'Irradiance vs Solar Elevation')
In [120]: ax.legend(loc=2);

Grid with clear sky irradiance for a few PW and AOD values.
In [121]: times = pd.date_range(start='2014-09-01', end='2014-09-02', freq='1Min', tz=tz)
In [122]: solpos = pvlib.solarposition.get_solarposition(times, latitude, longitude)
In [123]: apparent_elevation = solpos['apparent_elevation']
In [124]: pressure = pvlib.atmosphere.alt2pres(altitude)
In [125]: dni_extra = pvlib.irradiance.get_extra_radiation(times)
In [126]: aod700 = [0.01, 0.1]
In [127]: precipitable_water = [0.5, 5]
In [128]: fig, axes = plt.subplots(ncols=2, nrows=2, sharex=True, sharey=True, squeeze=True)
In [129]: axes = axes.flatten()
In [130]: 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))
.....:

Contour plots of irradiance as a function of both PW and AOD.
In [131]: aod700 = np.linspace(0, 0.5, 101)
In [132]: precipitable_water = np.linspace(0, 10, 101)
In [133]: apparent_elevation = 70
In [134]: pressure = 101325
In [135]: dni_extra = 1364
In [136]: aod700, precipitable_water = np.meshgrid(aod700, precipitable_water)
# inputs are arrays, so solis is an OrderedDict
In [137]: solis = clearsky.simplified_solis(apparent_elevation, aod700,
.....: precipitable_water, pressure,
.....: dni_extra)
.....:
In [138]: n = 15
In [139]: vmin = None
In [140]: vmax = None
In [141]: def plot_solis(key):
.....: irrad = solis[key]
.....: fig, ax = plt.subplots()
.....: im = ax.contour(aod700, precipitable_water, irrad[:, :], n, vmin=vmin, vmax=vmax)
.....: imf = ax.contourf(aod700, precipitable_water, irrad[:, :], n, 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 [142]: plot_solis('ghi')
In [143]: plot_solis('dni')
In [144]: plot_solis('dhi')



Validation¶
See [Ine16].
We encourage users to compare the pvlib implementation to Ineichen’s Excel tool.
Detect Clearsky¶
The detect_clearsky()
function implements the
[Ren16] algorithm to detect the clear and cloudy points of a time
series. The algorithm was designed and validated for analyzing GHI time
series only. Users may attempt to apply it to other types of time series
data using different filter settings, but should be skeptical of the
results.
The algorithm detects clear sky times by comparing statistics for a measured time series and an expected clearsky time series. Statistics are calculated using a sliding time window (e.g., 10 minutes). An iterative algorithm identifies clear periods, uses the identified periods to estimate bias in the clearsky data, scales the clearsky data and repeats.
Clear times are identified by meeting 5 criteria. Default values for these thresholds are appropriate for 10 minute windows of 1 minute GHI data.
Next, we show a simple example of applying the algorithm to synthetic GHI data. We first generate and plot the clear sky and measured data.
In [145]: abq = Location(35.04, -106.62, altitude=1619)
In [146]: times = pd.date_range(start='2012-04-01 10:30:00', tz='Etc/GMT+7', periods=30, freq='1min')
In [147]: cs = abq.get_clearsky(times)
# scale clear sky data to account for possibility of different turbidity
In [148]: ghi = cs['ghi']*.953
# add a cloud event
In [149]: ghi['2012-04-01 10:42:00':'2012-04-01 10:44:00'] = [500, 300, 400]
# add an overirradiance event
In [150]: ghi['2012-04-01 10:56:00'] = 950
In [151]: fig, ax = plt.subplots()
In [152]: ghi.plot(label='input');
In [153]: cs['ghi'].plot(label='ineichen clear');
In [154]: ax.set_ylabel('Irradiance $W/m^2$');
In [155]: plt.legend(loc=4);

Now we run the synthetic data and clear sky estimate through the
detect_clearsky()
function.
In [156]: clear_samples = clearsky.detect_clearsky(ghi, cs['ghi'], cs.index, 10)
In [157]: fig, ax = plt.subplots()
In [158]: clear_samples.astype(int).plot();
In [159]: ax.set_ylabel('Clear (1) or Cloudy (0)');

The algorithm detected the cloud event and the overirradiance event.
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.
- Ren16
Reno, M.J. and C.W. Hansen, “Identification of periods of clear sky irradiance in time series of GHI measurements” Renewable Energy, v90, p. 520-531, 2016.
- Mol98
B. Molineaux, P. Ineichen, and N. O’Neill, “Equivalence of pyrheliometric and monochromatic aerosol optical depths at a single key wavelength.,” Appl. Opt., vol. 37, no. 30, pp. 7008–18, Oct. 1998.
- Kas96
F. Kasten, “The linke turbidity factor based on improved values of the integral Rayleigh optical thickness,” Sol. Energy, vol. 56, no. 3, pp. 239–244, Mar. 1996.
- Bir80
R. E. Bird and R. L. Hulstrom, “Direct Insolation Models,” 1980.
- Ang61
A. ÅNGSTRÖM, “Techniques of Determinig the Turbidity of the Atmosphere,” Tellus A, vol. 13, no. 2, pp. 214–223, 1961.
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 real-time 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.
We do not know of a similarly easy way to access archives of forecast data.
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
# import pvlib forecast models
In [4]: from pvlib.forecast import GFS, NAM, NDFD, HRRR, RAP
# specify location (Tucson, AZ)
In [5]: latitude, longitude, tz = 32.2, -110.9, 'US/Arizona'
# specify time range.
In [6]: start = pd.Timestamp(datetime.date.today(), tz=tz)
In [7]: end = start + pd.Timedelta(days=7)
In [8]: 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 [9]: model = GFS()
# retrieve data. returns pandas.DataFrame object
In [10]: raw_data = model.get_data(latitude, longitude, start, end)
In [11]: print(raw_data.head())
Total_cloud_cover_high_cloud_Mixed_intervals_Average ... v-component_of_wind_isobaric
2019-10-10 09:00:00-07:00 0.0 ... 5.378662
2019-10-10 12:00:00-07:00 0.0 ... 3.623459
2019-10-10 15:00:00-07:00 0.0 ... 2.341147
2019-10-10 18:00:00-07:00 0.0 ... 2.289145
2019-10-10 21:00:00-07:00 0.0 ... 2.377673
[5 rows x 11 columns]
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 [12]: data = raw_data
# rename the columns according the key/value pairs in model.variables.
In [13]: data = model.rename(data)
# convert temperature
In [14]: data['temp_air'] = model.kelvin_to_celsius(data['temp_air'])
# convert wind components to wind speed
In [15]: data['wind_speed'] = model.uv_to_speed(data)
# 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 [16]: irrad_data = model.cloud_cover_to_irradiance(data['total_clouds'])
In [17]: data = data.join(irrad_data, how='outer')
# keep only the final data
In [18]: data = data[model.output_variables]
In [19]: print(data.head())
temp_air wind_speed ... mid_clouds high_clouds
2019-10-10 09:00:00-07:00 15.750000 5.856925 ... 0.0 0.0
2019-10-10 12:00:00-07:00 14.449982 3.907449 ... 0.0 0.0
2019-10-10 15:00:00-07:00 22.050018 2.634409 ... 0.0 0.0
2019-10-10 18:00:00-07:00 37.582275 5.424297 ... 0.0 0.0
2019-10-10 21:00:00-07:00 38.550018 6.207394 ... 0.0 0.0
[5 rows x 9 columns]
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 [20]: data = model.process_data(raw_data)
In [21]: print(data.head())
temp_air wind_speed ... mid_clouds high_clouds
2019-10-10 09:00:00-07:00 15.750000 5.856925 ... 0.0 0.0
2019-10-10 12:00:00-07:00 14.449982 3.907449 ... 0.0 0.0
2019-10-10 15:00:00-07:00 22.050018 2.634409 ... 0.0 0.0
2019-10-10 18:00:00-07:00 37.582275 5.424297 ... 0.0 0.0
2019-10-10 21:00:00-07:00 38.550018 6.207394 ... 0.0 0.0
[5 rows x 9 columns]
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 [22]: data = model.get_processed_data(latitude, longitude, start, end)
In [23]: print(data.head())
temp_air wind_speed ... mid_clouds high_clouds
2019-10-10 09:00:00-07:00 15.750000 5.856925 ... 0.0 0.0
2019-10-10 12:00:00-07:00 14.449982 3.907449 ... 0.0 0.0
2019-10-10 15:00:00-07:00 22.050018 2.634409 ... 0.0 0.0
2019-10-10 18:00:00-07:00 37.582275 5.424297 ... 0.0 0.0
2019-10-10 21:00:00-07:00 38.550018 6.207394 ... 0.0 0.0
[5 rows x 9 columns]
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 [24]: cloud_vars = ['total_clouds', 'low_clouds',
....: 'mid_clouds', 'high_clouds']
....:
In [25]: data[cloud_vars].plot();
In [26]: plt.ylabel('Cloud cover %');
In [27]: plt.xlabel('Forecast Time ({})'.format(tz));
In [28]: plt.title('GFS 0.5 deg forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [29]: 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 [30]: data = model.rename(raw_data)
In [31]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='clearsky_scaling')
In [32]: irrads.plot();
In [33]: plt.ylabel('Irradiance ($W/m^2$)');
In [34]: plt.xlabel('Forecast Time ({})'.format(tz));
In [35]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "clearsky_scaling"'
....: .format(latitude, longitude));
....:
In [36]: 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 [37]: irrads = model.cloud_cover_to_irradiance(data['total_clouds'], how='liujordan')
In [38]: irrads.plot();
In [39]: plt.ylabel('Irradiance ($W/m^2$)');
In [40]: plt.xlabel('Forecast Time ({})'.format(tz));
In [41]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} using "liujordan"'
....: .format(latitude, longitude));
....:
In [42]: 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 [43]: resampled_data = data.resample('5min').interpolate()
In [44]: resampled_irrads = model.cloud_cover_to_irradiance(resampled_data['total_clouds'], how='clearsky_scaling')
In [45]: resampled_irrads.plot();
In [46]: plt.ylabel('Irradiance ($W/m^2$)');
In [47]: plt.xlabel('Forecast Time ({})'.format(tz));
In [48]: plt.title('GFS 0.5 deg forecast for lat={}, lon={} resampled'
....: .format(latitude, longitude));
....:
In [49]: 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. 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 [50]: model = HRRR()
In [51]: data = model.get_processed_data(latitude, longitude, start, end)
In [52]: data[irrad_vars].plot();
In [53]: plt.ylabel('Irradiance ($W/m^2$)');
In [54]: plt.xlabel('Forecast Time ({})'.format(tz));
In [55]: plt.title('HRRR 3 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [56]: 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. 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 [57]: model = RAP()
In [58]: data = model.get_processed_data(latitude, longitude, start, end)
In [59]: data[irrad_vars].plot();
In [60]: plt.ylabel('Irradiance ($W/m^2$)');
In [61]: plt.xlabel('Forecast Time ({})'.format(tz));
In [62]: plt.title('RAP 13 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [63]: 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 [64]: model = NAM()
In [65]: data = model.get_processed_data(latitude, longitude, start, end)
In [66]: data[irrad_vars].plot();
In [67]: plt.ylabel('Irradiance ($W/m^2$)');
In [68]: plt.xlabel('Forecast Time ({})'.format(tz));
In [69]: plt.title('NAM 20 km forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....:
In [70]: 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 [71]: model = NDFD()
In [72]: data = model.get_processed_data(latitude, longitude, start, end)
In [73]: data[irrad_vars].plot();
In [74]: plt.ylabel('Irradiance ($W/m^2$)');
In [75]: plt.xlabel('Forecast Time ({})'.format(tz));
In [76]: plt.title('NDFD forecast for lat={}, lon={}'
....: .format(latitude, longitude));
....: plt.legend();
....: plt.close();
....:
File "<ipython-input-76-1c7cdda0217f>", line 3
plt.legend();
^
IndentationError: unexpected indent
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 [77]: from pvlib.pvsystem import PVSystem, retrieve_sam
In [78]: from pvlib.temperature import TEMPERATURE_MODEL_PARAMETERS
In [79]: from pvlib.tracking import SingleAxisTracker
In [80]: from pvlib.modelchain import ModelChain
In [81]: sandia_modules = retrieve_sam('sandiamod')
In [82]: cec_inverters = retrieve_sam('cecinverter')
In [83]: module = sandia_modules['Canadian_Solar_CS5P_220M___2009_']
In [84]: inverter = cec_inverters['SMA_America__SC630CP_US_315V__CEC_2012_']
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2896 try:
-> 2897 return self._engine.get_loc(key)
2898 except KeyError:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'SMA_America__SC630CP_US_315V__CEC_2012_'
During handling of the above exception, another exception occurred:
KeyError Traceback (most recent call last)
<ipython-input-84-857f3df47609> in <module>
----> 1 inverter = cec_inverters['SMA_America__SC630CP_US_315V__CEC_2012_']
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/frame.py in __getitem__(self, key)
2978 if self.columns.nlevels > 1:
2979 return self._getitem_multilevel(key)
-> 2980 indexer = self.columns.get_loc(key)
2981 if is_integer(indexer):
2982 indexer = [indexer]
~/checkouts/readthedocs.org/user_builds/tylunelpvlib-python/envs/stable/lib/python3.7/site-packages/pandas/core/indexes/base.py in get_loc(self, key, method, tolerance)
2897 return self._engine.get_loc(key)
2898 except KeyError:
-> 2899 return self._engine.get_loc(self._maybe_cast_indexer(key))
2900 indexer = self.get_indexer([key], method=method, tolerance=tolerance)
2901 if indexer.ndim > 1 or indexer.size > 1:
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/index.pyx in pandas._libs.index.IndexEngine.get_loc()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
pandas/_libs/hashtable_class_helper.pxi in pandas._libs.hashtable.PyObjectHashTable.get_item()
KeyError: 'SMA_America__SC630CP_US_315V__CEC_2012_'
In [85]: temperature_model_parameters = TEMPERATURE_MODEL_PARAMETERS['sapm']['open_rack_glass_glass']
# model a big tracker for more fun
In [86]: system = SingleAxisTracker(module_parameters=module, inverter_parameters=inverter, temperature_model_parameters=temperature_model_parameters, modules_per_string=15, strings_per_inverter=300)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-86-ca98666f4ea5> in <module>
----> 1 system = SingleAxisTracker(module_parameters=module, inverter_parameters=inverter, temperature_model_parameters=temperature_model_parameters, modules_per_string=15, strings_per_inverter=300)
NameError: name 'inverter' is not defined
# fx is a common abbreviation for forecast
In [87]: fx_model = GFS()
In [88]: fx_data = fx_model.get_processed_data(latitude, longitude, start, end)
# 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-a32dd34c6acb> in <module>
----> 1 mc = ModelChain(system, fx_model.location)
NameError: name 'system' is not defined
# extract relevant data for model chain
In [90]: mc.run_model(fx_data.index, weather=fx_data);
Now we plot a couple of modeling intermediates and the forecast power. Here’s the forecast plane of array irradiance…
In [91]: mc.total_irrad.plot();
In [92]: plt.ylabel('Plane of array irradiance ($W/m^2$)');
In [93]: plt.legend(loc='best');

…the cell and module temperature…
In [94]: mc.cell_temperature.plot();
In [95]: plt.ylabel('Cell Temperature (C)');

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

API reference¶
Classes¶
pvlib-python provides a collection of classes for users that prefer object-oriented programming. These classes can help users keep track of data in a more organized way, and can help to simplify the modeling process. The classes do not add any functionality beyond the procedural code. Most of the object methods are simple wrappers around the corresponding procedural code.
|
Location objects are convenient containers for latitude, longitude, timezone, and altitude data associated with a particular geographic location. |
|
The PVSystem class defines a standard set of PV system attributes and modeling functions. |
|
Inherits the PV modeling methods from |
|
The ModelChain class to provides a standardized, high-level interface for all of the modeling steps necessary for calculating PV power from a time series of weather inputs. |
|
The LocalizedPVSystem class defines a standard set of installed PV system attributes and modeling functions. |
The LocalizedSingleAxisTracker class defines a standard set of installed PV system attributes and modeling functions. |
Solar Position¶
Functions and methods for calculating solar position.
The location.Location.get_solarposition()
method and the
solarposition.get_solarposition()
function with default
parameters are fast and accurate. We recommend using these functions
unless you know that you need a different function.
|
Uses the |
|
A convenience wrapper for the solar position calculators. |
|
Calculate the solar position using a python implementation of the NREL SPA algorithm described in [1]. |
|
Python-native solar position calculator. |
|
Calculate the solar position using the PyEphem package. |
|
Calculate the solar position using the C implementation of the NREL SPA code. |
Additional functions for quantities closely related to solar position.
|
Calculate the time between lower_bound and upper_bound where the attribute is equal to value. |
Calculates the distance from the earth to the sun using pyephem. |
|
|
Calculates the distance from the earth to the sun using the NREL SPA algorithm described in [1]_. |
|
Calculate the difference between Terrestrial Dynamical Time (TD) and Universal Time (UT). |
Functions for calculating sunrise, sunset and transit times.
Calculate sunrise, sunset and transit times. |
|
Calculate the next sunrise and sunset times using the PyEphem package. |
|
Calculate the sunrise, sunset, and sun transit times using the NREL SPA algorithm described in [1]. |
|
Geometric calculation of solar sunrise, sunset, and transit. |
The spa module contains the implementation of the built-in NREL SPA algorithm.
Calculate the solar position using the NREL SPA algorithm either using numpy arrays or compiling the code to machine language with numba. |
Correlations and analytical expressions for low precision solar position calculations.
Analytical expression of solar zenith angle based on spherical trigonometry. |
|
Analytical expression of solar azimuth angle based on spherical trigonometry. |
|
|
Solar declination from Duffie & Beckman [1] and attributed to Spencer (1971) and Iqbal (1983). |
|
Solar declination from Duffie & Beckman [1] and attributed to Cooper (1969) |
Equation of time from Duffie & Beckman and attributed to Spencer (1971) and Iqbal (1983). |
|
|
Equation of time from PVCDROM. |
|
Hour angle in local solar time. |
Geometric calculation of solar sunrise, sunset, and transit. |
Clear sky¶
|
Calculate the clear sky estimates of GHI, DNI, and/or DHI at this location. |
|
Determine clear sky GHI, DNI, and DHI from Ineichen/Perez model. |
|
Look up the Linke Turibidity from the |
|
Calculate the clear sky GHI, DNI, and DHI according to the simplified Solis model [1]_. |
|
Determine clear sky GHI using the Haurwitz model. |
|
Detects clear sky times according to the algorithm developed by Reno and Hansen for GHI measurements [1]. |
|
Bird Simple Clear Sky Broadband Solar Radiation Model |
Airmass and atmospheric models¶
|
Calculate the relative and absolute airmass. |
|
Determine absolute (pressure corrected) airmass from relative airmass and pressure |
|
Gives the relative (not pressure-corrected) airmass. |
|
Determine altitude from site pressure. |
|
Determine site pressure from altitude. |
|
Calculates precipitable water (cm) from ambient air temperature (C) and relatively humidity (%) using an empirical model. |
Spectral mismatch modifier based on precipitable water and absolute (pressure corrected) airmass. |
|
|
Approximate broadband aerosol optical depth. |
|
Calculate Linke turbidity factor using Kasten pyrheliometric formula. |
|
Get AOD at specified wavelength using Angstrom turbidity model. |
|
Calculate Angstrom alpha exponent. |
Irradiance¶
Methods for irradiance calculations¶
|
Uses the |
|
Get the angle of incidence on the system. |
Uses the |
Decomposing and combining irradiance¶
|
Determine extraterrestrial radiation from day of year. |
|
Calculates the angle of incidence of the solar vector on a surface. |
|
Calculates the dot product of the sun position unit vector and the surface normal unit vector; in other words, the cosine of the angle of incidence. |
Calculates the ratio of the beam components of the plane of array irradiance and the horizontal irradiance. |
|
|
Calculates the beam component of the plane of array irradiance. |
|
Determine in-plane irradiance components. |
|
Estimate diffuse irradiance from ground reflections given irradiance, albedo, and surface tilt |
|
Determine DNI from GHI and DHI. |
Transposition models¶
|
Determine total in-plane irradiance and its beam, sky diffuse and ground reflected components, using the specified sky diffuse irradiance model. |
|
Determine in-plane sky diffuse irradiance component using the specified sky diffuse irradiance model. |
|
Determine diffuse irradiance from the sky on a tilted surface using the isotropic sky model. |
|
Determine diffuse irradiance from the sky on a tilted surface using one of the Perez models. |
|
Determine diffuse irradiance from the sky on a tilted surface using Hay & Davies’ 1980 model |
|
Determine diffuse irradiance from the sky on a tilted surface using Klucher’s 1979 model |
|
Determine diffuse irradiance from the sky on a tilted surface using Reindl’s 1990 model |
|
Determine diffuse irradiance from the sky on a tilted surface using the King model. |
DNI estimation models¶
|
Estimate Direct Normal Irradiance from Global Horizontal Irradiance using the DISC model. |
|
Determine DNI from GHI using the DIRINT modification of the DISC model. |
|
Determine DNI from GHI using the DIRINDEX model. |
|
Estimate DNI and DHI from GHI using the Erbs model. |
|
Determine DNI, DHI, GHI from extraterrestrial flux, transmittance, and optical air mass number. |
|
Determine GHI, DNI, DHI from POA global using the GTI DIRINT model. |
Clearness index models¶
|
Calculate the clearness index. |
Calculate the zenith angle independent clearness index. |
|
|
Calculate the clearsky index. |
PV Modeling¶
Classes¶
The PVSystem
class provides many methods that
wrap the functions listed below. See its documentation for details.
|
The PVSystem class defines a standard set of PV system attributes and modeling functions. |
|
The LocalizedPVSystem class defines a standard set of installed PV system attributes and modeling functions. |
AOI modifiers¶
|
Determine the incidence angle modifier using refractive index, extinction coefficient, and glazing thickness. |
|
Determine the incidence angle modifier using the ASHRAE transmission model. |
|
Calculates the SAPM angle of incidence loss coefficient, F2. |
PV temperature models¶
|
Calculate cell temperature per the Sandia PV Array Performance Model [1]. |
|
Calculate module back surface temperature per the Sandia PV Array Performance Model [1]. |
|
Calculate cell temperature using an empirical heat loss factor model as implemented in PVsyst. |
Single diode models¶
Functions relevant for single diode models.
|
Calculates five parameter values for the single diode equation at effective irradiance and cell temperature using the CEC model described in [1]. |
|
Calculates five parameter values for the single diode equation at effective irradiance and cell temperature using the De Soto et al. |
|
Calculates five parameter values for the single diode equation at effective irradiance and cell temperature using the PVsyst v6 model described in [1,2,3]. |
|
Device current at the given device voltage for the single diode model. |
|
Solve the single-diode model to obtain a photovoltaic IV curve. |
|
Device voltage at the given device current for the single diode model. |
|
Given the single diode equation coefficients, calculates the maximum power point (MPP). |
Low-level functions for solving the single diode equation.
|
Rough estimate of open circuit voltage useful for bounding searches for |
|
Explicit calculation of points on the IV curve described by the single diode equation [1]_. |
|
Find current given any voltage. |
|
Find voltage given any current. |
|
Find max power point. |
SAPM model¶
Functions relevant for the SAPM model.
|
The Sandia PV Array Performance Model (SAPM) generates 5 points on a PV module’s I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to SAND2004-3535. |
Calculates the SAPM effective irradiance using the SAPM spectral loss and SAPM angle of incidence loss functions. |
|
Calculates the SAPM spectral loss coefficient, F1. |
|
|
Calculates the SAPM angle of incidence loss coefficient, F2. |
|
Converts DC power and voltage to AC power using Sandia’s Grid-Connected PV Inverter model. |
|
Calculate cell temperature per the Sandia PV Array Performance Model [1]. |
Pvsyst model¶
Functions relevant for the Pvsyst model.
|
Calculate cell temperature using an empirical heat loss factor model as implemented in PVsyst. |
PVWatts model¶
|
Implements NREL’s PVWatts DC power model [1]_: |
|
Implements NREL’s PVWatts inverter model [1]_. |
|
Implements NREL’s PVWatts system loss model [1]_: |
|
Implements NREL’s PVWatts system loss model [1]_: |
Functions for fitting PV models¶
|
Fits the single diode equation (SDE) to an IV curve. |
|
Estimates parameters for the CEC single diode model (SDM) using the SAM SDK. |
Other¶
|
Retrieve latest module and inverter info from a local file or the SAM website. |
|
Generates a dict of system parameters used throughout a simulation. |
|
Scales the voltage, current, and power of the DataFrames returned by |
Tracking¶
SingleAxisTracker¶
The SingleAxisTracker
inherits from
PVSystem
.
|
Inherits the PV modeling methods from |
Get tracking data. |
|
Uses the |
|
Creates a |
|
The LocalizedSingleAxisTracker class defines a standard set of installed PV system attributes and modeling functions. |
Functions¶
|
Determine the rotation angle of a single axis tracker using the equations in [1] when given a particular sun zenith and azimuth angle. |
IO Tools¶
Functions for reading and writing data from a variety of file formats relevant to solar energy modeling.
|
Read a TMY2 file in to a DataFrame. |
|
Read a TMY3 file in to a pandas dataframe. |
|
Read an EPW file in to a pandas dataframe. |
|
Read University of Oregon SRML[1] 1min .tsv file into pandas dataframe. |
Request a month of SRML[1] data from solardat and read it into a Dataframe. |
|
|
Read in a daily NOAA SURFRAD[1] file. |
|
Read in National Renewable Energy Laboratory Measurement and Instrumentation Data Center [1]_ weather data. |
|
Request and read MIDC data directly from the raw data api. |
|
Read data from ECMWF MACC reanalysis netCDF4 file. |
|
Download data from ECMWF MACC Reanalysis API. |
|
Read NOAA USCRN [1]_ [2]_ fixed-width file into pandas dataframe. |
|
Read NOAA SOLRAD [1]_ [2]_ fixed-width file into pandas dataframe. |
|
Get PSM3 data |
A Location
object may be created from metadata
in some files.
|
Create an object based on a metadata dictionary from tmy2 or tmy3 data readers. |
TMY¶
Warning
The pvlib.tmy
module is deprecated; it will be removed
in pvlib 0.7. Please see the pvlib.iotools package.
Methods and functions for reading data from TMY files.
|
Create an object based on a metadata dictionary from tmy2 or tmy3 data readers. |
|
|
|
Forecasting¶
Forecast models¶
|
Subclass of the ForecastModel class representing GFS forecast model. |
|
Subclass of the ForecastModel class representing NAM forecast model. |
|
Subclass of the ForecastModel class representing RAP forecast model. |
|
Subclass of the ForecastModel class representing HRRR forecast model. |
|
Subclass of the ForecastModel class representing NOAA/GSD/ESRL’s HRRR forecast model. |
|
Subclass of the ForecastModel class representing NDFD forecast model. |
Getting data¶
|
Submits a query to the UNIDATA servers using Siphon NCSS and converts the netcdf data to a pandas DataFrame. |
Get and process forecast data. |
Processing data¶
|
Defines the steps needed to convert raw forecast data into processed forecast data. |
|
Renames the columns according the variable mapping. |
Convert cloud cover to GHI using a linear relationship. |
|
|
Estimates irradiance from cloud cover in the following steps: |
|
Convert cloud cover to atmospheric transmittance using a linear model. |
|
Estimates irradiance from cloud cover in the following steps: |
Convert cloud cover to irradiance. |
|
Converts Kelvin to celsius. |
|
|
Calculates temperature from isobaric temperature. |
Computes wind speed from wind components. |
|
|
Computes standard wind speed from gust. |
IO support¶
These are public for now, but use at your own risk.
Retrieves the designated dataset, creates NCSS object, and creates a NCSS query object. |
|
Sets the NCSS query location latitude and longitude. |
|
|
Sets the location for the query. |
Converts time data into a pandas date object. |
ModelChain¶
Creating a ModelChain object.
|
The ModelChain class to provides a standardized, high-level interface for all of the modeling steps necessary for calculating PV power from a time series of weather inputs. |
Running¶
Running a ModelChain.
|
Run the model. |
Determine the missing irradiation columns. |
|
|
Prepare the solar position, irradiance, and weather inputs to the model. |
Attributes¶
Simple ModelChain attributes:
system, location, clearsky_model, transposition_model,
solar_position_method, airmass_model
Inference methods¶
Methods that automatically determine which models should be used based
on the information in the associated PVSystem
object.
Functions¶
Functions for power modeling.
|
An experimental function that computes all of the modeling steps necessary for calculating power or energy for a PV system at a given location. |
|
Determine a PV system’s surface tilt and surface azimuth using a named strategy. |
Bifacial¶
Methods for calculating back surface irradiance¶
|
Calculate front and back surface plane-of-array irradiance on a fixed tilt or single-axis tracker PV array configuration, and using the open-source “pvfactors” package. |
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 between \(90\deg\) and \(90\deg\) |
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-1:2017 – Photovoltaic system performance - Part 1: Monitoring section: 3 – Terms and definitions; the Indian Standard referencing the withdrawn earlier global IEC standard IEC 61724:1998 is available online: IS/IEC 61724 (1998) and can provide relevant contents.
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.
Single Diode Equation¶
This section reviews the solutions to the single diode equation used in pvlib-python to generate an IV curve of a PV module.
pvlib-python supports two ways to solve the single diode equation:
Lambert W-Function
Bishop’s Algorithm
The pvlib.pvsystem.singlediode()
function allows the user to choose the
method using the method
keyword.
Lambert W-Function¶
When method='lambertw'
, the Lambert W-function is used as previously shown
by Jain, Kapoor [1, 2] and Hansen [3]. The following algorithm can be found on
Wikipedia: Theory of Solar Cells, given the basic single
diode model equation.
Lambert W-function is the inverse of the function \(f \left( w \right) = w \exp \left( w \right)\) or \(w = f^{-1} \left( w \exp \left( w \right) \right)\) also given as \(w = W \left( w \exp \left( w \right) \right)\). Defining the following parameter, \(z\), is necessary to transform the single diode equation into a form that can be expressed as a Lambert W-function.
Then the module current can be solved using the Lambert W-function, \(W \left(z \right)\).
Bishop’s Algorithm¶
The function pvlib.singlediode.bishop88()
uses an explicit solution [4]
that finds points on the IV curve by first solving for pairs \((V_d, I)\)
where \(V_d\) is the diode voltage \(V_d = V + I*Rs\). Then the voltage
is backed out from \(V_d\). Points with specific voltage, such as open
circuit, are located using the bisection search method, brentq
, bounded
by a zero diode voltage and an estimate of open circuit voltage given by
We know that \(V_d = 0\) corresponds to a voltage less than zero, and we can also show that when \(V_d = V_{oc, est}\), the resulting current is also negative, meaning that the corresponding voltage must be in the 4th quadrant and therefore greater than the open circuit voltage (see proof below). Therefore the entire forward-bias 1st quadrant IV-curve is bounded because \(V_{oc} < V_{oc, est}\), and so a bisection search between 0 and \(V_{oc, est}\) will always find any desired condition in the 1st quadrant including \(V_{oc}\).
References¶
[1] “Exact analytical solutions of the parameters of real solar cells using Lambert W-function,” A. Jain, A. Kapoor, Solar Energy Materials and Solar Cells, 81, (2004) pp 269-277. DOI: 10.1016/j.solmat.2003.11.018
[2] “A new method to determine the diode ideality factor of real solar cell using Lambert W-function,” A. Jain, A. Kapoor, Solar Energy Materials and Solar Cells, 85, (2005) 391-396. DOI: 10.1016/j.solmat.2004.05.022
[3] “Parameter Estimation for Single Diode Models of Photovoltaic Modules,” Clifford W. Hansen, Sandia Report SAND2015-2065, 2015 DOI: 10.13140/RG.2.1.4336.7842
[4] “Computer simulation of the effects of electrical mismatches in photovoltaic cell interconnection circuits” JW Bishop, Solar Cell (1988) DOI: 10.1016/0379-6787(88)90059-2