Skip to content

Commit 7bd1a3f

Browse files
add capability for reverse bias (#948)
* add capability for reverse bias * lint, correct test_pvsyst_breakdown * default value for breakdown_factor * handle recombination in breakdown test, lint * correct parameter parsing * whatsnew * Update pvlib/tests/test_singlediode.py Co-Authored-By: Cameron Stark <CameronTStark@users.noreply.github.com> * Update pvlib/singlediode.py Co-Authored-By: Cameron Stark <CameronTStark@users.noreply.github.com> * Update pvlib/singlediode.py Co-Authored-By: Cameron Stark <CameronTStark@users.noreply.github.com> * fix formatting * improve docstring * lint Co-authored-by: Cameron Stark <CameronTStark@users.noreply.github.com>
1 parent 06f8f6e commit 7bd1a3f

File tree

3 files changed

+236
-52
lines changed

3 files changed

+236
-52
lines changed

docs/sphinx/source/whatsnew/v0.7.2.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ Enhancements
3535
* Add new module :py:mod:`pvlib.snow` to contain models related to snow coverage and effects on a PV system. (:pull:`764`)
3636
* Add snow coverage model :py:func:`pvlib.snow.coverage_nrel` and function to identify when modules are fully covered by snow :py:func:`pvlib.snow.fully_covered_nrel`. (:issue:`577`)
3737
* Add function :py:func:`pvlib.snow.dc_loss_nrel` for effect of snow coverage on DC output. (:pull:`764`)
38+
* Add capability to calculate current at reverse bias using an avalanche
39+
breakdown model, affects :py:func:`pvlib.singlediode.bishop88`,
40+
:py:func:`pvlib.singlediode.bishop88_i_from_v`, :py:func:`pvlib.singlediode.bishop88_v_from_i`,
41+
:py:func:`pvlib.singlediode.bishop88_mpp`. (:pull:`948`)
3842

3943
Bug fixes
4044
~~~~~~~~~

pvlib/singlediode.py

Lines changed: 132 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -69,42 +69,65 @@ def estimate_voc(photocurrent, saturation_current, nNsVth):
6969

7070
def bishop88(diode_voltage, photocurrent, saturation_current,
7171
resistance_series, resistance_shunt, nNsVth, d2mutau=0,
72-
NsVbi=np.Inf, gradients=False):
73-
"""
72+
NsVbi=np.Inf, breakdown_factor=0., breakdown_voltage=-5.5,
73+
breakdown_exp=3.28, gradients=False):
74+
r"""
7475
Explicit calculation of points on the IV curve described by the single
7576
diode equation. Values are calculated as described in [1]_.
7677
78+
The single diode equation with recombination current and reverse bias
79+
breakdown is
80+
81+
.. math::
82+
83+
I = I_{L} - I_{0} (\exp \frac{V_{d}}{nNsVth} - 1)
84+
- \frac{V_{d}}{R_{sh}}
85+
- \frac{I_{L} \frac{d^{2}}{\mu \tau}{N_{s} V_{bi} - V_{d}}
86+
- a \frac{V_{d}{R_{sh}} (1 - \frac{V_{d}}{V_{br}})^-m
87+
88+
The input `diode_voltage` must be :math:`V + I R_{s}`.
89+
90+
7791
.. warning::
92+
* Usage of ``d2mutau`` is required with PVSyst
93+
coefficients for cadmium-telluride (CdTe) and amorphous-silicon
94+
(a:Si) PV modules only.
7895
* Do not use ``d2mutau`` with CEC coefficients.
79-
* Usage of ``d2mutau`` with PVSyst coefficients is required for cadmium-
80-
telluride (CdTe) and amorphous-silicon (a:Si) PV modules only.
8196
8297
Parameters
8398
----------
8499
diode_voltage : numeric
85100
diode voltages [V]
86101
photocurrent : numeric
87-
photo-generated current [A]
102+
photo-generated current :math:`I_{L}` [A]
88103
saturation_current : numeric
89-
diode reverse saturation current [A]
104+
diode reverse saturation current :math:`I_{0}` [A]
90105
resistance_series : numeric
91-
series resistance [ohms]
106+
series resistance :math:`R_{s}` [ohms]
92107
resistance_shunt: numeric
93-
shunt resistance [ohms]
108+
shunt resistance :math:`R_{sh}` [ohms]
94109
nNsVth : numeric
95-
product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
96-
and number of series cells ``Ns``
110+
product of thermal voltage :math:`V_{th}` [V], diode ideality factor
111+
``n``, and number of series cells :math:`N_{s}`
97112
d2mutau : numeric, default 0
98113
PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
99114
(a-Si) modules that accounts for recombination current in the
100115
intrinsic layer. The value is the ratio of intrinsic layer thickness
101116
squared :math:`d^2` to the diffusion length of charge carriers
102-
:math:`\\mu \\tau`. [V]
117+
:math:`\mu \tau`. [V]
103118
NsVbi : numeric, default np.inf
104119
PVsyst parameter for cadmium-telluride (CdTe) and amorphous-silicon
105120
(a-Si) modules that is the product of the PV module number of series
106-
cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
107-
[V].
121+
cells :math:`N_{s}` and the builtin voltage :math:`V_{bi}` of the
122+
intrinsic layer. [V].
123+
breakdown_factor : numeric, default 0
124+
fraction of ohmic current involved in avalanche breakdown :math:`a`.
125+
Default of 0 excludes the reverse bias term from the model. [unitless]
126+
breakdown_voltage : numeric, default -5.5
127+
reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
128+
[V]
129+
breakdown_exp : numeric, default 3.28
130+
avalanche breakdown exponent :math:`m` [unitless]
108131
gradients : bool
109132
False returns only I, V, and P. True also returns gradients
110133
@@ -150,21 +173,39 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
150173
# calculate temporary values to simplify calculations
151174
v_star = diode_voltage / nNsVth # non-dimensional diode voltage
152175
g_sh = 1.0 / resistance_shunt # conductance
153-
i = (photocurrent - saturation_current * np.expm1(v_star)
154-
- diode_voltage * g_sh - i_recomb)
176+
if breakdown_factor > 0: # reverse bias is considered
177+
brk_term = 1 - diode_voltage / breakdown_voltage
178+
brk_pwr = np.power(brk_term, -breakdown_exp)
179+
i_breakdown = breakdown_factor * diode_voltage * g_sh * brk_pwr
180+
else:
181+
i_breakdown = 0.
182+
i = (photocurrent - saturation_current * np.expm1(v_star) # noqa: W503
183+
- diode_voltage * g_sh - i_recomb - i_breakdown) # noqa: W503
155184
v = diode_voltage - i * resistance_series
156185
retval = (i, v, i*v)
157186
if gradients:
158187
# calculate recombination loss current gradients where d2mutau > 0
159188
grad_i_recomb = np.where(is_recomb, i_recomb / v_recomb, 0)
160189
grad_2i_recomb = np.where(is_recomb, 2 * grad_i_recomb / v_recomb, 0)
161190
g_diode = saturation_current * np.exp(v_star) / nNsVth # conductance
162-
grad_i = -g_diode - g_sh - grad_i_recomb # di/dvd
191+
if breakdown_factor > 0: # reverse bias is considered
192+
brk_pwr_1 = np.power(brk_term, -breakdown_exp - 1)
193+
brk_pwr_2 = np.power(brk_term, -breakdown_exp - 2)
194+
brk_fctr = breakdown_factor * g_sh
195+
grad_i_brk = brk_fctr * (brk_pwr + diode_voltage *
196+
-breakdown_exp * brk_pwr_1)
197+
grad2i_brk = (brk_fctr * -breakdown_exp # noqa: W503
198+
* (2 * brk_pwr_1 + diode_voltage # noqa: W503
199+
* (-breakdown_exp - 1) * brk_pwr_2)) # noqa: W503
200+
else:
201+
grad_i_brk = 0.
202+
grad2i_brk = 0.
203+
grad_i = -g_diode - g_sh - grad_i_recomb - grad_i_brk # di/dvd
163204
grad_v = 1.0 - grad_i * resistance_series # dv/dvd
164205
# dp/dv = d(iv)/dv = v * di/dv + i
165206
grad = grad_i / grad_v # di/dv
166207
grad_p = v * grad + i # dp/dv
167-
grad2i = -g_diode / nNsVth - grad_2i_recomb # d2i/dvd
208+
grad2i = -g_diode / nNsVth - grad_2i_recomb - grad2i_brk # d2i/dvd
168209
grad2v = -grad2i * resistance_series # d2v/dvd
169210
grad2p = (
170211
grad_v * grad + v * (grad2i/grad_v - grad_i*grad2v/grad_v**2)
@@ -176,7 +217,9 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
176217

177218
def bishop88_i_from_v(voltage, photocurrent, saturation_current,
178219
resistance_series, resistance_shunt, nNsVth,
179-
d2mutau=0, NsVbi=np.Inf, method='newton'):
220+
d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
221+
breakdown_voltage=-5.5, breakdown_exp=3.28,
222+
method='newton'):
180223
"""
181224
Find current given any voltage.
182225
@@ -185,13 +228,13 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
185228
voltage : numeric
186229
voltage (V) in volts [V]
187230
photocurrent : numeric
188-
photogenerated current (Iph or IL) in amperes [A]
231+
photogenerated current (Iph or IL) [A]
189232
saturation_current : numeric
190-
diode dark or saturation current (Io or Isat) in amperes [A]
233+
diode dark or saturation current (Io or Isat) [A]
191234
resistance_series : numeric
192-
series resistance (Rs) in ohms
235+
series resistance (Rs) in [Ohm]
193236
resistance_shunt : numeric
194-
shunt resistance (Rsh) in ohms
237+
shunt resistance (Rsh) [Ohm]
195238
nNsVth : numeric
196239
product of diode ideality factor (n), number of series cells (Ns), and
197240
thermal voltage (Vth = k_b * T / q_e) in volts [V]
@@ -206,18 +249,27 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
206249
(a-Si) modules that is the product of the PV module number of series
207250
cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
208251
[V].
209-
method : str
210-
one of two optional search methods: either ``'brentq'``, a reliable and
211-
bounded method or ``'newton'`` which is the default.
252+
breakdown_factor : numeric, default 0
253+
fraction of ohmic current involved in avalanche breakdown :math:`a`.
254+
Default of 0 excludes the reverse bias term from the model. [unitless]
255+
breakdown_voltage : numeric, default -5.5
256+
reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
257+
[V]
258+
breakdown_exp : numeric, default 3.28
259+
avalanche breakdown exponent :math:`m` [unitless]
260+
method : str, default 'newton'
261+
Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
262+
if ``breakdown_factor`` is not 0.
212263
213264
Returns
214265
-------
215266
current : numeric
216-
current (I) at the specified voltage (V) in amperes [A]
267+
current (I) at the specified voltage (V). [A]
217268
"""
218269
# collect args
219270
args = (photocurrent, saturation_current, resistance_series,
220-
resistance_shunt, nNsVth, d2mutau, NsVbi)
271+
resistance_shunt, nNsVth, d2mutau, NsVbi,
272+
breakdown_factor, breakdown_voltage, breakdown_exp)
221273

222274
def fv(x, v, *a):
223275
# calculate voltage residual given diode voltage "x"
@@ -230,9 +282,12 @@ def fv(x, v, *a):
230282
# brentq only works with scalar inputs, so we need a set up function
231283
# and np.vectorize to repeatedly call the optimizer with the right
232284
# arguments for possible array input
233-
def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi):
285+
def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
286+
breakdown_factor, breakdown_voltage, breakdown_exp):
234287
return brentq(fv, 0.0, voc,
235-
args=(v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi))
288+
args=(v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
289+
breakdown_factor, breakdown_voltage,
290+
breakdown_exp))
236291

