数值方法:Euler法、梯形法、RK2、RK4求解ODE的MATLAB实现

一次挺满意的小组合作,记录一下使用的代码和过程。事先声明代码来自chatGPT,由于CSDN上没有找到类似的代码,这也是为以后或许要用到的同学留个资源吧。虽然说代码不是亲自写的,理解还是很容易,顺便注明一些我们走的弯路。

cut-off

数值方法:

欧拉法:

function [t, y] = euler(f, y0, t0, tf, h)
% Solve an ODE using the Euler method.

% Inputs:
% f: function handle of the ODE
% y0: initial value of y
% t0: initial time
% tf: final time
% h: step size

% Outputs:
% t: array of time values
% y: array of solution values

t = t0:h:tf;
y = zeros(size(t));
y(1) = y0;
for i = 1:length(t)-1
    y(i+1) = y(i) + h*f(t(i), y(i));
end

很容易理解,顺带提一句chatGPT的代码注释真的很强大。

欧拉法调用方法:

(我们作业内的ODE是y'=10y(1-y),所以下面的都以这个ODE为例,再顺带提一句,使用matlab解ODE用dsolve就好。)

f = @(t,y) 10*y*(1-y);
y0 = 0.01;
t0 = 0;
tf = 1;
h = 0.01;
[t, y] = euler(f, y0, t0, tf, h);

plot(t, y, '-o');
xlabel('t');
ylabel('y(t)');
title('Euler method solution for dy/dt = 10*y*(1-y)');

本来想解释一下,但真没啥解释的...定义函数,初值,区间,步长,调用函数,画图,坐标轴和图名,就这些。

欧拉法输出图像:

欧拉法图像

梯形法:

英文是Trapezoidal method,tra pe zo i dal,梯形。

function [t,y] = trapezoidal(f, tspan, y0, h)
    % 变量初始化
    t0 = tspan(1);
    tf = tspan(2);
    t = t0:h:tf;
    y = zeros(length(y0), length(t));
    y(:,1) = y0;

    % 梯形法迭代过程
    for i = 1:(length(t)-1)
        y(:,i+1) = y(:,i) + h/2*(f(t(i), y(:,i)) + f(t(i+1), y(:,i)+h*f(t(i), y(:,i))));
    end
end

这次tspan是区间,不懂就看下面输入案例。

梯形法调用方法:

f = @(t, y) 10*y*(1-y);      % 定义方程
tspan = [0, 1];       % 时间区间
y0 = 0.01;               % 初始值
h = 0.01;              % 步长
[t,y] = trapezoidal(f, tspan, y0, h);  % 调用函数求解
plot(t, y);           % 绘制函数图像
xlabel('t');
ylabel('y');
title('Trapezoidal Method');

梯形法输出图像:

梯形法图像

RK2:

function [t, y] = rk2(f, tspan, y0, h)
% 使用RK2方法求解一阶常微分方程组 dy/dt = f(t, y)
% 输入参数:
%   - dydt: 函数句柄,表示方程组 dy/dt = f(t, y) 中的 f(t, y)
%   - tspan: 时间区间,形如 [t0, tf]
%   - y0: 初始值数组,长度应与输出的 y 数组长度相同
%   - h: 步长
% 输出参数:
%   - t: 时间数组,包含所有求解点的时间戳
%   - y: 解数组,包含所有求解点的值
% 初始化数组
t = tspan(1):h:tspan(2);
n = length(t);
y = zeros(length(y0), n);
y(:, 1) = y0;
for i = 1:n-1
    k1 = h * f(t(i), y(:, i));
    k2 = h * f(t(i)+h, y(:, i)+k1);
    y(:, i+1) = y(:, i) + 0.5 * (k1 + k2);
end

RK2调用方法:

f = @(t, y) 10*y*(1-y);  % 定义方程
tspan = [0, 1];       % 时间区间
y0 = 0.01;               % 初始值
h = 0.01;              % 步长
[t,y] = rk2(f, tspan, y0, h);  % 调用函数求解
plot(t, y);           % 绘制函数图像
xlabel('t');
ylabel('y');
title('Runge-Kutta 2 Method');

图不放了,同上(虽然说调用方法也类似,我还是把饼递到嘴边吧)。

RK4:

function [t, y] = rk4(f, tspan, y0, h)
    % 变量初始化
    t0 = tspan(1);
    tf = tspan(2);
    t = t0:h:tf;
    y = zeros(length(y0), length(t));
    y(:,1) = y0;

    % 龙格库塔迭代过程
    for i = 1:(length(t)-1)
        k1 = f(t(i), y(:,i));
        k2 = f(t(i)+h/2, y(:,i)+h/2*k1);
        k3 = f(t(i)+h/2, y(:,i)+h/2*k2);
        k4 = f(t(i)+h, y(:,i)+h*k3);
        y(:,i+1) = y(:,i) + h/6*(k1 + 2*k2 + 2*k3 + k4);
    end
end

RK4调用方法:

f = @(t, y) 10*y*(1-y);  % 定义方程
tspan = [0, 1];       % 时间区间
y0 = 0.01;               % 初始值
h = 0.01;              % 步长
[t,y] = rk4(f, tspan, y0, h);  % 调用函数求解
plot(t, y);           % 绘制函数图像
xlabel('t');
ylabel('y');
title('Runge-Kutta Method');

cut-off

误差分析:

我们做的误差分析是得出原函数在t=1时的值和数值方法逼近的值差的绝对值再取对数,听着有点麻烦啊,写成式子应该是这样:\text{error}=\log{|y(1)-y_{m}(1)|}。这里我们将步长设为自变量,从10的-1到-6次方,误差为因变量,这样取对数后,正常的误差分析结果应该是几条斜率不同的直线。

代码:

% Defining Differential Equations and Primitive Functions
f = @(t, y) 10*y*(1-y);%ODE方程
y_exact = @(t) 1/(1 + 99*exp(-10*t));%原函数,可以用dsolve求解

% Define step size and interval
h_values = [0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001];%步长
tspan = [0, 1];%区间

% Initialization
n_values = length(h_values);
error_euler = zeros(n_values, 1);
error_trapezoidal = zeros(n_values, 1);
error_rk4 = zeros(n_values, 1);
error_rk2 = zeros(n_values, 1);
error_exact = zeros(n_values, 1);

% Calculate the error between the Euler method and the original function at t=1
for i = 1:n_values
    h = h_values(i);
    [t_euler, y_euler] = euler(f, [0.01], 0, 1, h);
    y_exact_value = y_exact(1);
    error_euler(i) = abs(y_euler(end) - y_exact_value);
    error_exact(i) = abs(y_exact_value - y_exact(t_euler(end)));
end

% Calculate the error between the trapezoidal method and the original function at t=1
for i = 1:n_values
    h = h_values(i);
    [t_trapezoidal, y_trapezoidal] = trapezoidal(f, tspan, [0.01], h);
    y_exact_value = y_exact(1);
    error_trapezoidal(i) = abs(y_trapezoidal(end) - y_exact_value);
    error_exact(i) = abs(y_exact_value - y_exact(t_trapezoidal(end)));
end

% Calculate the error between the RK4 method and the original function at t=1
for i = 1:n_values
    h = h_values(i);
    [t_rk4, y_rk4] = rk4(f, tspan, [0.01], h);
    y_exact_value = y_exact(1);
    error_rk4(i) = abs(y_rk4(end) - y_exact_value);
    error_exact(i) = abs(y_exact_value - y_exact(t_rk4(end)));
end

% Calculate the error between the RK2 method and the original function at t=1
for i = 1:n_values
    h = h_values(i);
    [t_rk2, y_rk2] = rk2(f, tspan, [0.01], h);
    y_exact_value = y_exact(1);
    error_rk2(i) = abs(y_rk2(end) - y_exact_value);
    error_exact(i) = abs(y_exact_value - y_exact(t_rk2(end)));
end

% Draw error image
log_h_values = log10(h_values);
log_error_euler = log10(error_euler);
log_error_rk4 = log10(error_rk4);
log_error_trapezoidal = log10(error_trapezoidal);
log_error_rk2 = log10(error_rk2);
log_error_exact = log10(error_exact);
plot(log_h_values, log_error_rk2, 'o-', log_h_values, log_error_euler, 's-', log_h_values, log_error_rk4, 'x-', log_h_values, log_error_trapezoidal, '+-');
set(gca,'XDir','reverse'); %Set the x-axis direction to reverse
xlabel('log(h)');
ylabel('log(error)');
title('Error image');
legend('RK2', 'Euler','RK4','Trapezoidal');

误差分析图像:

误差分析图像

小问答:这里直线的斜率分别为1,2,4,原因是什么?

另外,效果最好的是RK4,我一开始疑惑为什么RK4方法会触底反弹,chatGPT的解释是:

使用RK4方法拟合函数时,有时在步长减小到一个值时,随着步长继续减小,误差反而增大的原因:在RK4方法中,步长的大小会影响拟合精度,通常情况下,减小步长可以提升拟合的精度。然而,当步长继续减小时,误差反而增大的原因在于舍入误差的影响。由于计算机存储浮点数是有限精度的,当步长较小时,函数的微小变化可能会因为精度不足而被忽略,从而导致误差逐渐累积,最终使得计算结果和真实结果之间的差距增大。

我也询问了为什么欧拉法和RK2为什么不会如此,chatGPT解释是:

欧拉法和RK2方法在同样的函数上进行数值积分时,没有出现这种问题的原因是因为它们的计算精度相对较低,误差随着步长的减小而不是增大。欧拉法和RK2方法都是数值积分常用的一阶显式方法,与RK4方法相比,它们的计算复杂度较低,每次迭代只需要使用一次函数导数计算即可。这种简单的计算方式使得它们在处理简单的函数时更加高效。然而,这种高效性也带来了精度的牺牲。相较于RK4方法的四阶精度,欧拉法只有一阶,而RK2方法只有二阶。因此,在进行数值积分时,它们的误差通常较大。

一些踩坑:

之后,我们换了一个新的函数,区间设为0到60。我们一开始想用函数中间的值来对比误差,即y(30),结果搞出一个四不像图像,具体的就不放了。后来在做报告前,我们才发现在误差分析的代码中,我们用了例如y_exact(t_euler(end))的函数。我们将步长,初值,区间和取值全都换为了新的函数,但就是没发现这个...最后作差是将函数中间数值方法的值和原函数末端值比较,当然完全不对了。还是没有完全理解如此简单的代码啊。顺带一提这些迭代的方法,比较函数末端(end)值其实应该是最能显示误差的。

最后看看我们对另一个函数做的误差分析图吧:

f = @(t, y) -0.051*(y-24);

注意两个误差分析图的斜率哦!

QR Code
微信扫一扫,欢迎咨询~

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 155-2731-8020
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

手机不正确

公司不为空