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

cpp 复制代码
// ============================================================
//  隐式梯形法则求解中等刚性 ODE / DAE
//  编译:  g++ -O2 -std=c++17 ode23t.cpp -o ode23t
//  运行:  ./ode23t
// =============================================================
#include <vector>
#include <functional>
#include <cmath>
#include <algorithm>
#include <stdexcept>
#include <limits>
#include <iostream>
#include <iomanip>

// -------------------------------------------------------------
//  类型别名
// -------------------------------------------------------------
using Vec     = std::vector<double>;
using OdeFunc = std::function<Vec(double, const Vec&)>;
using JacFunc = std::function<void(double, const Vec&, std::vector<Vec>&)>;
using MassFunc= std::function<void(double, const Vec&, std::vector<Vec>&)>;

// -------------------------------------------------------------
//  选项结构
// -------------------------------------------------------------
struct Ode23tOptions {
    double RelTol       = 1e-3;
    Vec    AbsTol;                    // 长度 n,默认 1e-6
    double InitialStep  = 0.0;        // 0 表示自动选取
    double MaxStep      = 0.0;        // 0 表示 = (tf - t0)/10
    bool   NonNegative  = false;
    bool   UseMass      = false;      // 是否使用质量矩阵

    Ode23tOptions() {}
    explicit Ode23tOptions(int n) : AbsTol(n, 1e-6) {}
};

// -------------------------------------------------------------
//  结果结构
// -------------------------------------------------------------
struct Ode23tResult {
    Vec              t;
    std::vector<Vec> y;
};

// -------------------------------------------------------------
//  线性方程组求解(带部分主元的高斯-约当消元)
//  A 会被原地修改;返回解向量 x 满足 A x = b
// -------------------------------------------------------------
static Vec solve_linear(std::vector<Vec> A, Vec b)
{
    const int n = static_cast<int>(b.size());
    for (int col = 0; col < n; ++col) {
        // 选主元
        int piv = col;
        for (int r = col + 1; r < n; ++r)
            if (std::abs(A[r][col]) > std::abs(A[piv][col])) piv = r;
        std::swap(A[col], A[piv]);
        std::swap(b[col], b[piv]);

        const double d = A[col][col];
        if (std::abs(d) < 1e-300)
            throw std::runtime_error("ode23t: singular Jacobian in Newton step");

        for (int j = col; j < n; ++j) A[col][j] /= d;
        b[col] /= d;

        for (int r = 0; r < n; ++r) {
            if (r == col) continue;
            const double factor = A[r][col];
            if (factor == 0.0) continue;
            for (int j = col; j < n; ++j)
                A[r][j] -= factor * A[col][j];
            b[r] -= factor * b[col];
        }
    }
    return b;
}

