Experiences with Developing a “Somewhat Large” ACT Extension in ANSYS

With each release of ANSYS the customization toolkit continues to evolve and grow.  Recently I developed what I would categorize as a decent sized ACT extension.    My purpose in this post is to highlight a few of the techniques and best practices that I learned along the way.

Why I chose C#?

Most ACT extensions are written in Python.  Python is a wonderfully useful language for quickly prototyping and building applications, frankly of all shapes and sizes.  Its weaker type system, plethora of libraries, large ecosystem and native support directly within the ACT console make it a natural choice for most ACT work.  So, why choose to move to C#?

The primary reasons I chose to use C# instead of python for my ACT work were the following:

  1. I prefer the slightly stronger type safety afforded by the more strongly typed language. Having a definitive compilation step forces me to show my code first to a compiler.  Only if and when the compiler can generate an assembly for my source do I get to move to the next step of trying to run/debug.  Bugs caught at compile time are the cheapest and generally easiest bugs to fix.  And, by definition, they are the most likely to be fixed.  (You’re stuck until you do…)
  2. The C# development experience is deeply integrated into the Visual Studio developer tool. This affords not only a great editor in which to write the code, but more importantly perhaps the world’s best debugger to figure out when and how things went wrong.   While it is possible to both edit and debug python code in Visual Studio, the C# experience is vastly superior.

The Cost of Doing ACT Business in C#

Unfortunately, writing an ACT extension in C# does incur some development cost in terms setting up the development environment to support the work.  When writing an extension solely in Python you really only need a decent text editor.  Once you setup your ACT extension according to the documented directory structure protocol, you can just edit the python script files directly within that directory structure.  If you recall, ACT requires an XML file to define the extension and then a directory with the same name that contains all of the assets defining the extension like scripts, images, etc…  This “defines” the extension.

When it comes to laying out the requisite ACT extension directory structure on disk, C# complicates things a bit.  As mentioned earlier, C# involves a compilation step that produces a DLL.  This DLL must then somehow be loaded into Mechanical to be used within the extension.  To complicate things a little further, Visual Studio uses a predefined project directory structure that places the build products (DLLs, etc…) within specific directories of the project depending on what type of build you are performing.   Therefore the compiled DLL may end up in any number of different directories depending on how you decide to build the project.  Finally, I have found that the debugging experience within Visual Studio is best served by leaving the DLL located precisely wherever Visual Studio created it.

Here is a summary list of the requirements/problems I encountered when building an ACT extension using C#

  1. I need to somehow load the produced DLL into Mechanical so my extension can use it.
  2. The DLL that is produced during compilation may end up in any number of different directories on disk.
  3. An ACT Extension must conform to a predefined structural layout on the filesystem. This layout does not map cleanly to the Visual studio project layout.
  4. The debugging experience in Visual Studio is best served by leaving the produced DLL exactly where Visual Studio left it.

The solution that I came up with to solve these problems was twofold.

First, the issue of loading the proper DLL into Mechanical was solved by using a combination of environment variables on my development machine in conjunction with some Python programming within the ACT main python script.  Yes, even though the bulk of the extension is written in C#, there is still a python script to sort of boot-load the extension into Mechanical.  More on that below.

Second, I decided to completely rebuild the ACT extension directory structure on my local filesystem every time I built the project in C#.  To accomplish this, I created in visual studio what are known as post-build events that allow you to specify an action to occur automatically after the project is successfully built.  This action can be quite generic.  In my case, the “action” was to locally run a python script and provide it with a few arguments on the command line.  More on that below.

Loading the Proper DLL into Mechanical

As I mentioned above, even an ACT extension written in C# requires a bit of Python code to bootstrap it into Mechanical.  It is within this bit of Python that I chose to tackle the problem of deciding which dll to actually load.  The code I came up with looks like the following:

Essentially what I am doing above is querying for the presence of a particular environment variable that is on my machine.  (The assumption is that it wouldn’t randomly show up on end user’s machine…) If that variable is found and its value is 1, then I determine whether or not to load a debug or release version of the DLL depending on the type of build.  I use two additional environment variables to specify where the debug and release directories for my Visual Studio project exist.  Finally, if I determine that I’m running on a user’s machine, I simply look for the DLL in the proper location within the extension directory.  Setting up my python script in this way enables me to forget about having to edit it once I’m ready to share my extension with someone else.  It just works.

Rebuilding the ACT Extension Directory Structure

The final piece of the puzzle involves rebuilding the ACT extension directory structure upon the completion of a successful build.  I do this for a few different reasons.

  1. I always want to have a pristine copy of my extension laid out on disk in a manner that could be easily shared with others.
  2. I like to store all of the various extension assets, like images, XML files, python files, etc… within the Visual Studio Project. In this way, I can force the project to be out of date and in need of a rebuild if any of these files change.  I find this particularly useful for working with the XML definition file for the extension.
  3. Having all of these files within the Visual Studio Project makes tracking thing within a version control system like SVN or git much easier.

As I mentioned before, to accomplish this task I use a combination of local python scripting and post build events in Visual Studio.  I won’t show the entire python code, but essentially what it does is programmatically work through my local file system where the C# code is built and extract all of the files needed to form the ACT extension.  It then deletes any old extension files that might exist from a previous build and lays down a completely new ACT extension directory structure in the specified location.  The definition of the post build event is specified within the project settings in Visual Studio as follows:

As you can see, all I do is call out to the system python interpreter and pass it a script with some arguments.  Visual Studio provides a great number of predefined variables that you can use to build up the command line for your script.  So, for example, I pass in a string that specifies what type of build I am currently performing, either “Debug” or “Release”.  Other strings are passed in to represent directories, etc…

