<commit_before># -*- coding: utf-8 -*-
"""
Low-level functions for solving the single diode equation.
"""

from functools import partial
import numpy as np
from pvlib.tools import _golden_sect_DataFrame

# Try to import brentq from scipy to use when specified in bishop88_i_from_v,
# bishop88_v_from_i, and bishop88_mpp methods below. If not imported, raises
# ImportError when 'brentq' method is specified for those methods.
try:
    from scipy.optimize import brentq
except ImportError:
    def brentq(): raise NotImplementedError(
        "brentq can't be imported because scipy isn't installed")

# FIXME: change this to newton when scipy-1.2 is released
try:
    from scipy.optimize import _array_newton
except ImportError:
    from pvlib.tools import _array_newton
# rename newton and set keyword arguments
newton = partial(_array_newton, tol=1e-6, maxiter=100, fprime2=None)

# intrinsic voltage per cell junction for a:Si, CdTe, Mertens et al.
VOLTAGE_BUILTIN = 0.9  # [V]


def estimate_voc(photocurrent, saturation_current, nNsVth):
    """
    Rough estimate of open circuit voltage useful for bounding searches for
    ``i`` of ``v`` when using :func:`~pvlib.pvsystem.singlediode`.

    Parameters
    ----------
    photocurrent : numeric
        photo-generated current [A]
    saturation_current : numeric
        diode reverse saturation current [A]
    nNsVth : numeric
        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
        and number of series cells ``Ns``

    Returns
    -------
    numeric
        rough estimate of open circuit voltage [V]

    Notes
    -----
    Calculating the open circuit voltage, :math:`V_{oc}`, of an ideal device
    with infinite shunt resistance, :math:`R_{sh} \\to \\infty`, and zero
    series resistance, :math:`R_s = 0`, yields the following equation [1]. As
    an estimate of :math:`V_{oc}` it is useful as an upper bound for the
    bisection method.

    .. math::

        V_{oc, est}=n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)

    [1] http://www.pveducation.org/pvcdrom/open-circuit-voltage
    """

    return nNsVth * np.log(np.asarray(photocurrent) / saturation_current + 1.0)


def bishop88(diode_voltage, photocurrent, saturation_current,
             resistance_series, resistance_shunt, nNsVth, d2mutau=0,
             NsVbi=np.Inf, gradients=False):
    """
    Explicit calculation of points on the IV curve described by the single
    diode equation [1]_.

    .. warning::
       * Do not use ``d2mutau`` with CEC coefficients.
       * Usage of ``d2mutau`` with PVSyst coefficients is required for cadmium-
         telluride (CdTe) and amorphous-silicon (a:Si) PV modules only.

    Parameters
    ----------
    diode_voltage : numeric
        diode voltages [V]
    photocurrent : numeric
        photo-generated current [A]
    saturation_current : numeric
        diode reverse saturation current [A]
    resistance_series : numeric
        series resistance [ohms]
    resistance_shunt: numeric
        shunt resistance [ohms]
    nNsVth : numeric
        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
        and number of series cells ``Ns``
    d2mutau : numeric
        PVSyst thin-film recombination parameter that is the ratio of thickness
        of the intrinsic layer squared :math:`d^2` and the diffusion length of
        charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V]
    NsVbi : numeric
        PVSyst thin-film recombination parameter that is the product of the PV
        module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of
        the intrinsic layer, in volts [V], defaults to ``np.inf``
    gradients : bool
        False returns only I, V, and P. True also returns gradients

    Returns
    -------
    tuple
        currents [A], voltages [V], power [W], and optionally
        :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
    ``NsVbi`` are only applied to cadmium-telluride (CdTe) and amorphous-
    silicon (a:Si) PV modules, [2]_, [3]_. The builtin voltage :math:`V_{bi}`
    should account for all junctions. For example: tandem and triple junction
    cells would have builtin voltages of 1.8[V] and 2.7[V] respectively, based
    on the default of 0.9[V] for a single junction. The parameter ``NsVbi``
    should only account for the number of series cells in a single parallel
    sub-string if the module has cells in parallel greater than 1.

    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", <NAME> and <NAME>, 25th EUPVSEC,
       2010
       :doi:`10.4229/25thEUPVSEC2010-4BV.1.114`
    """
    # calculate recombination loss current where d2mutau > 0
    is_recomb = d2mutau > 0  # True where there is thin-film recombination loss
    v_recomb = np.where(is_recomb, NsVbi - diode_voltage, np.inf)
    i_recomb = np.where(is_recomb, photocurrent * d2mutau / v_recomb, 0)
    # 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 - i_recomb)
    v = diode_voltage - i * resistance_series
    retval = (i, v, i*v)
    if gradients:
        # calculate recombination loss current gradients where d2mutau > 0
        grad_i_recomb = np.where(is_recomb, i_recomb / v_recomb, 0)
        grad_2i_recomb = np.where(is_recomb, 2 * grad_i_recomb / v_recomb, 0)
        g_diode = saturation_current * np.exp(v_star) / nNsVth  # conductance
        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 - 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)
            + grad_i
        )  # d2p/dv/dvd
        retval += (grad_i, grad_v, grad, grad_p, grad2p)
    return retval


