"""
Centralize tau ordering, lifetime summaries, FRET efficiency, and fit-quality metrics.
This module belongs to :mod:`pyfli.solver` and is part of PyFLI least-squares, maximum-
likelihood, CPU, GPU, binned, and global FLI fitting routines. Public API includes
functions :func:`enforce_tau_ordering`, :func:`compute_fli_stats`,
:func:`compute_average_lifetime`, and :func:`compute_fret_efficiency`.
"""
from typing import Any
import numpy as np
_FIXED_BOUND_TOL = 1e-5
[docs]
def enforce_tau_ordering(
popt: np.ndarray,
perr: Any | None = None,
pcov: np.ndarray | None = None,
bounds: tuple[np.ndarray, np.ndarray] | None = None,
) -> tuple[Any, ...]:
"""
Enforce tau ordering.
Parameters
----------
popt : np.ndarray
Optimized model parameter vector.
perr : Any | None
One-standard-deviation parameter uncertainty estimates.
pcov : np.ndarray | None
Parameter covariance matrix.
bounds : tuple[np.ndarray, np.ndarray] | None
Optional (low, high) bound vectors used for the fit. When either tau1 or tau2
was pinned by the caller (low == high), that parameter's slot is left alone.
Returns
-------
tuple[Any, ...]
Tuple containing the reordered parameter vector and any reordered uncertainty or covariance data.
"""
popt = np.asarray(popt, dtype=float)
if bounds is not None:
low, high = bounds
tau1_fixed = abs(float(high[2]) - float(low[2])) < _FIXED_BOUND_TOL
tau2_fixed = abs(float(high[3]) - float(low[3])) < _FIXED_BOUND_TOL
if tau1_fixed or tau2_fixed:
return popt, perr, pcov
if popt[1] > 0.999:
popt[1], popt[3] = 1.0, popt[2]
elif popt[1] < 0.001:
popt[1], popt[2] = 0.0, popt[3]
if popt[2] > popt[3]:
popt[2], popt[3] = popt[3], popt[2]
popt[1] = 1.0 - popt[1]
if perr is not None:
perr = np.asarray(perr, dtype=float)
perr[2], perr[3] = perr[3], perr[2]
if pcov is not None:
pcov[[2, 3], :] = pcov[[3, 2], :]
pcov[:, [2, 3]] = pcov[:, [3, 2]]
return popt, perr, pcov
[docs]
def compute_fli_stats(
final_model: np.ndarray, d_fit: np.ndarray, n_params: int
) -> tuple[Any, ...]:
"""
Compute FLI fit statistics.
Parameters
----------
final_model : np.ndarray
Model decay evaluated at the fitted parameters.
d_fit : np.ndarray
Measured decay samples over the fitted range.
n_params : int
Number of fitted model parameters.
Returns
-------
tuple[Any, ...]
Tuple containing SSR, chi-square, reduced chi-square, R-squared, and
RMSE statistics.
"""
residuals = final_model - d_fit
ssr = float(np.sum(residuals**2))
chi_sq = float(np.sum(residuals**2 / np.clip(final_model, 1.0, None)))
dof = max(len(d_fit) - n_params, 1)
red_chi_sq = chi_sq / dof
ss_tot = float(np.sum((d_fit - np.mean(d_fit)) ** 2))
r_sq = 1.0 - ssr / ss_tot if ss_tot > 0 else 0.0
rmse = float(np.sqrt(np.mean(residuals**2)))
return ssr, chi_sq, red_chi_sq, r_sq, rmse
[docs]
def compute_average_lifetime(popt: np.ndarray) -> float:
"""
Compute average lifetime.
Parameters
----------
popt : np.ndarray
Optimized model parameter vector.
Returns
-------
float
Amplitude-weighted average lifetime for bi-exponential fits or the mono-exponential lifetime.
"""
if len(popt) == 6:
return float(popt[1] * popt[2] + (1.0 - popt[1]) * popt[3])
return float(popt[1])
[docs]
def compute_fret_efficiency(popt: np.ndarray) -> float:
"""
Compute FRET efficiency.
Parameters
----------
popt : np.ndarray
Optimized model parameter vector.
Returns
-------
float
FRET efficiency estimated from the fitted short and long lifetimes.
"""
if len(popt) == 6:
tau1, tau2 = popt[2], popt[3]
if tau2 > 0:
return float(1.0 - tau1 / tau2)
return 0.0