The Synergies of Using Both Approaches

Finally, I will conclude with a note on the synergies you can achieve by using both of the approaches mentioned above.  One of the final enhancements I made to my post build script was to allow it to “edit” some of the text based assets that are used to define the ACT extension.  A text based asset is something like an XML file or python script.  What I came to realize is that certain aspects of the XML file that define the extension need to be different depending upon whether or not I wish to debug the extension locally or release the extension for an end user to consume.  Since I didn’t want to have to remember to make those modifications before I “released” the extension for someone else to use, I decided to encode those modifications into my post build script.  If the post build script was run after a “debug” build, I coded it to configure the extension for optimal debugging on my local machine.  However, if I built a “release” version of the extension, the post build script would slightly alter the XML definition file and the main python file to make it more suitable for running on an end user machine.   By automating it in this way, I could easily build for either scenario and confidently know that the resulting extension would be optimally configured for the particular end use.

Conclusions

Now that I have some experience in writing ACT extensions in C# I must honestly say that I prefer it over Python.  Much of the “extra plumbing” that one must invest in in order to get a C# extension up and running can be automated using the techniques described within this post.  After the requisite automation is setup, the development process is really straightforward.  From that point onward, the increased debugging fidelity, added type safety and familiarity a C based language make the development experience that much better!  Also, there are some cool things you can do in C# that I’m not 100% sure you can accomplish in Python alone.  More on that in later posts!

If you have ideas for an ACT extension to better serve your business needs and would like to speak with someone who has developed some extensions, please drop us a line.  We’d be happy to help out however we can!

 

Reading ANSYS Mechanical Result Files (.RST) from C/C++ (Part 3)

ansys-fortran-to-c-cpp-1-00In the last post of this series I illustrated how I handled the nested call structure of the procedural interface to ANSYS’ BinLib routines.  If you recall, any time you need to extract some information from an ANSYS result file, you have to bracket the function call that extracts the information with a *Begin and *End set of function calls.  These two functions setup and tear down internal structures needed by the FORTRAN library.  I showed how I used RAII principles in C++ along with a stack data structure to manage this pairing implicitly.  However, I have yet to actually read anything truly useful off of the result file!  This post centers on the design of a set of C++ iterators that are responsible for actually reading data off of the file.  By taking the time to write iterators, we expose the ANSYS RST library to many of the algorithms available within the standard template library (STL), and we also make our own job of writing custom algorithms that consume result file data much easier.  So, I think the investment is worthwhile.

If you’ve programmed in C++ within the last 10 years, you’ve undoubtedly been exposed to the standard template library.  The design of the library is really rather profound.  This image represents the high level design of the library in a pictorial fashion:

ansys-fortran-to-c-cpp-3-01

On one hand, the library provides a set of generic container objects that provide a robust implementation of many of the classic data structures available within the field of computer science.  The collection of containers includes things like arbitrarily sized contiguous arrays (vectors), linked lists, associative arrays, which are implemented as either binary trees or as a hash container, as well as many more.  The set of containers alone make the STL quite valuable for most programmers.

On the other hand, the library provides a set of generic algorithms that encompass a whole suite of functionality defined in classic computer science.  Sorting, searching, rearranging, merging, etc… are just a handful of the algorithms provided by the library.  Furthermore, extreme care has been taken within the implementation of these algorithms such that an average programmer would hard pressed to produce something safer and more efficient on their own.

However, the real gem of the standard library are iterators.  Iterators bridge the gap between generic containers on one side and the generic algorithms on the other side.  Need to sort a vector of integers, or a double ended queue of strings?  If so, you just call the same sort function and pass it a set of iterators.  These iterators “know” how to traverse their parent container.  (Remember containers are the data structures.)

So, what if we could write a series of iterators to access data from within an ANSYS result file?  What would that buy us?  Well, depending upon which concepts our iterators model, having them available would open up access to at least some of the STL suite of algorithms.  That’s good.  Furthermore, having iterators defined would open up the possibility of providing range objects.  If we can provide range objects, then all of the sudden range based for loops are possible.  These types of loops are more than just syntactic sugar.  By encapsulating the bounds of iteration within a range, and by using iterators in general to perform the iteration, the burden of a correct implementation is placed on the iterators themselves.  If you spend the time to get the iterator implementation correct, then any loop you write after that using either the iterators or better yet the range object will implicitly be correct from a loop construct standpoint.  Range based for loops also make your code cleaner and easier to reason about locally.

Now for the downside…  Iterators are kind of hard to write.  The price for the flexibility they offer is paid for in the amount of code it takes to write them.  Again, though, the idea is that you (or, better yet somebody else) writes these iterators once and then you have them available to use from that point forward.

Because of their flexibility, standard conformant iterators come in a number of different flavors.  In fact, they are very much like an ice cream Sunday where you can pick and choose what features to mix in or add on top.  This is great, but again it makes implementing them a bit of a chore.  Here are some of the design decisions you have to answer when implementing an iterator:

Decision Options Choice for RST Reader Iterators
Dereference Data Type Anything you want Special structs for each type of iterator.
Iteration Category 1.       Forward iterator
2.       Single pass iterator
3.       Bidirectional iterator
4.       Random access iterator
Forward, Single Pass

