"""Cell metrics, SR curves, and Rs extraction from the implicit diode model."""
from __future__ import annotations
import math
import numpy as np
from psc_sim.iv_solver import terminal_voc
from psc_sim.parameters import EXP_CLIP, CellMetrics, CellParameters
from psc_sim.simulation.backends import IVBackend, PythonIVBackend
def _current_at_v(
v: float,
p: CellParameters,
rs: float,
backend: IVBackend,
i_hint: float | None,
) -> float:
return backend.solve_point(v, p, rs=rs, i_hint=i_hint)
[docs]
def normalized_sr_at_bias(v: float, i: float, p: CellParameters, rs: float) -> tuple[float, float]:
"""Return (SR, SRinv) = (dI/dIs, (dI/dIs)^-1) at one bias point."""
vt = p.vt()
vd = v + i * rs
ev = min(vd / vt, EXP_CLIP)
exp_term = p.I0 * math.exp(ev)
denom = 1.0 + exp_term * rs / vt + rs / p.Rsh
sr = 1.0 / denom
return sr, denom
def sr_vs_bias(
p: CellParameters,
v0: float = 0.0,
v1: float = 0.9,
step: float = 0.01,
rs: float | None = None,
*,
iv_backend: IVBackend | None = None,
) -> tuple[np.ndarray, np.ndarray]:
backend = iv_backend or PythonIVBackend()
rs_use = p.Rs if rs is None else rs
V = np.arange(v0, v1 + step * 0.5, step, dtype=float)
SR = np.empty_like(V)
prev: float | None = None
for idx, v in enumerate(V):
cur = _current_at_v(float(v), p, rs_use, backend, prev)
prev = cur
sr, _ = normalized_sr_at_bias(float(v), cur, p, rs_use)
SR[idx] = sr
return V, SR
[docs]
def compute_metrics(
p: CellParameters,
pin_w_cm2: float = 0.1,
*,
iv_backend: IVBackend | None = None,
) -> CellMetrics:
"""Compute Voc/Isc/FF/PCE metrics using the same sampling as the HTML app."""
backend = iv_backend or PythonIVBackend()
rs = p.Rs
isc = _current_at_v(0.0, p, rs, backend, None)
voc = terminal_voc(p, rs, v_hi=1.5) or 0.0
pmax = 0.0
vmp = 0.0
imp = 0.0
v = 0.0
i_prev = float(isc)
while v <= voc + 1e-12:
vq = round(float(v), 6)
cur = _current_at_v(vq, p, rs, backend, i_prev)
if not math.isfinite(cur):
break
i_prev = cur
power = -vq * cur
if power > pmax:
pmax, vmp, imp = power, vq, cur
v += 0.002
ff = min(1.0, max(0.0, pmax / (voc * abs(isc)))) if voc > 0 and isc != 0 else float("nan")
pce = pmax / pin_w_cm2 * 100.0 if pin_w_cm2 > 0 else float("nan")
rs_x = extract_rs_from_sr_inv(p, iv_backend=backend)
return CellMetrics(
voc=voc,
isc=isc,
ff=ff,
pce=pce,
rs_extracted=rs_x,
pmax=pmax,
vmp=vmp,
imp=imp,
)