简介:这份资源面向从事颗粒动力学模拟的科研人员与工程师,提供EDEM离散元软件中凝聚力接触模型的API编程模版,帮助解决颗粒间因分子作用力产生团聚、粘附等行为的自定义建模问题。压缩包共7个文件,以3个cpp源文件与3个h头文件为主,另附1个txt参数配置说明,整体约8KB,结构紧凑,便于直接嵌入现有工程。源码覆盖初始化参数设置、自定义力模型、接触事件处理、粒子属性设定及输入输出等模块,可据此实现距离依赖的势能函数并扩展EDEM原有功能。目前已有786人学习下载,适合具备一定C++基础、希望深入掌握EDEM二次开发的中高级用户参考,通过研读模板可快速理解API调用逻辑,为粉末混合、湿润颗粒团聚等复杂工况的定制化模拟提供可复用的代码起点。
1. 从一份“凝聚力接触模型API模版”说起:离散元二次开发到底在做什么
如果你手头正好有一份名为“EDEM离散元软件凝聚力接触模型API编程模版”的压缩包,或者你正在搜索引擎里反复敲“EDEM 凝聚力接触模型 二次开发”这几个词,那你大概率已经越过了“点几下鼠标跑个仿真”的阶段,开始琢磨怎么让颗粒之间“粘”起来这件事了。EDEM 作为离散元方法(DEM)的主流工具之一,自带了不少接触模型,但真正做粉体、土壤、药片、电池浆料或者湿颗粒造粒的人都知道,标准模型往往不够用——凝聚力(cohesion)这一块,JKR、Johnson-Kendall-Roberts 也好,简化版的平行键也罢,参数和物理行为对不上你的实际工况是常态。这时候,API 编程模版就成了你绕不开的入口:它不是一个能直接出结果的软件,而是一套让你把自定义的凝聚力接触力学写进求解器里的骨架代码。
这份模版解决的核心问题很具体:让你不用从零去啃 EDEM 的 API 头文件和回调机制,而是拿到一个已经搭好框架的工程,把精力集中在“法向粘附力怎么算、切向粘附怎么耦合、能量耗散怎么处理”这些物理层面。适合谁?适合已经能跑通基本 DEM 案例、知道颗粒半径、杨氏模量、泊松比、恢复系数这些参数在哪设,但一看到 API 文档里那些 ContactModel、ParticleBody、Simulation 类就头大的工程师和研究生。接下来的内容,我会按“先搞懂模版结构,再动手改一个最小凝聚力模型,最后把编译、调试、验证的坑一个个填上”的顺序来讲,你照着做,至少能少走两三个星期的弯路。
2. 拆开模版看结构:凝聚力接触模型API的五个核心模块
2.1 模版目录里到底有什么,哪些文件你必须改
拿到压缩包解压后,常见做法是先别急着打开 IDE,用文件管理器把目录树看一遍。一个典型的 EDEM 接触模型 API 模版,目录结构大致长这样(不同版本会有差异,但核心文件跑不掉):
CohesionContactModel/ ├── include/ │ ├── CohesionContactModel.h // 模型类声明,继承自外部接触模型基类 │ ├── CohesionContactModelParameters.h // 参数结构体,暴露给UI │ └── ApiHelpers.h // 向量、四元数等工具函数 ├── src/ │ ├── CohesionContactModel.cpp // 核心力学计算实现 │ ├── CohesionContactModelParameters.cpp // 参数注册与读写 │ └── PluginEntry.cpp // 插件入口,注册模型到求解器 ├── CMakeLists.txt // 构建脚本 └── README.md // 编译说明(往往写得比较简略)你真正需要动手改的,通常只有三个文件:CohesionContactModel.h里加你的私有成员变量(比如粘附能量密度、接触半径历史),CohesionContactModel.cpp里重写computeForce或者evaluate这类回调函数,CohesionContactModelParameters.cpp里把你要在 UI 上让用户填的参数注册进去。PluginEntry.cpp和ApiHelpers.h一般不用动,除非你要加新的全局配置。这里有个血泪经验:很多人一上来就改PluginEntry.cpp,结果编译过了但模型在 EDEM 界面里死活不出现,原因就是入口注册的宏写错了或者漏了导出符号。先别碰入口文件,把力学算对了再说。
2.2 接触模型基类的回调时机:什么时候算力,什么时候更新历史
EDEM 的 API 把接触计算拆成了几个阶段,你的代码会在每个时间步被调用多次。最关键的两个回调是onContactStart和computeForce(不同版本命名可能略有差异,比如initialise和getForce)。onContactStart在接触第一次建立时触发,适合做初始化:记录初始接触点、设置粘附力历史变量的初值、判断是不是需要激活凝聚力逻辑。computeForce在每个时间步对每个接触对调用,你要在这里根据当前重叠量、相对速度、以及你维护的历史变量,算出法向力和切向力,然后写回给求解器。
为什么这个时机很重要?因为凝聚力模型往往有“加载历史”依赖——比如 JKR 模型里,拉脱力的大小和接触形成时的最大重叠量有关,你不能只看当前帧。如果你把历史变量放在computeForce里初始化,那每次调用都会重置,颗粒永远粘不住。常见做法是在onContactStart里把pullOffForce或者contactRadius存到接触对象的用户数据区,后续在computeForce里读取并更新。模版里一般会有一个ContactData结构体或者userData指针,别忽略它。
2.3 参数注册:怎么让自定义凝聚力参数出现在EDEM界面里
你肯定不希望每次改粘附功都要重新编译代码。模版里的CohesionContactModelParameters类就是干这个的:它继承自参数基类,通过宏或者虚函数把参数名、类型、默认值、单位注册到求解器的参数系统。一个典型的注册代码片段如下:
// CohesionContactModelParameters.cpp void CohesionContactModelParameters::registerParameters() { // 注册粘附能量密度,单位 J/m^2,默认值 0.01 registerScalarParameter("AdhesionEnergyDensity", &m_adhesionEnergyDensity, 0.01, "J/m^2", "单位面积粘附功,控制颗粒间凝聚力强度"); // 注册拉脱力放大系数,无量纲,默认 1.0 registerScalarParameter("PullOffMultiplier", &m_pullOffMultiplier, 1.0, "-", "用于修正理论拉脱力的经验系数"); // 注册是否启用切向粘附的开关 registerBoolParameter("EnableTangentialCohesion", &m_enableTangentialCohesion, false, "开启后切向相对运动也会产生粘附阻力"); }逻辑说明:registerScalarParameter的第一个参数是显示在 UI 上的名字,第二个是指向成员变量的指针,第三个是默认值,第四个是单位,第五个是提示文本。参数说明:AdhesionEnergyDensity直接对应 JKR 理论里的Δγ,如果你做的是湿颗粒,这个值可能要从液桥力反推;PullOffMultiplier是给你调参用的后悔药,理论算出来偏小就乘个系数;EnableTangentialCohesion在干粉场景通常关掉,湿颗粒或者有剪切粘附的场景才打开。注册完之后,记得在CohesionContactModel.cpp的构造函数里把成员变量初始化成和默认值一致,否则 UI 显示和实际计算会对不上。
2.4 法向凝聚力计算的最小实现:从JKR到简化粘附力
凝聚力接触模型最核心的公式就是法向粘附力。JKR 理论给出的拉脱力是 ( F_{pull-off} = \frac{3}{2} \pi \Delta\gamma R^* ),其中 ( R^* ) 是等效半径。但在 DEM 里直接套这个公式会有一个问题:它假设接触是静态的,而你的颗粒在不停运动。常见做法是在computeForce里做一个分段判断:当颗粒对处于压缩状态(重叠量 δ > 0)时,法向力用 Hertz 接触力加上一个额外的粘附吸引力;当颗粒对开始分离(δ < 0 但还没断开)时,法向力用一个负的拉力,直到拉力超过拉脱力阈值才把接触标记为断开。
// CohesionContactModel.cpp 中的 computeForce 片段 void CohesionContactModel::computeForce(Contact* contact, const Particle& p1, const Particle& p2) { double delta = contact->getOverlap(); // 当前重叠量,压缩为正 double R_eff = contact->getEffectiveRadius(); // 等效半径 double E_eff = contact->getEffectiveYoungsModulus(); // Hertz 法向弹性力 double F_hertz = (4.0/3.0) * E_eff * sqrt(R_eff) * pow(delta, 1.5); // 粘附力:基于 JKR 的简化形式,在压缩和拉伸区都施加吸引力 double F_adhesion = 0.0; if (delta > 0.0) { // 压缩区:粘附力随重叠量增加而缓慢增加,这里用线性近似 F_adhesion = -m_adhesionEnergyDensity * M_PI * R_eff * (1.0 + m_pullOffMultiplier * delta / R_eff); } else { // 拉伸区:粘附力保持为拉脱力,直到超过阈值断开 double pullOffForce = 1.5 * M_PI * m_adhesionEnergyDensity * R_eff; F_adhesion = -pullOffForce * m_pullOffMultiplier; // 如果拉力超过拉脱力,标记接触断开 if (fabs(F_adhesion) > pullOffForce * 1.5) { contact->setBroken(true); } } // 法向总力 = Hertz 排斥 + 粘附吸引 double F_normal = F_hertz + F_adhesion; // 切向力(简化:仅库仑摩擦,未启用切向粘附时) double F_tangential = contact->getTangentialStiffness() * contact->getTangentialOverlap(); double F_t_max = contact->getFrictionCoefficient() * fabs(F_normal); if (fabs(F_tangential) > F_t_max) { F_tangential = copysign(F_t_max, F_tangential); } // 将力写回接触对象 contact->setNormalForce(F_normal); contact->setTangentialForce(F_tangential); }逻辑说明:先算 Hertz 排斥力,再算粘附吸引力,两者相加得到法向总力。参数说明:m_adhesionEnergyDensity从 UI 读取,单位是 J/m²,典型值范围从 0.001(弱粘)到 0.1(强粘);m_pullOffMultiplier默认 1.0,如果你发现颗粒粘得太牢或者太松,先调这个,别急着改公式。注意contact->setBroken(true)这个调用,不同版本的 API 可能叫deactivate或者setInactive,查一下头文件里的方法名。这段代码是最小实现,没有考虑切向粘附、滚动阻力、以及粘附力对切向摩擦的影响,但已经能让你的颗粒在仿真里“抱团”了。
2.5 编译与加载:CMake配置和插件注册的检查点
模版里的CMakeLists.txt通常已经写好了大部分内容,但你需要确认几个关键路径:EDEM 的 API 头文件目录、库文件目录、以及输出插件的后缀名(Windows 下是.dll,Linux 下是.so)。一个常见的 CMake 片段如下:
cmake_minimum_required(VERSION 3.10) project(CohesionContactModel) # 设置 EDEM API 路径,根据你的安装位置修改 set(EDEM_API_DIR "C:/Program Files/EDEM/API" CACHE PATH "EDEM API directory") set(EDEM_LIB_DIR "C:/Program Files/EDEM/API/lib" CACHE PATH "EDEM library directory") include_directories(${EDEM_API_DIR}/include) link_directories(${EDEM_LIB_DIR}) add_library(CohesionContactModel SHARED src/CohesionContactModel.cpp src/CohesionContactModelParameters.cpp src/PluginEntry.cpp ) target_link_libraries(CohesionContactModel ${EDEM_LIB_DIR}/edem_api.lib )参数说明:EDEM_API_DIR和EDEM_LIB_DIR必须改成你机器上的实际路径,别直接抄。add_library的SHARED表示生成动态库,EDEM 在启动时会扫描插件目录加载。编译成功后,把生成的.dll或.so复制到 EDEM 的插件目录(通常在安装目录下的plugins文件夹),重启 EDEM,在接触模型下拉列表里应该能看到你的模型名字。如果看不到,检查PluginEntry.cpp里的导出宏是否正确,Windows 下需要__declspec(dllexport),Linux 下需要extern "C"和可见性属性。这一步翻车的人最多,现象是编译零错误但界面里没有,原因九成是导出符号没写对。
3. 避坑指南:凝聚力API开发中最容易翻车的五个地方
3.1 颗粒粘住了但一碰就飞:时间步长和刚度不匹配
现象:仿真跑起来后,颗粒确实粘在一起了,但过几个时间步就突然弹开,速度大得离谱,能量不守恒。原因:凝聚力模型引入了额外的吸引力,相当于增加了系统的等效刚度,而你用的时间步长还是基于 Hertz 接触算的。粘附力在接触断开瞬间会产生一个力突变,如果时间步太大,求解器会把这个突变放大成速度突变。解决:把时间步长乘以 0.5 到 0.8 的系数,或者启用 EDEM 的自适应时间步。更稳妥的做法是在computeForce里对粘附力做平滑处理,比如在断开前 10% 的拉伸距离内把力线性降到零,而不是直接跳变。
3.2 拉脱力理论值对不上:等效半径算错了
现象:你按 JKR 公式手算的拉脱力是 0.5 mN,但仿真里测出来只有 0.2 mN。原因:getEffectiveRadius()返回的是两个颗粒的等效半径 ( R^* = \frac{R_1 R_2}{R_1 + R_2} ),但如果你在代码里用了算术平均或者直接用了颗粒1的半径,结果就会偏。另外,如果颗粒和几何体接触,几何体的半径应该视为无穷大,等效半径就是颗粒半径。解决:在onContactStart里打印一次R_eff,和手算值对比。如果对不上,检查 API 文档里getEffectiveRadius的定义,有些版本返回的是调和平均,有些是几何平均,别想当然。
3.3 参数改了但仿真没变化:UI注册和成员变量没同步
现象:你在 EDEM 界面里把AdhesionEnergyDensity从 0.01 改成 0.05,重新跑仿真,结果颗粒行为一模一样。原因:参数注册时绑定的成员变量指针和computeForce里实际读取的变量不是同一个,或者你在构造函数里把成员变量初始化成了硬编码值,覆盖了 UI 的输入。解决:在computeForce开头加一行日志输出当前m_adhesionEnergyDensity的值,确认它和 UI 里填的一致。如果不一致,检查registerScalarParameter的第二个参数是不是写成了局部变量的地址,或者是不是在registerParameters之后又调用了某个重置函数。
3.4 切向粘附导致颗粒自旋失控:扭矩没做限幅
现象:开启切向粘附后,颗粒开始疯狂自旋,角速度越来越大,最后仿真崩溃。原因:切向粘附力会产生一个扭矩,如果你没有对这个扭矩做限幅,或者切向粘附力的计算里用了错误的力臂,扭矩会正反馈放大。解决:在计算切向粘附力时,把力臂限制在接触半径以内,并且对切向力本身做库仑摩擦上限的约束。另外,切向粘附的刚度不要设得比法向刚度还大,常见做法是切向刚度取法向刚度的 0.5 到 0.8 倍。
3.5 多线程下的数据竞争:历史变量被覆盖
现象:单线程跑得好好的,一开多线程(EDEM 支持并行计算),颗粒的粘附行为就变得随机,有时候粘有时候不粘。原因:你在computeForce里用了类的成员变量来存历史数据,但多个线程同时调用同一个接触模型实例,成员变量被并发读写。解决:把所有和单个接触对相关的历史变量放到接触对象的用户数据区(contact->getUserData()或者类似的接口),而不是放在模型类的成员变量里。模型类的成员变量只用来存从 UI 读来的全局参数,那些是只读的,多线程安全。
4. 验证与调参:怎么确认你的凝聚力模型真的算对了
4.1 用双颗粒拉拔仿真做基准测试
最直接的验证方法:建两个球体,给它们一个初始重叠,然后让它们以恒定速度分离,记录法向力随分离距离的变化曲线。你的模型应该给出一个峰值拉力(拉脱力),然后力迅速降到零。把这个峰值和 JKR 理论值对比,误差在 10% 以内就算合格。如果偏差大,先检查等效半径和粘附能量密度的单位换算——J/m² 和 mJ/m² 差一千倍,很多人在这里翻车。常见做法是在仿真里输出接触力和重叠量的时间序列,用 Python 画个图,和理论曲线叠在一起看。
4.2 参数敏感性分析:粘附能量密度和拉脱力倍率怎么调
一旦基准测试过了,下一步就是调参。粘附能量密度AdhesionEnergyDensity是主控参数,它直接线性影响拉脱力。拉脱力倍率PullOffMultiplier是修正系数,用来补偿理论模型和实际颗粒形状、表面粗糙度之间的差异。我一般会先固定倍率为 1.0,扫描能量密度从 0.001 到 0.1,看颗粒团的崩塌角或者抗拉强度怎么变。如果仿真里颗粒粘得太牢,先把倍率降到 0.8 试试,而不是直接改能量密度,因为能量密度还影响压缩区的粘附行为。下面是一个简单的参数扫描表格,你可以照着填自己的数据:
| 能量密度 (J/m²) | 倍率 | 拉脱力仿真值 (mN) | 拉脱力理论值 (mN) | 相对误差 |
|---|---|---|---|---|
| 0.01 | 1.0 | 0.48 | 0.50 | 4% |
| 0.05 | 1.0 | 2.35 | 2.50 | 6% |
| 0.05 | 0.8 | 1.88 | 2.00 | 6% |
| 0.10 | 1.0 | 4.90 | 5.00 | 2% |
注意:理论值按 ( F = 1.5 \pi \Delta\gamma R^* ) 计算,等效半径取 0.5 mm。如果你的误差超过 15%,检查一下时间步长是不是太大,或者接触刚度是不是设得太软。
4.3 从单接触到大颗粒团:尺度放大时的三个检查点
当你把验证过的模型用到几万个颗粒的体系时,有三件事必须重新检查。第一,时间步长。颗粒越多,系统的最高频率可能越高,原来单接触能用的步长在大体系里可能不稳定。第二,邻居搜索的更新频率。凝聚力会让颗粒团变得紧密,邻居列表如果更新太慢,会漏掉新形成的接触。第三,能量耗散。粘附力在断开时释放的能量如果太大,会让局部温度(如果开了热耦合)或者速度异常升高。我一般会在放大体系之前,先跑一个 5000 颗粒的小体系,确认颗粒团的抗拉强度或者休止角和实验值对得上,再往上加。
5. 进阶技巧:把凝聚力模型和液桥力、滚动阻力耦合起来
如果你做的是湿颗粒或者高湿度粉体,光靠 JKR 凝聚力是不够的,液桥力往往占主导。液桥力的经典模型是 ( F_{liquid} = 2\pi \gamma R^* \cos\theta ),其中 ( \gamma ) 是液体表面张力,( \theta ) 是接触角。你可以在computeForce里把液桥力和 JKR 粘附力叠加,但要注意两者的作用距离不同:液桥力在颗粒分离到一定距离(比如 0.1 倍颗粒半径)时才会断开,而 JKR 粘附力在接触断开瞬间就消失了。常见做法是给液桥力单独维护一个历史变量,记录液桥是否已经断裂。
滚动阻力是另一个容易被忽略的耦合项。颗粒粘在一起后,滚动阻力会显著影响颗粒团的剪切行为。你可以在模版里加一个简单的滚动阻力扭矩:( M_{rolling} = -\mu_{rolling} F_{normal} R^* \omega_{rel} / |\omega_{rel}| ),其中 ( \mu_{rolling} ) 是滚动摩擦系数,( \omega_{rel} ) 是相对角速度。这个扭矩要加到接触对象的扭矩累加器里,而不是直接改角速度。参数方面,滚动摩擦系数一般取 0.01 到 0.1,湿颗粒取大值,干颗粒取小值。
最后说一个我自己的习惯:每次改完凝聚力模型,先别急着跑大仿真,用两个颗粒的拉拔测试跑 100 个时间步,把接触力和重叠量打印到控制台,肉眼扫一遍曲线。如果曲线光滑、峰值合理、断开干脆,再往下做。这个习惯帮我省了无数次重跑大案例的时间。希望帮到你。
本文还有配套的精品资源,点击获取