Iterators syntactically function much like pointers in C or C++.  That is, like a pointer you can increment an iterator with the ++ operator, you can dereference an iterator with the * operator and you can compare two iterators for equality.  We will talk more about incrementing and comparisons in a minute, but first let’s focus on dereferencing.  One thing we have to decide is what type of data the client of our iterator will receive when they dereference it.  My choice is to return a simple C++ structure with data members for each piece of data.  For example, when we are iterating over the nodal geometry, the RST file contains the node number, the nodal coordinates and the nodal rotations.  To represent this data, I create a structure like this:ansys-fortran-to-c-cpp-3-02

I think this is pretty self-explanatory.  Likewise, if we are iterating over the element geometry section of an RST file, there is quite a bit of useful information for each element.  The structure I use in that case looks like this:

ansys-fortran-to-c-cpp-3-03

 

Again, pretty self-explanatory.  So, when I’m building a node geometry iterator, I’m going to choose the NodalCoordinateData structure as my dereference type.

The next decision I have to make is what “kind” of iterator I’m going to create.  That is, what types of “iteration” will it support?  The C++ standard supports a variety of iterator categories.  You may be wondering why anyone would ever care about an “iteration category”?  Well, the reason is fundamental to the design of the STL.   Remember that the primary reason iterators exist is to provide a bridge between generic containers and generic algorithms.  However, any one particular algorithm may impose certain requirements on the underlying iterator for the algorithm to function appropriately.

Take the algorithm “sort” for example.  There are, in fact, lots of different “sort” algorithms.  The most efficient versions of the “sort” algorithm require that an iterator be able to jump around randomly in constant time within the container.  If the iterator supports jumping around (a.k.a. random access) then you can use it within the most efficient sort algorithm.   However, there are certain kinds of iterators that don’t support jumping around.  Take a linked list container as an example.  You cannot randomly jump around in a linked list in constant time.  To get to item B from item A you have to follow the links, which means you have to jump from link to link to link, where each jump takes some amount of processing time.  This means, for example, there is no easy way to cut a linked list exactly in half even if you know how many items in total are in the list.  To cut it in half you have to start at the beginning and follow the links until you’ve followed size/2 number of links.  At that point you are at the “center” of the list.  However, with an array, you simply choose an index equal to size/2 and you automatically get to the center of the array in one step.  Many sort algorithms, as an example, obtain their efficiency by effectively chopping the container into two equal pieces and recursively sorting the two pieces.  You lose all that efficiency if you have to walk out to the center.

If you look at the “types” of iterators in the table above you will see that they build upon one another.  That is, at the lowest level, I have to answer the question, can I just move forward one step?  If I can’t even do that, then I’m not an iterator at all.  After that, assuming I can move forward one step, can I only go through the range once, or can I go over the range multiple times?  If I can only go over the range once, I’m a single pass iterator.  Truthfully, the forward iterator concept and the single pass iterator concept form level 1A and 1B of the iterator hierarchy.  The next higher level of functionality is a bidirectional iterator.  This type of iterator can go forward and backwards one step in constant time.  Think of a doubly linked list.  With forward and backward links, I can go either direction one step in constant time.  Finally, the most flexible iterator is the random access iterator.  These are iterators that really are like raw pointers.  With a pointer you can perform pointer arithmetic such that you can add an arbitrary offset to a base pointer and end up at some random location in a range.  It’s up to you to make sure that you stay within bounds.  Certain classes of iterators provide this level of functionality, namely those associated with vectors and deques.

So, the question is what type of iterator should we support?  Perusing through the FORTRAN code shipped with ANSYS, there doesn’t appear to be an inherent limitation within the functions themselves that would preclude random access.  But, my assumption is that the routines were designed to access the data sequentially.  (At least, if I were the author of the functions that is what I would expect clients to do.)  So, I don’t know how well they would be tested regarding jumping around.  Furthermore, disk controllers and disk subsystems are most likely going to buffer the data as it is read, and they very likely perform best if the data access is sequential.  So, even if it is possible to randomly jump around on the result file, I’m not sold on it being a good idea from a performance standpoint.  Lastly, I explicitly want to keep all of the data on the disk, and not buffer large chunks of it into RAM within my library.  So, I settled on expressing my iterators as single pass, forward iterators.  These are fairly restrictive in nature, but I think they will serve the purpose of reading data off of the file quite nicely.

Regarding my choice to not buffer the data, let me pause for a minute and explain why I want to keep the data on the disk. First, in order to buffer the data from disk into RAM you have to read the data off of the disk one time to fill the buffer.  So, that process automatically incurs one disk read.  Therefore, if you only ever need to iterate over the data once, perhaps to load it into a more specialized data structure, buffering it first into an intermediate RAM storage will actually slow you down, and consume more RAM.  The reason for this is that you would first iterate over the data stored on the disk and read it into an intermediate buffer.  Then, you would let your client know the data is ready and they would iterate over your internal buffer to access the data.  They might as well just read the data off the disk themselves! If the end user really wants to buffer the data, that’s totally fine.  They can choose to do that themselves, but they shouldn’t have to pay for it if they don’t need it.

Finally, we are ready to implement the iterators themselves.  To do this I rely on a very high quality open source library called Boost.  Boost has within it a library called iterator_facade that takes care of supplying most all of the boilerplate code needed to create a conformant iterator.  Using it has proven to be a real time saver.  To define the actual iterator, you derive your iterator class from iterator_facade and pass it a few template parameters.  One is the category defining the type of iterator you are creating.  Here is the definition for the nodal geometry iterator:

ansys-fortran-to-c-cpp-3-04

You can see that there are a few private functions here that actually do all of the work.  The function “increment” is responsible for moving the iterator forward one spot.  The function “equal” is responsible for determining if two different iterators are in fact equal.  And the function “dereference” is used to return the data associated with the current iteration spot.  You will also notice that I locally buffer the single piece of data associated with the current location in the iteration space inside the iterator.  This is stored in the pData member function.  I also locally store the current index.   Here are the implementations of the functions just mentioned:

