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

cpp 复制代码
// ============================================================================
//  TR-BDF2 ODE Solver (类似 MATLAB 的 ode23tb)
//  单文件实现,包含示例主函数
//  编译: g++ -O2 -std=c++17 trbdf2.cpp -o trbdf2
// ============================================================================

#include <chrono>
#include <iostream>
#include <iomanip>
#include <vector>
#include <cmath>
#include <functional>
#include <algorithm>
#include <limits>
#include <stdexcept>

using Vector = std::vector<double>;
using Matrix = std::vector<Vector>;   // 行优先存储

// ============================================================================
//  线性方程组求解 (带部分主元的高斯消元)
// ============================================================================
static Vector solveLinearSystem(Matrix A, Vector b) {
    const size_t n = b.size();
    for (size_t k = 0; k < n; ++k) {
        // 部分主元
        size_t pivot = k;
        double maxv = std::abs(A[k][k]);
        for (size_t i = k + 1; i < n; ++i) {
            if (std::abs(A[i][k]) > maxv) { maxv = std::abs(A[i][k]); pivot = i; }
        }
        if (maxv < 1e-300)
            throw std::runtime_error("线性方程组奇异");
        if (pivot != k) {
            std::swap(A[k], A[pivot]);
            std::swap(b[k], b[pivot]);
        }
        // 消元
        for (size_t i = k + 1; i < n; ++i) {
            double factor = A[i][k] / A[k][k];
            A[i][k] = 0.0;
            for (size_t j = k + 1; j < n; ++j)
                A[i][j] -= factor * A[k][j];
            b[i] -= factor * b[k];
        }
    }
    // 回代
    Vector x(n);
    for (int i = static_cast<int>(n) - 1; i >= 0; --i) {
        double s = b[i];
        for (size_t j = i + 1; j < n; ++j)
            s -= A[i][j] * x[j];
        x[i] = s / A[i][i];
    }
    return x;
}

// ============================================================================
//  数值雅可比: J(i,j) = ∂f_i / ∂y_j
// ============================================================================
static Matrix numericalJacobian(
    const std::function<Vector(double, const Vector&)>& f,
    double t, const Vector& y, const Vector& f0)
{
    const size_t n = y.size();
    Matrix J(n, Vector(n, 0.0));
    Vector yp = y;
    for (size_t j = 0; j < n; ++j) {
        double eps = std::sqrt(std::numeric_limits<double>::epsilon())
                     * std::max(1.0, std::abs(y[j]));
        yp[j] += eps;
        Vector fp = f(t, yp);
        for (size_t i = 0; i < n; ++i)
            J[i][j] = (fp[i] - f0[i]) / eps;
        yp[j] = y[j];
    }
    return J;
}

// ============================================================================
//  TR-BDF2 求解器
// ============================================================================
class TRBDF2Solver {
public:
    using OdeFunction = std::function<Vector(double, const Vector&)>;

    explicit TRBDF2Solver(OdeFunction ode) : f_(std::move(ode)) {
        // γ = 2 - √2  ≈ 0.5857864376
        gamma_ = 2.0 - std::sqrt(2.0);

        // 梯形法则系数: y_g = y_n + h*γ/2 * (f_n + f_g)
        trap_factor_ = 0.5 * gamma_;

        // BDF2 系数
        // y_{n+1} = c1*y_g - c2*y_n + c3*h*f_{n+1}
        double d = gamma_ * (2.0 - gamma_);
        c1_ = 1.0 / d;
        c2_ = (1.0 - gamma_) * (1.0 - gamma_) / d;
        c3_ = (1.0 - gamma_) / (2.0 - gamma_);

        // 误差估计系数: e = err_coef * h * (f_n - 2*f_g + f_{n+1})
        err_coef_ = (2.0 - gamma_) / (6.0 - 3.0 * gamma_);
    }

    void setTolerances(double rtol, double atol) { rtol_ = rtol; atol_ = atol; }
    void setInitialStep(double h0) { h_init_ = h0; }
    void setMinMaxStep(double hmin, double hmax) { h_min_ = hmin; h_max_ = hmax; }

