-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Changes from 4 commits
4b0df1b
2bd8b13
1650bc6
9d96585
f5d6dda
a62759e
bfa9c5f
79fde5b
1e9434a
3d0deb2
8a1efcb
21157e2
adde5bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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): | ||
| """ | ||
|
|
@@ -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, d2mutau=0, | ||
| cells_in_series=None, voltage_builtin=VOLTAGE_BUILTIN, | ||
| gradients=False): | ||
| """ | ||
| Explicit calculation of points on the IV curve described by the single | ||
| diode equation [1]. | ||
|
|
@@ -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: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| v_recomb = voltage_builtin * cells_in_series - diode_voltage | ||
| i_recomb = photocurrent * d2mutau / v_recomb | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would go with 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
|
|
||
| import numpy as np | ||
| from pvlib import pvsystem | ||
| from pvlib.singlediode_methods import bishop88, estimate_voc, VOLTAGE_BUILTIN | ||
| from conftest import requires_scipy | ||
|
|
||
| POA = 888 | ||
|
|
@@ -125,3 +126,135 @@ def test_brentq_fs_495(): | |
| method='lambertw') | ||
| assert np.isclose(pvs_ixx, ixx) | ||
| return isc, voc, imp, vmp, pmp, i, v, pvs | ||
|
|
||
|
|
||
| def pvsyst_fs_495(): | ||
| """ | ||
| PVSyst First Solar FS-495 parameters. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Actually I would prefer to simply hard-code the dict of results, and provide a brief statement for the source of these values - where does the initial
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ the initial data comes from the PVSyst v6.7.2 database, I don't think any of the values were altered but it was vetted internally. I've removed the method, and hard coded the values with a comment re: initial source.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not having a current PVsyst license, I'm guessing that it does NOT permit uploading that database to pvlib?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not the whole database, but I'm guessing the relevant values come from pan files generated by FS. |
||
|
|
||
| Returns | ||
| ------- | ||
| dictionary of PVSyst First Solar FS-495 parameters | ||
| """ | ||
| fs_495 = dict(d2mutau=1.31, alpha_sc=0.00039, gamma_ref=1.48, | ||
| mu_gamma=0.001, I_o_ref=0.962e-9, R_sh_ref=5000, | ||
| R_sh_0=12500, R_sh_exp=3.1, R_s=4.6, beta_oc=-0.2116, | ||
| EgRef=1.475, 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) | ||
| Vt = 0.025693001600485238 # thermal voltage at reference (V) | ||
| nNsVt = fs_495['cells_in_series'] * fs_495['gamma_ref'] * Vt | ||
| Vd = fs_495['I_sc_ref'] * fs_495['R_s'] # diode voltage at short circuit | ||
| Id = fs_495['I_o_ref'] * (np.exp(Vd / nNsVt) - 1) # diode current (A) | ||
| Ish = Vd / fs_495['R_sh_ref'] # shunt current (A) | ||
| # builtin potential difference (V) | ||
| dv = VOLTAGE_BUILTIN * fs_495['cells_in_series'] - Vd | ||
| # calculate photo-generated current at reference condition (A) | ||
| fs_495['I_L_ref'] = ( | ||
| (fs_495['I_sc_ref'] + Id + Ish) / (1 - fs_495['d2mutau'] / dv) | ||
| ) | ||
| return fs_495 | ||
|
|
||
|
|
||
| PVSYST_FS_495 = pvsyst_fs_495() # PVSyst First Solar FS-495 parameters | ||
|
|
||
|
|
||
| def test_pvsyst_fs_495_recombination_loss(): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the test is more general than just the
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ agreed, changed to |
||
| """test pvsystem.singlediode with Brent method on SPR-E20-327""" | ||
| poa, temp_cell = 1000.0, 25.0 # test conditions | ||
|
|
||
| # first evaluate DeSoto model | ||
| cec_fs_495 = CECMOD.First_Solar_FS_495 # CEC parameters for | ||
| il_cec, io_cec, rs_cec, rsh_cec, nnsvt_cec = pvsystem.calcparams_desoto( | ||
| effective_irradiance=poa, temp_cell=temp_cell, | ||
| alpha_sc=cec_fs_495.alpha_sc, a_ref=cec_fs_495.a_ref, | ||
| I_L_ref=cec_fs_495.I_L_ref, I_o_ref=cec_fs_495.I_o_ref, | ||
| R_sh_ref=cec_fs_495.R_sh_ref, R_s=cec_fs_495.R_s, | ||
| EgRef=1.475, dEgdT=-0.0003 | ||
| ) | ||
| voc_est_cec = estimate_voc(photocurrent=il_cec, saturation_current=io_cec, | ||
| nNsVth=nnsvt_cec) | ||
| vd_cec = np.linspace(0, voc_est_cec, 1000) | ||
| desoto = bishop88( | ||
| diode_voltage=vd_cec, photocurrent=il_cec, saturation_current=io_cec, | ||
| resistance_series=rs_cec, resistance_shunt=rsh_cec, nNsVth=nnsvt_cec | ||
| ) | ||
|
|
||
| # now evaluate PVSyst model with thin-film recombination loss current | ||
| 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 expected change in max power | ||
| assert np.isclose(max(desoto[2]) - max(pvsyst[2]), 0.01949420697212645) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test passes if the Desoto model and the PVsyst model are 'close', meaning that the test would fail is the CEC parameter database entry for this module changes, if there is an error in the Desoto functions, or if the PVsyst function has an error. I'd change the test condition to compare the PVsyst output to known values calculated by a separate method.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ I've changed the test to be independent of DeSoto, now it only tests 2 conditions:
But I should note that the output at (888[W/m^2], 55[degC]) doesn't agree well with DeSoto at the same conditions: In [1]: from pvlib import pvsystem
In [2]: CECMOD = pvsystem.retrieve_sam('cecmod')
In [3]: fs_495 = CECMOD.First_Solar_FS_495
In [4]: il, io, rs, rsh, nnsvt= pvsystem.calcparams_desoto(888, 55,
...: alpha_sc=fs_495.alpha_sc, a_ref=fs_495.a_ref, I_L_ref=fs_495.I_L_ref,
...: I_o_ref=fs_495.I_o_ref, R_sh_ref=fs_495.R_sh_ref, R_s=fs_495.R_s,
...: EgRef=1.475, dEgdT=-0.0003)
In [5]: pvs = pvsystem.singlediode(il, io, rs, rsh, nnsvt, method='lambertw')
In [6]: pvs
Out[6]:
OrderedDict([('i_sc', 1.4020527838304147), # PVSyst: 1.3868344548308347
('v_oc', 75.41704855182024), # PVSyst: 79.29198489135703
('i_mp', 1.2599626119945482),
('v_mp', 57.70813302772382),
('p_mp', 72.71009002293975), # PVSyst: 76.26211540956041
('i_x', 1.3606253515830833),
('i_xx', 0.8340096657730403)])According to @jdnewmil this is possibly due to a setting in PVSyst that adjusts
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cwhanse if we wanted to try to emulate this 2nd order
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree. I don't know what PVsyst might be doing behind the interface when
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it isn't clear what PVsyst is doing, then I suggest mailing Bruno to get clarification. I think they're quite open about the calculations. |
||
| # test expected change in short circuit current | ||
| isc_desoto = np.interp(0, desoto[1], desoto[0]) | ||
| isc_pvsyst = np.interp(0, pvsyst[1], pvsyst[0]) | ||
| assert np.isclose(isc_desoto - isc_pvsyst, -7.955827628380874e-05) | ||
| # test expected change in open circuit current | ||
| voc_desoto = np.interp(0, desoto[0][::-1], desoto[1][::-1]) | ||
| voc_pvsyst = np.interp(0, pvsyst[0][::-1], pvsyst[1][::-1]) | ||
| assert np.isclose(voc_desoto - voc_pvsyst, -0.04184247739671321) | ||
| return desoto, pvsyst | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Although great for development and testing, I believe we don't keep
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✔️ agreed, removed |
||
| import matplotlib.pyplot as plt | ||
| a, b = test_pvsyst_fs_495_recombination_loss() | ||
| ab0 = np.interp(a[1], b[1], b[0]) | ||
| pmpa, pmpb = max(a[2]), max(b[2]) | ||
| isca = np.interp(0, a[1], a[0]) | ||
| iscb = np.interp(0, b[1], b[0]) | ||
| voca = np.interp(0, a[0][::-1], a[1][::-1]) | ||
| vocb = np.interp(0, b[0][::-1], b[1][::-1]) | ||
| f1 = plt.figure('power') | ||
| plt.plot(a[1], a[2], a[1], ab0 * a[1], b[1], b[2], '--', | ||
| a[1], a[2] - ab0 * a[1]) | ||
| plt.plot([a[1][0], a[1][-1]], [pmpa]*2, ':', | ||
| [b[1][0], b[1][-1]], [pmpb]*2, ':') | ||
| plt.legend(['DeSoto', 'PVSyst interpolated', 'PVSyst', '$\Delta P$', | ||
| '$P_{mp,DeSoto}=%4.1f$' % pmpa, | ||
| '$P_{mp,PVSyst}=%4.1f$' % pmpb]) | ||
| plt.grid() | ||
| plt.xlabel('voltage (V)') | ||
| plt.ylabel('power (W)') | ||
| plt.title('FS-495 power, DeSoto vs. PVSyst with recombination loss') | ||
| f1.show() | ||
| f2 = plt.figure('current') | ||
| plt.plot(a[1], a[0], a[1], ab0, b[1], b[0], '--', a[1], a[0] - ab0) | ||
| plt.plot([a[1][0], a[1][-1]], [isca]*2, ':', | ||
| [b[1][0], b[1][-1]], [iscb]*2, ':') | ||
| plt.plot([voca]*2, [a[0][0], a[0][-1]], ':', | ||
| [vocb]*2, [b[0][0], b[0][-1]], ':') | ||
| plt.legend(['DeSoto', 'PVSyst interpolated', 'PVSyst', '$\Delta I$', | ||
| '$I_{sc,DeSoto}=%4.2f$' % isca, | ||
| '$I_{sc,PVSyst}=%4.2f$' % iscb, | ||
| '$V_{oc,DeSoto}=%4.1f$' % voca, | ||
| '$V_{oc,PVSyst}=%4.1f$' % vocb]) | ||
| plt.grid() | ||
| plt.xlabel('voltage (V)') | ||
| plt.ylabel('current (A)') | ||
| plt.title('FS-495 IV-curve, DeSoto vs. PVSyst with recombination loss') | ||
| f2.show() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit - re-order the arguments to keep the non-recombination current terms first, followed by
d2mutauandvoltage_builtin- just movecells_in_seriesup one position.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️