This is the last notebook in a series exploring how to model PV systems using the single diode model (SDM). The first two notebooks presented implicit approaches that return either the corresponding current for a specified voltage or the current and voltage of the max power point. This notebook uses an explicit method which parameterizes the SDM using the diode voltage, and returns a current and voltage corresponding to the value of the parameter. The diode voltage is defined as the voltage across the diode in the SDM and is related to the current and voltage by the following relation, $V_{diode} = V + I R_s$, in which $R_s$ is the series resistance.
PVMismatch is a Python package that solves IV curves using this explicit approach which was discussed in by J. Bishop in Solar (1980). There is another notebook in this series that is a repeat of this notebook, but using PVMismatch.
Overall, it appears that the explicit approach is about 5x faster than the implicit approaches.
%matplotlib inline
import numpy as np
from matplotlib import pyplot as plt
# CONSTANTS
IL = 7.0 # [A] photogenerated current
I0 = 1.0e-6 # [A] dark current
RSH = 20.0 # [ohms] shunt resistance
RS = 0.001 # [ohms] series resistance
GAMMA = 1.23 # diode ideality
VT = 0.026 # [V] thermal voltage at 300[K]
VBYPASS = -0.5 # bypass diode trigger voltage [V]
NSERIES_CELLS = 72 # number of series cells per module
NMODS = 8 # number of modules per string
NSTRINGS = 3 # number of strings in system
NBYPASS = 3 # number of bypass diodes per module, assumes cells divided evenly
def sdm(il, i0, rsh, rs, gamma, vt, vdiode):
"""
Residual for single diode model (SDM) and derivative.
Args:
il (numeric): photogenerated current [A]
i0 (numeric): dark current [A]
rsh (numeric): shunt resistance [Ohms]
rs (numeric): series resistance [Ohms]
gamma (numeric): diode ideality factor
vt (numeric): thermal voltage [V]
vdiode (numeric): diode voltage [V]
Returns:
vcell (numeric): cell voltages [V]
icell (numeric): cell currents [A]
"""
icell = il - i0*(np.exp(vdiode / gamma / vt) - 1.0) - vdiode/rsh
vcell = vdiode - icell*rs # voltage drop across diode in SDM [V]
return icell, vcell
# add some noise to the irradiance and temperature
noise = np.random.randn(NSERIES_CELLS, NMODS, NSTRINGS) / 1000
# estimate upper limit of system voltage
VOC_EST = (GAMMA*VT * np.log(IL / I0 + 1.0))*NMODS*NSERIES_CELLS
VOC_EST
290.3329375367371
# make a distribution that is more closely spaced around Vmp to Voc
# and less spaced near Isc, also in reverse order, so current increases
NPTS = 1000
VDIODE = (VOC_EST-np.logspace(0, np.log10(VOC_EST), NPTS))/(NMODS*NSERIES_CELLS)
plt.plot(VDIODE)
plt.title('diode voltage is log spaced')
plt.xlabel('index')
plt.ylabel('voltage [V]')
plt.grid();
# make the cells with some noise
iv = []
for pvstr in range(NSTRINGS):
for pvmod in range(NMODS):
for pvcell in range(NSERIES_CELLS):
i, v = sdm(IL+noise[pvcell, pvmod, pvstr], I0, RSH, RS, GAMMA, VT, VDIODE)
iv.append([i, v])
pvcells = np.array(iv).reshape(NSTRINGS, NMODS, NSERIES_CELLS, 2, NPTS) # reshape
# make a plot of the the cell-0, mod-0, str-0
plt.plot(pvcells[0, 0, 0][1, :], pvcells[0, 0, 0][0, :])
plt.title('iv curve cell-0, mod-0, str-0')
plt.ylabel('current [A]')
plt.xlabel('voltage [V]')
plt.grid();
# combine substrings and check bypass diode activation
iv = []
ncells_per_sub = NSERIES_CELLS/NBYPASS
for pvstr in range(NSTRINGS):
for pvmod in range(NMODS):
for pvsub in range(NBYPASS):
idx0 = int(pvsub*ncells_per_sub) # 1st index of substring
idx1 = int(idx0+ncells_per_sub) # last indest of substring
isub = pvcells[pvstr, pvmod, idx0][0, :]
vsub = np.zeros_like(isub)
for ivcell in pvcells[pvstr, pvmod, idx0:idx1]:
vsub += np.interp(isub, ivcell[0, :], ivcell[1, :])
vsub[vsub < VBYPASS] = VBYPASS
iv.append([isub, vsub])
pvsubs = np.array(iv).reshape(NSTRINGS, NMODS, NBYPASS, 2, NPTS)
# make a plot of the the substring-0, mod-0, str-0
plt.plot(pvsubs[0, 0, 0][1, :], pvsubs[0, 0, 0][0, :])
plt.title('iv curve substring-0, mod-0, str-0')
plt.ylabel('current [A]')
plt.xlabel('voltage [V]')
plt.grid();
# combine substrings into modules
iv = []
for pvstr in range(NSTRINGS):
for pvmod in range(NMODS):
imod = pvsubs[pvstr, pvmod, 0][0, :]
vmod = np.zeros_like(imod)
for ivsub in pvsubs[pvstr, pvmod, :]:
vmod += np.interp(imod, ivsub[0, :], ivsub[1, :])
iv.append([imod, vmod])
pvmods = np.array(iv).reshape(NSTRINGS, NMODS, 2, NPTS)
# make a plot of the the mod-0, str-0
plt.plot(pvmods[0, 0][1, :], pvmods[0, 0][0, :])
plt.title('iv curve mod-0, str-0')
plt.ylabel('current [A]')
plt.xlabel('voltage [V]')
plt.grid();
# combine modules into strings
iv = []
for pvstr in range(NSTRINGS):
istr = pvmods[pvstr, 0][0, :]
vstr = np.zeros_like(istr)
for ivmod in pvmods[pvstr, :]:
vstr += np.interp(istr, ivmod[0, :], ivmod[1, :])
iv.append([istr, vstr])
pvstrs = np.array(iv).reshape(NSTRINGS, 2, NPTS)
# make a plot of the the str-0
plt.plot(pvstrs[0][1, :], pvstrs[0][0, :])
plt.title('iv curve str-0')
plt.ylabel('current [A]')
plt.xlabel('voltage [V]')
plt.grid();
# combine string into system
vsys = pvstrs[0][1, ::-1]
isys = np.zeros_like(vsys)
for ivstr in pvstrs:
isys += np.interp(vsys, ivstr[1, ::-1], ivstr[0, ::-1])
pvsys = np.array([isys, vsys, isys*vsys])
# make a plot of the the system
f, ax = plt.subplots(1, 2, figsize=(12, 4))
ax[0].plot(pvsys[1, :], pvsys[0, :])
ax[0].set_title('iv curve system')
ax[0].set_ylabel('current [A]')
ax[0].set_xlabel('voltage [V]')
ax[0].grid()
# power
ax[1].plot(pvsys[1, :], pvsys[2, :])
ax[1].set_title('iv curve system')
ax[1].set_ylabel('power [W]')
ax[1].set_xlabel('voltage [V]')
ax[1].grid();
plt.tight_layout()
# approximate max power, depends on NPTS and spacing
Pmp = np.max(pvsys[2, :])
Pmp
4625.798350817047
# approximate location of MPP, depends on NPTS and spacing
mpp = np.argmax(pvsys[2, :])
Imp, Vmp, mpp = pvsys[:, mpp]
Imp, Vmp, mpp
(19.41512223191414, 238.25749307996963, 4625.798350817047)