next up previous contents index
Next: IV.3 A modular post-processing Up: IV. FeResPost Examples with Previous: IV.1 A small satellite   Contents   Index

Subsections


IV.2 A few small examples

In this Chapter, one presents very small examples of data files performing simple operations with the FE model and Results. This allows to familiarize the reader to the use of FeResPost, and possibly to ruby also.

The examples are divided four categories:


IV.2.1 Utilities Module

In file "SMALLEX/UTIL/util.rb", one defines the ``Util'' Module that contains several useful methods:


IV.2.2 Examples without Results

One presents here several examples illustrating the manipulation of Groups in FeResPost. They show how the Groups defined in the DataBase can be inspected, and how new Groups can be constructed and added to the DataBase.


IV.2.2.1 Reading Bulk Data

The first example illustrates different versions of the call to ``readBdf'' method:

   require "FeResPost"
   include FeResPost

# Creates  and initializes the DataBase :

   db=NastranDb.new()
   db.Name="tmpDB1"   
   db.readBdf("../../MODEL/MAINS/unit_xyz.bdf")
The first call to ``readBdf'' is the default instruction used to read the Bulk file in most examples.

A second bdf read operation is performed as follows:

   db=NastranDb.new()
   db.Name="tmpDB2" 
   begin 
      db.readBdf("unit_xyz_V1.bdf",[],"bdf",{},true)
   rescue Exception => x then
      printf ("\n\nMaybe you should modify the two first include statements in main file!\n")
      printf ("**********************************************************************\n\n")
      raise x
   end
The revised version reads main file ``unit_xyz_V1.bdf'' that illustrates several possible interpretation of the ``include'' statements in Nastran Bulk Data Files. The user must uncomment the corresponding statement, and modify the absolute paths in the include statements of ``unit_xyz_V1.bdf'' file. (The ``begin'' and ``rescue'' statements have been added to remind the user of this necessary modification. An error message is issued if the reading fails.)

Include statement in the data file look as follows:

include '/home/ferespost/Documents/FERESPOST/TESTSAT/
   MODEL/MESH/coordSys.bdf'
include /home/ferespost/Documents/FERESPOST/   ,
        TESTSAT/MODEL/MATS/mats.bdf
include '../../MODEL/PROPS/props.bdf'
$
include ../../MODEL/MESH/elemNodes_pan_MX.bdf
   include ../../MODEL/MESH/elemNodes_pan_MY.bdf
include    ../../MODEL/MESH/elemNodes_pan_MZ.bdf
include ../../   MODEL/MES   H/elemNod   es_pan_PX.bdf
include ../../MODEL/MESH/   elemNodes_pan_PY.bdf
The example is given in file ``SMALLEX/EX01/readBdf.rb''.

Another version of the example is given in file ``SMALLEX/EX01/readBdf_V2.rb''. It illustrates the reading of Bulk Data Files containing include statements in which symbols are used. In that example, the call to ``readBdf'' looks as follows:

   symbols=Hash.new
   symbols["INCDIR"]="../../MODEL"
   
   db=NastranDb.new()
   db.Name="tmpDB2"   
   db.readBdf("unit_xyz_V2.bdf",[],"bdf",symbols,true)
The variable ``symbols'' is a Hash containing the list of symbols that must be substituted in the include statements. (Only one symbol is defined in this case.) The include statements of the BDF file look as follows:
include INCDIR:/MESH/elemNodes_pan_MX.bdf
   include INCDIR:/MESH/elemNodes_pan_MY.bdf
include    INCDIR:/MESH/elemNodes_pan_MZ.bdf
include ../../   MODEL/MES   H/elemNod   es_pan_PX.bdf
include INCDIR:/MESH/   elemNodes_pan_PY.bdf

The example ``readBdf_V3'' proposes a slightly more complicated case of file inclusions in a main BDF.

In Example ``readBdf_V7'' one proposes to illustrate several functions allowing the manipulation of the database FEM entities. A list of Nastran cards corresponding to the FEM definition is build as follows:

    cards=[]
    db.each_coordSysId do |id|
        cards << db.fillCard("CoordSys",id)
    end
    db.each_nodeId do |id|
        cards << db.fillCard("Node",id)
    end
    db.each_elemId do |id|
        cards << db.fillCard("Element",id)
    end
    db.each_rbeId do |id|
        cards << db.fillCard("RBE",id)
    end
    db.each_materialId do |id|
        cards << db.fillCard("Material",id)
    end
    db.each_propertyId do |id|
        cards << db.fillCard("Property",id)
    end
Then, one builds a new database using these cards:
    db3=NastranDb.new()
    db3.Name="tmpDB3"
    db3.insertCards(cards);
Finally, one checks the content of the new database:
    db3.writeBdfLines("out.bdf","w","left","short","All");

    vectStr=NastranDb.writeNastranCardsToVectStr("left","short",cards);
    vectStr.each do |line|
        puts line
    end


IV.2.2.2 Group examples

IV.2.2.2.1 Printing the list of a DataBase's Groups

It shows how it is possible to obtain the list of Groups contained in a DataBase. This may be useful if one wants to check whether all Groups read from a session file have been correctly integrated in the DataBase. The ruby program looks like:

# Creates  and initializes the DataBase :

   db=NastranDb.new()
   db.Name="tmpDB"
   db.readBdf("../../MODEL/MAINS/unit_xyz.bdf")
   db.readGroupsFromPatranSession("../../MODEL/PATRAN/groups.ses")

# Prints all the group names :

   puts db.getAllGroupNames()
The last line performs the printing of the names of all Groups contained in the DataBase. The other lines are for DataBase creation and initialization.

The example is given in file "SMALLEX/EX02/printGroupNames.rb".

IV.2.2.2.2 Content of DataBase's Groups

In the previous example one simply printed the list of the names of Groups contained in a DataBase. In this new examples, one also print information on the content of each Group. Practically, this is done as follows:

The program looks like this:
# Creates  and initializes the DataBase :

   db=NastranDb.new()
   db.Name="tmpDB"
   db.readBdf("../../MODEL/MAINS/unit_xyz.bdf")
   db.readGroupsFromPatranSession("../../MODEL/PATRAN/groups.ses")

# Prints Groups' data :

   printf("%20s%10s%10s%10s%10s\n","groupName","Nodes",\
      "Elements","MPCs","CoordSys")
   db.each_groupName do |groupName|
      grp = db.getGroupCopy(groupName)
      nodesNbr = grp.NbrElements
      elementsNbr = grp.NbrNodes
      mpcsNbr = grp.NbrMpcs
      coordNbr = grp.NbrCoordsys
      printf("%20s%10d%10d%10d%10d\n",groupName,nodesNbr,\
         elementsNbr,mpcsNbr,coordNbr)
   end
The example is given in file "SMALLEX/EX02/printGroups.rb".

IV.2.2.2.3 Printing a Patran session file

The third example prints Groups defined in a Patran session file without importing them into a DataBase. Groups are directly read into a Hash object with the following statement:

   h=Post.readGroupsFromPatranSession("../../MODEL/PATRAN/groups.ses")
Then, for each Group, the entities are printed as lists of integer. One shows below how it is done for the nodes:
   nbrEntitiesPerLine=8
   
   h.each do |id,grp|
      os.printf("Group \"%s\":\n\n",id)
      
      ...
      
      nbr=grp.getNbrEntitiesByType("Node")
      os.printf("   Nbr Nodes: %d",nbr)
      counter=0
      grp.each_node do |id|
         if (counter%nbrEntitiesPerLine==0) then
            os.printf("\n      ")
         end
         os.printf("%8d",id)
         counter+=1
      end
      os.printf("\n\n")
      
      ...
      
   end
The example is given in file "SMALLEX/EX02/writeGroupEntities.rb".


IV.2.2.3 Manipulating Group entities

The entities stored in a Group can be manipulated. For example, the following statements:

   grpList=[]

   grp=Group.new
   grp.addEntities("Element 20000:24999 Node 20000:24999")
   grp.Name="pan_MX"
   grp.matchWithDbEntities(db)
   grpList << grp
create a Group, add elements and nodes into it, remove the elements and nodes undefined in the db DataBase and insert the created Group into an Array.

It is also possible to add or remove entities with range defined with steps:

   grp=Group.new
   grp.addEntities("Element 20000:24999:7 Node 20000:24999:7")
   grp.removeEntities("Element 20000:24999:28 Node 20000:24999:28")
   grp.Name="pan_MX_7_28"
   grp.matchWithDbEntities(db)
   grpList << grp
At the end of the example, the Groups are saved into a Patran session file:
   Post::writeGroupsToPatranSession("groups.ses",grpList)
The example is given in file "SMALLEX/EX03/manipGroups.rb".


IV.2.2.4 Adding Groups to a DataBase

In the examples of section 
refexamples.smallEx.Groups.ex2.sec, one initialized a DataBase and examined its Groups and the content of the Groups. In this example, one shows how the Groups can be manipulated, and the DataBase modified during the execution of the program.

The groups contained in session file "groups.ses" are not sufficient to suit our post-processing requirements. Indeed, it would be very practical if for each panel, a distinction between the skins and honeycomb could be made. We decide that it shall be done by adding new Groups to the DataBase. This problem can be solved in four steps:

  1. Initialization of a DataBase.
  2. Creation of Groups by association to materials.
  3. Creation of Groups by intersections and insertion in the DataBase.
  4. Printing of the Groups contained in the DataBase (for checking the result of the operation).
Steps 1 and 4 above correspond to the operation of the example presented in section IV.2.2.2. Therefore, one does not present those parts of the program here. One only gives explanation on steps 2 and 3.

The creation of ``material Groups'' is done by calling the DataBase ``getElementsAssociatedToMaterialId'' method. Three Groups are created, corresponding to honeycomb 50 $ {\text{kg/m}}^{3}$ , honeycomb 72 $ {\text{kg/m}}^{3}$ and Aluminum 2024 T3 respectively. Practically this is programmed as follows:

# Groups created by Materials :

   tmpGroup_Honey_50  = db.getElementsAssociatedToMaterialId(5)
   tmpGroup_Honey_50 += db.getElementsAssociatedToMaterialId(4)
   tmpGroup_Honey_50.Name="Honey_50"
   
   tmpGroup_Honey_72 = db.getElementsAssociatedToMaterialId(6)
   tmpGroup_Honey_72.Name="Honey_72"
   
   tmpGroup_Al_2024 = db.getElementsAssociatedToMaterialId(3)
   tmpGroup_Al_2024.Name="Al_2024"
   
   tmpGroup_CFRP = db.getElementsAssociatedToMaterials(10000)
   tmpGroup_CFRP.Name="CFRP"
   
   matGroups = Array.new()
   matGroups << tmpGroup_Honey_50
   matGroups << tmpGroup_Honey_72
   matGroups << tmpGroup_Al_2024
   matGroups << tmpGroup_CFRP
   
   db.addGroupCopy(tmpGroup_Honey_50)
   db.addGroupCopy(tmpGroup_Honey_72)
   db.addGroupCopy(tmpGroup_Al_2024)
   db.addGroupCopy(tmpGroup_CFRP)
One can make a few remarks about the previous ruby lines: After the creation of material Groups, one creates the other Groups by intersection (step 3). This is done as follows:
# Groups created by intersection :
   
   panelGroupNames = Array.new()
   panelGroupNames << "pan_MX"
   panelGroupNames << "pan_MY"
   panelGroupNames << "pan_MZ"
   panelGroupNames << "pan_PX"
   panelGroupNames << "pan_PY"
   panelGroupNames << "pan_PZ"
   panelGroupNames << "pan_SUP"
   
   for panelGroupName in panelGroupNames
      panelGroup = db.getGroupCopy(panelGroupName)
      for matGrp in matGroups
         newGrp = panelGroup * matGrp
         newGrp.Name=panelGroupName+"_"+matGrp.Name
         if newGrp.getEntitiesByType("Element").size > 0
            db.addGroupCopy(newGrp)
         end
      end
   end
Here again, a few commentaries can be done: The example is given in file ``SMALLEX/EX03/makeMatGroups.rb''.


IV.2.3 Examples with iterators

One presents one example illustrating the use of some of the iterators defined in DataBase class. The following lines print the elements connectivity:

   db.each_elemId do |elemId|
      STDOUT.printf(" %d =>",elemId)
      db.each_nodeOfElement(elemId) do |nodeId|
         STDOUT.printf(" %d",nodeId)
      end
      STDOUT.printf("\n")
   end
Two iterators have been used in the calculation: ``each_elemId'' and ``each_nodeOfElement''. A second version of the loop restricts the printing of connectivity to corner nodes only.

The example is given in file ``SMALLEX/EX16/elemConnectivity.rb''.

An iterator is also used in example ``SMALLEX/EX03/properties.rb''. In that example, one uses the ``fillCard'' method of NastranDb class to obtain the definition of the properties in the model:

    db.each_propertyId do |id|
        puts "Property",id
        card=db.fillCard("Property",id)
        puts card
    end
(See section III.1.1.4 for the definition of ``fillCard'' method.)


IV.2.4 Examples with Results


IV.2.4.1 Inspecting Results contained in a DataBase

The following example prints the information on Results available in the DataBase. It starts with the following lines:

   require "FeResPost"
   include FeResPost
   
   #DataBase::disableLayeredResultsReading
   #DataBase::disableSubLayersReading("Bottom")
   #DataBase::disableSubLayersReading("Mid")
   #DataBase::disableSubLayersReading("Top")
The commented lines are methods that disable partially or entirely the reading of composite element layered Results. After reading the corresponding manual in section I.1.3.2, you may un-comment some of these instructions to check the effect on the reading of composite Results.

The main part of the example program looks like this:

# Reading or generating Results :

   db.readOp2("../../MODEL/EXEC_OP2/unit_xyz.op2","Results")
   
   db.generateCoordResults
   db.generateCoordResults("Fake Coords Case","No SubCase","coords")
   
# Inspecting and reading Results :

   db.each_resultKeyCaseId do |lcName|
      printf("LOADCASE: \"%s\"\n",lcName)
   end
   db.each_resultKeySubCaseId do |scName|
      printf("SUBCASE: \"%s\"\n",scName)
   end
   db.each_resultKeyLcScId do |lcName,scName|
      printf("LOADCASE and SUBCASE: \"%s\" - \"%s\"\n",lcName,scName)
   end
   db.each_resultKeyResId do |resName|
      printf("RESULT: \"%s\"\n",resName)
   end

   db.each_resultKey do |lcName,scName,tpName|
      tmpRes=db.getResultCopy(lcName,scName,tpName)
      printf("%-20s%-15s%-50s%-10d\n",lcName,scName,tpName,\
             tmpRes.Size)
   end
It works as follows: The example is provided in file "SMALLEX/EX04/printResLists.rb".


IV.2.4.2 Calculations with Results

One shows here how calculations can be performed with Result objects. One first initializes the DataBase and imports Results with function ``readOp2''. In this example, one works with the thermo-elastic version of the model, but its initialization is vey similar to the initialization in other examples. One also add addition Groups corresponding to skins and honeycomb of the sandwich panels, like in the example of section IV.2.2.4. Those parts are not described here.

One first describes the manipulation of results that lead to the calculation of maximum equivalent Von Mises stress in the skins of upper panel. The corresponding ruby lines look like this:

   targetGrp = db.getGroupCopy("pan_PZ_Al_2024")
   stress =  db.getResultCopy("ORBIT_ONE_MS2_Z","Statics",\
      "Stress Tensor","ElemCorners",targetGrp,[])
   scalar = stress.deriveTensorToOneScal("VonMises")
   
   maxScalar = scalar.extractResultMax
   maxRkl = maxScalar.extractRkl
   maxStress = stress.extractResultOnRkl(maxRkl)
   maxScalarData = maxScalar.getData()[0]
   maxStressData = maxStress.getData()[0]

   puts
   puts "Maximum Von Mises stress in panel +Z skins :"
   puts
   printf("   %.2f Pa on element %d (layer=\"%s\").\n",
      maxScalarData[5],maxScalarData[0],maxScalarData[2])
   printf("      Sxx = %.2f, Syy = %.2f, Szz = %.2f,\n",maxStressData[5],\
             maxStressData[6],maxStressData[7])
   printf("      Sxy = %.2f, Syz = %.2f, Szx = %.2f\n",maxStressData[8],\
             maxStressData[9],maxStressData[10])
Basically, the process can be divided into three parts:
  1. Actual calculation of Von Mises stress. One recovers the Cauchy stress tensor corresponding to the selected load case and selected Group. Then one derives a scalar equivalent Von Mises stress.
  2. Selection of the data corresponding to the maximum Von Mises stress. This is done as follows:
  3. Printing of the Results. The reader will understand by himself how it works.
In the same file, one also calculates a maximum out of plane shear stress in the honeycomb of the +Z panel. The calculation of this stress is done as follows:
   targetGrp = db.getGroupCopy("pan_PZ_Honey_72")
   stress = db.getResultCopy("ORBIT_ONE_MS2_Z","Statics",\
      "Stress Tensor","ElemCorners",targetGrp,[])
   sXZ = stress.deriveTensorToOneScal("Component XZ")
   sYZ = stress.deriveTensorToOneScal("Component YZ")
   scalar = Post.sqrt(sXZ*sXZ+sYZ*sYZ)
Similarly, one calculates the ``MaxShear'' stress (obtained from the eigen values of the Cauchy stress tensor):
   targetGrp = db.getGroupCopy("pan_PZ_Honey_72")
   stress =  db.getResultCopy("ORBIT_ONE_MS2_Z","Statics",\
      "Stress Tensor","ElemCorners",targetGrp,[])
   scalar = stress.deriveTensorToOneScal("MaxShear")
In the same data file, one shows how the bar stresses are recovered:
   targetGrp = db.getGroupCopy("strut_A")
   stress =  db.getResultCopy("ORBIT_ONE_MS2_X","Statics",\
      "Beam Axial Stress for Bending Loads","ElemCorners",targetGrp,[])
   scalar = Post.abs(stress)
   
   maxScalar = scalar.extractResultMax
   maxRkl = maxScalar.extractRkl
   maxStress = stress.extractResultOnRkl(maxRkl)
   maxScalarData = maxScalar.getData()[0]
   maxStressData = maxStress.getData()[0]

   puts
   puts "Maximum bar stress in strut A :"
   puts
   printf("   %.2f Pa on element %d (layer=\"%s\").\n",
      maxScalarData[5],maxScalarData[0],maxScalarData[2])
   printf("      Sxx = %.2f\n",maxStressData[5])
   puts
   puts