def bishop88_i_from_v(voltage, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      method='newton'):
    """
    Find current given any voltage.

    Parameters
    ----------
    voltage : numeric
        voltage (V) in volts [V]
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    current : numeric
        current (I) at the specified voltage (V) in amperes [A]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)

    def fv(x, v, *a):
        # calculate voltage residual given diode voltage "x"
        return bishop88(x, *a)[1] - v

    if method.lower() == 'brentq':
        if brentq is NotImplemented:
            raise ImportError('This function requires scipy')
        # first bound the search using voc
        voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

        # brentq only works with scalar inputs, so we need a set up function
        # and np.vectorize to repeatedly call the optimizer with the right
        # arguments for possible array input
        def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma):
            return brentq(fv, 0.0, voc, args=(v, iph, isat, rs, rsh, gamma))

        vd_from_brent_vectorized = np.vectorize(vd_from_brent)
        vd = vd_from_brent_vectorized(voc_est, voltage, *args)
    elif method.lower() == 'newton':
        # make sure all args are numpy arrays if max size > 1
        # if voltage is an array, then make a copy to use for initial guess, v0
        args, v0 = _prepare_newton_inputs((voltage,), args, voltage)
        vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=v0,
                    fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4],
                    args=args)
    else:
        raise NotImplementedError("Method '%s' isn't implemented" % method)
    return bishop88(vd, *args)[0]


def bishop88_v_from_i(current, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      method='newton'):
    """
    Find voltage given any current.

    Parameters
    ----------
    current : numeric
        current (I) in amperes [A]
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    voltage : numeric
        voltage (V) at the specified current (I) in volts [V]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)
    # first bound the search using voc
    voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

    def fi(x, i, *a):
        # calculate current residual given diode voltage "x"
        return bishop88(x, *a)[0] - i

    if method.lower() == 'brentq':
        if brentq is NotImplemented:
            raise ImportError('This function requires scipy')

        # brentq only works with scalar inputs, so we need a set up function
        # and np.vectorize to repeatedly call the optimizer with the right
        # arguments for possible array input
        def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma):
            return brentq(fi, 0.0, voc, args=(i, iph, isat, rs, rsh, gamma))

        vd_from_brent_vectorized = np.vectorize(vd_from_brent)
        vd = vd_from_brent_vectorized(voc_est, current, *args)
    elif method.lower() == 'newton':
        # make sure all args are numpy arrays if max size > 1
        # if voc_est is an array, then make a copy to use for initial guess, v0
        args, v0 = _prepare_newton_inputs((current,), args, voc_est)
        vd = newton(func=lambda x, *a: fi(x, current, *a), x0=v0,
                    fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3],
                    args=args)
    else:
        raise NotImplementedError("Method '%s' isn't implemented" % method)
    return bishop88(vd, *args)[1]