ansys-fortran-to-c-cpp-3-05

First you can see that testing iterator equality is easy.  All we do is just look to see if the two iterators are pointing to the same index.  If they are, we define them as equal. (Note, an important nuance of this is that we don’t test to see if their buffered data is equal, just their index.  This is important later on.)  Likewise, increment is easy to understand as well.  We just increase the index by one, and then buffer the new data off of disk into our local storage.  Finally, dereference is easy too.  All we do here is just return a reference to the local data store that holds this one node’s data.  The only real work occurs in the readData() function.  Inside that function you will see the actual call to the CResRdNode() function.  We pass that function our current index and it populates an array of 6 doubles with data and returns the actual node number.  After we have that, we simply parse out of that array of 6 doubles the coordinates and rotations, storing them in our local storage.  That’s all there is to it.  A little more work, but not bad.

With these handful of operations, the boost iterator_facade class can actually build up a fully conformant iterator will all the proper operator overloads, etc… It just works.  Now that we have iterators, we need to provide a “begin” and “end” function just like the standard containers do.  These functions should return iterators that point to the beginning of our data set and to one past the end of our data set.  You may be thinking to yourself, wait a minute, how to we provide an “end” iterator without reading the whole set of nodes?  The reality is, we just need to provide an iterator that “equality tests” to be equal to the end of our iteration space?  What does that mean?  Well, what it means is that we just need to provide an iterator that, when compared to another iterator which has walked all the way to the end, it passes the “equal” test.  Look at the “equal” function above.  What do two iterators need to have in common to be considered equal?  They need to have the same index.  So, the “end” iterator simply has an index equal to one past the end of the iteration space.  We know how big our iteration space is because that is one of the pieces of metadata supplied by those ResRd*Begin functions.  So, here are our begin/end functions to give us a pair of conformant iterators.

ansys-fortran-to-c-cpp-3-06

Notice, that the nodes_end() function creates a NodeIterator and passes it an index that is one past the maximum number of nodes that have coordinate data stored on file.  You will also notice, that it does not have a second Boolean argument associated with it.  I use that second argument to determine if I should immediately read data off of the disk when the iterator is constructed.  For the begin iterator, I need to do that.  For the end iterator, I don’t actually need to cache any data.  In fact, no data for that node is defined on disk.  I just need a sentinel iterator that is one past the iteration space.

So, there you have it.  Iterators are defined that implicitly walk over the rst file pulling data off as needed and locally buffering one piece of it.  These iterators are standard conformant and thus can be used with any STL algorithm that accepts a single pass, read only, forward iterator.  They are efficient in time and storage.  There is, though, one last thing that would be nice.  That is to provide a range object so that we can have our cake and eat it too.  That is, so we can write these C++11 range based for loops.  Like this:ansys-fortran-to-c-cpp-3-07

To do that we need a bit of template magic.  Consider this template and type definition:

ansys-fortran-to-c-cpp-3-08

There is a bit of machinery that is going on here, but the concept is simple.  I just want the compiler to stamp out a new type that has a “begin()” and “end()” member function that actually call my “nodes_begin()” and “nodes_end()” functions.  That is what this template will do.  I can also create a type that will call my “elements_begin()” and “elements_end()” function.  Once I have those types, creating range objects suitable for C++11 range based for loops is a snap.  You just make a function like this:

ansys-fortran-to-c-cpp-3-09

 

This function creates one of these special range types and passes in a pointer to our RST reader.  When the compiler then sees this code:

ansys-fortran-to-c-cpp-3-10

It sees a range object as the return type of the “nodes()” function.  That range object is compatible with the semantics of range based for loops, and therefore the compiler happily generates code for this construction.

Now, after all of this work, the client of the RST reader library can open a result file, select something of interest, and start looping over the items in that category; all in three lines of code.  There is no need to understand the nuances of the binlib functions.  But best of all, there is no appreciable performance hit for this abstraction.  At the end of the day, we’re not computationally doing anything more than what a raw use of the binlib functions would perform.  But, we’re adding a great deal of type safety, and, in my opinion, readability to the code.  But, then again, I’m only slightly partial to C++.  Your mileage may vary…

Reading ANSYS Mechanical Result Files (.RST) from C/C++ (Part 2)

ansys-fortran-to-c-cpp-1-00In the last post in this series I illustrated how you can interface C code with FORTRAN code so that it is possible to use the ANSYS, Inc. BinLib routines to read data off of an ANSYS result file within a C or C++ program.  If you recall, the routines in BinLib are written in FORTRAN, and my solution was to use the interoperability features of the Intel FORTRAN compiler to create a shim library that sits between my C++ code and the BinLib code. In essence, I replicated all of the functions in the original BinLib library, but gave them a C interface. I call this library CBinLib.

You may remember from the last post that I wanted to add a more C++ friendly interface on top of the CBinLib functions.  In particular, I showed this piece of code, and alluded to an explanation of how I made this happen.  This post covers the first half of what it takes to make the code below possible.

ansys-fortran-to-c-cpp-2-01

What you see in the code above is the use of C++11 range based “for loops” to iterate over the nodes and elements stored on the result file.  To accomplish this we need to create conformant STL style iterators and ranges that iterate over some space.  I’ll describe the creation of those in a subsequent post.  In this post, however, we have to tackle a different problem.  The solution to the problem is hidden behind the “select” function call shown in the code above.  In order to provide some context for the problem, let me first show you the calling sequence for the procedural interface to BinLib.  This is how you would use BinLib if you were programming in FORTRAN or if you were directly using the CBinLib library described in the previous post.  Here is the recommended calling sequence:

ansys-fortran-to-c-cpp-2-02

You can see the design pattern clearly in this skeleton code.  You start by calling ResRdBegin, which gives you a bunch of useful data about the contents of the file in general.  Then, if you want to read geometric data, you need to call ResRdGeomBegin, which gives you a little more useful metadata.  After that, if you want to read the nodal coordinate data you call ResRdNodeBegin followed by a loop over nodes calling ResRdNode, which gives you the actual data about individual nodes, and then finally call ResRdNodeEnd.  If at that point you are done with reading geometric data, you then call ResRdGeomEnd.  And, if you are done with the file you call ResRdEnd.

Now, one thing jumps off the page immediately.  It looks like it is important to pair up the *Begin and*End calls.  In fact, if you peruse the ResRd.F FORTRAN file included with the ANSYS distribution you will see that in many of the *End functions, they release resources that were allocated and setup in the corresponding *Begin function.

So, if you forget to call the appropriate *End, you might leak resources.  And, if you forget to call the appropriate *Begin, things might not be setup properly for you to iterate.  Therefore, in either case, if you fail to call the right function, things are going to go badly for you.

This type of design pattern where you “construct” some scaffolding, do some work, and then “destruct” the scaffolding is ripe for abstracting away in a C++ type.  In fact, one of the design principles of C++ known as RAII (Resource Acquisition Is Initialization) maps directly to this problem.  Imagine that we create a class in which in the constructor of the class we call the corresponding *Begin function.  Likewise, in the destructor of the class we call the corresponding *End function.  Now, as long as we create an instance of the class before we begin iterating, and then hang onto that instance until we are done iterating, we will properly match up the *Begin, *End calls.  All we have to do is create classes for each of these function pairs and then create an instance of that class before we start iterating.  As long as that instance is still alive until we are finished iterating, we are good.

Ok, so abstracting the *Begin and *End function pairs away into classes is nice, but how does that actually help us?  You would still have to create an instance of the class, hold onto it while you are iterating, and then destroy it when you are done.  That sounds like more work than just calling the *Begin, *End directly.  Well, at first glance it is, but let’s see if we can use the paradigm more intelligently.  For the rest of this article, I’ll refer to these types of classes as BeginEnd classes, though I call them something different in the code.

First, what we really want is to fire and forget with these classes.  That is, we want to eliminate the need to manually manage the lifetime of these BeginEnd classes.  If I don’t accomplish this, then I’ve failed to reduce the complexity of the *Begin and *End requirements.  So, what I would like to do is to create the appropriate BeginEnd class when I’m ready to extract a certain type of data off of the file, and then later on have it magically delete itself (and thus call the appropriate *End function) at the right time.  Now, one more complication.  You will notice that these *Begin and*End function pairs are nested.  That is, I need to call ResRdGeomBegin before I call ResRdNodeBegin.  So, not only do I want a solution that allows me to fire and forget, but I want a solution that manages this nesting.

Whenever you see nesting, you should think of a stack data structure.  To increase the nesting level, you push an item onto the stack.  To decrease the nesting level, you pop and item off of the stack.  So, we’re going to maintain a stack of these BeginEnd classes.  As an added benefit, when we introduce a container into the design space, we’ve introduced something that will control object lifetime for us.  So, this stack is going to serve two functions for us.  It’s going to ensure we call the *Begin’s and *End’s in the right nested order, and second, it’s going to maintain the BeginEnd object lifetimes for us implicitly.

To show some code, here is the prototype for my pure virtual class that serves as a base class for all of the BeginEnd classes.  (In my code, I call these classes FileSection classes)

ansys-fortran-to-c-cpp-2-03

You can see that it is an interface class by noting the pure virtual function getLevel.  You will also notice that this function returns a ResultFileSectionLevel.  This is just an enum over file section types.  I like to use an enum as opposed to relying on RTTI.  Now, for each BeginEnd pair, I create an appropriate derived class from this base ResultFileSection class.  Within the destructor of each of the derived classes I call the appropriate *End function.  Finally, here is my stack data structure definition:

ansys-fortran-to-c-cpp-2-03p5

 

You can see that it is just a std::stack holding objects of the type SectionPtrT.  A SectionPtrT is a std::unique_ptr for objects convertible to my base section class.  This will enable the stack to hold polymorphic data, and the std::unique_ptr will manage the lifetime of the objects appropriately.   That is, when we pop and object off of the stack, the std::unique_ptr will make sure to call delete, which will call the destructor.  The destructor calls the appropriate *End function as we’ve mentioned before.

At this point, we’ve reduced our problem to managing items on a stack.  We’re getting closer to making our lives easier, trust me!  Let’s look at a couple of different functions to show how we pull these pieces together.  The first function is called reduceToLevelOrBegin(level).  See the code below:ansys-fortran-to-c-cpp-2-04

The operation of this function is fairly straightforward, yet it serves an integral role in our BeginEnd management infrastructure.   What this function does is it iteratively pops items off of our section stack until it either reaches the specified level, or it reaches the topmost ResRdBegin level.  Again, through the magic of C++ polymorphism, when an item gets popped off of the stack, eventually its destructor is called and that in turn calls the appropriate *End function.  So, what this function accomplishes is it puts us at a known level in the nested section hierarchy and, while doing so, ensures that any necessary *End functions are called appropriately to free resources on the FORTRAN side of things.  Notice that all of that happens automatically because of the type system in C++.  By popping items off of the stack, I implicitly clean up after myself.

