先把结论说在前面: Chrono 的 Multicore 框架确实是 矩阵-free 的 Schur 补操作 + 并行 data manager + 迭代投影解算器,你要移植到 Verosim/RBD 里,最理性的做法不是“一股脑照搬”,而是:

先在 Verosim 里搭一个“Chrono 风格的解算接口 + SchurProduct 抽象”,但仍然用你现在的密集矩阵 A 做参考; 然后再一步步换成矩阵-free,再加并行。

下面我给你一个比较“工程可落地”的里程碑方案,每一步都能单独编译、单独跑测试。


0. 先统一脑内架构图:Chrono ↔ Verosim 对应关系

Chrono Multicore 这边

关键几块你已经有代码:

所以很明确:Multicore 确实是 matrix-free 的,核心就是 ChSchurProduct::operator() 这玩意,所有迭代解算器只需要 “给我一个算子 A(x) + 约束投影就行”。


Verosim / RBD 这边现状

你已经实现了一个 密集矩阵版 APGDRBDLcpAPGD,内部持有 pad 对齐的 A 数组 + b + 上下界 + 摩擦块索引等等。


总体迁移策略(大纲)

我们目标是:保留 Verosim 的 Cluster & 约束装配风格,只在“求解这一步”换成 Chrono 风格:SchurProduct + Project + matrix-free + 并行。

一个合理的分阶段路线:

  1. 接口对齐(仍然用密集 A)

    • 在 RBD 里仿造 ChSolverMulticore 定义一个统一求解接口 + RBDSchurProduct/RBDProjectConstraints 抽象,但内部还是直接调你现有的 RBDLcpAPGD(A,b)
    • 好处:先把“上层调用风格”改成 Chrono 这种 functor 结构,不动数学和数值行为。
  2. 在 RBD 内部实现一个 “伪 SchurProduct”(用 dense A 实现)

    • 写一个 RBDSchurProductDense:内部存一个 VSM::MatrixNxM 或 double* A,operator()(x,y) 做 y = A*x
    • 你的 “multicore 风格 APGD” 直接用这个 SchurProduct;数值上对齐当前 RBDLcpAPGD。
  3. 把 APGD 算法改成真正的 functor 版本

    • 参考 ChSolverMulticoreAPGDREF,写一个 RBDSolverAPGD_MF

      • 不再持有 A 矩阵,而是只持有:

        • RBDSchurProduct& Schur
        • RBDProjectConstraints& Proj
        • bgamma、临时向量等
      • 梯度用 Schur(gamma, tmp) 计算,投影用 Proj(gamma)

    • 这一步完成后,你就有了:“接口 = Chrono,多核的数学结构 = 你的 APGD”,但还在密集矩阵世界里。

  4. 引入真正的矩阵-free SchurProduct(仍然单线程)

    • 利用 RBDClusterLagrangeMultipliers 里已经有的 multiplyMinvImplJT / multiplyMinvImplVector

      • 你有稀疏的 RBMJacobeanMatrix J + 当前 cluster bodies + 质量信息
    • 新建一个类 RBDSchurProductMatrixFree,构造时传入:

      • 对应 cluster 的 RBMJacobeanMatrix& J
      • bodies 列表引用
      • 预存 M_inv_JT 或直接在 operator() 里:

        1. 临时 tmp = M_invD * x,用 multiplyMinvImplJT 或改个版本 JTMinv
        2. J.multiplySymmetricVec(tmp, y) 或直接 J^T ( tmp ) 组合出 SchurProduct: [ y = J M^{-1} J^T x + E x ] $y = J M^{-1} J^T x + E x$
    • 先不考虑稀疏格式转换(比如压成 blaze 的 CompressedMatrix),完全用现有 RBMJacobean 的存储结构做 matrix-free 运算。

  5. 把 RBDClusterLagrangeMultipliers 的求解路径切换到“新接口 + matrix-free APGD”

    • doTimeStep() 里,原来是:

      1. 构造 J,密集化成 matA
      2. new 一个 RBDLcpAPGD(size, nub, maxIters)
      3. setValuesInMatrix(matA)/setLowVector/…/solve()
    • 改成:

      1. 构造 J(这一段不变)
      2. 构造 RBDSchurProductMatrixFree Schur(J, bodies, ...)
      3. 构造 RBDProjectConstraintsFromBounds,从 lambdaLow / lambdaHigh / frictionIndex / addFriction 生成投影逻辑(其实你 RBDLcpAPGD::projectBounds/projectFriction 的代码可以直接搬)
      4. 构造 RBDSolverAPGD_MF solver; solver.Solve(Schur, Project, maxIter, size, b, lambda);
    • 关键点:在这一步你可以同时保留旧路径,用一个 if (use_multicore_style) 开关做 A/B 对比(lambda、v_new、残差对比)。

  6. 并行化热点:SchurProduct & 投影