def bishop88_mpp(photocurrent, saturation_current, resistance_series,
                 resistance_shunt, nNsVth, method='newton'):
    """
    Find max power point.

    Parameters
    ----------
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    OrderedDict or pandas.DataFrame
        max power current ``i_mp`` [A], max power voltage ``v_mp`` [V], and
        max power ``p_mp`` [W]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)
    # first bound the search using voc
    voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

    def fmpp(x, *a):
        return bishop88(x, *a, gradients=True)[6]

    if method.lower() == 'brentq':
        if brentq is NotImplemented:
            raise ImportError('This function requires scipy')
        # break out arguments for numpy.vectorize to handle broadcasting<commit_msg>do not check brentq is NotImplemented (#593)<commit_after># -*- coding: utf-8 -*-
"""
Low-level functions for solving the single diode equation.
"""

from functools import partial
import numpy as np
from pvlib.tools import _golden_sect_DataFrame

# Try to import brentq from scipy to use when specified in bishop88_i_from_v,
# bishop88_v_from_i, and bishop88_mpp methods below. If not imported, raises
# ImportError when 'brentq' method is specified for those methods.
try:
    from scipy.optimize import brentq
except ImportError:
    def brentq(): raise NotImplementedError(
        "brentq can't be imported because scipy isn't installed")

# FIXME: change this to newton when scipy-1.2 is released
try:
    from scipy.optimize import _array_newton
except ImportError:
    from pvlib.tools import _array_newton
# rename newton and set keyword arguments
newton = partial(_array_newton, tol=1e-6, maxiter=100, fprime2=None)

# intrinsic voltage per cell junction for a:Si, CdTe, Mertens et al.
VOLTAGE_BUILTIN = 0.9  # [V]


def estimate_voc(photocurrent, saturation_current, nNsVth):
    """
    Rough estimate of open circuit voltage useful for bounding searches for
    ``i`` of ``v`` when using :func:`~pvlib.pvsystem.singlediode`.

    Parameters
    ----------
    photocurrent : numeric
        photo-generated current [A]
    saturation_current : numeric
        diode reverse saturation current [A]
    nNsVth : numeric
        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
        and number of series cells ``Ns``

    Returns
    -------
    numeric
        rough estimate of open circuit voltage [V]

    Notes
    -----
    Calculating the open circuit voltage, :math:`V_{oc}`, of an ideal device
    with infinite shunt resistance, :math:`R_{sh} \\to \\infty`, and zero
    series resistance, :math:`R_s = 0`, yields the following equation [1]. As
    an estimate of :math:`V_{oc}` it is useful as an upper bound for the
    bisection method.

    .. math::

        V_{oc, est}=n Ns V_{th} \\log \\left( \\frac{I_L}{I_0} + 1 \\right)

    [1] http://www.pveducation.org/pvcdrom/open-circuit-voltage
    """

    return nNsVth * np.log(np.asarray(photocurrent) / saturation_current + 1.0)


def bishop88(diode_voltage, photocurrent, saturation_current,
             resistance_series, resistance_shunt, nNsVth, d2mutau=0,
             NsVbi=np.Inf, gradients=False):
    """
    Explicit calculation of points on the IV curve described by the single
    diode equation [1]_.

    .. warning::
       * Do not use ``d2mutau`` with CEC coefficients.
       * Usage of ``d2mutau`` with PVSyst coefficients is required for cadmium-
         telluride (CdTe) and amorphous-silicon (a:Si) PV modules only.

    Parameters
    ----------
    diode_voltage : numeric
        diode voltages [V]
    photocurrent : numeric
        photo-generated current [A]
    saturation_current : numeric
        diode reverse saturation current [A]
    resistance_series : numeric
        series resistance [ohms]
    resistance_shunt: numeric
        shunt resistance [ohms]
    nNsVth : numeric
        product of thermal voltage ``Vth`` [V], diode ideality factor ``n``,
        and number of series cells ``Ns``
    d2mutau : numeric
        PVSyst thin-film recombination parameter that is the ratio of thickness
        of the intrinsic layer squared :math:`d^2` and the diffusion length of
        charge carriers :math:`\\mu \\tau`, in volts [V], defaults to 0[V]
    NsVbi : numeric
        PVSyst thin-film recombination parameter that is the product of the PV
        module number of series cells ``Ns`` and the builtin voltage ``Vbi`` of
        the intrinsic layer, in volts [V], defaults to ``np.inf``
    gradients : bool
        False returns only I, V, and P. True also returns gradients

    Returns
    -------
    tuple
        currents [A], voltages [V], power [W], and optionally
        :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
    ``NsVbi`` are only applied to cadmium-telluride (CdTe) and amorphous-
    silicon (a:Si) PV modules, [2]_, [3]_. The builtin voltage :math:`V_{bi}`
    should account for all junctions. For example: tandem and triple junction
    cells would have builtin voltages of 1.8[V] and 2.7[V] respectively, based
    on the default of 0.9[V] for a single junction. The parameter ``NsVbi``
    should only account for the number of series cells in a single parallel
    sub-string if the module has cells in parallel greater than 1.

    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." <NAME>, 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", <NAME> and <NAME>, 25th EUPVSEC,
       2010
       :doi:`10.4229/25thEUPVSEC2010-4BV.1.114`
    """
    # calculate recombination loss current where d2mutau > 0
    is_recomb = d2mutau > 0  # True where there is thin-film recombination loss
    v_recomb = np.where(is_recomb, NsVbi - diode_voltage, np.inf)
    i_recomb = np.where(is_recomb, photocurrent * d2mutau / v_recomb, 0)
    # 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 - i_recomb)
    v = diode_voltage - i * resistance_series
    retval = (i, v, i*v)
    if gradients:
        # calculate recombination loss current gradients where d2mutau > 0
        grad_i_recomb = np.where(is_recomb, i_recomb / v_recomb, 0)
        grad_2i_recomb = np.where(is_recomb, 2 * grad_i_recomb / v_recomb, 0)
        g_diode = saturation_current * np.exp(v_star) / nNsVth  # conductance
        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 - 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)
            + grad_i
        )  # d2p/dv/dvd
        retval += (grad_i, grad_v, grad, grad_p, grad2p)
    return retval


def bishop88_i_from_v(voltage, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      method='newton'):
    """
    Find current given any voltage.

    Parameters
    ----------
    voltage : numeric
        voltage (V) in volts [V]
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    current : numeric
        current (I) at the specified voltage (V) in amperes [A]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)

    def fv(x, v, *a):
        # calculate voltage residual given diode voltage "x"
        return bishop88(x, *a)[1] - v

    if method.lower() == 'brentq':
        # first bound the search using voc
        voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

        # brentq only works with scalar inputs, so we need a set up function
        # and np.vectorize to repeatedly call the optimizer with the right
        # arguments for possible array input
        def vd_from_brent(voc, v, iph, isat, rs, rsh, gamma):
            return brentq(fv, 0.0, voc, args=(v, iph, isat, rs, rsh, gamma))

        vd_from_brent_vectorized = np.vectorize(vd_from_brent)
        vd = vd_from_brent_vectorized(voc_est, voltage, *args)
    elif method.lower() == 'newton':
        # make sure all args are numpy arrays if max size > 1
        # if voltage is an array, then make a copy to use for initial guess, v0
        args, v0 = _prepare_newton_inputs((voltage,), args, voltage)
        vd = newton(func=lambda x, *a: fv(x, voltage, *a), x0=v0,
                    fprime=lambda x, *a: bishop88(x, *a, gradients=True)[4],
                    args=args)
    else:
        raise NotImplementedError("Method '%s' isn't implemented" % method)
    return bishop88(vd, *args)[0]


