Skip to content

ENH: implementing pvsyst recombination loss current for CdTe and a:Si #504

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions pvlib/singlediode_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
# rename newton and set keyword arguments
newton = partial(_array_newton, tol=1e-6, maxiter=100, fprime2=None)

VOLTAGE_BUILTIN = 0.9 # (V) intrinsic voltage for a:Si, CdTe, Mertens et al.


def estimate_voc(photocurrent, saturation_current, nNsVth):
"""
Expand Down Expand Up @@ -62,7 +64,9 @@ def estimate_voc(photocurrent, saturation_current, nNsVth):


def bishop88(diode_voltage, photocurrent, saturation_current,
resistance_series, resistance_shunt, nNsVth, gradients=False):
resistance_series, resistance_shunt, nNsVth, cells_in_series=None,
d2mutau=0, voltage_builtin=VOLTAGE_BUILTIN,
gradients=False):
"""
Explicit calculation of points on the IV curve described by the single
diode equation [1].
Expand Down Expand Up @@ -97,21 +101,31 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
:math:`\\frac{dI}{dV}`, :math:`\\frac{dP}{dV}`, and
:math:`\\frac{d^2 P}{dV dV_d}`
"""
# check if need to calculate recombination loss current
i_recomb, v_recomb = 0, np.inf
if d2mutau > 0:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

d2mutau : numeric implies that it can be an array (see here). This if statement will error if d2mutau is an array. Perhaps the docstring should be changed to scalar?

v_recomb = voltage_builtin * cells_in_series - diode_voltage
i_recomb = photocurrent * d2mutau / v_recomb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would go with

else:
    i_recomb, v_recomb = 0, np.inf

instead of defining them first, then potentially redefining them. Seems cleaner, no?

# calculate temporary values to simplify calculations
v_star = diode_voltage / nNsVth # non-dimensional diode voltage
g_sh = 1.0 / resistance_shunt # conductance
i = (photocurrent - saturation_current * np.expm1(v_star)
- diode_voltage * g_sh)
- diode_voltage * g_sh - i_recomb)
v = diode_voltage - i * resistance_series
retval = (i, v, i*v)
if gradients:
# check again if need to calculate recombination loss current gradients
grad_i_recomb = grad_2i_recomb = 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No offense, but I don't care for this assignment pattern. Two lines, please.

if d2mutau > 0:
grad_i_recomb = i_recomb / v_recomb
grad_2i_recomb = 2 * grad_i_recomb / v_recomb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here, too, I prefer an else statement.

g_diode = saturation_current * np.exp(v_star) / nNsVth # conductance
grad_i = -g_diode - g_sh # di/dvd
grad_i = -g_diode - g_sh - grad_i_recomb # di/dvd
grad_v = 1.0 - grad_i * resistance_series # dv/dvd
# dp/dv = d(iv)/dv = v * di/dv + i
grad = grad_i / grad_v # di/dv
grad_p = v * grad + i # dp/dv
grad2i = -g_diode / nNsVth # d2i/dvd
grad2i = -g_diode / nNsVth - grad_2i_recomb # d2i/dvd
grad2v = -grad2i * resistance_series # d2v/dvd
grad2p = (
grad_v * grad + v * (grad2i/grad_v - grad_i*grad2v/grad_v**2)
Expand Down
67 changes: 66 additions & 1 deletion pvlib/test/test_singlediode_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import numpy as np
from pvlib import pvsystem
from pvlib.singlediode_methods import bishop88, estimate_voc
import pytest
from conftest import requires_scipy

POA = 888
Expand Down Expand Up @@ -100,7 +102,7 @@ def test_brentq_spr_e20_327():

@requires_scipy
def test_brentq_fs_495():
"""test pvsystem.singlediode with Brent method on SPR-E20-327"""
"""test pvsystem.singlediode with Brent method on FS495"""
fs_495 = CECMOD.First_Solar_FS_495
x = pvsystem.calcparams_desoto(
effective_irradiance=POA, temp_cell=TCELL,
Expand All @@ -125,3 +127,66 @@ def test_brentq_fs_495():
method='lambertw')
assert np.isclose(pvs_ixx, ixx)
return isc, voc, imp, vmp, pmp, i, v, pvs


# PVsyst parameters for First Solar FS-495 module from PVSyst-6.7.2 database
# I_L_ref derived from Isc_ref conditions:
# I_L_ref = (I_sc_ref + Id + Ish) / (1 - d2mutau/(Vbi*N_s - Vd))
# where
# Vd = I_sc_ref * R_s
# Id = I_o_ref * (exp(Vd / nNsVt) - 1)
# Ish = Vd / R_sh_ref
PVSYST_FS_495 = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dangerous to use a module level dictionary in tests because it can be modified. Suggest turning this into a pytest fixture

@pytest.fixture
def PVSYST_FS_495():
    return {...}

probably with a lowercase name but no big deal here.

'd2mutau': 1.31, 'alpha_sc': 0.00039, 'gamma_ref': 1.48, 'mu_gamma': 0.001,
'I_o_ref': 9.62e-10, 'R_sh_ref': 5000, 'R_sh_0': 12500, 'R_sh_exp': 3.1,
'R_s': 4.6, 'beta_oc': -0.2116, 'EgRef': 1.5, 'cells_in_series': 108,
'cells_in_parallel': 2, 'I_sc_ref': 1.55, 'V_oc_ref': 86.5,
'I_mp_ref': 1.4, 'V_mp_ref': 67.85, 'temp_ref': 25, 'irrad_ref': 1000,
'I_L_ref': 1.5743233463848496
}


@pytest.mark.parametrize(
'poa, temp_cell, expected, tol',
[(PVSYST_FS_495['irrad_ref'], PVSYST_FS_495['temp_ref'],
{'pmp': PVSYST_FS_495['I_mp_ref']*PVSYST_FS_495['V_mp_ref'],
'isc': PVSYST_FS_495['I_sc_ref'], 'voc': PVSYST_FS_495['V_oc_ref']},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't recall if these dict look ups will work if you turn PVSYST_FS_495 into a fixture. If not, I guess it's ok to leave as is for now.

(5e-4, 0.04)),
(POA, TCELL, {'pmp': 76.26, 'isc': 1.387, 'voc': 79.29}, (1e-3, 1e-3))]
) # DeSoto @(888[W/m**2], 55[degC]) = {Pmp: 72.71, Isc: 1.402, Voc: 75.42)
def test_pvsyst_recombination_loss(poa, temp_cell, expected, tol):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if PVSYST_FS_495 becomes a fixture, add it as a parameter so that the value can be accessed in line 161 below.

"""test PVSst recombination loss"""
# first evaluate PVSyst model with thin-film recombination loss current
# at reference conditions
pvsyst_fs_495 = PVSYST_FS_495
x = pvsystem.calcparams_pvsyst(
effective_irradiance=poa, temp_cell=temp_cell,
alpha_sc=pvsyst_fs_495['alpha_sc'],
gamma_ref=pvsyst_fs_495['gamma_ref'],
mu_gamma=pvsyst_fs_495['mu_gamma'], I_L_ref=pvsyst_fs_495['I_L_ref'],
I_o_ref=pvsyst_fs_495['I_o_ref'], R_sh_ref=pvsyst_fs_495['R_sh_ref'],
R_sh_0=pvsyst_fs_495['R_sh_0'], R_sh_exp=pvsyst_fs_495['R_sh_exp'],
R_s=pvsyst_fs_495['R_s'],
cells_in_series=pvsyst_fs_495['cells_in_series'],
EgRef=pvsyst_fs_495['EgRef']
)
il_pvsyst, io_pvsyst, rs_pvsyst, rsh_pvsyst, nnsvt_pvsyst = x
voc_est_pvsyst = estimate_voc(photocurrent=il_pvsyst,
saturation_current=io_pvsyst,
nNsVth=nnsvt_pvsyst)
vd_pvsyst = np.linspace(0, voc_est_pvsyst, 1000)
pvsyst = bishop88(
diode_voltage=vd_pvsyst, photocurrent=il_pvsyst,
saturation_current=io_pvsyst, resistance_series=rs_pvsyst,
resistance_shunt=rsh_pvsyst, nNsVth=nnsvt_pvsyst,
d2mutau=pvsyst_fs_495['d2mutau'],
cells_in_series=pvsyst_fs_495['cells_in_series']
)
# test max power
assert np.isclose(max(pvsyst[2]), expected['pmp'], *tol)
# test short circuit current
isc_pvsyst = np.interp(0, pvsyst[1], pvsyst[0])
assert np.isclose(isc_pvsyst, expected['isc'], *tol)
# test open circuit current
voc_pvsyst = np.interp(0, pvsyst[0][::-1], pvsyst[1][::-1])
assert np.isclose(voc_pvsyst, expected['voc'], *tol)