Note that the way maximum stress is recovered from FE Results is different because Nastran calculates only the longitudinal component of the stress tensor at four locations in the cross-section. The shear stress is not taken into account in this calculation. More complicated calculations have to be performed to take into account all the components of the stress tensor for bar and beam elements.

These examples are provided in file "SMALLEX/EX05/printStressMax.rb".


IV.2.4.3 Using predefined criteria

The example given in file ``SMALLEX/EX05/calcHoneyAccel.rb'' explains how a predfined criterion can be calculated. This is done for the ``HoneycombAirbusSR'' predefined criterion presented in detail in section IX.D.1.2. The operations can be sorted in three steps:

Note that the seventh element of the ``output'' Array above is the only Result object created and returned by the predefined criterion. The same object could be obtained by a few ruby statements like:

        shearL=@@stressTensor.deriveTensorToOneScal("Component XZ")
        shearW=@@stressTensor.deriveTensorToOneScal("Component YZ")
        tmp=sq(shearL/allL)+sq(shearW/allW)
        sr=fos*sqrt(tmp)
The computational cost of these few statement can be very important however. Indeed, several FeResPost Result objects are created by these few lines. One creates consecutively the following result objects:
  1. One ``shearL'' scalar Result by extraction of XZ component.
  2. One ``shearW'' scalar Result by extraction of YZ component.
  3. One ``shearL/allL'' scalar Result (division by real value).
  4. One ``shearW/allW'' scalar Result (division by real value).
  5. One ``sq(shearL/allL)'' scalar Result (scalar Result to the square).
  6. One ``sq(shearW/allW)'' scalar Result (scalar Result to the square).
  7. One ``tmp'' Result obtained by summation of two scalar Results.
  8. One ``sqrt(tmp)'' Result obtaiend by extracting the square root of a scalar Result.
  9. And finally, the ``sr'' Result, which is the only one that shall be kept.
This means that 8 intermediate Result objects have been created and are discared at the end. Each of the 8 intermediate Result creation involves a loop on all the key-value pairs of the operations argument(s), and insertion in the new Result. If the initial Cauchy Stress Tensor Result contains a large number of key-value pairs, the computation cost of this criterion can be very important.


IV.2.4.4 Printing Results' content

One presents here an example in which Results corresponding to the STRAIN Nastran output statement are printed. Note that the non-diagonal components of the Nastran tensors corresponding to STRAIN statement are multiplied by two by Nastran. So, when imported into a DataBase, one divides the corresponding components by two (see remark 4 page [*] in Chapter III.1).

The preliminary part of the program is similar to the previous one: one initializes a DataBase, imports a Nastran model, produces the Groups and read Results. Then, the strain tensor in the honeycomb of +Z panel is output. This is done as follows:

   targetGrp = db.getGroupCopy("pan_PZ_Honey_72")
   strain =  db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Strain Tensor","ElemCenters",targetGrp,[])
   
   puts
   puts "Strain tensor in panel +Z honeycomb :"
   puts
   strain.each("int","int","int") do |key,values|
      for j in 0..3
         printf("%10s",key[j].to_s)
      end
      if (values[0]) then
         printf("%10s",values[0].to_s)
      else
         printf("%10s","nil")
      end
      for j in 1..6
         printf("%14f",values[j])
      end
      printf("\n")
   end
The "each" iterator is used with three "int" parameters. This leads to a printed output in which the layers are output with integer values.

In surface elements, two Results correspond to the STRAIN Nastran output statement: the strain tensor and the curvature tensor. The way components of the strain tensor are printed is similar as for the honeycomb. For the curvature tensor, the print is done as follows:

   targetGrp = db.getGroupCopy("pan_PZ_Al_2024")
   strain =  db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Strain Tensor","ElemCenters",targetGrp,[])
   
   puts
   puts "Strain tensor in panel +Z skins :"
   puts
   strain.each do |key,values|
      for j in 0..3
         printf("%10s",key[j].to_s)
      end
      if (values[0]) then
         printf("%10s",values[0].to_s)
      else
         printf("%10s","nil")
      end
      for j in 1..6
         printf("%14f",values[j])
      end
      printf("\n")
   end
This example is provided in file "SMALLEX/EX08/printStrain.rb".

Similarly, one print Results corresponding to Forces and Moments in CBAR elements. The interesting part is given below:

   targetGrp = db.getGroupAllFEM
   
   forces = db.getResultCopy("LAUNCH_ONE_MS2_X","Statics",\
      "Beam Forces","Elements",targetGrp,[])
      
   moments = db.getResultCopy("LAUNCH_ONE_MS2_X","Statics",\
      "Beam Moments","Elements",targetGrp,[])
   
   Util::printRes(STDOUT,"Forces",forces)
   Util::printRes(STDOUT,"Moments",moments)
The reader will observe in Results that ``Beam Forces'' in CBAR elements are given at the center of elements only, while ``Beam Moments'' are printed at the two end nodes of each element.

This example is provided in file "SMALLEX/EX08/printBeamForces.rb".


IV.2.4.5 Coordinate system transformations

One first presents an example, in which one modifies the coordinates of a point, and the components of a vector and of a tensor attached to this point. One first extracts the coordinate systems that are used in this example, and one defines the entities that shall be transformed:

    cs5=db.getCoordSysCopy(5)
    cs6=db.getCoordSysCopy(6)
        
    x=[5.0,0.0,0.0]
    v=[1.5,3.2,-4.0]
    m=[[2.0,3.0,-7.0],[1.0,0.0,0.0],[0.0,0.0,1.0]]
One assumes that the coordinates and components of the vector and tensor defined above are given in coordinate system 5. Coordinates and components can be expressed in basic coordinate system as follows:
    x0=cs5.changeCoordsA20(x)
    v0=cs5.changeCompsA20(x,v)
    m0=cs5.changeCompsA20(x,m)
Then, the coordinates and components can be re-expressed wrt coordinate system 5 using the following transformations:
    x5=cs5.changeCoords02B(x0)
    v5=cs5.changeComps02B(x0,v0)
    m5=cs5.changeComps02B(x0,m0)
Coordinates and components can also be transformed directly from coordinate system 5 to 6:
    x6=cs5.changeCoordsA2B(x5,cs6)
    v6=cs5.changeCompsA2B(x5,v5,cs6)
    m6=cs5.changeCompsA2B(x5,m5,cs6)
and then back to coordinate system 5:
    x5=cs6.changeCoordsA2B(x6,cs5)
    v5=cs6.changeCompsA2B(x6,v6,cs5)
    m5=cs6.changeCompsA2B(x6,m6,cs5)
The entire example is given in file "SMALLEX/EX09/modifCS.rb".

One presents below an example that illustrates the transformations of coordinate system in which Result components are expressed. One loads results on elements with PCOMP properties. Then the components are printed after several transformations:

The transformations are performed with the following instructions:
   ... 
   stress.modifyRefCoordSys(db,0)
   ... 
   stress.modifyRefCoordSys(db,"elemIJK")
   ... 
   stress.modifyRefCoordSys(db,7)
   ... 
   stress.modifyRefCoordSys(db,"matCS")
   ... 
   stress.modifyRefCoordSys(db,0,[1.0, 0.0, 0.0])
   ... 
   stress.modifyRefCoordSys(db,0,[0.0, 1.0, 0.0])
   ... 
   stress.modifyRefCoordSys(db,"elemCS")
   ...
Two versions of the sequence of transformations are proposed:
  1. In the first version, the transformations are done from the native Results. This means that the Result object to be transformed is reloaded from the DataBase prior to each transformation. One also presents an example of use of the ``each'' iterator of Result class.
  2. In the second version, the transformation is done successively on the same Result object which has been loaded only once. One also presents examples of the use of ``each'', ``each_key'' and ``each_values'' of the Result class. For example, two versions of the use of ``each_key'' iterator are presented:
          ...
          stress.each_key do |stressKey|
             for j in 0..3
                printf("%10s",stressKey[j].to_s)
             end
             printf("\n")
          end
          
          stress.each_key("int","int","int","int") do |elemId,nodeId,layerId,subLayerId|
             printf("%10s",elemId.to_s)
             printf("%10s",nodeId.to_s)
             printf("%10s",layerId.to_s)
             printf("%10s",subLayerId.to_s)
             printf("\n")
          end
          ...
    
The example ends with an error message for the second version because it is not possible to transform a Result object if stored values are already expressed in a projected coordinate system.

This example is provided in file "SMALLEX/EX09/modifCS2D.rb". A second version of the example with transformation of results on 3D elements is given in file "SMALLEX/EX09/modifCS3D.rb".

A third version involving the use of user defined coordinate systems is provided in file "SMALLEX/EX09/modifCS2Db.rb". This version illustrate the manipulation of CoordSys objects.

Finally, a fourth version of the 2D example is obtained by addition new arguments to the ``modifyRefCoordSys'' method in such a way that Results expressed in user or projected coordinate systems can further be modified without resulting in error messages. The successive transformation of coordinate systems become:

   ... 
   stress.modifyRefCoordSys(db,0)
   ... 
   stress.modifyRefCoordSys(db,"elemIJK")
   ... 
   stress.modifyRefCoordSys(db,7)
   ... 
   stress.modifyRefCoordSys(db,"matCS")
   ... 
   stress.modifyRefCoordSys(db,0,[1.0, 0.0, 0.0])
   ... 
   stress.modifyRefCoordSys(db,0,[0.0, 1.0, 0.0],0,[1.0, 0.0, 0.0])
   ... 
   stress.modifyRefCoordSys(db,"elemCS",nil,0,[0.0, 1.0, 0.0])
   ...
