In this Chapter, one presents an example of modular automated post-processing program using the ``FeResPost'' ruby library. This Chapter is organized as follows:
Different modules corresponding to the different concepts used in the post-processing.
The ``LoadCase'' modules corresponds to the concept of load case. In our post-processing program, a load case corresponds to the definition of a set of Results, and their association to a DataBase. The Results can be directly read from an ``op2'' Nastran Result file, or produced by linear combination of elementary Results.
LoadCase module has only one member data: @@dbList
. This Hash
contains a list of DataBases used for the post-processing.
Several methods are defined:
In the example, the method reads Groups from a Patran session files. Then, other Groups are defined by topological operations. The ``version'' argument is not used because all the DataBases contain the same Group definition. (Generally, Groups defined in different DataBases are not the same.)
The ``createGroups'' method is used when a new DataBase is defined.
@@dbList
member data. If it already exists, the DataBase is
returned. If it does not exists, a new DataBase is created. It is read
from a BDF file, and groups are defined by a call to ``createGroups''
methods. The created DataBase is stored in @@dbList
and returned.
Note that the programming of this ``proc'' has been done in such a way that it can easily be switched to read Samcef result. This is the reason which the list of load cases to be processed is given in a Hash argument.
case version when "thermo_const" then db=getDb("ORBIT") dirName=getResDirName() op2Name="temp_disc.op2" lcList={} lcList[1]="TEMP_PZ_COLD" lcList[2]="TEMP_PZ_HOT" lcList[3]="TEMP_PANLAT_COLD" lcList[4]="TEMP_PANLAT_HOT" postList.call``thermo_const'' is the ``version'' argument of the iterator. One first retrieves the DataBase corresponding to orbital (thermo-elastic) load cases by calling ``getDb'' method. Then, one specifies which op2 file is to be read. Finally, the list of load cases to be read and post-processed is build. Finally, the proc is called.
when "qs_launch" then db=getDb("LAUNCH") dirName=getResDirName() op2Name="unit_xyz.op2" elemNames=["LAUNCH_ONE_MS2_X","LAUNCH_ONE_MS2_Y",\ "LAUNCH_ONE_MS2_Z"] db.readOp2(getResDirName()+"/"+op2Name,"Results",elemNames) postCombili.call("GLOB_COMPRESSION",[0.0,0.0,-200.0],elemNames) postCombili.call("GLOB_TENSION",[0.0,-15.0,180.0],elemNames) postCombili.call("GLOB_LATERAL_1",[30.0,0.0,-50.0],elemNames) postCombili.call("GLOB_LATERAL_2",[21.21,21.21,-50.0],elemNames) postCombili.call("GLOB_LATERAL_3",[0.0,30.0,-50.0],elemNames) postCombili.call("GLOB_LATERAL_4",[-21.21,21.21,-50.0],elemNames) postCombili.call("GLOB_LATERAL_5",[-30.0,0.0,-50.0],elemNames) postCombili.call("GLOB_LATERAL_6",[-21.21,-21.21,-50.0],elemNames) postCombili.call("GLOB_LATERAL_7",[0.0,-30.0,-50.0],elemNames) postCombili.call("GLOB_LATERAL_8",[21.21,-21.21,-50.0],elemNames) elemNames.each do |name| db.removeResults("CaseId",name) end elemNames=nil GC.startFirst, the DataBase is prepared and elementary load cases are read from ``unit_xyz.op2''. Then the ``postCombili'' proc objects is called with the appropriate arguments to generate the linear combination of Results. At the end of the calculations, the elementary results are removed from the DataBase.
when "All" then LoadCases::each("qs_launch") do |db,lcName,scName| yield([db,lcName,scName]) end LoadCases::each("thermo_grad") do |db,lcName,scName| yield([db,lcName,scName]) end LoadCases::each("thermo_const") do |db,lcName,scName| yield([db,lcName,scName]) end
One defines two-post-processing modules. The first module, described in section IV.3.2.1 uses the Grid Point Forces and Moments to calculate connection margins of safety. The second module, presented in section IV.3.2.3 uses the Cauchy stress tensor to calculate margins of safety.
The module ``Post_Connect'' defines a post-processing of connections considered individually. It builds the Results corresponding to forces and moments at connections. Then, up to three criteria can be calculated. The criteria correspond respectively to sliding, gapping, and failure of inserts.
The member data defined in class ``Post_Connect'' are given below:
@@fAxial=nil @@fShear=nil @@mTorsion=nil @@mBending=nil``fAxial'', ``fShear'', ``mTorsion'' and ``mBending'' contain scalar Results corresponding to different components of the connection loads. These member data are set by method ``calcOneInterface''.
This methods builds the scalar Results corresponding to connection loads. It works in several phases:
grpA = db.getGroupCopy(@@grpNameA) grpB = db.getGroupCopy(@@grpNameB) tmpGrp = grpA * grpB grpC = db.getElementsAssociatedToNodes(tmpGrp) grpC += tmpGrp grpC *= grpA``grpC'' is build in such a way that it contains all the elements and nodes necessary to recover the contributing Grid Point Forces (internal forces and moments). Note that, the Groups defined in the DataBase must be such that ``grpA'' contains all the contributing element and nodes, and ``grpB'' contains all the contributing nodes.
params = getParameters(nil) csId = params["csId"] direction = params["direction"] norme=0.0 for i in 0..2 norme+=direction[i]*direction[i] end norme=Math.sqrt(norme) for i in 0..2 direction[i]/=norme end criteriaList=params["criteriaList"]Note that the list of failure criteria that shall be calculated for each connection is defined in the parameters that are retrieved.
tmpForces=db.getResultCopy(lcName,scName,\ "Grid Point Forces, Internal Forces","ElemNodes",grpC,[]) tmpMoments=db.getResultCopy(lcName,scName,\ "Grid Point Forces, Internal Moments","ElemNodes",grpC,[]) tmpForces.modifyRefCoordSys(db,csId) tmpMoments.modifyRefCoordSys(db,csId) tmpForces=tmpForces.deriveByRemapping("CornersToNodes",\ "sum",db) tmpMoments=tmpMoments.deriveByRemapping("CornersToNodes",\ "sum",db) @@fAxial=tmpForces*direction @@fShear=sqrt(sq(tmpForces)-sq(@@fAxial)) @@mTorsion=tmpMoments*direction @@mBending=sqrt(sq(tmpMoments)-sq(@@mTorsion))
criteriaList.each do |critName| case critName when "sliding" then crit_Sliding(db,lcName,scName) when "gapping" then crit_Gapping(db,lcName,scName) when "insert" then crit_Insert(db,lcName,scName) end endCriteria methods are described below.
This criterion, defined by ``crit_Sliding'' method is used to calculate sliding margins of safety with the following expression:
in which
One gives the lines used for the calculation of margins of safety:
mos=(cf*pMin/fos)/(max(@@fAxial,0.0)*cf+@@fShear)-1.0 mosMin=mos.extractResultMin rklMin=mosMin.extractRkl fAxialMin=@@fAxial.extractResultOnRkl(rklMin) fShearMin=@@fShear.extractResultOnRkl(rklMin)Other programming lines are devoted to the extraction of parameters and printing of Results. One first checks whether the output file exists. If it exists, one opens it in ``append'' mode. If it does not exists, it is opened in ``write'' mode and a title line is printed:
if (File.exist?(outputFile)) then os=File.open(outputFile,"a") else os=File.open(outputFile,"w") os.printf("%30s%40s%10s%8s%10s%8s%8s%14s%14s%8s\n",\ "LoadCase ID","Interface","Elem ID","FoS",\ "Type","Pmin","Cf","Faxial","Fshear","MoS") endIn either case, the critical margin and corresponding information is printed in the result file:
interfStr=format("%s/%s",@@grpNameA,@@grpNameB) os.printf("%30s%40s%10s%8.2f%10s%8.1f%8.3f%14.1f%14.1f",\ lcName,interfStr,mosData[1],fos,connectType,pMin,\ cf,fAxialData[5],fShearData[5]) if (mosData[5]>1000.0) then os.printf("%8s\n",">1000") else os.printf("%8.2f\n",mosData[5]) endFinally, the output stream is closed.
This criterion, defined by ``crit_Gapping'' method is used to calculate gapping margins of safety with the following expression:
in which
One only gives the lines used for the calculation of margins of safety:
mos=(pMin/fos)/(max(@fAxial,0.0)+@mBending/radius)-1.0 mosMin=mos.extractResultMin rklMin=mosMin.extractRkl fAxialMin=@fAxial.extractResultOnRkl(rklMin) mBendingMin=@mBending.extractResultOnRkl(rklMin)Other programming lines are devoted to the extraction of parameters and printing of Results.
This criterion, defined by ``crit_Insert'' method is used to calculate inserts margins of safety with the following expression:
In which ``PSS'' is the axial allowable of the insert and ``QSS'' is its shear allowable.
One only gives the lines used for the calculation of margins of safety:
tmp = sq(@fAxial/pss)+sq(@fShear/qss) tmpMax = tmp.extractResultMax mosMin = (1.0/fos)/sqrt(tmpMax)-1.0 rklMin = mosMin.extractRkl fAxialMin = @fAxial.extractResultOnRkl(rklMin) fShearMin = @fShear.extractResultOnRkl(rklMin)Other programming lines are devoted to the extraction of parameters and printing of Results.
The interfaces (lists of pair of Groups) on which connection margins will be calculated are defined in ``calcAll'' method. This method corresponds to a definition of data. One first defines a list of pair of groups with statement like:
list = [] list << ["pan_MX","bar_MXMY"] list << ["pan_MX","bar_MXMZ"] list << ["pan_MX","bar_MXPY"] list << ["pan_MX","bar_MXPZ"] list << ["pan_MX","corner_MXMYMZ"] ...Then a loop on these data is done, and method ``calcOneInterface'' is called for each interface:
list.each do |groupNameA,groupNameB| @@grpNameA=groupNameA @@grpNameB=groupNameB calcOneInterface(db,lcName,scName) endParameters ``@@grpNameA'' and ``@@grpNameB'' are passed by member data of the module. The other parameters are passed as arguments of the call to ``calcOneInterface''.
Some parameters depend on the interfaces. For example, the direction of connections, allowables... The method ``getParameters'' is used to produce the parameters corresponding to each interface.
This method has one parameter ``critName'' a String argument corresponding to the criterion that requires the parameters. If the argument is nil, one considers that the method is called by ``calcOneInterface'' and data corresponding to the different orientation of the connection are returned. If the method is called by a criterion method, the data returned correspond to allowables used in the calculation of margins of safety.
The ``Post_Connect'' module defines methods corresponding to the calculation operations, and methods than can be considered as definition of data. Of course, many different types of data definitions are possible. For example, the definition of interfaces, and of the calculation parameters could be read from a file.
The calculation methods as well as the data are defined in a single module. However, it could be interesting to split the definition of data into several files. This could be interesting, for example, when several persons work on the same project. At the same time, the copying of the methods corresponding to calculation methods into different data files is a poor way to use the object-oriented capabilities of ruby language.
One shows in section IV.3.2.3 a different modular design that allows not to repeat the writing of calculation methods, and at the same time to split the module into separate smaller entities. More precisely, one defines a generic ``Post_Cauchy'' module that performs calculations based on the components of Cauchy stress tensor. Then two modules calculating honeycomb margins of safety and skin margins of safety are defined as two specialized modules using ``Post_Cauchy'' capabilities.
The module ``Post_Cauchy'' performs the post-processing of Results corresponding to the Cauchy stress tensor. Presently, three criteria corresponding to the stress tensor are available: an ``Airbus'' criterion for the calculation of honeycomb, a ``MaxShear'' criterion for the calculation of honeycomb, and a ``VonMises'' criterion for the calculation of metallic parts.
The class has three member data:
This method has one more argument than the corresponding method in ``Post_Connect'' module:
def Post_Cauchy::calcOneGroup(db,lcName,scName,paramsMethod)``paramsMethod'' is the method to be called when one wishes to retrieve calculation parameters.
``calcOneGroup'' performs the building of ``stressTensor'' member data by retrieving the corresponding Results. The first operations performed by the method are programmed as follows:
grp = db.getGroupCopy(@@groupName) params = paramsMethod.call(nil) interpolation = params["interpolation"] layers = params["layers"] criteriaList=params["criteriaList"] @@stressTensor=db.getResultCopy(lcName,scName,\ "Stress Tensor",interpolation,grp,layers)So far, the method is not very different than the corresponding method of ``Post_Connect'' module. Just note the way the parameters method is called.
The rest of the method is similar too:
criteriaList.each do |critName| case critName when "airbus" then crit_HoneyAirbus(db,lcName,scName,paramsMethod) when "maxShear" then crit_HoneyMaxShear(db,lcName,scName,paramsMethod) when "vonMises" then crit_VonMises(db,lcName,scName,paramsMethod) end endThe different methods that performs the criteria calculations are called if necessary. Note that the method to be called to retrieve parameters is passed as argument to the different criteria methods.
This criterion, defined by ``crit_HoneyAirbus'' method is used to calculate margins of safety in the honeycomb with the following expression:
in which
As the programming of the criterion is not more complicated than the programming of ``Post_Connect'' module criteria, one does not describe the instructions.
This criterion, defined by ``crit_HoneyMaxShear'' method is used to calculate margins of safety in the honeycomb with the following expression:
in which
This criterion, defined by ``crit_VonMises'' method is used to calculate margins of safety in the metallic parts with the following expression:
in which
This module includes ``Post_Cauchy'' module:
module Post_honeycomb include Post_Cauchy ...This means that the methods of ``Post_Cauchy'' module are now visible in ``Post_honeycomb''. In this example, the module has two specific methods:
list = ["pan_MX_Honey_50", "pan_MY_Honey_50", "pan_PX_Honey_50", "pan_PY_Honey_50", "pan_PZ_Honey_72", "pan_SUP_Honey_50"] list.each do |groupName| @@groupName=groupName Post_Cauchy::calcOneGroup(db,lcName,scName,method(:getParameters)) endNote that the call to ``calcOneGroup'' has a fourth parameter: the method that shall be called to retrieve the necessary data.
This module is very similar to `Post_honeycomb'' module.
The file ``testSat.rb'' contains the ``testSat'' method that starts the loop on load cases, and where the different post-processing criteria done for each load case are selected.
The different modules that are used in the ``testSat'' method are made visible by several require statements:
require "util" require "loadCases" require "data_Post_Connect" require "data_Post_honeycomb" require "data_Post_skins"Then, in ``testSat'' method, a loop on the load cases is started by calling the ``each'' iterator of ``LoadCases'' module with appropriate parameter:
version="All" LoadCases.each(version) do |db,lcName,scName| PostConnect.calcAll(db,lcName,scName) Post_honeycomb.calcAll(db,lcName,scName) Post_skins.calcAll(db,lcName,scName) GC.start endThe different post-processing criteria are called in the block that follows the iterator. At the end of each load case calculation a call to the garbage collector cleans the memory.
One presented in the Chapter a finite element post-processing program written by defining modules. This post-processing works and it is possible to trick the language in order to prevent the rewriting of codes. (See the ``Post_Cauchy'' class.)
However, the program written in this example uses poorly the object-oriented capabilities of ruby. One presents in Chapter IV.4 an example of object-oriented post-processing.