等到 matrix-free 路线在单线程下跑得对、数值上跟密集版对齐之后,再加 OpenMP:

因为你说“只考虑我自己求解器的加速”,完全可以只在 APGD 路径上做这些,Dantzig/GS 继续用旧代码,当作 fallback。


按“可执行任务”拆成里程碑

我给你一个更“todo 风”的分解,你可以几天 / 一周完成一个小块:

里程碑 1:建立 RBD 版 Multicore 解算接口(仍然用密集 A)

目标

实现建议

改动点

测试


里程碑 2:实现 functor 风格的 APGD(但还是靠 dense SchurProduct)

目标

实现建议(和你现有 RBDLcpAPGD 映射)

测试


里程碑 3:在 RBD 中做真正的 Matrix-Free SchurProduct(单线程)

目标

实现思路(伪代码):

class RBDSchurProductMatrixFree : public RBDISchurProduct {
public:
    RBDSchurProductMatrixFree(const RBMJacobeanMatrix& J,
                              const RBDRigidBodyPtrSet& bodies,
                              const VSM::VectorNDynamic& E_diag_or_matrix, ...);

    void operator()(const VSM::VectorN& x, VSM::VectorN& y) override {
        // 1) tmp = J^T * x   (用 RBMJacobeanMatrix 的数据结构)
        // 2) z   = M^{-1} * tmp  (用 multiplyMinvImplVector)
        // 3) y   = J * z         (再走一遍 J 的 rows)
        // 4) y  += E * x         (如果有 CFM/compliance)
    }
};

Chrono 那边是 output = D_T * (M_invD * x) + E * x,我们只是用 RBD 自己的 Jacobian & Minv 代替 blaze 稀疏矩阵而已。

测试策略


里程碑 4:把 Cluster 的 APGD 路线切到 Matrix-Free

目标

myLcp = new RBDLcpAPGD(matA.rows(), numberEqualityConstraints, ...);
// ...
myLcp->setValuesInMatrix(matA);
myLcp->setLowVector(lambdaLow);
...
myLcp->solve();

变成:

RBDSchurProductMatrixFree schur(J, getRigidBodies(), ..., /* E / CFM 信息 */);
RBDProjectFromBounds proj(lambdaLow, lambdaHigh, frictionIndices, addFriction);

RBDSolverAPGD_Functor solver(numberEqualityConstraints, maxIters, tol);
solver.Solve(schur, proj, b, lambda);

保留 debug 开关


里程碑 5:SchurProduct & Project 的并行化

你只关心“自己求解器”的加速,所以最值得并行的就是:

这俩都满足“很规整的并行循环 + 几乎没有复杂依赖”。

示例方向


顺便回答你之前的问题:Multicore 用到 matrix-free 吗?

非常明确:用到,而且是默认路径

你现在在 RBD 里已经有 J + M_inv 的稀疏结构 + 高效的 multiplyMinvImpl,所以可以不必构造完整矩阵,直接学这套模式。


总结一下这套方案的味道

每一步你都可以:

这样你不会陷入“一口吃掉整个 Chrono multicore”的深坑,而是稳步把 Verosim 的求解器长成一只多核怪兽。

Step 1

不加 .cpp,不改 APGD 算法逻辑、不碰 SOA。

把 Chrono 风格的接口骨架 搭出来,但一切实现先还是用你现有的 dense matA / lambdaLow / lambdaHigh。

在 RBD 里增加一套统一接口:SchurProduct + Project + Solver,全部 header-only,方便后面逐步接 APGD 和 matrix-free。

  1. 新增一个头文件(唯一新文件):RBDSolverMulticoreInterface.h