237292
vd_from_brent_vectorized = np.vectorize(vd_from_brent)
238293
vd = vd_from_brent_vectorized(voc_est, voltage, *args)
@@ -250,7 +305,9 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi):
250305

251306
def bishop88_v_from_i(current, photocurrent, saturation_current,
252307
resistance_series, resistance_shunt, nNsVth,
253-
d2mutau=0, NsVbi=np.Inf, method='newton'):
308+
d2mutau=0, NsVbi=np.Inf, breakdown_factor=0.,
309+
breakdown_voltage=-5.5, breakdown_exp=3.28,
310+
method='newton'):
254311
"""
255312
Find voltage given any current.
256313
@@ -259,13 +316,13 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
259316
current : numeric
260317
current (I) in amperes [A]
261318
photocurrent : numeric
262-
photogenerated current (Iph or IL) in amperes [A]
319+
photogenerated current (Iph or IL) [A]
263320
saturation_current : numeric
264-
diode dark or saturation current (Io or Isat) in amperes [A]
321+
diode dark or saturation current (Io or Isat) [A]
265322
resistance_series : numeric
266-
series resistance (Rs) in ohms
323+
series resistance (Rs) in [Ohm]
267324
resistance_shunt : numeric
268-
shunt resistance (Rsh) in ohms
325+
shunt resistance (Rsh) [Ohm]
269326
nNsVth : numeric
270327
product of diode ideality factor (n), number of series cells (Ns), and
271328
thermal voltage (Vth = k_b * T / q_e) in volts [V]
@@ -280,9 +337,17 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
280337
(a-Si) modules that is the product of the PV module number of series
281338
cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
282339
[V].
283-
method : str
284-
one of two optional search methods: either ``'brentq'``, a reliable and
285-
bounded method or ``'newton'`` which is the default.
340+
breakdown_factor : numeric, default 0
341+
fraction of ohmic current involved in avalanche breakdown :math:`a`.
342+
Default of 0 excludes the reverse bias term from the model. [unitless]
343+
breakdown_voltage : numeric, default -5.5
344+
reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
345+
[V]
346+
breakdown_exp : numeric, default 3.28
347+
avalanche breakdown exponent :math:`m` [unitless]
348+
method : str, default 'newton'
349+
Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
350+
if ``breakdown_factor`` is not 0.
286351
287352
Returns
288353
-------
@@ -291,7 +356,8 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
291356
"""
292357
# collect args
293358
args = (photocurrent, saturation_current, resistance_series,
294-
resistance_shunt, nNsVth, d2mutau, NsVbi)
359+
resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor,
360+
breakdown_voltage, breakdown_exp)
295361
# first bound the search using voc
296362
voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
297363

