C++代码实现MATLAB中的ode45函数功能

cpp 复制代码
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

// C callback for derivatives: fills dydt of length n given y of length n at time t.
typedef void (*ode45_deriv_cb)(double t, const double* y, double* dydt, size_t n, void* ctx);

// Integrate from t0 to t1 using Dormand-Prince 5(4) with adaptive steps

// Integrate from t0 to t1 using Dormand-Prince 5(4) with adaptive steps.
// Inputs:
// - t0, y0[n]: initial time/state
// - t1: target time
// - h_init: initial step size (non-zero)
// - atol, rtol: tolerances
// - normControl: 0 (component-wise max) or 1 (global 2-norm)
// - max_steps: safety cap
// - cb, ctx: derivative callback + user context
// Output:
// - y_out[n]: state at t1
// Returns 0 on success, non-zero on failure.
int ode45_integrate_final(double t0,
                          const double* y0,
                          size_t n,
                          double t1,
                          double h_init,
                          double atol,
                          double rtol,
                          int normControl,
                          int max_steps,
                          ode45_deriv_cb cb,
                          void* ctx,
                          double* y_out);

// Integrate and return samples at explicit times.
// Inputs:
// - t0, y0[n]
// - times[m]: strictly monotone in desired direction (first equals t0 recommended)
// - Remaining parameters like above.
// Output:
// - y_out[m*n]: concatenated row-major blocks of length n for each time.
// Returns 0 on success, non-zero on failure.
int ode45_integrate_times(double t0,
                          const double* y0,
                          size_t n,
                          const double* times,
                          size_t m,
                          double h_init,
                          double atol,
                          double rtol,
                          int normControl,
                          int max_steps,
                          ode45_deriv_cb cb,
                          void* ctx,
                          double* y_out);

#ifdef __cplusplus
}
#endif

#include <cmath>
#include <cstdlib>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <array>
#include <utility>
#include <string>
#include <vector>

// Standalone Dormand-Prince 5(4) (DOPRI5) adaptive Runge-Kutta integrator.
// No Boost dependencies.
using State = std::vector<double>;
using DerivFunc = std::function<void(double /*t*/, const State& /*y*/, State& /*dydt*/)>;

// Forward declaration for NormControl two-norm error
static double two_norm_scaled(const State& err, const State& y, const State& y_next, double atol, double rtol);
static State zeros_like(const State& y) {
    return State(y.size(), 0.0);
}

// Dense output for Dormand-Prince 5(4): Hermite quartic interpolation (Octave-like)
static State dopri5_dense_output(double dt, const State& x0, const State& x1, const std::array<State,7>& K, double s) {
    // s in [0,1], dt = h, K[0]=k1 at t, K[6]=k7 at t+dt
    const size_t n = x0.size();
    State out(n);
    if (n == 0) return out;

    // Coefficients for u_half = y(t+dt/2)
    constexpr double c_half[7] = {
        6025192743.0/30085553152.0,
        0.0,
        51252292925.0/65400821598.0,
        -2691868925.0/45128329728.0,
        187940372067.0/1594534317056.0,
        -1776094331.0/19743644256.0,
        11237099.0/235043384.0,
    };

    State u_half(n, 0.0);
    for (size_t i = 0; i < n; ++i) {
        double acc = 0.0;
        for (int k = 0; k < 7; ++k) acc += c_half[k] * K[k][i];
        u_half[i] = x0[i] + 0.5 * dt * acc;
    }

    // Hermite quartic basis polynomials
    const double s2 = s*s;
    const double s3 = s2*s;
    const double s4 = s3*s;

    const double H0 = 1.0 - 11.0*s2 + 18.0*s3 - 8.0*s4;
    const double H1 = s - 4.0*s2 + 5.0*s3 - 2.0*s4;
    const double H2 = 16.0*s2 - 32.0*s3 + 16.0*s4;
    const double H3 = -5.0*s2 + 14.0*s3 - 8.0*s4;
    const double H4 = s2 - 3.0*s3 + 2.0*s4;

    for (size_t i = 0; i < n; ++i) {
        out[i] = H0 * x0[i]
               + H1 * (dt * K[0][i])
               + H2 * u_half[i]
               + H3 * x1[i]
               + H4 * (dt * K[6][i]);
    }

    return out;
}

