next up previous contents index
Next: IV.5 Using the composite Up: IV. FeResPost Examples with Previous: IV.3 A modular post-processing   Contents   Index

Subsections


IV.4 An object-oriented post-processing

This Chapter is devoted to the presentation of an object-oriented post-processing program. The purpose of the example is to illustrate the flexibility that object-oriented programming introduces in the development of post-processing. Note however, that this example requires a better knowledge of object-orientation, and of the ruby language.

The example is very similar to the example presented in Chapter IV.3. Most programming lines are identical. When presenting the program one only present the different aspects that are specific to the object-orientation of the program. There is however one significant difference between versions ``A'' and ``B'' of the post-processing. Version ``B'' presents one possible programming of dynamic Results post-processing.

The example program is located under "POSTPROCb" directory.

IV.4.1 Difference in file organization

One characteristic of the new post-processing, is that it allows to better separate the definition of calculation operations and of the definition of data. Therefore, two directories have been created under "POSTPROCb" directory:

  1. Directory "POST" contains the definition of classes used in the post-processing. It corresponds to the definition of calculation operations.
  2. Directory "Data" contains the definition of modules, classes and objects corresponding to the definition of data.
Directory "POSTPROCb" still contains the main ruby file "testSat.rb".

IV.4.2 Transformation of modules into classes

Two post-processing classes are defined: "PostCauchy" and "PostConnect". Each of these classes has been obtained by modifying slightly the corresponding post-processing modules of Chapter IV.3. Note that for connections, new criteria have been defined.

Both classes inherit the generic post-processing class "GenPost". This class is very short, and its main purpose is to manage a list of all the post-processing objects that shall be created when the data are defined (see below). The programming of the class looks as follows:

class GenPost

   @@postList = []
   
   public

   def initialize
      @@postList << self
   end
   
   def GenPost::each
      @@postList.each do |current|
         yield current
      end
   end

end # class Post
The class also defines an iterator that loops on all the instances of the class that have been stored in class member data "@@postList".

IV.4.2.1 Post-processing of Cauchy stress tensor

One presents below the class "Post_Cauchy" which has been more deeply modified than
"Post_Connect". The inheritance of "GenPost" is ensured by the use of following statements:

require "genPost"
class PostCauchy < GenPost
One decided also that member data are no-longer module or class member data. Instead, the become instance member data:
   @groupName 
   @layerNames 
   @stressTensor 
   
   @currentMoSResult 
   @minMosResults
For this post-processing, two member data have been added to allow the storage of results in the object between the different calls to its methods.

One also adds and initializes a method that defines the member data when an instance of the class is created:

   def initialize
      super
   
      @groupName = nil 
      @layerNames = nil 
      @stressTensor = nil 
      
      @currentMoSResult = nil 
      @minMosResults = {}
   end
Note the call to "super" that ensures that the corresponding initialize method of "GenPost" class shall be called too. This ensures that each time an instance of "Post_Cauchy" is created, the "GenPost" class is made aware of it, and a pointer to this object is added to its "@@postList" class member data.

Class "Post_Cauchy" defines a method used to write Gmsh result files:

   def writeGmshMinMosResults(db,fileName,skeleton)
      results=[]
      @minMosResults.each do |key,val|
         results << [val,key,"ElemCorners"]
      end
      db.writeGmsh(fileName,0,results,\
                   [[skeleton,"mesh_slat"]],\
                   [[skeleton,"skel_slat"]])
   end
the Results stored in the file correspond to those stored in the new member data "@minMosResults". This member data is a Hash that contains the pairs of String identifiers and Results corresponding to minimum margins of safety. This variable is updated at the end of each criterion calculation to contain maps of the minimum margins of safety:
         tmpStr=@groupName+"_"+critName
         if (@minMosResults.has_key?(tmpStr)) then
            tmpRes1=@minMosResults[tmpStr]
            tmpRes2=Post.min(tmpRes1,@currentMoSResult)
            @minMosResults[tmpStr]=tmpRes2
         else
            @minMosResults[tmpStr]=@currentMoSResult
         end