This last version is given in file "SMALLEX/EX09/modifCS2Dc.rb".


IV.2.4.6 Manipulation of Complex Results

In this example, Complex Results will be read from a Nastran xdb file. One first examines the information stored in the result file, in order to extract only the Results on needs to illustrate the manipulation of Complex Results.

IV.2.4.6.1 Extracting xdb lists of load cases and Results

The part of interest looks as follows:

   xdbFileName="../../MODEL/EXEC_XDB/sol111_ri_xyz.xdb"
   tab=NastranDb.getXdbLcScResNames(xdbFileName)
   lcNames=tab[0]
   scNames=tab[1]
   resNames=tab[2]
The lists of load cases, sub-cases and result types stored in the xdb file are saved into three Arrays of Strings that can be printed or used for other purposes.

IV.2.4.6.2 Extracting xdb Results information

The part of interest looks as follows:

   xdbFileName="../../MODEL/EXEC_XDB/sol111_ri_xyz.xdb"
   infos=NastranDb.getXdbLcInfos(xdbFileName)
   
   infos.each do |tab|
      STDOUT.printf("%-20s %-25s %-6d %-6d %-15g %-15g\n",
                    tab[0],tab[1],tab[3],tab[4],tab[5],tab[6])
   end
One extracts the information about the load cases and sub-cases to which Results are associated in the xdb file. Then, the name of load cases, sub-cases and associated integer and real data are printed. (Note, that the third String ID, which is always void, is not printed.) The example is provided in file "SMALLEX/EX17/printXdbLcInfos.rb".

IV.2.4.6.3 Manipulation of Complex Results

The results of "printXdbLcInfos.rb" are used to select load cases and sub-cases for which Results are imported into the DataBase. One also selects some of the Results:

   lcNames=[]
   lcNames << "SINUS_X"
   
   scNames=[]
   scNames << "Output 70 (f = 119.0000)"
   scNames << "Output 30 (f = 79.0000)"
   scNames << "Output 1 (f = 50.0000)"
   
   resNames=[]
   resNames << "Accelerations (MP), translational"
   resNames << "MPC Forces (MP), Forces"
   resNames << "MPC Forces (MP), Moments"
   resNames << "Accelerations (RI), translational"
   resNames << "MPC Forces (RI), Forces"
   resNames << "MPC Forces (RI), Moments"
Note that the selection of sub-cases to be imported into the DataBase is useful even when all the load cases are to be post-processed. Indeed the post-processing of a limited number of load cases at the same time reduces the amount of memory required to store all the Results. However, this means that several Result importations might be necessary, and this increases the time needed for disk IO operations.

Then the results are imported from xdb files corresponding to SOL111 Nastran calculations. One reads two files: one in which the Results are saved in rectangular format, and one in which they are saved in polar format:

   xdbFileName="../../MODEL/EXEC_XDB/sol111_ri_xyz.xdb"
   db.readXdb(xdbFileName,lcNames,scNames,resNames)
   
   xdbFileName="../../MODEL/EXEC_XDB/sol111_mp_xyz.xdb"
   db.readXdb(xdbFileName,lcNames,scNames,resNames)
To illustrate the manipulation of Results, the extraction is done for one particular load case, one particular sub-case, and on a small Group of 11 nodes only:
   tmpGroup=Group.new
   tmpGroup.setEntities("Node 60100:60110")
   
   lcName="SINUS_X"
   scName="Output 30 (f = 79.0000)"
Only the ``Accelerations'' Result is studied:
   resRI=db.getResultCopy(lcName,scName,"Accelerations (RI), translational",
                          "Nodes",tmpGroup,[])
   Util::printRes(STDOUT,"Accelerations resRI",resRI)
   
   resMP=db.getResultCopy(lcName,scName,"Accelerations (MP), translational",
                          "Nodes",tmpGroup,[])
   Util::printRes(STDOUT,"Accelerations resMP",resMP)
The following statements illustrate the polar-rectangular formats conversions:
   calcMP=Result.new
   calcMP.set2MP(resRI)
   Util::printRes(STDOUT,"Accelerations calcMP",calcMP)

   calcRI=Result.new
   calcRI.set2RI(resMP)
   Util::printRes(STDOUT,"Accelerations calcRI",calcRI)
And here, one shows how Real Results can be extracted from complex ones:
   resR=calcMP.getR
   resM=calcMP.getM
   resI=calcMP.getI
   resP=calcMP.getP
   Util::printRes(STDOUT,"Accelerations resR",resR)
   Util::printRes(STDOUT,"Accelerations resM",resM)
   Util::printRes(STDOUT,"Accelerations resI",resI)
   Util::printRes(STDOUT,"Accelerations resP",resP)
Of course, the reverse operation can be done too. Here is how Complex Results can be assembled from one pair of Real Results:
   assyRI=Result.new
   assyMP=Result.new
   assyRI.assembleComplex(2,resR,resI);
   assyMP.assembleComplex(3,resM,resP);

   Util::printRes(STDOUT,"Accelerations assyRI",assyRI)
   Util::printRes(STDOUT,"Accelerations assyMP",assyMP)
Finally, FeResPost allow to perform operations on Results that have Complex number arguments. For example, a Result object may be multiplied by a Complex number:
   require "complex"   

   Z=Complex.new(3.0,2.0)

   multRI=resRI.clone
   multRI*=Z
   Util::printRes(STDOUT,"Accelerations multRI",multRI)
   
   multMP=resMP.clone
   multMP*=Z
   Util::printRes(STDOUT,"Accelerations multMP",multMP)

IV.2.4.6.4 Working with XDB attachments

The example ``printXdbLcScResSizes.rb'' illustrates the extraction of Results from an XDB attachment. The advantage of this method for accessing Results, is that they must no be a priori loaded into a NastranDb object. The attachment is done as follows:

   xdbFileName="../../MODEL/EXEC_XDB/sol111_ri_xyz.xdb"
   db.attachXdb(xdbFileName)
One then selects the load case, the list of sub-cases, and the Result types for the extraction:
   lcName="SINUS_X"
    
    scNames=[]
    scNames << "Output 13 (f = 62.0000)"
    scNames << "Output 14 (f = 63.0000)"
    scNames << "Output 15 (f = 64.0000)"
    scNames << "Output 16 (f = 65.0000)"
    scNames << "Output 17 (f = 66.0000)"
    scNames << "Output 18 (f = 67.0000)"
    
    resNames=[]
    resNames << "Accelerations (RI), Rotational"
    resNames << "Accelerations (RI), Translational"
    resNames << "Displacements (RI), Rotational"
    resNames << "Displacements (RI), Translational"
The extraction is then done with a call like this one:
    h=db.getAttachmentResults(xdbFileName,lcName,scNames,resNames,"Nodes",grp)
Finally, the Results that have been returned in h (a Hash object) can be retrieved as follows:
   h.each do |id,res|
        lcName=id[0]
        scName=id[1]
        resName=id[2]
        size=res.Size
        STDOUT.printf("%s - %s - %s : %d\n",lcName,scName,resName,size)
    end


IV.2.4.7 Manipulation of XDB attachments

This section illustrates the manipulation of XDB attachments. All the examples are to be found in directory ``SMALLEX/EX19''. The different examples are in increasing order of difficulty.

In order to reduce the size of extracted Results, the extractions are done on the nodes of a small Group in all the examples below. Note however that other extractions could have been done. Also, if the two last parameters of the extraction functions are omitted, all the Result values are returned by the extraction methods.

IV.2.4.7.1 Extracting information from XDB attached file

In the example ``attachedXdbLcInfos.rb'', one shows how content information can be extracted from an attached XDB file. The XDB file must first be attached to the DataBase:

   xdbFileName="../../MODEL/EXEC_XDB/sol111_ri_xyz.xdb"
   db.attachXdb(xdbFileName)
Then, the information is extracted and printed exactly as in one of the examples of section IV.2.4.6:
   infos=db.getAttachmentLcInfos(xdbFileName)
   infos.each do |tab|
      STDOUT.printf("%-20s %-25s %-6d %-6d %-15g %-15g\n",
                    tab[0],tab[1],tab[3],tab[4],tab[5],tab[6])
   end
It is also possible to extract other information like the list of Result names, sub-case names or load cases names. One shows below how the list of result names can be printed:
   resNames=db.getAttachmentResNames(xdbFileName)
   resNames.each do |resName|
      STDOUT.printf("%-20s\n",resName)
   end

IV.2.4.7.2 Extracting Results from XDB attached file

The example ``attachedXdbResults.rb'' shows how Results can be extracted from an XDB attachment. As in the previous example, the file is first attached. Then, one decides which result types and for which load case Results are extracted:

   lcName="SINUS_Z"
   resName="Accelerations (RI), Translational"
Remember than only one load case name can be specified. However, an Array of result names can be provided. In this case, one decides to extract only one result type: ``Accelerations (RI), Translational''. On the other hand, an Array of sub-cases can be specified for the extraction of Results. In this case, the Array is first obtained by calling the ``getAttachmentScNames'' method:
   scNames=db.getAttachmentScNames(xdbFileName)
Then, the Results are extracted as follows:
   results=db.getAttachmentResults(xdbFileName,lcName,scNames,resName,
        "Nodes",grp)