static double inf_norm_scaled(const State& err, const State& y, const State& y_next, double atol, double rtol) {
    // Matches Octave's AbsRel_norm with NormControl == false:
    // err_norm = max_i (|err_i| / scale_i), scale_i = max(AbsTol, RelTol * max(|y_i|, |y_next_i|))
    if (err.size() != y.size() || err.size() != y_next.size()) {
        throw std::runtime_error("State size mismatch");
    }
    if (err.empty()) {
        return 0.0;
    }
    double max_ratio = 0.0;
    for (size_t i = 0; i < err.size(); ++i) {
        const double sc = std::max(atol, rtol * std::max(std::abs(y[i]), std::abs(y_next[i])));
        const double r = (sc > 0.0) ? (std::abs(err[i]) / sc) : std::abs(err[i]);
        if (r > max_ratio) max_ratio = r;
    }
    return max_ratio;
}

// Performs one DOPRI5 step. Computes y_next (5th order) and err (difference 5th-4th).
static void runge_kutta_dopri5_step(const DerivFunc& f, double t, const State& y, double h, State& y_next,
State& err, std::array<State,7>& K) {
    const size_t n = y.size();
    if (n == 0) {
        y_next.clear();
        err.clear();
        return;
    }

    State k1(n), k2(n), k3(n), k4(n), k5(n), k6(n), k7(n);
    State tmp(n);

    // Butcher tableau coefficients for Dormand-Prince 5(4)
    // c_i
    constexpr double c2 = 1.0 / 5.0;
    constexpr double c3 = 3.0 / 10.0;
    constexpr double c4 = 4.0 / 5.0;
    constexpr double c5 = 8.0 / 9.0;
    constexpr double c6 = 1.0;
    constexpr double c7 = 1.0;

    // a (lower triangular):
    constexpr double a21 = 1.0 / 5.0;

    constexpr double a31 = 3.0 / 40.0;
    constexpr double a32 = 9.0 / 40.0;

    constexpr double a41 = 44.0 / 45.0;
    constexpr double a42 = -56.0 / 15.0;
    constexpr double a43 = 32.0 / 9.0;

constexpr double a51 = 19372.0 / 6561.0;
constexpr double a52 = -25360.0 / 2187.0;
constexpr double a53 = 64448.0 / 6561.0;
constexpr double a54 = -212.0 / 729.0;

constexpr double a61 = 9017.0 / 3168.0;
constexpr double a62 = -355.0 / 33.0;
constexpr double a63 = 46732.0 / 5247.0;
constexpr double a64 = 49.0 / 176.0;
constexpr double a65 = -5103.0 / 18656.0;

constexpr double a71 = 35.0 / 384.0;
constexpr double a72 = 0.0;
constexpr double a73 = 500.0 / 1113.0;
constexpr double a74 = 125.0 / 192.0;
constexpr double a75 = -2187.0 / 6784.0;
constexpr double a76 = 11.0 / 84.0;

// b (5th order) equals row 7 coefficients (a71..a76) with b7=0.
constexpr double b1 = a71;
constexpr double b2 = a72;
constexpr double b3 = a73;
constexpr double b4 = a74;
constexpr double b5 = a75;
constexpr double b6 = a76;
constexpr double b7 = 0.0;

// b_hat (4th order):
constexpr double bh1 = 5179.0 / 57600.0;
constexpr double bh2 = 0.0;
constexpr double bh3 = 7571.0 / 16695.0;
constexpr double bh4 = 393.0 / 640.0;
constexpr double bh5 = -92097.0 / 339200.0;
constexpr double bh6 = 187.0 / 2100.0;
constexpr double bh7 = 1.0 / 40.0;

// k1
f(t, y, k1);

// k2
for (size_t i = 0; i < n; ++i) tmp[i] = y[i] + h * (a21 * k1[i]);
f(t + c2 * h, tmp, k2);

// k3
for (size_t i = 0; i < n; ++i) tmp[i] = y[i] + h * (a31 * k1[i] + a32 * k2[i]);
f(t + c3 * h, tmp, k3);

// k4
for (size_t i = 0; i < n; ++i) tmp[i] = y[i] + h * (a41 * k1[i] + a42 * k2[i] + a43 * k3[i]);
f(t + c4 * h, tmp, k4);

// k5
for (size_t i = 0; i < n; ++i)
    tmp[i] = y[i] + h * (a51 * k1[i] + a52 * k2[i] + a53 * k3[i] + a54 * k4[i]);
f(t + c5 * h, tmp, k5);

// k6
for (size_t i = 0; i < n; ++i)
    tmp[i] = y[i] + h * (a61 * k1[i] + a62 * k2[i] + a63 * k3[i] + a64 * k4[i] + a65 * k5[i]);
f(t + c6 * h, tmp, k6);

// 5th order solution y_next using b1..b6
y_next.resize(n);
for (size_t i = 0; i < n; ++i)
    y_next[i] = y[i] + h * (b1 * k1[i] + b2 * k2[i] + b3 * k3[i] + b4 * k4[i] + b5 * k5[i] + b6 * k6[i] + b7 * 0.0);

// k7 evaluated at (t+h, y_next)
f(t + c7 * h, y_next, k7);

// error estimate (difference between 5th and 4th order solutions)
err.resize(n);
for (size_t i = 0; i < n; ++i) {
    const double e = h * ((b1 - bh1) * k1[i] + (b2 - bh2) * k2[i] + (b3 - bh3) * k3[i] + (b4 - bh4) * k4[i] +
                          (b5 - bh5) * k5[i] + (b6 - bh6) * k6[i] + (0.0 - bh7) * k7[i]);
    err[i] = e;
}

// Export stages for dense output interpolation
K[0] = std::move(k1);
K[1] = std::move(k2);
K[2] = std::move(k3);
K[3] = std::move(k4);
K[4] = std::move(k5);
K[5] = std::move(k6);
K[6] = std::move(k7);
}

