/// @module std::core::ode
/// ODE Integrators
///
/// Basic Euler and RK4 integrators for scalar and vector systems.
function vec_add(a, b) {
let mut out = [];
for i in range(0, a.len()) {
out.push(a[i] + b[i]);
}
out
}
function vec_scale(a, s) {
let mut out = [];
for i in range(0, a.len()) {
out.push(a[i] * s);
}
out
}
function vec_add_scaled(a, b, s) {
vec_add(a, vec_scale(b, s))
}
/// Euler integrator for scalar ODE
pub fn euler(f, y0, t_start, t_end, dt) {
let steps = floor((t_end - t_start) / dt);
let mut t = t_start;
let mut y = y0;
let mut results = [];
for i in range(0, steps + 1) {
results.push({ t: t, y: y });
y = y + f(t, y) * dt;
t = t + dt;
}
results
}
/// RK4 integrator for scalar ODE
pub fn rk4(f, y0, t_start, t_end, dt) {
let steps = floor((t_end - t_start) / dt);
let mut t = t_start;
let mut y = y0;
let mut results = [];
for i in range(0, steps + 1) {
results.push({ t: t, y: y });
let k1 = f(t, y);
let k2 = f(t + dt / 2.0, y + k1 * dt / 2.0);
let k3 = f(t + dt / 2.0, y + k2 * dt / 2.0);
let k4 = f(t + dt, y + k3 * dt);
y = y + (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4);
t = t + dt;
}
results
}
/// Euler integrator for vector systems
pub fn euler_system(f, y0_vec, t_start, t_end, dt) {
let steps = floor((t_end - t_start) / dt);
let mut t = t_start;
let mut y = y0_vec;
let mut results = [];
for i in range(0, steps + 1) {
results.push({ t: t, y: y });
let dy = f(t, y);
y = vec_add_scaled(y, dy, dt);
t = t + dt;
}
results
}
/// RK4 integrator for vector systems
pub fn rk4_system(f, y0_vec, t_start, t_end, dt) {
let steps = floor((t_end - t_start) / dt);
let mut t = t_start;
let mut y = y0_vec;
let mut results = [];
for i in range(0, steps + 1) {
results.push({ t: t, y: y });
let k1 = f(t, y);
let k2 = f(t + dt / 2.0, vec_add_scaled(y, k1, dt / 2.0));
let k3 = f(t + dt / 2.0, vec_add_scaled(y, k2, dt / 2.0));
let k4 = f(t + dt, vec_add_scaled(y, k3, dt));
let step = vec_add(vec_add(k1, vec_scale(k2, 2.0)), vec_add(vec_scale(k3, 2.0), k4));
y = vec_add_scaled(y, step, dt / 6.0);
t = t + dt;
}
results
}
// ===== Adaptive Step-Size Integrators =====
function vec_sub(a, b) {
let mut out = [];
for i in range(0, a.len()) {
out.push(a[i] - b[i]);
}
out
}
function vec_norm(a) {
let mut s = 0.0;
for i in range(0, a.len()) {
s = s + a[i] * a[i];
}
sqrt(s)
}
/// RK45 adaptive integrator for scalar ODE (Dormand-Prince method)
///
/// Automatically adjusts step size to maintain the requested tolerance.
/// Uses an embedded 4th/5th order pair for error estimation.
///
/// @param f - derivative function (t, y) => dy/dt
/// @param y0 - initial value
/// @param t_start - start time
/// @param t_end - end time
/// @param tol - error tolerance (default 1e-6)
/// @param dt_init - initial step size (default (t_end - t_start) / 100)
/// @param dt_min - minimum step size (default 1e-12)
/// @param dt_max - maximum step size (default (t_end - t_start) / 4)
/// @returns array of { t, y } records at each accepted step
pub fn rk45(f, y0, t_start, t_end, tol = 0.000001, dt_init = 0.0, dt_min = 0.000000000001, dt_max = 0.0) {
let safety = 0.9;
let max_factor = 5.0;
let min_factor = 0.2;
let mut h = dt_init;
if h == 0.0 {
h = (t_end - t_start) / 100.0;
}
let mut h_max = dt_max;
if h_max == 0.0 {
h_max = (t_end - t_start) / 4.0;
}
let mut t = t_start;
let mut y = y0;
let mut results = [];
results.push({ t: t, y: y });
let max_steps = 100000;
let mut step_count = 0;
while t < t_end && step_count < max_steps {
// Clamp step to not overshoot t_end
if t + h > t_end {
h = t_end - t;
}
if h < dt_min {
h = dt_min;
}
// Dormand-Prince stages
let k1 = f(t, y);
let k2 = f(t + h / 5.0, y + h * k1 / 5.0);
let k3 = f(t + 3.0 * h / 10.0, y + h * (3.0 * k1 / 40.0 + 9.0 * k2 / 40.0));
let k4 = f(t + 4.0 * h / 5.0, y + h * (44.0 * k1 / 45.0 - 56.0 * k2 / 15.0 + 32.0 * k3 / 9.0));
let k5 = f(t + 8.0 * h / 9.0, y + h * (19372.0 * k1 / 6561.0 - 25360.0 * k2 / 2187.0 + 64448.0 * k3 / 6561.0 - 212.0 * k4 / 729.0));
let k6 = f(t + h, y + h * (9017.0 * k1 / 3168.0 - 355.0 * k2 / 33.0 + 46732.0 * k3 / 5247.0 + 49.0 * k4 / 176.0 - 5103.0 * k5 / 18656.0));
// 5th order solution (for advancing)
let y5 = y + h * (35.0 * k1 / 384.0 + 500.0 * k3 / 1113.0 + 125.0 * k4 / 192.0 - 2187.0 * k5 / 6784.0 + 11.0 * k6 / 84.0);
// 4th order solution (for error estimate)
let k7 = f(t + h, y5);
let y4 = y + h * (5179.0 * k1 / 57600.0 + 7571.0 * k3 / 16695.0 + 393.0 * k4 / 640.0 - 92097.0 * k5 / 339200.0 + 187.0 * k6 / 2100.0 + k7 / 40.0);
// Error estimate
let mut err = abs(y5 - y4);
if err < 0.000000000000001 {
err = 0.000000000000001;
}
if err <= tol {
// Accept step
t = t + h;
y = y5;
results.push({ t: t, y: y });
}
// Adjust step size
let mut factor = safety * pow(tol / err, 0.2);
if factor > max_factor {
factor = max_factor;
}
if factor < min_factor {
factor = min_factor;
}
h = h * factor;
if h > h_max {
h = h_max;
}
step_count = step_count + 1;
}
results
}
/// RK45 adaptive integrator for vector systems (Dormand-Prince method)
///
/// @param f - derivative function (t, y_vec) => dy_vec/dt
/// @param y0_vec - initial state vector
/// @param t_start - start time
/// @param t_end - end time
/// @param tol - error tolerance (default 1e-6)
/// @param dt_init - initial step size (default (t_end - t_start) / 100)
/// @param dt_min - minimum step size (default 1e-12)
/// @param dt_max - maximum step size (default (t_end - t_start) / 4)
/// @returns array of { t, y } records at each accepted step
pub fn rk45_system(f, y0_vec, t_start, t_end, tol = 0.000001, dt_init = 0.0, dt_min = 0.000000000001, dt_max = 0.0) {
let safety = 0.9;
let max_factor = 5.0;
let min_factor = 0.2;
let mut h = dt_init;
if h == 0.0 {
h = (t_end - t_start) / 100.0;
}
let mut h_max = dt_max;
if h_max == 0.0 {
h_max = (t_end - t_start) / 4.0;
}
let mut t = t_start;
let mut y = y0_vec;
let mut results = [];
results.push({ t: t, y: y });
let max_steps = 100000;
let mut step_count = 0;
while t < t_end && step_count < max_steps {
if t + h > t_end {
h = t_end - t;
}
if h < dt_min {
h = dt_min;
}
// Dormand-Prince stages (vector version)
let k1 = f(t, y);
let k2 = f(t + h / 5.0, vec_add_scaled(y, k1, h / 5.0));
let y3 = vec_add(vec_add_scaled(y, k1, 3.0 * h / 40.0), vec_scale(k2, 9.0 * h / 40.0));
let k3 = f(t + 3.0 * h / 10.0, y3);
let y4_stage = vec_add(vec_add(vec_add_scaled(y, k1, 44.0 * h / 45.0), vec_scale(k2, -56.0 * h / 15.0)), vec_scale(k3, 32.0 * h / 9.0));
let k4 = f(t + 4.0 * h / 5.0, y4_stage);
let y5_stage = vec_add(vec_add(vec_add(vec_add_scaled(y, k1, 19372.0 * h / 6561.0), vec_scale(k2, -25360.0 * h / 2187.0)), vec_scale(k3, 64448.0 * h / 6561.0)), vec_scale(k4, -212.0 * h / 729.0));
let k5 = f(t + 8.0 * h / 9.0, y5_stage);
let y6 = vec_add(vec_add(vec_add(vec_add(vec_add_scaled(y, k1, 9017.0 * h / 3168.0), vec_scale(k2, -355.0 * h / 33.0)), vec_scale(k3, 46732.0 * h / 5247.0)), vec_scale(k4, 49.0 * h / 176.0)), vec_scale(k5, -5103.0 * h / 18656.0));
let k6 = f(t + h, y6);
// 5th order solution
let y_next = vec_add(vec_add(vec_add(vec_add(vec_add_scaled(y, k1, 35.0 * h / 384.0), vec_scale(k3, 500.0 * h / 1113.0)), vec_scale(k4, 125.0 * h / 192.0)), vec_scale(k5, -2187.0 * h / 6784.0)), vec_scale(k6, 11.0 * h / 84.0));
// 4th order solution (for error)
let k7 = f(t + h, y_next);
let y4_sol = vec_add(vec_add(vec_add(vec_add(vec_add(vec_add_scaled(y, k1, 5179.0 * h / 57600.0), vec_scale(k3, 7571.0 * h / 16695.0)), vec_scale(k4, 393.0 * h / 640.0)), vec_scale(k5, -92097.0 * h / 339200.0)), vec_scale(k6, 187.0 * h / 2100.0)), vec_scale(k7, h / 40.0));
// Error estimate (vector norm)
let err_vec = vec_sub(y_next, y4_sol);
let mut err = vec_norm(err_vec);
if err < 0.000000000000001 {
err = 0.000000000000001;
}
if err <= tol {
t = t + h;
y = y_next;
results.push({ t: t, y: y });
}
let mut factor = safety * pow(tol / err, 0.2);
if factor > max_factor {
factor = max_factor;
}
if factor < min_factor {
factor = min_factor;
}
h = h * factor;
if h > h_max {
h = h_max;
}
step_count = step_count + 1;
}
results
}