The results are returned in a Hash object that contains pairs of Result keys, and the corresponding Results. The Results can be printed as follows:
   results.each do |key,res|
       Util::printRes(STDOUT,key[1]+" ==> "+key[2],res)
   end
Note that at the beginning of the script, the buffer total maximum capacity is set to 1Mb as follows:
  NastranDb::setStorageBufferMaxCapacity(1.0)
Another example of Results extraction from an XDB attachment is presented in file ``attachedXdbExtract.rb''. There several Result types are extracted for a single load case and a single sub-case:
   lcName="LAUNCH_ONE_MS2_Y"
   scName="Statics"
   resNames=[]
   resNames << "Shell Forces"
   resNames << "Shell Moments"
   resNames << "Strain Tensor"
   resNames << "Curvature Tensor"
   
   location="ElemCenters"
   grp=db.getGroupCopy("pan\_MZ")
   layers="NONE"
    
   results=db.getAttachmentResults(xdbFileName,lcName,scName,resNames,
        location,grp,layers)
Four Result types have been selected. The list of layers is set to ``NONE'' to avoid the extraction of Strains on each ply of each element. (One is interested only in the laminate average Strain.)

Results can then be accessed, individually by extracting the elements of the Hash object returned by the ``getAttachmentResults'' method. For example:

   key=[lcName,scName,"Shell Moments"]
   shMoments=results[key]
   key=[lcName,scName,"Shell Forces"]
   shForces=results[key]
   key=[lcName,scName,"Strain Tensor"]
   shAvrgStrains=results[key]
   key=[lcName,scName,"Curvature Tensor"]
   shCurvatures=results[key]

IV.2.4.7.3 Extracting linear combination of Results from XDB

In order to save time and simplify the programming of post-processing, it is also possible to extract linear combinations of Results. This is presented in example ``attachedXdbCombili.rb''.

The linear combination is defined as an Array defining the factors and elementary load cases to consider:

   scName="Statics"
   resName="Displacements, Translational"
   lcNames=["LAUNCH_ONE_MS2_X","LAUNCH_ONE_MS2_Y","LAUNCH_ONE_MS2_Z"]
   factors=[100.0, 50.0, 20.0]
   
   ...
   
   combili=[]
   (0..2).each do |i|
      combili << [factors[i], xdbFileName, lcNames[i]]
   end
Then, the linearly combine Results are extracted as follows:
   lcName="CombiLC"
   results=db.getAttachmentResultsCombili(lcName,combili,scName,resName,
        "Nodes",grp)
Note that in this case, one single Result object is returned in the ``results'' Hash object. For example, in this case, one could have provided an Array of Result names instead of the ``resName'' String argument.

In example ``attachedXdbDynamCombili.rb'' the same operation is performed for dynamic Results, and an Array of String is provided as list of sub-cases argument. This illustrates the use of ``getAttachmentResultsCombili'' method returning several Results.

IV.2.4.7.4 Random analysis or integration of PSD functions

Example ``EX19/attachedXdbRandom.rb'' illustrates the use of method ``Post.calcRandomResponsecalcRandomResponse'' in ``Post'' Module. One calculates the RMS equivalent for a random response.

The calculation is done using the XDB file corresponding to a SOL111 Nastran analysis. The RMS values for accelerations are calculated. The example defines two functions:

The first part of ``computeRms'' function identifies the subcase names in XDB file, for the selected load case, recovers the corresponding frequencies, and sorts the sub-case names by order of increasing frequency:
    infos=db.getAttachmentLcInfos(xdbFileName)
    h={}
    infos.each do |tab|
        if tab[0]==lcName then
            f=tab[5]
            scName=tab[1]
            h[f]=scName
        end
    end
    allFreqs=h.keys.sort
    totalNbrFreqs=allFreqs.size
Then, the integration is calculated by slices. The ``addRes'' output of a call to ``Post.calcRandomResponsecalcRandomResponse'' is used as argument for the next call to the same method. This ``addRes'' corresponds to the last PSD integration Result object:
   idMin=idMax=0
    addRes=nil
    res=nil
    while idMax<totalNbrFreqs
        idMin=idMax
        idMax=[idMin+maxNbrFreqsPerSlice-1,totalNbrFreqs].min
        freqs=allFreqs[idMin..idMax]
        scNames=[]
        psdInput=[]
        freqs.each do |f|
            scName=h[f]
            scNames << scName
            psdInput << psdFunction(f)
        end
        results=db.getAttachmentResults(xdbFileName,lcName,scNames,
            resName,method,grp)
        sortedResults=[]
        scNames.each do |scName|
            sortedResults << results[[lcName,scName,resName]]
        end
        ret=Post.calcRandomResponse(false,false,sortedResults,freqs,
            psdInput,integType,addRes)
        addRes=ret[1]
        res=ret[2]
    end


IV.2.5 A few useful tools

One presents in this section a few examples that provide at the same time, tools that can be useful in many projects.


IV.2.5.1 Definition of acceleration fields

When sizing a satellite's structure, one is often asked to define load cases corresponding to quasi-static accelerations on sub-parts of the structure. For example, at system level, dynamic analyses have shown that the upper part of the structure may be submitted to more severe accelerations than the rest of the satellite.

Unfortunately, Nastran does not allow the definition of quasi-static accelerations on sub-parts of the structure. The ``GRAV'' card, only allows the definition of accelerations globally on the whole structure.

It is possible to solve the problem in two steps:

  1. One calculates with Nastran three elementary load cases corresponding to unit accelerations of 1  $ {\text{m}}/{\text{s}}^2$ applied on the entire structure oriented on the three structural axes respectively. The definition of these loads is done with Nastran ``GRAV'' cards.
  2. Then one recovers the finite element Results of the Nastran ``op2'' file, and after performing some operations on the Results, one performs an appropriate printing of the Results to produce ``FORCE'' Nastran Bulk cards. Two different results can be used to perform this operation: the applied loads provided by ``OLOAD'' output, or the applied loads obtained from ``GPFORCE'' output.
This method is equivalent to the production of a force field.

In this examples, one defines several functions. Therefore, a module ``Grav'' has also be created, and all functions are placed in the module. A first utility function is used to add to a DataBase a new Group created by performing a union of elementary Groups:

   def Grav.AddNewGroupsByUnions(db,totalGroupName,elemGroupNames)
      totalGroup=Group.new()
      for i in 0...elemGroupNames.size
         elemGroup=db.getGroupCopy(elemGroupNames[i])
         totalGroup+=elemGroup
      end
      totalGroup.Name=totalGroupName
      db.addGroupCopy(totalGroup)
   end
The first argument is the DataBase from which the elementary Groups are Retrieved and to which the new Group is created. The second argument is a String object the value of which is the new Group name. The last argument is an Array of Strings containing the names of the elementary Groups.

Another function is devoted to the printing of the ``FORCE'' Nastran Bulk Data Cards in a file. The function is defined as follows:

def Grav.writeForce(fileName,lcId,coordSysId,forces)
    print "creating file " 
    puts fileName

    table=forces.getData()
    cards=[]
    for oneRes in table
        values=[]
        values << lcId;
        values << oneRes[1]
        values << coordSysId
        x=oneRes[5]
        y=oneRes[6]
        z=oneRes[7]
        norm=Math.sqrt(x*x+y*y+z*z)
        if (norm>1.0e-10) then
            values << norm
            values << x/norm
            values << y/norm
            values << z/norm
        end
        cards << values
    end
    NastranDb.writeNastranCards(fileName,"w","left","short","FORCE",cards);
end
This function has four arguments:
  1. A String corresponding to the name of the file in which the cards are printed.
  2. An integer corresponding to the load case identifier.
  3. An integer corresponding to the coordinate system in which the components of the ``FORCE'' vectors are given.
  4. A Result object containing the nodal forces. The Result must be vectorial and defined at nodes. Also the forces should be expressed in the coordinate system identified by the third argument.
The function works as follows: This version of the method is proposed in ``SMALLEX/EX06/makeGravForces.rb''file.

Another version of the function, provided in ``SMALLEX/EX06/makeGravForcesB.rb'' reads as follows:

def Grav.writeForce(fileName,lcId,coordSysId,forces)
    print "creating file " 
    puts fileName

    table=forces.getData()
    cards=[]
    for oneRes in table
        values=[]
        values << "FORCE"
        values << lcId;
        values << oneRes[1]
        values << coordSysId
        x=oneRes[5]
        y=oneRes[6]
        z=oneRes[7]
        norm=Math.sqrt(x*x+y*y+z*z)
        if (norm>1.0e-10) then
            values << norm
            values << x/norm
            values << y/norm
            values << z/norm
        end
        cards << values
    end
    NastranDb.writeNastranCards(fileName,"w","left","short",cards);
end
Remark that one uses the ``5 arguments'' version of ``writeNastranCards'' method. (The ``cardName'' argument is omitted.) On the other hand, each ``values'' Array has one additional element. The first element of the Array is the ``FORCE'' card name.

