Skip to content

Add an SDE solver using the "LogWright" function  #1856

Open
@kandersolar

Description

@kandersolar

Is your feature request related to a problem? Please describe.
The lambertw method has to take some pains to appropriately handle numerical overflow in an intermediate calculation.

Describe the solution you'd like
An implementation of [1,2], which reformulates the SDE so that it uses a different form of the Lambert W function and does not require calculating such large intermediate values.

Additionally, according to [3] and also my own brief testing, it is also computationally faster than the lambertw method.

Additional context
[1] "On Calculating the Current-Voltage Characteristic of Multi-Diode Models for Organic Solar Cells": https://arxiv.org/abs/1601.02679
[2] "A Robust Approximation to a Lambert-Type Function": https://arxiv.org/abs/1504.01964
[3] "Solar Cells, Lambert W and the LogWright Functions": https://arxiv.org/abs/2307.08099

(I notice also that @markcampanelli is mentioned in the acknowledgements of [2])

Here is a rough implementation of the method, which I observe to be 2x faster than _lambertw_v_from_i while staying within $\pm$ 1e-10 of its output.

def _g(x, n=2):
    y = np.where(x <= -np.e, x, 1)
    y = np.where((-np.e < x) & (x < np.e), -np.e + (1 + np.e) * (x + np.e) / (2 * np.e), y)
    np.log(x, out=y, where=x >= np.e)
    
    for _ in range(n):
        ey = np.exp(y)
        ey_plus_one = 1 + ey
        y_ey_x = y + ey - x
        y = y - 2 * (y_ey_x) * (ey_plus_one) / ((2 * (ey_plus_one)**2 - (y_ey_x)*ey))
    
    return y


def _logwright_v_from_i(current, photocurrent, saturation_current,
                        resistance_series, resistance_shunt, nNsVth):
    output_is_scalar = all(map(np.isscalar,
                               (current, photocurrent, saturation_current,
                                resistance_series, resistance_shunt, nNsVth)))

    I, IL, I0, Rs, Rsh, a = \
        np.broadcast_arrays(current, photocurrent, saturation_current,
                            resistance_series, resistance_shunt, nNsVth)

    x = np.log(I0 * Rsh / a) + Rsh * (-I + IL + I0) / a
    V = -I * Rs - a * np.log(I0 * Rsh / a) + a * _g(x)

    if output_is_scalar:
        return V.item()
    else:
        return V

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions