最近,笔者在使用Abaqus的过程中遇到了需要重复计算多种工况的问题,并且需要提取每一个工况下的时间历程数据。如果采用手动提交的方式,则提交过程非常繁琐,并且只能等到前一个模型计算完成之后才能进行后续的计算。因此,笔者很快想到能否在Abaqus中实现inp文件的批量提交。
通过查阅相关资料,笔者发现目前inp文件的批量运算主要有两种实现方法:Bat文件提交方法和Python提交方法。Bat文件提交方法主要是利用了Abaqus Command中的任务提交语句。例如,一个最简单的inp文件提交格式如下所示:
abaqus job=case1.inp cpus=4 inter
为了批量提交inp文件,可以将多个inp文件的提交语句汇总到一个批处理文件(.bat)中,通过执行该bat文件即可完成计算。一个包含多个inp文件的提交命令如下所示:
cmd/c abaqus job=case1.inp cpus=4 inter
cmd/c abaqus job=case2.inp cpus=4 inter
cmd/c abaqus job=case3.inp cpus=4 inter
注意,为了保证inp文件顺序提交,必须使用cmd/c而非call关键字,并且需要使用int关键字(int为interactive的缩写,int关键字用于在运行过程中反馈进程,在隐式计算中输出log文件,还可以保证inp文件顺序提交,防止同时提交时导致计算机资源耗尽)。
Bat文件提交方法使用较为简单,但在计算过程中无法实现其他交互式操作。另一种方法为采用Python脚本批量提交inp文件进行计算,一个最简单的计算脚本如下所示:
# -*- coding: UTF-8 -*-
from abaqus import *
from abaqusConstants import *
#inp文件列表
Jobs_list = ['case1', 'case2', 'case3']
#利用for循环批量提交计算
for i in Jobs_list:
mdb.JobFromInputFile(name = i,
inputFileName = i + '.inp',
numCpus = 4,
numDomains = 4)
mdb.jobs[i].submit()
mdb.jobs[i].waitForCompletion()
上述脚本的计算原理为将inp文件名存放在一个名为Jobs_list的列表中,随后通过mdb对象中的JobFromInputFile方法读取inp文件并创建一个jobs对象,并利用jobs对象中的submit方法提交任务进行计算。注意在提交之后必须使用waitForCompletion方法保证只有当计算完成之后才执行后续语句,否则程序将直接进入下一轮循环,导致inp文件被同时提交计算。
在Abaqus中,Python脚本可以通过两种方式被执行:一种为在打开Abaqus/CAE的情况下,通过菜单栏中的File→Run Script直接运行脚本;另一种方式为在Abaqus Command中执行如下语句:
abaqus cae noGUI=xxx.py
该语句可以在不打开Abaqus/CAE的情况下执行Python脚本。
尽管上述语句给出了利用python批量提交inp文件的实现原理,但在使用过程中仍存在一定的局限性。例如在计算队列中若某一个计算任务意外终止,则会导致整个计算队列终止,无法完成后续计算。此外,笔者还需要在计算完成后顺序输出每一个模型中的时间历程数据。为了解决上述问题,本文拟通过Python脚本实现下述功能:
基于inp文件名顺序提交计算,并检测计算状态,当计算意外终止时自动开始后续任务的计算;
每次计算完成后实时输出该模型对应的时间历程数据。
在利用Python批量处理Abaqus时间历程数据一文中,笔者详细介绍了通过Abaqus中的Session对象获取时间历程数据的实现原理。在本文中,笔者将介绍提取时间历程数据的另一种方法:通过Odb对象直接获取时间历程数据或场输出结果。为了对Odb对象有一个更为清楚的认识,本文给出了输出数据库(Output database)对象的模型图,如图1.1所示。通过该模型图,可以帮助我们理解模型中父对象与子对象之间的关系。
例如,当我们打开或创建一个输出数据库(.odb文件)时,Abaqus会自动创建一个Odb对象。从图1.1中可以看出,Odb对象中包含有模型数据(Model Data)和结果数据(Results Data)。模型数据包含有用于组成装配的部件(Part)和部件实例(Part instance),如节点坐标和单元类型等;结果数据则包含有模型的分析结果,如应力、应变和位移等数据,本文需要提取的时间历程数据同样包含在结果数据中。从图1.1中可以看出,所有的结果数据都包含在Odb对象的成员——Step对象中,而Step对象又同时拥有Frame对象和historyRegion对象,分别用于存储场输出结果和时间历程输出结果。
为了对Abaqus中的Odb对象有更为清晰的认识,本文依照利用Python批量处理Abaqus时间历程数据一文中的方法,通过对象名.__members__命令来输出对象中的所有成员。首先,通过如下命令打开数据库文件(.odb),并输出Odb对象的所有成员:
# -*- coding: UTF-8 -*-
from odbAccess import *
Work_directory = 'C:/Users/Desktop/'
Job_name = 'Model_Case'
odb = openOdb(path = Work_directory + Job_name + '.odb')
odb.__members__
>>['analysisTitle', 'closed', 'customData',
'description', 'diagnosticData',
'isReadOnly', 'jobData', 'materials', 'name',
'parts', 'path', 'profiles', 'readInternalSets',
'rootAssembly', 'sectionCategories', 'sections',
'sectorDefinition', 'steps', 'userData']
print(odb)
>>({'analysisTitle': '', 'closed': False,
'customData': None, 'description': 'DDB object',
'diagnosticData': 'OdbDiagnosticData object',
'isReadOnly': False, 'jobData': 'JobData object',
'materials': 'Repository object',
'name': 'C:/Users/xutian/Desktop/batch analysis/input file/Model_Case1.odb',
'parts': 'Repository object',
'path': 'C:/Users/xutian/Desktop/batch analysis/input file/Model_Case1.odb',
'profiles': 'Repository object', 'readInternalSets': False,
'rootAssembly': 'OdbAssembly object',
'sectionCategories': 'Repository object',
'sections': 'Repository object', 'sectorDefinition': None,
'steps': 'Repository object', 'userData': 'UserData object'})
库名['结构生成的对象名称']
在上面的语句中,结构生成的对象名称可以看作是一个键(key,通常是一个字符串),用于匹配相应的键值(通常是Abqus对象对象,Abaqus object object),这一点与Python的字典数据类型非常类似。
由于模型所有的结果数据均包含在名为steps的库对象中,因此本文继续输出steps对象的相关信息:
print(odb.steps)
>>{'Step-1': 'OdbStep object', 'Step-2': 'OdbStep object'}
MyStep1 = odb.steps['Step-1']
MyStep2 = odb.steps['Step-1']
odb.steps.keys()
>>['Step-1', 'Step-2']
odb.steps.keys()[-1]
>>'Step-2'
odb.steps.values()
>>[openOdb(r'C:/Users/Desktop/Model_Case.odb').steps['Step-1'], openOdb(r'C:/Users/Desktop/Model_Case.odb').steps['Step-2']]
odb.steps.values()[-1]
>>openOdb(r'C:/UsersDesktop/Model_Case.odb').steps['Step-2']
odb.steps.values()[-1].name
>>'Step-2'
Step_name = odb.steps.keys()[-1] #获取模型中最后一个分析步的名称
MyStep = odb.stpes[Step_name]
print(MyStep)
>>({'acousticMass': -1.0, 'acousticMassCenter': (), 'description': '', 'domain': TIME,
'eliminatedNodalDofs': 'NodalDofsArray object', 'frames': 'OdbFrameArray object',
'historyRegions': 'Repository object', 'inertiaAboutCenter': (), 'inertiaAboutOrigin': (),
'loadCases': 'Repository object', 'mass': -1.0, 'massCenter': (), 'name': 'Step-2',
'nlgeom': False, 'number': 2, 'previousStepName': 'Step-1', 'procedure': '*STATIC',
'retainedEigenModes': (), 'retainedNodalDofs': 'NodalDofsArray object',
'timePeriod': 1.0, 'totalTime': 1.0})
MyStep.historyRegions.keys()
>>['Assembly ASSEMBLY', 'Node PLATE-1.294']
MyRegion=MyStep.historyRegions['Assembly ASSEMBLY']
print(MyRegion)
>>({'description': 'Output at assembly ASSEMBLY',
'historyOutputs': 'Repository object', 'loadCase': None,
'name': 'Assembly ASSEMBLY', 'point': 'HistoryPoint object',
'position': WHOLE_MODEL})
MyRegion.historyOutputs.keys()
>>['ALLAE', 'ALLCCDW', 'ALLCCE', 'ALLCCEN', 'ALLCCET',
'ALLCCSD', 'ALLCCSDN', 'ALLCCSDT', 'ALLCD', 'ALLDMD',
'ALLDTI', 'ALLEE', 'ALLFD', 'ALLIE', 'ALLJD', 'ALLKE',
'ALLKL', 'ALLPD', 'ALLQB', 'ALLSD', 'ALLSE', 'ALLVD',
'ALLWK', 'ETOTAL']
MyOutput=MyRegion.historyOutputs['ALLAE']
print(MyOutput)
>>({'conjugateData': None, 'data': ((0.0, 0.00181813980452716), (1.0, 0.00181813980452716)), 'description': 'Artificial strain energy', 'name': 'ALLAE', 'type': SCALAR})
MyOutput.data
>>((0.0, 0.00181813980452716), (1.0, 0.00181813980452716))
MyOutput.data[0]
>>(0.0, 0.00181813980452716)
MyOutput.data[0][1]
>>0.00181813980452716
在Abaqus中inp文件的批量提交方法主要参考了前面给出的脚本,同时,为了防止因部分任务出错而导致整个计算队列终止,本文将利用MonitorMgr对象实现对计算任务的检测,在计算成功时实时输出当前任务的时间历程数据,在计算出错时输出错误信息,并直接开始下一次计算。这一部分的实现语句如下所示:
# -*- coding: UTF-8 -*-
from abaqus import *
from abaqusConstants import *
from odbAccess import *
from jobMessage import *
CPU_num = 4 #并行计算使用的CPU数量
Job_num = 5 #待求解inp文件的数量
Work_directory = 'C:/UsersDesktop/' #inp和py文件所在文件路径
Base_job_name = 'Model_Case' #inp文件的公共名称部分
#创建列表用于存储Job名称
Job_name = ['']*(Job_num+1)
for i in range(1,Job_num+1):
Job_name[i] = Base_job_name + str(i)
#
#创建状态文件用于输出求解inp文件和读取odb文件中的状态信息
file_status_info = open('Status_info.txt','w')
file_status_info.write('%-30s' % 'Input file name')
file_status_info.write('%-30s' % 'Analysis status')
file_status_info.write('%-30s\n' % 'Postprocess status')
#创建结果文件用于存储时间历程数据
file_hist_data = open('Hist_output.txt','w')
file_hist_data.write('%-30s' % 'Input file name')
file_hist_data.write('%-30s\n' % 'results')
#利用循环批量提交inp文件进行求解
for i in range(1,Job_num+1):
#创建job
mdb.JobFromInputFile(name = Job_name[i],
inputFileName = Work_directory + Job_name[i] + '.inp',
numCpus = CPU_num, numDomains = CPU_num)
#监控job
#当求解器收到messageType中指定的消息时将会触发回调函数
#常用的消息类型有:ANY_MESSAGE_TYPE,ABORTED,COMPLETED,ERROR
#这里选择ANY_MESSAGE_TYPE意味着任何消息类型都将触发回调函数
#将当前模型的基本信息(模型名称和工作路径)通过userData传递到回调函数中
Model_info = [Job_name[i], Work_directory]
monitorManager.addMessageCallback(jobName = Job_name[i],
messageType = ANY_MESSAGE_TYPE,
callback = jobMonitorCallback,
userData = Model_info)
#执行job
mdb.jobs[Job_name[i]].submit()
#为了保证inp文件提交运算后可以被顺序执行 需要等到当前计算完成后再进行后续操作
mdb.jobs[Job_name[i]].waitForCompletion()
#关闭文本文件
file_status_info.close()
file_hist_data.close()
这一部分语句主要定义了参与计算的inp文件名称和数量,以及求解文件所在的工作路径。同时还定义了Status_info和Hist_output两个文本文件,分别用于输出每个计算任务的状态信息以及时间历程数据。与前面给出的批量计算脚本有所不同的是,这里用到了monitorManager对象和其对应的函数来实现对计算任务的检测,并在收到检测任务的反馈信息后执行相应的回调函数,完成对反馈信息的响应。例如,在本文的示例中,当收到任务计算完成的反馈信息后,即开始提取该任务的时间历程数据,并将反馈信息和结果输出到文本文件。
用于设定回调函数的addMessageCallback方法至少需要输入3个参数:
monitorManager.addMessageCallback(jobName, messageType, callback, userData)
其中jobName为需要检测的任务名称,当需要检测所有任务时,可以使用Abaqus常量ANY_JOB;messageType是指定的反馈信息类型,常用的类型有:ABORTED、ANY_JOB、ANY_MESSAGE_TYPE、COMPLETED等,只有当收到对应的反馈信息类型时,才会执行回调函数,为了响应所有的反馈信息,这里可以将其设为ANY_MESSAGE_TYPE;callback参数需要传入一个回调函数,即响应对应信息的函数;userData可以是任意的Abaqus对象,主要用于将一些用户自定义的额外信息传递到回调函数中,例如,在本例中,我们将当前执行计算的inp文件名和文件路径传递到了回调函数,便于读取计算完成后的odb文件。
在Abaqus中,对应的回调函数也有一定的接口要求:
def functionName(jobName, messageType, data, userData)
这里jobName、messageType和userData和addMessageCallback方法中的参数是相同的,而data为Abaqus传入的数据对象。在本例中,回调函数的实现语句如下所示:
def jobMonitorCallback(jobName, messageType, data, userData):
if (messageType == ABORTED) or (messageType == ERROR): #计算终止或报错
# 将错误信息输出到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % userData[0])
file_status_info.write('%-30s' % 'ERROR')
file_status_info.write('%-30s\n' % 'ERROR')
#写入无效值到结果文件
file_hist_data = open('Hist_output.txt', 'a')
file_hist_data.write('%-30s' % userData[0])
file_hist_data.write('%-30s\n' % 'NaN')
#移除对job的监控
#回调函数将不再被执行
monitorManager.removeMessageCallback(jobName = userData[0],
messageType = ANY_MESSAGE_TYPE,
callback = jobMonitorCallback,
userData = userData)
elif messageType == JOB_COMPLETED:
# 移除对job的监控
#回调函数将不再被执行
monitorManager.removeMessageCallback(jobName=userData[0],
messageType=ANY_MESSAGE_TYPE,
callback=jobMonitorCallback,
userData=userData)
#读取odb文件进行数据后处理
Hist_extract(userData)
这一部分语句的实现功能为判断计算任务为失败或成功。当收到计算任务失败的反馈信息时,将字符串ERROR写入到状态文件中,同时将无效值NaN写入到结果文件,并通过removeMessageCallback方法移除对当前任务的监控;若收到计算任务成功的反馈信息,则移除对当前任务的监控,并执行Hist_extract()函数开始提取时间历程数据。Hist_extract()函数的实现语句如下所示:
def Hist_extract(Model_data):
# 通过异常处理(exception handing)来读取odb文件 防止读取失败导致整个计算终止
Current_job_name = Model_data[0]
Work_path = Model_data[1]
try:
odb = openOdb(path=Work_path + Current_job_name + '.odb') # 打开当前计算完成的odb文件
#写入信息到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % Model_data[0])
file_status_info.write('%-30s' % 'Complete')
file_status_info.write('%-30s\n' % 'Successful')
except:
#打开失败写入错误信息到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % Model_data[0])
file_status_info.write('%-30s' % 'Complete')
file_status_info.write('%-30s\n' % 'Failed')
#写入无效值到结果文件
file_hist_data = open('Hist_output.txt', 'a')
file_hist_data.write('%-30s' % Model_data[0])
file_hist_data.write('%-30s\n' % 'NaN')
return
#查询模型中的分析步名称
Step_name = odb.steps.values()[-1].name #获取模型中最后一个分析步的名称
Step = odb.steps[Step_name]
region = Step.historyRegions['Node PLATE-1.294']
u1data = region.historyOutputs['U1'].data #获取节点294沿x方向的位移值
#输出时间历程数据到结果文件
file_hist_data =open('Hist_output.txt','a')
file_hist_data.write('%-30s' % Current_job_name)
file_hist_data.write('%-30.4f\n' % u1data[-1][1])
上面的语句实现的功能为首先通过userData传入的当前求解任务的文件名以及工作路径,尝试读取求解任务的odb文件,若读取失败则写入错误信息到状态文件,并直接开始进行下一轮计算;读取成功则开始提取时间历程数据,并将模型中最后一个分析步的最后一个时刻的数据结果文件中。
本文创建了6个inp文件,并通过修改关键字时其中3个无法 正常计算,通过在Abaqus/CAE中运行上述语句得到状态文件和结果文件的输出结果如图2.1所示。
图2.1 状态文件和结果文件输出结果
# -*- coding: UTF-8 -*-
#Author: Tian
#Date: 2020-11-15
from abaqus import *
from abaqusConstants import *
from odbAccess import *
from jobMessage import *
CPU_num = 4 #并行计算使用的CPU数量
Job_num = 3 #待求解inp文件的数量
Work_directory = 'C:/Users/xutian/Desktop/batch analysis/input file/' #inp和py文件所在文件路径
Base_job_name = 'Model_Case' #inp文件的公共名称部分
#******************************************************************
#定义回调函数用于监控Job执行情况
def jobMonitorCallback(jobName, messageType, data, userData):
if (messageType == ABORTED) or (messageType == ERROR): #计算终止或报错
# 将错误信息输出到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % userData[0])
file_status_info.write('%-30s' % 'ERROR')
file_status_info.write('%-30s\n' % 'ERROR')
#写入无效值到结果文件
file_hist_data = open('Hist_output.txt', 'a')
file_hist_data.write('%-30s' % userData[0])
file_hist_data.write('%-30s\n' % 'NaN')
#移除对job的监控
#回调函数将不再被执行
monitorManager.removeMessageCallback(jobName = userData[0],
messageType = ANY_MESSAGE_TYPE,
callback = jobMonitorCallback,
userData = userData)
elif messageType == JOB_COMPLETED:
# 移除对job的监控
#回调函数将不再被执行
monitorManager.removeMessageCallback(jobName=userData[0],
messageType=ANY_MESSAGE_TYPE,
callback=jobMonitorCallback,
userData=userData)
#读取odb文件进行数据后处理
Hist_extract(userData)
#******************************************************************
#******************************************************************
#定义函数体用于读取odb文件并提取时间历程数据
def Hist_extract(Model_data):
# 通过异常处理(exception handing)来读取odb文件 防止读取失败导致整个计算终止
Current_job_name = Model_data[0]
Work_path = Model_data[1]
try:
odb = openOdb(path=Work_path + Current_job_name + '.odb') # 打开当前计算完成的odb文件
#写入信息到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % Model_data[0])
file_status_info.write('%-30s' % 'Complete')
file_status_info.write('%-30s\n' % 'Successful')
except:
#打开失败写入错误信息到状态文件
file_status_info = open('Status_info.txt', 'a')
file_status_info.write('%-30s' % Model_data[0])
file_status_info.write('%-30s' % 'Complete')
file_status_info.write('%-30s\n' % 'Failed')
#写入无效值到结果文件
file_hist_data = open('Hist_output.txt', 'a')
file_hist_data.write('%-30s' % Model_data[0])
file_hist_data.write('%-30s\n' % 'NaN')
return
#查询模型中的分析步名称
Step_name = odb.steps.values()[-1].name #获取模型中最后一个分析步的名称
Step = odb.steps[Step_name]
region = Step.historyRegions['Node PLATE-1.294']
u1data = region.historyOutputs['U1'].data #获取节点294沿x方向的位移值
#输出时间历程数据到结果文件
file_hist_data =open('Hist_output.txt','a')
file_hist_data.write('%-30s' % Current_job_name)
file_hist_data.write('%-30.4f\n' % u1data[-1][1])
#******************************************************************
#创建列表用于存储Job名称
Job_name = ['']*(Job_num+1)
for i in range(1,Job_num+1):
Job_name[i] = Base_job_name + str(i)
#
#创建状态文件用于输出求解inp文件和读取odb文件中的状态信息
file_status_info = open('Status_info.txt','w')
file_status_info.write('%-30s' % 'Input file name')
file_status_info.write('%-30s' % 'Analysis status')
file_status_info.write('%-30s\n' % 'Postprocess status')
#创建结果文件用于存储时间历程数据
file_hist_data = open('Hist_output.txt','w')
file_hist_data.write('%-30s' % 'Input file name')
file_hist_data.write('%-30s\n' % 'results')
#利用循环批量提交inp文件进行求解
for i in range(1,Job_num+1):
#创建job
mdb.JobFromInputFile(name = Job_name[i],
inputFileName = Work_directory + Job_name[i] + '.inp',
numCpus = CPU_num, numDomains = CPU_num)
#监控job
#当求解器收到messageType中指定的消息时将会触发回调函数
#常用的消息类型有:ANY_MESSAGE_TYPE,ABORTED,COMPLETED,ERROR
#这里选择ANY_MESSAGE_TYPE意味着任何消息类型都将触发回调函数
#将当前模型的基本信息(模型名称和工作路径)通过userData传递到回调函数中
Model_info = [Job_name[i], Work_directory]
monitorManager.addMessageCallback(jobName = Job_name[i],
messageType = ANY_MESSAGE_TYPE,
callback = jobMonitorCallback,
userData = Model_info)
#执行job
mdb.jobs[Job_name[i]].submit()
#为了保证inp文件提交运算后可以被顺序执行 需要等到当前计算完成后再进行后续操作
mdb.jobs[Job_name[i]].waitForCompletion()
#关闭文本文件
file_status_info.close()
file_hist_data.close()