Increasing Speed of DPF in Extensions

Nick R
Nick R Member Posts: 12
First Answer 5 Likes First Comment
**
edited June 2023 in Structures

I am looking into using DPF to drive the results in extensions instead of using the legacy results reader.

While DPF is much faster than the legacy reader for getting things like nodal von mises stress, I am finding that the extensions performance is still substantially worse than the hard coded von-mises available in workbench. For large models containing multiple results steps the code performs around 10x worse and can take hours to evaluate results.

I'm assuming this is because the workbench von mises code is written in C# and is optimized for performing this single task.

I figured I'd ask to see if there are any tricks to speeding up the use of DPF / python in extensions? Otherwise, it looks like I should rewrite my extension in C#.


Here is an example xml for an extension that gets the von mises nodal stresses:

<extension version="1" name="Mises_DPF_Performance_Testing">
  <guid shortid="DPF_Performance"></guid>
	<script src="main.py" />
	<interface context="Mechanical">
		<images>images</images>
		<toolbar name="Mises_Stress" caption="Mises DPF Performance Test">
			<entry name="Mises DPF Performance Test" icon="result">
				<callbacks>
					<onclick>Create_Mises_DPF_Performance_Test</onclick>
				</callbacks>
			</entry>
		</toolbar>
	</interface>
	<simdata context="Mechanical">
		<result name="Mises DPF Performance Test" version="1" caption="Mises DPF Performance Test" unit="Stress" icon="result" location="node" type="scalar">
			<callbacks>
				<onstarteval>SetStartTime</onstarteval>
				<onendeval>ReturnEndTime</onendeval>
				<evaluate>Evaluate_Stress</evaluate>
			</callbacks>
			<property name="Geometry" caption="Geometry" control="scoping"></property>
			<property name="RunTime" caption="Run Time" control="float" default="0.0" readonly="true"/>
		</result>
	</simdata>
</extension>


And here is an example of the main.py file contents:


import units
import math
import os
import time

def Create_Mises_DPF_Performance_Test(analysis):
		analysis.CreateResultObject("Mises DPF Performance Test", ExtAPI.ExtensionManager.CurrentExtension)

Start_Time = 0
global Start_Time

def SetStartTime(result, step):
	if step == 1:
		global Start_Time
		Start_Time = time.time()

def ReturnEndTime(result, step):
	global Start_Time
	Current_Time = time.time()
	result.Properties["RunTime"].Value = Current_Time - Start_Time

# Evaluate all nodes using collector
def Evaluate_Stress(object, stepInfo, collector):
		analysis = object.Analysis
		dpfc = DPF_Controller(ExtAPI, analysis, collector.Ids)
		step = stepInfo.Set
		
		stressValues = dpfc.Get_Nodal_Von_Mises_Stress(step, "psi")
		
		values = []
		for id in collector.Ids:
			values.append(stressValues[id])
			
		collector.SetAllValues(values)


class DPF_Controller:
	def __init__(self, api, analysis, scopingIds):
		self.api = api
		self.Import_DPF()
		self.analysis = analysis
		self.data_sources = self.GetDataSources(analysis)
		self.streams_container = self.mech_dpf.GetStreams(self.GetAnalysisIndex(analysis)) # Must get index of analysis
		self.node_scoping = self.GetNodeScoping(scopingIds)
		
		# Set up Time Scoping
		op = self.dpf.operators.metadata.time_freq_provider()
		op.inputs.data_sources.Connect(self.data_sources)
		time_freq_support = op.outputs.time_freq_support.GetData()
		self.time_scoping = time_freq_support.TimeFreqs.Scoping
		
	# Get Results
	def Get_Nodal_Von_Mises_Stress(self, step, unitString):
		self.time_scoping.Ids = [step]
		op = self.dpf.operators.result.stress_von_mises()
		op.inputs.time_scoping.Connect(self.time_scoping)
		op.inputs.mesh_scoping.Connect(self.node_scoping)
		op.inputs.data_sources.Connect(self.data_sources)
		op.inputs.streams_container.Connect(self.streams_container)
		von_mises_field_container = op.outputs.fields_container.GetData()
		unit_Converted_Field = self.ConvertFieldUnits(von_mises_field_container, unitString) # psi, Pa
		von_mises_stresses = unit_Converted_Field.GetFieldByTimeId(step).Data
		field_ids = unit_Converted_Field.GetFieldByTimeId(step).ScopingIds
		return dict(zip(field_ids, von_mises_stresses))
	
	# Generic Helpers
	def GetDataSources(self, analysis):
		working_directory = analysis.WorkingDir
		result_file = os.path.join(working_directory, "file.rst")
		data_sources = self.dpf.DataSources(result_file)
		return data_sources
		
	def GetNodeScoping(self, nodeIds):
		node_scoping = self.dpf.Scoping()
		node_scoping.Ids = nodeIds
		node_scoping.Location = "Nodal"
		return node_scoping
		
	def ConvertFieldUnits(self, originalField, convertToString):
		op = self.dpf.operators.math.unit_convert_fc()
		op.inputs.fields_container.Connect(originalField)
		op.inputs.unit_name.Connect(convertToString)
		converted_field_container = op.outputs.fields_container.GetData()
		return converted_field_container
	
	def GetAnalysisIndex(self, analysis):
		ii = 0
		for analysis in self.api.DataModel.AnalysisList:
			if analysis == self.analysis:
				return ii
			else:
				ii+=1
	
	def Import_DPF(self):
		import mech_dpf as mech_dpf
		import Ans.DataProcessing as dpf
		self.dpf = dpf
		self.mech_dpf = mech_dpf
		self.mech_dpf.setExtAPI(self.api)


Tagged:

Answers

  • Pernelle Marone-Hitz
    Pernelle Marone-Hitz Member, Moderator, Employee Posts: 871
    100 Answers 500 Comments 250 Likes First Anniversary
    ✭✭✭✭

    Hi @Nick R , as you mentionned, DPF was created with efficiency in mind. I think what takes time here is the extension itself, probably when setting the values into the collector. Have you tried using a Python Result object instead of creating a complete ACT extension? You would not have to bother with creating an .xml file, and passing the DPF results to the Mechanical Model is not handled through the ACT collector and will perform faster.

    Here is the link to the help page on Python Result object:

    https://ansyshelp.ansys.com/account/secured?returnurl=/Views/Secured/corp/v222/en/wb_sim/ds_python_result.html

  • Ayush Kumar
    Ayush Kumar Member, Moderator, Employee Posts: 467
    100 Answers 250 Likes 100 Comments Second Anniversary
    ✭✭✭✭
    edited January 2023

    Hi @Nick R, the issue should come from rearranging the DPF returned values in a standard Python Dictionary.

    dict(zip(field_ids, von_mises_stresses))
    

    The data from DPF is returned in DPF datatypes (similar to NumPy), rearranging them to standard Python datatypes and then looping over to fetch values slows down the code considerably. You can time the DPF data fetching part and the loop part (below) to compare:

    stressValues = dpfc.Get_Nodal_Von_Mises_Stress(step, "psi")
    values = []
    for id in collector.Ids:
        values.append(stressValues[id])
        collector.SetAllValues(values)
    

    As @Pernelle Marone-Hitz mentioned, if Python-Code is a feasible solution for you, that should help.

  • Nick R
    Nick R Member Posts: 12
    First Answer 5 Likes First Comment
    **

    The above suggestions were spot on for the collector being the culprit! I altered my code to perform a sort of the data on the nodes within dpf and return them directly and its much faster (dpf is definitely the way to go for gathering and manipulating results).

    One other thing that could be improved in my code above is to collect all of the data from the results file at once instead of changing the time scoping and calling into the results file multiple times. The less times it has to access the results file the faster the code runs.

    Bottom line: 1. Limit the calls of dpf to the results file. 2. Whenever possible perform data manipulation using dpf instead of using python loops or dictionaries 3. If an operation cannot be performed in dpf consider writing a compiled C# script for that operation.

    Thanks!

  • Philipp_92
    Philipp_92 Member Posts: 5
    First Anniversary Name Dropper First Comment
    **

    Hello @Nick R ,

    thank you for your post.
    Can you give me a hint on how you managed to sort the data within dpf and returned them directly?
    I am facing a similar problem, where i want to manipulate stress data within an ACT-Result with DPF. The manupulating is pretty fast. At some point, the moment arrives when the collector needs to be filled – data is then retrieved using the .GetData() command. At this juncture, the data loading process takes a considerable amount of time.

    Thank You!