The function ``Grav.genAllGravFields'' is the function that performs the extraction of force fields from the Results stored in the DataBase:

   def Grav.genAllGravFields(db,data)
      nbr=data.size
      for i in 0...data.size
         groupName=data[i][0]
         extName=data[i][1]
         baseLID=data[i][2]
         csId=1001

         target=db.getGroupCopy(groupName)

         forces1=db.getResultCopy("LAUNCH_ONE_MS2_X","Statics",\
            "Applied Loads, Forces","Nodes",target,[])
         forces1.modifyRefCoordSys(db,csId)
         forces2=db.getResultCopy("LAUNCH_ONE_MS2_Y","Statics",\
            "Applied Loads, Forces","Nodes",target,[])
         forces2.modifyRefCoordSys(db,csId)
         forces3=db.getResultCopy("LAUNCH_ONE_MS2_Z","Statics",\
            "Applied Loads, Forces","Nodes",target,[])
         forces3.modifyRefCoordSys(db,csId)

         Grav.writeForce("force1_"+extName+".bdf",baseLID+1,csId,forces1)
         Grav.writeForce("force2_"+extName+".bdf",baseLID+2,csId,forces2)
         Grav.writeForce("force3_"+extName+".bdf",baseLID+3,csId,forces3)

         GC.start()
      end
   end