// -------------------------------------------------------------
//  单步隐式梯形法
//    求 z 满足:  M (z - y_n) - (h/2) [ f(t_n, y_n) + f(t_n+h, z) ] = 0
//    若 M == nullptr 则退化为:  z - y_n - (h/2)[f_n + f_{n+1}] = 0
//    使用牛顿迭代 + 数值 / 解析 Jacobian
//  返回值: true 表示收敛
// -------------------------------------------------------------
static bool trapezoidal_step(
    const OdeFunc& f,
    double t, double h,
    const Vec& y_n,
    const std::vector<Vec>* M,     // 质量矩阵(行主序),可 nullptr
    const JacFunc* jac,            // 解析 Jacobian,可 nullptr(用数值)
    Vec& y_new,
    int    max_iter = 25,
    double newton_tol = 1e-10)
{
    const int n = static_cast<int>(y_n.size());
    const Vec f_n = f(t, y_n);

    // 初值猜测:显式 Euler
    y_new.resize(n);
    for (int i = 0; i < n; ++i)
        y_new[i] = y_n[i] + h * f_n[i];

    for (int iter = 0; iter < max_iter; ++iter) {
        const Vec f_new = f(t + h, y_new);

        // 计算残差 G(z)
        Vec G(n, 0.0);
        if (M) {
            for (int i = 0; i < n; ++i) {
                double sum = 0.0;
                for (int j = 0; j < n; ++j)
                    sum += (*M)[i][j] * (y_new[j] - y_n[j]);
                G[i] = sum - 0.5 * h * (f_n[i] + f_new[i]);
            }
        } else {
            for (int i = 0; i < n; ++i)
                G[i] = y_new[i] - y_n[i] - 0.5 * h * (f_n[i] + f_new[i]);
        }

        // 收敛判断(无穷范数 + 缩放)
        double gnorm = 0.0;
        for (int i = 0; i < n; ++i)
            gnorm = std::max(gnorm, std::abs(G[i]));
        if (gnorm < newton_tol) return true;

        // 组装 Jacobian J = df/dy (t+h, y_new)
        std::vector<Vec> J(n, Vec(n, 0.0));
        if (jac) {
            (*jac)(t + h, y_new, J);
        } else {
            // 数值 Jacobian(前向差分)
            const double eps = 1e-8;
            Vec yp = y_new;
            for (int j = 0; j < n; ++j) {
                const double save = yp[j];
                const double dj   = eps * std::max(1.0, std::abs(save));
                yp[j] = save + dj;
                const Vec fp = f(t + h, yp);
                for (int i = 0; i < n; ++i)
                    J[i][j] = (fp[i] - f_new[i]) / dj;
                yp[j] = save;
            }
        }

        // 牛顿系统: ( M - (h/2) J ) * delta = -G
        std::vector<Vec> A(n, Vec(n, 0.0));
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                const double base = M ? (*M)[i][j] : (i == j ? 1.0 : 0.0);
                A[i][j] = base - 0.5 * h * J[i][j];
            }
        }
        Vec rhs(n);
        for (int i = 0; i < n; ++i) rhs[i] = -G[i];

        const Vec delta = solve_linear(A, rhs);
        for (int i = 0; i < n; ++i) y_new[i] += delta[i];
    }
    return false;  // 未收敛
}

// -------------------------------------------------------------
//  误差估计:步长加倍法
//    比较 1 步 h 与 2 步 h/2 的结果
//    返回误差范数估计(若失败返回 +inf),并输出更精确的 y_out
// -------------------------------------------------------------
static double estimate_error(
    const OdeFunc& f,
    double t, double h,
    const Vec& y_n,
    const std::vector<Vec>* M,
    const JacFunc* jac,
    Vec& y_out)
{
    const int n = static_cast<int>(y_n.size());

    // 一整步
    Vec y_full;
    if (!trapezoidal_step(f, t, h, y_n, M, jac, y_full))
        return std::numeric_limits<double>::infinity();

    // 两步 h/2
    Vec y_half, y_half2;
    if (!trapezoidal_step(f, t, h * 0.5, y_n,     M, jac, y_half) ||
        !trapezoidal_step(f, t + h * 0.5, h * 0.5, y_half, M, jac, y_half2))
        return std::numeric_limits<double>::infinity();

    // 局部误差 = |y_full - y_half2| / (2^2 - 1)
    double err = 0.0;
    for (int i = 0; i < n; ++i)
        err = std::max(err, std::abs(y_full[i] - y_half2[i]) / 3.0);

    // 步长加倍法的更精确解:采用两半步行结果
    y_out = std::move(y_half2);
    return err;
}

