Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions docs/sphinx/source/whatsnew/v0.6.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Enhancements
:func:`~pvlib.singlediode_methods.bishop88_i_from_v`,
:func:`~pvlib.singlediode_methods.bishop88_v_from_i`, and
:func:`~pvlib.singlediode_methods.bishop88_mpp`.
* Add PVSyst thin-film recombination losses for CdTe and a:Si (:issue:`163`)


Bug fixes
Expand Down
72 changes: 64 additions & 8 deletions pvlib/singlediode_methods.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
"""
Low-level functions for solving the single diode equation.
"""
Expand All @@ -22,6 +23,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,14 +65,20 @@ 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].
diode equation [1]_.

[1] "Computer simulation of the effects of electrical mismatches in
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
https://doi.org/10.1016/0379-6787(88)90059-2
.. warning::
* Do not use ``d2mutau`` with CEC coefficients.
* Usage of ``d2mutau`` with PVSyst coefficients is required for CdTe and
a:Si modules.
Copy link
Member

Choose a reason for hiding this comment

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

I wonder about the short hand for module type here. I suppose this is fairly low level function so we should not worry too much.

* For PVSyst CdTe and a:Si modules, the ``cells_in_series`` parameter
must only account for a single parallel sub-string if the module has
cells in parallel greater than 1.

Parameters
----------
Expand All @@ -86,6 +95,18 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
nNsVth : numeric
product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
and number of series cells ``Ns``
cells_in_series : int
Copy link
Member

Choose a reason for hiding this comment

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

None or int

number of cells in series per parallel module sub-string, only required
for PVSyst thin-film recombination loss, if unset default is ``None``
which raises ``TypeError`` if ``d2mutau`` is set.
d2mutau : numeric
PVSyst thin-film recombination parameter that is the ratio of thickness
of the intrinsic thin-film layer squared :math:`d^2` and the diffusion
length of charge carriers :math:`\\mu \\tau`, in volts [V], defaults to
0[V]
voltage_builtin : numeric
PVSyst thin-film recombination parameter that is the builtin voltage of
the intrinsic thin-film layer, in volts [V], defaults to 0.9[V]
gradients : bool
False returns only I, V, and P. True also returns gradients

Expand All @@ -96,22 +117,57 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
:math:`\\frac{dI}{dV_d}`, :math:`\\frac{dV}{dV_d}`,
:math:`\\frac{dI}{dV}`, :math:`\\frac{dP}{dV}`, and
:math:`\\frac{d^2 P}{dV dV_d}`

Notes
-----
The PVSyst thin-film recombination losses parameters ``d2mutau`` and
``voltage_builtin`` are only applied to cadmium-telluride (CdTe) and
amorphous-silicon (a:Si) PV modules, [2]_, [3]_. The builtin voltage should
account for all junctions. *EG*: tandem and triple junction cell would have
Copy link
Member

Choose a reason for hiding this comment

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

EG -> For example

builtin voltages of 1.8[V] and 2.7[V] respectively, based on the default of
0.9[V] for a single junction.

References
----------
.. [1] "Computer simulation of the effects of electrical mismatches in
photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
:doi:`10.1016/0379-6787(88)90059-2`

.. [2] "Improved equivalent circuit and Analytical Model for Amorphous
Silicon Solar Cells and Modules." J. Mertens, et al., IEEE Transactions
on Electron Devices, Vol 45, No 2, Feb 1998.
:doi:`10.1109/16.658676`

.. [3] "Performance assessment of a simulation model for PV modules of any
available technology", André Mermoud and Thibault Lejeune, 25th EUPVSEC,
2010
:doi:`10.4229/25thEUPVSEC2010-4BV.1.114`
"""
# 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)