.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "tutorials\mapping\mapping_prepare_workflow.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_tutorials_mapping_mapping_prepare_workflow.py: .. _ref_tutorials_mapping_prepare_workflow: RBF-based workflow mapping =========================== Generate a reusable workflow for mapping results using Radial Basis Function (RBF) filters. This tutorial demonstrates how to use the :class:`prepare_mapping_workflow` operator to generate a reusable :class:`Workflow` that maps results between meshes with different topologies using RBF filters. Unlike shape-function interpolation (used by ``on_coordinates`` and ``on_reduced_coordinates``), RBF-based mapping can transfer data between non-conforming meshes where element boundaries do not align. It evaluates target values by weighting surrounding source nodes based on distance. The filter radius acts like a standard deviation in Gaussian weighting—small radii capture local gradients while larger radii produce smoother trends. The optional influence box limits the spatial search window as a computational optimization. The resulting workflow accepts a source field and returns the interpolated target field; it can be reused for multiple field types without repeating the setup step. .. GENERATED FROM PYTHON SOURCE LINES 50-53 Import modules and load the model ---------------------------------- Import the required modules and load a result file. .. GENERATED FROM PYTHON SOURCE LINES 53-63 .. code-block:: Python # Import the ``ansys.dpf.core`` module # Import Matplotlib for plotting import matplotlib.pyplot as plt from ansys.dpf import core as dpf # Import the examples and operators modules from ansys.dpf.core import examples, operators as ops .. GENERATED FROM PYTHON SOURCE LINES 64-68 Load model ---------- Download the crankshaft result file and create a :class:`Model` object. .. GENERATED FROM PYTHON SOURCE LINES 68-73 .. code-block:: Python result_file = examples.download_crankshaft() model = dpf.Model(data_sources=result_file) print(model) .. rst-class:: sphx-glr-script-out .. code-block:: none DPF Model ------------------------------ Static analysis Unit system: MKS: m, kg, N, s, V, A, degC Physics Type: Mechanical Available results: - node_orientations: Nodal Node Euler Angles - displacement: Nodal Displacement - velocity: Nodal Velocity - acceleration: Nodal Acceleration - reaction_force: Nodal Force - stress: ElementalNodal Stress - elemental_volume: Elemental Volume - stiffness_matrix_energy: Elemental Energy-stiffness matrix - artificial_hourglass_energy: Elemental Hourglass Energy - kinetic_energy: Elemental Kinetic Energy - co_energy: Elemental co-energy - incremental_energy: Elemental incremental energy - thermal_dissipation_energy: Elemental thermal dissipation energy - elastic_strain: ElementalNodal Strain - elastic_strain_eqv: ElementalNodal Strain eqv - element_orientations: ElementalNodal Element Euler Angles - structural_temperature: ElementalNodal Structural temperature ------------------------------ DPF Meshed Region: 69762 nodes 39315 elements Unit: m With solid (3D) elements ------------------------------ DPF Time/Freq Support: Number of sets: 3 Cumulative Time (s) LoadStep Substep 1 1.000000 1 1 2 2.000000 1 2 3 3.000000 1 3 .. GENERATED FROM PYTHON SOURCE LINES 74-79 Define the input support ------------------------ The input support is the mesh from which results will be mapped. Use :meth:`bounding_box` to inspect the spatial extent of the crankshaft before choosing a filter radius. .. GENERATED FROM PYTHON SOURCE LINES 79-88 .. code-block:: Python input_mesh = model.metadata.meshed_region bb_data = input_mesh.bounding_box.data[0] # [xmin, ymin, zmin, xmax, ymax, zmax] print( f"Bounding box: x=[{bb_data[0]:.4f}, {bb_data[3]:.4f}] " f"y=[{bb_data[1]:.4f}, {bb_data[4]:.4f}] " f"z=[{bb_data[2]:.4f}, {bb_data[5]:.4f}] m" ) .. rst-class:: sphx-glr-script-out .. code-block:: none Bounding box: x=[-0.0250, 0.0350] y=[-0.0250, 0.0250] z=[-0.0720, 0.0500] m .. GENERATED FROM PYTHON SOURCE LINES 89-103 Define the output support -------------------------- The output support is the target mesh onto which results will be mapped. To demonstrate RBF mapping between non-conforming meshes with genuinely different topologies, the output mesh is built in two steps: 1. Extract the external surface of the crankshaft using the ``skin`` operator. 2. Decimate that surface to ~30 % of its original faces using :class:`decimate_mesh`, which produces a coarser triangulated mesh. This gives a source (solid, hexahedral) / target (surface, triangulated) pair with entirely different topology — exactly the scenario RBF-based mapping is designed for. .. GENERATED FROM PYTHON SOURCE LINES 103-122 .. code-block:: Python skin_mesh = ops.mesh.skin(mesh=input_mesh).outputs.mesh() output_mesh = ops.mesh.decimate_mesh( mesh=skin_mesh, preservation_ratio=0.3, ).outputs.mesh() print( f"Source mesh: {input_mesh.nodes.n_nodes} nodes, {input_mesh.elements.n_elements} elements (solid)" ) print( f"Skin mesh: {skin_mesh.nodes.n_nodes} nodes, {skin_mesh.elements.n_elements} elements (surface)" ) print( f"Decimated target mesh:{output_mesh.nodes.n_nodes} nodes, {output_mesh.elements.n_elements} elements (triangles)" ) input_mesh.plot(title="Source mesh (crankshaft solid)") output_mesh.plot(title="Target mesh (decimated surface)") .. rst-class:: sphx-glr-horizontal * .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_001.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_001.png :class: sphx-glr-multi-img * .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_002.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_002.png :class: sphx-glr-multi-img .. rst-class:: sphx-glr-script-out .. code-block:: none Source mesh: 69762 nodes, 39315 elements (solid) Skin mesh: 32922 nodes, 16460 elements (surface) Decimated target mesh:2471 nodes, 4938 elements (triangles) ([], ) .. GENERATED FROM PYTHON SOURCE LINES 123-130 Prepare the mapping workflow ----------------------------- Call ``prepare_mapping_workflow`` to build an RBF-based :class:`Workflow` that maps fields from the input support to the output support. The filter radius controls the RBF smoothing scale. A value of 5 mm (0.005 m) is appropriate for the crankshaft which spans ~60 mm in its narrowest dimension. .. GENERATED FROM PYTHON SOURCE LINES 130-143 .. code-block:: Python filter_radius = 0.005 prepare_op = ops.mapping.prepare_mapping_workflow( input_support=input_mesh, output_support=output_mesh, filter_radius=filter_radius, ) mapping_workflow = prepare_op.eval() mapping_workflow.progress_bar = False print("Generated mapping workflow:") print(mapping_workflow) .. rst-class:: sphx-glr-script-out .. code-block:: none Generated mapping workflow: DPF Workflow: with 3 operator(s): apply_mapping, make_rbf_mapper, default_value the exposed input pins are: optional_target_support, source the exposed output pins are: target .. GENERATED FROM PYTHON SOURCE LINES 144-147 Examine the generated workflow -------------------------------- The workflow exposes a ``"source"`` input pin and a ``"target"`` output pin. .. GENERATED FROM PYTHON SOURCE LINES 147-152 .. code-block:: Python print("Workflow operator names:") for i, name in enumerate(mapping_workflow.operator_names): print(f" {i + 1}. {name}") .. rst-class:: sphx-glr-script-out .. code-block:: none Workflow operator names: 1. apply_mapping 2. make_rbf_mapper 3. default_value .. GENERATED FROM PYTHON SOURCE LINES 153-157 Map displacement using the workflow ------------------------------------- Connect a displacement field to the workflow ``"source"`` pin and retrieve the interpolated result from the ``"target"`` pin. .. GENERATED FROM PYTHON SOURCE LINES 157-178 .. code-block:: Python displacement_fc = model.results.displacement.eval() displacement_field = displacement_fc[0] input_mesh.plot(field_or_fields_container=displacement_fc, title="Source displacement (crankshaft)") input_pin_name = "source" output_pin_name = "target" mapping_workflow.connect(pin_name=input_pin_name, inpt=displacement_field) mapped_displacement_field = mapping_workflow.get_output( pin_name=output_pin_name, output_type=dpf.types.field, ) output_mesh.plot( field_or_fields_container=mapped_displacement_field, title="Mapped displacement on decimated surface", ) print(f"Source field: {len(displacement_field.data)} entities (solid nodes)") print(f"Mapped field: {len(mapped_displacement_field.data)} entities (surface nodes)") .. rst-class:: sphx-glr-horizontal * .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_003.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_003.png :class: sphx-glr-multi-img * .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_004.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_004.png :class: sphx-glr-multi-img .. rst-class:: sphx-glr-script-out .. code-block:: none Source field: 69762 entities (solid nodes) Mapped field: 2471 entities (surface nodes) .. GENERATED FROM PYTHON SOURCE LINES 179-183 Reuse the workflow for a different result type ----------------------------------------------- The same workflow can be reconnected to map another field type without rebuilding the RBF setup. .. GENERATED FROM PYTHON SOURCE LINES 183-196 .. code-block:: Python stress_fc = model.results.stress.on_location(dpf.locations.nodal).eval() stress_field = stress_fc[0] mapping_workflow.connect(pin_name=input_pin_name, inpt=stress_field) mapped_stress_field = mapping_workflow.get_output( pin_name=output_pin_name, output_type=dpf.types.field, ) output_mesh.plot( field_or_fields_container=mapped_stress_field, title="Mapped stress on decimated surface" ) .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_005.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_005.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none (None, ) .. GENERATED FROM PYTHON SOURCE LINES 197-201 Add the influence box parameter --------------------------------- The influence box further limits the RBF search window and can improve performance for sparse or asymmetric meshes. .. GENERATED FROM PYTHON SOURCE LINES 201-221 .. code-block:: Python influence_box = 0.01 prepare_op_with_box = ops.mapping.prepare_mapping_workflow( input_support=input_mesh, output_support=output_mesh, filter_radius=filter_radius, influence_box=influence_box, ) mapping_workflow_with_box = prepare_op_with_box.eval() mapping_workflow_with_box.progress_bar = False mapping_workflow_with_box.connect(pin_name=input_pin_name, inpt=displacement_field) mapped_disp_with_box = mapping_workflow_with_box.get_output( pin_name=output_pin_name, output_type=dpf.types.field ) output_mesh.plot( field_or_fields_container=mapped_disp_with_box, title="Mapped displacement with influence box (decimated surface)", ) .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_006.png :alt: mapping prepare workflow :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_006.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none (None, ) .. GENERATED FROM PYTHON SOURCE LINES 222-230 Effect of filter radius on mapping quality ------------------------------------------- A larger filter radius produces smoother interpolation but may lose fine details. Compare the displacement range across several radii. A reference value is first obtained by running the mapping without setting ``filter_radius``, so the operator uses its built-in default. The swept values are then plotted against this untuned baseline. .. GENERATED FROM PYTHON SOURCE LINES 230-293 .. code-block:: Python ref_prep_op = ops.mapping.prepare_mapping_workflow( input_support=input_mesh, output_support=output_mesh, ) ref_workflow: dpf.Workflow = ref_prep_op.eval() ref_workflow.progress_bar = False ref_workflow.connect(pin_name=input_pin_name, inpt=displacement_field) ref_result = ref_workflow.get_output(pin_name=output_pin_name, output_type=dpf.types.field) filter_radii = [0.001, 0.003, 0.006, 0.01, 0.02, 0.03, 0.04, 0.05] mean_mags = [] min_mags = [] max_mags = [] for radius in filter_radii: prep_op = ops.mapping.prepare_mapping_workflow( input_support=input_mesh, output_support=output_mesh, filter_radius=radius, ) workflow: dpf.Workflow = prep_op.eval() workflow.connect(pin_name=input_pin_name, inpt=displacement_field) workflow.progress_bar = False result = workflow.get_output(pin_name=output_pin_name, output_type=dpf.types.field) norm_field = ops.math.norm(field=result).outputs.field() min_max_op = ops.min_max.min_max(field=norm_field) min_mags.append(min_max_op.outputs.field_min().data[0]) max_mags.append(min_max_op.outputs.field_max().data[0]) mean_mags.append( ops.math.accumulate(fieldA=norm_field).outputs.field().data[0] / norm_field.scoping.size ) ref_norm_field = ops.math.norm(field=ref_result).outputs.field() ref_min_max_op = ops.min_max.min_max(field=ref_norm_field) ref_min_mag = ref_min_max_op.outputs.field_min().data[0] ref_max_mag = ref_min_max_op.outputs.field_max().data[0] reference_mean_mag = ( ops.math.accumulate(fieldA=ref_norm_field).outputs.field().data[0] / ref_norm_field.scoping.size ) print(f"Reference mean displacement magnitude (no filter_radius set): {reference_mean_mag:.4e} m") fig, ax = plt.subplots() ax.fill_between(filter_radii, min_mags, max_mags, alpha=0.2, label="Min-max range") ax.plot(filter_radii, mean_mags, "o-", label="Mean magnitude") ax.plot(filter_radii, min_mags, "v--", color="tab:blue", alpha=0.6, label="Min magnitude") ax.plot(filter_radii, max_mags, "^--", color="tab:blue", alpha=0.6, label="Max magnitude") ax.axhline( reference_mean_mag, color="gray", linestyle="-", label=f"Default - mean ({reference_mean_mag:.2e} m)", ) ax.axhline(ref_min_mag, color="gray", linestyle=":", label=f"Default - min ({ref_min_mag:.2e} m)") ax.axhline(ref_max_mag, color="gray", linestyle=":", label=f"Default - max ({ref_max_mag:.2e} m)") ax.set_xlabel("Filter radius (m)") ax.set_ylabel("Displacement magnitude (m)") ax.set_title( "Effect of filter radius on mapped displacement\n(narrowing min-max band = loss of fine detail)" ) ax.legend(fontsize="small") plt.tight_layout() plt.show() .. image-sg:: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_007.png :alt: Effect of filter radius on mapped displacement (narrowing min-max band = loss of fine detail) :srcset: /tutorials/mapping/images/sphx_glr_mapping_prepare_workflow_007.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none Reference mean displacement magnitude (no filter_radius set): 1.6280e-03 m .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 31.079 seconds) .. _sphx_glr_download_tutorials_mapping_mapping_prepare_workflow.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: mapping_prepare_workflow.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: mapping_prepare_workflow.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: mapping_prepare_workflow.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_