    // 返回 { t 数组, y 数组 }
    std::pair<Vector, std::vector<Vector>> solve(
        double t0, double tf, const Vector& y0)
    {
        if (tf <= t0) throw std::runtime_error("tspan 必须满足 tf > t0");

        double t = t0;
        Vector y = y0;
        Vector t_out; t_out.push_back(t);
        std::vector<Vector> y_out; y_out.push_back(y);

        // 初始步长
        double h = (h_init_ > 0.0) ? h_init_ : estimateInitialStep(t0, y0, tf);
        h = std::min(std::max(h, h_min_), h_max_);

        // 保存上一成功步的导数,避免重复计算
        Vector f_cur = f_(t, y);
        int reject_count = 0;

        while (t < tf - 1e-14 * std::abs(tf)) {
            if (t + h > tf) h = tf - t;

            StepResult sr = doStep(t, y, f_cur, h);

            if (sr.success) {
                t += h;
                y = sr.y_new;
                f_cur = sr.f_new;
                t_out.push_back(t);
                y_out.push_back(y);
                h = adjustStepSize(h, sr.error_norm);
                reject_count = 0;
            } else {
                h *= 0.5;
                ++reject_count;
                if (h < h_min_) {
                    std::cerr << "警告: 步长降到最小值仍无法收敛,提前终止。\n";
                    break;
                }
                if (reject_count > 50) {
                    std::cerr << "警告: 步长反复被拒绝,提前终止。\n";
                    break;
                }
            }
        }

        return {t_out, y_out};
    }

private:
    OdeFunction f_;
    double gamma_;
    double trap_factor_;
    double c1_, c2_, c3_;
    double err_coef_;
    double rtol_ = 1e-3;
    double atol_ = 1e-6;
    double h_init_ = 0.0;
    double h_min_ = 1e-12;
    double h_max_ = 1.0;
    int    max_newton_iter_ = 12;
    double newton_rtol_ = 1e-10;
    double newton_atol_ = 1e-12;

    struct StepResult {
        bool success = false;
        Vector y_new;
        Vector f_new;
        double error_norm = 1e30;
    };

    // ---------- 初始步长估计 ----------
    double estimateInitialStep(double t, const Vector& y, double tf) {
        Vector f0 = f_(t, y);
        double d0 = 0.0, d1 = 0.0;
        for (size_t i = 0; i < y.size(); ++i) {
            double sc = atol_ + std::abs(y[i]) * rtol_;
            d0 += (y[i] / sc) * (y[i] / sc);
            d1 += (f0[i] / sc) * (f0[i] / sc);
        }
        d0 = std::sqrt(d0 / y.size());
        d1 = std::sqrt(d1 / y.size());
        double h;
        if (d0 < 1e-5 || d1 < 1e-5) h = 1e-6;
        else                          h = 0.01 * (d0 / d1);
        h = std::min(h, tf - t);
        return std::max(h, h_min_);
    }

