From 1a6cfef29cd075277e33ee2c26289a071de21f6a Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 13 Nov 2025 11:12:59 +0100 Subject: [PATCH 01/16] doc(tuto): DPF collections tutorial --- .../tutorials/data_structures/collections.rst | 340 ++++++++++++++++++ .../tutorials/data_structures/index.rst | 8 +- 2 files changed, 342 insertions(+), 6 deletions(-) create mode 100644 doc/source/user_guide/tutorials/data_structures/collections.rst diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst new file mode 100644 index 00000000000..39e45375495 --- /dev/null +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -0,0 +1,340 @@ +.. _ref_tutorials_collections: + +=============== +DPF Collections +=============== + +.. include:: ../../links_and_refs.rst + +This tutorial shows how to create and work with some DPF collections: FieldsContainer, MeshesContainer and ScopingsContainer. + +DPF collections are homogeneous groups of labeled raw data storage structures that allow you to organize and manipulate related data efficiently. Collections are essential for handling multiple time steps, frequency sets, or other labeled datasets in your analysis workflows. + +:jupyter-download-script:`Download tutorial as Python script` +:jupyter-download-notebook:`Download tutorial as Jupyter notebook` + +Introduction to Collections +--------------------------- + +Collections in DPF serve as containers that group related objects with labels. The main collection types are: + +- |FieldsContainer|: A collection of |Field| objects, typically representing results over multiple time steps or frequency sets +- |MeshesContainer|: A collection of |MeshedRegion| objects for different configurations or time steps +- |ScopingsContainer|: A collection of |Scoping| objects for organizing entity selections + +Each collection provides methods to: + +- Add, retrieve, and iterate over contained objects +- Access objects by label (time, frequency, set ID, etc.) +- Perform operations across all contained objects + +Set up the Analysis +------------------- + +First, we import the required modules and load a transient analysis result file that contains multiple time steps. + +.. jupyter-execute:: + + # Import the ansys.dpf.core module + from ansys.dpf import core as dpf + + # Import the examples module + from ansys.dpf.core import examples + + # Load a transient analysis with multiple time steps + result_file_path = examples.find_msup_transient() + + # Create a DataSources object + data_sources = dpf.DataSources(result_path=result_file_path) + + # Create a Model from the data sources + model = dpf.Model(data_sources=data_sources) + + # Display basic model information + print(f"Number of time steps: {len(model.metadata.time_freq_support.time_frequencies)}") + print(f"Available results: {list(model.metadata.result_info.available_results.keys())}") + +Working with FieldsContainer +----------------------------- + +A |FieldsContainer| is the most commonly used collection in DPF. It stores multiple |Field| objects, each associated with a label such as time step or frequency. + +Extract Results into a FieldsContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Let's extract displacement results for all time steps, which will automatically create a |FieldsContainer|. + +.. jupyter-execute:: + + # Get displacement results for all time steps + displacement_fc = model.results.displacement.eval() + + # Display FieldsContainer information + print(f"Type: {type(displacement_fc)}") + print(f"Number of fields: {len(displacement_fc)}") + print(f"Labels: {displacement_fc.get_labels()}") + print(f"Available time sets: {list(displacement_fc.get_label_space(0).keys())}") + +Access Individual Fields in the Container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can access individual fields by their label or index. + +.. jupyter-execute:: + + # Access field by index (first time step) + first_field = displacement_fc[0] + print(f"First field info:") + print(f" Location: {first_field.location}") + print(f" Number of entities: {first_field.scoping.size}") + print(f" Components: {first_field.component_count}") + + # Access field by label (specific time step) + time_sets = list(displacement_fc.get_label_space(0).keys()) + if len(time_sets) > 1: + second_time_field = displacement_fc.get_field({"time": time_sets[1]}) + print(f"\nSecond time step field:") + print(f" Time set: {time_sets[1]}") + print(f" Max displacement magnitude: {max(second_time_field.data):.6f}") + +Create a Custom FieldsContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can create your own |FieldsContainer| and add fields with custom labels. + +.. jupyter-execute:: + + # Create an empty FieldsContainer + custom_fc = dpf.FieldsContainer() + + # Set up labels for the container + custom_fc.labels = ["time", "zone"] + + # Create sample fields for different time steps and zones + for time_step in [1, 2]: + for zone in [1, 2]: + # Create a simple field with sample data + field = dpf.Field(location=dpf.locations.nodal, nature=dpf.natures.scalar) + + # Add some sample nodes and data + field.scoping.ids = [1, 2, 3, 4, 5] + field.data = [float(time_step * zone * i) for i in range(1, 6)] + + # Add field to container with labels + custom_fc.add_field({"time": time_step, "zone": zone}, field) + + # Display the custom FieldsContainer + print(f"Custom FieldsContainer:") + print(f" Number of fields: {len(custom_fc)}") + print(f" Labels: {custom_fc.labels}") + print(f" Label space: {custom_fc.get_label_space()}") + +Working with ScopingsContainer +------------------------------ + +A |ScopingsContainer| holds multiple |Scoping| objects, which define sets of entity IDs (nodes, elements, etc.). + +Create and Populate a ScopingsContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Let's create different node selections and organize them in a |ScopingsContainer|. + +.. jupyter-execute:: + + # Get the mesh from our model + mesh = model.metadata.meshed_region + + # Create a ScopingsContainer + scopings_container = dpf.ScopingsContainer() + + # Set labels for different selections + scopings_container.labels = ["selection_type"] + + # Create different node selections + + # Selection 1: First 10 nodes + first_nodes = dpf.Scoping(location=dpf.locations.nodal) + first_nodes.ids = list(range(1, 11)) + scopings_container.add_scoping({"selection_type": "first_ten"}, first_nodes) + + # Selection 2: Every 10th node (sample) + all_node_ids = mesh.nodes.scoping.ids + every_tenth = dpf.Scoping(location=dpf.locations.nodal) + every_tenth.ids = all_node_ids[::10] # Every 10th node + scopings_container.add_scoping({"selection_type": "every_tenth"}, every_tenth) + + # Selection 3: Last 10 nodes + last_nodes = dpf.Scoping(location=dpf.locations.nodal) + last_nodes.ids = all_node_ids[-10:] + scopings_container.add_scoping({"selection_type": "last_ten"}, last_nodes) + + # Display ScopingsContainer information + print(f"ScopingsContainer:") + print(f" Number of scopings: {len(scopings_container)}") + print(f" Labels: {scopings_container.labels}") + + # Show details of each scoping + for i, scoping in enumerate(scopings_container): + label_space = scopings_container.get_label_space(i) + print(f" Scoping {i}: {label_space} - {scoping.size} entities") + +Use ScopingsContainer with Operators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +|ScopingsContainer| objects can be used with operators to apply operations to multiple selections. + +.. jupyter-execute:: + + # Create an operator to extract displacement on specific node sets + displacement_op = dpf.operators.result.displacement() + displacement_op.inputs.data_sources(data_sources) + displacement_op.inputs.mesh_scoping(scopings_container) + + # Evaluate to get results for all scopings + scoped_displacements = displacement_op.eval() + + print(f"Displacement results for different node selections:") + print(f" Result type: {type(scoped_displacements)}") + print(f" Number of result fields: {len(scoped_displacements)}") + + # Display information for each scoped result + for i, field in enumerate(scoped_displacements): + label_space = scoped_displacements.get_label_space(i) + max_displacement = max(field.data) if len(field.data) > 0 else 0 + print(f" Field {i}: {label_space} - {field.scoping.size} nodes, max displacement: {max_displacement:.6f}") + +Working with MeshesContainer +---------------------------- + +A |MeshesContainer| stores multiple |MeshedRegion| objects. This is useful when working with different mesh configurations or time-dependent meshes. + +Create a MeshesContainer +^^^^^^^^^^^^^^^^^^^^^^^^ + +Let's create a |MeshesContainer| with mesh data for different analysis configurations. + +.. jupyter-execute:: + + # Create a MeshesContainer + meshes_container = dpf.MeshesContainer() + + # Set labels for different mesh configurations + meshes_container.labels = ["configuration"] + + # Get the original mesh + original_mesh = model.metadata.meshed_region + + # Add original mesh + meshes_container.add_mesh({"configuration": "original"}, original_mesh) + + # Create a modified mesh (example: subset of elements) + # Get element scoping for first half of elements + all_element_ids = original_mesh.elements.scoping.ids + subset_element_ids = all_element_ids[:len(all_element_ids)//2] + + # Create element scoping for subset + element_scoping = dpf.Scoping(location=dpf.locations.elemental) + element_scoping.ids = subset_element_ids + + # Extract subset mesh using an operator + mesh_extract_op = dpf.operators.mesh.extract_skin() + mesh_extract_op.inputs.mesh(original_mesh) + mesh_extract_op.inputs.element_scoping(element_scoping) + subset_mesh = mesh_extract_op.eval() + + # Add subset mesh to container + meshes_container.add_mesh({"configuration": "subset"}, subset_mesh) + + # Display MeshesContainer information + print(f"MeshesContainer:") + print(f" Number of meshes: {len(meshes_container)}") + print(f" Labels: {meshes_container.labels}") + + # Show details of each mesh + for i, mesh in enumerate(meshes_container): + label_space = meshes_container.get_label_space(i) + print(f" Mesh {i}: {label_space}") + print(f" Nodes: {mesh.nodes.n_nodes}") + print(f" Elements: {mesh.elements.n_elements}") + +Collection Operations and Iteration +------------------------------------ + +Collections support various operations for data manipulation and analysis. + +Iterate Through Collections +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can iterate through collections using different methods. + +.. jupyter-execute:: + + # Iterate through FieldsContainer by index + print("Iterating through displacement fields by index:") + for i in range(min(3, len(displacement_fc))): # Show first 3 fields + field = displacement_fc[i] + label_space = displacement_fc.get_label_space(i) + max_value = max(field.data) if len(field.data) > 0 else 0 + print(f" Field {i}: {label_space}, max value: {max_value:.6f}") + + print("\nIterating through ScopingsContainer:") + for i, scoping in enumerate(scopings_container): + label_space = scopings_container.get_label_space(i) + print(f" Scoping {i}: {label_space}, size: {scoping.size}") + +Filter and Select from Collections +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can filter collections based on labels or criteria. + +.. jupyter-execute:: + + # Get specific fields from FieldsContainer by label criteria + if len(displacement_fc) >= 2: + # Get the second time step + time_sets = list(displacement_fc.get_label_space(0).keys()) + if len(time_sets) > 1: + specific_field = displacement_fc.get_field({"time": time_sets[1]}) + print(f"Retrieved field for time {time_sets[1]}:") + print(f" Components: {specific_field.component_count}") + print(f" Location: {specific_field.location}") + + # Get scoping by selection criteria + first_ten_scoping = scopings_container.get_scoping({"selection_type": "first_ten"}) + print(f"\nRetrieved 'first_ten' scoping:") + print(f" Size: {first_ten_scoping.size}") + print(f" First 5 IDs: {first_ten_scoping.ids[:5]}") + +Collection Summary and Best Practices +-------------------------------------- + +Let's summarize the key concepts and best practices for working with DPF collections. + +.. jupyter-execute:: + + print("DPF Collections Summary:") + print("=" * 50) + + print(f"\n1. FieldsContainer:") + print(f" - Purpose: Store multiple Field objects with labels") + print(f" - Common use: Results over time steps, frequencies, or load cases") + print(f" - Example size: {len(displacement_fc)} fields") + print(f" - Labels: {displacement_fc.get_labels()}") + + print(f"\n2. ScopingsContainer:") + print(f" - Purpose: Store multiple Scoping objects (entity selections)") + print(f" - Common use: Different node/element selections for analysis") + print(f" - Example size: {len(scopings_container)} scopings") + print(f" - Labels: {scopings_container.labels}") + + print(f"\n3. MeshesContainer:") + print(f" - Purpose: Store multiple MeshedRegion objects") + print(f" - Common use: Different mesh configurations or time-dependent meshes") + print(f" - Example size: {len(meshes_container)} meshes") + print(f" - Labels: {meshes_container.labels}") + + print(f"\nKey Benefits:") + print(f" - Efficient organization of related data") + print(f" - Label-based access for easy data retrieval") + print(f" - Integration with DPF operators for batch processing") + print(f" - Memory-efficient handling of large datasets") \ No newline at end of file diff --git a/doc/source/user_guide/tutorials/data_structures/index.rst b/doc/source/user_guide/tutorials/data_structures/index.rst index 6043628416d..34f67fbba08 100644 --- a/doc/source/user_guide/tutorials/data_structures/index.rst +++ b/doc/source/user_guide/tutorials/data_structures/index.rst @@ -29,20 +29,16 @@ These tutorials explains how these structures work and how you can manipulate da .. grid-item-card:: DPF collections - :link: ref_tutorials_language_and_usage + :link: ref_tutorials_collections :link-type: ref :text-align: center - :class-header: sd-bg-light sd-text-dark - :class-footer: sd-bg-light sd-text-dark This tutorial shows how to create and work with some DPF collections: FieldsContainer, MeshesContainer and ScopingsContainer - +++ - Coming soon - .. toctree:: :maxdepth: 2 :hidden: data_arrays.rst + collections.rst From e4de9ef25d49c14a47ba09db54ce165d55d5c727 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Thu, 13 Nov 2025 11:17:00 +0100 Subject: [PATCH 02/16] Update doc/source/user_guide/tutorials/data_structures/collections.rst --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index 39e45375495..d88e7faae08 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -36,7 +36,7 @@ First, we import the required modules and load a transient analysis result file .. jupyter-execute:: # Import the ansys.dpf.core module - from ansys.dpf import core as dpf + import ansys.dpf.core as dpf # Import the examples module from ansys.dpf.core import examples From d5d9d4580033ae01e4aec16a4646f4e9a68f9b6e Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Thu, 13 Nov 2025 14:51:47 +0100 Subject: [PATCH 03/16] Update doc/source/user_guide/tutorials/data_structures/collections.rst --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index d88e7faae08..762795df82b 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -52,7 +52,7 @@ First, we import the required modules and load a transient analysis result file # Display basic model information print(f"Number of time steps: {len(model.metadata.time_freq_support.time_frequencies)}") - print(f"Available results: {list(model.metadata.result_info.available_results.keys())}") + print(f"Available results: {model.metadata.result_info.available_results}") Working with FieldsContainer ----------------------------- From 20b2ca786fd5985c0d4d2e0415c0833911a20639 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 13 Nov 2025 16:26:09 +0100 Subject: [PATCH 04/16] Fixes --- .../tutorials/data_structures/collections.rst | 53 +++++++------------ 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index 762795df82b..cc56a267edf 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -72,7 +72,7 @@ Let's extract displacement results for all time steps, which will automatically # Display FieldsContainer information print(f"Type: {type(displacement_fc)}") print(f"Number of fields: {len(displacement_fc)}") - print(f"Labels: {displacement_fc.get_labels()}") + print(f"Labels: {displacement_fc.labels}") print(f"Available time sets: {list(displacement_fc.get_label_space(0).keys())}") Access Individual Fields in the Container @@ -146,28 +146,22 @@ Let's create different node selections and organize them in a |ScopingsContainer # Create a ScopingsContainer scopings_container = dpf.ScopingsContainer() - # Set labels for different selections scopings_container.labels = ["selection_type"] - - # Create different node selections - # Selection 1: First 10 nodes first_nodes = dpf.Scoping(location=dpf.locations.nodal) first_nodes.ids = list(range(1, 11)) - scopings_container.add_scoping({"selection_type": "first_ten"}, first_nodes) - + scopings_container.add_scoping(label_space={"selection_type": 0}, scoping=first_nodes) # Selection 2: Every 10th node (sample) all_node_ids = mesh.nodes.scoping.ids - every_tenth = dpf.Scoping(location=dpf.locations.nodal) + every_tenth = dpf.Scoping(location=dpf.locations.nodal) every_tenth.ids = all_node_ids[::10] # Every 10th node - scopings_container.add_scoping({"selection_type": "every_tenth"}, every_tenth) - + scopings_container.add_scoping(label_space={"selection_type": 1}, scoping=every_tenth) # Selection 3: Last 10 nodes last_nodes = dpf.Scoping(location=dpf.locations.nodal) last_nodes.ids = all_node_ids[-10:] - scopings_container.add_scoping({"selection_type": "last_ten"}, last_nodes) - + scopings_container.add_scoping(label_space={"selection_type": 2}, scoping=last_nodes) + # Display ScopingsContainer information print(f"ScopingsContainer:") print(f" Number of scopings: {len(scopings_container)}") @@ -217,45 +211,38 @@ Let's create a |MeshesContainer| with mesh data for different analysis configura # Create a MeshesContainer meshes_container = dpf.MeshesContainer() - + # Set labels for different mesh configurations - meshes_container.labels = ["configuration"] - + meshes_container.labels = ["variation"] + # Get the original mesh original_mesh = model.metadata.meshed_region - + # Add original mesh - meshes_container.add_mesh({"configuration": "original"}, original_mesh) - + meshes_container.add_mesh({"variation": 0}, original_mesh) + # Create a modified mesh (example: subset of elements) # Get element scoping for first half of elements all_element_ids = original_mesh.elements.scoping.ids subset_element_ids = all_element_ids[:len(all_element_ids)//2] - + # Create element scoping for subset element_scoping = dpf.Scoping(location=dpf.locations.elemental) element_scoping.ids = subset_element_ids - + # Extract subset mesh using an operator - mesh_extract_op = dpf.operators.mesh.extract_skin() + mesh_extract_op = dpf.operators.mesh.from_scoping() mesh_extract_op.inputs.mesh(original_mesh) - mesh_extract_op.inputs.element_scoping(element_scoping) + mesh_extract_op.inputs.scoping(element_scoping) subset_mesh = mesh_extract_op.eval() - + # Add subset mesh to container - meshes_container.add_mesh({"configuration": "subset"}, subset_mesh) - + meshes_container.add_mesh({"variation": 1}, subset_mesh) + # Display MeshesContainer information print(f"MeshesContainer:") print(f" Number of meshes: {len(meshes_container)}") print(f" Labels: {meshes_container.labels}") - - # Show details of each mesh - for i, mesh in enumerate(meshes_container): - label_space = meshes_container.get_label_space(i) - print(f" Mesh {i}: {label_space}") - print(f" Nodes: {mesh.nodes.n_nodes}") - print(f" Elements: {mesh.elements.n_elements}") Collection Operations and Iteration ------------------------------------ @@ -300,7 +287,7 @@ You can filter collections based on labels or criteria. print(f" Location: {specific_field.location}") # Get scoping by selection criteria - first_ten_scoping = scopings_container.get_scoping({"selection_type": "first_ten"}) + first_ten_scoping = scopings_container.get_scoping({"selection_type": 0}) print(f"\nRetrieved 'first_ten' scoping:") print(f" Size: {first_ten_scoping.size}") print(f" First 5 IDs: {first_ten_scoping.ids[:5]}") From cd4eac989eb50400dd4a5d1c06613689c5ced510 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 13 Nov 2025 16:28:17 +0100 Subject: [PATCH 05/16] Fixes --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index cc56a267edf..ae9ef1dc9a0 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -194,7 +194,7 @@ Use ScopingsContainer with Operators # Display information for each scoped result for i, field in enumerate(scoped_displacements): label_space = scoped_displacements.get_label_space(i) - max_displacement = max(field.data) if len(field.data) > 0 else 0 + max_displacement = field.data.max() print(f" Field {i}: {label_space} - {field.scoping.size} nodes, max displacement: {max_displacement:.6f}") Working with MeshesContainer From acc44d67e23fd653fe07ebf6d11ae8efb60a1b16 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 13 Nov 2025 19:28:53 +0100 Subject: [PATCH 06/16] Fixes --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index ae9ef1dc9a0..cf5475f79e2 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -306,7 +306,7 @@ Let's summarize the key concepts and best practices for working with DPF collect print(f" - Purpose: Store multiple Field objects with labels") print(f" - Common use: Results over time steps, frequencies, or load cases") print(f" - Example size: {len(displacement_fc)} fields") - print(f" - Labels: {displacement_fc.get_labels()}") + print(f" - Labels: {displacement_fc.labels()}") print(f"\n2. ScopingsContainer:") print(f" - Purpose: Store multiple Scoping objects (entity selections)") From aa8797e1a5ae31a052808c023660c60ef9399f0f Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Tue, 18 Nov 2025 11:30:03 +0100 Subject: [PATCH 07/16] Apply suggestions from code review --- .../tutorials/data_structures/collections.rst | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index cf5475f79e2..06db628a3aa 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -70,10 +70,7 @@ Let's extract displacement results for all time steps, which will automatically displacement_fc = model.results.displacement.eval() # Display FieldsContainer information - print(f"Type: {type(displacement_fc)}") - print(f"Number of fields: {len(displacement_fc)}") - print(f"Labels: {displacement_fc.labels}") - print(f"Available time sets: {list(displacement_fc.get_label_space(0).keys())}") + print(displacement_fc) Access Individual Fields in the Container ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -124,10 +121,7 @@ You can create your own |FieldsContainer| and add fields with custom labels. custom_fc.add_field({"time": time_step, "zone": zone}, field) # Display the custom FieldsContainer - print(f"Custom FieldsContainer:") - print(f" Number of fields: {len(custom_fc)}") - print(f" Labels: {custom_fc.labels}") - print(f" Label space: {custom_fc.get_label_space()}") + print(custom_fc) Working with ScopingsContainer ------------------------------ @@ -163,9 +157,7 @@ Let's create different node selections and organize them in a |ScopingsContainer scopings_container.add_scoping(label_space={"selection_type": 2}, scoping=last_nodes) # Display ScopingsContainer information - print(f"ScopingsContainer:") - print(f" Number of scopings: {len(scopings_container)}") - print(f" Labels: {scopings_container.labels}") + print(scopings_container) # Show details of each scoping for i, scoping in enumerate(scopings_container): @@ -240,9 +232,7 @@ Let's create a |MeshesContainer| with mesh data for different analysis configura meshes_container.add_mesh({"variation": 1}, subset_mesh) # Display MeshesContainer information - print(f"MeshesContainer:") - print(f" Number of meshes: {len(meshes_container)}") - print(f" Labels: {meshes_container.labels}") + print(meshes_container) Collection Operations and Iteration ------------------------------------ @@ -305,20 +295,14 @@ Let's summarize the key concepts and best practices for working with DPF collect print(f"\n1. FieldsContainer:") print(f" - Purpose: Store multiple Field objects with labels") print(f" - Common use: Results over time steps, frequencies, or load cases") - print(f" - Example size: {len(displacement_fc)} fields") - print(f" - Labels: {displacement_fc.labels()}") print(f"\n2. ScopingsContainer:") print(f" - Purpose: Store multiple Scoping objects (entity selections)") print(f" - Common use: Different node/element selections for analysis") - print(f" - Example size: {len(scopings_container)} scopings") - print(f" - Labels: {scopings_container.labels}") print(f"\n3. MeshesContainer:") print(f" - Purpose: Store multiple MeshedRegion objects") print(f" - Common use: Different mesh configurations or time-dependent meshes") - print(f" - Example size: {len(meshes_container)} meshes") - print(f" - Labels: {meshes_container.labels}") print(f"\nKey Benefits:") print(f" - Efficient organization of related data") From d0bd71daab412abc14110c5bbc102759e229776f Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:20:38 +0100 Subject: [PATCH 08/16] Apply suggestion from @PProfizi --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index 06db628a3aa..52fb537e2ec 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -251,7 +251,7 @@ You can iterate through collections using different methods. for i in range(min(3, len(displacement_fc))): # Show first 3 fields field = displacement_fc[i] label_space = displacement_fc.get_label_space(i) - max_value = max(field.data) if len(field.data) > 0 else 0 + max_value = max(field.data) print(f" Field {i}: {label_space}, max value: {max_value:.6f}") print("\nIterating through ScopingsContainer:") From 726718f11f00e2313306b66098d58f6ce9df9301 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Tue, 18 Nov 2025 15:01:08 +0100 Subject: [PATCH 09/16] Update doc/source/user_guide/tutorials/data_structures/collections.rst --- doc/source/user_guide/tutorials/data_structures/collections.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index 52fb537e2ec..b92f815eb37 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -251,7 +251,7 @@ You can iterate through collections using different methods. for i in range(min(3, len(displacement_fc))): # Show first 3 fields field = displacement_fc[i] label_space = displacement_fc.get_label_space(i) - max_value = max(field.data) + max_value = field.data.max() print(f" Field {i}: {label_space}, max value: {max_value:.6f}") print("\nIterating through ScopingsContainer:") From faba69478b8740a13d7822b29f42d846f2293afb Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:56:46 +0100 Subject: [PATCH 10/16] Apply suggestions from code review --- .../tutorials/data_structures/collections.rst | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index b92f815eb37..a9fac6e7c2b 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -51,8 +51,7 @@ First, we import the required modules and load a transient analysis result file model = dpf.Model(data_sources=data_sources) # Display basic model information - print(f"Number of time steps: {len(model.metadata.time_freq_support.time_frequencies)}") - print(f"Available results: {model.metadata.result_info.available_results}") + print(model) Working with FieldsContainer ----------------------------- @@ -67,7 +66,7 @@ Let's extract displacement results for all time steps, which will automatically .. jupyter-execute:: # Get displacement results for all time steps - displacement_fc = model.results.displacement.eval() + displacement_fc = model.results.displacement.on_all_time_freqs.eval() # Display FieldsContainer information print(displacement_fc) @@ -82,17 +81,15 @@ You can access individual fields by their label or index. # Access field by index (first time step) first_field = displacement_fc[0] print(f"First field info:") - print(f" Location: {first_field.location}") - print(f" Number of entities: {first_field.scoping.size}") - print(f" Components: {first_field.component_count}") + print(first_field) # Access field by label (specific time step) - time_sets = list(displacement_fc.get_label_space(0).keys()) - if len(time_sets) > 1: - second_time_field = displacement_fc.get_field({"time": time_sets[1]}) - print(f"\nSecond time step field:") - print(f" Time set: {time_sets[1]}") - print(f" Max displacement magnitude: {max(second_time_field.data):.6f}") + second_time_field = displacement_fc.get_field({"time": 2}) + # Equivalent to: + second_time_field = displacement_fc.get_field_by_time_id(2) + print(f"\nSecond time step field:") + print(second_time_field) + print(f" Max displacement magnitude: {max(second_time_field.data):.6f}") Create a Custom FieldsContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -158,11 +155,6 @@ Let's create different node selections and organize them in a |ScopingsContainer # Display ScopingsContainer information print(scopings_container) - - # Show details of each scoping - for i, scoping in enumerate(scopings_container): - label_space = scopings_container.get_label_space(i) - print(f" Scoping {i}: {label_space} - {scoping.size} entities") Use ScopingsContainer with Operators ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -180,14 +172,7 @@ Use ScopingsContainer with Operators scoped_displacements = displacement_op.eval() print(f"Displacement results for different node selections:") - print(f" Result type: {type(scoped_displacements)}") - print(f" Number of result fields: {len(scoped_displacements)}") - - # Display information for each scoped result - for i, field in enumerate(scoped_displacements): - label_space = scoped_displacements.get_label_space(i) - max_displacement = field.data.max() - print(f" Field {i}: {label_space} - {field.scoping.size} nodes, max displacement: {max_displacement:.6f}") + print(scoped_displacements) Working with MeshesContainer ---------------------------- @@ -267,14 +252,9 @@ You can filter collections based on labels or criteria. .. jupyter-execute:: # Get specific fields from FieldsContainer by label criteria - if len(displacement_fc) >= 2: - # Get the second time step - time_sets = list(displacement_fc.get_label_space(0).keys()) - if len(time_sets) > 1: - specific_field = displacement_fc.get_field({"time": time_sets[1]}) - print(f"Retrieved field for time {time_sets[1]}:") - print(f" Components: {specific_field.component_count}") - print(f" Location: {specific_field.location}") + # Get all fields of ``custom_fc`` where ``zone=1`` + zone_1_fields = custom_fc.get_fields({"zone": 1}) + print(zone_1_fields) # Get scoping by selection criteria first_ten_scoping = scopings_container.get_scoping({"selection_type": 0}) From 75d094014096fdea2f4aad9d22bde7ffb66fb1d4 Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:58:39 +0100 Subject: [PATCH 11/16] Apply suggestions from code review --- .../tutorials/data_structures/collections.rst | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index a9fac6e7c2b..d100caea658 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -255,37 +255,3 @@ You can filter collections based on labels or criteria. # Get all fields of ``custom_fc`` where ``zone=1`` zone_1_fields = custom_fc.get_fields({"zone": 1}) print(zone_1_fields) - - # Get scoping by selection criteria - first_ten_scoping = scopings_container.get_scoping({"selection_type": 0}) - print(f"\nRetrieved 'first_ten' scoping:") - print(f" Size: {first_ten_scoping.size}") - print(f" First 5 IDs: {first_ten_scoping.ids[:5]}") - -Collection Summary and Best Practices --------------------------------------- - -Let's summarize the key concepts and best practices for working with DPF collections. - -.. jupyter-execute:: - - print("DPF Collections Summary:") - print("=" * 50) - - print(f"\n1. FieldsContainer:") - print(f" - Purpose: Store multiple Field objects with labels") - print(f" - Common use: Results over time steps, frequencies, or load cases") - - print(f"\n2. ScopingsContainer:") - print(f" - Purpose: Store multiple Scoping objects (entity selections)") - print(f" - Common use: Different node/element selections for analysis") - - print(f"\n3. MeshesContainer:") - print(f" - Purpose: Store multiple MeshedRegion objects") - print(f" - Common use: Different mesh configurations or time-dependent meshes") - - print(f"\nKey Benefits:") - print(f" - Efficient organization of related data") - print(f" - Label-based access for easy data retrieval") - print(f" - Integration with DPF operators for batch processing") - print(f" - Memory-efficient handling of large datasets") \ No newline at end of file From 3d4c48bc9adc5e52ea7944605d4ec5232951b107 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Wed, 19 Nov 2025 15:09:40 +0100 Subject: [PATCH 12/16] Update with mention of the factory and built-in types --- .../tutorials/data_structures/collections.rst | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index d100caea658..d6ead31822b 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -255,3 +255,52 @@ You can filter collections based on labels or criteria. # Get all fields of ``custom_fc`` where ``zone=1`` zone_1_fields = custom_fc.get_fields({"zone": 1}) print(zone_1_fields) + +Other Built-in Collection Types +------------------------------ + +DPF provides several built-in collection types for common DPF objects, implemented in their respective modules: + +- :class:`ansys.dpf.core.fields_container.FieldsContainer` for fields +- :class:`ansys.dpf.core.meshes_container.MeshesContainer` for meshes +- :class:`ansys.dpf.core.scopings_container.ScopingsContainer` for scopings + +Additionally, the following specialized collection types are available (from ``collection_base.py``): + +- :class:`ansys.dpf.core.collection_base.IntegralCollection` for integral types +- :class:`ansys.dpf.core.collection_base.IntCollection` for integers +- :class:`ansys.dpf.core.collection_base.FloatCollection` for floats +- :class:`ansys.dpf.core.collection_base.StringCollection` for strings + +These built-in collections are optimized for their respective DPF types and should be used when working with fields, meshes, scopings, or basic types. For other supported types, you can use the :py:meth:`ansys.dpf.core.collection.Collection.collection_factory` method to create a custom collection class at runtime. + +Using the Collection Factory +--------------------------- + +.. note:: + Collections can only be made for types supported by DPF. Attempting to use unsupported or arbitrary Python types will result in an error. + +The :py:meth:`ansys.dpf.core.collection.Collection.collection_factory` method allows you to create a collection class for any supported DPF type at runtime. This is useful when you want to group and manage objects that are not covered by the built-in collection types (such as FieldsContainer, MeshesContainer, or ScopingsContainer). + +For example, you can create a collection for :class:`ansys.dpf.core.DataSources` objects: + +.. jupyter-execute:: + + from ansys.dpf.core import Collection, DataSources + from ansys.dpf.core import examples + + # Create a collection class for DataSources + DataSourcesCollection = Collection.collection_factory(DataSources) + ds_collection = DataSourcesCollection() + ds_collection.labels = ["case"] + + # Add DataSources objects to the collection + ds1 = DataSources("path/to/first/result/file.rst") + ds2 = DataSources("path/to/second/result/file.rst") + ds_collection.add_entry({"case": 0}, ds1) + ds_collection.add_entry({"case": 1}, ds2) + + # Show the collection + print(ds_collection) + +This approach allows you to leverage the powerful labeling and grouping features of DPF collections for any supported DPF object type, making your workflows more flexible and organized. From 77e241a09952e81bdd33be7a426b8321afe3687a Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:42:16 +0100 Subject: [PATCH 13/16] Update doc/source/user_guide/tutorials/data_structures/collections.rst --- doc/source/user_guide/tutorials/data_structures/collections.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index d6ead31822b..c2101af4700 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -89,7 +89,6 @@ You can access individual fields by their label or index. second_time_field = displacement_fc.get_field_by_time_id(2) print(f"\nSecond time step field:") print(second_time_field) - print(f" Max displacement magnitude: {max(second_time_field.data):.6f}") Create a Custom FieldsContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 41389ab82957af0e88f40fba32baf5d31e90edbe Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Wed, 19 Nov 2025 19:52:24 +0100 Subject: [PATCH 14/16] Update doc/source/user_guide/tutorials/data_structures/collections.rst --- .../user_guide/tutorials/data_structures/collections.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index c2101af4700..a38638a4c92 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -285,8 +285,9 @@ For example, you can create a collection for :class:`ansys.dpf.core.DataSources` .. jupyter-execute:: - from ansys.dpf.core import Collection, DataSources + from ansys.dpf.core import DataSources from ansys.dpf.core import examples + from ansys.dpf.core.collection import Collection # Create a collection class for DataSources DataSourcesCollection = Collection.collection_factory(DataSources) From 6aef712498aebf2b0635b3738bd71597b64f3209 Mon Sep 17 00:00:00 2001 From: PProfizi Date: Thu, 20 Nov 2025 10:00:32 +0100 Subject: [PATCH 15/16] Small improvements --- .../user_guide/tutorials/data_structures/collections.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index a38638a4c92..8e7e3e0c2d9 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -28,6 +28,9 @@ Each collection provides methods to: - Access objects by label (time, frequency, set ID, etc.) - Perform operations across all contained objects +Collections are widely used in DPF workflows to provide vectorized data to operators, +allowing to process the data in bulk or to process it in parallel whenever possible. + Set up the Analysis ------------------- @@ -253,7 +256,9 @@ You can filter collections based on labels or criteria. # Get specific fields from FieldsContainer by label criteria # Get all fields of ``custom_fc`` where ``zone=1`` zone_1_fields = custom_fc.get_fields({"zone": 1}) - print(zone_1_fields) + print(f"\nFields in custom_fc with zone=1:") + for field in zone_1_fields: + print(field) Other Built-in Collection Types ------------------------------ From 61d256542086c7ce8bbc714c8f0cd7097b0b828f Mon Sep 17 00:00:00 2001 From: Paul Profizi <100710998+PProfizi@users.noreply.github.com> Date: Thu, 20 Nov 2025 15:41:49 +0100 Subject: [PATCH 16/16] Apply suggestions from code review Co-authored-by: JennaPaikowsky <98607744+JennaPaikowsky@users.noreply.github.com> --- .../tutorials/data_structures/collections.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/source/user_guide/tutorials/data_structures/collections.rst b/doc/source/user_guide/tutorials/data_structures/collections.rst index 8e7e3e0c2d9..94cf2b6fd0e 100644 --- a/doc/source/user_guide/tutorials/data_structures/collections.rst +++ b/doc/source/user_guide/tutorials/data_structures/collections.rst @@ -6,7 +6,7 @@ DPF Collections .. include:: ../../links_and_refs.rst -This tutorial shows how to create and work with some DPF collections: FieldsContainer, MeshesContainer and ScopingsContainer. +This tutorial demonstrates how to create and work with some DPF collections: FieldsContainer, MeshesContainer and ScopingsContainer. DPF collections are homogeneous groups of labeled raw data storage structures that allow you to organize and manipulate related data efficiently. Collections are essential for handling multiple time steps, frequency sets, or other labeled datasets in your analysis workflows. @@ -25,16 +25,16 @@ Collections in DPF serve as containers that group related objects with labels. T Each collection provides methods to: - Add, retrieve, and iterate over contained objects -- Access objects by label (time, frequency, set ID, etc.) +- Access objects by label (time, frequency, set ID, and so on) - Perform operations across all contained objects Collections are widely used in DPF workflows to provide vectorized data to operators, -allowing to process the data in bulk or to process it in parallel whenever possible. +allowing you to process the data in bulk or to process it in parallel whenever possible. Set up the Analysis ------------------- -First, we import the required modules and load a transient analysis result file that contains multiple time steps. +First, import the required modules and load a transient analysis result file that contains multiple time steps. .. jupyter-execute:: @@ -64,7 +64,7 @@ A |FieldsContainer| is the most commonly used collection in DPF. It stores multi Extract Results into a FieldsContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Let's extract displacement results for all time steps, which will automatically create a |FieldsContainer|. +Extract displacement results for all time steps, which will automatically create a |FieldsContainer|. .. jupyter-execute:: @@ -130,7 +130,7 @@ A |ScopingsContainer| holds multiple |Scoping| objects, which define sets of ent Create and Populate a ScopingsContainer ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Let's create different node selections and organize them in a |ScopingsContainer|. +Create different node selections and organize them in a |ScopingsContainer|. .. jupyter-execute:: @@ -184,7 +184,7 @@ A |MeshesContainer| stores multiple |MeshedRegion| objects. This is useful when Create a MeshesContainer ^^^^^^^^^^^^^^^^^^^^^^^^ -Let's create a |MeshesContainer| with mesh data for different analysis configurations. +Create a |MeshesContainer| with mesh data for different analysis configurations. .. jupyter-execute::