This method uses the last calculated margin mapping result, stored in "@currentMoSResult".

IV.4.2.2 A composite post-processing

The Class ``PostComposite'' calculates composite failure indices and reserve factors for a specified failure criterion. The calculation is done with CLA classes using a loading using finite element ``Shell Forces'' and ``Shell Moments'' Results. Practically, one defines a loading as follows:

      ld=ClaLoad.new
      ld.Id="testLoad"
      ld.setMembrane([0.0,0.0,0.0],"femFM","femFM","femFM")
      ld.setFlexural([0.0,0.0,0.0],"femFM","femFM","femFM")
      ld.setOutOfPlane([0.0,0.0],"femFM","femFM")
      
      res=db.getResultCopy(lcName,scName,"Shell Forces",
                           interpolation,grp,layers)
      res.modifyRefCoordSys(db,"lamCS")
      ld.setShellForces(res)
      
      res=db.getResultCopy(lcName,scName,"Shell Moments",
                           interpolation,grp,layers)
      res.modifyRefCoordSys(db,"lamCS")
      ld.setShellMoments(res)
Then, the failure indices and reserve factors are calculated as follows:
      criteria=[]
      criteria << ["composite_RF",criterion,"RF",false,false]
      criteria << ["composite_FI",criterion,"FI",false,false]
      outputs=db.calcFiniteElementResponse(@compDb,0.0,ld,[false,true,false],
         [],[],fos,criteria)
      
      rfRes=outputs["composite_RF"]
      fiRes=outputs["composite_FI"]
Note that the calculation uses a composite database stored in ``@compDb'' member data. This ClaDb object must have been defined before. The class also records envelopes of failure indices in ``@minMosResults'' member data.

Note that there is a limitation to the post-processing: the thermo-elastic part of the laminate loading is not considered in this example. Therefore, the results might be inexact for thermo-elastic load cases (orbit load cases for example). The class is defined in file
``POST/post_Composite.rb''.

IV.4.3 A new post-processing for dynamic Results

The new object-oriented structure for the post-processing also allows the post-processing of dynamic Results. One Presents below two examples of post-processing for dynamic Results.

IV.4.3.1 Simple extraction of components

A new post-processing class is created to allow the presentation of dynamic Results. Actually, this class only extracts the magnitude and phase for one finite element entity and one component, and saves it into an Array, for later output in a text file. The class is called ``PostExtract'' and has only one member data: ``extracts'' in which the extracted Results shall be stored.

The ``initialize'' method calls the constructor of the parent class and initializes ``extracts'' to a void Hash:

   def initialize
      super
      @extracts = {}
   end
Then the sequence of operations to perform the extractions is described below. It is performed by the ``calcOneGroup'' method:
   def calcOneGroup(db,lcName,scName,refName,grpContent,resName,
                    extractMethod,csId,component)
      ...
The method has nine arguments: Then the following sequence of operations: The class ``PostExtract'' also defines a method for the final processing of the values stored in ``extracts'' member data. This method, called ``gnuplot'' outputs the Results in text files created in ``OUT_DYNAM'' directory. Also, a ``dat'' file containing the gnuplot commands to create graphical outputs is created in the same directory. The name of this command file is the argument of ``gnuplot'' method:
   def gnuplot(datName)
      gnuplotOs=File::open("OUT\_DYNAM/"+datName,"w")
      gnuplotOs.printf("\nset terminal png\n\n")
      @extracts.each do |key,tabs|
         fileName="OUT\_DYNAM/"+key+".txt"
         os=File::open(fileName,"w")
         tabs.each do |mode,freq,mag,phase|
            os.printf("%4d%15g%15g%15g\n",mode,freq,mag,phase)
         end
         os.close
         gnuplotOs.printf("set output \"%s_m.png\"\n",key)
         gnuplotOs.printf("plot \"%s.txt\" using 2:3 with lines\n",key)
         gnuplotOs.printf("set output \"%s_p.png\"\n",key)
         gnuplotOs.printf("plot \"%s.txt\" using 2:4 with points 1\n\n",key)
      end
      gnuplotOs.close
   end

