A Further Assessment of Design Assessment

Last weeks PADT ANSYS Webinar Series webinar was on a little used feature in ANSYS Mechanical called Design Assessment, or DA.  If you missed it, you can view a recording at:


And a PDF of the presentation can be found here:

As promised, this weeks The Focus posting will be a follow up on that webinar with an in-depth look at the scripts that were used in the example.  But first, let us review what DA is for those that don’t remember, fell asleep, or don’t want to sit through 60+ minutes of me mumbling into the telephone.

Review of DA

Design Assessment is a tool in ANSYS Workbench that works with ANSYS Mechanical to take results from multiple time or load steps, perform post processing on those results, and bring the calculated values back into ANSYS Mechanical for viewing as contour plots.  It was developed to allow the ANSYS, Inc. developers to add some special post processing tools needed in the off shore industry, but as they were working on it they saw the value of exposing the Application Programmers Interface (API) to the user community so anyone can write their own post processing tools.


You use it by adding a Design Assessment system to you project.  In its most basic form, the default configuration, it is set up to do load case combinations.  That in itself is worth knowing how to use it.  But if you want to do more, you can point it to a custom post processing set of files and do your own calculations.

A custom DA is defined by two things.  First is an XML file that tells ANSYS Mechanical what user input you want to capture, how you want to get results out of mechanical, what you want to do with the results, and how you want them displayed in your model tree.  Second is one or more Python scripts that actually do the work of capturing what the user input, getting results, doing the calculation, and sticking the resulting values back in the model.  Both are well documented and, once you get your head around the whole thing, pretty simple.

Right now DA works with Static and Transient Structural models.  It also only allows access to element stress values.  Lots of good enhancements are coming in R14, but R13 is mature enough to use now.

If that review was too quick, review the recording or the PowerPoint presentation.

A Deep Dive Into an Example

For the webinar we had a pretty simple, and a bit silly, example – the custom post processing tool took the results from a static stress model and truncates the stress values if they are above or below a user specified value.  Not a lot of calculating but a good example of how the tool works. 

Note, this posting is going to be long because there is a lot of code pasted in.  For each section of code I’ll also include a link to the original file so you can download that yourself to use.

Here is the XML file for the example (Original File):

   1:  <?xml version="1.0" encoding="utf-8"?>
The first lesson learned was that you have to get all the tags and headers just right. 
It is case sensitive and all the version and other stuff has to be there
Cutting and pasting from something that work is the best way to go
   2:  <!-- 
   3:  www.PADTINC.com 
   4:            XML file for ANSYS DA R13
   5:            Demonstration of how to use DA at R13
   6:            Goes through results and sets stresses below floor value to floor
   7:              value and above ceiling value to ceiling value.     
   9:          User adds DA Result to specify Floor and Ceiling
  10:          Attribute group can be used to specify a comment
  12:          Calls da_trunc_solve.py and da_trunc_eval.py in c:\temp
  14:          Eric Miller
  15:          5/18/2011
  16:  -->
Everything is in a DARoot tag.
  18:  <DARoot ObjId ="1" Type="CAERep" Ver="2">
Attributes tags define items you want to ask the user about.  
  19:    <Attributes ObjId="2" Type="CAERepBase" Ver="2">
This first attribute is a drop down for the user to decide which stress value they want 
You use <AttributeType> to make it a dropdown then put the values in <Validation>
  20:      <DAAttribute ObjId="101" Type="DAAttribute" Ver="2">
  21:        <AttributeName PropType="string">Stress Value</AttributeName>
  22:        <AttributeType PropType="string">DropDown</AttributeType>
  23:        <Application PropType="string">All</Application>
  24:        <Validation PropType="vector&amp;lt;string>">
  25:               SX,SY,SZ,SXY,SYZ,SXZ,S1,S2,S3,SEQV
  26:        </Validation>
  27:      </DAAttribute>