    // ---------- 牛顿迭代辅助 ----------
    // 用简化牛顿法求解 G(z) = 0,其中 G(z) = z - RHS - a*h*f(t_z, z)
    // 迭代矩阵 M = I - a*h*J
    bool newtonSolve(
        double t_z, const Vector& rhs, double a_h,
        Vector& z, Vector& f_z)
    {
        const size_t n = z.size();
        for (int iter = 0; iter < max_newton_iter_; ++iter) {
            f_z = f_(t_z, z);

            // 残差 r = z - rhs - a_h * f_z
            Vector r(n);
            double rnorm = 0.0, znorm = 0.0;
            for (size_t i = 0; i < n; ++i) {
                r[i] = z[i] - rhs[i] - a_h * f_z[i];
                double sc = newton_atol_ + newton_rtol_ * std::max(std::abs(z[i]), std::abs(rhs[i]));
                rnorm += (r[i] / sc) * (r[i] / sc);
                znorm += (z[i] / sc) * (z[i] / sc);
            }
            rnorm = std::sqrt(rnorm / n);
            if (rnorm < 1.0) return true;   // 收敛

            // 雅可比
            Matrix J = numericalJacobian(f_, t_z, z, f_z);

            // 构造 M = I - a_h*J
            Matrix M(n, Vector(n));
            for (size_t i = 0; i < n; ++i)
                for (size_t j = 0; j < n; ++j)
                    M[i][j] = ((i == j) ? 1.0 : 0.0) - a_h * J[i][j];

            // 解 M*delta = -r
            Vector negr(n);
            for (size_t i = 0; i < n; ++i) negr[i] = -r[i];

            Vector delta;
            try {
                delta = solveLinearSystem(M, negr);
            } catch (...) {
                return false;
            }

            // 阻尼: 限制更新幅度
            double dnorm = 0.0;
            for (size_t i = 0; i < n; ++i) dnorm += delta[i] * delta[i];
            dnorm = std::sqrt(dnorm);
            double max_upd = 10.0 * (1.0 + std::sqrt(znorm / n));
            if (dnorm > max_upd && dnorm > 0.0) {
                double s = max_upd / dnorm;
                for (size_t i = 0; i < n; ++i) delta[i] *= s;
            }
            for (size_t i = 0; i < n; ++i) z[i] += delta[i];
        }
        return false;
    }

    // ---------- 单步 TR-BDF2 ----------
    StepResult doStep(double t, const Vector& y_n, const Vector& f_n, double h) {
        const size_t n = y_n.size();
        StepResult sr;

        // ===== 阶段 1: 梯形规则 =====
        // y_g = y_n + (γh/2) * (f_n + f_g)
        double t_g = t + gamma_ * h;
        Vector y_g(n);
        for (size_t i = 0; i < n; ++i)
            y_g[i] = y_n[i] + trap_factor_ * h * f_n[i];  // 显式预测

        Vector rhs_g(n);
        for (size_t i = 0; i < n; ++i)
            rhs_g[i] = y_n[i] + trap_factor_ * h * f_n[i];

        Vector f_g;
        if (!newtonSolve(t_g, rhs_g, trap_factor_ * h, y_g, f_g)) {
            return sr;   // 失败
        }

        // ===== 阶段 2: BDF2 =====
        // y_{n+1} = c1*y_g - c2*y_n + c3*h*f_{n+1}
        double t_next = t + h;
        Vector y_next(n);
        Vector rhs_next(n);
        for (size_t i = 0; i < n; ++i) {
            rhs_next[i] = c1_ * y_g[i] - c2_ * y_n[i];
            y_next[i]  = rhs_next[i];  // 初值
        }

        Vector f_next;
        if (!newtonSolve(t_next, rhs_next, c3_ * h, y_next, f_next)) {
            return sr;   // 失败
        }

        // ===== 误差估计 =====
        // e_i = err_coef * h * (f_n - 2*f_g + f_{n+1})
        double err_norm = 0.0;
        for (size_t i = 0; i < n; ++i) {
            double e = err_coef_ * h * (f_n[i] - 2.0 * f_g[i] + f_next[i]);
            double sc = atol_ + rtol_ * std::max(std::abs(y_n[i]), std::abs(y_next[i]));
            err_norm += (e / sc) * (e / sc);
        }
        err_norm = std::sqrt(err_norm / n);

        sr.success    = true;
        sr.y_new      = std::move(y_next);
        sr.f_new      = std::move(f_next);
        sr.error_norm = err_norm;
        return sr;
    }

    // ---------- 步长调整 ----------
    double adjustStepSize(double h, double err) {
        const double safety  = 0.9;
        const double min_fac = 0.2;
        const double max_fac = 5.0;
        double fac;
        if (err <= 1e-16) fac = max_fac;
        else              fac = safety * std::pow(1.0 / err, 1.0 / 3.0);
        fac = std::min(std::max(fac, min_fac), max_fac);
        double h_new = h * fac;
        return std::min(std::max(h_new, h_min_), h_max_);
    }
};