// Adaptive integration wrapper.
static State integrate_runge_kutta_dopri5(const DerivFunc& f,
                                          double t0,
                                          const State& y0,
                                          double t1,
                                          double h_init,
                                          double atol,
                                          double rtol,
                                          bool normControl = false,
                                          int max_steps = 100000) {
    if (h_init == 0.0) {
        throw std::runtime_error("h_init must be non-zero");
    }
    if (max_steps <= 0) {
        throw std::runtime_error("max_steps must be positive");
    }

    const double dir = (t1 >= t0) ? 1.0 : -1.0;
    double t = t0;
    State y = y0;
    double h = std::abs(h_init) * dir;

    // Step-size control parameters
    const double safety = 0.9;
    const double min_factor = 0.2;
    const double max_factor = 10.0;
    const double tiny = std::numeric_limits<double>::min();

    for (int step = 0; step < max_steps; ++step) {
        if ((dir > 0.0 && t >= t1) || (dir < 0.0 && t <= t1)) {
            return y;
        }
        // Clamp step to hit t1 exactly
        if ((dir > 0.0 && t + h > t1) || (dir < 0.0 && t + h < t1)) {
            h = t1 - t;
        }

        State y_next, err;
        std::array<State,7> K;
        runge_kutta_dopri5_step(f, t, y, h, y_next, err, K);
        const double err_norm = normControl
            ? two_norm_scaled(err, y, y_next, atol, rtol)
            : inf_norm_scaled(err, y, y_next, atol, rtol);

        if (err_norm <= 1.0) {
            // Accept step
            t += h;
            y.swap(y_next);
        }

        // Compute next step size
        // Embedded RK 5(4): controller exponent 1/5.
        double factor;
        if (err_norm <= tiny) {
            factor = max_factor;
        } else if (!std::isfinite(err_norm)) {
            // Non-finite error: shrink step aggressively
            factor = min_factor;
        } else {
            factor = safety * std::pow(1.0 / err_norm, 0.2);
        
        factor = std::min(max_factor, std::max(min_factor, factor));
}
        h *= factor;
        if (h == 0.0) {
            throw std::runtime_error("Step size underflow");
        }
        // If rejected (including non-finite), try again without advancing time/state.
        if (err_norm > 1.0 || !std::isfinite(err_norm)) {
            continue;
        }
    }

    throw std::runtime_error("Reached max_steps without finishing integration");
}