def bishop88_v_from_i(current, photocurrent, saturation_current,
                      resistance_series, resistance_shunt, nNsVth,
                      method='newton'):
    """
    Find voltage given any current.

    Parameters
    ----------
    current : numeric
        current (I) in amperes [A]
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    voltage : numeric
        voltage (V) at the specified current (I) in volts [V]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)
    # first bound the search using voc
    voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

    def fi(x, i, *a):
        # calculate current residual given diode voltage "x"
        return bishop88(x, *a)[0] - i

    if method.lower() == 'brentq':
        # brentq only works with scalar inputs, so we need a set up function
        # and np.vectorize to repeatedly call the optimizer with the right
        # arguments for possible array input
        def vd_from_brent(voc, i, iph, isat, rs, rsh, gamma):
            return brentq(fi, 0.0, voc, args=(i, iph, isat, rs, rsh, gamma))

        vd_from_brent_vectorized = np.vectorize(vd_from_brent)
        vd = vd_from_brent_vectorized(voc_est, current, *args)
    elif method.lower() == 'newton':
        # make sure all args are numpy arrays if max size > 1
        # if voc_est is an array, then make a copy to use for initial guess, v0
        args, v0 = _prepare_newton_inputs((current,), args, voc_est)
        vd = newton(func=lambda x, *a: fi(x, current, *a), x0=v0,
                    fprime=lambda x, *a: bishop88(x, *a, gradients=True)[3],
                    args=args)
    else:
        raise NotImplementedError("Method '%s' isn't implemented" % method)
    return bishop88(vd, *args)[1]


def bishop88_mpp(photocurrent, saturation_current, resistance_series,
                 resistance_shunt, nNsVth, method='newton'):
    """
    Find max power point.

    Parameters
    ----------
    photocurrent : numeric
        photogenerated current (Iph or IL) in amperes [A]
    saturation_current : numeric
        diode dark or saturation current (Io or Isat) in amperes [A]
    resistance_series : numeric
        series resistance (Rs) in ohms
    resistance_shunt : numeric
        shunt resistance (Rsh) in ohms
    nNsVth : numeric
        product of diode ideality factor (n), number of series cells (Ns), and
        thermal voltage (Vth = k_b * T / q_e) in volts [V]
    method : str
        one of two optional search methods: either ``'brentq'``, a reliable and
        bounded method or ``'newton'`` which is the default.

    Returns
    -------
    OrderedDict or pandas.DataFrame
        max power current ``i_mp`` [A], max power voltage ``v_mp`` [V], and
        max power ``p_mp`` [W]
    """
    # collect args
    args = (photocurrent, saturation_current, resistance_series,
            resistance_shunt, nNsVth)
    # first bound the search using voc
    voc_est = estimate_voc(photocurrent, saturation_current, nNsVth)

    def fmpp(x, *a):
        return bishop88(x, *a, gradients=True)[6]

    if method.lower() == 'brentq':
        # break out arguments for numpy.vectorize to handle broadcasting
{'fraction_non_alphanumeric': 0.09910784277128672, 'fraction_numerical': 0.014147597894658255, 'mean_word_length': 2.601290496034413, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1162269', 'n_tokens_mistral': 8844, 'n_tokens_neox': 8200, 'n_words': 2829}