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__
'0.2.0dev'
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)
Tucson: latitude=32.2, longitude=-111, tz=US/Arizona, altitude=700 Albuquerque: latitude=35, longitude=-106, tz=US/Mountain, altitude=1619 Johannesburg: latitude=-26.2044, longitude=28.0456, tz=Africa/Johannesburg, altitude=1753
# 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
Vintage 2009 Area 1.701 Material c-Si #Series 96 #Parallel 1 Isco 5.09115 Voco 59.2608 Impo 4.54629 Vmpo 48.3156 Aisc 0.000397 Aimp 0.000181 C0 1.01284 C1 -0.0128398 Bvoco -0.21696 Mbvoc 0 Bvmpo -0.235488 Mbvmp 0 N 1.4032 C2 0.279317 C3 -7.24463 A0 0.928385 A1 0.068093 A2 -0.0157738 A3 0.0016606 A4 -6.93035e-05 B0 1 B1 -0.002438 B2 0.0003103 B3 -1.246e-05 B4 2.112e-07 B5 -1.359e-09 DTC 3 FD 1 A -3.40641 B -0.0842075 C4 0.996446 C5 0.003554 IXO 4.97599 IXXO 3.18803 C6 1.15535 C7 -0.155353 Notes Source: Sandia National Laboratories Updated 9... Name: Canadian_Solar_CS5P_220M___2009_, dtype: object
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')