SIM_API_v1: functions: params_to_constitutive: description: "Computes state-dependent battery parameters with guards." inputs: x: "[z, v_p, T_b, S, w]" params: "MODEL_SPEC.parameters" outputs: V_oc: "Open-circuit voltage [V]" R0: "Internal resistance [Ohm]" Q_eff: "Effective capacity [Ah]" logic: - "z_eff = max(z, params.z_min)" - "V_oc = params.E0 - params.K * (1/z_eff - 1) + params.A * exp(-params.B * (1 - z))" - "R0 = params.R_ref * exp((params.E_a / params.R_g) * (1/T_b - 1/params.T_ref)) * (1 + params.eta_R * (1 - S))" - "Q_eff = max(params.Q_nom * S * (1 - params.alpha_Q * (params.T_ref - T_b)), params.Q_eff_floor)" power_mapping: description: "Maps user inputs and radio state to total power demand." inputs: u: "[L, C, N, Ψ, T_a]" x: "[z, v_p, T_b, S, w]" params: "MODEL_SPEC.parameters" outputs: P_tot: "Total power [W]" logic: - "P_scr = params.P_scr0 + params.k_L * L^params.gamma" - "P_cpu = params.P_cpu0 + params.k_C * C^params.eta" - "P_net = params.P_net0 + params.k_N * N / (Ψ + params.epsilon)^params.kappa + params.k_tail * w" - "P_tot = params.P_bg + P_scr + P_cpu + P_net" current_cpl: description: "Solves the quadratic CPL equation for current." inputs: V_oc: "[V]" v_p: "[V]" R0: "[Ohm]" P_tot: "[W]" outputs: Delta: "Discriminant [V^2]" I: "Current [A]" logic: - "Delta = (V_oc - v_p)^2 - 4 * R0 * P_tot" - "I = (Delta >= 0) ? (V_oc - v_p - sqrt(Delta)) / (2 * R0) : NaN" rhs: description: "Computes the derivative vector and algebraic variables." inputs: t: "Time [s]" x: "[z, v_p, T_b, S, w]" u: "[L, C, N, Ψ, T_a]" params: "MODEL_SPEC.parameters" outputs: dx_dt: "[dz/dt, dv_p/dt, dT_b/dt, dS/dt, dw/dt]" algebraics: "{Delta, I, V_term, V_oc, R0, Q_eff, P_tot}" logic: - "V_oc, R0, Q_eff = params_to_constitutive(x, params)" - "P_tot = power_mapping(u, x, params)" - "Delta, I = current_cpl(V_oc, v_p, R0, P_tot)" - "V_term = V_oc - v_p - I * R0" - "dz_dt = -I / (3600 * Q_eff)" - "dvp_dt = I/params.C1 - v_p / (params.R1 * params.C1)" - "dTb_dt = (I^2 * R0 + I * v_p - params.hA * (T_b - T_a)) / params.C_th" - "dS_dt = 0 (Single discharge assumption, or use MODEL_SPEC Option A)" - "sigma_N = min(1, N)" - "tau_N = (sigma_N >= w) ? params.tau_up : params.tau_down" - "dw_dt = (sigma_N - w) / tau_N" - "return [dz_dt, dvp_dt, dTb_dt, dS_dt, dw_dt], {Delta, I, V_term, V_oc, R0, Q_eff, P_tot}" rk4_step: logic: - "u_n = scenario.u(t_n)" - "k1, alg_n = rhs(t_n, x_n, u_n, params)" - "k2, _ = rhs(t_n + dt/2, x_n + dt*k1/2, scenario.u(t_n + dt/2), params)" - "k3, _ = rhs(t_n + dt/2, x_n + dt*k2/2, scenario.u(t_n + dt/2), params)" - "k4, _ = rhs(t_n + dt, x_n + dt*k3, scenario.u(t_n + dt), params)" - "x_next_raw = x_n + dt*(k1 + 2*k2 + 2*k3 + k4)/6" - "x_next = [clamp(x_next_raw[0],0,1), x_next_raw[1], x_next_raw[2], clamp(x_next_raw[3],0,1), clamp(x_next_raw[4],0,1)]" - "Store alg_n and x_n at t_n" OutputSchema: trajectory_columns: - t - z - v_p - T_b - S - w - V_oc - R0 - Q_eff - P_tot - Delta - I - V_term metadata: - dt - t_max - termination_reason - t_star - TTE_seconds ValidationPlan: convergence: method: "step-halving" metrics: - "max_abs_diff_z: max|z_dt - z_dt2| < 1e-4" - "rel_err_tte: |TTE_dt - TTE_dt2| / TTE_dt2 < 0.01" feasibility: guard: "If Delta < 0 at any rhs evaluation within RK4 stages, immediately trigger DELTA_ZERO event at current t_n." action: "Record termination_reason = 'DELTA_ZERO' and invoke TTE_SPEC interpolation."