Simulation system

ChSystem 与两种系统

Time steppers(时间积分器)

经验:DVI/硬接触 ⇒ EULER_IMPLICIT_LINEARIZED软接触/FEA/需要二阶与阻尼 ⇒ HHT/NEWMARK

Solvers(求解器)★重点

时间积分器每步都会调用“求解器”来求未知的加速度与反力。它往往是计算热点

推荐的迭代求解器与特性:

chrono 求解器结构

A P G D

ChSolverAPGD.cpp

A)SchurBvectorCompute(ChSystemDescriptor& sysd):装配 Schur 右端 r

目标:构造 Schur 方程 N * λ = b_schur 的右端,其中

逐行解释:

  1. 对每个变量做“解质量矩阵”,把 q = M^-1 * k 写回变量的 State()

    var->ComputeMassInverseTimesVector(var->State(), var->Force());
    

    这一步把“广义力 k”通过 M^-1 映射成速度增量模板 q(等会儿进入 D' q)。

  2. 清空 r,然后遍历所有激活的约束,累加

    r[s_i] = constraint->ComputeJacobianTimesState();
    

    这就是 - D' * q(命名和号可能与你直觉相反,但看后面的目标写法可知,最终得到的是我们想要的 r)。

  3. 再取系统装配的 b_i = -c = phi/h(接触/约束的“偏置项”,例如恢复量/归一化渗透),加到 r 上:

    sysd.BuildBiVector(tmp);  // tmp = b_i
    r += tmp;                 // r = -D' q + b_i
    

得到的这个 r,被用于后续所有梯度和目标计算;配合下面的写法,最终的目标是


B) Res4(sysd):全局最优性(KKT/VI)残差

作用:计算 投影梯度映射 的范数 ‖ λ - Proj_K( λ - gdiff * (N*λ + r) ) ‖ / gdiff,其中 Proj_K 是把乘子投回可行集 K 的投影(包含等式/不等式/摩擦锥),gdiff 取一个很小的步长(这里固定为 1/(nc^2),nc=约束数)。

流程:

  1. tmp = N * gammaNew(这里 gammaNew 就是当前 λ);
  2. tmp = gammaNew - gdiff * (tmp + r)(负梯度迈一步);
  3. sysd.ConstraintsProject(tmp)(把 tmp 投回可行域 K:

    • 等式约束:保持等式(实现细节在 ConstraintsProject 内部);
    • 单边/盒约束:逐分量 clamp;
    • 摩擦锥:按接触块把切向缩回半径 = μ * λ_n 的圆盘/圆锥);
  4. tmp = (gammaNew - tmp) / gdiff(这就是“梯度映射”向量);
  5. 返回 tmp.norm() 作为全局一阶最优性残差。 当这个残差 → 0,就意味着 λ = Proj_K( λ - t * grad f(λ) ) 成立,也就是 KKT/VI 的不动点条件成立 ⇒ 达到全局最优(在这类凸问题里)。

C) Solve(sysd):APGD 主流程(逐步讲清)

C.0 预备与内存

C.1 右端装配(全局)

C.2 备份 Minvk(回写原始变量要用)

C.3 初值与动量初始化

C.4 估 L 并设步长 t = 1/L

解释:对光滑凸目标 f(λ) = 0.5 λ' N λ + r' λ,梯度 ∇f(λ) = N λ + r 的 Lipschitz 常数是 ‖N‖_2。APGD/FGM 的稳定步长就是 1/L

C.5 迭代主循环(k = 0…max_iter)

每一轮做三件事:梯度 + 投影回溯线搜索Nesterov 动量 & 收敛管理

梯度 + 投影(全局耦合更新)

回溯线搜索(保证“足够下降”的上界条件)

Nesterov 动量、残差、重启与 L 的轻微衰减

循环直到迭代次数用完或残差达标。

C.6 写回乘子与原始变量(速度)

你关心的几个关键点,钉死它们

  1. “全局 vs 逐约束”

    • SchurComplementProduct 是对整个 λ 向量做一次 N*λ(全局耦合);
    • ConstraintsProject 是对集合 K = 盒 × 等式 × 摩擦锥(分块)一次性投影,虽然实现上“逐分量/按摩擦块”处理,但数学上是对整个 λ 的投影;
    • 残差 Res4全局的 KKT/VI 不动点残差(不是单约束的误差)。 所以它解的是全局(在当前系统描述符内)的凸二次问题,不是“每个约束的小最优”。
  2. 目标与梯度到底是什么(由代码反推)

    • 你看到 gammaNew = y - t * (g + r),以及 obj = x' (0.5 N x + r),所以 目标函数是 f(λ) = 0.5 * λ' N λ + r' λ, 梯度是 ∇f(λ) = N λ + r
    • 这与 SchurBvectorCompute 的产物一致:r = -D' q + b_i,在标准 NSC 推导里,这正对应 -b_schur
  3. 为什么残差是“全局最优性”

    • 最优性等价式:解 λ* 满足 λ* = Proj_K(λ* − t * ∇f(λ*))(任意 t>0),也等价于 VI/KKT 条件;
    • Res4 计算的就是 ‖λ − Proj_K(λ − gdiff * ∇f(λ))‖/gdiff 的范数 → 0,表示满足最优性;
    • 在这类凸问题(N 半正定、K 凸)里,“局部最优 = 全局最优”,因此这个残差达标就意味着全局最优达标。
  4. 回溯线搜索在保什么

    • 用 Lipschitz 上界 f(x) ≤ f(y) + ∇f(y)'(x−y) + 0.5 L ‖x−y‖^2 做判据;
    • 若不满足就把 L 翻倍(步长 t 变小),直到满足;
    • 这保证了每步更新后目标不“犯规”,数值更稳。