2. 动力系统建模基础

参数扫描与行为识别的前提,是先把研究对象写成一个“可被程序反复求值”的数学模型。所谓参数化模型,就是把系统的结构固定下来,把可变的量抽成参数,使模型成为参数向量 p 的函数。只有这样,扫描器才能在一组参数网格上批量生成模型实例、求解轨迹并比较行为。本节围绕连续与离散两类动力系统的数学表达、状态变量、参数与向量场的含义展开,并给出一个可直接用于后续扫描的建模流程。

一、连续系统:状态、向量场与参数

连续动力系统最通用的形式是一阶常微分方程组:

dx/dt = f(x, p, t),   x ∈ R^n,  p ∈ R^m

其中 x 是状态变量向量,p 是参数向量,f 称为向量场。向量场的几何含义是:在状态空间的每一点 x 处,f 给出一个速度矢量,轨迹沿该矢量方向流动。因此“求解系统”在几何上等价于“沿向量场积分”。理解这一点非常关键:参数扫描时改变 p,本质上是在改变整个状态空间上的箭头分布,而不是改变某一条轨迹的局部细节。

以经典的阻尼单摆为例,取状态 x = (θ, ω),参数 p = (g/L, c),则

dθ/dt = ω
dω/dt = -(g/L)·sin(θ) - c·ω

这里 θ 是摆角,ω 是角速度,g/L 决定回复力的强弱,c 是阻尼系数。若把 c 从 0 逐步增大,向量场中“向原点收敛”的分量逐渐增强,系统从等幅振荡过渡到衰减振荡,最终趋于过阻尼。这正是后续章节要识别的行为变化,而它在建模阶段就已经由参数位置决定了。

建模约定要先行:状态变量的单位、角度用弧度还是度、时间正向如何定义、参数取值范围是否包含 0,都应在写模型时固定下来。后续扫描与图示必须沿用同一约定,否则轨迹比较会失去意义。

二、离散系统:映射、迭代与采样

离散动力系统用映射描述状态的逐拍演化:

x_{k+1} = F(x_k, p),   k = 0, 1, 2, ...

它既可以来自本身离散的物理过程(如逐年种群更新),也可以来自对连续系统的采样离散化。以显式欧拉法为例,步长 h 下:

x_{k+1} = x_k + h·f(x_k, p)

此时 h 本身也成为一个参数。这一点在扫描中极易被忽视:h 过大会引入数值不稳定,产生看似“系统混沌”实为“积分失真”的假象。因此建模时应把数值参数与物理参数分开登记,扫描时优先固定数值参数,只让物理参数变化。

离散系统还有一个连续系统没有的特性:映射可以一步跨越大范围状态,因此更容易出现周期轨道与倍周期分岔。经典的一维逻辑斯蒂映射 x_{k+1} = r·x_k·(1 - x_k) 中,r 从约 3.0 增大到 3.57 的过程中,稳定周期依次由 1 变为 2、4、8……这正是参数扫描最直观的演示对象。

三、连续与离散的对照

对比项连续系统离散系统
数学表达dx/dt = f(x, p)x_{k+1} = F(x_k, p)
时间含义连续流动整数拍或采样时刻
核心对象向量场映射(迭代函数)
典型行为平衡点、极限环不动点、周期轨道、倍周期
额外参数通常无步长 h、采样周期
扫描注意点初值敏感性、积分误差数值参数混入、瞬态丢弃

下图给出一个参数化模型的完整数据流:从结构定义出发,经参数向量注入,分别进入连续向量场与离散映射两条求解路径,最终输出可供行为识别的轨迹与指标。

结构定义 状态变量 x ∈ R^n 方程形式 f 或 F 参数向量 p 物理参数 + 数值参数 扫描网格来源 初值 x₀ 与瞬态丢弃策略 共同决定可比性 连续路径 dx/dt = f(x, p) 向量场积分 离散路径 x_{k+1} = F(x_k, p) 映射迭代 / 采样 轨迹与指标 时间序列 / 相图 振幅、周期、收敛率 行为标签 稳定 / 周期 / 混沌 供参数扫描比对 同一参数网格 参数化模型 = 结构 + 参数 + 初值 + 求解约定 扫描器只需替换 p,即可批量生成可比对的模型实例 数值参数(步长、容差)须与物理参数分开登记

四、把模型写成可扫描的形式

“可扫描”不是一句口号,它要求模型在代码层面具备三个特征:参数集中、求值纯函数化、输出标准化。下面给出一个 Python 结构示例,用同一套接口同时容纳连续与离散模型:

import numpy as np

def pendulum_field(x, p):
    theta, omega = x
    gL, c = p["gL"], p["c"]
    return np.array([omega, -gL*np.sin(theta) - c*omega])

def logistic_map(x, p):
    r = p["r"]
    return r * x * (1.0 - x)

def simulate_continuous(field, x0, p, dt=0.01, steps=4000):
    xs = np.empty((steps, len(x0)))
    x = np.array(x0, dtype=float)
    for k in range(steps):
        xs[k] = x
        x = x + dt * field(x, p)      # 显式欧拉
    return xs

def simulate_discrete(F, x0, p, steps=2000, burn=500):
    xs = np.empty(steps)
    x = x0
    for k in range(steps + burn):
        if k >= burn:
            xs[k - burn] = x
        x = F(x, p)
    return xs

注意 burn 参数:离散映射在扫描时通常要丢弃前若干拍的瞬态,否则周期轨道的判定会被初始过渡污染。连续系统同理,若关注稳态行为,也应截去前一段暂态。这个约定必须在建模阶段写进接口,而不是留给后续分析临时处理。

五、建模阶段的检查要点

  • 量纲一致:g/L 的单位应为 s⁻²,c 为 s⁻¹,若混用会导致参数扫描区间失去物理意义。
  • 参数可辨识:只把真正影响行为的量放进 p;常数应固化,避免扫描维度虚高。
  • 数值参数隔离:步长 dt、容差、瞬态长度单独存放,扫描时保持不变。
  • 输出统一:无论连续还是离散,都输出等长轨迹数组与一组标量指标,便于后续批量比对。
  • 边界防护:逻辑斯蒂映射中 x 超出 [0,1] 会发散,建模时应加入范围检查或裁剪策略并明确记录。
常见误区:把“模型跑出了复杂轨迹”直接当作系统本身的混沌行为。若步长过大或未丢弃瞬态,数值伪影同样会产生看似复杂的序列。建模阶段固定求解约定,是后续行为识别可信的前提。

完成上述工作后,一个动力系统就被压缩成了“结构 + 参数向量 + 初值 + 求解约定”四元组。后续的参数扫描只需在这四元组上替换 p,即可批量生成模型实例;而行为识别则比较这些实例输出的轨迹与指标。建模的质量,直接决定了扫描结果是否可比、是否可信。