This function receives two arguments:
  1. The DataBase from which the Results are extracted.
  2. An Array of Arrays containing the information necessary for the production of the different force fields. Each element Array contains three elements:
    1. A String object containing the name of the Group on which the Result forces are retrieved.
    2. A String containing the name of the extension to be added to the output file name.
    3. An integer corresponding to the base of the load identifier by which the force field shall be referred in the bulk data file. (Actually, three force fields are produced for each Group in the directions X, Y and Z respectively. The corresponding identifiers are produced by adding 1, 2 or 3 respectively to the base identifier of the load.
The function performs a loop on all the elements of the ``data'' Array argument. For each element Finally The ``main'' function looks like this:
   def Grav.main()

   # Creation of the dataBase :

      db=NastranDb.new()
      db.Name="tmpDB"
      db.readBdf("../../MODEL/MAINS/unit_xyz.bdf")
      db.readGroupsFromPatranSession("../../MODEL/PATRAN/groups.ses")

      Grav.AddNewGroupsByUnions(db,"upper_set",\
         ["pan_SUP", "struts_ALL", "fittings_ALL"])

   # Reading of results :

      db.readOp2("../../MODEL/EXEC_OP2/unit_xyz.op2","Results")
      
   # Production of force fields :

      data=Array.new()
      data.push(["pan_MX",    "PAN_MX", 611000])
      data.push(["pan_MY",    "PAN_MY", 612000])
      data.push(["pan_MZ",    "PAN_MZ", 613000])
      data.push(["pan_PX",    "PAN_PX", 614000])
      data.push(["pan_PY",    "PAN_PY", 615000])
      data.push(["pan_PZ",    "PAN_PZ", 616000])
      data.push(["upper_set", "UPPER",  617000])
      
      Grav.genAllGravFields(db,data)

   end
It works as follows: The program is executed by a call to main function:
   Grav.main()
Note that some of the output files produced by this example are used in the definition of loads for Nastran calculations in Chapter IV.1.

These examples are provided in files "SMALLEX/EX06/makeGravForces.rb" and "SMALLEX/EX06/makeGravForcesB.rb".


IV.2.5.2 Definition of temperature fields

This example is similar to the previous one, but instead of generating ``FORCE'' fields, one generates temperature fields with ``TEMP'' Nastran cards. Also, the example differs by the fact that no Results are read from an ``op2'' file.

One writes two functions devoted to the printing of ``TEMP'' cards in a Bulk Data File. The first function writes a constant temperature field on a Group:

   def Therm.writeConstTempCards(fileName,lcId,target,constT)
      print "creating file " 
      puts fileName
      
      nodes = target.getEntitiesByType("Node")
      index = 0
      cards=[]
      while index < nodes.size
         if (nodes.size-index>=3) then
            values = []
            values << lcId
            for i in 0..2
               values << nodes[index]
               values << constT
               index+=1
            end
         else
            values = []
            values << lcId
            for i in 0...(nodes.size-index)
               values << nodes[index]
               values << constT
               index+=1
            end
         end
         cards << values
      end
      NastranDb.writeNastranCards(fileName,"w+","left","short",
         "TEMP",cards);
   end
The principle of the function is that one recovers the list of nodes contained in the Group. Then for each node, one writes a ``TEMP'' entry. The second printing function prints a temperature field corresponding to a scalar Result object:
   def Therm.writeFieldTempCards(fileName,lcId,tempField)
      print "creating file " 
      puts fileName
      
      tempData = tempField.getData
      index = 0
      size = tempData.size
      cards=[]
      while index < size
         if (size-index>=3) then
            values = []
            values << lcId
            for i in 0..2
               values << tempData[index][1]
               values << tempData[index][5]
               index+=1
            end
         else
            values = []
            values << lcId
            for i in 0...(size-index)
               values << tempData[index][1]
               values << tempData[index][5]
               index+=1
            end
         end
         cards << values
      end
      NastranDb.writeNastranCards(fileName,"w+","left","short",
         "TEMP",cards);
   end
The principle of the function is very similar to the principle of function ``FORCE'' field printing function described in section IV.2.5.1.

The main function begins with an initialization of the DataBase:

      db=NastranDb.new()
      db.Name="tmpDB"
      db.readBdf("../../MODEL/MAINS/orbit_unit_xyz.bdf")
      db.readGroupsFromPatranSession("../../MODEL/PATRAN/groups.ses")
Then one defines constant temperature fields on parts of the structure. One defines four cases obtained by a combination of cold or hot temperatures, and application to two Groups. The second Group is build by assembling the lateral panels. This part of the main functions looks like:
      # Generation of temperature fields on panel +Z :
      
      tmpGrp = db.getGroupCopy("pan_PZ")
      Therm.writeConstTempCards("temp_P120_PAN_PZ.bdf",621001,tmpGrp, 120.0)
      Therm.writeConstTempCards("temp_M100_PAN_PZ.bdf",621001,tmpGrp,-100.0)

      # Generation of temperature fields on lateral panels :
      
      tmpGrp = db.getGroupCopy("pan_PX")
      tmpGrp += db.getGroupCopy("pan_PY")
      tmpGrp += db.getGroupCopy("pan_MX")
      tmpGrp += db.getGroupCopy("pan_MY")
      Therm.writeConstTempCards("temp_P120_PAN_LAT.bdf",622001,tmpGrp, 120.0)
      Therm.writeConstTempCards("temp_M100_PAN_LAT.bdf",622002,tmpGrp,-100.0)
One also defines temperature fields by production of a corresponding Result object. In this case, the Result is build from the coordinates:
      # Generation of a temperature gradient field : 
      
      tmpGrp = Group.new
      tmpGrp.setEntities("Node 1:99999")
      tmpGrp.matchWithDbEntities(db)
      db.generateCoordResults
      
      coords=db.getResultCopy("","","Coordinates","NodesOnly",tmpGrp,[])
      coords.modifyPositionRefCoordSys(db,1001)
      tGradX=coords*[100.0,   0.0,   0.0]
      Therm.writeFieldTempCards("temp_GRAD_X.bdf",623001,tGradX)
      tGradY=coords*[  0.0, 100.0,   0.0]
      Therm.writeFieldTempCards("temp_GRAD_Y.bdf",623002,tGradY)
      tGradZ=coords*[  0.0,   0.0, 100.0]
      Therm.writeFieldTempCards("temp_GRAD_Z.bdf",623003,tGradZ)
Note that the previous ruby lines illustrate the use of several capabilities of FeResPost: Note that the temperature fields printed in BDF files are used in the definition of loads for Nastran calculations in Chapter IV.1.

The example is provided in file "SMALLEX/EX07/makeTempFields.rb".


IV.2.5.3 Calculation of a total force and moment

One explains here how the resulting global force and moment can be calculated from distributed force and moments. This example illustrates the use of method ``calcResultingFM'' in class ``Result''.

The ruby function that performs the calculation of the total force and moments looks like follows:

def calcOneGlobFM(db,lcName,scName,elemGrp,nodeGrp,locCS,coords)
   
   # Target Group :

      targetGrp = Group.new()
      tmpNodeGrp=db.getNodesAssociatedToElements(elemGrp)
      targetGrp = tmpNodeGrp * nodeGrp
      tmpElemGrp=db.getElementsAssociatedToNodes(targetGrp)
      targetGrp += tmpElemGrp * elemGrp

   # Inspecting and reading Results :

      tpNameF = "Grid Point Forces, Internal Forces"
      tpNameM = "Grid Point Forces, Internal Moments"

      tmpF =  db.getResultCopy(lcName,scName,tpNameF,"ElemNodes",targetGrp,[])
      tmpM =  db.getResultCopy(lcName,scName,tpNameM,"ElemNodes",targetGrp,[])
      
      resFM = Result.calcResultingFM(db,tmpF,tmpM,locCS,coords)
      return resFM
   
end
The arguments of the function are:
  1. ``db'', the DataBase given as argument to ``calcResultingFM''.
  2. ``lcName'', a String containing the name of the load case for which the results are retrieved.
  3. ``scName'', a String containing the name of the sub-case for which the results are retrieved.
  4. ``elemGrp'', a Group containing the elements from which one recovers the local forces and moments.
  5. ``nodeGrp'', a Group containing the nodes on which the forces will be recovered.
  6. ``locCS'', the coordinate system in which results are recovered. Its value can be of integer or CoordSys type.
  7. ``coords'', an Array of three Real values containing the coordinates of the recovery point expressed in the coordinate system.
The function builds a Group called ``targetGrp'' containing the list of elements and nodes on which Grid Point Forces and Moments are recovered. To reduce the computation cost, the targetGrp object contains only the elements and nodes on which the results are recovered. One this Group is defined, one recovers the corresponding Force and Moment fields, and an appropriate call to ``Result.calcResultingFM'' calculates the resulting total force and moment which are returned by the function.

The main part of the example consists in building the DataBase, loading the Results and performing the calculations for any combination of three load cases and six interfaces. The definition of load cases and interfaces are done as follows:

   lcNames = ["LAUNCH_ONE_MS2_X", "LAUNCH_ONE_MS2_Y", "LAUNCH_ONE_MS2_Z"] 
   scName = "Statics"
   
   interfaces=Array.new()
   interfaces << ["pan_PZ", "fitting_PXMYMZ",1001,[ 0.440000,-0.440, 0.6445]]
   interfaces << ["pan_PZ", "fitting_MXMZ",  1001,[-0.426667, 0.000, 0.6445]]
   interfaces << ["pan_SUP","fitting_MXMYPZ",1002,[ 0.310835,-120.0, 1.4000]]
   interfaces << ["pan_SUP","fitting_PXPZ",  1002,[ 0.310835,   0.0, 1.4000]]
   interfaces << ["pan_SUP","fitting_MXPYPZ",1002,[ 0.310835, 120.0, 1.4000]]
   interfaces << ["pan_PZ", "fitting_PXPYMZ",1001,[ 0.440000, 0.440, 0.6445]]
Then the loops on data are performed, with the calls to ``calcOneGlobFM'', and the results are printed:
   for lcName in lcNames
      printf "\n   %s :\n\n",lcName
      for interf in interfaces
         elemGrp = db.getGroupCopy(interf[0])
         nodeGrp = db.getGroupCopy(interf[1])
         cs=interf[2]
         coords=interf[3]
         
         fm = calcOneGlobFM(db,lcName,scName,elemGrp,nodeGrp,cs,coords)         
         f=fm[0]
         m=fm[1]
      
         printf "%20s%20s%10d%10.3f%10.3f%10.3f%10.3f%10.3f%10.3f\n",\
                   elemGrp.Name,nodeGrp.Name,cs,\
                   f[0],f[1],f[2],m[0],m[1],m[2]
      end
   end
   printf "\n"
Typically, the calculation of global force and moment for a given interface can be used to estimate loads to be used to calculate a detailed model (of a metallic fitting, for example). It can also be used for post-processing (for example to calculate margins of safety for a global sliding of an interface).

The example is provided in file "SMALLEX/EX10/makeTempFields.rb".


IV.2.5.4 Outputting a Gmsh file

This example illustrates the creation of a Gmsh file for later visualization with Gmsh. The part of the data file specific to the ``writeGmsh'' function call is as follows:

   # Group creation :
      
   meshGrp=db.getGroupAllFEM

   targetGrp = db.getGroupCopy("pan_PZ_Al_2024")
   targetGrp2 = db.getGroupCopy("pan_PZ")
   
   # Stress data in skins :

   stress = db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Stress Tensor","ElemCenters",targetGrp,[])
   stress2 = db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Stress Tensor","ElemCorners",targetGrp,[])
   
   displ = db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Displacements, Translational","Nodes",targetGrp2,[])
   norm = displ.deriveVectorToOneScal("abs")
   
   # Stress data in honeycomb :
   
   targetGrp =tmpGroup_Honey_72
   honeyStress=db.getResultCopy("TEMP_GRAD_X","Statics",\
      "Stress Tensor","ElemCenters",targetGrp,[])

   # Gmsh output :
   
   db.writeGmshMesh("brol.msh",0,meshGrp,false)
   db.writeGmsh("brol.gmsh",0,[[stress,"stress","ElemCenters"],\
                               [stress2,"stress2","ElemCorners"],\
                               [honeyStress,"honeyStress","ElemCenterPoints"],\
                               [displ,"displ","Nodes"],\
                               [norm,"norm","Nodes"]],\
                              [[db.getGroupCopy("pan_PZ"),"mesh pan_PZ"],\
                               [db.getGroupCopy("pan_MZ"),"mesh pan_MZ"],\
                               [db.getGroupCopy("pan_PX"),"mesh pan_PX"]],\
                              [[meshGrp,"skel sat"]])
Actually, only the last function call is new. The example is provided in file "SMALLEX/EX11/writeGmsh.rb".


IV.2.6 Saving and retrieving Results from an SQL database

``Result'' and ``Group'' objects can be saved into SQL BLOBs for storing or manipulation in SQL databases. This feature has been introduced to allow the management of persistence of intermediate results calculated with FeResPost.

One proposes here a small example illustrating this feature. The example deals with Nastran dynamic analysis Results that are store into an SQLite database. This type of operation may be handy, as the access to dynamic analysis results from an XDB file may sometimes be very unpractical. In particular, the insertion table is organized in such a way that Results can be accessed separately by subcases.

This example is based on SQLite database system but the adaptation to other SQL database systems should not be a problem. To run the example you must first install the ``sqlite3'' ruby gem on your computer.


IV.2.6.1 Saving objects in an SQLite database

To use SQLite, one first requires the corresponding ruby gem:

    require "rubygems"
    require "sqlite3"
Then, the database can be created:
    fName="brol.fdb"
    if (File::exists?(fName))
        sqldb = SQLite3::Database.open( fName )
    else
        sqldb = SQLite3::Database.new( fName )
sqldb.execute <<SQL
    PRAGMA auto_vacuum = FULL
SQL
    end
In this case only one SQL table is created in the database. The columns correspond to several data associated with each individual Results, and the BLOB corresponding to the Result itself:
sqldb.execute <<SQL
    CREATE TABLE IF NOT EXISTS dynam_results_1 (
        lcName TEXT,
        scName TEXT,
        resName TEXT,
        tensorOrder INTEGER,
        intId1 INTEGER,
        intId2 INTEGER,
        realId1 REAL,
        realId2 REAL,
        size INTEGER,
        result BLOB,
        PRIMARY KEY(lcName,scName,resName)
    );
SQL
Finally, one loops on xdb attachment Results. For each load case and Result name, one extracts the Results corresponding to each subcase, and inserts it into the database:
    db.attachXdb(xdbFileName)

    lcNames=db.getAttachmentLcNames(xdbFileName)
    scNames=db.getAttachmentScNames(xdbFileName)
    resNames=db.getAttachmentResNames(xdbFileName)
    
    lcNames.each do |lcName|
        resNames.each do |resName|
            results=db.getAttachmentResults(xdbFileName,lcName,scNames,resName)
            if (results) then
                results.each do |key,res|
                    puts key
                    sqldb.execute( "insert or replace into 
                        dynam_results_1 values (?,?,?,?,?,?,?,?,?,?)",
                        lcName,key[1],resName,res.TensorOrder,
                        res.getIntId(0),res.getIntId(1),
                        res.getRealId(0),res.getRealId(1),res.Size,
                        SQLite3::Blob.new(res.toBlob()))
                end
            else
                puts "NO FOR" + lcName + resName
            end
        end
The example is provided in file "SMALLEX/EX20/rehashDynamicResults.rb"


IV.2.6.2 Retrieving objects from an SQLite database

This example, uses the database created in the example of section IV.2.6.1 and retrieves Results or deletes some of the data. An example of statements that allow to retrieve Results is given below:

    sqldb.query("select * from dynam_results_1 where lcName = ? and realId1 = ?", 
            ["SINUS_X",97.0] ).each do |tab|
        puts tab[0],tab[1],tab[2],tab[8]
        res=Post.convertBlob(tab[9])
        puts res.Size()
    end
Items can be deleted from database by statements of this type:
    sqldb.execute("delete from dynam_results_1 where resName = ?", 
            "Strain Tensor (RI)" )
    sqldb.execute("delete from dynam_results_1 where resName = ?", 
            "Applied Loads (RI), Forces" )
    sqldb.execute("delete from dynam_results_1 where resName = ?",
            "Applied Loads (RI), Moments" )
    sqldb.execute("delete from dynam_results_1 where resName = ?", 
            "Accelerations (RI), Rotational" )
    sqldb.execute("delete from dynam_results_1 where resName = ?", 
            "Velocities (RI), Rotational" )
    sqldb.execute("delete from dynam_results_1 where resName = ?", 
            "Displacements (RI), Rotational" )
    sqldb.execute("delete from dynam_results_1 where realId1 < ?",  
            90.0 )
The example is provided in file "SMALLEX/EX20/deleteSomeResults.rb"


IV.2.7 Reading optimization results

One provides here an example for the reading of optimization results. The first steps of the example consists in the creation of a NastranDb object, and the attachment of an XDB file:

    db=NastranDb.new()
    db.Name="tmpDB"
    #~ db.readBdf("../../MODEL/EXEC_XDB/sol200_a.bdf")

    xdbFileName="../../MODEL/EXEC_XDB/sol200_a.xdb"
    #~ xdbFileName="../../MODEL/EXEC_XDB/sol200_b.xdb"
    db.attachXdb(xdbFileName)
Remark, that the reading of the Nastran finite element model from a BDF file is not necessary to access optimization results.

In the Nastran examples, one provides two optimization runs:

The two runs lead to similar kinds of outputs, including the history of design variables. Only, the topometric optimization generates automatically design variables (for example, one variable per element). Then, the numbering of design variables kind by awkward.

The printing of design variables history is done with the following ruby instructions:

	x=db.getAttachmentDesVarHistory(xdbFileName,nil,nil)
	STDOUT.printf("%14s%14s%14s\n","STEP","DVID","VALUE")
	x.each do |tab|
		STDOUT.printf("%14d%14d%14f\n",tab[0],tab[1],tab[2])
	end
In this case, one prints the history of all design variables, and for all steps. (``nil'' values are passed for corresponding arguments of the ``getAttachmentDesVarHistory'' method.) If you do the same with ``sol200_b.xdb'', file, you will obtain a very long output as the number of design variables can be very large for topometric optimization.

The definition of constraints is printed as follows:

	x=db.getAttachmentConstrDefinitions(xdbFileName)
	STDOUT.printf("%8s%8s%8s%8s%8s%14s\n","IDCID","DCID","IRID","TYPE","LUFLAG","BOUND")
	x.each do |tab|
		str=""
		if tab[4]==1 then
			str=">"
		elsif tab[4]==2 then
			str="<"
		end
		STDOUT.printf("%8d%8d%8d%8d%8s%14f\n",tab[0],tab[1],tab[2],tab[3],str,tab[5])
	end
And the corresponding histories are obtained as follows:
	x=db.getAttachmentConstrHistory(xdbFileName)
	STDOUT.printf("%8s%8s%14s\n","STEP","IDCID","VALUE")
	x.each do |tab|
		STDOUT.printf("%8d%8d%14f\n",tab[0],tab[1],tab[2])
	end
Here, the history is printed for all optimization steps, as the corresponding parameter is not provided.

And similarly, the objective history is printed as follows:

	x=db.getAttachmentObjectiveHistory(xdbFileName)
	STDOUT.printf("%8s%14s%8s%14s\n","STEP","OBJ.","IRID","Cst. VALUE")
	x.each do |tab|
		STDOUT.printf("%8d%14f%8d%14f\n",tab[0],tab[1],tab[2],tab[3])
	end
You can remark that when a topometric optimization is calculated by Nastran as a design variable optimization. (Nastran defines automatically one design variable per element.)

The example is provided in file "SMALLEX/EX21/printSol200Infos.rb".


IV.2.8 ``Raw'' access to XDB file content

In directory ``SMALLEX/EX22'', one presents several examples with Ruby extension that illustrates the raw access to XDB files. (The corresponding examples for COM component and .NET assembly are provided in directories ``COMEX/EX13'' and ``NETEX/EX22''.)

IV.2.8.1 Utilities

In file ``dictPrint.rb'' one defines two methods that print the list of dictionnary keys, and/or the entire dictionnary. These methods are:

(These two methods are called in the different examples.)

IV.2.8.2 Printing Coordinate System Table Matrix

The example is provided in ``SMALLEX/EX22/recoverCSTM.rb''. The interesting part of the example is the use of the iterator:

db.iter_xdbRaw(xdbFileName,["CSTM",0],"iiffffffffffffifffffffff").each do |tab|
    STDOUT.printf("Coordinate system ID: %d\n",tab[0])
    STDOUT.printf("Coordinate system type: %d\n",tab[1])
    STDOUT.printf("Coordinate system origin (wrt 0): %14g %14g %14g\n",
            tab[2],tab[3],tab[4])
    STDOUT.printf("Coordinate system V1 (wrt 0)    : %14g %14g %14g\n",
            tab[5],tab[6],tab[7])
    STDOUT.printf("Coordinate system V2 (wrt 0)    : %14g %14g %14g\n",
            tab[8],tab[9],tab[10])
    STDOUT.printf("Coordinate system V3 (wrt 0)    : %14g %14g %14g\n",
            tab[11],tab[12],tab[13])
end
Note that the CSTM table correspond to a FEM modeling table and not to a result table. This shows that the raw access to XDB file can be used to access modeling information.

Generally, maximum one table per type is defined in XDB file, if the table corresponds to modeling information. However, this is sometimes different. For example FEM modeling table may correspond to an output of optimization run.

IV.2.8.3 Accessing results

We give here an example, in which CBAR element forces are read from the XDB file. This is done to illustrate the extraction of element results. Otherwise, there is no practical use to this example, as the corresponding extractions can be done by the ``usual'' XDB result extraction methods. The example is provided in file ``SMALLEX/EX22/recoverFBAR.rb''.

One first attempts to access the definition of load cases:

db.each_xdbRaw(xdbFileName,["SUBCASES",0],"iiiiiiii") do |tab|
    puts tab
end

str="i"
(0..95).each do |i|
    str+="s"
end
db.each_xdbRaw(xdbFileName,["SUBCTITL",0],str) do |tab|
    STDOUT.printf("%d\n",tab[0])
    (0..2).each do |j|
        str=""
        (1..32).each do |i|
            str+=tab[32*j+i]
        end
        str.strip!
        STDOUT.printf("%s\n",str)
    end
end
The integers extracted from ``SUBCASES'' table correspond to sub-case integer ID, the corresponding load ID, the SPC ID... The access to ``SUBCTITL'' shows how strings must be concatenated when they correspond to several words.

The reading of CBAR forces is done as follows:

modulo=-1;
db.each_xdbRaw(xdbFileName,["FBARR",2],"iiiiiiiii") do |tab|
    if (tab[0]==0) then
        modulo=tab[1]
    end
    break;
end
db.each_xdbRaw(xdbFileName,["FBARR",2],"iffffffff") do |tab|
    accessType=12
    if (tab[0]>0) then
        elemIntId=tab[0]/modulo
        elemExtId=db.getAttachmentElementExtId(xdbFileName,accessType,elemIntId)
        STDOUT.printf("%30s : %d\n","Element ID",elemExtId)
        STDOUT.printf("%30s : %g\n","M bending A 1",tab[1])
        STDOUT.printf("%30s : %g\n","M bending A 2",tab[2])
        STDOUT.printf("%30s : %g\n","M bending B 1",tab[3])
        STDOUT.printf("%30s : %g\n","M bending B 2",tab[4])
        STDOUT.printf("%30s : %g\n","F shear 1",tab[5])
        STDOUT.printf("%30s : %g\n","F shear 2",tab[6])
        STDOUT.printf("%30s : %g\n","F axial",tab[7])
        STDOUT.printf("%30s : %g\n\n","M torque",tab[8])
    end
end
One must add a few explanations:

IV.2.8.4 Producing a ``clean'' model from topometric optimization

We provide here an example where modeling information is read from the XDB file. The modeling information corresponds to the last output of a topometric optimization. The purpose of the script is to produce a ``clean'' FEM corresponding to the topometric optimization. The example is given in ``SMALLEX/EX22/recoverTopometricModel.rb''.

The reading of PSHELL cards is done as follows:

pshellCards={}
db.each_xdbRaw(xdbFileName,["PSHELL",49],"iifififfffi") do |tab|
    card=[]
    card << "PSHELL"
    (0...10).each do |i|
        if (tab[i].class==Float&&tab[i].nan?) then
            card << ""
        else
            card << tab[i]
        end
    end
    if (tab[10]==0) then
        card << ""
    else
        card << tab[10]
    end
    pshellCards[tab[0]]=card
end
Remark that: In the rest of the script, one generates renumbered PSHELL cards. Each PSHELL PID will be the same as the element ID of the CQUAD4 that refers to the property. The CQUAD4 elements are modified accordingly. The new Nastran cards are output in ``newModel.bdf'' file, and a GMSH file corresponding to the modified thicknesses is output:
outputCards=[]
propRemap={}
elemIds=cquad4Cards.keys.sort
elemIds.each do |elemId|
    elemCard=cquad4Cards[elemId].clone
    propId=elemCard[2]
    if propId>1000000 then
        elemCard[2]=elemId
        propRemap[propId]=elemId
    end
    outputCards << elemCard[0..7]
end

propIds=pshellCards.keys.sort
res=Result.new
res.TensorOrder=0
res.Format=1
propIds.each do |propId|
    propCard=pshellCards[propId].clone
    if propRemap.has_key?(propId) then
        propCard[1]=propRemap[propId]
        res.insert([propCard[1]],["NONE",propCard[3]])
    end
    outputCards << propCard[0..8]
end
NastranDb::writeNastranCards("newModel.bdf","w","right","short",outputCards)
#~ Util::printRes($stdout,"brol",res)
db.writeGmsh("thicknesses.gmsh",0,[[res,"thickness","ElemCenters"]])
Note that the example could be improved. For example, you could also consider the CTRIA3 elements in the model generation.


IV.2.8.5 Reading and saving the temperature distributions

In order to calculate laminate load response from the distribution of shell forces and moments, it is sometimes also necessary to know the temperature distribution for each load case.

Example ``SMALLEX/EX22/recoverTEMP.rb'' reads the ``TEMP'' cards from the XDB files and stores them in an SQLite database. More precisely, function ``getXdbTemperature'':

Method ``saveResultsInSqlite'' is used to save the temperature fields into a SQLite database. The table is called ``element_temperatures'' and associates integer keys (the Nastran temperature load ID) to BLOB objects corresponding to the temperature fields (FeResPost Result object).

These temperature fields can be used in other post-processing scripts to estimate laminate load response analysis from finite element model results. The SQLite database is here called ``sqliteResults.db3''.

An example showing how the temperature fields stored in the SQLite database can be used is presented in section VI.4.4.3


next up previous contents index
Next: IV.3 A modular post-processing Up: IV. FeResPost Examples with Previous: IV.1 A small satellite   Contents   Index
FeResPost 2017-05-28