// ============================================================================
//  示例: 求解范德波尔方程 (刚性问题)
//    y1' = y2
//    y2' = μ*(1 - y1^2)*y2 - y1,   μ = 1000
// ============================================================================
int main() {
    const double mu = 1000.0;
    auto vdp = [mu](double /*t*/, const Vector& y) -> Vector {
        Vector d(2);
        d[0] = y[1];
        d[1] = mu * (1.0 - y[0] * y[0]) * y[1] - y[0];
        return d;
    };

    TRBDF2Solver solver(vdp);
    solver.setTolerances(1e-4, 1e-7);
    solver.setMinMaxStep(1e-12, 0.1);

    Vector y0 = {2.0, 0.0};
    double t0 = 0.0;
    double tf = 3000.0;

    auto t_start = std::chrono::high_resolution_clock::now();
    auto [t_out, y_out] = solver.solve(t0, tf, y0);
    auto t_end = std::chrono::high_resolution_clock::now();

    double elapsed = std::chrono::duration<double>(t_end - t_start).count();

    std::cout << std::scientific << std::setprecision(6);
    std::cout << "==================== TR-BDF2 求解结果 ====================\n";
    std::cout << "时间区间: [" << t0 << ", " << tf << "]\n";
    std::cout << "初值    : y1=" << y0[0] << ", y2=" << y0[1] << "\n";
    std::cout << "容差    : rtol=1e-4, atol=1e-7\n";
    std::cout << "----------------------------------------------------------\n";
    std::cout << "输出点数  : " << t_out.size() << "\n";
    std::cout << "耗时      : " << elapsed << " 秒\n";
    std::cout << "最终解    : t = " << t_out.back()
              << ", y1 = " << y_out.back()[0]
              << ", y2 = " << y_out.back()[1] << "\n";
    std::cout << "==========================================================\n";

    // 打印前 5 个和最后 5 个输出点
    std::cout << "\n前 5 个输出点:\n";
    std::cout << std::setw(18) << "t"
              << std::setw(20) << "y1"
              << std::setw(20) << "y2" << "\n";
    size_t nshow = std::min<size_t>(5, t_out.size());
    for (size_t i = 0; i < nshow; ++i)
        std::cout << std::setw(18) << t_out[i]
                  << std::setw(20) << y_out[i][0]
                  << std::setw(20) << y_out[i][1] << "\n";

    std::cout << "\n最后 5 个输出点:\n";
    std::cout << std::setw(18) << "t"
              << std::setw(20) << "y1"
              << std::setw(20) << "y2" << "\n";
    size_t start = (t_out.size() > 5) ? t_out.size() - 5 : 0;
    for (size_t i = start; i < t_out.size(); ++i)
        std::cout << std::setw(18) << t_out[i]
                  << std::setw(20) << y_out[i][0]
                  << std::setw(20) << y_out[i][1] << "\n";

    return 0;
}
相关推荐
2601_966949651 小时前
为什么量化回测中 for 循环比 DataFrame 慢三个数量级?从向量化原理讲起
开发语言·python·pandas·股票数据·quantdash
S_WEAN1 小时前
C++STL - string类的模拟实现
c++
S_WEAN1 小时前
【C++】STL - string类
c++
我是章汕呐1 小时前
地级市能源消耗量及消耗强度数据【2006-2023年】平衡面板
人工智能·经验分享·算法·回归
charliejohn2 小时前
计算机考研 408 数据结构 堆的插入与删除 堆排序
算法
码匠许师傅2 小时前
【C++三方组件】toml++:人性化的配置文件格式
c++
老鱼说AI2 小时前
从点积到希尔伯特空间:向量内积的几何本质与大模型相似度度量
人工智能·深度学习·线性代数·算法·机器学习·数学建模
边境悍匪2 小时前
蜗牛学苑 Java 智能体学习 Day45|Knife4j+SpringBoot3、书城项目整合 MyBatis 思维导图复盘
java·开发语言·vue.js·学习·spring