Spaces:
Running
Running
| import numpy as np | |
| from scipy.integrate import solve_ivp | |
| def compute_ftle_metrics(rhs, x0, y0, te, t_eval, x, y): | |
| """ | |
| Computes FTLE (Finite-Time Lyapunov Exponent) and related metrics. | |
| Args: | |
| rhs: Right-hand side function of the ODE system | |
| x0, y0: Initial conditions | |
| te: End time | |
| t_eval: Time points array | |
| x, y: Solution arrays from the main trajectory | |
| Returns: | |
| tuple: (ftle, final_d, ftle_r2) or (np.nan, np.nan, np.nan) if computation fails | |
| """ | |
| eps = 1e-6 * (1.0 + abs(x0) + abs(y0)) | |
| xp0, yp0 = x0 + eps, y0 + 0.5 * eps | |
| try: | |
| sol_p = solve_ivp(rhs, (0, te), (xp0, yp0), method='DOP853', t_eval=t_eval) | |
| if sol_p.success: | |
| xp, yp = sol_p.y | |
| dist = np.sqrt((x - xp) ** 2 + (y - yp) ** 2) | |
| dist = np.where(dist <= 0, 1e-12, dist) | |
| final_d = float(dist[-1]) | |
| s_idx, e_idx = int(0.25 * len(t_eval)), int(0.75 * len(t_eval)) | |
| if e_idx > s_idx + 1: | |
| d_slice = dist[s_idx:e_idx] | |
| t_slice = t_eval[s_idx:e_idx] | |
| d_slice = np.clip(d_slice, 1e-12, None) | |
| ln_d = np.log(d_slice) | |
| # linear fit and r2 diagnostics | |
| slope, intercept = np.polyfit(t_slice, ln_d, 1) | |
| ftle = float(slope) | |
| resid = ln_d - (slope * t_slice + intercept) | |
| ss_res = np.sum(resid ** 2) | |
| ss_tot = np.sum((ln_d - np.mean(ln_d)) ** 2) | |
| ftle_r2 = 1 - ss_res / ss_tot if ss_tot > 0 else np.nan | |
| return ftle, final_d, ftle_r2 | |
| # Return NaN values if computation was unsuccessful | |
| return np.nan, np.nan, np.nan | |
| except Exception: | |
| # Return NaN values in case of exception | |
| return np.nan, np.nan, np.nan |