struct Trajectory {
    std::vector<double> t;
    std::vector<State> y;
};


// Integrate and return dense output samples according to refine (>=1)
static Trajectory integrate_runge_kutta_dopri5_refined(const DerivFunc& f,
                                                       double t0,
                                                       const State& y0,
                                                       double t1,
                                                       double h_init,
                                                       double atol,
                                                       double rtol,
                                                       bool normControl,
                                                       int refine,
                                                       int max_steps = 100000) {
    if (h_init == 0.0) throw std::runtime_error("h_init must be non-zero");
    if (max_steps <= 0) throw std::runtime_error("max_steps must be positive");
    if (refine < 1) refine = 1;

    const double dir = (t1 >= t0) ? 1.0 : -1.0;
    double t = t0;
    State y = y0;
    double h = std::abs(h_init) * dir;

    const double safety = 0.9;
    const double min_factor = 0.2;
    const double max_factor = 10.0;
    const double tiny = std::numeric_limits<double>::min();

    Trajectory traj;
    traj.t.push_back(t);
    traj.y.push_back(y);

    for (int step = 0; step < max_steps; ++step) {
        if ((dir > 0.0 && t >= t1) || (dir < 0.0 && t <= t1)) {
            return traj;
        }

        if ((dir > 0.0 && t + h > t1) || (dir < 0.0 && t + h < t1)) {
            h = t1 - t;
        }

        State y_next, err;
        std::array<State,7> K;
        runge_kutta_dopri5_step(f, t, y, h, y_next, err, K);
        const double err_norm = normControl
            ? two_norm_scaled(err, y, y_next, atol, rtol)
            : inf_norm_scaled(err, y, y_next, atol, rtol);

        if (err_norm <= 1.0) {
            // Accepted: emit interior refined points using dense output
            const double dt = h;
            for (int i = 1; i < refine; ++i) {
                const double s = static_cast<double>(i) / static_cast<double>(refine);
                State yi = dopri5_dense_output(dt, y, y_next, K, s);
                traj.t.push_back(t + s * h);
                traj.y.push_back(std::move(yi));
            }
            // Endpoint
            t += h;
            y.swap(y_next);
            traj.t.push_back(t);
            traj.y.push_back(y);
        }

        // Step-size controller
        double factor;
        if (err_norm <= tiny) {
            factor = max_factor;
        } else if (!std::isfinite(err_norm)) {
            factor = min_factor;
        } else {
            factor = safety * std::pow(1.0 / err_norm, 0.2);
            factor = std::min(max_factor, std::max(min_factor, factor));
        }

        h *= factor;
        if (h == 0.0) throw std::runtime_error("Step size underflow");
        if (err_norm > 1.0 || !std::isfinite(err_norm)) continue;
    }

    throw std::runtime_error("Reached max_steps without finishing integration");
}