IV.4.3.2 Post-processing of composite dynamic Results

One also defines a modified version of ``PostComposite'' class devoted to the corresponding post-processing of dynamic results in Real-Imaginary format. This Class is defined in file ``POST/post_DynamComposite.rb'' and the Class is named ``PostDynamComposite'' Its definition is nearly the same as the corresponding static class. Only, for each frequency, an additional loop performs the calculation for Real Results extracted at different rotation angles:

      ...
      
      ld=ClaLoad.new
      ld.Id="testLoad"
      ld.setMembrane([0.0,0.0,0.0],"femFM","femFM","femFM")
      ld.setFlexural([0.0,0.0,0.0],"femFM","femFM","femFM")
      ld.setOutOfPlane([0.0,0.0],"femFM","femFM")
      
      forces=db.getResultCopy(lcName,scName,"Shell Forces (RI)",
                              interpolation,grp,layers)
      forces.modifyRefCoordSys(db,"lamCS")
      
      moments=db.getResultCopy(lcName,scName,"Shell Moments (RI)",
                           interpolation,grp,layers)
      moments.modifyRefCoordSys(db,"lamCS")
      
      critTheta=critElem=critNode=critLayer=critSubLayer=critFI=critRF=nil;
      
      (0..nbrAngles).step(1) do |i|
         theta=360.0*i/nbrAngles
      
         res=forces.getR(theta)
         ld.setShellForces(res)
         
         res=moments.getR(theta)
         ld.setShellMoments(res)
   
         ...
         
      end # Angles loop
Then, the critical angle is identified and the corresponding Results are printed in the a result file.

The calculation has one additional parameter: ``nbrAngles'', which corresponds to the number of rotation angles to be tested in the post-processing.

IV.4.4 Main function

"testSat" method is slightly more complicated than before:

def testSat
   db=LoadCases.getDb("LAUNCH")
   GenPost::each do |current|
      if current.respond_to?("preCalc") then
         current.preCalc(db)
      end
   end
   GC.start

   version="All"
   LoadCases.each(version) do |db,lcName,scName|
      GenPost::each do |current|
         if current.respond_to?("calcAll") then
            current.calcAll(db,lcName,scName)
         end
      end
      GC.start
   end
   
   db=LoadCases.getDb("LAUNCH")
   GenPost::each do |current|
      if current.respond_to?("postCalc") then
         current.postCalc(db)
      end
   end
   GC.start
end
The "GenPost" iterator is used to loop on the different object of class "GenPost" or of one of its derived classes. This iterator is called three times:
  1. The first time "GenPost" iterator is used, one checks the existence of ``preCalc'' method in each object to perform preliminary operations before the loop on load case.
  2. Then, the iterator is called inside the loop on load cases to perform the operations required for each load case.
  3. The third time the iterator is called, this is done outside the load cases loop. Then the instance method "postCalc" is called to perform the operations that are to be done at the very end of the program. In the example, this operation corresponds to the printing of the maps of critical margins.
Note that ``preCalc'', ``calcAll'' and ``postCalc'' methods are now instance methods. This means they are specific to a particular instance of the classes. Note also, that the availability of these instance methods is tested before the method is called.

