Now that we know how to obtain the plane of array (POA) irradiance and cell temperature, let's calculate a module's performance assuming a subset of irradiance and temperature conditions. The objectives for this tutorial are to use pvlib python to do the following:
numpy.meshgrid
try: except
clauses to catch errorsdf.index.str.startswith('Canadian')
where df
is a dataframe of PV modules.The most basic condition is called "standard test conditions" or STC, which is considered the reference for most PV modules. For example, all of the PV modules in the SAM CEC module database list their nameplate power at STC.
The standard reference AM1.5g (ASTM G-173) is defined as the solar spectrum of global irradiance that passes through 1.5 atmospheres. For more information see NREL Solar Spectra.
Another common set of test conditions is the IEC 61853 standard which provides a PV module test matrix that covers the expected range of inicident irradiance and cell temperatures for PV modules assuming that the irradiance is normal and the solar spectrum is similar to AM1.5g.
The figure below shows IEC 61853 test results performed at CFV labs:
Certain combinations are excluded because they're unlikely: (1100-W/m², 15°C), (400-W/m², 75°C), (200-W/m², 50°C), (200-W/m², 75°C), (100-W/m², 50°C), and (100-W/m², 75°C). The figure below shows SAM parametrs fit to IEC61853 test results for two different cell technologies. The white space shows combinations of irradiance and temperature which were intentionally excluded from testing.
Attribution: NREL Dobos, Freeman 2019
# if running on google colab, uncomment the next line and execute this cell to install the dependencies and prevent "ModuleNotFoundError" in later cells:
# !pip install -r https://raw.githubusercontent.com/PVSC-Python-Tutorials/PVSC50/main/requirements.txt
# import pvlib and other useful python packages
import pvlib
import numpy as np
from matplotlib import pyplot as plt
import pandas as pd
# set STC reference conditions
E0 = 1000 # W/m^2
T0 = 25 # degC
# set the IEC61853 test matrix
E_IEC61853 = [100, 200, 400, 600, 800, 1000, 1100] # irradiances [W/m^2]
T_IEC61853 = [15, 25, 50, 75] # temperatures [degC]
# create a meshgrid of temperatures and irradiances
# for all 28 combinations in the test matrix
IEC61853 = np.meshgrid(T_IEC61853, E_IEC61853)
# meshgrid returns two 2-D arrays in the same order as the input arguments
# so the first item in the output is a 2-D array of temperatures and
# the second item is the mesh of irradiances
# display temperature and irradiance test matrices
IEC61853
[array([[15, 25, 50, 75], [15, 25, 50, 75], [15, 25, 50, 75], [15, 25, 50, 75], [15, 25, 50, 75], [15, 25, 50, 75], [15, 25, 50, 75]]), array([[ 100, 100, 100, 100], [ 200, 200, 200, 200], [ 400, 400, 400, 400], [ 600, 600, 600, 600], [ 800, 800, 800, 800], [1000, 1000, 1000, 1000], [1100, 1100, 1100, 1100]])]
PV module performance can be modeled using point or continuous IV-curve models.
Point models like PVWatts and The Sandia Array Performance Model (SAPM, aka King model) yield the current (I), voltage (V), and power (P) at a single or discrete set of points. PVWatts only yields the performance at the max power point (MPP) of the module, whereas the SAPM also yields the short circuit current (Isc), open circuit voltage (Voc).
Continuous IV curve models like the CEC, PVsyst, and DeSoto models yield a relation between current and voltage called an IV curve, and therefore yield a continuous set of (V, I) points spanning from Isc to Voc and beyond. The domain of the IV curve is in quadrants 1, 2, and 4 where voltage is on the horizontal and current is on the vertical axis. The figure below from PV Education PVCDROM shows an IV curve of an "ideal" cell.
Attribution: PV Education, UNSW, ASU, et al.
The IV curve relationship is based on an electrical analog called the "single diode model" or SDM which is defined by 5 parameters: the light current ($I_L$), shunt resistance ($R_{sh}$), series resistance ($R_s$), diode saturation current ($I_o$ or $I_{sat}$), and the diode ideality factor ($n$). Other symbols for diode ideality factor are ($\gamma$) used by PVsyst and ($a$) used by SAM, but ($\gamma$) is also frequently used for power temperature coefficient. This "ideal" cell is described by the electrical schematic drawing below.
Attribution: Sandia NL PV Performance Modeling Collaborative
Combining the components in the SDM using Ohm's and Kirchhoff's laws yields the following equation, which is implicit because current (I) is on both sides of the equation, and cannot be solved explicitly:
$$ I = I_L - I_o \left( \exp \left( \frac{V + I R_s}{n V_T} \right) - 1 \right) - \frac{V + I R_s}{R_{sh}} $$with the diode voltage ($V_D = V + I R_s$), the diode current ($I_D$) given by the ideal diode equation:
$$ I_D = I_o \left( \exp \left( \frac{V_D}{n V_T} \right) - 1 \right) $$the thermal voltage ($V_T = k_T / q_e$), elementary charge ($q_e$), Boltzmann constant ($k_T$), and the shunt current ($I_{sh} = V_D / R_{sh}$)
The California Energy Commision (CEC) contracted authorized testing labs to measure at STC the nameplate power (Pmp), Isc, Voc, and the MPP voltage and current (Vmp, Imp), as well as Isc and Voc temperature coefficients, the module dimensions, the number of series cells (Ns), parallel substrings (Np), module area in m² (Ac), and more. Tables of the CEC module parameters are available from the Solar Equipment Lists. These measurements have been fit to the SDM by the NREL System Advisor Model (SAM) and stored in a CSV file that is bundled with SAM. You can access the SAM library on GitHub. This SAM library of module coefficients derived from the CEC measurements are collectively called CEC modules and the SAM model that uses the derived SDM coefficients is called the CEC model. The CEC model used in SAM is sometimes also called the 6-parameter model because of the Adjust
additional parameter which differentiates it from the DeSoto model.
There are several functions we can use in pvlib python:
# use pvlib python to retrieve CEC module parameters from the SAM libraries
# with the "name" argument set to "CECMod"
CECMODS = pvlib.pvsystem.retrieve_sam(name='CECMod')
# the CEC modules are a pandas DataFrame oriented as columns, transpose to arrange
# as indices
CECMODS.T.head()
Technology | Bifacial | STC | PTC | A_c | Length | Width | N_s | I_sc_ref | V_oc_ref | ... | a_ref | I_L_ref | I_o_ref | R_s | R_sh_ref | Adjust | gamma_r | BIPV | Version | Date | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
A10Green_Technology_A10J_S72_175 | Mono-c-Si | 0 | 175.0914 | 151.2 | 1.3 | 1.576 | 0.825 | 72 | 5.17 | 43.99 | ... | 1.981696 | 5.175703 | 0.0 | 0.316688 | 287.102203 | 16.057121 | -0.5072 | N | SAM 2018.11.11 r2 | 1/3/2019 |
A10Green_Technology_A10J_S72_180 | Mono-c-Si | 0 | 179.928 | 155.7 | 1.3 | 1.576 | 0.825 | 72 | 5.31 | 44.06 | ... | 1.988414 | 5.316148 | 0.0 | 0.299919 | 259.047943 | 16.418983 | -0.5072 | N | SAM 2018.11.11 r2 | 1/3/2019 |
A10Green_Technology_A10J_S72_185 | Mono-c-Si | 0 | 184.7016 | 160.2 | 1.3 | 1.576 | 0.825 | 72 | 5.43 | 44.14 | ... | 1.984817 | 5.435676 | 0.0 | 0.311962 | 298.424438 | 15.688233 | -0.5072 | N | SAM 2018.11.11 r2 | 1/3/2019 |
A10Green_Technology_A10J_M60_220 | Multi-c-Si | 0 | 219.876 | 189.1 | 1.624 | 1.632 | 0.995 | 60 | 7.95 | 36.06 | ... | 1.673094 | 7.959062 | 0.0 | 0.140393 | 123.168404 | 21.875164 | -0.5196 | N | SAM 2018.11.11 r2 | 1/3/2019 |
A10Green_Technology_A10J_M60_225 | Multi-c-Si | 0 | 224.9856 | 193.5 | 1.624 | 1.632 | 0.995 | 60 | 8.04 | 36.24 | ... | 1.671782 | 8.047206 | 0.0 | 0.14737 | 164.419479 | 20.698376 | -0.5196 | N | SAM 2018.11.11 r2 | 1/3/2019 |
5 rows × 25 columns
Periodically a static copy of CEC module parameters is copied from the SAM library to pvlib python. The modules are roughly named according the following scheme:
<manufacturer name> <model name>
Whitespace, dashes, and other non-alphanumerical characters are all replaced by underscores in pvlib python.
EG: "Canadian Solar Inc. CS6X-300M" becomes
Canadian_Solar_Inc__CS6X_300M
The main CEC module parameters are defined as follows:
parameter | data type | description and units |
---|---|---|
Technology |
string | one of "Mono-c-Si", "Multi-c-Si", "Thin Film", "CdTe", or "CIGS" families of cells |
Bifacial |
boolean | is bifacial? |
STC |
float | nameplate in W at STC |
PTC |
float | nameplate in W at PVUSA test conditions (1-sun, 20° ambient temperature, 1-m/s windspeed) |
A_c |
float | module area in m² |
Length |
float | module length in m; |
Width |
float | module width in m; |
N_s |
int | number of cells in series |
I_sc_ref |
float | short circuit current in A at reference condition |
V_oc_ref |
float | open circuit voltage in V at reference condition |
I_mp_ref |
float | max power current in A at reference condition |
V_mp_ref |
float | max power voltage in V at reference condition |
alpha_sc |
float | short circuit current temperature coefficient in A/Δ°C |
beta_oc |
float | open circuit voltage temperature coefficient in V/Δ°C |
T_NOCT |
float | normal operating cell temperature in °C |
a_ref |
float | diode ideality factor |
I_L_ref |
float | light or photogenerated current at reference condition in A |
I_o_ref |
float | diode saturation current at reference condition in A |
R_s |
float | series resistance in Ω |
R_sh_ref |
float | shunt resistance at reference condition in Ω |
Adjust |
float | adjustment to short circuit temperature coefficient in % |
gamma_r |
float | power temperature coefficient at reference condition in %/Δ°C |
BIPV |
boolean | is building integrated PV? |
# One trick to find the modules is to search the indices using string filters
# For example: find all Candian Solar 220-W mono-Si modules
cs_220m_mods = CECMODS.T.index.str.startswith('Canadian_Solar') & CECMODS.T.index.str.contains('220M')
CECMODS.T[cs_220m_mods]
Technology | Bifacial | STC | PTC | A_c | Length | Width | N_s | I_sc_ref | V_oc_ref | ... | a_ref | I_L_ref | I_o_ref | R_s | R_sh_ref | Adjust | gamma_r | BIPV | Version | Date | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Canadian_Solar_Inc__CS5P_220M | Mono-c-Si | 0 | 219.961 | 200.1 | 1.7 | 1.602 | 1.061 | 96 | 5.1 | 59.4 | ... | 2.635926 | 5.11426 | 0.0 | 1.066023 | 381.254425 | 8.619516 | -0.476 | N | SAM 2018.11.11 r2 | 1/3/2019 |
Canadian_Solar_Inc__CS6P_220M | Mono-c-Si | 0 | 219.775 | 198.5 | 1.549 | 1.615 | 0.959 | 60 | 7.97 | 36.9 | ... | 1.515583 | 7.980784 | 0.0 | 0.397651 | 293.871094 | -3.311191 | -0.436 | N | SAM 2018.11.11 r2 | 1/3/2019 |
2 rows × 25 columns
# that was almost too easy, let's use the CS5P-220M
# NOTE: don't transpose CECMODS, get column with desired module
CS_220M = CECMODS['Canadian_Solar_Inc__CS5P_220M']
CS_220M
Technology Mono-c-Si Bifacial 0 STC 219.961 PTC 200.1 A_c 1.7 Length 1.602 Width 1.061 N_s 96 I_sc_ref 5.1 V_oc_ref 59.4 I_mp_ref 4.69 V_mp_ref 46.9 alpha_sc 0.004539 beta_oc -0.222156 T_NOCT 42.4 a_ref 2.635926 I_L_ref 5.11426 I_o_ref 0.0 R_s 1.066023 R_sh_ref 381.254425 Adjust 8.619516 gamma_r -0.476 BIPV N Version SAM 2018.11.11 r2 Date 1/3/2019 Name: Canadian_Solar_Inc__CS5P_220M, dtype: object
Get any CEC module from the CECMODS
or pvfree.
# use this cell to search CECMODS.T or pvfree
your_mod = 'your module goes here'
try:
your_mod = CECMODS[your_mod]
except KeyError:
print(f"*** Sorry, '{your_mod}' wasn't found in CECMODS. Please try again. ***")
else:
# display your module
your_mod
*** Sorry, 'your module goes here' wasn't found in CECMODS. Please try again. ***
The module parameters are given at the reference condition. Use pvlib.pvsystem.calcparams_cec()
to generate the five SDM parameters at your desired irradiance and temperature to use with pvlib.pvsystem.singlediode()
to calculate the IV curve information.
nNsVth
, what's this?¶The diode ideality factor (n) is combined with the number of cells (Ns) and the thermal voltage (Vth) to create a convenience parameter. This is syntactic sugar.
# finally this is the magic
temp_cell, effective_irradiance = IEC61853
cecparams = pvlib.pvsystem.calcparams_cec(
effective_irradiance=effective_irradiance,
temp_cell=temp_cell,
alpha_sc=CS_220M.alpha_sc,
a_ref=CS_220M.a_ref,
I_L_ref=CS_220M.I_L_ref,
I_o_ref=CS_220M.I_o_ref,
R_sh_ref=CS_220M.R_sh_ref,
R_s=CS_220M.R_s,
Adjust=CS_220M.Adjust,
EgRef=1.121,
dEgdT=-0.0002677)
IL, I0, Rs, Rsh, nNsVth = cecparams
# display the photogenerated current
IL
array([[0.50727824, 0.511426 , 0.5217954 , 0.5321648 ], [1.01455648, 1.022852 , 1.0435908 , 1.0643296 ], [2.02911296, 2.045704 , 2.0871816 , 2.1286592 ], [3.04366944, 3.068556 , 3.1307724 , 3.19298881], [4.05822592, 4.091408 , 4.1743632 , 4.25731841], [5.0727824 , 5.11426 , 5.217954 , 5.32164801], [5.58006064, 5.625686 , 5.7397494 , 5.85381281]])
Now that we have the 5 SDM parameters (IL
, Io
, Rs
, Rsh
, and nNsVth
) corresponding to each of the test conditions in the IEC61853 test matrix, we can calculate the IV curve information for that irradiance and cell temperature.
# flatten the meshgrid to allow single diode to broadcast the output
curve_info = pvlib.pvsystem.singlediode(
photocurrent=IL.flatten(),
saturation_current=I0.flatten(),
resistance_series=Rs,
resistance_shunt=Rsh.flatten(),
nNsVth=nNsVth.flatten(),
ivcurve_pnts=101,
method='lambertw')
# display the max power points
curve_info['p_mp']
array([ 22.47344592, 21.33362028, 18.40249412, 15.37302335, 46.09151614, 43.87428661, 38.16846011, 32.26328112, 93.53042781, 89.2112251 , 78.09399529, 66.58418353, 140.31159673, 133.92211364, 117.48149685, 100.47026643, 186.01692511, 177.5719005 , 155.85443596, 133.40606739, 230.45399426, 219.96096044, 192.99622092, 165.16045701, 252.16130617, 240.64592808, 211.06610839, 180.55358287])
# plot the calculated curves:
exclude = [(1100, 15), (400, 75), (200, 50), (200, 75), (100, 50), (100, 75)]
kolor = ['#1f77b4', '#2ca02c', '#8c564b', '#9467bd', '#d62728', '#e377c2', '#ff7f0e']
f, ax = plt.subplots(2, 2, figsize=(16, 10), sharex=True, sharey=True)
for m, irr in enumerate(E_IEC61853):
for n, tc in enumerate(T_IEC61853):
if (irr, tc) in exclude:
continue
i = n + 4*m
j = n // 2, n % 2
label = (
"$G_{eff}$ " + f"{irr} $W/m^2$"
)
ax[j].plot(curve_info['v'][i], curve_info['i'][i], label=label, c=kolor[m])
v_mp = curve_info['v_mp'][i]
i_mp = curve_info['i_mp'][i]
# mark the MPP
ax[j].plot(v_mp, i_mp, ls='', marker='o', c=kolor[m])
ax[j].vlines(v_mp, 0, i_mp, linestyle='dashed', color=kolor[m])
# just repeat this every time doesn't hurt anyone
ax[j].legend(loc='right')
if j[0] == 1:
ax[j].set_xlabel('Module voltage [V]')
if j[1] == 0:
ax[j].set_ylabel('Module current [A]')
ax[j].set_title(f"{CS_220M.name}, " + "$T_{cell}$ " + f"{tc} " + "$^{\circ}C$")
ax[j].grid(True)
ax[j].set_xlim([0, 80])
f.tight_layout()
So is this what you expected? What do you notice about the relation between voltage and temperature? What about the relation between current and irradiance? What other patterns do you observe?
This work is licensed under a Creative Commons Attribution 4.0 International License.