// Integrate and return samples exactly at requested times using dense output per accepted step.
static Trajectory integrate_runge_kutta_dopri5_at_times(const DerivFunc& f,
                                                        double t0,
                                                        const State& y0,
                                                        const std::vector<double>& times,
                                                        double h_init,
                                                        double atol,
                                                        double rtol,
                                                        bool normControl,
                                                        int max_steps = 100000) {
    if (h_init == 0.0) throw std::runtime_error("h_init must be non-zero");
    if (max_steps <= 0) throw std::runtime_error("max_steps must be positive");
    if (times.empty()) throw std::runtime_error("times must be non-empty");
    // Ensure monotonicity direction matches integration
    const double dir = (times.back() >= times.front()) ? 1.0 : -1.0;
    // t0 must equal first time to match initial condition semantics
    if (std::abs(times.front() - t0) > 0) {
        t0 = times.front();
    }

    double t = t0;
    State y = y0;
    double h = std::abs(h_init) * dir;

    const double safety = 0.9;
    const double min_factor = 0.2;
    const double max_factor = 10.0;
    const double tiny = std::numeric_limits<double>::min();

    Trajectory traj;
    size_t idx = 0;
    // Emit initial sample if requested equals t0
    if ((dir > 0 && times[idx] <= t) || (dir < 0 && times[idx] >= t)) {
        traj.t.push_back(times[idx]);
        traj.y.push_back(y);
        ++idx;
    }

    for (int step = 0; step < max_steps && idx < times.size(); ++step) {
        // If we've passed the last requested time, stop
        if ((dir > 0.0 && t >= times.back()) || (dir < 0.0 && t <= times.back())) {
            break;
        }

        // Adjust step to not skip beyond last requested time
        if ((dir > 0.0 && t + h > times.back()) || (dir < 0.0 && t + h < times.back())) {
            h = times.back() - t;
        }

        State y_next, err;
        std::array<State,7> K;
        runge_kutta_dopri5_step(f, t, y, h, y_next, err, K);
        const double err_norm = normControl
            ? two_norm_scaled(err, y, y_next, atol, rtol)
            : inf_norm_scaled(err, y, y_next, atol, rtol);

        // Step-size controller
        double factor;
        if (err_norm <= tiny) {
            factor = max_factor;
        } else if (!std::isfinite(err_norm)) {
            factor = min_factor;
        } else {
            factor = safety * std::pow(1.0 / err_norm, 0.2);
            factor = std::min(max_factor, std::max(min_factor, factor));
        }
        h *= factor;
        if (h == 0.0) throw std::runtime_error("Step size underflow");
        if (err_norm > 1.0 || !std::isfinite(err_norm)) continue; // rejected

        // Accepted: emit samples at times within (t, t+h]
        const double dt = times.back() - times.front(); // unused; keep h for s calc
        while (idx < times.size()) {
            const double tau = times[idx];
            // Check whether tau is within this step interval
            const bool in_interval = (dir > 0.0) ? (tau > t && tau <= t + h) : (tau < t && tau >= t + h);
            if (!in_interval) break;
            const double s = (tau - t) / h; // works both directions
            State yi = dopri5_dense_output(std::abs(h), y, y_next, K, s);
            traj.t.push_back(tau);
            traj.y.push_back(std::move(yi));
            ++idx;
        }

        // Advance to end of step
        t += h;
        y.swap(y_next);
    }

    if (idx != times.size()) {
        throw std::runtime_error("Failed to sample all requested times before reaching max_steps");
    }
    return traj;
}


struct Options {
    double t0 = 0.0;
    double t1 = 5.0;
    double h_init = 0.1;
    double atol = 1e-10;
    double rtol = 1e-10;
    bool normControl = false; // Octave default is off
    int refine = 4;           // Octave default is 4 for ode45
    int max_steps = 100000;
    std::vector<double> times; // explicit sample times; overrides refine when non-empty
};


static Options parseArgs(int argc, char** argv, const Options& defaults) {
    Options opt = defaults;
    auto starts_with = [](const std::string& s, const std::string& p) { return s.rfind(p,0)==0; };
    for (int i = 1; i < argc; ++i) {
        std::string a = argv[i];
        if (a == "--help" || a == "-h") {
            std::cout << "Options:\n"
                      << "  --t0=VAL      start time (default 0)\n"
                      << "  --t1=VAL      end time (default 5)\n"
                      << "  --h-init=VAL  initial step size\n"
                      << "  --atol=VAL    absolute tolerance\n"
                      << "  --rtol=VAL    relative tolerance\n"
                      << "  --normcontrol=on|off norm control (default off)\n"
                      << "  --refine=N    points per step (default 4)\n"
                      << "  --maxsteps=N  max steps (default 100000)\n"
                      << "  --times=t0,t1,... explicit times to sample (overrides refine)\n";
            std::exit(0);
        }
        auto eq = a.find('=');
        if (eq == std::string::npos) continue;
        const std::string key = a.substr(0, eq+1);
        const std::string val = a.substr(eq+1);
        try {
            if (key == "--t0=") opt.t0 = std::stod(val);
            else if (key == "--t1=") opt.t1 = std::stod(val);
            else if (key == "--h-init=") opt.h_init = std::stod(val);
            else if (key == "--atol=") opt.atol = std::stod(val);
            else if (key == "--rtol=") opt.rtol = std::stod(val);
            else if (key == "--refine=") opt.refine = std::stoi(val);
            else if (key == "--maxsteps=") opt.max_steps = std::stoi(val);
            else if (key == "--normcontrol=") {
                const std::string v = val;
                opt.normControl = (v == "on" || v == "ON" || v == "true" || v == "1");
            } else if (key == "--times=") {
                // Parse comma-separated list of doubles
                opt.times.clear();
                size_t start = 0; while (start <= val.size()) {
                    size_t comma = val.find(',', start);
                    std::string tok = val.substr(start, (comma==std::string::npos)? std::string::npos : (comma-start));
                    if (!tok.empty()) opt.times.push_back(std::stod(tok));
                    if (comma == std::string::npos) break; else start = comma + 1;
                }
            }
        } catch (...) {
            std::cerr << "Invalid value for option: " << a << "\n";
            std::exit(1);
        }
    }
    if (opt.refine < 1) opt.refine = 1;
    return opt;
}


