@@ -109,13 +109,13 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
109109 (a-Si) modules that is the product of the PV module number of series
110110 cells :math:`N_{s}` and the builtin voltage :math:`V_{bi}` of the
111111 intrinsic layer. [V].
112- breakdown_factor : float , default 0
112+ breakdown_factor : numeric , default 0
113113 fraction of ohmic current involved in avalanche breakdown :math:`a`.
114114 Default of 0 excludes the reverse bias term from the model. [unitless]
115- breakdown_voltage : float , default -5.5
115+ breakdown_voltage : numeric , default -5.5
116116 reverse breakdown voltage of the photovoltaic junction :math:`V_{br}`
117117 [V]
118- breakdown_exp : float , default 3.28
118+ breakdown_exp : numeric , default 3.28
119119 avalanche breakdown exponent :math:`m` [unitless]
120120 gradients : bool
121121 False returns only I, V, and P. True also returns gradients
@@ -162,12 +162,11 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
162162 # calculate temporary values to simplify calculations
163163 v_star = diode_voltage / nNsVth # non-dimensional diode voltage
164164 g_sh = 1.0 / resistance_shunt # conductance
165- if breakdown_factor > 0 : # reverse bias is considered
166- brk_term = 1 - diode_voltage / breakdown_voltage
167- brk_pwr = np .power (brk_term , - breakdown_exp )
168- i_breakdown = breakdown_factor * diode_voltage * g_sh * brk_pwr
169- else :
170- i_breakdown = 0.
165+
166+ brk_term = 1 - diode_voltage / breakdown_voltage
167+ brk_pwr = np .power (brk_term , - breakdown_exp )
168+ i_breakdown = breakdown_factor * diode_voltage * g_sh * brk_pwr
169+
171170 i = (photocurrent - saturation_current * np .expm1 (v_star ) # noqa: W503
172171 - diode_voltage * g_sh - i_recomb - i_breakdown ) # noqa: W503
173172 v = diode_voltage - i * resistance_series
@@ -177,18 +176,14 @@ def bishop88(diode_voltage, photocurrent, saturation_current,
177176 grad_i_recomb = np .where (is_recomb , i_recomb / v_recomb , 0 )
178177 grad_2i_recomb = np .where (is_recomb , 2 * grad_i_recomb / v_recomb , 0 )
179178 g_diode = saturation_current * np .exp (v_star ) / nNsVth # conductance
180- if breakdown_factor > 0 : # reverse bias is considered
181- brk_pwr_1 = np .power (brk_term , - breakdown_exp - 1 )
182- brk_pwr_2 = np .power (brk_term , - breakdown_exp - 2 )
183- brk_fctr = breakdown_factor * g_sh
184- grad_i_brk = brk_fctr * (brk_pwr + diode_voltage *
185- - breakdown_exp * brk_pwr_1 )
186- grad2i_brk = (brk_fctr * - breakdown_exp # noqa: W503
187- * (2 * brk_pwr_1 + diode_voltage # noqa: W503
188- * (- breakdown_exp - 1 ) * brk_pwr_2 )) # noqa: W503
189- else :
190- grad_i_brk = 0.
191- grad2i_brk = 0.
179+ brk_pwr_1 = np .power (brk_term , - breakdown_exp - 1 )
180+ brk_pwr_2 = np .power (brk_term , - breakdown_exp - 2 )
181+ brk_fctr = breakdown_factor * g_sh
182+ grad_i_brk = brk_fctr * (brk_pwr + diode_voltage *
183+ - breakdown_exp * brk_pwr_1 )
184+ grad2i_brk = (brk_fctr * - breakdown_exp # noqa: W503
185+ * (2 * brk_pwr_1 + diode_voltage # noqa: W503
186+ * (- breakdown_exp - 1 ) * brk_pwr_2 )) # noqa: W503
192187 grad_i = - g_diode - g_sh - grad_i_recomb - grad_i_brk # di/dvd
193188 grad_v = 1.0 - grad_i * resistance_series # dv/dvd
194189 # dp/dv = d(iv)/dv = v * di/dv + i
@@ -247,12 +242,19 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
247242 breakdown_exp : float, default 3.28
248243 avalanche breakdown exponent :math:`m` [unitless]
249244 method : str, default 'newton'
250- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
251- if ``breakdown_factor`` is not 0.
245+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
246+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
247+
248+ .. note::
249+ ``'chandrupatla'`` requires scipy 1.15 or greater.
250+
252251 method_kwargs : dict, optional
253- Keyword arguments passed to root finder method. See
254- :py:func:`scipy:scipy.optimize.brentq` and
255- :py:func:`scipy:scipy.optimize.newton` parameters.
252+ Keyword arguments passed to the root finder. For options, see:
253+
254+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
255+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
256+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
257+
256258 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
257259 returned. See examples section.
258260
@@ -291,7 +293,7 @@ def bishop88_i_from_v(voltage, photocurrent, saturation_current,
291293 .. [1] "Computer simulation of the effects of electrical mismatches in
292294 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
293295 :doi:`10.1016/0379-6787(88)90059-2`
294- """
296+ """ # noqa: E501
295297 # collect args
296298 args = (photocurrent , saturation_current ,
297299 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -333,6 +335,30 @@ def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
333335 vd = newton (func = lambda x , * a : fv (x , voltage , * a ), x0 = x0 ,
334336 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[4 ],
335337 args = args , ** method_kwargs )
338+ elif method == 'chandrupatla' :
339+ try :
340+ from scipy .optimize .elementwise import find_root
341+ except ModuleNotFoundError as e :
342+ # TODO remove this when our minimum scipy version is >=1.15
343+ msg = (
344+ "method='chandrupatla' requires scipy v1.15 or greater "
345+ "(available for Python 3.10+). "
346+ "Select another method, or update your version of scipy."
347+ )
348+ raise ImportError (msg ) from e
349+
350+ voc_est = estimate_voc (photocurrent , saturation_current , nNsVth )
351+ shape = _shape_of_max_size (voltage , voc_est )
352+ vlo = np .zeros (shape )
353+ vhi = np .full (shape , voc_est )
354+ bounds = (vlo , vhi )
355+ kwargs_trimmed = method_kwargs .copy ()
356+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
357+
358+ result = find_root (fv , bounds , args = (voltage , * args ), ** kwargs_trimmed )
359+ vd = result .x
360+ if method_kwargs .get ('full_output' ):
361+ vd = (vd , result ) # mimic the other methods
336362 else :
337363 raise NotImplementedError ("Method '%s' isn't implemented" % method )
338364
@@ -388,12 +414,19 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
388414 breakdown_exp : float, default 3.28
389415 avalanche breakdown exponent :math:`m` [unitless]
390416 method : str, default 'newton'
391- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
392- if ``breakdown_factor`` is not 0.
417+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
418+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
419+
420+ .. note::
421+ ``'chandrupatla'`` requires scipy 1.15 or greater.
422+
393423 method_kwargs : dict, optional
394- Keyword arguments passed to root finder method. See
395- :py:func:`scipy:scipy.optimize.brentq` and
396- :py:func:`scipy:scipy.optimize.newton` parameters.
424+ Keyword arguments passed to the root finder. For options, see:
425+
426+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
427+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
428+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
429+
397430 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
398431 returned. See examples section.
399432
@@ -432,7 +465,7 @@ def bishop88_v_from_i(current, photocurrent, saturation_current,
432465 .. [1] "Computer simulation of the effects of electrical mismatches in
433466 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
434467 :doi:`10.1016/0379-6787(88)90059-2`
435- """
468+ """ # noqa: E501
436469 # collect args
437470 args = (photocurrent , saturation_current ,
438471 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -474,6 +507,29 @@ def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma, d2mutau, NsVbi,
474507 vd = newton (func = lambda x , * a : fi (x , current , * a ), x0 = x0 ,
475508 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[3 ],
476509 args = args , ** method_kwargs )
510+ elif method == 'chandrupatla' :
511+ try :
512+ from scipy .optimize .elementwise import find_root
513+ except ModuleNotFoundError as e :
514+ # TODO remove this when our minimum scipy version is >=1.15
515+ msg = (
516+ "method='chandrupatla' requires scipy v1.15 or greater "
517+ "(available for Python 3.10+). "
518+ "Select another method, or update your version of scipy."
519+ )
520+ raise ImportError (msg ) from e
521+
522+ shape = _shape_of_max_size (current , voc_est )
523+ vlo = np .zeros (shape )
524+ vhi = np .full (shape , voc_est )
525+ bounds = (vlo , vhi )
526+ kwargs_trimmed = method_kwargs .copy ()
527+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
528+
529+ result = find_root (fi , bounds , args = (current , * args ), ** kwargs_trimmed )
530+ vd = result .x
531+ if method_kwargs .get ('full_output' ):
532+ vd = (vd , result ) # mimic the other methods
477533 else :
478534 raise NotImplementedError ("Method '%s' isn't implemented" % method )
479535
@@ -526,12 +582,19 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
526582 breakdown_exp : numeric, default 3.28
527583 avalanche breakdown exponent :math:`m` [unitless]
528584 method : str, default 'newton'
529- Either ``'newton'`` or ``'brentq'``. ''method'' must be ``'newton'``
530- if ``breakdown_factor`` is not 0.
585+ Either ``'newton'``, ``'brentq'``, or ``'chandrupatla'``.
586+ ''method'' must be ``'newton'`` if ``breakdown_factor`` is not 0.
587+
588+ .. note::
589+ ``'chandrupatla'`` requires scipy 1.15 or greater.
590+
531591 method_kwargs : dict, optional
532- Keyword arguments passed to root finder method. See
533- :py:func:`scipy:scipy.optimize.brentq` and
534- :py:func:`scipy:scipy.optimize.newton` parameters.
592+ Keyword arguments passed to the root finder. For options, see:
593+
594+ * ``method='brentq'``: :py:func:`scipy:scipy.optimize.brentq`
595+ * ``method='newton'``: :py:func:`scipy:scipy.optimize.newton`
596+ * ``method='chandrupatla'``: :py:func:`scipy:scipy.optimize.elementwise.find_root`
597+
535598 ``'full_output': True`` is allowed, and ``optimizer_output`` would be
536599 returned. See examples section.
537600
@@ -571,7 +634,7 @@ def bishop88_mpp(photocurrent, saturation_current, resistance_series,
571634 .. [1] "Computer simulation of the effects of electrical mismatches in
572635 photovoltaic cell interconnection circuits" JW Bishop, Solar Cell (1988)
573636 :doi:`10.1016/0379-6787(88)90059-2`
574- """
637+ """ # noqa: E501
575638 # collect args
576639 args = (photocurrent , saturation_current ,
577640 resistance_series , resistance_shunt , nNsVth , d2mutau , NsVbi ,
@@ -611,6 +674,31 @@ def fmpp(x, *a):
611674 vd = newton (func = fmpp , x0 = x0 ,
612675 fprime = lambda x , * a : bishop88 (x , * a , gradients = True )[7 ],
613676 args = args , ** method_kwargs )
677+ elif method == 'chandrupatla' :
678+ try :
679+ from scipy .optimize .elementwise import find_root
680+ except ModuleNotFoundError as e :
681+ # TODO remove this when our minimum scipy version is >=1.15
682+ msg = (
683+ "method='chandrupatla' requires scipy v1.15 or greater "
684+ "(available for Python 3.10+). "
685+ "Select another method, or update your version of scipy."
686+ )
687+ raise ImportError (msg ) from e
688+
689+ vlo = np .zeros_like (photocurrent )
690+ vhi = np .full_like (photocurrent , voc_est )
691+ kwargs_trimmed = method_kwargs .copy ()
692+ kwargs_trimmed .pop ("full_output" , None ) # not valid for find_root
693+
694+ result = find_root (fmpp ,
695+ (vlo , vhi ),
696+ args = args ,
697+ ** kwargs_trimmed )
698+ vd = result .x
699+ if method_kwargs .get ('full_output' ):
700+ vd = (vd , result ) # mimic the other methods
701+
614702 else :
615703 raise NotImplementedError ("Method '%s' isn't implemented" % method )
616704
0 commit comments