shape-runtime 0.3.0

Bytecode compiler, builtins, and runtime infrastructure for Shape
Documentation
/// @module std::core::ode
/// ODE Integrators
///
/// Basic Euler and RK4 integrators for scalar and vector systems.

function vec_add(a: Array<number>, b: Array<number>) -> Array<number> {
    let mut out: Array<number> = [];
    for i in range(0, a.len()) {
        out.push(a[i] + b[i]);
    }
    out
}

function vec_scale(a: Array<number>, s: number) -> Array<number> {
    let mut out: Array<number> = [];
    for i in range(0, a.len()) {
        out.push(a[i] * s);
    }
    out
}

function vec_add_scaled(a: Array<number>, b: Array<number>, s: number) -> Array<number> {
    let scaled: Array<number> = vec_scale(b, s);
    vec_add(a, scaled)
}

/// Euler integrator for scalar ODE
pub fn euler(f: (number, number) => number, y0: number, t_start: number, t_end: number, dt: number) {
    let steps: int = floor((t_end - t_start) / dt) as int;
    let mut t: number = t_start;
    let mut y: number = y0;
    let mut results: Array<{t: number, y: number}> = [];

    for i in range(0, steps + 1) {
        results.push({ t: t, y: y });
        let fy: number = f(t, y);
        y = y + fy * dt;
        t = t + dt;
    }

    results
}