The second function to consider is one of a family of similar functions.  We will look at the function that prepares the result file reader to read element geometry data off of the file.  Here it is:

ansys-fortran-to-c-cpp-2-05

You will notice that we start by reducing the nested level to either the “Geometry” level or the “Begin” level.  Effectively what this does is unwind any nesting we have done previously.  This is the machinery that makes “fire and forget” possible.  That is, whenever in ages past that we requested something to be read off of the result file, we would have pushed onto the stack a series of objects to represent the level needed to read the data in question.  Now that we wish to read something else, we unwind any previously existing nested Begin calls before doing so.   That is, we clean up after ourselves only when we ask to read a different set of data.  By waiting until we ask to read some new set of data to unwind the stack, we implicitly allow the lifetime of our BeginEnd classes to live beyond iteration.

At this point we have the stack in a known state; either it is at the Begin level or the Geometry level.  So, we simply call the appropriate *Begin functions depending on what level we are at, and push the appropriate type of BeginEnd objects onto the stack to record our traversal for later cleanup.  At this point, we are ready to begin iterating.  I’ll describe the process of creating iterators in the next post.  Clearly, there are lots of different select*** functions within my class.  I have chosen to make all of them private and have a single select function that takes an enum descriptor of what to select and some additional information for specifying result data.

One last thing to note with this design.  Closing the result file is easy. All that is required is that we simply fully unwind the stack.  That will ensure all of the appropriate FORTRAN cleanup code is called in the right order.  Here is the close function:

ansys-fortran-to-c-cpp-2-06

As you can see, cleanup is handled automatically.  So, to summarize, we use a stack of polymorphic data to manage the BeginEnd function calls that are necessary in the FORTRAN interface.  By doing this we ensure a level of safety in our class design.  Also, this moves us one step closer to this code:

ansys-fortran-to-c-cpp-2-07

In the next post I will show how I created iterators and range objects to enable clean for loops like the ones shown above.

Reading ANSYS Mechanical Result Files (.RST) from C/C++ (Part 1)

ansys-fortran-to-c-cpp-1-00Recently, I’ve encountered the need to read the contents of ANSYS Mechanical result files (e.g. file.rst, file.rth) into a C++ application that I am writing for a client. Obviously, these files are stored in a proprietary binary format owned by ANSYS, Inc.  Even if the format were published, it would be daunting to write a parser to handle it.  Fortunately, however, ANSYS supplies a series of routines that are stored in a library called BinLib which allow a programmer to access the contents of a result file in a procedural way.  That’s great!  But, the catch is the routines are written in FORTRAN… I’ve been programming for a long time now, and I’ll be honest, I can’t quite stomach FORTRAN.  Yes, the punch card days were before my time, but seriously, doesn’t a compiler have something better to do than gripe about what column I’m typing on… (Editor’s note: Matt does not understand the pure elegance of FORTRAN’s majestic simplicity. Any and all FORTRAN bashing is the personal opinion of Mr. Sutton and in no way reflects the opinion of PADT as a company or its owners. – EM) 

So, the problem shifts from how to read an ANSYS result file to how to interface between C/C++ and FORTRAN.  It turns out this is more complicated than it really should be, and that is almost exclusively because of the abomination known as CHARACTER(*) arrays.  Ah, FORTRAN… You see, if weren’t for the shoddy character of CHARACTER(*) arrays the mapping between the basic data types in FORTRAN and C would be virtually one for one. And thus, the mechanics of function calls could fairly easily be made to be identical between the two languages.   If the function call semantics were made identical, then sharing code between the two languages would be quite straightforward.  Alas, because a CHARACTER array has a kind of implicit length associated with it, the compiler has to do some kind of magic within any function signature that passes one or more of these arrays.  Some compilers hide parameters for the length and then tack them on to the end of the function call.  Some stuff the hidden parameters right after the CHARACTER array in the call sequence.  Some create a kind of structure that combines the length with the actual data into a special type. And then some compilers do who knows what…  The point is, there is no convention among FORTRAN compilers for handling the function call semantics, so there is no clean interoperability between C and FORTRAN.

Fortunately, the Intel FORTRAN compiler has created this markup language for FORTRAN that functions as an interoperability framework that enables FORTRAN to speak C and vice versa.  There are some limitations, however, which I won’t go into detail on here.  If you are interested you can read about it in the Intel FORTRAN compiler manual.  What I want to do is highlight an example of what this looks like and then describe how I used it to solve my problem.  First, an example:

ansys-fortran-to-c-cpp-1-01

What you see in this image is code for the first function you would call if you want to read an ANSYS result file.  There are a lot of arguments to this function, but in essence what you do is pass in the file name of the result file you wish to open (Fname), and if everything goes well, this function sends back a whole bunch of data about the contents of the file.  Now, this function represents code that I have written, but it is a mirror image of the ANSYS routine stored in the binlib library.

I’ve highlighted some aspects of the code that constitute part of the interoperability markup.  First of all, you’ll notice the markup BIND highlighted in red.  This markup for the FORTRAN function tells the compiler that I want it to generate code that can be called from C and I want the name of the C function to be “CResRdBegin”.  This is the first step towards making this function callable from C.  Next, you will see highlighted in blue something that looks like a comment.  This however, instructs the compiler to generate a stub in the exports library for this routine if you choose to compile it into a DLL.  You won’t get a .lib file when compiling this as a .dll without this attribute.  Finally, you see the ISO_C_BINDING and definition of the type of character data we can make interoperable.  That is, instead of a CHARACTER(261) data type, we use an array of single CHARACTER(1) data.  This more closely matches the layout of C, and allows the FORTRAN compiler to generate compatible code.  There is a catch here, though, and that is in the Title parameter.  ANSYS, Inc. defines this as an array of CHARACTER(80) data types.  Unfortunately, the interoperability stuff from Intel doesn’t support arrays of CHARACTER(*) data types.  So, we flatten it here into a single string.  More on that in a minute.

