简介:这份资源是面向离散元模拟开发者与颗粒动力学研究者的EDEM凝聚力接触模型API编程模版,针对颗粒间因分子作用力产生团聚、粘附等行为难以自定义建模的问题,提供可参考的二次开发骨架。压缩包共7个文件,以3个cpp源文件与3个h头文件为主,另含1个txt参数配置文本,整体约8KB,源码与配置分离,便于对照阅读与移植。内容围绕初始化参数设置、自定义凝聚力计算、接触事件处理、粒子属性设定及输入输出等模块展开,可帮助读者理解如何借助EDEM底层接口扩展接触力模型。目前已有786人学习下载,适合具备一定C++基础、希望深入掌握EDEM API并开发定制化颗粒模拟方案的中高级用户参考实践。
1. 从一份“凝聚力接触模型API模版”说起:它到底解决什么问题
如果你正在用EDEM做粉体、土壤、药片或者电池浆料的仿真,大概率绕不开一个尴尬:软件自带的接触模型不够用了。JKR、Hertz-Mindlin这些内置模型能覆盖大部分场景,但一旦涉及颗粒间存在液桥、烧结颈、或者需要自定义的粘附力-位移曲线,内置模型就变成了黑匣子——你只能调那几个暴露出来的系数,改不了内核逻辑。
这时候“凝聚力接触模型API编程模版”就派上用场了。它本质上是一套让你用C++写自定义接触力模型的骨架代码,编译成动态库后挂到EDEM的求解器里。你不需要改EDEM本体,只需要实现几个虚函数:计算法向力、切向力、以及能量耗散。听起来简单,但第一次上手的人往往卡在编译环境、API版本匹配、以及力-位移曲线的数值稳定性上。这篇笔记就按我实际跑通的路径,把选型理由、代码骨架、参数标定和踩坑记录一次讲清楚。适合已经会用EDEM做基础仿真、想往二次开发走的人。
2. 凝聚力模型API的骨架拆解:从虚函数到力-位移曲线
2.1 为什么不用内置JKR而要走API
内置JKR模型基于Hertz接触理论,把粘附力处理成表面能的函数。它的假设是:颗粒接触时,粘附力在分离过程中保持恒定,直到达到临界分离距离。这个假设对干粉还行,但对湿颗粒或者有塑性变形的物料就不准了。比如你模拟的是湿法制粒,颗粒间有液桥,液桥力随距离是非线性衰减的,JKR根本表达不了。
走API的好处是你自己定义力-位移关系。常见做法是写一个分段函数:接触阶段用Hertz弹性力加阻尼,分离阶段用指数衰减的凝聚力。这样液桥的毛细力、粘性力都能塞进去。代价是你得自己处理数值稳定性——力对位移的导数不能突变,否则时间步会崩。
2.2 模版里必须实现的三个核心函数
EDEM的接触模型API通常暴露一个基类,你需要继承它并重写三个方法。下面是我常用的骨架,类名和函数签名按你拿到的头文件为准,这里用通用写法:
// 自定义凝聚力接触模型骨架 class CohesiveContactModel : public ContactModelBase { public: // 计算法向力:输入重叠量、相对速度,输出法向力 virtual double computeNormalForce(double overlap, double normalVel) override { // 弹性部分:Hertz理论,E*为等效杨氏模量,R*为等效半径 double elastic = (4.0/3.0) * E_star * sqrt(R_star * overlap) * overlap; // 阻尼部分:仅当颗粒接近时激活,防止能量注入 double damping = (normalVel < 0) ? -dampingCoef * normalVel : 0.0; // 凝聚力:当重叠量小于临界值,附加一个指数衰减的拉力 double cohesion = 0.0; if (overlap < criticalOverlap) { cohesion = -cohesionPeak * exp(-decayRate * (criticalOverlap - overlap)); } return elastic + damping + cohesion; } // 计算切向力:简化用Mindlin理论加库仑摩擦上限 virtual double computeTangentialForce(double overlap, double tangentialVel) override { double ks = 8.0 * G_star * sqrt(R_star * overlap); // 切向刚度 double ft = ks * tangentialOverlap; // 切向弹性力 double ftMax = frictionCoef * fabs(normalForce); // 库仑上限 return (fabs(ft) > ftMax) ? copysign(ftMax, ft) : ft; } // 能量耗散:用于能量审计,通常返回阻尼做的功 virtual double computeEnergyDissipation() override { return dissipatedEnergy; } private: double E_star, G_star, R_star; // 等效模量、剪切模量、半径 double dampingCoef; // 法向阻尼系数 double cohesionPeak, decayRate; // 凝聚力峰值和衰减率 double criticalOverlap; // 凝聚力作用的临界重叠量 double frictionCoef; // 摩擦系数 double normalForce; // 缓存上一步法向力 double dissipatedEnergy = 0.0; };逻辑说明:computeNormalForce里弹性项用Hertz公式,阻尼项只在颗粒相互接近时(normalVel < 0)激活,避免分离时阻尼把颗粒往回拉。凝聚力项用指数衰减,criticalOverlap控制作用范围,cohesionPeak是最大拉力。computeTangentialForce里切向刚度按Mindlin理论算,但必须用库仑摩擦上限截断,否则切向力会无限增大。参数说明:E_star和G_star由颗粒材料属性换算,R_star是两颗粒等效半径,dampingCoef一般取0.1到0.3倍临界阻尼,cohesionPeak和decayRate需要根据实际物料标定。
2.3 编译成动态库并挂载的完整命令
写好的C++文件需要编译成EDEM能加载的动态库。Linux下用g++,Windows下用Visual Studio的cl.exe。关键是头文件路径和EDEM的API库要链接对。下面是我在Linux环境下的编译命令:
# 编译自定义接触模型为动态库 g++ -shared -fPIC -O2 \ -I$EDEM_API_DIR/include \ -I$EDEM_API_DIR/include/contact_models \ cohesive_model.cpp \ -L$EDEM_API_DIR/lib -lEDEMContactAPI \ -o libcohesive_model.so # 检查符号是否导出成功 nm -D libcohesive_model.so | grep computeNormalForce逻辑说明:-shared -fPIC生成位置无关的动态库,-I指定EDEM API头文件目录,-L和-l链接EDEM的接触模型库。编译完用nm -D确认computeNormalForce等符号是T(已定义)而不是U(未定义)。参数说明:$EDEM_API_DIR是你安装EDEM时API文件夹的路径,通常在安装目录下的api或src子目录。如果链接时报undefined reference,八成是库名或路径不对,用ldd libcohesive_model.so看依赖是否都能找到。
挂载时在EDEM的接触模型设置里选“自定义”,指向生成的.so或.dll文件,然后重启求解器。如果求解器启动时报“无法加载库”,先检查动态库的架构是否和EDEM一致(64位对64位),再检查依赖的EDEM库版本是否匹配。
3. 参数标定与验证:让凝聚力模型跑出可信结果
3.1 用单轴压缩试验反推凝聚力峰值
模型写好了,参数怎么定?最可靠的办法是拿实验数据反推。常见做法是做一个单轴压缩仿真:生成一柱颗粒,上下加压板,记录压力-位移曲线。然后调整cohesionPeak和decayRate,让仿真曲线的峰值和实验对得上。
具体步骤:先固定decayRate为一个经验值(比如1000 1/m),然后二分法调cohesionPeak。每次仿真跑5000步,提取压板上的反力。如果仿真峰值比实验高,减小cohesionPeak;如果曲线下降段太陡,减小decayRate。一般迭代5到8次就能收敛。
# 用Python脚本自动跑参数扫描(调用EDEM命令行接口) import subprocess import numpy as np cohesion_peak = 50.0 # 初始猜测,单位N decay_rate = 1000.0 # 初始猜测,单位1/m target_peak = 120.0 # 实验测得的峰值力,单位N for i in range(8): # 生成EDEM输入文件,替换参数 with open('template.dem', 'r') as f: content = f.read() content = content.replace('COHESION_PEAK', str(cohesion_peak)) content = content.replace('DECAY_RATE', str(decay_rate)) with open('run.dem', 'w') as f: f.write(content) # 运行EDEM求解器 subprocess.run(['edem_solver', '-i', 'run.dem', '-o', 'result.txt']) # 读取峰值力 data = np.loadtxt('result.txt', skiprows=1) sim_peak = np.max(data[:, 1]) # 更新参数 error = sim_peak - target_peak if abs(error) < 1.0: break cohesion_peak -= error * 0.5 # 简单比例控制 print(f"标定结果: cohesionPeak={cohesion_peak:.2f}, decayRate={decay_rate:.2f}")逻辑说明:这个脚本用比例控制迭代cohesionPeak,每次跑完仿真读峰值力,和实验值比较后调整。decayRate先固定,等峰值对上后再单独调下降段。参数说明:target_peak来自实验,cohesion_peak初始值可以取实验峰值的1/3左右,decay_rate一般取颗粒直径的倒数量级。注意每次仿真前要清空结果文件,否则会读到旧数据。
3.2 时间步与阻尼系数的匹配关系
凝聚力模型最容易翻车的地方是时间步太大。因为凝聚力在分离阶段是拉力,如果时间步超过颗粒振荡周期的1/10,拉力会把颗粒拉飞,能量不守恒。我一般用Rayleigh波时间步的0.2倍作为上限:
# 计算临界时间步 # t_crit = pi * R * sqrt(rho/G) / (0.163*nu + 0.876) # R: 颗粒半径, rho: 密度, G: 剪切模量, nu: 泊松比假设颗粒半径1mm,密度2500 kg/m³,剪切模量1e7 Pa,泊松比0.3,算出来临界时间步约1.2e-6秒。实际用2e-7秒。如果仿真中出现颗粒速度突然跳到几百m/s,先检查时间步是不是太大了。阻尼系数dampingCoef也不能太大,一般取临界阻尼的0.1到0.3倍,太大相当于人为加了很多粘性,凝聚力峰值会被掩盖。
3.3 用能量审计判断模型是否稳定
跑完仿真后,EDEM会输出能量统计。重点看三个量:总动能、总势能、阻尼耗散能。如果总动能在没有外力的情况下持续增长,说明模型在注入能量,通常是凝聚力项在分离时做了正功。解决办法是给凝聚力项加一个“只拉不推”的判断:当颗粒相对速度为正(分离)时,凝聚力才激活;接近时凝聚力为零。
// 修正后的凝聚力项:只在分离时激活 double cohesion = 0.0; if (normalVel > 0 && overlap < criticalOverlap) { cohesion = -cohesionPeak * exp(-decayRate * (criticalOverlap - overlap)); }这个改动能解决大部分能量不守恒的问题。另外,能量审计里阻尼耗散能应该是单调递增的,如果出现下降,说明阻尼系数符号写反了。
4. 避坑记录:凝聚力模型API开发中的五个典型翻车现场
4.1 编译通过但求解器加载时报“符号未找到”
现象:nm -D能看到符号,但EDEM加载时提示undefined symbol: _Z17computeNormalForcedd。原因:C++函数名经过name mangling,EDEM的加载器可能按C风格符号查找。解决:在函数声明外加extern "C",或者用-fno-mangle编译。但更稳妥的做法是查EDEM API文档,确认它要求的符号命名规则。我一般直接在头文件里用extern "C"包住导出函数。
4.2 凝聚力峰值设大了,颗粒直接穿模
现象:仿真开始几步,颗粒重叠量变成负的(分离),然后突然消失。原因:cohesionPeak超过了弹性力能承受的范围,颗粒被拉穿。解决:先算一下最大弹性力,F_elastic_max = (4/3)*E_star*sqrt(R_star*overlap_max)*overlap_max,cohesionPeak不要超过这个值的0.5倍。另外把criticalOverlap设小一点,让凝聚力只在很小范围内起作用。
4.3 切向力不截断导致颗粒自旋失控
现象:颗粒在接触后转速越来越大,最后飞出去。原因:computeTangentialForce里忘了用库仑摩擦上限截断,切向力随切向重叠量线性增长,没有上限。解决:每次计算切向力后,用frictionCoef * fabs(normalForce)截断。注意normalForce要用当前步的法向力,不能缓存旧值。
4.4 时间步太大导致能量爆炸
现象:总动能曲线指数上升,颗粒速度达到几百m/s。原因:时间步超过临界值,凝聚力项的刚度导致数值不稳定。解决:按Rayleigh波时间步的0.2倍设置,或者用EDEM的自动时间步功能,但自动时间步在自定义模型下可能不准,建议手动设小一点。如果还是爆炸,检查凝聚力项的指数衰减率decayRate是不是太大了,导致力对位移的导数突变。
4.5 参数标定时仿真曲线和实验对不上
现象:峰值力对上了,但下降段差很多。原因:decayRate控制下降段斜率,但实验中的液桥力还受液桥体积影响,单指数衰减不够。解决:用双指数衰减,或者分段线性。我一般先用单指数跑,如果下降段误差超过20%,再改成双指数:cohesion = -A*exp(-k1*d) - B*exp(-k2*d),其中A+B=cohesionPeak,k1和k2分别控制快慢衰减。
5. 进阶技巧:用Python脚本批量生成API模版并自动编译
如果你需要同时开发多个接触模型,手动改代码、编译、挂载太慢。我一般写一个Python脚本,从模板生成C++文件,自动替换参数,然后调用编译器。下面是一个可复用的脚本骨架:
# 批量生成并编译自定义接触模型 import os import subprocess from string import Template # C++模板,用$占位符 cpp_template = Template(''' #include "contact_model_base.h" class ${model_name} : public ContactModelBase { public: virtual double computeNormalForce(double overlap, double normalVel) override { double elastic = (4.0/3.0) * ${E_star} * sqrt(${R_star} * overlap) * overlap; double damping = (normalVel < 0) ? -${damping_coef} * normalVel : 0.0; double cohesion = 0.0; if (normalVel > 0 && overlap < ${critical_overlap}) { cohesion = -${cohesion_peak} * exp(-${decay_rate} * (${critical_overlap} - overlap)); } return elastic + damping + cohesion; } // ... 其他函数省略 }; ''') # 参数组合 params_list = [ {'model_name': 'CohesiveModel_A', 'E_star': '1e7', 'R_star': '0.001', 'damping_coef': '0.2', 'cohesion_peak': '50.0', 'decay_rate': '1000', 'critical_overlap': '0.0001'}, {'model_name': 'CohesiveModel_B', 'E_star': '5e6', 'R_star': '0.002', 'damping_coef': '0.15', 'cohesion_peak': '80.0', 'decay_rate': '800', 'critical_overlap': '0.0002'}, ] for params in params_list: # 生成C++文件 cpp_code = cpp_template.substitute(params) filename = f"{params['model_name']}.cpp" with open(filename, 'w') as f: f.write(cpp_code) # 编译 cmd = ['g++', '-shared', '-fPIC', '-O2', '-I/opt/edem/api/include', filename, '-L/opt/edem/api/lib', '-lEDEMContactAPI', '-o', f"lib{params['model_name']}.so"] subprocess.run(cmd, check=True) print(f"编译完成: lib{params['model_name']}.so")逻辑说明:用string.Template做占位符替换,比format更安全,因为C++代码里有花括号。每个参数组合生成一个.cpp文件,然后调用g++编译。参数说明:E_star和R_star按实际物料填,damping_coef取0.1到0.3,cohesion_peak和decay_rate按标定结果填,critical_overlap一般取颗粒半径的0.1倍。注意编译前确认/opt/edem/api/include路径存在,否则会报找不到头文件。
这个脚本跑通后,你可以把参数扫描、编译、仿真、后处理串成一条流水线。我一般还会加一个自动检查:编译完用nm -D确认符号导出,然后跑一个10步的短仿真看求解器能不能加载。如果加载失败,脚本直接报错退出,不用手动去翻日志。
最后说一个血泪教训:每次改完代码,一定要先跑一个单颗粒对撞的极简案例,确认力-位移曲线和手算一致,再去跑大规模仿真。我早期偷懒跳过这一步,结果在一个两万颗粒的案例里跑了三天才发现凝聚力符号写反了,颗粒全被推开而不是吸在一起。现在我的习惯是:任何自定义模型,先过单颗粒测试,再过单轴压缩,最后才上生产案例。希望帮到你。
本文还有配套的精品资源,点击获取