@@ -110,13 +110,13 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
110110 (a-Si) modules that is the product of the PV module number of series
111111 cells :math:`N_{s}` and the builtin voltage :math:`V_{bi}` of the
112112 intrinsic layer. [V].
113- breakdown_factor : float , default 0
113+ breakdown_factor : numeric , default 0
114114 fraction of ohmic current involved in avalanche breakdown :math:`a`.
115115 Default of 0 excludes the reverse bias term from the model. [unitless]
116- breakdown_voltage : float , default -5.5
116+ breakdown_voltage : numeric , default -5.5
117117 reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
118118 [V]
119- breakdown_exp : float , default 3.28
119+ breakdown_exp : numeric , default 3.28
120120 avalanche breakdown exponent :math:`m` [unitless]
121121 gradients : bool
122122 False returns only I, V, and P. True also returns gradients
@@ -163,12 +163,11 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
163163 # calculate temporary values to simplify calculations
164164 v_star = diode_voltage / nNsVth # non-dimensional diode voltage
165165 g_sh = 1.0 / resistance_shunt # conductance
166- if breakdown_factor > 0 : # reverse bias is considered
167- brk_term = 1 - diode_voltage / breakdown_voltage
168- brk_pwr = np .power (brk_term , - breakdown_exp )
169- i_breakdown = breakdown_factor * diode_voltage * g_sh * brk_pwr
170- else :
171- i_breakdown = 0.
166+
167+ brk_term = 1 - diode_voltage / breakdown_voltage
168+ brk_pwr = np .power (brk_term , - breakdown_exp )
169+ i_breakdown = breakdown_factor * diode_voltage * g_sh * brk_pwr
170+
172171 i = (photocurrent - saturation_current * np .expm1 (v_star ) # noqa: W503
173172 - diode_voltage * g_sh - i_recomb - i_breakdown ) # noqa: W503
174173 v = diode_voltage - i * resistance_series
@@ -178,18 +177,14 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
178177 grad_i_recomb = np .where (is_recomb , i_recomb / v_recomb , 0 )
179178 grad_2i_recomb = np .where (is_recomb , 2 * grad_i_recomb / v_recomb , 0 )
180179 g_diode = saturation_current * np .exp (v_star ) / nNsVth # conductance
181- if breakdown_factor > 0 : # reverse bias is considered
182- brk_pwr_1 = np .power (brk_term , - breakdown_exp - 1 )
183- brk_pwr_2 = np .power (brk_term , - breakdown_exp - 2 )
184- brk_fctr = breakdown_factor * g_sh
185- grad_i_brk = brk_fctr * (brk_pwr + diode_voltage *
186- - breakdown_exp * brk_pwr_1 )
187- grad2i_brk = (brk_fctr * - breakdown_exp # noqa: W503
188- * (2 * brk_pwr_1 + diode_voltage # noqa: W503
189- * (- breakdown_exp - 1 ) * brk_pwr_2 )) # noqa: W503
190- else :
191- grad_i_brk = 0.
192- grad2i_brk = 0.
180+ brk_pwr_1 = np .power (brk_term , - breakdown_exp - 1 )
181+ brk_pwr_2 = np .power (brk_term , - breakdown_exp - 2 )
182+ brk_fctr = breakdown_factor * g_sh
183+ grad_i_brk = brk_fctr * (brk_pwr + diode_voltage *
184+ - breakdown_exp * brk_pwr_1 )
185+ grad2i_brk = (brk_fctr * - breakdown_exp # noqa: W503
186+ * (2 * brk_pwr_1 + diode_voltage # noqa: W503
187+ * (- breakdown_exp - 1 ) * brk_pwr_2 )) # noqa: W503
193188 grad_i = - g_diode - g_sh - grad_i_recomb - grad_i_brk # di/dvd
194189 grad_v = 1.0 - grad_i * resistance_series # dv/dvd
195190 # dp/dv = d(iv)/dv = v * di/dv + i
@@ -248,12 +243,19 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
248243 breakdown_exp : float, default 3.28
249244 avalanche breakdown exponent :math:`m` [unitless]
250245 method : str, default 'newton'
251- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
252- if ``breakdown_factor`` is not 0.
246+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
247+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
248+
249+ .. note::
250+ ``'chandrupatla'`` requires scipy 1.15 or greater.
251+
253252 method_kwargs : dict, optional
254- Keyword arguments passed to root finder method. See
255- :py:func:`scipy:scipy.optimize.brentq` and
256- :py:func:`scipy:scipy.optimize.newton` parameters.
253+ Keyword arguments passed to the root finder. For options, see:
254+
255+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
256+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
257+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
258+
257259 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
258260 returned. See examples section.
259261
@@ -292,7 +294,7 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
292294 .. [1] "Computer simulation of the effects of electrical mismatches in
293295 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
294296 :doi:`10.1016/0379-6787(88)90059-2`
295- """
297+ """ # noqa: E501
296298 # collect args
297299 args = (photocurrent , saturation_current ,
298300 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -334,6 +336,30 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
334336 vd = newton (func = lambda x , * a : fv (x , voltage , * a ), x0 = x0 ,
335337 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[4 ],
336338 args = args , ** method_kwargs )
339+ elif method == 'chandrupatla' :
340+ try :
341+ from scipy .optimize .elementwise import find_root
342+ except ModuleNotFoundError as e :
343+ # TODO remove this when our minimum scipy version is >=1.15
344+ msg = (
345+ "method='chandrupatla' requires scipy v1.15 or greater "
346+ "(available for Python 3.10+). "
347+ "Select another method, or update your version of scipy."
348+ )
349+ raise ImportError (msg ) from e
350+
351+ voc_est = estimate_voc (photocurrent , saturation_current , nNsVth )
352+ shape = _shape_of_max_size (voltage , voc_est )
353+ vlo = np .zeros (shape )
354+ vhi = np .full (shape , voc_est )
355+ bounds = (vlo , vhi )
356+ kwargs_trimmed = method_kwargs .copy ()
357+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
358+
359+ result = find_root (fv , bounds , args = (voltage , * args ), ** kwargs_trimmed )
360+ vd = result .x
361+ if method_kwargs .get ('full_output' ):
362+ vd = (vd , result ) # mimic the other methods
337363 else :
338364 raise NotImplementedError ("Method '%s' isn't implemented" % method )
339365
@@ -389,12 +415,19 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
389415 breakdown_exp : float, default 3.28
390416 avalanche breakdown exponent :math:`m` [unitless]
391417 method : str, default 'newton'
392- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
393- if ``breakdown_factor`` is not 0.
418+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
419+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
420+
421+ .. note::
422+ ``'chandrupatla'`` requires scipy 1.15 or greater.
423+
394424 method_kwargs : dict, optional
395- Keyword arguments passed to root finder method. See
396- :py:func:`scipy:scipy.optimize.brentq` and
397- :py:func:`scipy:scipy.optimize.newton` parameters.
425+ Keyword arguments passed to the root finder. For options, see:
426+
427+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
428+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
429+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
430+
398431 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
399432 returned. See examples section.
400433
@@ -433,7 +466,7 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
433466 .. [1] "Computer simulation of the effects of electrical mismatches in
434467 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
435468 :doi:`10.1016/0379-6787(88)90059-2`
436- """
469+ """ # noqa: E501
437470 # collect args
438471 args = (photocurrent , saturation_current ,
439472 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -475,6 +508,29 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
475508 vd = newton (func = lambda x , * a : fi (x , current , * a ), x0 = x0 ,
476509 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[3 ],
477510 args = args , ** method_kwargs )
511+ elif method == 'chandrupatla' :
512+ try :
513+ from scipy .optimize .elementwise import find_root
514+ except ModuleNotFoundError as e :
515+ # TODO remove this when our minimum scipy version is >=1.15
516+ msg = (
517+ "method='chandrupatla' requires scipy v1.15 or greater "
518+ "(available for Python 3.10+). "
519+ "Select another method, or update your version of scipy."
520+ )
521+ raise ImportError (msg ) from e
522+
523+ shape = _shape_of_max_size (current , voc_est )
524+ vlo = np .zeros (shape )
525+ vhi = np .full (shape , voc_est )
526+ bounds = (vlo , vhi )
527+ kwargs_trimmed = method_kwargs .copy ()
528+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
529+
530+ result = find_root (fi , bounds , args = (current , * args ), ** kwargs_trimmed )
531+ vd = result .x
532+ if method_kwargs .get ('full_output' ):
533+ vd = (vd , result ) # mimic the other methods
478534 else :
479535 raise NotImplementedError ("Method '%s' isn't implemented" % method )
480536
@@ -527,12 +583,19 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
527583 breakdown_exp : numeric, default 3.28
528584 avalanche breakdown exponent :math:`m` [unitless]
529585 method : str, default 'newton'
530- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
531- if ``breakdown_factor`` is not 0.
586+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
587+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
588+
589+ .. note::
590+ ``'chandrupatla'`` requires scipy 1.15 or greater.
591+
532592 method_kwargs : dict, optional
533- Keyword arguments passed to root finder method. See
534- :py:func:`scipy:scipy.optimize.brentq` and
535- :py:func:`scipy:scipy.optimize.newton` parameters.
593+ Keyword arguments passed to the root finder. For options, see:
594+
595+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
596+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
597+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
598+
536599 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
537600 returned. See examples section.
538601
@@ -572,7 +635,7 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
572635 .. [1] "Computer simulation of the effects of electrical mismatches in
573636 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
574637 :doi:`10.1016/0379-6787(88)90059-2`
575- """
638+ """ # noqa: E501
576639 # collect args
577640 args = (photocurrent , saturation_current ,
578641 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -612,6 +675,31 @@ def fmpp(x, *a):
612675 vd = newton (func = fmpp , x0 = x0 ,
613676 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[7 ],
614677 args = args , ** method_kwargs )
678+ elif method == 'chandrupatla' :
679+ try :
680+ from scipy .optimize .elementwise import find_root
681+ except ModuleNotFoundError as e :
682+ # TODO remove this when our minimum scipy version is >=1.15
683+ msg = (
684+ "method='chandrupatla' requires scipy v1.15 or greater "
685+ "(available for Python 3.10+). "
686+ "Select another method, or update your version of scipy."
687+ )
688+ raise ImportError (msg ) from e
689+
690+ vlo = np .zeros_like (photocurrent )
691+ vhi = np .full_like (photocurrent , voc_est )
692+ kwargs_trimmed = method_kwargs .copy ()
693+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
694+
695+ result = find_root (fmpp ,
696+ (vlo , vhi ),
697+ args = args ,
698+ ** kwargs_trimmed )
699+ vd = result .x
700+ if method_kwargs .get ('full_output' ):
701+ vd = (vd , result ) # mimic the other methods
702+
615703 else :
616704 raise NotImplementedError ("Method '%s' isn't implemented" % method )
617705
0 commit comments