You will notice too, that there are markups like (c_int), etc… that the compiler uses to explicitly map the FORTRAN data type to a C data type.  This is just so that everything is explicitly called out, and there is no guesswork when it comes to calling the routine.  Now, consider this bit of code:

ansys-fortran-to-c-cpp-1-02

First, I direct your attention to the big red circle. Here you see that all I am doing is collecting up a bunch of arguments and passing them on to the ANSYS, Inc. routine stored in BinLib.lib.  You also should notice the naming convention.  My FORTRAN function is named CResRdBegin, whereas the ANSYS, Inc. function is named ResRdBegin.  I continue this pattern for all of the functions defined in the BinLib library.  So, this function is nothing more than a wrapper around the corresponding binlib routine, but it is annotated and constrained to be interoperable with the C programming language.  Once I compile this function with the FORTRAN compiler, the resulting code will be callable directly from C.

Now, there are a few more items that have to be straightened up.  I direct your attention to the black arrow.  Here, what I am doing is converting the passed in array of CHARACTER(1) data into a CHARACTER(*) data type.  This is because the ANSYS, Inc. version of this function expects that data type.  Also, the ANSYS, Inc. version needs to know the length of the file path string.  This is stored in the variable ncFname.  You can see that I compute this value using some intrinsics available within the compiler by searching for the C NULL character.  (Remember that all C strings are null terminated and the intent is to call this function from C and pass in a C string.)

Finally, after the call to the base function is made, the strings representing the JobName and Title must be converted back to a form compatible with C.  For the jobname, that is a fairly straightforward process.  The only thing to note is how I append the C_NULL_CHAR to the end of the string so that it is a properly terminated C string.

For the Title variable, I have to do something different.  Here I need to take the array of title strings and somehow represent that array as one string.  My choice is to delimit each title string with a newline character in the final output string.  So, there is a nested loop structure to build up the output string appropriately.

After all of this, we have a C function that we can call directly.  Here is a function prototype for this particular function.

ansys-fortran-to-c-cpp-1-03

So, with this technique in place, it’s just a matter of wrapping the remaining 50 functions in binlib appropriately!  Now, I was pleased with my return to the land of C, but I really wanted more.  The architecture of the binlib routines is quite easy to follow and very well laid out; however, it is very, very procedural for my tastes.  I’m writing my program in C++, so I would really like to hide as much of this procedural stuff as I can.   Let’s say I want to read the nodes and elements off of a result file.  Wouldn’t it be nice if my loops could look like this:

ansys-fortran-to-c-cpp-1-04

That is, I just do the following:

  1. Ask to open a file (First arrow)
  2. Tell the library I want to read nodal geometric data (Second arrow)
  3. Loop over all of the nodes on the RST file using C++11 range based for loops
  4. Repeat for elements

Isn’t that a lot cleaner?  What if we could do it without buffering data and have it compile down to something very close to the original FORTRAN code in size and speed?  Wouldn’t that be nice?  I’ll show you how I approached it in Part 2.

Programming a Simple Polygon Editor

polygon-editor-icon-1Part of my job at PADT is writing custom software for our various clients.  We focus primarily on developing technical software for the engineering community, with a particular emphasis on tools that integrate with the ANSYS suite of simulation tools.  Frankly, writing software is my favorite thing to do at PADT, simply because software development is all about problem solving.

This morning I got to work on a fairly simple feature of a much larger tool that I am currently developing.  The feature I’m working on involves graphically editing polygons.  Why, you ask am I doing this?  Well, that I can’t say, but nonetheless I can share a particularly interesting problem (to me at least) that I got to take a swing at solving.  The problem is this:

When a user is editing a node in the polygon by dragging it around on the screen, how do you handle the case when they drop it on an existing node?

Consider this polygon I sketched out in a prototype of the tool.

polygon-editor-f01

What should happen if the user drags this node over on top of that node:polygon-editor-f02

Well, I think the most logical thing to do is that you merge the two nodes together.  Implementing that is pretty easy.  The slightly harder question is what to do with the remaining structure of the polygon?  For my use case, polygons have to be manifold in that no vertex is connected to more than two edges. (The polygons can be open and thus have two end vertices connected to only one edge.)  So, what part do you delete?  Well, my solution is that you delete the “smaller” part, where “smaller” is defined as the part that has the fewest nodes.  So, for example, this is what my polygon looks like after the “drop”

polygon-editor-f03Conceptually, this sounds pretty simple, but how do you do it programmatically?  To give some background, note that the nodes in my polygon class are stored in a simple, ordered C++ std::list<>.

Now, I use a std::list<> simply because I know I’m going to be inserting and deleting nodes at random places.  Linked lists are great for that, and for rendering, I have to walk the whole list anyway, so there’s no performance hit there.  Graphically, my data structure looks
something like this:polygon-editor-f04Pretty simple.  For a closed polygon, my class maintains a flag and simply draws an edge from the last node to the first node.

The rub comes when you start to realize that there are tons of different ways a user might try to merge nodes together in either an open or closed polygon.  I’ve illustrated a few below along with what nodes would need to be merged in the corresponding data structure.  In the data structure pictures, the red node is the target (the node on which the user will be dropping) and the green node is the one they are manipulating (the source node).

