I have a model built in Mechanical and I would like to use PyDPF to postprocess the contact reaction forces and get the same results as in Mechanical.
This post should be read in continuation of this first post:
How to postprocess a Mechanical model with PyDPF-Core? - Community Forum
Mechanical has several postprocessing options for the Force Reaction on contact regions:
This options determines which elements are selected for the extraction of the reaction forces, based on the contact and target sides:
We'll use a similar method in PyDPF to select the specific elements.
First, let's define a scoping for the contact elements of 'Contact ' region, as in the previous post:
ns_scoping_op = dpf.operators.scoping.on_named_selection() ns_scoping_op.inputs.requested_location.connect('Elemental') ns_scoping_op.inputs.named_selection_name.connect('CONTACT 1') ns_scoping_op.inputs.data_sources.connect(datasources) ns_scoping = ns_scoping_op.outputs.mesh_scoping() print(ns_scoping)
Then, to imitate the "Contact - Underlying Element" method, we have to select the underlying elements of this contact region.
We'll use the 'transpose' operator to do this.
transpose_op = dpf.operators.scoping.transpose() transpose_op.inputs.meshed_region.connect(model.metadata.meshed_region) transpose_op.inputs.mesh_scoping.connect(ns_scoping) nodal_scop = transpose_op.outputs.mesh_scoping_as_scoping() print(nodal_scop) transpose_op.inputs.mesh_scoping.connect(nodal_scop) ns_scoping_transposed = transpose_op.outputs.mesh_scoping_as_scoping() print(ns_scoping_transposed)
In 'ns_scoping', we have an elemental scoping. Through the first transpose operators, we get the nodes attached to those contact elements. Then in the second transpose operator, we get the elements attached to the nodes, hence, the underlying elements.
Once this is done, we can use the 'force_summation' operator to get the reaction force:
fsum_op = dpf.operators.averaging.force_summation() fsum_op.inputs.elemental_scoping.connect(ns_scoping_transposed) fsum_op.inputs.nodal_scoping.connect(nodal_scop) fsum_op.inputs.data_sources.connect(datasources) fsum_op.inputs.scoping_filter.connect(0) # 0: sum all nodal forces excluding contact elements, 1: sum all nodal forces for contact nodes only, 2: sum all nodal forces for all selected nodes including contact elements my_force_accumulation = fsum_op.outputs.force_accumulation() my_forces_on_nodes = fsum_op.outputs.forces_on_nodes() print(my_force_accumulation[0].data)
This matches the results we get in Mechanical:
The second method, using the target elements, is slightly more complicated. The contact and target elements share the same real number in case of an asymmetric contact. We'll use that property to select the target elements.
First, extract the APDL real id of the contact elements:
op = dpf.operators.metadata.mesh_property_provider() # operator instantiation op.inputs.mesh_scoping.connect(ns_scoping) op.inputs.data_sources.connect(model.metadata.data_sources) op.inputs.property_name.connect("apdl_real_id") my_property = op.outputs.property_as_property_field() print(my_property.data) apdl_id = int(my_property.data[0]) print(apdl_id)
In my example, apdl_id = 5.
Then, use this information to create a mesh scoping of all the elements that have this APDL id:
scoping_on_mesh_prop_op = dpf.operators.scoping.on_property() scoping_on_mesh_prop_op.inputs.property_name.connect("apdl_real_id") scoping_on_mesh_prop_op.inputs.property_id.connect(apdl_id) scoping_on_mesh_prop_op.inputs.data_sources.connect(model.metadata.data_sources) real_5 = scoping_on_mesh_prop_op.outputs.mesh_scoping() print(real_5)
Finally, use the 'intersect' operator to reselect only the target elements:
op = dpf.operators.scoping.intersect() # operator instantiation op.inputs.scopingA.connect(real_5) op.inputs.scopingB.connect(fs_scoping) my_intersection = op.outputs.intersection() target_scop = op.outputs.scopingA_min_intersection() print(target_scop)
Once this is done, use the 'transpose' operator to perform the same ESLN/NSLE logic that we did for the method on contact elements:
transpose_op = dpf.operators.scoping.transpose() transpose_op.inputs.meshed_region.connect(model.metadata.meshed_region) transpose_op.inputs.mesh_scoping.connect(target_scop) nodes_targe = transpose_op.outputs.mesh_scoping_as_scoping() print(nodes_targe)
transpose_op.inputs.mesh_scoping.connect(nodes_targe) elements = transpose_op.outputs.mesh_scoping_as_scoping() print(elements)
Finally, the force extraction can be done:
fsum_op = dpf.operators.averaging.force_summation() fsum_op.inputs.elemental_scoping.connect(elements) fsum_op.inputs.nodal_scoping.connect(nodes_targe) fsum_op.inputs.data_sources.connect(datasources) fsum_op.inputs.scoping_filter.connect(0) # 0: sum all nodal forces excluding contact elements, 1: sum all nodal forces for contact nodes only, 2: sum all nodal forces for all selected nodes including contact elements my_force_accumulation = fsum_op.outputs.force_accumulation() my_forces_on_nodes = fsum_op.outputs.forces_on_nodes() print(my_force_accumulation[0].data)