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]

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]