The ``testSat'' example is defined in ``testSat.rb'' file. Similarly, a ``dynam.rb'' file is defined to provide an example for dynamic analysis. `dynam.rb'' is very similar to ``testSat.rb'', but the file calls another version of the LoadCases and includes other data for post-processing:

require "DATA/data_Post_accel"
require "DATA/data_Post_cbush"
One should keep in mind that the data for dynamic Results post-processing are generally very different than the data for Static load cases post-processing. This justifies that separate ``main'' data files are written for these different categories of load cases.

IV.4.5 Definition of data

IV.4.5.1 Data for load cases

Those data are very similar to those defined in ``A'' version of the post-processing. One added however, new methods to the ``LoadCase'' module to allow the post-processing of dynamic Results (SOL108 and SOL111 of Nastran).

In iterator ``LoadCases::each'', a new proc object called ``makeDynamLoop'' loops on all the dynamic sub-cases (frequency outputs) for a given load case name. The three argument of this loop are

The first operation performed by ``makeDynamLoop'' is to build a list of sub-cases in sorted in order of increasing frequencies:
         lcNames=[lcName]
         tmpList={}
         xdbInfos=db.getXdbLcInfos(fullXdbName)
         xdbInfos.each do |info|
            if (info[0]==lcName) then
               tmpList[info[4]]=info[1]
            end
         end
         scList=tmpList.sort
In the previous instructions, one first loads the information about load cases and sub-cases stored in the xdb Result file. Then, the sub-cases corresponding to the selected load case name are selected. Finally, they are sorted and stored in the Array ``scList''.

Then, a loop is done on the list of sub-cases stored in ``scList''. A new Array ``scNames'' containing a list of sub-cases is filled. Each time its size reaches the values specified by the proc argument, one reads the Results, yields them, and finally erases them from the DataBase. This is done as follows:

         scNames=[]
         scList.each do |intId,scName|
            scNames << scName
            if (scNames.size==maxScNbr) then
               db.readXdb(fullXdbName,lcNames,scNames)
               scNames.each do |name|
                  yield([db,lcName,name])
                  db.removeResults("SubCaseId",name)
                  GC.start
               end
               scNames=[]
            end
         end
At the end, the remaining sub-cases are calculated the same way:
         db.readXdb(fullXdbName,lcNames,scNames)
         scNames.each do |name|
            yield([db,lcName,name])
            db.removeResults("SubCaseId",name)
            GC.start
         end
         GC.start
      end
An example of use of the ``makeDynamLoop'' proc follows:
      when "SINUS_Z" then
         db=getDb("LAUNCH")
         fullXdbName=getXdbDirName()+"/sol111_ri_xyz.xdb"
         makeDynamLoop.call(fullXdbName,"SINUS_Z",30)
In this case, the Results of ``SINUS_Z'' load case are required, and the maximum number of sub-cases loaded simultaneously in the DataBase is 30. Note that this number should be chosen with care: if it is too small, many readings of the Nastran xdb file will be necessary which increases the disk access time. On the other hand, if the number is too big, a larger amount of memory might be necessary to store the Results in the DataBase. This is important if you have limited resources. It is the responsibility of ``LoadCases'' module manager to select an appropriate value of this integer parameter.

Note that we voluntarily limit the example of dynamic Results post-processing to a simple extraction from an xdb file. Actually, the possibilities of FeResPost are larger that. For example, it should be possible to read simultaneously the Results for different load cases as ``SINUS_X'', ``SINUS_Y'' and ``SINUS_Z'' and to yield linear combinations of these elementary Results for the different frequency outputs.

IV.4.5.2 Data of post-processing

One presents the example of data for honeycomb calculation. Also examples of data for dynamic post-processing with ``PostExtract'' class are presented.

IV.4.5.2.1 Static post-processing

The first operation consists in creating an instance object of class "PostCauchy":

require "post_Cauchy"
post_honeycomb=PostCauchy.new
Then, three instance methods are created. For example, the "calcAll" method definition looks as follows:
def post_honeycomb.calcAll(db,lcName,scName)
   list = ["pan_MX_Honey_50", "pan_MY_Honey_50", ...
end
One sees that the method is attached to the instance object created earlier, and not to its class.

The object also defines a "postCalc" method that defines several data and performs a call to writeGmshMinMosResults method:

def post_honeycomb.postCalc(db)
   skeleton=Group.new
   skeleton.setEntitiesByType("Element","Element 1:100000")
   skeleton.matchWithDbEntities(db)
   writeGmshMinMosResults(db,"OUT_STATICS/postSandwichHoney.gmsh",skeleton)
end
Note that object "post_skins" does not define the method "postCalc" and produces no Gmsh file.

In file ``DATA/data_Post_TsaiHill.rb'', one defines the corresponding data for the calculation of Tsai-Hill criterion in panel -Z. Before doing a loop on the different load cases, one initializes the ``@compDb'' member data as follows:

def post_TsaiHill.preCalc(db)
   @compDb=db.getClaDb
end
At the end of the calculations, the envelopes of Results and the mesh are printed:
def post_TsaiHill.postCalc(db)
   skeleton=db.getGroupAllFEM()
   writeGmshMinMosResults(db,"OUT_STATIC/postTsaiHill.gmsh",skeleton)
   
   db.writeGmshMesh("OUT_STATIC/postTsaiHill.msh",0,skeleton)
end

IV.4.5.2.2 Dynamic post-processing

A first instance of the ``PostExtract'' class is created. This instance is devoted to the printing of several nodal accelerations in Z direction. Basically, the method contains several calls to ``calcOneGroup'' method with the appropriate arguments that define the data:

def post_accel.calcAll(db,lcName,scName)
   resName="Accelerations (RI), translational"
   method="Nodes"
   csId=0
   component="Z"
   
   calcOneGroup(db,lcName,scName,"Accel_Node_500001", "Node 500001", 
                resName, method, csId, component)
   calcOneGroup(db,lcName,scName,"Accel_Node_20919", "Node 20919",
                resName, method, csId, component)
   calcOneGroup(db,lcName,scName,"Accel_Node_20920", "Node 20920",
                resName, method, csId, component)
   calcOneGroup(db,lcName,scName,"Accel_Node_40913", "Node 40913",
                resName, method, csId, component)
   ...
A second instance of the method is used to output the launcher interface force recovered from the corresponding CBUSH element=
def post_cbush.calcAll(db,lcName,scName)
   resName="Beam Forces (RI)"
   method="Elements"
   csId=0
   component="XZ"
   
   calcOneGroup(db,lcName,scName,"Force_launcher", "Element 500003", 
                resName, method, csId, component)
end
Remember that the ``calcAll'' method is called for each load case or sub-case.

The final printing of values in ``OUT_DYNAM'' directory is called from ``postCalc'' method:

def post_accel.postCalc(db)
   gnuplot("post_accel.dat")
end
When the post-processing is finished, and if you have gnuplot on your computer, you can visualize the values by entering the ``OUT_DYNAM'' directory and typing:
   gnuplot < post_accel.dat
   gnuplot < post_cbush.dat
For example, the results obtained for the post-processing of CBUSH element forces are represented in Figure IV.4.1 and IV.4.2.

Figure IV.4.1: Magnitude of launcher interface loads in direction Z for axial unit acceleration case.

Figure IV.4.2: Phase of launcher interface loads in direction Z for axial unit acceleration case.

The data for the composite dynamic post-processing are defined in file
``DATA/data_Post_TsaiHillDynam.rb''. This file is very similar to the corresponding file for static post-processing. Only one selects 12 sub-divisions for the rotation angles.

IV.4.6 Acceleration with predefined criterion

The ``PostCauchy'' class also defines two accelerated versions of the honeycomb and Von Mises criteria. These are defined by the ``crit_HoneyAirbusAccel'' and ``crit_VonMisesAccel'' methods of the class.

The data for the calculations of these versions of the criterion are defined in files `` DATA/data_Post_honeycomb2.rb'' and ``DATA/data_Post_skins2.rb''. The activation/deactivation of these calculations can be obtained by uncommenting or commenting the corresponding require statements in ``testSat.rb'' file:

    require "DATA/loadCases"
    require "DATA/data_Post_Interf"
    require "DATA/data_Post_honeycomb"
    #~ require "DATA/data_Post_honeycomb2"
    require "DATA/data_Post_skins"
    #~ require "DATA/data_Post_skins2"
    require "DATA/data_Post_TsaiHill"

IV.4.7 Conclusions

We advise the use to play with the example and try to understand it. This post-processing program architecture is very flexible and should allow the developments of very sophisticated and powerful tools.

The example ``dynam.rb'' illustrating the post-processing of dynamic Complex Results is very preliminary and can be improved in several ways:


next up previous contents index
Next: IV.5 Using the composite Up: IV. FeResPost Examples with Previous: IV.3 A modular post-processing   Contents   Index
FeResPost 2017-05-28