Here is one example:

polygon-editor-f05polygon-editor-f06

Here is another example:

polygon-editor-f07
polygon-editor-f09b

Finally, here is one more:polygon-editor-f08polygon-editor-f09

In all these examples, we have different “cases” that we need to handle.  For instance, in the first example the portion of the data structure we want to keep is the stuff between the source and target nodes.  So, the stuff on the “ends” of the list needs to be deleted.  In the middle case, we just need to merge the source and target together.  Finally, in the last case, the nodes between the source and target need to be deleted, whereas the stuff at the “ends” of the list need to be kept.

This simple type of problem causes shivers in many programmers, and I’ll admit, I was nervous at first that this problem was going to lead to a solution that handled each individual case respectively.  Nothing in all of programming is more hideous than that.  So, there has to be a simple way to figure out what part of the list to keep, and what part of the list to throw away.

Now, I’m sure this problem has been solved numerous times before, but I wanted to take a shot at it without googling.  (I still haven’t googled, yet… so if this is similar to any other approach, they get the credit and I just reinvented the wheel…)  I remember a long time ago listening to a C++ programmer espouse the wonders of the standard library’s algorithm section.  I vaguely remember him droning on about how wonderful the std::rotate algorithm is.  At the time, I didn’t see what all the fuss was about.  Now, I’m right there with him.  std::rotate is pretty awesome!

std::rotate is a simple algorithm.  Essentially what it does is it takes the first element in a list, pops it off the list and moves it to the rear of the list.  Everything else in the list shifts up one spot.  This is called a left rotate, because you can imagine the items in the list rotating to the left until they get to the front of the line, at which point they fall off and are put back on the end of the list.  (Using reverse iterators you can effectively perform a right rotate as well.)  So, how can we take advantage of this to simplify figuring out what needs to be deleted from our list of nodes?

Well, the answer is remarkably simple.  Once we locate the source and target nodes in the list, regardless of their relative position with respect to one another or to the ends of the list, we simply left rotate the list until the target becomes the head of the list.  That is, if we start with this:polygon-editor-f10We left rotate until we have this:polygon-editor-f11That’s great, but what does that buy us?  Well, now that one of the participating nodes is at the head of the list, our problem is much simpler because all of the nodes that we need to delete are now at either end of the list.  The only question left to answer is which end of the list do we trim off?  The answer to that question is trivial.  We simply trim off the shorter end of the list with respect to the source node (the green node in the diagram). The “lengths” of the two lists are defined as follows.  For the head section, it’s the number of nodes up to, but not including the source. (This section obviously includes the target node)  For the tail, it’s the number of nodes from the source to the end, including the source.  (This section includes the source node).  Since we define the two sections this way we are guaranteed to delete either the source or the target, but not both.  Its fine to delete either one of them, because at this point we’ve deemed the geometrically coincident, but we must not accidentally delete both!!

In the example just given, after the rotate, we would delete the head of the list.  However, let’s take a look at our first example.  Here is the original list:

polygon-editor-f12Here is the rotated list:polygon-editor-f13So, in this case, the “end” of the list (including the source) is the shortest.  If it is a tie, then it doesn’t matter, just pick one.  Interestingly enough, if the two nodes are adjacent in the original list, then the rotated list will look like either this:polygon-editor-f14 Or this, if the source is “before” the target in the original list:polygon-editor-f15In either case, the algorithm works unchanged, and we only delete one node.  It’s beautiful! (At least in my opinion…)  Modern C++ makes this type of code really clean and easy to write.  Here is the entire thing, including the search to located geometrically adjacent nodes as well as the merge. The standard library algorithms really help out!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Search lambda function for looking for any other node in the list that is
// coindicent to this node, except this node.
auto searchAdjacentFun = [this, pNode](const NodeListTool::AdjustNodePtrT &amp;pOtherNode)-&gt;bool
{
if (pNode-&gt;tag() == pOtherNode-&gt;tag()) return false;
return (QVector2D(pNode-&gt;pos() - pOtherNode-&gt;pos()).length() &lt; m_snapTolerance); }; auto targetLoc = std::find_if(m_nodes.begin(), m_nodes.end(), searchAdjacentFun); // If we don't find an adjacent node within the tolerance, then we can't merge if (targetLoc == m_nodes.end()) { return false; } // Tidy things up so that the source has exactly the same position as the target pNode-&gt;setPos((*targetLoc)-&gt;pos());
 
// Begin the merge by left rotating the target so that it is at the
// beginning of the list
std::rotate(m_nodes.begin(), targetLoc, m_nodes.end());
 
// Find this node in the list
auto searchThis = [this, pNode](const NodeListTool::AdjustNodePtrT &amp;pOtherNode)-&gt;bool
{
return (pNode-&gt;tag() == pOtherNode-&gt;tag());
};
auto sourceLoc = std::find_if(m_nodes.begin(), m_nodes.end(), searchThis);
 
// Now, figure out which nodes we are going to delete.
auto distToBeg = std::distance(m_nodes.begin(), sourceLoc);
auto distToEnd = std::distance(sourceLoc, m_nodes.end());
 
if (distToBeg &lt; distToEnd) { // If our source is closer to the beginning (which is the target) // than it is to the end of the list, then we need to delete // the nodes at the front of the list m_nodes.erase(m_nodes.begin(), sourceLoc); } else { // Otherwise, delete the nodes at the end of the list m_nodes.erase(sourceLoc, m_nodes.end()); } // Now, see if we still have more than 2 vertices if (m_nodes.size() &gt; 2) {
m_bClosed = true;
}
else {
m_bClosed = false;
}
return true;