// -------------------------------------------------------------
//  ode23t 自适应步长求解主函数
//  - 误差估计:步长加倍法(estimate_error)
//  - 初始步长:Hairer 公式(常数 0.01、阈值 1e-5)
//  - 步长控制:I 控制器(safety=0.9, max_scale=5.0, min_scale=0.2)
//  - 最大步长:opts.MaxStep,0 表示 (tf-t0)/10
// -------------------------------------------------------------
static Ode23tResult ode23t(
    const OdeFunc& f,
    const JacFunc* jac,              // 解析 Jacobian,可 nullptr
    double t0, double tf,
    const Vec& y0,
    const Ode23tOptions& opts,
    const std::vector<Vec>* M = nullptr)   // 质量矩阵,可 nullptr
{
    const int n = static_cast<int>(y0.size());

    // 容差向量(AbsTol 长度不足时用 1e-6 填充)
    Vec atol = opts.AbsTol;
    if (atol.size() != static_cast<size_t>(n)) atol.assign(n, 1e-6);
    const double rtol = opts.RelTol;

    const double span = tf - t0;
    const double hmax = opts.MaxStep > 0.0 ? opts.MaxStep : span / 10.0;

    Ode23tResult res;
    res.t.reserve(400);
    res.y.reserve(400);
    res.t.push_back(t0);
    res.y.push_back(y0);
    if (span <= 0.0) return res;

    // ---- 初始步长(Hairer 公式)----
    double h;
    if (opts.InitialStep > 0.0) {
        h = std::min(opts.InitialStep, hmax);
    } else {
        const Vec f0 = f(t0, y0);
        double d0 = 0.0, d1 = 0.0;
        for (int i = 0; i < n; ++i) {
            const double w = atol[i] + rtol * std::abs(y0[i]);
            d0 = std::max(d0, std::abs(y0[i]) / w);
            d1 = std::max(d1, std::abs(f0[i]) / w);
        }
        if (d0 < 1e-5 || d1 < 1e-5) h = 1e-6;
        else                        h = 0.01 * d0 / d1;
        if (h > hmax) h = hmax;
    }
    if (!(h > 0.0) || !std::isfinite(h)) h = 1e-6;

    // ---- 步长控制参数 ----
    const double safety    = 0.9;
    const double max_scale = 5.0;
    const double min_scale = 0.2;

    double t = t0;
    Vec    y = y0;
    double prev_err = 1.0;   // 上一步缩放误差(PI 项记忆)

    while (t < tf) {
        // 限制步长不超过 MaxStep,且不越过终点
        if (h > hmax) h = hmax;
        if (t + h > tf) h = tf - t;
        if (!(h > 0.0)) break;

        // 试探一步,得到误差估计与更精确的候选解
        Vec y_next;
        const double err = estimate_error(f, t, h, y, M, jac, y_next);

        if (!std::isfinite(err)) {
            // 牛顿迭代未收敛:缩小步长重试
            h *= 0.5;
            if (h < hmax * 1e-12) break;
            continue;
        }

        // 加权误差范数(无穷范数类):
        //   w_i = AbsTol_i + RelTol * max(|y_i|, |y_next_i|)
        //   err_scaled = err / w,取整体代表权重,避免单一分量权值过小
        double w_norm = 0.0, w_min = std::numeric_limits<double>::max();
        double maxy = 0.0;
        for (int i = 0; i < n; ++i) {
            const double wi = atol[i] + rtol * std::max(std::abs(y[i]), std::abs(y_next[i]));
            w_norm += wi * wi;
            w_min   = std::min(w_min, wi);
            maxy    = std::max(maxy, std::abs(y_next[i]));
        }
        // 用误差主导分量的权重做缩放(此处以"LF 范数 / RMS 权重"折中)
        const double w_rms = std::sqrt(w_norm / n);
        const double err_scaled_raw = err / w_rms;

        // 接受 / 拒绝
        if (err_scaled_raw > 1.0) {
            // 拒绝:缩小步长重试(极限缩放 min_scale=0.2 之外再加安全系数)
            double scale = safety * std::pow(err_scaled_raw, -1.0 / 3.0);
            scale = std::min(scale, 0.5 / std::pow(err_scaled_raw, 1.0 / 3.0));
            if (scale < 1e-3) scale = 1e-3;
            h *= scale;
            if (h < hmax * 1e-12) break;
            continue;
        }

        // 接受该步
        res.t.push_back(t + h);
        res.y.push_back(y_next);
        t += h;
        y = std::move(y_next);

        // 更新下一候选步长(PI 型控制器)
        double scale = safety * std::pow(err_scaled_raw, -1.0 / 3.0);
        // PI 修正项:(prev/curr)^(1/6) 的常见形式,温和起见取 1/8
        scale *= std::pow(prev_err / std::max(err_scaled_raw, 1e-300), 1.0 / 8.0);
        if (scale > max_scale) scale = max_scale;
        if (scale < min_scale) scale = min_scale;
        h *= scale;
        prev_err = err_scaled_raw;

        (void)maxy;   // 保留,便于后续调整权重策略
    }

    return res;
}