@@ -303,9 +369,12 @@ def fi(x, i, *a):
303369
# brentq only works with scalar inputs, so we need a set up function
304370
# and np.vectorize to repeatedly call the optimizer with the right
305371
# arguments for possible array input
306-
def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi):
372+
def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
373+
breakdown_factor, breakdown_voltage, breakdown_exp):
307374
return brentq(fi, 0.0, voc,
308-
args=(i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi))
375+
args=(i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
376+
breakdown_factor, breakdown_voltage,
377+
breakdown_exp))
309378

310379
vd_from_brent_vectorized = np.vectorize(vd_from_brent)
311380
vd = vd_from_brent_vectorized(voc_est, current, *args)
@@ -323,20 +392,21 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi):
323392

324393
def bishop88_mpp(photocurrent, saturation_current, resistance_series,
325394
resistance_shunt, nNsVth, d2mutau=0, NsVbi=np.Inf,
326-
method='newton'):
395+
breakdown_factor=0., breakdown_voltage=-5.5,
396+
breakdown_exp=3.28, method='newton'):
327397
"""
328398
Find max power point.
329399
330400
Parameters
331401
----------
332402
photocurrent : numeric
333-
photogenerated current (Iph or IL) in amperes [A]
403+
photogenerated current (Iph or IL) [A]
334404
saturation_current : numeric
335-
diode dark or saturation current (Io or Isat) in amperes [A]
405+
diode dark or saturation current (Io or Isat) [A]
336406
resistance_series : numeric
337-
series resistance (Rs) in ohms
407+
series resistance (Rs) in [Ohm]
338408
resistance_shunt : numeric
339-
shunt resistance (Rsh) in ohms
409+
shunt resistance (Rsh) [Ohm]
340410
nNsVth : numeric
341411
product of diode ideality factor (n), number of series cells (Ns), and
342412
thermal voltage (Vth = k_b * T / q_e) in volts [V]
@@ -351,9 +421,17 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
351421
(a-Si) modules that is the product of the PV module number of series
352422
cells ``Ns`` and the builtin voltage ``Vbi`` of the intrinsic layer.
353423
[V].
354-
method : str
355-
one of two optional search methods: either ``'brentq'``, a reliable and
356-
bounded method or ``'newton'`` which is the default.
424+
breakdown_factor : numeric, default 0
425+
fraction of ohmic current involved in avalanche breakdown :math:`a`.
426+
Default of 0 excludes the reverse bias term from the model. [unitless]
427+
breakdown_voltage : numeric, default -5.5
428+
reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
429+
[V]
430+
breakdown_exp : numeric, default 3.28
431+
avalanche breakdown exponent :math:`m` [unitless]
432+
method : str, default 'newton'
433+
Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
434+
if ``breakdown_factor`` is not 0.
357435
358436
Returns
359437
-------
@@ -363,7 +441,8 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
363441
"""
364442
# collect args
365443
args = (photocurrent, saturation_current, resistance_series,
366-
resistance_shunt, nNsVth, d2mutau, NsVbi)
444+
resistance_shunt, nNsVth, d2mutau, NsVbi, breakdown_factor,
445+
breakdown_voltage, breakdown_exp)
367446
# first bound the search using voc
368447
voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)
369448

@@ -373,9 +452,10 @@ def fmpp(x, *a):
373452
if method.lower() == 'brentq':
374453
# break out arguments for numpy.vectorize to handle broadcasting
375454
vec_fun = np.vectorize(
376-
lambda voc, iph, isat, rs, rsh, gamma, d2mutau, NsVbi:
377-
brentq(fmpp, 0.0, voc,
378-
args=(iph, isat, rs, rsh, gamma, d2mutau, NsVbi))
455+
lambda voc, iph, isat, rs, rsh, gamma, d2mutau, NsVbi, vbr_a, vbr,
456+
vbr_exp: brentq(fmpp, 0.0, voc,
457+
args=(iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
458+
vbr_a, vbr, vbr_exp))
379459
)
380460
vd = vec_fun(voc_est, *args)
381461
elif method.lower() == 'newton':

0 commit comments

Comments
 (0)