This tutorial explores the pvlib.pvsystem
module. The module has functions for importing PV module and inverter data and functions for modeling module and inverter performance.
This tutorial has been tested against the following package versions:
It should work with other Python and Pandas versions. It requires pvlib >= 0.2.0 and IPython >= 3.0.
Authors:
In [33]:
# built-in python modules
import os
import inspect
import datetime
# scientific python add-ons
import numpy as np
import pandas as pd
# plotting stuff
# first line makes the plots appear in the notebook
%matplotlib inline
import matplotlib.pyplot as plt
# seaborn makes your plots look better
try:
import seaborn as sns
sns.set(rc={"figure.figsize": (12, 6)})
except ImportError:
print('We suggest you install seaborn using conda or pip and rerun this cell')
# finally, we import the pvlib library
import pvlib
In [34]:
import pvlib
from pvlib import pvsystem
pvlib
can import TMY2 and TMY3 data. Here, we import the example files.
In [35]:
pvlib_abspath = os.path.dirname(os.path.abspath(inspect.getfile(pvlib)))
tmy3_data, tmy3_metadata = pvlib.tmy.readtmy3(os.path.join(pvlib_abspath, 'data', '703165TY.csv'))
tmy2_data, tmy2_metadata = pvlib.tmy.readtmy2(os.path.join(pvlib_abspath, 'data', '12839.tm2'))
In [36]:
pvlib.pvsystem.systemdef(tmy3_metadata, 0, 0, .1, 5, 5)
Out[36]:
In [37]:
pvlib.pvsystem.systemdef(tmy2_metadata, 0, 0, .1, 5, 5)
Out[37]:
In [38]:
angles = np.linspace(-180,180,3601)
ashraeiam = pd.Series(pvsystem.ashraeiam(.05, angles), index=angles)
ashraeiam.plot()
plt.ylabel('ASHRAE modifier')
plt.xlabel('input angle (deg)')
Out[38]:
In [39]:
angles = np.linspace(-180,180,3601)
physicaliam = pd.Series(pvsystem.physicaliam(4, 0.002, 1.526, angles), index=angles)
physicaliam.plot()
plt.ylabel('physical modifier')
plt.xlabel('input index')
Out[39]:
In [40]:
plt.figure()
ashraeiam.plot(label='ASHRAE')
physicaliam.plot(label='physical')
plt.ylabel('modifier')
plt.xlabel('input angle (deg)')
plt.legend()
Out[40]:
PV system efficiency can vary by up to 0.5% per degree C, so it's important to accurately model cell and module temperature. The sapm_celltemp
function uses plane of array irradiance, ambient temperature, wind speed, and module and racking type to calculate cell and module temperatures. From King et. al. (2004):
The $a$, $b$, and $\Delta T$ parameters depend on the module and racking type. The default parameter set is open_rack_cell_glassback
.
sapm_celltemp
works with either scalar or vector inputs, but always returns a pandas DataFrame.
In [41]:
# scalar inputs
pvsystem.sapm_celltemp(900, 5, 20) # irrad, wind, temp
Out[41]:
In [42]:
# vector inputs
times = pd.DatetimeIndex(start='2015-01-01', end='2015-01-02', freq='12H')
temps = pd.Series([0, 10, 5], index=times)
irrads = pd.Series([0, 500, 0], index=times)
winds = pd.Series([10, 5, 0], index=times)
pvtemps = pvsystem.sapm_celltemp(irrads, winds, temps)
pvtemps.plot()
Out[42]:
Cell and module temperature as a function of wind speed.
In [43]:
wind = np.linspace(0,20,21)
temps = pd.DataFrame(pvsystem.sapm_celltemp(900, wind, 20), index=wind)
temps.plot()
plt.legend()
plt.xlabel('wind speed (m/s)')
plt.ylabel('temperature (deg C)')
Out[43]:
Cell and module temperature as a function of ambient temperature.
In [44]:
atemp = np.linspace(-20,50,71)
temps = pvsystem.sapm_celltemp(900, 2, atemp).set_index(atemp)
temps.plot()
plt.legend()
plt.xlabel('ambient temperature (deg C)')
plt.ylabel('temperature (deg C)')
Out[44]:
Cell and module temperature as a function of incident irradiance.
In [45]:
irrad = np.linspace(0,1000,101)
temps = pvsystem.sapm_celltemp(irrad, 2, 20).set_index(irrad)
temps.plot()
plt.legend()
plt.xlabel('incident irradiance (W/m**2)')
plt.ylabel('temperature (deg C)')
Out[45]:
Cell and module temperature for different module and racking types.
In [46]:
models = ['open_rack_cell_glassback',
'roof_mount_cell_glassback',
'open_rack_cell_polymerback',
'insulated_back_polymerback',
'open_rack_polymer_thinfilm_steel',
'22x_concentrator_tracker']
temps = pd.DataFrame(index=['temp_cell','temp_module'])
for model in models:
temps[model] = pd.Series(pvsystem.sapm_celltemp(1000, 5, 20, model=model).ix[0])
temps.T.plot(kind='bar') # try removing the transpose operation and replotting
plt.legend()
plt.ylabel('temperature (deg C)')
Out[46]:
In [47]:
inverters = pvsystem.retrieve_sam('sandiainverter')
inverters
Out[47]:
In [48]:
vdcs = pd.Series(np.linspace(0,50,51))
idcs = pd.Series(np.linspace(0,11,110))
pdcs = idcs * vdcs
pacs = pvsystem.snlinverter(inverters['ABB__MICRO_0_25_I_OUTD_US_208_208V__CEC_2014_'], vdcs, pdcs)
#pacs.plot()
plt.plot(pacs, pdcs)
plt.ylabel('ac power')
plt.xlabel('dc power')
Out[48]:
Need to put more effort into describing this function.
The CEC module database.
In [49]:
cec_modules = pvsystem.retrieve_sam('cecmod')
cec_modules
Out[49]:
In [50]:
cecmodule = cec_modules.Example_Module
cecmodule
Out[50]:
The Sandia module database.
In [51]:
sandia_modules = pvsystem.retrieve_sam(name='SandiaMod')
sandia_modules
Out[51]:
In [52]:
sandia_module = sandia_modules.Canadian_Solar_CS5P_220M___2009_
sandia_module
Out[52]:
Generate some irradiance data for modeling.
In [53]:
from pvlib import clearsky
from pvlib import irradiance
from pvlib import atmosphere
from pvlib.location import Location
tus = Location(32.2, -111, 'US/Arizona', 700, 'Tucson')
times = pd.date_range(start=datetime.datetime(2014,4,1), end=datetime.datetime(2014,4,2), freq='30s')
ephem_data = pvlib.solarposition.get_solarposition(times, tus)
irrad_data = clearsky.ineichen(times, tus)
#irrad_data.plot()
aoi = irradiance.aoi(0, 0, ephem_data['apparent_zenith'], ephem_data['azimuth'])
#plt.figure()
#aoi.plot()
am = atmosphere.relativeairmass(ephem_data['apparent_zenith'])
# a hot, sunny spring day in the desert.
temps = pvsystem.sapm_celltemp(irrad_data['ghi'], 0, 30)
Now we can run the module parameters and the irradiance data through the SAPM function.
In [54]:
sapm_1 = pvsystem.sapm(sandia_module, irrad_data['dni']*np.cos(np.radians(aoi)),
irrad_data['ghi'], temps['temp_cell'], am, aoi)
sapm_1.head()
Out[54]:
In [55]:
def plot_sapm(sapm_data):
"""
Makes a nice figure with the SAPM data.
Parameters
----------
sapm_data : DataFrame
The output of ``pvsystem.sapm``
"""
fig, axes = plt.subplots(2, 3, figsize=(16,10), sharex=False, sharey=False, squeeze=False)
plt.subplots_adjust(wspace=.2, hspace=.3)
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)')
ax = axes[1,0]
[ax.plot(sapm_data['effective_irradiance'], current, label=name) for name, current in
sapm_data.filter(like='i_').iteritems()]
ax.set_ylabel('Current (A)')
ax.set_xlabel('Effective Irradiance')
ax.legend(loc=2)
ax = axes[1,1]
[ax.plot(sapm_data['effective_irradiance'], voltage, label=name) for name, voltage in
sapm_data.filter(like='v_').iteritems()]
ax.set_ylabel('Voltage (V)')
ax.set_xlabel('Effective Irradiance')
ax.legend(loc=4)
ax = axes[1,2]
ax.plot(sapm_data['effective_irradiance'], sapm_data['p_mp'], label='p_mp')
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)
In [56]:
plot_sapm(sapm_1)
For comparison, here's the SAPM for a sunny, windy, cold version of the same day.
In [57]:
temps = pvsystem.sapm_celltemp(irrad_data['ghi'], 10, 5)
sapm_2 = pvsystem.sapm(sandia_module, irrad_data['dni']*np.cos(np.radians(aoi)),
irrad_data['dhi'], temps['temp_cell'], am, aoi)
plot_sapm(sapm_2)
In [58]:
sapm_1['p_mp'].plot(label='30 C, 0 m/s')
sapm_2['p_mp'].plot(label=' 5 C, 10 m/s')
plt.legend()
plt.ylabel('Pmp')
plt.title('Comparison of a hot, calm day and a cold, windy day')
Out[58]:
The IV curve function only calculates the 5 points of the SAPM. We will add arbitrary points in a future release, but for now we just interpolate between the 5 SAPM points.
In [59]:
import warnings
warnings.simplefilter('ignore', np.RankWarning)
In [60]:
def sapm_to_ivframe(sapm_row):
pnt = sapm_row.T.ix[:,0]
ivframe = {'Isc': (pnt['i_sc'], 0),
'Pmp': (pnt['i_mp'], pnt['v_mp']),
'Ix': (pnt['i_x'], 0.5*pnt['v_oc']),
'Ixx': (pnt['i_xx'], 0.5*(pnt['v_oc']+pnt['v_mp'])),
'Voc': (0, pnt['v_oc'])}
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
In [61]:
sapm_to_ivframe(sapm_1['2014-04-01 10:00:00'])
Out[61]:
In [62]:
times = ['2014-04-01 07:00:00', '2014-04-01 08:00:00', '2014-04-01 09:00:00',
'2014-04-01 10:00:00', '2014-04-01 11:00:00', '2014-04-01 12:00:00']
times.reverse()
fig, ax = plt.subplots(1, 1, figsize=(12,8))
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')
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()
Out[62]:
The same data run through the desoto model.
In [63]:
photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth = (
pvsystem.calcparams_desoto(irrad_data.ghi,
temp_cell=temps['temp_cell'],
alpha_isc=cecmodule['alpha_sc'],
module_parameters=cecmodule,
EgRef=1.121,
dEgdT=-0.0002677) )
In [64]:
photocurrent.plot()
plt.ylabel('Light current I_L (A)')
Out[64]:
In [65]:
saturation_current.plot()
plt.ylabel('Saturation current I_0 (A)')
Out[65]:
In [66]:
resistance_series
Out[66]:
In [67]:
resistance_shunt.plot()
plt.ylabel('Shunt resistance (ohms)')
plt.ylim(0,100)
Out[67]:
In [68]:
nNsVth.plot()
plt.ylabel('nNsVth')
Out[68]:
In [69]:
single_diode_out = pvsystem.singlediode(cecmodule, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth)
single_diode_out
Out[69]:
In [70]:
single_diode_out['i_sc'].plot()
Out[70]:
In [71]:
single_diode_out['v_oc'].plot()
Out[71]:
In [72]:
single_diode_out['p_mp'].plot()
Out[72]:
In [ ]: