ANSYS in a Python Web App, Part 1: Post Processing with PyDPF | by Steve Kiefer | Towards Data Science

ANSYS recently announced it is supporting an open source project: PyAnsys. PyAnsys is split into multiple packages: PyMAPDL for interacting with an instance of an Ansys multiphysics simulation and equation solver and PyDPF-Core (& its simplified sibling PyDPF-Post) for post-processing Ansys results files. This is really intriguing to me as a structural analyst that uses ANSYS and Python pretty much daily. I have also used Plotly’s Dash framework to build fairly simple web-apps for post processing data and solving various equations or systems.
After playing around with PyANSYS in a Jupyter notebook for a while I decided to see if I could mash together a web-app that uses PyANSYS as a backend for a purpose-built analysis tool that a non-analyst could use and get insight (or at least build something that heads in that direction). PyANSYS is split into several packages. PyMAPDL is focused on communicating with a running MAPDL instance (local or remote) which pulls a license while running. PyDPF-Core and PyDPF-Post are focused on post-processing already-solved files (eg *.rst). These libraries do require Ansys to be installed.
In this article I’ll walk through using PyDPF, Dash & Dash-VTK to build a web app that loads and plots results from a few PyDPF examples. You can see the full files (notebook and Dash app) in this GitHub repository.
Plotly’s Dash open source framework is described as a framework that can be used to build a fully functioning web-application with only python. I really like it for building applications I expect other users (besides myself) will use.
While its grown a lot since its debut, you can read the 2017 announcement essay to get more context to what Dash is all about:
medium.com
Years ago I built a few VTK-based desktop applications for post-processing FEA results. So when I learned about Dash-VTK I was excited to try and port some of that into a web-app. I put together an example with a writeup you can check out here:
towardsdatascience.com
With Dash we are able to build an application with a simple and clean user interface and thanks to Dash-VTK view 3D mesh and associated results. If you are using Ansys to solve your analysis problems you would need to export the results from Ansys into some fixed (likely text) format so they could be read in by the web-app. You would have to export the model (mesh) as well as the results from Ansys and handle all the mapping. This could be fragile and would be limited to whatever results you (as the developer) would code for reading. This is where PyDPF comes in to help eliminate that export of model and mesh step and go right from a full results file to data processing and plotting.
The DPF in PyDPF stands for Data Processing Framework and (according to the PyAnsys Documentation) is dedicated to post-processing:
DPF is a workflow-based framework which allows simple and/or complex evaluations by chaining operators. The data in DPF is defined based on physics agnostic mathematical quantities described in a self-sufficient entity called field. This allows DPF to be a modular and easy to use tool with a large range of capabilities. It’s a product designed to handle large amount of data.
The PyAnsys documentation and examples highlight its use in Jupyter notebooks (including plotting). I explored the libraries using VS Code ‘Interactive window’ & ‘Python code files’ which are effectively a Jupyter notebook. Let’s see what it would look like in a notebook:
....and voila! Lots of info about the results file such as results sets, units, and mesh statistics.
DPF Model------------------------------DPF Result InfoAnalysis: staticPhysics Type: mecanicUnit system: MKS: m, kg, N, s, V, A, degCAvailable results:U Displacement :nodal displacementsENF Element nodal Forces :element nodal forcesENG_VOL Volume :element volumeENG_SE Energy-stiffness matrix :element energy associated with the stiffness matrixENG_AHO Hourglass Energy :artificial hourglass energyENG_TH thermal dissipation energy :thermal dissipation energyENG_KE Kinetic Energy :kinetic energyENG_CO co-energy :co-energy (magnetics)ENG_INC incremental energy :incremental energy (magnetics)BFE Temperature :element structural nodal temperatures------------------------------DPF  Meshed Region:3751 nodes3000 elementsUnit: mWith solid (3D) elements------------------------------DPF  Time/Freq Support:Number of sets: 1Cumulative     Time (s)       LoadStep       Substep1              1.000000       1              1
Now we go a little further and plot some results. In the script below we first setup a few variables for selecting the results type, component, and time step (lines 3–7). Then, using the available_results attribute of the model.metadata.result_info object we can get the result info objects for each result type in the results file (eg displacement, stress, etc). The result info object is not where the result data is actually stored. It is only a metadata object, but has all the information to retrieve the actual results using operators. First we create an operator using the operator_name attribute of the metadata object and connect it to the model.metadata.data_sources which associates the generic operator with our model object. We then use the time_scoping input to select the results set by index (converting from typical 0-based to 1-based in this case). If there is only 1 component in this results set then we get the fields container of the selected data set by calling the res_op.outputs.field_container() method. If there are more than 1 component then we create a new operator (component_selector_fc()) and chain it to the results operator by connecting the results operator outputs to the comp_sel operator on line 21 of the gist. We then add another input to select the component number (line 22) and request the fields container object by calling the .outputs.fields_container() method. There should only be 1 field object inside our field_container and we can access it as the first item in a list. finally we pass our filtered field to the plot method on our mesh object.
To change result type, component, or time step you just change the index parameters (res_idx, comp_idx, or tf_idx). If you check out the PyDPF examples you will notice there are shortcuts to plotting displacement, but we will use this more generic method later.
PyDPF is using VTK objects in the background to plot the results. Under the hood PyDPF converts the mesh object to a VTK unstructured grid and adds the arrays to that grid object before plotting. When we get to Dash_VTK we will also want the grid object so lets try and explicitly use it. Lets put it in a function!
In this function we pass in the meshed region (model.metadata.meshed_region) and the filtered down field object we want to plot. We check the location of the field (grids/nodes or elements/cells) and then map the field data order to the order of the nodes/cells in the vtk grid object. This is needed because the raw field data may have a different mapping/order than the order of the nodes/elements (so you cant just assign the field.data array directly to the grid object!). Once we get our grid with the results array assigned we can replace the mesh.plot(f0) line with grid.plot(scalars=name). The main point here is that we are plotting directly with the VTK object (which we will need for Dash_vtk)....
And now for the main course....
Ok we have a script that can filter down a results set to a specific field (by result type, time, and component) and we have a function that can extract a vtk grid object and assign that field to it with appropriate scoping. We now want to setup the Dash app so we can select one of the built in examples, and select the result type (by name) and time step (by time) and (if appropriate) select the component of the result. That sounds like 4x dcc.dropdown components. We will also use dash_bootstrap_components to make it look nice and add some callbacks that crawl through the selected ansys.dpf.core.examples example to collect the appropriate options for the dropdowns. we will put the actual plotting behind a “plot” button for stability.
Below is an excerpt of the layout with dropdowns and the plot button. I am using a global variable APP_ID mostly out of habit to distinguish id names in the case of a multi-page app. Several dropdowns are missing options as well as value parameters. We will populate these with callbacks.
Below are 2 callbacks that populate the result type, time step, and component dropdown options and default values. The first is triggered when the example is selected. This callback crawls through the available_results in the metadata.result_info object and creates the dropdown options using the name of the result type. We also create the timestep options using the float value as the label, but the integer index as the value parameter (remember using the index in the notebook example? We also send back default values (last timestep and first result type).
The second callback is fired when a result type is selected. We use the result name to find the appropriate result_info item (using next). Once we have that we determine the number of components and create the options, values, and if the dropdown should be disabled (for the case of 1 component).
Now for the callback that return the 3D object for Dash_VTK. This callback is fired when the ‘plot’ button is pressed. We get the model object using the example_dropdown and a global variable (dict) that maps the example names (simple_bar, msup_transient, and static) to their appropriate ansys.dpf.core.examples object. We get the result_info object as in the previous callback but now we get the meshed_region (mesh) object. The next part should look familiar from the notebook example except after we get the grid object with the field assigned, we then convert it to a mesh_state object that the Dash_VTK GeometryRepresentation component is expecting. we also set the colorscale range to the min/max of the field results. Not shown here is a function that creates a plotly figure with only a colorbar. While you can make a few edits to the dash_vtk source and rebuild the javascript (using node.js, and npm)to get a scalrabar to work with Dash_VTK (see here) I found it fairly simple to just create a plotly colorbar with the same background as Dash_VTK. See the full project for details.
You can see the full files (notebook and Dash app) in this GitHub repository. I also included a yml file for installing all required libraries via conda.
Note: I did notice some instability in the javascript when switching result types that have different quantity of components, eg from displacement (3 components)to stress (6 components) . I tried a few things, but digging into the javascript (react_VTK) is beyond my expertise. If you see an error you can refresh the page and start over. The first plot always works.
Good question.... I believe packaging pyAnsys into a web app is most powerful when the intended user is not a simulation expert. Otherwise you could just use the notebook interface, Workbench, or MAPDL. I can think of a couple examples where post-processing could be useful in a web-app:
A results file database where results files would be archived with some extra metadata and could be brought up to review very specific results (eg stress)Storing base ‘unit’ analyses that could be scaled / combined using superposition controlled by parameters provided in the web app
A results file database where results files would be archived with some extra metadata and could be brought up to review very specific results (eg stress)
Storing base ‘unit’ analyses that could be scaled / combined using superposition controlled by parameters provided in the web app
Well thanks for making it this far. I hope you found something in here useful. In a future article I will cover integrating the PyAnsys pyMAPDL library into a Dash app which may be more useful as the Dash app can act as a very restricted preprocessor. It will include solving and extracting some results as well.