// -------------------------------------------------------------
//  Van der Pol 演示问题
//    y1' = y2
//    y2' = mu * (1 - y1^2) * y2 - y1
// -------------------------------------------------------------
static Vec vdp_rhs(double /*t*/, const Vec& y)
{
    const double mu = 10.0;
    Vec f(2);
    f[0] = y[1];
    f[1] = mu * (1.0 - y[0] * y[0]) * y[1] - y[0];
    return f;
}

int main()
{
    using std::cout;
    using std::fixed;
    using std::setprecision;
    using std::setw;

    // ---- 标准演示配置:mu = 10, tspan [0, 20], RelTol = 1e-4, MaxStep = 0.5 ----
    Ode23tOptions opts(2);
    opts.RelTol  = 1e-4;
    opts.AbsTol  = { 1e-6, 1e-6 };
    opts.MaxStep = 0.5;

    const double t0 = 0.0, tf = 20.0, mu = 10.0;
    const Vec y0 = { 2.0, 0.0 };

    const Ode23tResult result = ode23t(vdp_rhs, nullptr, t0, tf, y0, opts);

    const int steps = static_cast<int>(result.t.size()) - 1;

    cout << "============================================\n";
    cout << " ode23t (implicit trapezoidal) demo\n";
    cout << " Problem : Van der Pol, mu = " << fixed << setprecision(6) << mu << "\n";
    cout << " tspan   : [" << t0 << ", " << tf << "]\n";
    cout << " RelTol  : " << opts.RelTol << "\n";
    cout << "============================================\n";
    cout << " Steps taken : " << steps << "\n";
    cout << " Final t     : " << fixed << setprecision(6) << result.t.back() << "\n";
    cout << " Final y     : (" << result.y.back()[0] << ", " << result.y.back()[1] << ")\n";
    cout << "--------------------------------------------\n";
    cout << " First few steps:\n";
    cout << "   idx        t            y1           y2\n";
    const int show = std::min(steps, 9);
    for (int i = 0; i <= show; ++i)
        cout << setw(5) << i << "  " << fixed << setprecision(6)
             << setw(10) << result.t[i] << "   "
             << setw(10) << result.y[i][0] << "   "
             << setw(10) << result.y[i][1] << "\n";

    return 0;
}
相关推荐
wuminyu1 小时前
Markword在紧凑对象头上的实现原理剖析
java·linux·c语言·jvm·c++
萧西待水1 小时前
奥赛一本通 1451 棋盘游戏
算法·宽度优先
君顾11 小时前
24小时自助健身房系统开发实战与完整指南
java·开发语言·健身房
Niuguangshuo1 小时前
论文解读:Paraformer,非自回归中文 ASR 的并行 Transformer
算法·音视频·语音识别
鹿角片ljp1 小时前
LeetCode 78:子集|回溯、选与不选、递归和path快照
java·数据结构·算法
hansang_IR2 小时前
【代数与组合数学 | 那忘算 5】生成函数 & 例题 & 卷积
c++·算法·多项式·生成函数·母函数
Zane19942 小时前
快速排序凭什么叫"快"排序?平均O(nlogn)背后,藏着一个能让它退化成O(n²)的选择
算法
无忧.芙桃2 小时前
数据结构之排序算法(上):从评价指标到插入、希尔、选择与堆排序
c语言·c++·排序算法
syagain_zsx2 小时前
算法基础篇 · 02 高精度(C++ 题解)
开发语言·c++·学习·高精度