Next is the prompt for the stress floor value
  28:      <DAAttribute ObjId="102" Type="DAAttribute" Ver="2">
  29:        <AttributeName PropType="string">Stress Floor</AttributeName>
  30:        <AttributeType PropType="string">Double</AttributeType>
  31:        <Application PropType="string">All</Application>
  32:        <Validation PropType="vector&amp;lt;string>">-1000000,10000000</Validation>
  33:      </DAAttribute>
Then the Ceiling Value
  34:       <DAAttribute ObjId="103" Type="DAAttribute" Ver="2">
  35:        <AttributeName PropType="string">Stress Ceiling</AttributeName>
  36:        <AttributeType PropType="string">Double</AttributeType>
  37:        <Application PropType="string">All</Application>
  38:        <Validation PropType="vector&amp;lt;string>">-1000000,10000000</Validation>
  39:      </DAAttribute>
Finally a user comment, just to show how to do a string.
  40:      <DAAttribute ObjId="201" Type="DAAttribute" Ver="2">
  41:        <AttributeName PropType="string">User Comments</AttributeName>
  42:        <AttributeType PropType="string">Text</AttributeType>
  43:        <Application PropType="string">All</Application>
  44:      </DAAttribute>
  45:    </Attributes>
To expose an attribute, you can put it in an AttributeGroup to get info shared by
all DA result objects.  This one just does the Comment
  46:    <AttributeGroups ObjId ="3" Type="CAERepBase" Ver="2">
  47:      <DAAttributeGroup ObjId="100002" Type="DAAttributeGroup" Ver="2">
  48:        <GroupType PropType="string">User Comments</GroupType>
  49:        <GroupSubtype PropType="string">Failure Criteria</GroupSubtype>
  50:        <AttributeIDs PropType="vector&amp;lt;unsigned int>">201</AttributeIDs>
  51:      </DAAttributeGroup>
  52:    </AttributeGroups>
<DAScripts> is the most important part of the file.  It defines the scripts to run for a 
solve and for a evaluate.  At R13 you do need to specify the whole path to your python files
  53:    <DAScripts ObjId="4" Type="DAScripts" Ver="2">
  54:      <Solve PropType="string">c:\temp\da_trunc_solve.py</Solve>
  55:      <Evaluate PropType="string">c:\temp\da_trunc_eval.py</Evaluate>
  56:      <DAData PropType="int">1</DAData>
  57:      <CombResults PropType="int">1</CombResults>
  58:      <SelectionExtra PropType="vector&amp;lt;string>">DeltaMin, DeltaMax</SelectionExtra>
  59:    </DAScripts>
The other way to get user input is to put attributes into a <Results> object.  
Here we are putting the choice of stresses (101),and the floor and ceiling (102,103)
into an object
  60:    <Results ObjId="5" Type="CAERepBase" Ver="2">
  61:      <DAResult ObjId ="110000"  Type="DAResult" Ver="2">
  62:        <GroupType PropType="string">Ceiling and Floor Values</GroupType>
  63:        <AttributeIDs PropType="vector&amp;lt;unsigned int>">101,102,103</AttributeIDs>
  64:        <DisplayType PropType="string">ElemCont</DisplayType>
  65:      </DAResult>
  66:    </Results>
  67:  </DARoot>

I always have to go over XML files a few times to figure them out. There is a lot of information, but only a small amount that you need to pay attention to.  After a while you figure out which is which.

Now on to the fun part, the Python scripts.  The first one gets executed when they user chooses solve. 

da_trunc_solve.py is shown below and you can get the original here.   The comments inside pretty much explain it all.  It basically does two things:  creates an ANSYS MAPDL macro that extracts all the element stresses and puts them in a text file, then it runs MAPDL with that macro.

   1:  import subprocess
   2:  import os
   3:  import shutil
   5:  #======================================================================
   6:  #
   7:  #   ------------------------------------------------------------  PADT
   8:  #    www.PADTINC.com
   9:  #
  10:  #    da_trunc_solve.py
  11:  #         Demonstration python script for Design Assessment in 
  12:  #            ANSYS R13
  13:  #         Called on solve from ANSYS Mechanical
  14:  #         Bulk of code copied and modified from TsaiWu example
  15:  #            provided by ANSYS, Inc.
  16:  #
  17:  #       E. Miller
  18:  #       5/18/2011
  19:  #======================================================================
  20:  def trunc_solve(DesignAssessment) :
  22:      # Get number of elements in model
  25:      # Change directory to current workspace for DA
  26:      originaldir = os.getcwd()
  27:      os.chdir(DesignAssessment.getHelper().getResultPath())
  29:      #Get the path to the results file name and the location of the temp directory
  30:      rstFname = DesignAssessment.Selection(0).Solution(0).getResult().ResultFilePath()
  31:      rstFname = rstFname.rstrip('.rst')
  32:      apath = DesignAssessment.getHelper().getResultPath()
  34:      print "Result File:", rstFname
  35:      print "Apath:", apath
  37:      # Write an ANSYS APDL macro to start ANSYS, resume the result file, grab the stress
  38:      #   results and write them to a file
  40:      macfile = open(DesignAssessment.getHelper().getResultPath()+"\\runda1.inp", "w")
  42:      macfile.write("/batch\n")
  43:      macfile.write("/post1\n")
  44:      macfile.write("file,"+rstFname+"\n")
  45:      macfile.write("set,last\n")
  46:      macfile.write("*get,emx,elem,,num,max\n")
  47:      macfile.write("*dim,evls,,emx,10\n")
  48:      macfile.write("etable,esx,s,x\n")
  49:      macfile.write("etable,esy,s,y\n")
  50:      macfile.write("etable,esz,s,z\n")
  51:      macfile.write("etable,esxy,s,xy\n")
  52:      macfile.write("etable,esyz,s,yz\n")
  53:      macfile.write("etable,esxz,s,xz\n")
  54:      macfile.write("etable,es1,s,1\n")
  55:      macfile.write("etable,es2,s,2\n")
  56:      macfile.write("etable,es3,s,3\n")
  57:      macfile.write("etable,eseqv,s,eqv\n")
  58:      macfile.write("*vget,evls(1, 1),elem,1,etab,  esx\n")
  59:      macfile.write("*vget,evls(1, 2),elem,1,etab,  esy\n")
  60:      macfile.write("*vget,evls(1, 3),elem,1,etab,  esz\n")
  61:      macfile.write("*vget,evls(1, 4),elem,1,etab, esxy\n")
  62:      macfile.write("*vget,evls(1, 5),elem,1,etab, esyz\n")
  63:      macfile.write("*vget,evls(1, 6),elem,1,etab, esxz\n")
  64:      macfile.write("*vget,evls(1, 7),elem,1,etab,  es1\n")
  65:      macfile.write("*vget,evls(1, 8),elem,1,etab,  es2\n")
  66:      macfile.write("*vget,evls(1, 9),elem,1,etab,  es3\n")
  67:      macfile.write("*vget,evls(1,10),elem,1,etab,eseqv\n")
  69:      macfile.write("*cfopen,darsts,txt\n")
  70:      macfile.write("*vwrite,evls(1,1),evls(1,2),evls(1,3),evls(1,4),
  71:      macfile.write("(G16.9, X, G16.9, X, G16.9, X, G16.9, X, G16.9, X, 
G16.9, X, G16.9, X, G16.9, X, G16.9, X, G16.9)\n")
  72:      macfile.write("*cfclose\n")
  73:      macfile.write("finish\n")
  74:      macfile.write("/exit,nosave\n")
  76:      macfile.close()
  78:      # Set up execution of ANSYS MAPDL. 
  79:      #   Note: Right now you need to grab a different license than what Workbench is using
  80:      exelocation = "C:\\Program Files\\ANSYS Inc\\v130\\ansys\\bin\\winx64\\ansys130.exe"
  81:      commandlinestring = " -p ansys -b nolist -i runda1.inp -o runda1.out /minimise"
  83:      #Execute MAPDL and wait for it to finish
  84:      proc = subprocess.Popen(commandlinestring,shell=True,executable=exelocation)
  85:      rc = proc.wait()
  87:      # Read the output fromt the run and echo it to the DA log file
  88:      File = open("runda1.out","r")
  89:      DesignAssessment.getHelper().WriteToLog(File.read())
  90:      File.close()
  92:      # Go back to the original directory
  93:      os.chdir(originaldir)
  95:  trunc_solve(DesignAssessment

Some key things you should note about this script:

  • You have to use MAPDL to get your stress values. Right now there is no method in the API to get the values directly.
  • You need a second MAPDL license in order to run this script.  It does not share the license you are using for ANSYS Mechanical at R13.  This should be addressed in R14.
    • One work around right now is to use an APDL code snippet in the ANSYS mechanical run that makes the text file when the original problem is solved.  The SOLVE script is then no longer needed and you can just have an evaluate script.  Not a great solution but it will work if you only have once seat of ANSYS available.
  • Note the directory changes and getting of result file paths.  This is important. Mechanical does stuff all over the place and not in just one directory.
  • Make sure the MAPDL execution stuff is correct for your installation.

Once the solve is done and our text file, darsts.txt, is written, we can start truncating with the evaluate script (Original File).  This script is a little more sophisticated. First it simply reads the darsts.txt file into a python array.  It then has to go through a list of DA Results objects that the user added to their model and extract the stress value wanted as well as the floor and ceiling to truncate to. For each result object requested, it then loops through all the elements truncating as needed.  Then it stores the truncated values.

   1:  import subprocess
   2:  import os
   3:  import shutil
   4:  import sys
   6:  #======================================================================
   7:  #
   8:  #   ------------------------------------------------------------  PADT
   9:  #    www.PADTINC.com
  10:  #
  11:  #    da_trunc_eval.py
  12:  #         Demonstration python script for Design Assessment in 
  13:  #            ANSYS R13
  14:  #         Called on eval from ANSYS Mechanical
  15:  #         Bulk of code copied and modified from TsaiWu example
  16:  #            provided by ANSYS, Inc.
  17:  #
  18:  # NOTE: Right now it just does SX.  XML and script need to be modified to allow user
  19:  #       to specify component to use (X, Y, or Z)
  20:  #
  21:  #       E. Miller
  22:  #       5/18/2011
  23:  #======================================================================
  24:  def trunc_eval(DesignAssessment) :
  26:      # Change directory to current workspace for DA
  27:      originaldir = os.getcwd()
  28:      os.chdir(DesignAssessment.getHelper().getResultPath())
  30:      # Find number of elements in DA
  31:      Mesh = DesignAssessment.GeometryMeshData()
  32:      Elements = Mesh.Elements()
  33:      Ecount = len(Elements)
  34:      print "DA number of elements is ",Ecount
  35:      print "Number of Result Groups:",DesignAssessment.NoOfResultGroups()
  37:      # get User comment from Atribute Group
  38:      # Note: Assuems one.  Need to use a loop for multiple
  39:      attg = DesignAssessment.AttributeGroups()
  40:      atts = attg[0].Attributes()
  41:      usercomment = atts[0].Value().GetAsString()
  42:      print "User Comment = ", usercomment
  44:          # create arrays for SX/Y/Z values
  45:      sx = []
  46:      sy =  []
  47:      sz = []
  48:      sxy = []
  49:      syz =  []
  50:      sxz = []
  51:      s1 = []
  52:      s2 = []
  53:      s3 = []
  54:      seqv = []
  55:      # read file written during solve phase
  56:      #   append stress values to SX/Y/Z arrays
  57:      File = open("darsts.txt","r")
  58:      for line in File:
  59:          words = line.split()
  60:          sx.append(float(words[0]))
  61:          sy.append(float(words[1]))
  62:          sz.append(float(words[2]))
  63:          sxy.append(float(words[3]))
  64:          syz.append(float(words[4]))
  65:          sxz.append(float(words[5]))
  66:          s1.append(float(words[6]))
  67:          s2.append(float(words[7]))
  68:          s3.append(float(words[8]))
  69:          seqv.append(float(words[9]))
  70:      File.close()
  72:      # Loop over DA Results created by user
  73:      for ResultGroupIter in range(DesignAssessment.NoOfResultGroups()):
  74:          # Get the Result Group
  75:          ResultGroup = DesignAssessment.ResultGroup(ResultGroupIter)
  76:          # Extract Cieling and Floor for the Group
  77:          strscmp  = ResultGroup.Attribute(0).Value().GetAsString()
  78:          strFloor = float(ResultGroup.Attribute(1).Value().GetAsString())
  79:          strCeil  = float(ResultGroup.Attribute(2).Value().GetAsString())
  80:          print "DA Result", ResultGroupIter+1, ":", strFloor, strCeil
  82:          #Add a set of results to store values in
  83:          ResultStructure = ResultGroup.AddStepResult()
  85:          print "strscmp", strscmp
  87:          # Loop on elements 
  88:          for ElementIter in range(Ecount):
  89:              #Add a place to put the results for this element
  90:              ResultValue = ResultStructure.AddElementResultValue()
  91:              # Get the element number and then grab SX values that
  92:              #   was read from file
  93:              Element = Mesh.Element(ElementIter).ID()
  95:              if strscmp == "SX":
  96:                  sss = sx[Element-1]
  97:              elif strscmp == "SY":
  98:                  sss = sy[Element-1]
  99:              elif strscmp == "SZ":
 100:                  sss = sz[Element-1]
 101:              elif strscmp == "SXY":
 102:                  sss = sxy[Element-1]
 103:              elif strscmp == "SYZ":
 104:                  sss = syz[Element-1]
 105:              elif strscmp == "SXZ":
 106:                  sss = sxz[Element-1]
 107:              elif strscmp == "S1":
 108:                  sss = s1[Element-1]
 109:              elif strscmp == "S2":
 110:                  sss = s2[Element-1]
 111:              elif strscmp == "S3":
 112:                  sss = s3[Element-1]
 113:              elif strscmp == "SEQV":
 114:                  sss = seqv[Element-1]
 116:              # Compare to Ceiling and Floor and truncate if needed
 117:              if sss > strCeil:
 118:                  sss = strCeil
 119:              if sss < strFloor:
 120:                  sss = strFloor
 121:              # Store the stress value
 122:              ResultValue.setValue(sss)
 123:      # Go back to the original directory
 124:      os.chdir(originaldir)
 126:  trunc_eval(DesignAssessment)

Some things to note about this script are:

  • The same directory issues hold here.  Make sure you follow them
  • Always loop on ResultGroups.  You can assume the number of attributes is constant but you never know how many results the user has asked for.
  • In this example it is assumed that the stress label is the first attribute and that the floor and ceiling are the second and third.  This is probably lazy on my part and it should be more general.
    • The way to make it more general is to loop on the attributes in a group and grab their label, then use the label to determine which value it represents.
  • Before you can store values, you have to create the result object and then each result value in that structure:
    • ResultStructure = ResultGroup.AddStepResult() for each result object the user adds to the tree
    • ResultValue = ResultStructure.AddElementResultValue() for each element
    • ResultValue.setValue(sss) to set the actual value

And that is our example.  It should work with any model, just make sure you get the paths right for where the files are.

Be a Good Citizen and Share!

If you have the gumption to go and try this tool out, we do ask that you share what you come up with.  A good place is www.ANSYS.NET.  That is the repository for most things ANSYS.  If you have questions or need help, try xansys.org or your ANSYS support provider.

Happy Assessing!