This notebook contains all of the code needed to reproduce the figures found in the PVLIB Python 2015 manuscript and poster in the 42nd IEEE PVSC. It is not intended to be a comprehensive introduction to pvlib-python
.
The manuscript and poster authors are William F. Holmgren, Robert W. Andrews, Antonio T. Lorenzo, Joshua S. Stein.
Table of Contents
The essential functionality requires:
matplotlib
numpy
pandas
The plotting library seaborn
is needed to reproduce the figures exactly. You'll need to comment out any sns
lines if you don't install seaborn
.
All of these packages can be easily installed using the conda package manager. To create a new conda environment to run this notebook, run these commands in your shell:
$ conda create -n pvlibpvsc python matplotlib numpy pandas seaborn ipython-notebook ephem
$ source activate pvlibpvsc
$ git clone https://github.com/pvlib/pvlib-python.git
$ cd pvlib-python
$ git checkout 621ad97
$ pip install .
$ ipython-notebook
First, the standard scientific Python imports.
# plotting modules
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mpl
try:
import seaborn as sns
sns.set_style('ticks')
sns.set_context('notebook', font_scale=1.5)
except ImportError:
pass
# built in python modules
import datetime
# python add-ons
import numpy as np
import pandas as pd
Import pvlib
import pvlib
from pvlib.location import Location
pvlib.__version__
Make some pvlib Location
objects. These objects are convenient ways to keep track of coordinates, time zones, and elevations.
tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
print(tus)
abq = Location(35, -106, tz='US/Mountain', altitude=1619, name='Albuquerque')
print(abq)
johannesburg = Location(-26.2044, 28.0456, 'Africa/Johannesburg', 1753, 'Johannesburg')
print(johannesburg)
# Boolean to control saving the figures.
# You'll need to change the directories if you want to save them.
save = True
Simulation of single axis tracker output near Albuquerque, NM (figure 1).
times = pd.date_range(start=datetime.datetime(2015,6,1), end=datetime.datetime(2015,6,2), freq='5Min')
solpos = pvlib.solarposition.get_solarposition(times, abq)
tracker_data = pvlib.tracking.singleaxis(solpos['apparent_zenith'], solpos['azimuth'],
axis_tilt=0, axis_azimuth=180, max_angle=45,
backtrack=True, gcr=.3)
ax = tracker_data.drop('surface_azimuth', axis=1).plot()
plt.ylim(-100,100)
plt.ylabel('Angle (degrees)')
plt.title('Single Axis Tracker Simulated Angles')
sns.despine()
ax.set_xlabel('Time of day')
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%H', tz=solpos.index.tz))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=0, ha='center')
if save: plt.savefig('/home/will/git_repos/pvsc2015/abq-tracker.eps', format='eps')
irrad_data = pvlib.clearsky.ineichen(times, abq)
dni_et = pd.Series(pvlib.irradiance.extraradiation(times.dayofyear, method='asce'), index=times).tz_localize(abq.tz)
ground_irrad = pvlib.irradiance.grounddiffuse(tracker_data['surface_tilt'], irrad_data['GHI'], albedo=.25)
haydavies_diffuse = pvlib.irradiance.haydavies(tracker_data['surface_tilt'], tracker_data['surface_azimuth'],
irrad_data['DHI'], irrad_data['DNI'], dni_et,
solpos['apparent_zenith'], solpos['azimuth'])
global_in_plane = pvlib.irradiance.globalinplane(tracker_data['aoi'], irrad_data['DNI'],
haydavies_diffuse, ground_irrad)
ax = global_in_plane.plot()
sns.despine()
plt.ylabel('Irradiance (W m$^{-2}$)')
plt.title('Single Axis Tracker Simulated POA Irradiance')
ax.set_xlabel('Time of day')
ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%H', tz=solpos.index.tz))
plt.setp(ax.xaxis.get_majorticklabels(), rotation=0, ha='center')
if save: plt.savefig('/home/will/git_repos/pvsc2015/abq-tracker-irrad.eps', format='eps')
Some simulations using the Sandia Array Performance Model.
Get the module database from NREL.
sandia_modules = pvlib.pvsystem.retrieve_sam(name='SandiaMod')
#sandia_modules
Choose a random model from another pvlib-python tutorial.
sandia_module = sandia_modules.Canadian_Solar_CS5P_220M___2009_
sandia_module
Recalculate simulation parameters for a new location and a fixed tilt system.
tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
times = pd.date_range(start=datetime.datetime(2015,4,1), end=datetime.datetime(2015,4,2), freq='30s')
solpos = pvlib.solarposition.get_solarposition(times, tus)
irrad_data = pvlib.clearsky.ineichen(times, tus)
aoi = pvlib.irradiance.aoi(tus.latitude, 180, solpos['apparent_zenith'], solpos['azimuth'])
#plt.figure()
#aoi.plot()
am = pvlib.atmosphere.relativeairmass(solpos['apparent_zenith'])
# a hot, sunny spring day in the desert. no wind.
temps = pvlib.pvsystem.sapm_celltemp(irrad_data['GHI'], 0, 30)
sapm_1 = pvlib.pvsystem.sapm(sandia_module, irrad_data['DNI']*np.cos(np.radians(aoi)),
irrad_data['DHI'], temps['tcell'], am, aoi)
#sapm_1.head()
Make a reusable plotting function to generate a nice panel.
def plot_sapm(sapm_data, figsize=(16,10)):
"""
Makes a nice figure with the SAPM data.
Parameters
----------
sapm_data : DataFrame
The output of ``pvsystem.sapm``
"""
fig, axes = plt.subplots(2, 3, figsize=figsize, sharex=False, sharey=False, squeeze=False)
plt.subplots_adjust(wspace=.3, hspace=.4)
ax = axes[0,0]
sapm_data.filter(like='I').plot(ax=ax)
ax.set_ylabel('Current (A)')
ax = axes[0,1]
sapm_data.filter(like='V').plot(ax=ax)
ax.set_ylabel('Voltage (V)')
ax = axes[0,2]
sapm_data.filter(like='P').plot(ax=ax)
ax.set_ylabel('Power (W)')
# x axis formatting
[ax.set_xlabel('Time of day') for ax in axes[0,:]]
[ax.xaxis.set_major_formatter(mpl.dates.DateFormatter('%H', tz=sapm_data.index.tz)) for ax in axes[0,:]]
[plt.setp(ax.xaxis.get_majorticklabels(), rotation=0, ha='center') for ax in axes[0,:]]
ax = axes[1,0]
[ax.plot(sapm_data['Ee'], current, label=name) for name, current in sapm_data.filter(like='I').items()]
ax.set_ylabel('Current (A)')
ax.set_xlabel('Effective Irradiance')
ax.legend(loc=2)
ax = axes[1,1]
[ax.plot(sapm_data['Ee'], voltage, label=name) for name, voltage in sapm_data.filter(like='V').items()]
ax.set_ylabel('Voltage (V)')
ax.set_xlabel('Effective Irradiance')
ax.legend(loc=4)
ax = axes[1,2]
ax.plot(sapm_data['Ee'], sapm_data['Pmp'], label='Pmp')
ax.set_ylabel('Power (W)')
ax.set_xlabel('Effective Irradiance')
ax.legend(loc=2)
# needed to show the time ticks
for ax in axes.flatten():
for tk in ax.get_xticklabels():
tk.set_visible(True)
return fig
with sns.axes_style('whitegrid'):
fig = plot_sapm(sapm_1)
#sns.despine(fig=fig)
if save: fig.savefig('/home/will/git_repos/pvsc2015/fixed_sapm.eps', format='eps')
Make some IV curves based on this data
import warnings
warnings.simplefilter('ignore', np.RankWarning)
Define a couple of functions to take the DataFrame 5 point model input and turn it into a smooth curve.
def sapm_to_ivframe(sapm_row):
pnt = sapm_row.T.ix[:,0]
ivframe = {'Isc': (pnt['Isc'], 0),
'Pmp': (pnt['Imp'], pnt['Vmp']),
'Ix': (pnt['Ix'], 0.5*pnt['Voc']),
'Ixx': (pnt['Ixx'], 0.5*(pnt['Voc']+pnt['Vmp'])),
'Voc': (0, pnt['Voc'])}
ivframe = pd.DataFrame(ivframe, index=['current', 'voltage']).T
ivframe = ivframe.sort('voltage')
return ivframe
def ivframe_to_ivcurve(ivframe, points=100):
ivfit_coefs = np.polyfit(ivframe['voltage'], ivframe['current'], 30)
fit_voltages = np.linspace(0, ivframe.ix['Voc', 'voltage'], points)
fit_currents = np.polyval(ivfit_coefs, fit_voltages)
return fit_voltages, fit_currents
Pick out a handful of times for which to make the IV curves.
times = ['2015-04-01 07:00:00', '2015-04-01 08:00:00', '2015-04-01 09:00:00',
'2015-04-01 10:00:00', '2015-04-01 11:00:00', '2015-04-01 12:00:00']
times.reverse()
fig, ax = plt.subplots(1, 1)
for time in times:
ivframe = sapm_to_ivframe(sapm_1[time])
fit_voltages, fit_currents = ivframe_to_ivcurve(ivframe)
ax.plot(fit_voltages, fit_currents, label=time)
ax.plot(ivframe['voltage'], ivframe['current'], 'ko') # comment/uncomment to plot actual pnts
ax.set_xlabel('Voltage (V)')
ax.set_ylabel('Current (A)')
ax.set_ylim(0, None)
ax.set_title('IV curves at multiple times')
ax.legend(bbox_to_anchor=(1.1,1), fontsize=12)
sns.despine()
if save: fig.savefig('/home/will/git_repos/pvsc2015/fixed_sapm_iv.eps', format='eps')