Abaqus为用户提供了强大而又灵活的用户子程序接口和应用程序接口,包括边界条件、荷载条件、接触条件、材料特性以及利用用户子程序和其它应用软件进行数据交换等等。
Fortran语言的可读性差,基于Fortran的开发和维护很困难,因此考虑采用C++进行子程序开发,在Abaqus6.13之后的版本,对于使用C++编写Abaqus用户子程序是完全支持的。在Abaqus中通过各种方式提交C++编写的子程序(.C .cpp)任务后,会首先调用C++编译器 ,编译子程序,然后调用Fortran编译器 ,链接子程序。
而在二次开发方面,Python接口 使用起来非常方便,这个在之前的文章中也有介绍Python提取ABAQUS求解结果odb中的变量信息 、点阵结构仿真计算与ABAQUS二次开发 。但是在进行后处理时对于网格遍历查询等操作用到大量for循环并且没有切片功能,Python运行很耗时,因此必要时可以采用C++接口代替,提高运行效率,C++接口面向有高性能需求的用户。
Abaqus的子程序需要使用Fortran编译器进行编译和链接,因此需要有Fortran编译器,而Fortran也依赖C/C++编译器。Abaqus为 Intel Fortran Compiler 提供了程序接口,Intel Fortran Compiler已被集成在Intel Parallel Studio XE中。C++ Compiler 则采用 Visual Studio 来配置。
已经安装了 ABAQUS 2019,需要配置Fortran和C++编译器。先安装 Visual Studio 2019,勾选C++桌面开发。
接下来下载安装Intel Parallel Studio XE 2020,安最简装只需要 Fortran Compile就可以了,还可以加装MPI SDK,如果检测到没有安装VS2019,会在 Fortran Compiler 选项下多出一个VS2019Shell子项,表示需要安装 Visual Studio 2019 命令行构件工具集。这里我已经安装过了,所以硬盘要求空间显示0。
在安装的最后,安装程序将自动完成Visual Studio与Intel Fortran Compiler的关联工作。安装完成后打开Visual Studio 2019,发现可以新建Fortran项目了。
后续编程中我们并不需要去编写Fortran工程,只需要用到Fortran编译器和链接器。接下来就是关联 Abaqus 配置,让Abaqus可以识别和使用 Fortran Compiler 和 C++ Compiler。
查找以下文件的路径:
vcvars64.bat文件。此版本默认路径为C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.batifortvars.bat文件。此版本默认路径为C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\bin\ifortvars.batlauncher.bat。默认路径为C:\SIMULIA\CAE\2019\win_b64\resources\install\cae\launcher.bat网上教程通常的做法是在文件 launcher.bat 中加入以下两条命令,在启动abaqus前先调用 vcvars64.bat 和 ifortvars.bat 识别系统编译环境。
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
call "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\bin\ifortvars.bat" intel64 vs2019
然而这只对从程序图标启动Abaqus有效,从命令行启动Abaqus用到的命令是 C:\SIMULIA\Commands\abaqus.bat。打开abaqus.bat 和 launcher.bat 发现二者都调用了"C:\SIMULIA\Commands\abq2019.bat",即最终的启动脚本,因此修改 abq2019.bat 可以一劳永逸。在abq2019.bat 中加入
@call ifortvars.bat intel64 vs2019
这里通过 ifortvars.bat 加载 vs2019 和 vcvars64.bat,不需要再单独加载 vcvars64.bat,需要把C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\bin 加入系统环境变量。或者写全路径名
@call "C:\Program Files (x86)\IntelSWTools\compilers_and_libraries_2020.4.311\windows\bin\ifortvars.bat" intel64 vs2019
检查Abaqus是否链接成功,使用 abaqus information=system 命令,可以看到在Abaqus命令生效前成功加载了 Intel Fortran 和 Visual Studio 2019 编译环境。
找到了 Intel Fortran 编译器和链接器,就表示配置成功了。这里提示没有找到 C++ Compiler 没有关系,这里是一个环境变量兼容性bug(可通过重装Abaqus解决),实际上Linker配置正确已经可以调用C++ Compiler 了。
Microsoft (R) Incremental Linker Version 14.29.30139.0
Intel Fortran Compiler 19.1
也可以通过 Abaqus Verification 检查,
-----------------------------------------------------------------------------
Abaqus make utility with Fortran
...PASS
Continuing...
-----------------------------------------------------------------------------
Abaqus make utility with C++
...PASS
Continuing...
-----------------------------------------------------------------------------
接下来我们需要配置 Visual Studio 工程,调用 Abaqus 的API编写C++程序。需要知道并不能直接使用 Visual Studio 的 VC++ 来编译包含 Abaqus API 的C++工程,即使配置了正确的包含路径和链接库路径也无法编译成功,而必须使用 Abaqus 的构建工具 Abaqus make 来进行编译。
因此这里配置 Visual Studio 工程的目的是为了方便编写源代码,高亮显示 Abaqus API 和进行必要的代码提示,而在 build 时在命令行使用 Abaqus make job=xxx.cpp 完成。所以在 Visual Studio 工程属性中只需要配置附加包含目录即可,即需要 Include Abaqus C++ API 。
一级接口的头文件都在 C:\Program Files\Dassault Systemes\SimulationServices\V6R2019x 路径下,但是包含了多级文件夹,使用起来不方便;而
C:\Program Files\Dassault Systemes\SimulationServices\V6R2019x\win_b64\code\include 包含了所有需要的头文件名和路径,是一个二级接口,也是源文件直接引用的.h文件集合。比如在 #include <odb_API.h> 时会跳转到 C:\Program Files\Dassault Systemes\SimulationServices\V6R2019x\win_b64\code\include 下的 odb_API.h 文件,其内容是 #include "SMAOdb\PublicInterfaces\odb_API.h",即把 SMAOdb\PublicInterfaces\odb_API.h 文件 include 进来并展开,所以必须要添加上面两个附加包含路径才可以正确识别。
接下来就可以高亮显示,比如odbString\odbSet等
还可以使用速览定义查看类型定义和函数用法,包括参数的定义从代码看都很明确,比慢慢查help手册方便很多,可以快速上手编程了,怕记不住类型名字还有代码提示。
接下来使用 Abaqus C++ API 测试,以后处理部分为例。要正确链接,cpp主文件不能包含 C++ 的mian()函数,而必须以名 ABQmain() 函数代替。比如新建一个测试文件 cpp_test.cpp 如下
#include <odb_API.h>
#include <iostream>
using namespace std;
int ABQmain(int argc, char **argv)
{
//Insert user code here
cout << "This is compiled by abaqus 2019!!!" << endl;
odb_initializeAPI();
odb_String odbPath;
bool ifOdbName = false;
odb_String elsetName;
cin.get();
return 0;
}
然后在命令行中输入 abaqus make job=cpp_test.cpp 进行编译,可以看到编译成功且得到 cpp_test.exe 文件,运行exe成功得到黑框,链接正常。
Abaqus JOB cpp_test
Begin Compiling User Post-Processing Program
2/10/2022 4:41:38 PM
用于 x64 的 Microsoft (R) C/C++ 优化编译器 19.16.27045 版
版权所有(C) Microsoft Corporation。保留所有权利。
cpp_test.cpp
End Compiling User Post-Processing Program
2/10/2022 4:41:39 PM
Begin Compiling User Post-Processing Program
2/10/2022 4:41:39 PM
用于 x64 的 Microsoft (R) C/C++ 优化编译器 19.16.27045 版
版权所有(C) Microsoft Corporation。保留所有权利。
main_7188.C
End Compiling User Post-Processing Program
2/10/2022 4:41:40 PM
Begin Linking User Post-Processing Program
2/10/2022 4:41:40 PM
2/10/2022 4:41:40 PM
End Linking User Post-Processing Program
Abaqus JOB cpp_test COMPLETED
以下实例来自 Abaqus Document 的 Abaqus | Scripting | Accessing an Output Database | Using C++ to access an output database | Example programs that access data from an output database 章节,确定odb中 von-mises 应力的最大值及其位置。
/***************************************************************
Finding the maximum value of von Mises stress
odbMaxMises.cpp
Code to determine the location and value of the maximum
von-mises stress in an output database.
Usage: abaqus odbMaxMises -odb odbName -elset(optional)
elsetName
Requirements:
1. -odb : Name of the output database.
2. -elset : Name of the assembly level element set.
Search will be done only for element belonging
to this set. If this parameter is not provided,
search will be performed over the entire model.
3. -help : Print usage
****************************************************************/
#if (defined(HP) && (! defined(HKS_HPUXI)))
#include <iostream.h>
#else
#include <iostream>
using namespace std;
#endif
#include <odb_API.h>
#include <sys/stat.h>
/*
***************
utility functions
***************
*/
bool fileExists(const odb_String &string);
void rightTrim(odb_String &string, const char* char_set);
void printExecutionSummary();
/***************************************************************/
int ABQmain(int argc, char **argv)
{
odb_String odbPath;
bool ifOdbName = false;
odb_String elsetName;
bool ifElset = false;
odb_Set myElset;
odb_String region = "over the entire model";
char msg[256];
char *abaCmd = argv[0];
for (int arg = 0; arg < argc; arg++)
{
if (strncmp(argv[arg], "-o**", 2) == 0)
{
arg++;
odbPath = argv[arg];
rightTrim(odbPath, ".odb");
if (!fileExists(odbPath))
{
cerr << "**ERROR** output database " << odbPath.CStr()
<< " does not exist\n" << endl;
exit(1);
}
ifOdbName = true;
}
else if (strncmp(argv[arg], "-e**", 2) == 0)
{
arg++;
elsetName = argv[arg];
ifElset = true;
}
else if (strncmp(argv[arg], "-h**", 2) == 0)
{
printExecutionSummary();
exit(0);
}
}
if (!ifOdbName)
{
cerr << "**ERROR** output database name is not provided\n";
printExecutionSummary();
exit(1);
}
// Open the output database
odb_Odb& myOdb = openOdb(odbPath);
odb_Assembly& myAssembly = myOdb.rootAssembly();
if (ifElset) {
if (myAssembly.elementSets().isMember(elsetName)) {
myElset = myAssembly.elementSets()[elsetName];
region = " in the element set : " + elsetName;
}
else
{
cerr << "An assembly level elset " << elsetName.CStr()
<< " does not exist in the output database :"
<< myOdb.name().CStr() << endl;
myOdb.close();
exit(0);
}
}
// Initialize maximum values.
float maxMises = -0.1;
int numFV = 0;
int maxElem = 0;
odb_String maxStep = "__None__";
int maxFrame = -1;
static const odb_String Stress = "S";
bool isStressPresent = false;
int numBD = 0, numElems = 0, numIP = 0, numComp = 0, position = 0;
// Iterate over all available steps
odb_StepRepository& sRep1 = myOdb.steps();
odb_StepRepositoryIT sIter1(sRep1);
for (sIter1.first(); !sIter1.isDone(); sIter1.next())
{
odb_Step& step = sRep1[sIter1.currentKey()];
cout << "Processing Step: " << step.name().CStr() << endl;
odb_SequenceFrame& frameSequence = step.frames();
int numFrames = frameSequence.size();
for (int f = 0; f < numFrames; f++)
{
odb_Frame& frame = frameSequence[f];
odb_FieldOutputRepository& fieldRep = frame.fieldOutputs();
if (fieldRep.isMember(Stress))
{
isStressPresent = true;
odb_FieldOutput field = fieldRep.get(Stress);
if (ifElset)
field = field.getSubset(myElset);
const odb_SequenceFieldBulkData& seqVal =
field.bulkDataBlocks();
int numBlocks = seqVal.size();
for (int iblock = 0; iblock < numBlocks; iblock++)
{
const odb_FieldBulkData& bulkData = seqVal[iblock];
numBD = bulkData.length();
numElems = bulkData.numberOfElements();
numIP = numBD / numElems;
numComp = bulkData.width();
float* mises = bulkData.mises();
int* elementLabels = bulkData.elementLabels();
int* integrationPoints = bulkData.integrationPoints();
for (int elem = 0; elem < numElems; elem++)
{
for (int ip = 0; ip < numIP; ip++)
{
position = elem * numIP + ip;
float misesData = mises[position];
if (misesData > maxMises)
{
maxMises = misesData;
maxElem = elementLabels[elem];
maxStep = step.name();
maxFrame = frame.incrementNumber();
}
}
}
}
}
}
}
cout << "Num of Elements:"<< numElems << endl;
cout << "Num of ElementNodes:"<< numIP << endl;
cout << "Num of Comp:"<< numComp << endl;
if (isStressPresent)
{
cout << "Maximum von Mises stress " << region.CStr()
<< " is " << maxMises << " in element "
<< maxElem << endl;
cout << "Location: frame # " << maxFrame << " step: "
<< maxStep.CStr() << endl;
}
else
{
cout << " Stress output is not available in the "
<< "output database : " << myOdb.name().CStr() << endl;
}
// close the output database before exiting the program
myOdb.close();
return(0);
}
bool fileExists(const odb_String &string)
{
bool exists = false;
struct stat buf;
if (stat(string.CStr(), &buf) == 0)
exists = true;
return exists;
}
void rightTrim(odb_String &string, const char* char_set)
{
int length = string.Length();
if (string.Find(char_set) == length)
string.append(odb_String(char_set));
}
void printExecutionSummary()
{
cout << " Code to determine the location and value of the\n"
<< " maximum von-mises stress in an output database.\n"
<< " Usage: abaqus odbMaxMises -odb odbName \n"
<< " -elset(optional), -elsetName\n"
<< " Requirements:\n"
<< " 1. -odb : Name of the output database.\n"
<< " 2. -elset : Name of the assembly level element set.\n"
<< " Search will be done only for element \n"
<< " belonging to this set.\n"
<< " If this parameter is not provided, search \n"
<< " will be performed over the entire model.\n"
<< " 3. -help : Print usage\n";
}
编译得到 odbMaxMises.exe ,然后对一个odb文件进行测试,40万单元数遍历的速度相当快。注意这里得到的值是单元的平均值,而CAE中显示的值是积分点上的值,因此会有差别,但是单元位置是相同的,都是35625
https://www.gofarlic.com\ProjectData\ABAQUS_DB\202202-cpp-fortran>odbMaxMises.exe -odb Job-TM-E1500-1MPa.odb
Processing Step: Step-1
Num of Elements:417739
Num of ElementNodes:4
Num of Comp:6
Maximum von Mises stress over the entire model is 8.19462 in element 35625
Location: frame # 10 step: Step-1
后处理的部分在 Abaqus Document 中描述比较详细了,完全可以自主学习和验证。Abaqus官方推荐在后处理二次开发时使用C++接口,面向有高性能需求的用户,比起Python速度可以提升30倍左右,其他情况还是推荐使用Python进行二次开发。
我个人的使用习惯是在CAE中操作一遍流程,保留最简化的模型作为模板,把重复且费时更改的设置部分通过python命令完成,利用shell脚本完成调度求解,而后处理部分对于大文件(超过100000单元)则是通过C++接口访问odb,再把数据传回python程序继续做数据分析。Python的好处是在rpy文件中记录了所有CAE操作的命令,还可以用交互方式运行单条命令,“所见即所得”,可以方便地组织程序结构和工作流;缺点是慢,尤其是涉及大量的网格和节点循环取值(Abaqus的序列并不提供切片等特性,想用numpy加速也不行),而且对于Abaqus内置的函数没有办法进行代码提示和浏览函数原型,因为都编写成了动态库二进制文件(源码保护),参数类型一头雾水只能去查手册了。C++接口的优点就是快,访问的 Field Output 数据都可以组织成连续内存形式,性能大大提升,而且经过配置可以快速查看函数定义和参数类型(接口都在.h文件提供),可操作性很强;缺点是需要配置额外的编译环境,代码需要编译后一次性运行,与CAE交互不方便,需要对Odb层次结构有深入的了解。总之,无论选取那种语言开发,都要勤查手册!