/// RK4 integrator for scalar ODE
pub fn rk4(f: (number, number) => number, y0: number, t_start: number, t_end: number, dt: number) {
    let steps: int = floor((t_end - t_start) / dt) as int;
    let mut t: number = t_start;
    let mut y: number = y0;
    let mut results: Array<{t: number, y: number}> = [];

    for i in range(0, steps + 1) {
        results.push({ t: t, y: y });

        let k1: number = f(t, y);
        let k2: number = f(t + dt / 2.0, y + k1 * dt / 2.0);
        let k3: number = f(t + dt / 2.0, y + k2 * dt / 2.0);
        let k4: number = 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: (number, Array<number>) => Array<number>, y0_vec: Array<number>, t_start: number, t_end: number, dt: number) {
    let steps: int = floor((t_end - t_start) / dt) as int;
    let mut t: number = t_start;
    let mut y: Array<number> = y0_vec;
    // NOTE: results seeded with first record rather than `[]` because v0.3
    // inference cannot match Array<{t:number, y:Array<number>}> through push.
    let mut results = [{ t: t, y: y }];
    let mut first: bool = true;

    for i in range(0, steps + 1) {
        if !first {
            results.push({ t: t, y: y });
        }
        first = false;
        let dy: Array<number> = f(t, y);
        y = vec_add_scaled(y, dy, dt);
        t = t + dt;
    }

    results
}

/// RK4 integrator for vector systems
pub fn rk4_system(f: (number, Array<number>) => Array<number>, y0_vec: Array<number>, t_start: number, t_end: number, dt: number) {
    let steps: int = floor((t_end - t_start) / dt) as int;
    let mut t: number = t_start;
    let mut y: Array<number> = y0_vec;
    // NOTE: results seeded per v0.3 inference gap (see euler_system).
    let mut results = [{ t: t, y: y }];
    let mut first: bool = true;

    for i in range(0, steps + 1) {
        if !first {
            results.push({ t: t, y: y });
        }
        first = false;

        let k1: Array<number> = f(t, y);
        let ys2: Array<number> = vec_add_scaled(y, k1, dt / 2.0);
        let k2: Array<number> = f(t + dt / 2.0, ys2);
        let ys3: Array<number> = vec_add_scaled(y, k2, dt / 2.0);
        let k3: Array<number> = f(t + dt / 2.0, ys3);
        let ys4: Array<number> = vec_add_scaled(y, k3, dt);
        let k4: Array<number> = f(t + dt, ys4);

        let sk2: Array<number> = vec_scale(k2, 2.0);
        let sk3: Array<number> = vec_scale(k3, 2.0);
        let half1: Array<number> = vec_add(k1, sk2);
        let half2: Array<number> = vec_add(sk3, k4);
        let step: Array<number> = vec_add(half1, half2);
        y = vec_add_scaled(y, step, dt / 6.0);
        t = t + dt;
    }

    results
}

// ===== Adaptive Step-Size Integrators =====

function vec_sub(a: Array<number>, b: Array<number>) -> Array<number> {
    let mut out: Array<number> = [];
    for i in range(0, a.len()) {
        out.push(a[i] - b[i]);
    }
    out
}

function vec_norm(a: Array<number>) -> number {
    let mut s: number = 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: (number, number) => number, y0: number, t_start: number, t_end: number, tol: number = 0.000001, dt_init: number = 0.0, dt_min: number = 0.000000000001, dt_max: number = 0.0) {
    let safety: number = 0.9;
    let max_factor: number = 5.0;
    let min_factor: number = 0.2;

    let mut h: number = dt_init;
    if h == 0.0 {
        h = (t_end - t_start) / 100.0;
    }
    let mut h_max: number = dt_max;
    if h_max == 0.0 {
        h_max = (t_end - t_start) / 4.0;
    }

    let mut t: number = t_start;
    let mut y: number = y0;
    let mut results: Array<{t: number, y: number}> = [];
    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: number = f(t, y);
        let k2: number = f(t + h / 5.0, y + h * k1 / 5.0);
        let k3: number = f(t + 3.0 * h / 10.0, y + h * (3.0 * k1 / 40.0 + 9.0 * k2 / 40.0));
        let k4: number = 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: number = 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: number = 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: number = 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: number = f(t + h, y5);
        let y4: number = 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: number = 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 pow_factor: number = pow(tol / err, 0.2);
        let mut factor: number = safety * pow_factor;
        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: (number, Array<number>) => Array<number>, y0_vec: Array<number>, t_start: number, t_end: number, tol: number = 0.000001, dt_init: number = 0.0, dt_min: number = 0.000000000001, dt_max: number = 0.0) {
    let safety: number = 0.9;
    let max_factor: number = 5.0;
    let min_factor: number = 0.2;

    let mut h: number = dt_init;
    if h == 0.0 {
        h = (t_end - t_start) / 100.0;
    }
    let mut h_max: number = dt_max;
    if h_max == 0.0 {
        h_max = (t_end - t_start) / 4.0;
    }

    let mut t: number = t_start;
    let mut y: Array<number> = y0_vec;
    // NOTE: results seeded per v0.3 inference gap (see euler_system).
    let mut results = [{ 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: Array<number> = f(t, y);
        let yk2: Array<number> = vec_add_scaled(y, k1, h / 5.0);
        let k2: Array<number> = f(t + h / 5.0, yk2);

        let y3a: Array<number> = vec_add_scaled(y, k1, 3.0 * h / 40.0);
        let y3b: Array<number> = vec_scale(k2, 9.0 * h / 40.0);
        let y3: Array<number> = vec_add(y3a, y3b);
        let k3: Array<number> = f(t + 3.0 * h / 10.0, y3);

        let y4a: Array<number> = vec_add_scaled(y, k1, 44.0 * h / 45.0);
        let y4b: Array<number> = vec_scale(k2, -56.0 * h / 15.0);
        let y4c: Array<number> = vec_scale(k3, 32.0 * h / 9.0);
        let y4_stage: Array<number> = vec_add(vec_add(y4a, y4b), y4c);
        let k4: Array<number> = f(t + 4.0 * h / 5.0, y4_stage);

        let y5a: Array<number> = vec_add_scaled(y, k1, 19372.0 * h / 6561.0);
        let y5b: Array<number> = vec_scale(k2, -25360.0 * h / 2187.0);
        let y5c: Array<number> = vec_scale(k3, 64448.0 * h / 6561.0);
        let y5d: Array<number> = vec_scale(k4, -212.0 * h / 729.0);
        let y5_stage: Array<number> = vec_add(vec_add(vec_add(y5a, y5b), y5c), y5d);
        let k5: Array<number> = f(t + 8.0 * h / 9.0, y5_stage);

        let y6a: Array<number> = vec_add_scaled(y, k1, 9017.0 * h / 3168.0);
        let y6b: Array<number> = vec_scale(k2, -355.0 * h / 33.0);
        let y6c: Array<number> = vec_scale(k3, 46732.0 * h / 5247.0);
        let y6d: Array<number> = vec_scale(k4, 49.0 * h / 176.0);
        let y6e: Array<number> = vec_scale(k5, -5103.0 * h / 18656.0);
        let y6: Array<number> = vec_add(vec_add(vec_add(vec_add(y6a, y6b), y6c), y6d), y6e);
        let k6: Array<number> = f(t + h, y6);

        // 5th order solution
        let yn_a: Array<number> = vec_add_scaled(y, k1, 35.0 * h / 384.0);
        let yn_b: Array<number> = vec_scale(k3, 500.0 * h / 1113.0);
        let yn_c: Array<number> = vec_scale(k4, 125.0 * h / 192.0);
        let yn_d: Array<number> = vec_scale(k5, -2187.0 * h / 6784.0);
        let yn_e: Array<number> = vec_scale(k6, 11.0 * h / 84.0);
        let y_next: Array<number> = vec_add(vec_add(vec_add(vec_add(yn_a, yn_b), yn_c), yn_d), yn_e);

        // 4th order solution (for error)
        let k7: Array<number> = f(t + h, y_next);
        let y4s_a: Array<number> = vec_add_scaled(y, k1, 5179.0 * h / 57600.0);
        let y4s_b: Array<number> = vec_scale(k3, 7571.0 * h / 16695.0);
        let y4s_c: Array<number> = vec_scale(k4, 393.0 * h / 640.0);
        let y4s_d: Array<number> = vec_scale(k5, -92097.0 * h / 339200.0);
        let y4s_e: Array<number> = vec_scale(k6, 187.0 * h / 2100.0);
        let y4s_f: Array<number> = vec_scale(k7, h / 40.0);
        let y4_sol: Array<number> = vec_add(vec_add(vec_add(vec_add(vec_add(y4s_a, y4s_b), y4s_c), y4s_d), y4s_e), y4s_f);

        // Error estimate (vector norm)
        let err_vec: Array<number> = vec_sub(y_next, y4_sol);
        let mut err: number = 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 pow_factor: number = pow(tol / err, 0.2);
        let mut factor: number = safety * pow_factor;
        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
}