int main(int argc, char** argv) {
    // Example: y' = -y, y(0) = 1. Exact solution: y(t) = exp(-t)
    DerivFunc f = [](double /*t*/, const State& y, State& dydt) {
        dydt.resize(y.size());
        for (size_t i = 0; i < y.size(); ++i) {
            dydt[i] = -y[i];
        }
    };

    const State y0 = {1.0};
    Options defaults;
    defaults.normControl = true; // demo default on; Octave is off
    Options opt = parseArgs(argc, argv, defaults);

    try {
        // If explicit times provided, sample there; otherwise run default and optional refine
        if (!opt.times.empty()) {
            // Expect first time equals opt.t0; if not, we start at first time.
            auto traj = integrate_runge_kutta_dopri5_at_times(f, opt.t0, y0, opt.times, opt.h_init,
                                                              opt.atol, opt.rtol, opt.normControl, opt.max_steps);
            std::cout << "samples (first 5 of explicit times):\n";
            for (size_t i = 0; i < std::min<size_t>(traj.t.size(),5); ++i) {
                std::cout << std::fixed << std::setprecision(8) << traj.t[i] << ": " << traj.y[i][0] << "\n";
            }
            // Also print last sample
            std::cout << std::setprecision(16);
            std::cout << "y(" << traj.t.back() << ") = " << traj.y.back()[0] << "\n";
        } else {
            const State y1 = integrate_runge_kutta_dopri5(f, opt.t0, y0, opt.t1, opt.h_init, opt.atol,
                                                          opt.rtol, opt.normControl, opt.max_steps);
            const double exact = std::exp(-opt.t1);
            std::cout << std::setprecision(16);
            std::cout << "y(" << opt.t1 << ") = " << y1[0] << "\n";
            std::cout << "exact = " << exact << "\n";
            std::cout << "abs err = " << std::abs(y1[0] - exact) << "\n";

            if (opt.refine > 1) {
                auto traj = integrate_runge_kutta_dopri5_refined(f, opt.t0, y0, opt.t1, opt.h_init, opt.atol,
                                                                 opt.rtol, opt.normControl, opt.refine, opt.max_steps);
                std::cout << "samples (first 5):\n";
                for (size_t i = 0; i < std::min<size_t>(traj.t.size(),5); ++i) {
                    std::cout << std::fixed << std::setprecision(8) << traj.t[i] << ": " << traj.y[i][0] << "\n";
                }
                // Verify Octave's length relationship: len_R = R*len_1 - (R-1)
                auto coarse = integrate_runge_kutta_dopri5_refined(f, opt.t0, y0, opt.t1, opt.h_init, opt.atol,
                                                                   opt.rtol, opt.normControl, 1, opt.max_steps);
                const size_t lenR = traj.t.size();
                const size_t len1 = coarse.t.size();
                std::cout << "length refine = " << opt.refine << ": " << lenR << ", expected " <<
                    (opt.refine*len1 - (opt.refine-1)) << "\n";
            }
        }
        return 0;
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
}


static double two_norm_scaled(const State& err, const State& y, const State& y_next, double atol, double rtol) {
    // Matches two_norm_scaled with NormControl == true:
    // sc = max(AbsTol(:), RelTol * max(||y||_2, ||y_next||_2))
    // err_norm = ||err||_2 / sc
    if (err.size() != y.size() || err.size() != y_next.size()) {
        throw std::runtime_error("State size mismatch");
    }
    if (err.empty()) {
        return 0.0;
    }

    auto norm2 = [](const State& v) {
        long double s = 0.0L;
        for (double vi : v) s += static_cast<long double>(vi) * static_cast<long double>(vi);
        return std::sqrt(static_cast<double>(s));
    };

    const double sc = std::max(atol, rtol * std::max(norm2(y), norm2(y_next)));
    const double e2 = norm2(err);
    return (sc > 0.0) ? (e2 / sc) : e2;
}


// --- C API wrappers (extern "C") ---
extern "C" {
typedef void (*ode45_deriv_cb)(double t, const double* y, double* dydt, size_t n, void* ctx);

int ode45_integrate_final(double t0,
                          const double* y0,
                          size_t n,
                          double t1,
                          double h_init,
                          double atol,
                          double rtol,
                          int norm_control,
                          int max_steps,
                          ode45_deriv_cb cb,
                          void* ctx,
                          double* y_out) {
    try {
        State y0v(n);
        for (size_t i = 0; i < n; ++i) y0v[i] = y0[i];
        DerivFunc f = [cb, n, ctx](double t, const State& y, State& dydt) {
            dydt.resize(n);
            cb(t, y.data(), dydt.data(), n, ctx);
        };
        State y1 = integrate_runge_kutta_dopri5(f, t0, y0v, t1, h_init, atol, rtol, norm_control != 0, max_steps);
        for (size_t i = 0; i < n; ++i) y_out[i] = y1[i];
        return 0;
    } catch (...) {
        return -1;
    }
}


int ode45_integrate_times(double t0,
                          const double* y0,
                          size_t n,
                          const double* times,
                          size_t m,
                          double h_init,
                          double atol,
                          double rtol,
                          int normControl,
                          int max_steps,
                          ode45_deriv_cb cb,
                          void* ctx,
                          double* y_out) {
    try {
        State y0v(n);
        for (size_t i = 0; i < n; ++i) y0v[i] = y0[i];
        std::vector<double> tv(m);
        for (size_t i = 0; i < m; ++i) tv[i] = times[i];
        DerivFunc f = [cb, n, ctx](double t, const State& y, State& dydt) {
            dydt.resize(n);
            cb(t, y.data(), dydt.data(), n, ctx);
        };
        Trajectory traj = integrate_runge_kutta_dopri5_at_times(f, t0, y0v, tv, h_init, atol, rtol, normControl != 0, max_steps);
        if (traj.t.size() != m || traj.y.size() != m) return -2;
        // Row-major blocks: for each time i, copy n entries
        for (size_t i = 0; i < m; ++i) {
            const State& yi = traj.y[i];
            for (size_t j = 0; j < n; ++j) {
                y_out[i*n + j] = yi[j];
            }
        }
        return 0;
    } catch (...) {
        return -1;
    }
}
}

/*
y(5) = 0.00673794718173955
exact = 0.006737946999085467
abs err = 1.98847773490845e-011
samples (first 5):
0.00000000: 1.00000000
0.00927251: 0.99077034
0.01854503: 0.98162587
0.02781754: 0.97256580
0.03709006: 0.96358935
length refine=4: 349, expected 349
*/
相关推荐
张小姐的猫1 小时前
【AI大模型接入SDK】 —— 数据管理 & 与Session模块进行联动
数据结构·数据库·c++·人工智能·python·chatgpt
不穿鞋的懒羊羊1 小时前
dfs深度优先搜索
算法
是立不是利1 小时前
大文件导致请求被拒的连锁反应
开发语言·前端·javascript
OYYHXPJR1 小时前
【算法】区间重叠模板和矩形重叠模板
算法
shehuiyuelaiyuehao1 小时前
算法46,分治快排,排序数组
java·算法·排序算法·排序
東隅已逝,桑榆非晚2 小时前
vector(模拟实现)
c++·笔记·学习
野生技术架构师2 小时前
1200 道 JAVA 面试题(各大企业常见面试题及答案)
java·开发语言
远航计算机2 小时前
GEO 选题从哪来?用一份 Query 词库把一年内容排出来
大数据·人工智能·算法·aigc
宣宣猪的小花园.2 小时前
【机器学习】神经网络与表征:为什么深度学习能自动提取特征
人工智能·嵌入式硬件·算法·机器学习