<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>自适应步长 on Keqi的博客</title><link>https://yekq.top/tags/%E8%87%AA%E9%80%82%E5%BA%94%E6%AD%A5%E9%95%BF/</link><description>Recent content in 自适应步长 on Keqi的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><managingEditor>plloningye@gmail.com (Keqi Ye)</managingEditor><webMaster>plloningye@gmail.com (Keqi Ye)</webMaster><copyright>Keqi Ye</copyright><lastBuildDate>Fri, 24 May 2024 11:00:00 +0800</lastBuildDate><atom:link href="https://yekq.top/tags/%E8%87%AA%E9%80%82%E5%BA%94%E6%AD%A5%E9%95%BF/index.xml" rel="self" type="application/rss+xml"/><item><title>SPH基础(五): Runge-Kutta自适应时间积分</title><link>https://yekq.top/posts/sphseries/5-adaptive-timestepping/</link><pubDate>Fri, 24 May 2024 11:00:00 +0800</pubDate><author>plloningye@gmail.com (Keqi Ye)</author><guid>https://yekq.top/posts/sphseries/5-adaptive-timestepping/</guid><description>&lt;h2 id="1-引言">1. 引言
&lt;/h2>&lt;p>在流体动力学乃至更广泛的科学计算领域中，光滑粒子流体动力学（Smoothed Particle Hydrodynamics, SPH）是一种强大的无网格拉格朗日方法。SPH模拟的核心之一是对描述系统演化的常微分方程组（ODEs）进行时间积分，以更新每个粒子的物理状态（如位置、速度、内能等）。&lt;/p>
&lt;p>传统的时间积分方案（如简单的欧拉法或固定步长的龙格-库塔法）虽然实现简单，但在处理复杂动态过程时面临效率与稳定性的两难困境：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>步长过大&lt;/strong>：可能导致数值不稳定，模拟结果迅速发散，功亏一篑。&lt;/li>
&lt;li>&lt;strong>步长过小&lt;/strong>：虽然能保证稳定性，但会极大地增加计算成本，尤其是在模拟过程相对平稳、变化缓慢的阶段，造成了不必要的资源浪费。&lt;/li>
&lt;/ul>
&lt;p>为了在保证计算精度的同时最大化效率，&lt;strong>自适应步长（Adaptive Time-Stepping）&lt;/strong> 的积分方法应运而生。本文将详细介绍自适应步长的核心思想，重点讲解龙格-库塔（Runge-Kutta）方法家族中的 &lt;strong>RK23&lt;/strong> 和 &lt;strong>RK45&lt;/strong> 算法，并阐述如何将其与 SPH 的物理计算流程相结合。&lt;/p>
&lt;h2 id="2-龙格-库塔runge-kutta方法简介">2. 龙格-库塔（Runge-Kutta）方法简介
&lt;/h2>&lt;h3 id="21-广义龙格-库塔方法">2.1 广义龙格-库塔方法
&lt;/h3>&lt;p>龙格-库塔方法是一类用于求解常微分方程的、应用广泛的显式和隐式迭代法。其核心思想是通过在当前时间步内评估多个“中间”斜率，并用这些斜率的加权平均值来更新解，从而获得比简单欧拉法更高的精度。&lt;/p>
&lt;p>对于一个形如 $\frac{dy}{dt} = f(t, y)$ 的初值问题，一个 $s$ 级的显式RK方法可以表示为：
&lt;/p>
$$
\begin{aligned}
k_1 &amp;= f(t_n, y_n) \\
k_2 &amp;= f(t_n + c_2 h, y_n + h a_{21} k_1) \\
k_3 &amp;= f(t_n + c_3 h, y_n + h (a_{31} k_1 + a_{32} k_2)) \\
&amp;\vdots \\
k_s &amp;= f(t_n + c_s h, y_n + h \sum_{j=1}^{s-1} a_{sj} k_j)
\end{aligned}
$$
&lt;p>
最终的解通过这些中间斜率的加权和来计算：
&lt;/p>
$$
y_{n+1} = y_n + h \sum_{i=1}^{s} b_i k_i
$$
&lt;p>
其中 $h$ 是时间步长，系数 $a_{ij}$, $c_i$, 和 $b_i$ 是预先确定的常数，它们的选择决定了方法的精度阶数和稳定性。例如，经典的四阶RK方法（RK4）就是这个家族的一个特例。&lt;/p>
&lt;h3 id="22-嵌入式rk方法自适应步长的关键">2.2 嵌入式RK方法：自适应步长的关键
&lt;/h3>&lt;p>固定步长的RK方法虽然精度高，但无法感知模拟过程的动态变化。自适应步长的精髓在于“在积分的同时估计误差”。&lt;strong>嵌入式龙格-库塔方法（Embedded Runge-Kutta Methods）&lt;/strong> 正是为此而生。&lt;/p>
&lt;p>这类方法（也称 RKF 或 Fehlberg 方法）通过一组精心设计的系数，在一次计算中同时得到两个不同阶数的解：&lt;/p>
&lt;ul>
&lt;li>一个 $p$ 阶精度的解 $y_{n+1}$。&lt;/li>
&lt;li>一个 $p-1$ 阶精度的嵌入解 $\hat{y}_{n+1}$。&lt;/li>
&lt;/ul>
&lt;p>这两个解共享大部分（甚至全部）的 $k_i$ 计算，因此额外开销很小。它们的差值则可以作为局部截断误差 $E_{n+1}$ 的一个可靠估计：
&lt;/p>
$$
E_{n+1} = \| y_{n+1} - \hat{y}_{n+1} \|
$$
&lt;p>
通过将这个误差估计 $E_{n+1}$ 与用户设定的容忍度 &lt;code>tol&lt;/code> 进行比较，我们就可以动态地调整时间步长 $h$：&lt;/p>
&lt;ul>
&lt;li>若 $E_{n+1} \le \text{tol}$，则接受当前步（通常使用更高阶的解 $y_{n+1}$），并可尝试在下一步增大大步长。&lt;/li>
&lt;li>若 $E_{n+1} &amp;gt; \text{tol}$，则拒绝当前步，缩小步长并重新计算。&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>RK23&lt;/strong> 和 &lt;strong>RK45&lt;/strong> 都是这个家族中的杰出代表。&lt;/p>
&lt;h2 id="3-两种经典的自适应rk算法">3. 两种经典的自适应RK算法
&lt;/h2>&lt;h3 id="31-rk23-bogacki-shampine-方法">3.1 RK23 (Bogacki-Shampine) 方法
&lt;/h3>&lt;p>RK23 方法是一种广泛应用的低阶嵌入式方法，它同时计算一个三阶解和一个二阶嵌入解。&lt;/p>
&lt;ul>
&lt;li>&lt;strong>特点&lt;/strong>：
&lt;ul>
&lt;li>它需要进行&lt;strong>3次&lt;/strong>函数求值（计算 $k_1, k_2, k_3$）来得到一个三阶精度的解。&lt;/li>
&lt;li>一个二阶精度的解可以通过这些 $k_i$ 的不同线性组合得到，用于误差估计。&lt;/li>
&lt;li>它具有 &lt;strong>FSAL (First Same As Last)&lt;/strong> 特性：一个步长计算结束时所用的 $k_3$（在新的 $y_{n+1}$ 处的值），可以作为下一个步长的 $k_1$，从而每步实际只需要额外计算2次函数求值，非常高效。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>适用场景&lt;/strong>：
&lt;ul>
&lt;li>对精度要求不是特别苛刻，但希望有自适应步长能力的场景。&lt;/li>
&lt;li>当函数 $f(t, y)$ 的计算成本较高时，其较少的函数求值次数是一个优势。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>其步长调整和误差控制逻辑与高阶方法完全相同，只是系数和阶数不同。&lt;/p>
&lt;h3 id="32-rk45-dormand-prince-54-方法">3.2 RK45 (Dormand-Prince 5(4)) 方法
&lt;/h3>&lt;p>RK45 是自适应积分方法中的“黄金标准”，也是 MATLAB &lt;code>ode45&lt;/code> 的默认选择。它通过一次计算得到一个五阶解和一个四阶嵌入解。&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>核心思想&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>它需要进行 &lt;strong>7 次&lt;/strong>函数求值（计算 $k_1$ 到 $k_7$）。&lt;/li>
&lt;li>使用这些 $k_i$ 的线性组合，分别构造出五阶解 $y_{n+1}^{(5)}$ (用于更新状态) 和四阶解 $y_{n+1}^{(4)}$ (用于误差估计)。&lt;/li>
&lt;li>&lt;strong>Dormand-Prince 系数&lt;/strong>经过特别优化，使得误差估计 $| y_{n+1}^{(5)} - y_{n+1}^{(4)} |$ 相对于步长 $h$ 更加平滑和精确。&lt;/li>
&lt;li>同样具备 &lt;strong>FSAL&lt;/strong> 特性，计算 $k_7$ 的函数值可以复用于下一步的 $k_1$，使得每个成功步长的平均函数求值次数约为6次。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>误差控制策略 (模仿 &lt;code>ode45&lt;/code>)&lt;/strong>：
为了使误差控制更具鲁棒性，我们不使用固定的绝对误差，而是结合&lt;strong>相对容忍度 (&lt;code>RelTol&lt;/code>)&lt;/strong> 和&lt;strong>绝对容忍度 (&lt;code>AbsTol&lt;/code>)&lt;/strong>。对于状态向量 &lt;code>y&lt;/code> 的每个分量 &lt;code>y_i&lt;/code>，容忍度阈值 &lt;code>Tol_i&lt;/code> 定义为：
&lt;/p>
$$
\text{Tol}_i = \text{RelTol} \times |y_i| + \text{AbsTol}
$$
&lt;p>
这个策略的优点是：&lt;/p>
&lt;ul>
&lt;li>当解的数值很大时，误差控制主要由相对容忍度决定。&lt;/li>
&lt;li>当解趋近于零时，由绝对容忍度托底，防止步长被无限压缩。&lt;/li>
&lt;/ul>
&lt;p>最终，我们计算一个归一化的误差率 &lt;code>err_rate&lt;/code>：
&lt;/p>
$$
\text{err\_rate} = \sqrt{ \frac{1}{N} \sum_{i=1}^{N} \left( \frac{E_{n+1, i}}{\text{Tol}_i} \right)^2 }
$$
&lt;p>
其中 $E_{n+1, i}$ 是第 $i$ 个分量的误差估计。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>步长调整决策&lt;/strong>：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>如果 &lt;code>err_rate &amp;lt;= 1.0&lt;/code>&lt;/strong>: &lt;strong>接受当前步&lt;/strong>。使用更高阶的解 $y_{n+1}^{(5)}$ 更新状态，并计算下一个建议步长 $h_{\text{new}}$。&lt;/li>
&lt;li>&lt;strong>如果 &lt;code>err_rate &amp;gt; 1.0&lt;/code>&lt;/strong>: &lt;strong>拒绝当前步&lt;/strong>。状态回退，使用一个更小的步长 $h_{\text{new}}$ 重新计算。&lt;/li>
&lt;/ul>
&lt;p>步长调整的经典公式为：
&lt;/p>
$$
h_{\text{new}} = h_{\text{old}} \times \text{safe} \times \left( \frac{1.0}{\text{err\_rate}} \right)^{p}
$$
&lt;ul>
&lt;li>&lt;code>safe&lt;/code>: 安全因子，通常取 0.9。&lt;/li>
&lt;li>指数 &lt;code>p&lt;/code>: 对于RK45，通常取 &lt;code>0.2&lt;/code> (即 $1/5$)。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h2 id="4-sph右端项rhs的计算">4. SPH右端项（RHS）的计算
&lt;/h2>&lt;p>在前面的讨论中，我们反复提到函数 $f(t, y)$，它代表了系统状态量的时间导数，即常微分方程的&lt;strong>右端项（Right-Hand Side, RHS）&lt;/strong>。在 SPH 模拟中，这个函数 &lt;code>compute_derivatives&lt;/code> 的任务就是根据当前所有粒子的状态，计算出它们各自的时间导数。&lt;/p>
&lt;p>对于复杂的物理过程，尤其是涉及固体力学的弹塑性、损伤和断裂时，RHS 的计算远不止一个简单的压力梯度。下面是构成 SPH 中 RHS 的核心部分：&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>速度导数（加速度 $\mathbf{a}$）&lt;/strong>:
这是动量方程的右端项。加速度由作用在粒子上的所有力的总和除以质量得到。
&lt;/p>
$$
\frac{d\mathbf{v}_i}{dt} = \mathbf{a}_i = \frac{1}{m_i} \sum_j \mathbf{F}_{ij}
$$
&lt;p>
力 $\mathbf{F}_{ij}$ 包括：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>压力梯度力&lt;/strong>: 由压强 $P$ 产生。&lt;/li>
&lt;li>&lt;strong>粘性力&lt;/strong>: 人工粘性，用于处理冲击波。&lt;/li>
&lt;li>&lt;strong>应力散度力&lt;/strong>: 这是固体力学中的关键项。总应力张量 $\boldsymbol{\sigma}$ 可以分解为各向同性的压力 $P$ 和&lt;strong>偏应力张量 $\mathbf{S}$&lt;/strong>。由偏应力引起的力代表了材料的抗剪切和形变能力。
$$ \boldsymbol{\sigma} = -P\mathbf{I} + \mathbf{S} $$
因此，加速度的计算需要准确的应力信息。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>应力导数（应力率 $\dot{\mathbf{S}}$）&lt;/strong>:
应力本身不是一个守恒量，它会随着材料的变形而演化。为了计算应力的变化，我们需要&lt;strong>本构模型（Constitutive Model）&lt;/strong>。对于弹塑性材料，通常使用客观应力率（如 Jaumann 率）来描述偏应力张量的时间导数：
&lt;/p>
$$
\frac{d\mathbf{S}_i}{dt} = \text{JaumannRate}(\mathbf{S}_i, \dot{\boldsymbol{\epsilon}}_i, \boldsymbol{\Omega}_i)
$$
&lt;p>
其中 $\dot{\boldsymbol{\epsilon}}$ 是应变率张量，$\boldsymbol{\Omega}$ 是自旋张量，均由速度梯度计算得到。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>屈服与塑性&lt;/strong>:
当应力达到材料的**屈服强度（Yield Strength）**时，材料进入塑性流动状态。&lt;strong>屈服模型（Yield Model）&lt;/strong>，如 von Mises 或 Drucker-Prager，定义了这个边界。对于岩石等材料，屈服强度还依赖于压力。一旦屈服，就需要通过“径向返回算法”将应力拉回到屈服面上，这个过程是非线性的，并决定了塑性变形的能量耗散。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>损伤导数（损伤率 $\dot{D}$）&lt;/strong>:
为了模拟材料的开裂和失效，我们引入一个内部状态变量——损伤 $D$ (从0到1)。&lt;strong>损伤模型（Damage Model）&lt;/strong>，如 Grady-Kipp 模型，描述了损伤如何随着拉伸或应变而累积。
&lt;/p>
$$
\frac{dD_i}{dt} = g(\boldsymbol{\sigma}_i, \dot{\boldsymbol{\epsilon}}_i, D_i, \dots)
$$
&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>总结&lt;/strong>：在每一个时间积分步中，&lt;code>compute_derivatives&lt;/code> 函数的计算流程大致如下：&lt;/p>
&lt;ol>
&lt;li>根据当前密度 $\rho$ 和内能 $u$，通过状态方程计算&lt;strong>压力&lt;/strong> $P$。&lt;/li>
&lt;li>利用&lt;strong>屈服模型&lt;/strong>判断当前应力状态。&lt;/li>
&lt;li>通过&lt;strong>本构模型&lt;/strong>计算弹性试探应力，如果屈服则进行塑性修正，得到最终的&lt;strong>偏应力&lt;/strong> $\mathbf{S}$。&lt;/li>
&lt;li>利用&lt;strong>损伤模型&lt;/strong>更新损伤变量 $D$。&lt;/li>
&lt;li>最后，将压力 $P$ 和偏应力 $\mathbf{S}$ 代入动量方程，计算出最终的&lt;strong>加速度&lt;/strong> $\mathbf{a}$。&lt;/li>
&lt;li>同时，计算内能变化率 $\dot{u}$、密度变化率 $\dot{\rho}$ 等其他变量的导数。&lt;/li>
&lt;/ol>
&lt;p>这些导数共同构成了时间积分器所需要的 RHS 向量。&lt;/p>
&lt;h2 id="5-应用算例简述">5. 应用算例简述
&lt;/h2>&lt;p>在实现复杂的自适应积分器后，用一些已知解或具有守恒律的简单问题进行验证是至关重要的一步。&lt;/p>
&lt;h3 id="51-太阳系模拟">5.1 太阳系模拟
&lt;/h3>&lt;p>这是一个经典的 N 体问题。每个行星（粒子）的 RHS 就是其他所有天体对其施加的万有引力之和。
&lt;/p>
$$
\frac{d\mathbf{v}_i}{dt} = \mathbf{a}_i = \sum_{j \ne i} \frac{G m_j (\mathbf{r}_j - \mathbf{r}_i)}{\| \mathbf{r}_j - \mathbf{r}_i \|^3}
$$
&lt;ul>
&lt;li>&lt;strong>验证重点&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>长期能量守恒和角动量守恒&lt;/strong>：是衡量积分器好坏的关键指标。&lt;/li>
&lt;li>&lt;strong>轨道精度&lt;/strong>：能否准确再现行星的椭圆轨道。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>自适应步长优势&lt;/strong>：对于具有高偏心率轨道的天体（如彗星），它在靠近太阳时（速度快，引力变化剧烈）会自动采用小步长，而在远离时（速度慢，引力平缓）则采用大步长，兼顾了精度和效率。&lt;/li>
&lt;/ul>
&lt;h3 id="52-单摆模拟">5.2 单摆模拟
&lt;/h3>&lt;p>一个简单的单摆系统由以下一阶方程组描述：
&lt;/p>
$$
\begin{cases}
\frac{d\theta}{dt} = \omega \\
\frac{d\omega}{dt} = -\frac{g}{L} \sin(\theta)
\end{cases}
$$
&lt;ul>
&lt;li>&lt;strong>验证重点&lt;/strong>：
&lt;ul>
&lt;li>&lt;strong>周期稳定性&lt;/strong>：对于小角度摆动，周期应接近 $2\pi\sqrt{L/g}$。&lt;/li>
&lt;li>&lt;strong>能量守恒&lt;/strong>：在无阻尼情况下，总能量（动能+势能）应保持不变。&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>自适应步长优势&lt;/strong>：当摆锤经过最低点（速度最快）时，步长会自然减小；在最高点（速度为零）时，步长会增大。这展示了积分器对系统动态的灵敏响应。&lt;/li>
&lt;/ul>
&lt;h2 id="6-结合sph的计算伪代码">6. 结合SPH的计算伪代码
&lt;/h2>&lt;p>现在，我们将上述 RK45 误差控制逻辑整合到 SPH 的主循环中。&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;span class="lnt">26
&lt;/span>&lt;span class="lnt">27
&lt;/span>&lt;span class="lnt">28
&lt;/span>&lt;span class="lnt">29
&lt;/span>&lt;span class="lnt">30
&lt;/span>&lt;span class="lnt">31
&lt;/span>&lt;span class="lnt">32
&lt;/span>&lt;span class="lnt">33
&lt;/span>&lt;span class="lnt">34
&lt;/span>&lt;span class="lnt">35
&lt;/span>&lt;span class="lnt">36
&lt;/span>&lt;span class="lnt">37
&lt;/span>&lt;span class="lnt">38
&lt;/span>&lt;span class="lnt">39
&lt;/span>&lt;span class="lnt">40
&lt;/span>&lt;span class="lnt">41
&lt;/span>&lt;span class="lnt">42
&lt;/span>&lt;span class="lnt">43
&lt;/span>&lt;span class="lnt">44
&lt;/span>&lt;span class="lnt">45
&lt;/span>&lt;span class="lnt">46
&lt;/span>&lt;span class="lnt">47
&lt;/span>&lt;span class="lnt">48
&lt;/span>&lt;span class="lnt">49
&lt;/span>&lt;span class="lnt">50
&lt;/span>&lt;span class="lnt">51
&lt;/span>&lt;span class="lnt">52
&lt;/span>&lt;span class="lnt">53
&lt;/span>&lt;span class="lnt">54
&lt;/span>&lt;span class="lnt">55
&lt;/span>&lt;span class="lnt">56
&lt;/span>&lt;span class="lnt">57
&lt;/span>&lt;span class="lnt">58
&lt;/span>&lt;span class="lnt">59
&lt;/span>&lt;span class="lnt">60
&lt;/span>&lt;span class="lnt">61
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-cpp" data-lang="cpp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// --- 全局参数 ---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">RelTol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1e-6&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 相对容忍度
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">AbsTol&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1e-9&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 绝对容忍度
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">h_min&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1e-8&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h_max&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">1e-2&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 步长上下限
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">safe_factor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.9&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">double&lt;/span> &lt;span class="n">max_increase&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">5.0&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">min_decrease&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.2&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// --- 主循环 ---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="kt">double&lt;/span> &lt;span class="n">t&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="mf">0.0&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="kt">double&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">initial_dt&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 初始步长
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// y_n 包含了所有粒子的位置、速度、内能、应力等状态量
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">StateVector&lt;/span> &lt;span class="n">y_n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">get_initial_conditions&lt;/span>&lt;span class="p">();&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// 预计算下一步的 k1 (利用 FSAL 特性)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>&lt;span class="n">RHSVector&lt;/span> &lt;span class="n">k1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">compute_derivatives&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_n&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">T_max&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="kt">bool&lt;/span> &lt;span class="n">step_accepted&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">false&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="o">!&lt;/span>&lt;span class="n">step_accepted&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">h&lt;/span> &lt;span class="o">&amp;lt;&lt;/span> &lt;span class="n">h_min&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">error&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;Timestep smaller than h_min&amp;#34;&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">break&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 1. RK45 核心计算：利用已有的 k1 计算 k2, ..., k7
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 并得到 y_next_4 (四阶解) 和 y_next_5 (五阶解)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// (此处省略繁杂的系数计算，但会返回 k_next 用于 FSAL)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="k">auto&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="n">y_next_4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_next_5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k_next&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">perform_rk45_step&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">k1&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 2. 计算误差率 err_rate (模仿 MATLAB)
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">err_rate&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">calculate_error_rate&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">y_n&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_next_4&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">y_next_5&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">RelTol&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">AbsTol&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 3. 决策与步长调整
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="kt">double&lt;/span> &lt;span class="n">h_new&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">safe_factor&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">pow&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">err_rate&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="o">-&lt;/span>&lt;span class="mf">0.2&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">err_rate&lt;/span> &lt;span class="o">&amp;lt;=&lt;/span> &lt;span class="mf">1.0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// --- 接受步长 ---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">step_accepted&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="nb">true&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">t&lt;/span> &lt;span class="o">+=&lt;/span> &lt;span class="n">h&lt;/span>&lt;span class="p">;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="n">y_n&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">y_next_5&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// 更新状态为更高阶的解
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">k1&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">k_next&lt;/span>&lt;span class="p">;&lt;/span> &lt;span class="c1">// FSAL: 下一步的 k1 已经算好
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 限制步长增幅
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">min&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="n">h&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">max_increase&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h_new&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h_max&lt;/span>&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span> &lt;span class="k">else&lt;/span> &lt;span class="p">{&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// --- 拒绝步长 ---
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="c1">// 状态 y_n, t, k1 保持不变
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// 限制步长降幅
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">h&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">max&lt;/span>&lt;span class="p">({&lt;/span>&lt;span class="n">h&lt;/span> &lt;span class="o">*&lt;/span> &lt;span class="n">min_decrease&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h_new&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="n">h_min&lt;/span>&lt;span class="p">});&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">// (可选) 在每个成功的时间步后，更新邻域、输出数据等
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">&lt;/span> &lt;span class="n">UpdateAndSaveData&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="n">t&lt;/span>&lt;span class="p">);&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">}&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// `compute_derivatives` 函数实现了第4节描述的RHS计算逻辑。
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">// `perform_rk45_step` 和 `calculate_error_rate` 分别实现了第3节的算法核心和误差控制。
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description></item></channel></rss>